* fix: dark-mode mermaid fills, Apple Silicon Docker build, fnm unzip, ROADMAP rows, site chrome translations
- site/lesson.html: mermaidPreprocess handles 3-digit hex fills so fill:#dfd stays legible in dark mode (#433)
- 07-docker-for-ai: pin FROM to linux/amd64 and document why the cu124 layer fails on Apple Silicon (#476)
- 01-dev-environment: add unzip to the apt line, fnm's installer needs it (#473)
- ROADMAP.md: add Phase 11 lessons 16 and 17, 521 rows to 523
- site: translate interface strings for the eight CI languages via ui-i18n.js + ui-strings.js, picker dispatches aifs:lang, drift guarded by test_ui_i18n.js in CI (#469, #404)
* docs: describe fnm's unzip check and scope the CUDA image to x86_64 hosts
- 01-dev-environment: the installer checks for unzip up front and exits with its own message; macOS goes through Homebrew
- 07-docker-for-ai: the linux/amd64 pin means the image is for x86_64 Linux hosts with an NVIDIA GPU
* feat(i18n): generate site chrome translations in the translate pipeline
The hand dictionary becomes site/ui-strings.json: an English key list plus
per-language overrides. scripts/translate_ui_strings.py fills every other key
through the lesson translator's provider layer, reuses what is already
published, protects file names, paths, and commands behind the same
placeholders, and writes i18n/<lang>/ui.json. A new ui-strings job in
translate.yml runs it per CI language and publishes to the translations
branch with the same race-safe worktree loop, so a new label or a new
language needs no translation written by hand.
site/ui-i18n.js now fetches i18n/<lang>/ui.json from the translations branch
at runtime, caches it per language, and falls back to English until a language
is published. Tests cover source consistency, precedence, placeholder
protection, the page drift guard, and the fetch path.
* fix(i18n): retranslate keys whose override was removed and keep failed dictionary loads retryable
- ui.json now carries {strings, pinned}; a key that was pinned in the last publication but has no override now is translated again instead of reusing the old pin
- the runtime no longer caches a failed fetch, so a language published later is picked up on the next switch without a reload
* fix(i18n): refresh flat ui.json values once when pin provenance is missing
A flat published file predates the {strings, pinned} shape and cannot say
which values came from overrides, so every value in it counts as pinned and
is retranslated on the first run instead of being reused blindly.
* fix(i18n): shard translation CI by phase, commit README translations, limit picker to supported languages
The translate workflow ran one job per language, but a full 503-lesson language
run is ~27h on a CPU runner. Every job hit the 5.5h limit and was killed before
its publish step, so it banked nothing and the translations branch never got
created: it would retry and die forever.
CI sharding
- Matrix is now one job per (language, phase). The largest phase (19, 85
lessons) is ~4.5h, comfortably under the limit; most are 1-1.6h. max-parallel
raised to 20.
- Cache is per-(language, phase) (i18n/<lang>/.cache/<phase>.json) and each job
publishes only its own i18n/<lang>/phases/<phase>/ slice, so disjoint shards
merge without clobbering. translate_lessons.py gains --phase __root__ for the
README and a per-phase cache path.
README translations (committed to main, not CI)
- scripts/build_readme_i18n.py rebuilds i18n/<lang>/README.md by replacing only
the translated line-spans in a copy of the English README; the banner, badges,
584-row lesson table, and every link are preserved byte-for-byte (round-trip
identity asserted every run). Root-relative links are rewritten to resolve two
levels deep.
- scripts/readme_translations.py holds hand-authored translations (highest
quality for a landing page) for 12 languages: es, fr, pt, de, it, zh, ja, ko,
hi, ar, ru, tr. Unmapped blocks fall back to English.
- README language bar points at the committed files and lists all 12. i18n/
README.md is un-ignored; lesson artifacts stay ignored.
- CI guard: build_readme_i18n.py --check fails on drift; the counts bot
regenerates them when it syncs the English stats block.
Language picker
- build.js emits only source + ci:true languages into langs.js, so the site
switcher offers only languages the site can actually serve (English + the 5
ci languages) instead of all 40 registry entries. Cache-bust bumped.
Fixes the timed-out run in the translate workflow.
* feat(learning): AI-native learning flow, learn-first homepage, evidence-backed learner wall
The course gains a terminal-first learning experience driven by any coding
agent, and the homepage leads with it.
Learning skills (skills/ canonical, .claude/skills/ mirror for cloners)
- start-learning: one-time onboarding. Three-question interview, placement
quiz, writes LEARNING.md (mission, entry phase, 20-phase path, progress log,
review queue) that every later session reads and updates.
- learn: the tutor loop. Warm-up recall from the previous lesson's quiz, then
the next lesson taught interactively (problem, concept, build, use), then the
post-stage quiz, then progress recorded. Works cloned or entirely over
raw.githubusercontent.com; no setup required.
- course-guide: topic router over the README contents. A topic, question, or
bug in; the exact lessons plus the right next command out.
- find-your-level: 503-lesson count, agent-neutral question flow, raw-fetch
fallback for ROADMAP.md, hand-off to start-learning/learn.
- check-understanding: no-clone fallback fetching lesson docs from raw.
- npx skills add rohitg00/ai-engineering-from-scratch installs exactly these
five for any SKILL.md agent (verified against this tree: 5 found, no dupes,
none of the 388 lesson artifacts). CI guard: skills/ and .claude/skills/
must stay identical.
README
- "Start learning in 30 seconds" section up top; Getting Started reordered
with terminal learning as Option A; skills table covers all five; the
toolkit section no longer claims npx installs the 388 outputs (it never
did; they install via scripts/install_skills.py).
Homepage
- Masthead install card: the two-command flow with real agent marks and a
copy chip (icon, hover invert, press scale, copied state, clipboard
fallback). Colophon "cp" button upgraded to the same component and its
label-swap no longer destroys the icon.
- Cycling figure plate fills the masthead's empty right side and explains
the course: FIG_001 forward pass draws itself and runs signal pulses;
FIG_002 types out a /learn agent session with a blinking caret; FIG_003
learning curves with graph grid, area fill, on-curve milestone markers
(computed on the bezier), axis arrowheads, and a rider dot. Sequential
fade between plates so text can never double-expose; fixed per-tier
width/offset so the plate never clips at any viewport; reduced-motion
shows a static plate.
- Learner wall: seamless JS-filled marquee (clones halves until the loop
covers any viewport, constant scroll speed, no blank gaps) with real
marks for every name that has a redistributable SVG (simple-icons plus
official Wikimedia files in site/logos/); IIT Bombay and Windsor are
type-set because only fair-use logos exist. Anonymized pull quote from a
Google AI engineer beneath. Grayscale with dark-theme invert.
- Mobile: command wraps instead of horizontal scrolling.
English stays canonical. Translations generate to a separate translations
branch (never main); the site, README, and books all consume them. Default
engine is NLLB-200 in the CI runner: no API key, no Vercel cost.
One pipeline, three surfaces, none bloating main:
- lessons: 503 docs -> translations branch, fetched at runtime with English
fallback; English path byte-identical to before
- README: translated to the same branch, linked from the README header
- books: build_book.py --lang reads translated markdown and emits
aiefs-vol{n}-{slug}-<lang>.epub/.pdf release assets (English fallback)
- languages.json: 40-language registry (FLORES-200 + ci flag) driving the
script, the site switcher, and the CI matrix
- scripts/translate_lessons.py: prose-only walker keeps code/math/figures/
tables/HTML/bold/links/metadata out of the model (byte-lossless across all
503 lessons + README); per-language sha256 cache written per lesson so runs
resume and never redo unchanged English; providers pluggable (nllb default)
- .github/workflows/translate.yml + translate-requirements.txt: one job per
language, restores its cache, translates changed docs, pushes race-safe
- site switcher (40 languages, English fallback); build.js emits gitignored
site/langs.js from the registry
- docs/i18n.md: architecture, no-waste guarantee, cost, quality sample