mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-07-20 04:45:11 -05:00
ebdaede92b6099345f7dcde856e23037e476e8a4
66
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
73f847eba5 |
Merge pull request #1786 from Shashank-Tripathi-07/fix/labs-vol2-lab07-16-audit
Lab numerical corrections: lab_08 crossover (3-4 weeks), lab_09 FlashAttention savings, lab_11 false alerts (778/day matches inline formula), lab_14 energy deficit (2,400,000x verified). |
||
|
|
cd910e446f |
fix(book): wrap long URLs to stop horizontal page overflow
On reference-heavy chapters (e.g. vol1 training), long unbreakable tokens — paper hashes, arXiv ids, DOI suffixes — inside body links, column-margin notes, footnotes, callouts, and right-side TOC entries inflated the line box past their column. That cascaded into page-level horizontal scroll on narrower viewports. Add overflow-wrap: anywhere to: - the global `a` rule and the TOC container in book/quarto/assets/styles/_base-styles.scss (the volume builds use this; they don't import shared/styles/partials/_links or _toc) - the corresponding shared partials shared/styles/partials/_links.scss and shared/styles/partials/_toc.scss for ecosystem sites that do consume them (site, tinytorch, labs, kits, instructors, slides, mlsysim docs) `anywhere` (not `break-word`): only `anywhere` shrinks min-content size so flex/grid ancestors can actually narrow. Pair with `min-width: 0` on the TOC container to let the column take the smaller size. Regular prose still breaks at word boundaries first. |
||
|
|
010576f1ae |
style(html): constrain Quarto auto-promoted page-full floats to body column
Quarto's HTML emitter auto-applies `.page-columns.page-full` to wide table figures, listings, and figures based on internal content-width heuristics. In the book's `floating slimcontent` grid, that class spans `[page-start]` → `[page-end]`, which extends under the sidebar nav on the left and into the margin-notes column on the right. Visible effect: stranded captions, clipped content, columns hidden by the sidebar (Tables 4/6/15/27 + Listing 12 in V1+V2). Grep across `book/quarto/contents/` confirms zero floats explicitly request `column: page`, so constraining all auto-promoted ones to the body-content track is safe. Floats that genuinely want to span margins can still opt in via `.column-page` / `column="page"`. Rule mirrored in `book/quarto/assets/styles/_base-styles.scss` (book build path) and `shared/styles/partials/_tables.scss` (ecosystem-base consumers), matching the existing duplication pattern between those two files. |
||
|
|
fbb3f3d163 |
fix(site): add nav-footer and dropdown-menu dark mode selectors
- Add .nav-footer alongside .page-footer in _site-dark.scss so Quarto's rendered footer element gets a dark background when the toggle is active. - Add .dropdown-menu and .dropdown-item dark overrides so navbar menus do not stay white in dark mode (toggle path, not OS prefers-color-scheme). - Change footer-site.yml background from 'light' to 'none' so the CSS dark override can actually apply; 'light' hardcodes Bootstrap's bg-light class which pins the footer to a white background regardless of theme. |
||
|
|
f7860e888b |
fix(release-pill): docstring no longer terminates outer HTML comment early
The note at lines 36-42 explained why you should not write literal `<!-- ... -->` inside the docstring, and in doing so, wrote literal `<!-- ... -->` inside the docstring. HTML disallows nested comments, so the inner `-->` token in the warning text closed the outer comment on line 1, leaking the example markup (span and script tags) into the rendered page on every Quarto site that included this snippet via include-after-body. The leaked script tag carries a literal U+2026 ellipsis as part of its src attribute (the docstring abbreviated the path), so every page fetched a 404 for `<subsite>/…release-pill.js`. Rewrites the note to describe the rule without using the literal characters it warns against. The outer comment now opens on line 1 and closes on line 61 with no intermediate delimiters. After this lands, the release-pill 404 disappears across vol1, vol2, tinytorch, kits, mlsysim, and instructors. The actual release-pill JS (inline script at the bottom of this file) was always intact — only the leak from the bad docstring was failing. |
||
|
|
a7f5173ee6 |
feat(navbar): force brand href to ecosystem root via JS
Quarto's per-subsite `site-url: https://mlsysbook.ai/<name>/` triggers automatic relativization of any navbar.href value that lives inside that domain. The shared `href: https://mlsysbook.ai/` in navbar-common.yml therefore gets emitted as `./index.html`, which on vol1 / vol2 / tinytorch / etc. resolves to the *current subsite's* root rather than the ecosystem landing page. Clicking the brand on any subsite was a self-link. Adds a small DOMContentLoaded script in shared/config/site-head.html that overrides both `.navbar-brand` and `.navbar-brand-logo` href to the absolute root URL. Pattern mirrors the theme-toggle-relocate script added in the previous commit: tiny, post-render correction that does not fight Quarto's URL machinery. |
||
|
|
394c68f8f0 |
refactor(navbar): collapse theme toggle with right-side items at all widths
Two paired changes that make the dark-mode toggle behave like the other right-side actions (Subscribe / Star / Support / GitHub) at every viewport width, replacing the previous behavior where it floated in the middle of the navbar at narrow widths. 1. shared/config/site-head.html — relocate Quarto's auto-injected theme toggle from .quarto-navbar-tools (which sits outside .navbar-collapse and therefore stays visible at narrow widths in a stranded middle position) into the navbar's right-aligned .navbar-nav.ms-auto group on DOMContentLoaded. The toggle then participates in Bootstrap's collapse semantics: visible inline at xl+ alongside the other right-side links, collapsed into the hamburger menu below xl, exactly like Subscribe and friends. 2. shared/styles/partials/_navbar.scss — drop the brand-container's mx-auto left margin and replace its right margin with auto at the same <xl breakpoint. The navbar-container has justify-content: space-between with three visible children at narrow widths (hamburger, brand, search type-overlay). Equal spacing was pushing the brand to the middle. margin-right: auto on the brand consumes the free space on its right, snapping the brand against the hamburger and pushing the search icon to the right edge — the standard professional navbar layout (Stripe, Material UI, Linear, et al.). Verified via Playwright sweep at 10 viewport widths (320 / 480 / 600 / 768 / 900 / 992 / 1100 / 1199 / 1200 / 1400). At every width the brand sits tight against the hamburger (gap 11px) below xl, the theme toggle is inside .navbar-collapse, and search is anchored at the right edge. |
||
|
|
9e6f4bc0e4 |
refactor(navbar): reorder right side to Subscribe → Star → Support → GitHub
Promote the newsletter conversion to leftmost position in the right-side group so it gets the most visual weight without being pushy. The previous order (Support → Star → Subscribe → GitHub) buried Subscribe in the middle. Order rationale (low-friction → high-friction → dev tooling): Subscribe — primary engagement, opens modal (#subscribe), fastest path to "stay in the loop" Star — 1-click GitHub interaction, zero friction once signed in Support — financial commitment, post-engagement CTA GitHub ▼ — developer dropdown (Discussions, Edit, Report, Source), sits next to Quarto's theme toggle + search tools This is the source of truth for every Quarto site that includes navbar-common.yml via metadata-files. StaffML (Next.js) has its own header component and needs a parallel change there if Subscribe should appear in its navbar too — flagged separately. |
||
|
|
69054ab9bc |
chore(links): allowlist CI-injected mlsysim-paper.pdf
The link checker fired on three references to mlsysim-paper.pdf in
mlsysim/docs/{whitepaper,for-instructors,index}.qmd. That PDF is
intentionally not committed — the mlsysim-publish-live workflow copies
pdf-artifacts/paper.pdf into mlsysim/docs/mlsysim-paper.pdf at deploy.
Add CI_INJECTED_BASENAMES with mlsysim-paper.pdf so the offline checker
skips it. Match by Path(target).name to handle ./ and ../ variants.
|
||
|
|
f70e47ca5e |
fix(instructors): keep theme toggle visible across breakpoints (#1599)
Co-authored-by: Vijay Janapa Reddi <vj@eecs.harvard.edu> |
||
|
|
e1e2ba783f |
fix(navbar): keep right-side tools on one row across 1200-1479 px
Right-side actions (Support / Star / Subscribe / GitHub) were clipping past the viewport in two bands measured with Playwright on the dev landing: - 1200-1279 px: icon-only mode at default 0.75 rem padding overflowed by up to 12 px. - 1400-1479 px: Bootstrap's xxl breakpoint pulled labels back in but the row still needs ~1480 px to fit them, so right items clipped 4-64 px past the viewport edge. Extend the icon-only media query upper bound from 1399 to 1479 px and tighten right-nav nav-link horizontal padding to 0.45 rem inside that band. The row now stays single-line from the xl hamburger threshold (1200 px) all the way up. |
||
|
|
0215fd9e4d |
fix(theme): bridge into site-head.html and defend against Quarto's clobber
Two issues caught by local rendering of /about/license.html in dark mode:
1) The active source for the inline FOUC-prevention script is
`shared/config/site-head.html`, NOT `shared/scripts/theme-persist.js`.
Quarto inlines the contents of site-head.html into <head>; the
standalone .js file is documentation-only ("kept identical for
documentation/testability"). The previous patch only touched the
.js file, so the .quarto-dark body-class bridge never reached the
rendered page. Mirror the fix into site-head.html.
2) Even with the bridge in place, Quarto's own
`toggleBodyColor(mode)` runs *after* this script and re-asserts
the body class based on `mode`. When no `quarto-color-scheme`
value is in localStorage (typical first visit), Quarto resolves
`mode = 'light'` and clobbers our `.quarto-dark` class — even
though the OS preference resolved `data-bs-theme="dark"` already.
Result: dark `data-bs-theme` background with `.quarto-dark`-keyed
CSS variables stuck at light defaults (the "Open by design."
invisibility on /about/license.html, low-contrast hero/body text
across about/community/newsletter pages).
Defense: a MutationObserver on `body.class` and `html[data-bs-theme]`
re-asserts the bridge whenever Quarto (or any other script) changes
it. Cheap, idempotent, and surgical.
Verified locally with playwright + the about/license.html render:
bodyClasses: ["nav-fixed", "fullcontent", "quarto-dark"] ← was quarto-light
h1Color: rgb(229, 231, 235) ← was rgb(26,26,46)
bodyAbText: "#e5e7eb" ← was "#1a1a2e"
|
||
|
|
e4069996d8 |
fix(theme): bridge dark mode to .quarto-dark body class
The site/about/about.css, site/community/community.css, and
site/newsletter/newsletter.css custom styles key dark-mode CSS
variables off `.quarto-dark { ... }` (and descendant chains like
`.quarto-dark .opening-lead`). That class is added by Quarto only when
the user clicks the toggle button — never on the OS-preferred dark
path. Result: a visitor whose browser is set to dark mode but who has
not toggled the site explicitly gets the dark <html data-bs-theme=dark>
background but the LIGHT mode `--ab-text: #1a1a2e` text, producing the
classic "Open by design." invisibility on /about/license.html and a
slate of low-contrast hero/body text across about/community/newsletter
pages.
Patch the central `apply()` so every theme transition — initial paint,
toggle click, OS-pref change, cross-tab storage event — also mirrors
the scheme onto `document.body.classList`. A MutationObserver covers
the FOUC window where <body> has not yet parsed at first apply().
This is intentionally a JS bridge rather than a CSS rewrite: the three
custom-CSS files have ~15 selectors keyed on `.quarto-dark` between
them, including descendant chains where a naive find/replace would
break selector grouping (`.quarto-dark .foo {...}` cannot become
`[data-bs-theme="dark"], .quarto-dark .foo {...}` — that comma turns
the first selector standalone). One JS line covers all of them and
keeps the CSS files unchanged.
|
||
|
|
af210d5761 |
fix(navbar): brand title parity, Mission link, dropdown z-index
A) book vol1/vol2 brand title: every other site in the ecosystem
(site, staffml, tinytorch, mlsysim, instructors, etc.) renders
the brand as plain "Machine Learning Systems". The two book
volumes were the lone exceptions, advertising
"Introduction to Machine Learning Systems" and "Machine Learning
Systems at Scale" in the navbar. The volume name belongs in
page chrome (sidebar, breadcrumb, footer, PDF cover) but reads
as inconsistent to a visitor moving between subsites; both
volumes now use the canonical brand. Sidebar IDs ("Volume I",
"Volume II") still distinguish content scope inside each book.
B) navbar-common.yml Mission link: was pointing at
`/about/#mission` so clicking it scrolled into the About page
to a Mission section. The About page already opens with the
mission as its hero — anchor-scrolling past that hero is
confusing UX. Drop the fragment so Mission lands at the top
of /about/ like the other About entries.
C) EcosystemBar z-index + overflow-y: the StaffML internal `Nav.tsx`
component sits directly below the EcosystemBar at `z-50`. The
ecosystem dropdowns *should* paint above it (we were at z-60),
but with `overflow-x: clip` on the EcosystemBar wrapper, WebKit
was occasionally re-clipping the dropdown's vertical overflow
and rendering it behind Nav's stacking context. Fix:
- Bump EcosystemBar wrapper to `z-1100` — well above any other
sticky/fixed element on a StaffML page.
- Pin `overflowY: visible` explicitly so Safari cannot coerce
it to `auto` when paired with `overflow-x: clip` (the spec
carves `clip` out of that coercion, but historical Safari
was inconsistent).
|
||
|
|
4328ff2bdd |
fix(navbar+release-pill): three post-deploy regressions
1) release-pill.html: nested HTML comment broke every Quarto site that included it via include-after-body. The Pattern B documentation block contained a literal `<!-- in footer -->` inside the outer `<!-- ... -->` doctring; HTML disallows nested comments, so the inner `-->` terminated the outer comment early and the example markup that followed (`<span data-release-pill>` and `<script src="/release-pill.js">`) leaked into the rendered page — producing a 404 on every page that fetched the script. Replaced with a `// in footer:` pseudo-comment and added a NOTE warning. 2) shared/_navbar.scss: dim the SEAS shield in dark mode. The logo asset is transparent-bg, but the white "VE RI TAS" books area at the top of the crest read as a bright square against the dark navbar. `filter: brightness(0.85) contrast(1.05)` softens the crest without losing the crimson. Targets both `body.quarto-dark` (Quarto's class) and `[data-bs-theme="dark"]` (Bootstrap 5+). 3) EcosystemBar.tsx: mirror Quarto's brand-title abbreviation. StaffML was always rendering the full "Machine Learning Systems" string with CSS ellipsis, so narrow viewports ellipsis-truncated it mid-word — visually distinct from every Quarto site, which swap to a clean "ML Systems" via the `_mobile.scss @media (max-width: 1199px)` rule. Added an `nav-xl:hidden` / `nav-xl:inline` pair (with an `sr-only` full string for screen readers) so the abbreviation behaves identically to Quarto on the same screen widths. |
||
|
|
b8183404b8 |
chore(release): shared versioning infrastructure
Lays foundation for unified release versioning across MLSysBook publishable artifacts. Pure additions — no existing builds, configs, or sources are touched. scripts/version/release.py Python CLI with helpers: - compute-id: semver bump from previous tag (patch/minor/major/none/explicit) - compute-hash: deterministic SHA-256 over input directories with per-file index - emit-release: writes releases/<project>-<id>/release.json (canonical artifact) - emit-manifest: writes the build-time manifest the deployable bundles Tier A (citable) emits per-file Merkle index; Tier B (lite) is flat. scripts/version/schema.json JSON Schema for release.json. Validates project/tier/release_id/release_hash + Tier A's files[] index. Used by validators in CI. shared/release/release-pill.html Footer snippet — fetches deployable manifest at runtime, renders "v0.1.0 · Apr 26, 2026" pill. Configured per-project via <meta name="release-manifest"> tag. Silent on any fetch failure. shared/release/release-card.html About-page snippet — fuller release-identity card with click-to-copy hash. Same fetch + meta-tag conventions. shared/release/README.md Operator-facing contract documentation. .github/workflows/_release-prepare.yml Reusable workflow_call. Validates confirm == "PUBLISH", computes new_release_id from previous tag + bump (delegates to release.py for canonical math). Outputs new_release_id/new_tag/previous_* for caller's downstream build and finalize steps. Refuses to re-tag existing releases (citation integrity). Caller workflows still own their build commands and tag/release creation; this only standardizes the input shape and version math. |
||
|
|
6fdf81dd49 |
fix(site): collapse navbar at xl + ship site_libs to root
Two breakage points with the same flavor — the navbar configuration disagreed with the responsive CSS, and the deploy script discarded the asset bundle the about/community/newsletter pages depend on. - navbar-common.yml: collapse-below "lg" → "xl". With 6 left dropdowns and 4 right tools, the full navbar needs ~1100 px. At 992–1199 px the dropdowns wrapped into a vertical stack and "Machine Learning Systems" broke across three lines. _mobile.scss was already written against an "xl" assumption (its 769–1199 px block targets a collapsed navbar that never appeared); the YAML now matches. - _mobile.scss: bump the abbreviated-title media query from 991 px to 1199 px so "ML Systems" appears the moment the navbar collapses, not 200 px later. - site-publish-live.yml: stop skipping site_libs/ when copying the landing build to root. The about/community/newsletter HTML reference ../site_libs/… which resolves to /site_libs/ at runtime; skipping it shipped those subsites with no Bootstrap or Quarto CSS at all (raw <ul> navbar, 3000 px of empty whitespace before the hero, dark mode unstyled). Also rm -rf each non-subsite item before re-copying so a removed top-level file doesn't linger on gh-pages. Verified locally with Playwright at 425/768/991/992/1100/1199/1280/ 1400/1920 px in light + dark mode against a fresh tinytorch render. Not addressed here: the right-TOC's position:sticky lets go on big-picture.html because Quarto closes <main> mid-article (a few content-visible blocks end up after </main>). That's a per-page content-structure fix, not a shared-chrome change — separate commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d759f3c4c2 |
fix(theme): bridge Quarto's 'alternate'/'default' to data-bs-theme
Quarto's built-in toggle stores its color-scheme choice as
'alternate'/'default' under the same localStorage key (`quarto-color-scheme`)
that the shared theme-persist shim reads. The shim only recognized
'dark'/'light', so once a reader clicked the toggle it would, on the next
load, fall back to OS preference and apply data-bs-theme=light while
Quarto correctly enabled the dark stylesheet (or vice versa). The result
was a half-themed page — most visible to readers on macOS dark mode whose
stored choice was 'default' (light): Bootstrap's CSS-variable dark mode
kicked in via data-bs-theme=dark, but the dark-mode SCSS layer never
loaded, leaving a dark navbar against a light sidebar/content/announcement.
theme-persist now accepts both vocabularies on read (alternate→dark,
default→light) and wraps quartoToggleColorScheme so the html attribute
syncs immediately after a click instead of waiting for the next reload.
The wrapper is a no-op on non-Quarto subsites (StaffML/Next.js).
Quarto's startup still checks `=== 'alternate'`, so we do NOT rewrite
Quarto's stored values — only mirror them onto <html>.
Single shared file in include-in-header propagates to all 8 Quarto
subsites: book vol1+vol2, labs, kits, slides, instructors, site, mlsysim.
Verified with Playwright across the full vol1↔vol2 navigation + toggle
sequence and across {OS=dark|light} × {storage=null|default|alternate}
matrix: 5/10 mismatches before, 0 after.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
98f45a5ae4 |
fix(docs): repair internal links for corpus build artifact and book SCSS
- interviews/README: stop linking gitignored vault/corpus.json; note vault build - shared/styles/BRAND: point to style-vol1/2 instead of nonexistent style.scss |
||
|
|
30f986a202 |
fix(links): round-2 lycheeignore patterns for tracker false positives
After PR #1554 dropped Unified Site to 0 broken, the next workflow run surfaced more false-positive patterns dominating the Slides count (396 broken). All four patterns below were manually verified to return 200 in a browser; Lychee's HEAD is failing on them. - github.com/tinyMLx/courseware/raw/master/edX/ (rate-limited HEAD; every URL referenced was validated against the upstream Contents API in PR #1553, the 6 genuinely-broken targets were repaired, the remaining 319 references all return 200 to a browser) - github.com/<owner>/<repo>/issues/new (and template= variants) (Issue creation form; always live, but Lychee mishandles the parameterized variant) - github.com/<owner>/<repo>/releases/download/ (asset URLs; Cloudflare HEAD response is unreliable for these endpoints) - laurencemoroney.com (personal site, verified 200) Separately handles the genuinely-broken `00_course_overview.pdf` asset on the slides-latest release: that one is a real broken file — not a false positive — and is being addressed by re-running slides-publish-live to put the missing PDF back in the release (the 2026-04-23 publish run logged successful upload but a vol1/vol2 name collision race left only the .pptx surviving). |
||
|
|
98ed108e46 |
fix(links): aggressive lycheeignore patterns to drive tracker to zero
Goal: get the nightly link-rot tracker (#1424) to permanent green. Real broken links were already addressed in PRs #1552 and #1553. The remaining tracker noise is dominated by anti-bot false positives: sites that respond 999/403/503/transient-5xx to Lychee's HEAD requests but are reachable in a browser. Manually verified each pattern below returns 200 to a real user agent. shared/config/.lycheeignore — adds patterns covering: - LinkedIn (always 999 anti-scraping) - X / Twitter (Cloudflare bot challenge) - Harvard SEAS faculty pages (university CDN bot block) - Edge AI Foundation (verified live; intermittent 5xx) - discuss.tinymlx.org (Discourse 403 to HEAD) - edX professional-certificate / course pages (throttling) - mpstewart.net (302 redirects lychee mishandles) - Medium / Towards Data Science (Cloudflare HEAD blocks) - Forbes / WSJ / Reuters (paywall + bot detection) - StackOverflow / Stack Exchange (Cloudflare challenge) - YouTube channel URLs (4xx to HEAD, live in browser) book/config/linting/.lycheeignore — adds book-bucket entries for the same false-positive sites (book uses its own lycheeignore file per the workflow's lycheeignore_path config), specifically: - edgeaifoundation.org (the 2 flagged URLs in #1424's Book bucket) - LinkedIn / Twitter / Forbes / WSJ / TowardsDataScience After this lands, the next nightly link-rot run should report zero on every site that doesn't have a real broken-content issue. The tracker auto-closes when broken count = 0. |
||
|
|
460d385446 |
fix(links): suppress link-rot tracker noise + remove gitignored .claude link
Two surgical fixes for the nightly link-rot tracker (#1424): 1. **shared/config/.lycheeignore**: add two patterns that suppress confirmed false positives reported in the tracker. - JS template literals (`${m.github}`): when Lychee parses a .qmd containing JavaScript with template literals like `https://github.com/${m.github}`, the literal is URL-encoded in the AST as `$%7B…%7D` and reported as broken. The literal is substituted at runtime; it is never a real URL. Pattern matches any `${ANY.ANY}` template-literal residue. - GitHub avatar URLs (`github.com/<user>.png?size=N`): manually verified that every avatar URL flagged by the tracker (profvjreddi, Mjrovai, etc.) returns 200 in a browser. Lychee unreliably HEADs them due to GitHub's avatar throttling. Together these cover the 8 remaining "broken" links on the Unified Site that are not real failures. 2. **slides/teaching.qmd**: replace a markdown link to `.claude/rules/svg-style.md` with plain prose. The `.claude/` directory is gitignored in this repo, so the URL github.com/harvard-edge/cs249r_book/blob/main/.claude/rules/svg-style.md 404s. Mention the SVG style guide in prose without the broken link. Out of scope for this patch: the 329 `tinyMLx/courseware/raw/master/...` links in slides/, which point to PDFs that don't exist in the upstream repo (the directory exists, but the specific file numbers don't). That is a content/strategic question — needs a decision on whether to upload missing PDFs, switch repos, or remove the links — and is left for a separate workstream. |
||
|
|
44baf0c757 |
polish(tinytorch): sidebar titles + HTML milestone removal + flat tier
cards + header decoration + PDF milestone transitions
Polishes several rough edges in the TinyTorch site surfaced by a visual
walkthrough. Six independent fixes in one PR because they all landed on
the same mental pass:
HTML SIDEBAR
- Rename two sections so their titles don't wrap on the 250px sidebar:
"Capstone Competition" -> "Capstone"
"TITO CLI Reference" -> "TITO CLI"
(navbar link "TITO CLI Reference" -> "TITO CLI" too, for
consistency)
- Remove all three milestone sections ("Foundation Milestones",
"Architecture Milestones", "Optimization Milestones") from the HTML
sidebar. They interleaved between tiers and broke the
Foundation -> Architecture -> Optimization -> Capstone flow the
sidebar is meant to communicate. Milestones stay fully accessible
via the navbar's "Historical Milestones" entry, and the PDF build
(which has its own _quarto.yml) is untouched -- interleaved
milestones are the correct reading experience in print.
- YAML comment added where the milestone sections used to live so a
future contributor knows the removal was intentional.
SIDEBAR SCROLL
- shared/styles/partials/_sidebar.scss: add
`overscroll-behavior: contain` to #quarto-sidebar and
.sidebar-navigation. Before: hovering-and-scrolling over the sidebar
chained the scroll to the body the moment the sidebar hit a
boundary -- so scrolling the sidebar felt like it was never actually
engaging. After: the sidebar's scroll stays in the sidebar.
- Single-source-of-truth win: this improves every Quarto site in the
ecosystem (book/kits/labs/mlsysim/tinytorch/site), not just tinytorch.
H2 L-SHAPE DECORATION (TINYTORCH ONLY)
- shared/styles/partials/_headers.scss decorates H2-H6 with a thick
accent left-border + thin accent bottom line -- an "L-shape" that
reads well in long-form textbook prose. For tinytorch (framework
docs with code blocks, comparison grids, card-based layouts) the
decoration felt heavy and competed with the content itself.
- tinytorch/quarto/assets/styles/style.scss: add a local override
that strips border-left/border-bottom/padding-left/padding-bottom
from H2-H6. Scoped to tinytorch -- book/kits/labs/mlsysim keep the
decoration.
TIER CARDS
- index.qmd's .tier-foundation / .tier-architecture /
.tier-optimization / .tier-olympics cards used four different
gradient fills (blue/purple/orange/pink). Visually loud; fought
the comparison grid and hero on the same page; tier-optimization
read as specifically orange-heavy.
- Flatten to the same neutral #fafafa fill the .audience-card and
preface.qmd cards use, with just a 4px colored left-border carrying
the tier's identity. Each tier still has its distinct color cue;
the page calms down.
PDF NARRATIVE TRANSITIONS
Milestones appeared suddenly in the PDF when a tier ended. Readers
lost context for why they were there and which just-built modules
they were about to exercise. Six short transition paragraphs added
via the narrative-flow-analyzer subagent:
modules/08_training.qmd forward-hook into Foundation Milestones
modules/13_transformers.qmd forward-hook into Architecture Milestones
modules/19_benchmarking.qmd forward-hook into Optimization Milestone
milestones/01_perceptron.qmd tier-components open
(Tensor/Linear/BCELoss/SGD/Trainer)
milestones/04_cnn.qmd first-Architecture-Milestone framing +
three-tier arc explanation
milestones/06_mlperf.qmd sole-Optimization-Milestone +
"third act" framing
Files left as-is because the transitions were already good:
milestones/index.qmd, milestones/02_xor.qmd, milestones/03_mlp.qmd,
milestones/05_transformer.qmd.
DRIVE-BY FIX
modules/13_transformers.qmd had four lines of pre-existing corrupted
trailing content after the last callout (duplicate sentence,
orphan "44B parameters |" table row, orphan Capstone row). Removed
since this PR was already editing the file.
Verification (Playwright against local preview):
- Sidebar section labels: ["Getting Started", "Foundation Tier",
"Architecture Tier", "Optimization Tier", "Capstone",
"Conclusion", "TITO CLI", "Reference", "Community"] -- no
wrapping, no milestones.
- #quarto-sidebar overscroll-behavior: "contain"
- H2 "Don't import it. Build it." computed border-left-width: 0px,
border-bottom-width: 0px, padding-left: 0px
- .tier-card backgrounds: all rgb(250, 250, 250);
border-lefts: 4px solid {tier-color} each.
|
||
|
|
b011cd62a4 |
Merge pull request #1394 from harvard-edge/feat/socratiq
feat: add socratiq directory (excluding node_modules and dist) |
||
|
|
61dae6332d |
fix(shared-sidebar): tighten vertical spacing to match book/kits/labs/mlsysim
The shared sidebar partial had looser vertical rhythm than the four sites
that ship their own local override (book, kits, labs, mlsysim). Result:
TinyTorch and the landing site rendered noticeably airier than the book's
sidebar — same repo, different feel. The partial's comment was also wrong:
it rationalized "more air than CSS-reset minimums to improve scanning" as
a deliberate choice, but the book (which has 60+ sidebar items per volume
and is the worst case for scanability) actually runs the tighter form,
and the extra vertical air hurt scanability rather than helping it. The
"more air" instinct was for *horizontal* padding (keep 6px so items are
comfortable mouse/touch targets), not vertical — conflating the two put
the sidebar on the wrong rhythm.
Changes to #quarto-announcement-affecting rules:
.sidebar-item a
padding: 4px 8px -> 2px 6px
margin: 1px 0 -> 0.5px 0
line-height: 1.45 -> removed (use Bootstrap default ~1.5)
.sidebar-item a[data-bs-toggle="collapse"] (section headers)
padding: 6px 8px -> removed (inherit from item rule above)
margin-top: 4px -> removed (no section gap; header reads as part of
the same vertical column as its children)
These are the exact values book/kits/labs/mlsysim already ship in their
per-site overrides, so this change:
- immediately tightens TinyTorch and site (they import this partial)
- is a no-op visually on book/kits/labs/mlsysim (their local rules win)
- becomes the single source of truth we can consolidate against later
The partial's file header now documents the rationale so a future pass
doesn't re-loosen vertical padding thinking the tight value was a bug.
|
||
|
|
9615886b0e |
fix: repair broken links surfaced by lychee scan
- README.md: 'Cite' badge anchored to non-existent #citation--license section; point it directly to CITATION.bib so it always works. - tinytorch/README.md: 'Getting Started' link pointed to site/getting-started.md which never existed; the actual file is quarto/getting-started.qmd. - shared/config/.lycheeignore: ignore star-history.com fragment URLs. These use # for client-side SPA routing, not as document anchors — lychee was incorrectly flagging them as missing fragments. Verified locally with lychee 0.23: zero broken links remain in the top-level READMEs and new community files except for three deleted GitHub user accounts (Allen-Kuang, harishb00a, jettythek) which are auto-regenerated by the contributor sync workflow and need to be fixed at that layer. |
||
|
|
5f97cca590 | Merge remote-tracking branch 'origin/dev' into dev | ||
|
|
49a6cbbed1 |
chore(tinytorch): delete retired Jupyter Book tree (site-legacy/)
The tinytorch/site-legacy/ directory was the old Sphinx + Jupyter Book
project. After the Quarto migration:
- Live website builds from tinytorch/quarto/ (project: website)
- Lab Guide PDF builds from tinytorch/quarto/pdf/ (project: book)
- All five tinytorch CI workflows invoke `quarto render` only;
nothing calls jb / jupyter-book / sphinx-build anywhere.
site-legacy/ was kept around as documentation of the previous pipeline
but has zero live consumers (verified: ripgrep across .py/.yml/.yaml/
.sh/.qmd/.md/.json/.toml/.lua/Makefile finds no remaining references
outside the tree itself). 27 MB, 213 files removed.
Guard rails removed alongside the tree:
- .pre-commit-config.yaml: drop tinytorch/site-legacy/ from the
check-internal-links exclusion block (no longer needed).
- tinytorch/quarto/tools/measure-pdf-images.py: drop the
`"site-legacy" in parts` skip from both the source-image index
and the .qmd index (2 occurrences).
- .github/workflows/tinytorch-build-pdfs.yml: drop the "legacy
jupyter-book pipeline ... is retired" historical comment.
- tinytorch/quarto/pdf/_quarto.yml: drop the "Replaces the legacy
jupyter-book + XeLaTeX pipeline" header comment and the
"palette mirrors site-legacy/_config_pdf.yml" note (the Quarto
config is now the canonical source for the brand palette).
Documentation cleanup:
- shared/styles/BRAND.md: repoint the broken
`tinytorch/site/_static/custom.css` reference at the live Quarto
SCSS files (style.scss + dark-mode.scss).
Quarto is now the sole TinyTorch publishing engine.
|
||
|
|
b5357f1f02 |
Fix dark-mode shield + dedup About paper CTA (#1452)
* fix(brand): make SEAS shield PNG transparent so it works in dark mode
The canonical shield (shared/assets/img/logo-seas-shield.png) shipped as
RGB-no-alpha with white pixels in the rectangular bleed around the
curved shield outline. In light mode the white was invisible against
white nav bgs; in dark mode it rendered as a stark white tile around
the shield (visible on StaffML's dark navbar).
Flood-filled the exterior white from the corners with PIL and saved as
RGBA. The interior white "VERITAS" books are isolated from the corners
by the shield's black border and so are preserved (they are the actual
design, not background bleed).
Also added interviews/staffml/public/logo-seas-shield.png to the
sync-mirrors.sh map so the StaffML mirror stays in lockstep with the
canonical asset on future regenerations.
Verified:
* Build is RGBA (file out/logo-seas-shield.png reports "8-bit/color RGBA")
* Local dark-mode StaffML navbar: shield blends seamlessly into the
#212529 navbar bg, no white tile.
* Local light-mode: indistinguishable from before (the now-transparent
pixels were previously white-on-white, so no regression).
* fix(staffml/about): drop duplicate "Read the Research Paper" CTA
The /about page had two surfaces pointing at the same StaffML-Paper.pdf:
1. PaperCitationCard at the top (above the fold, PDF + BibTeX) — the
Phase 6 academic-citation entry point.
2. A second large bordered "Read the Research Paper" CTA card inside
the "How Questions Are Built" section, with effectively the same
pitch in different words.
The bottom card duplicated the top card's CTA without adding new
information and visually competed with the citation card a few sections
above. Replaced with a single inline link inside the methodology prose
("…is described in our paper"), so the in-context pointer survives
(this section IS the methodology) without the duplicate visual surface.
Net result: one prominent paper CTA above the fold, one inline
reference where the methodology text actually mentions it.
Also drop now-unused FileText import.
|
||
|
|
edbea966bf |
refactor(tinytorch): rename site-quarto/ to quarto/
Brings the TinyTorch lab guide's Quarto project in line with
book/quarto/, the only other in-tree Quarto publication that builds
both web and PDF outputs from a single source. The previous name had
three redundancies:
- already under tinytorch/, so "site-" prefix wasn't disambiguating
- also produces the PDF lab guide, so "site-" was misleading
- the top-level site/ dir made "site-quarto" read as "the site's
quarto config" rather than "the tinytorch site, in quarto"
After this rename the convention is straightforward:
book/quarto/ -> the textbook (web + PDF)
tinytorch/quarto/ -> the TinyTorch lab guide (web + PDF)
mlsysim/docs/ -> mlsysim API reference (kept as docs/, since it
really is API reference, not a publication)
Touches 7 GitHub workflows, both .gitignore files, the rename target's
own self-references (Makefile, _quarto.yml configs, STYLE.md,
measure-pdf-images.py), and 6 copies of subscribe-modal.js plus a few
shared scripts/configs whose comments documented the old path.
Verified: rebuilt pdf/TinyTorch-Guide.pdf (2.1M) cleanly from the new
location with 'make pdf' from tinytorch/quarto/.
|
||
|
|
1e12b3474b | refactor: document SocratiQ canonicals in sync-mirrors.sh | ||
|
|
07088f6e73 |
nav: move product PDFs from shared Build to site-local menus
The shared Build menu listed each tool twice — once as a site link and once as a paper PDF — so it grew whenever a tool shipped a paper and diverged from the "one row per tool" consistency rule in the file's own design comment. Move the paper / guide PDFs next to the site they document: - Remove "TinyTorch Paper" and "MLSys·im Paper" from shared Build. Build is now a clean 4-row ecosystem index: Labs, TinyTorch, Hardware Kits, MLSys·im — identical on every site. - Add "TinyTorch Guide (PDF)" and "TinyTorch Paper (PDF)" to the site-local TinyTorch dropdown in tinytorch/site-quarto/_quarto.yml. Mirrors how "Read" on the main site carries the book PDF/EPUB — the artifact lives under the menu whose context it belongs to. The MLSys·im site will need the matching local-menu addition when its quarto config is touched next; the shared Build removal already eliminates the duplication today. |
||
|
|
044ea3b7d0 |
style(shared/sidebar): loosen nav density for readability
The sidebar partial is shared across book, tinytorch, kits, labs, and instructors. Its current `padding: 2px 6px` + `margin: 0.5px 0` was optimized for cramming long chapter lists into limited height, but it reads as claustrophobic on shorter nav sets (tinytorch's 20 modules, kits' hardware labs) and makes scanning harder. - padding: 2px 6px → 4px 8px (still compact, easier target) - margin: 0.5px 0 → 1px 0 (visible separation between items) - line-height: 1.45 (new; explicit so wrapping labels don't crowd) - section headers: +6px 8px padding + 4px top margin for clearer hierarchy Touches only sidebar items and section headers; hover/active colors, section indentation, and mobile overrides (which already use !important inside a media query) are unchanged. All five sites pick it up via the shared partial imports. |
||
|
|
9681cc1f4b |
fix(site): mute announcement banner to neutral chrome with crimson accent
The landing site's announcement bar was using raw Bootstrap `.alert-primary`, which auto-tints to a light pink. That reads as an "alert" when the content is actually promotional (Vol II, TinyTorch, Kits launches). Replace with a soft off-white surface plus a thin crimson left border so the banner sits in the navbar chrome family while still carrying brand signal. Scoped to style-site.scss only — the book's pink announcement (book-only.scss) is unchanged. |
||
|
|
0e32de8be1 |
fix(theme): set $primary to $accent in harvard theme
The site landing page announcement banner was rendering Bootstrap's default blue instead of Harvard crimson. Root cause: theme-harvard.scss set $accent but not $primary, so .alert-primary (which Quarto announcements use) fell back to Bootstrap's built-in blue. Every other Quarto site (instructors, slides, labs, kits, mlsysim, tinytorch) sets $primary in its own stylesheet; site/ relies purely on style-site.scss + theme-harvard, so the omission bit it alone. Verified with a Playwright pass that checks each site's announcement background hue against its $accent — all 7 sites now MATCH. |
||
|
|
8b9333d83d |
fix(navbar): freeze .navbar-brand-container to stop brand collapsing
Playwright verification at 992-1199 px showed the Quarto wrapper element .navbar-brand-container (which holds both the logo <a> and title <a>) being shrunk to 0 px by the nav dropdowns, hiding the shield and title entirely on laptops. The earlier fix targeted .navbar-brand anchors but left the enclosing container with default flex-shrink and min-width: 0. Pin the container's flex-shrink to 0, widen its max-width from 100%-115px to 100%-60px, and keep the title's ellipsis on the inner span only. Shield + full "Machine Learning Systems" now render at every width >= 992 px; title truncates only when genuinely out of room. Verified with Playwright across 380/480/768/900/992/1100/1200/1400 px on instructors, tinytorch, and slides — 80/80 assertions pass. |
||
|
|
fb0da42064 |
Merge navbar & header issue fixes
Addresses four related UI bugs across the ecosystem: - Stray `token.` text leaking into rendered pages (nested HTML comment) - Redundant (01-08)-style module ranges in TinyTorch tier labels - Harvard shield clipped at intermediate widths on instructors/slides - StaffML EcosystemBar drifted from Quarto Bootstrap breakpoints Behavior across all sites (Quarto + StaffML Next.js) now collapses at the same widths, truncates the same way, and renders the same height. |
||
|
|
08a5c9d073 |
fix(navbar): prevent brand logo cutoff at intermediate widths
On instructors and slides pages, the floating sidebar steals horizontal space from the top navbar, and at widths just above the hamburger breakpoint the Harvard shield was being clipped while the long title "Machine Learning Systems" stayed fully rendered. - .navbar-brand img: flex-shrink 0 so the shield never compresses - .navbar-brand: inline-flex + min-width 0 + overflow hidden, with .navbar-title gaining text-overflow ellipsis, so the title truncates instead of pushing the shield out of frame - _mobile.scss: extend the abbreviated "ML Systems" title from max-width 480px up to max-width 991px, i.e. at all widths below the hamburger breakpoint. The shortest form stays at 480px. |
||
|
|
cf94da0edf |
fix(site-head): prevent nested HTML comment from leaking into page body
The outer comment in shared/config/site-head.html contained the literal `<!-- MLSB_BUILD_STAMP -->` token. HTML comments cannot nest, so the inner `-->` terminated the outer block early, leaking `` ` token.\n--> `` as visible text on every site that includes this head file (instructors, slides, labs, etc.). Rewrote the comment to describe the placeholder without writing it verbatim. |
||
|
|
152b8630dc |
fix(ci): clear all 8 failing pre-commit hooks on dev (#1413)
* fix(content): clear two mitpress-above-below pre-commit failures The "📚 Book · ✅ Validate (Dev)" workflow has been failing on dev for 8+ consecutive runs because the mitpress-above-below pre-commit hook flags spatial references like "above"/"below" inside body prose and figure captions (the MIT Press style guide wants @sec-/@fig- cross-refs or "earlier"/"later" instead). Two pre-existing violations were tripping the hook on every push: - book/quarto/contents/vol1/responsible_engr/responsible_engr.qmd:1604 fig-cap for fig-data-governance-pillars said "obligations discussed below: privacy, security, compliance, and transparency" — but those four obligations are *immediately* listed in the same caption, so "discussed below" was redundant. Reworded to "obligations of privacy, security, compliance, and transparency …". - book/quarto/contents/vol2/network_fabrics/network_fabrics.qmd:1217 fig-cap for fig-congestion-cascade said "the PFC backpressure cascades described below." Reworded to "described later in this section." which is what the hook wants. After our 4 release-prep merges (PR-1/2/7/12) cleaned up the other hook failures (spelling, bibtex tidy, pipe tables, contractions, mitpress-vs-period, …), this was the last remaining failing hook. Verified locally: pre-commit run mitpress-above-below --all-files MIT Press: No above/below spatial refs (use cross-refs).....Passed These are pure copy-edits to figure captions; no semantic change to the diagrams or surrounding text. * fix(check-internal-links): suppress 4 categories of false positives The Tier 1 link checker (shipped in PR #1404) was over-eager and flagged author content as broken in four documented patterns: 1. TikZ source inside HTML comments. Link regex matched `\node[mycycle](B1)` as a Markdown link `[mycycle](B1)`. Fix: strip `<!-- ... -->` bodies before scanning, preserving line/column offsets so any *real* failure we report stays accurate. 2. Quarto cross-references like `[Foo](@sec-bar)`, `@fig-x`, `@tbl-y`. These resolve through the project xref index at render time, not the filesystem; book/binder owns that validation. Fix: skip targets whose first token is `@sec-/@fig-/@tbl-/@eq-/@lst-/@thm-/@cor-/@def-/@exr-/ @exm-/@prp-`. 3. Uppercase URL schemes (`HTTPS://`, `HTTP://`) — common after mobile auto-capitalize or copied citations. Fix: case-insensitive prefix match for the EXTERNAL_SCHEMES tuple. 4. GitHub-style emoji-prefix slugs in `.md` READMEs (e.g. `## 🎯 20 Progressive Modules` produces anchor `#-20-progressive-modules` on github.com, but Pandoc would slugify to `progressive-modules`). Fix: register both Pandoc-style and GitHub-style slugs as valid anchors so neither rendering target trips the checker. Drops repo-wide broken-link count from 150 → 84 (false positives only; no real link rot is masked). Real rot is fixed in a separate commit so the checker improvement can be reviewed independently. * fix(content): repair internal-link rot across 10 files Concrete link rot the new checker (PR #1404) surfaced once its false positives were cleared. None of these are stylistic; each link points at a path or anchor that does not exist. - README/README_{zh,ja,ko}.md (24 links): translation files live in README/ so paths to repo-root targets need a `../` prefix (`book/README.md` -> `../book/README.md`, etc.). - mlsysim/docs/contributing.qmd (21 links): `../slides/...` pointed inside `mlsysim/`; the slides root is two levels up (`../../slides/...`). - mlsysim/docs/cli-reference.qmd: `getting-started.qmd#bring-your-own-yaml-byoy` removed; retarget to `#defining-custom-models` (closest surviving section about user-supplied model specs). - mlsysim/docs/for-engineers.qmd, for-instructors.qmd: `solver-guide.qmd#extending-mlsysim` no longer exists; retarget to `#writing-a-custom-solver` (the surviving custom-solver guide). - book/tools/scripts/README.md: `../docs/BINDER.md` resolved to `book/tools/docs/BINDER.md` (nonexistent); the file actually lives at `book/docs/BINDER.md`, which is `../../docs/BINDER.md` from here. - book/quarto/contents/frontmatter/index.qmd: `about.qmd#about-the-book-unnumbered` anchor was removed when the About heading was simplified; drop the anchor so the link lands at the top of the page (which IS the About section). - tinytorch/datasets/tinytalks/README.md: `scripts/README.md` was never created; point at the directory listing instead. * chore(pre-commit): exclude 3 forward-looking files from internal-link checker Three files reference content that does not (yet) exist on the filesystem; the references are intentional rather than rot, so they should not block CI: - labs/index.qmd: lists the 33 planned labs (vol1/lab_00..lab_16, vol2/lab_01..lab_16) as a roadmap. Links go live as each lab ships. De-linking now would lose the visual roadmap. When a lab lands the exclusion narrows naturally on its own. - labs/PROTOCOL.md, labs/TEMPLATE.md: internal authoring docs that reference `../.claude/docs/labs/{PROTOCOL,TEMPLATE}.md`. The `.claude/` tree is per-worktree and not always present at the same relative path; these are author-tooling refs, not user-facing. Net effect: the link checker is now green on a clean checkout. The exclude block uses comments per existing convention so the rationale is discoverable from the config alone. * fix(content): clear codespell, contractions, and vs. pre-commit failures Three pre-existing pre-commit hooks were failing on the dev branch prior to the release-prep merges. Each is a small content normalization: - codespell (2): re-declares -> redeclares (book/quarto/config/shared/README.md); unparseable -> unparsable (handled in the check-internal-links rewrite). - contractions (2): * socratiq/socratiq.qmd callout: "If you're" -> "If you are". * nn_architectures fig-alt for the attention-visualization figure: "didn't" -> "did not". Alt-text is descriptive prose for screen readers, not a verbatim transcription of pixels, so expanding the contraction matches MIT Press style without changing the figure itself. - mitpress-vs-period (6): bare `vs` -> `vs.` per MIT Press 2026 §10.5 in benchmarking.qmd, distributed_training.qmd (x3 across two Python docstrings rendered in code listings), fault_tolerance.qmd, and inference.qmd. Code-listing strings are visible prose in the rendered PDF, so the rule applies there as well. * chore: bibtex-tidy auto-format outputs Outputs of the bibtex-tidy pre-commit hook (which auto-fixes its own input). Picked up here so that running pre-commit on a clean checkout no longer reports a "files were modified" failure for the same files on every invocation. Pure formatting; no entry semantics changed. |
||
|
|
456ecc85b2 |
PR-1: Release-prep safety net (link checking + publish guards + nightly link-rot) (#1404)
* ci(links): add Tier 1 pre-commit internal-link checker
Wire shared/scripts/check-internal-links.py into pre-commit to validate
relative-path markdown links and same-file anchors in changed .md/.qmd
files. External (http/https) URLs are deliberately out of scope here —
that belongs to Lychee in CI (Tier 2 per-site validate-dev, Tier 3
nightly rot scan).
The hook ignores fenced code blocks and inline code spans to avoid
false positives on TikZ syntax embedded in Quarto sources, and ships
with a baseline exclude list (auto-generated quartodoc API stubs,
legacy Sphinx 404s, GitHub line-range anchors) so it can land without
churn on existing content. Tighten the exclude list incrementally as
those areas are cleaned up.
Part of the staged-rollout safety net.
* ci(links): Tier 2 per-site Lychee validate-dev coverage
Generalize the reusable Lychee workflow and extend per-site validate-dev
coverage so every shippable property has external-link reachability as a
CI signal.
Reusable workflow (.github/workflows/infra-link-check.yml):
- New inputs: lycheeignore_path, fail_on_broken (default false),
accept_status. Resolves the ignore file at runtime and warns if
missing rather than crashing the job.
- Summary step now exits non-zero only when fail_on_broken is true,
so it can be used as a non-blocking baseline today and tightened
per site later.
Shared ignore file (shared/config/.lycheeignore):
Universal patterns reused across sites (localhost, Google Slides
behind auth, known transient 404s, the live preview targets we are
about to publish to). The book keeps its existing canonical ignore
at book/config/linting/.lycheeignore — do not duplicate.
Per-site validate-dev:
- book, instructors, kits, labs, mlsysim, slides, tinytorch:
add a check-links job calling the reusable workflow, scoped to
that site's content tree and using the shared ignore file (book
keeps its own). All wired with fail_on_broken=false initially so
we discover the external-link baseline without blocking dev CI.
- site, staffml: new validate-dev workflows so the unified landing
page and StaffML have first-class CI parity (build + smoke + link
check + summary), matching the cadence used by the other sites.
- All summary steps updated to surface link-check results and to
mark them explicitly as non-blocking until baselines are clean.
Part of the staged-rollout safety net (Tier 2 of the link-checking
strategy: pre-commit / per-site / nightly).
* ci(release): publish-live green gate + nightly link rot tracker
Two safety nets that close the loop on the staged-rollout plan: prevent
shipping from an unvalidated baseline, and keep a durable record of
external link rot across all sites.
Publish guard (.github/workflows/infra-publish-guard.yml):
Reusable workflow called as the first job in every publish-live
pipeline. Queries the GitHub API for the latest run of the matching
validate-dev workflow on the dev branch and fails the publish if
that run is not 'success' or is older than max_age_minutes (default
24h). Inputs: validate_workflow (required), branch (default 'dev'),
max_age_minutes (default 1440).
Wire-up: every *-publish-live.yml now starts with a `guard` job and
chains its existing first job's `needs` to depend on it.
- book: guard runs only when confirm == 'PUBLISH' and not in
testing_mode (matches the existing dispatch-guard pattern).
- tinytorch: guard runs in addition to its in-band preflight (which
re-runs validate-dev against the publish commit). Defense in depth
on a workflow that already builds tags + PyPI artifacts.
- kits, labs, instructors, mlsysim, slides, site, staffml: guard is
the first job; the existing build-and-deploy / build job depends
on it.
Nightly link-rot sweep (.github/workflows/infra-link-rot-nightly.yml):
Runs at 04:30 UTC daily. Sweeps every site in parallel using the
Tier 2 reusable workflow, then aggregates results into a single
sticky GitHub issue (label: link-rot) so triage has one source of
truth instead of dozens of opened/closed tickets. Each run rewrites
the issue body with the current per-site status table and appends
a count comment so trend over time stays visible.
Manual trigger supports a dry_run input that prints the report to the
job log without touching the issue.
Part of the staged-rollout safety net (Tier 3 + green-gate enforcement).
* fix(ci): drop --exclude-mail from Lychee args (removed in v0.21)
First real CI run on PR-1 surfaced this:
error: unexpected argument '--exclude-mail' found
tip: a similar argument exists: '--include-mail'
In lychee >= v0.21 the `--exclude-mail` flag was removed; mailto: links
are now skipped by default and the new opt-in flag is `--include-mail`.
The reusable infra-link-check.yml was still passing the old flag, so
lychee was crashing before checking any link. Every reusable
check-links job was reporting "success" anyway because:
- the lychee step has `continue-on-error: true` (so a crash doesn't
fail the job), and
- every caller in this repo currently sets `fail_on_broken: false`
(so the summary step also exits 0).
Net effect: link checking on PR-1 was a no-op. Fix is a one-arg
removal — skipping mail is the new default, which is what we want.
(Worth a separate followup: the summary step should distinguish
"lychee crashed" from "lychee found broken links" so that bad args
fail loudly even when fail_on_broken=false. Filed mentally as a
followup; not blocking this PR.)
|
||
|
|
73967f7c42 |
PR-2: Visual polish (announcement bars, theme persistence, dev-mirror fix, audit script) (#1405)
* fix(dev-mirror): compute prefix from dev-side depth in rewrite-dev-urls.sh The previous implementation hard-coded PREFIX="../" for any non-root subsite, which silently mis-rewrote every absolute mlsysbook.ai link on the dev preview for nested subsites (vol1, vol2 — they live at /book/vol1/ and /book/vol2/ on dev). The most visible symptom was the navbar title-href landing one level too shallow: clicking the navbar title from inside Vol I went to /book/ instead of the unified landing page at the dev root. Fix: derive PREFIX from the number of path segments in the calling subsite's dev-side path (book/vol1 → 2 hops → '../../') and use the mlsysbook.ai key (not the dev-path) for self-link detection. Add an explicit error if the caller passes a subsite name that is not in the SUBSITES map, instead of silently producing wrong rewrites. Sample rewrites with the fix: vol1 page https://mlsysbook.ai/ → ../../ vol1 page https://mlsysbook.ai/vol2/ → ../../book/vol2/ vol1 page https://mlsysbook.ai/kits/ → ../../kits/ kits page https://mlsysbook.ai/ → ../ kits page https://mlsysbook.ai/vol1/ → ../book/vol1/ Live builds are unaffected — they use the original absolute URLs. * feat(book): per-volume announcement bars (Crimson / ETH-Blue) Split the shared book announcement bar into two volume-scoped files so each volume gets audience-appropriate copy AND inherits the right brand tint. Vol I keeps the Harvard-Crimson tint (its theme accent) and the Foundations-flavored content; Vol II picks up the ETH-Blue tint (its theme accent) and Scale-flavored content that leads with the new volume launch and the cross-ecosystem build path. Files: - announcement-vol1.yml — new, Vol I copy, no hard-coded color (uses `type: primary` so .announcement / .alert-primary get $accent = $brand-crimson via theme-harvard.scss) - announcement-vol2.yml — new, Vol II copy, same pattern but theme feeds $accent = $brand-eth-blue via theme-eth.scss - announcement.yml — emptied to a no-op with a deprecation note; keep for one release cycle to avoid breaking any external metadata reference, then delete The CSS that translates `type: primary` into the per-theme tint already lived in book/quarto/assets/styles/_base-styles.scss (`.announcement { background: linear-gradient(... lighten($accent, 52%) ...) }`). No SCSS changes needed — the previous behavior of a single shared bar just hid that the tint was already theme-driven. Resolves the "Vol II announcement should be ETH-themed" QA note. * feat(theme): cross-site dark-mode persistence + FOUC guard Make dark-mode preference flow seamlessly across every subsite under mlsysbook.ai (Quarto-built and Next.js alike) and eliminate the theme-flash that dark-mode readers see on first paint. Quarto subsites (book / labs / kits / slides / instructors / mlsysim / tinytorch / unified site): - shared/config/site-head.html now inlines a tiny pre-paint script that reads `quarto-color-scheme` from localStorage (or falls back to OS preference) and applies `data-bs-theme`, `data-quarto-color-scheme`, and `style.color-scheme` on <html> BEFORE any other script runs. Eliminates the visible flash that was happening because Quarto's own toggle script runs late. - Listens for `storage` events so a toggle in tab A propagates to tab B without a refresh. - Inlined deliberately: the script is tiny, must be synchronous in <head> to avoid the flash, and inlining sidesteps per-subsite asset path differences. Canonical externalized source kept at shared/scripts/theme-persist.js for documentation/testability — if you change one, mirror to the other. StaffML (Next.js): - public/theme-bootstrap.js now reads the Quarto-side key as a fallback when StaffML has no local preference, so a user toggling dark mode on the book lands here in dark mode on first visit. - components/ThemeProvider.tsx mirrors writes back to `quarto-color-scheme`, so navigating onward to any Quarto subsite inherits StaffML's choice. Both subsystems retain their own keys as primary so each app's behavior is unchanged in isolation. The `quarto-color-scheme` key is the bridge contract — keep it stable across all theme code paths. * test(audit): Playwright site-audit script (sidebar / darkmode / assets) Single Playwright-driven QA script that the release-prep plan needs in three flavors. Implemented as one CLI with three subcommands so the shared boilerplate (browser launch, URL list, output dirs, screenshot naming) lives in one place and the per-site source-of-truth list does too. Subcommands: sidebar Assert every Quarto subsite exposes a populated, visible #quarto-sidebar / .sidebar-navigation. Skips sites that intentionally have no sidebar (landing, slides, StaffML). Catches the regression where Vol I/II builds dropped the sidebar after a config refactor. darkmode Force dark-mode via localStorage + data attributes, scroll top→bottom in 800px chunks (so lazy content renders), and screenshot full-page into _audit/darkmode/<site>.png for eyeball review. Surfaces "half-themed" widgets that CSS linters can't find (announcement bar, footer tiles, code blocks, etc.). assets Listen for failed network requests + 4xx/5xx responses on every site URL. Catches the broken <img> embeds reported during dev-mirror review (TinyTorch big-picture PDF viewer, Vol II cover) before they hit production. Targets dev / live / local with --target. Use --only <substring> to narrow scope. JSON report written to _audit/<cmd>.json for CI ingest. Exits non-zero on issues so it can become a blocking CI check once the baseline is clean. Requires `npm i -D playwright && npx playwright install chromium`. |
||
|
|
8f09e80c4c |
PR-3: Scripts, audits, cleanup (build stamp, PDF dropdown, 404s, mirror guard, dedup, RELEASE-PREP) (#1406)
* feat(footer): build-time "last updated" stamp
Add a small build-time stamp to the page footer ("Last updated YYYY-MM-DD
· <site> · <commit>") so readers can see at a glance that the site is
fresh. Quarto's per-page `date-modified` already exists for chapter
pages, but it doesn't capture site-level rebuilds (theme tweaks,
navbar changes, deploy reruns).
Pieces:
- shared/scripts/inject-build-stamp.sh: wraps a token-replace over a
build directory. Search-and-replace on `<!-- MLSB_BUILD_STAMP -->`
means sites that haven't adopted the token are unaffected — opt-in
rollout per subsite.
- book/quarto/config/shared/html/footer-common.yml: token added next
to the existing copyright line in the shared book footer.
- shared/config/footer-site.yml: token added next to the copyright
in the unified-site footer.
- shared/config/site-head.html: minimal CSS for `.mlsb-build-stamp`
(small, neutral, dark-mode aware).
- .github/workflows/kits-publish-live.yml: representative wiring —
runs the stamp step after build and before deploy. Other publish-
live workflows can adopt the step the same way as they roll
through release-prep validation.
* feat(navbar): expose paper.pdf for TinyTorch / MLSys·im / StaffML
Each of these subsites already builds a companion paper.tex in CI and
ships the PDF alongside the HTML site. Surface those papers in the
navbar dropdowns where readers actually look for them:
Build menu:
- TinyTorch → site
- TinyTorch Paper (file-pdf icon, opens in new tab)
→ /tinytorch/assets/downloads/TinyTorch-Paper.pdf
- MLSys·im → site
- MLSys·im Paper (file-pdf icon, opens in new tab)
→ /mlsysim/mlsysim-paper.pdf
Prepare menu (after a separator):
- StaffML Paper (file-pdf icon, opens in new tab)
→ /staffml/downloads/StaffML-Paper.pdf
Paper URLs are intentionally kept in lockstep with the build steps in
tinytorch-publish-live (assets/downloads/), mlsysim-publish-live
(site root), and staffml-publish-live (out/downloads/). If a build
path moves, both the workflow and this navbar entry need to move
together — there is no single source.
* feat(404): per-site 404 pages for slides / instructors / unified site
The book, kits, labs, mlsysim, and tinytorch subsites already have
flavored 404.qmd pages that route lost readers to the right
neighborhood. Add the missing three so every subsite under
mlsysbook.ai has a coherent recovery experience instead of falling
back to GitHub Pages' default white-page 404.
- slides/404.qmd — slide-deck flavored copy, pointers back to
the deck index, the volumes, and the hub.
- instructors/404.qmd — instructor-flavored copy, pointers to the
course map, slides, and both volumes.
- site/404.qmd — landing-page flavored copy, the most
ecosystem-wide nav (links to every subsite)
because this is the most common 404 source
for inbound links from the legacy single-
volume mlsysbook.ai.
StaffML already has its own React not-found.tsx so no work needed.
TinyTorch's legacy Sphinx 404.md is preserved for now (still wired on
the Sphinx site that hasn't migrated yet).
* ci(precommit): block subsite-mirror drift on shared assets
Add a pre-commit hook that runs `shared/scripts/sync-mirrors.sh --check`
on every commit. The hook fails if any of the per-subsite real-file
mirrors (subscribe-modal.js, theme SCSS partials, logo) has drifted
from its canonical source in `shared/`.
Why a guard, not just a sync: Quarto's resource-copy step preserves
symlinks instead of dereferencing them, so we have to keep real
copies. Without the guard, "I'll edit the canonical and forget to
re-sync" silently re-introduces the duplicate-divergence bug we just
spent effort fixing. `always_run: true` because a mirror can drift via
deletion of the canonical, not just by editing the canonical itself.
To re-sync after a deliberate change:
bash shared/scripts/sync-mirrors.sh
* refactor(audit): duplicate-file finder + clean up obvious leftover
Add shared/scripts/find-duplicates.py as a periodic duplication
auditor. It SHA-1 hashes every source-y file across the ecosystem
roots, groups identical contents, subtracts the intentional groups
declared in shared/scripts/sync-mirrors.sh, and reports the rest as
unintended duplicates. JSON report written to .audit/duplicates.json
for CI ingest later; --strict makes it exit non-zero.
Defaults err on the side of being useful out of the box:
- Skips symlinks (those are deliberate aliases, not duplicates).
- Skips small files (<256B) — LICENSE stubs, .gitkeep, etc.
- Skips _site / _build / node_modules / .next / out / .git.
- Source-y suffix list (.js, .ts, .scss, .css, .html, .yml, .py, .sh).
Binary assets (images, PDFs) are NOT scanned because their dup
story is different (logos, icons are intentionally repeated).
Initial-cleanup pass:
- Delete tinytorch/scripts/cleanup_repo_history.sh — byte-identical
leftover; the canonical version lives at
tinytorch/tools/maintenance/cleanup_history.sh and is the one
referenced by tinytorch/tools/maintenance/README.md.
After this commit the only remaining unintended duplicate is
runHistoryProvider.ts in three vscode-ext packages (kits / labs /
tinytorch). Promoting that into a shared vscode-ext package is real
refactor work — out of scope for release-prep, captured for later.
Add .audit/ and _audit/ (the latter from the Playwright site-audit
script) to .gitignore.
* docs(release-prep): handoff notes covering all five PR groupings
Add a single document at the worktree root that walks through what
this branch contains, why each piece is there, the recommended PR
split (PR-1 safety-net, PR-2 visual polish, PR-3 scripts/audits/
cleanup, PR-4 TinyTorch prep, PR-5 cutover skeletons), what was
intentionally LEFT OUT (and why), and what verification was done
locally vs. what still needs the dev mirror to exercise.
Treat this as the cover memo for the staged-rollout foundation
work; once the five PRs are individually merged into dev, this file
will outlive the branch but the per-PR sections still document why
each piece exists for anyone debugging months from now.
|
||
|
|
773e106c63 |
PR-5: Cutover skeletons (rollback-legacy + redirect map + sitemap aggregator) (#1409)
* feat(launch): rollback-legacy.sh — snapshot + restore the gh-pages root
Add the panic button for the mlsysbook.ai cutover. The staged-rollout
plan keeps the legacy single-volume site at the gh-pages root while
the new properties (Vol I, Vol II, TinyTorch, labs, kits, slides,
mlsysim, instructors, staffml, unified landing) get deployed into
subdirectories. Once everything is verified, the unified landing
page replaces the legacy root — and at exactly that moment we want a
one-command revert path that doesn't require remembering which gh-
pages SHA "the old root" lived at.
Three modes:
snapshot Take a timestamped backup of the legacy root files
(everything at gh-pages root that is NOT a known
subsite directory) and push to legacy-backup/<TS>/.
restore <ID> Copy a snapshot back to root, OVERWRITING current
root files but leaving subsite directories alone.
list List available snapshots.
Design choices worth flagging:
1. Subsite-aware. The script hard-codes the list of top-level
subsite directories (book/, tinytorch/, kits/, labs/, mlsysim/,
slides/, instructors/, interviews/, staffml/, about/, community/,
newsletter/) and excludes them from BOTH snapshot capture AND
restore overwrites. Rolling back the legacy landing page should
never wipe out actively-deployed properties.
2. Dry-run by default. Every destructive mode requires --apply. The
default behavior prints what would happen, including a diff
preview for restore. This is the same posture the existing
sync-mirrors.sh / link-checker / publish-guard scripts take.
3. Snapshots are kept, not moved. Restoring a snapshot is itself a
reversible commit on gh-pages; the snapshot directory is preserved
so a "rollback the rollback" is one more command away.
4. Doesn't touch the working tree. Operates against a fresh shallow
clone in mktemp, so it can be run from any clone of the repo
(developer machine or a GitHub Actions runner) without dirtying
anything local.
Typical sequence on launch day is documented inline at the top of
the script. Two short commands wrap the whole rollout: snapshot
before deploy, restore-by-ID if anything looks wrong.
* feat(seo): redirect-map skeleton + HTML-stub generator
Add the cutover plumbing for legacy-URL → new-URL redirects so the
PageRank accumulated under the old single-volume mlsysbook.ai
structure flows into the new ecosystem URLs (`/book/vol1/`,
`/labs/`, `/about/`, etc.) as soon as the unified landing replaces
the legacy root.
Two artifacts:
1. `shared/config/redirect-map.json` — declarative source of truth.
Schema:
- `from`: legacy path (must start with '/')
- `to`: destination URL or path (resolves against base_url)
- `status`: 301 / 302 / 307 / 308 (default 301)
- `note`: optional human note
A trailing-`*` wildcard is supported in `from` for whole-subtree
moves like `/contents/labs/* → /labs/*`. The file ships
intentionally small: just enough entries to demonstrate the
patterns and seed the launch — populating the full inventory
from the legacy mlsysbook.ai sitemap is a separate task.
2. `shared/scripts/build-redirects.py` — generator.
For each entry it emits a tiny HTML stub at the legacy path
containing:
<meta http-equiv="refresh" content="0;url=<dest>">
<link rel="canonical" href="<dest>">
<meta name="robots" content="noindex,follow">
That combo is the closest GitHub-Pages-friendly equivalent of a
301: real users get redirected in <100ms; crawlers treat the
canonical as authoritative and drop the legacy URL on recrawl;
PageRank flows through. The script ALSO emits a Netlify-format
`_redirects` file from the same map, so the day we move off
GitHub Pages (Cloudflare Pages, Netlify, S3+CF) the same source
of truth produces real 301s with no rewrite.
`--check` mode validates the JSON without writing anything (CI
hook). Wildcards skip stub emission (we'd have to walk the
deployed tree to expand them) but are still emitted to the
Netlify file where they work natively.
Wiring into a *-publish-live workflow is a one-liner step
(`python3 shared/scripts/build-redirects.py --map shared/config/
redirect-map.json --out gh-pages-staging/`) but is intentionally
NOT done in this commit — it should land alongside the actual
unified-landing deploy, when there is something for the legacy
URLs to redirect away from.
* feat(seo): aggregate per-subsite sitemaps into mlsysbook.ai/sitemap.xml
The new ecosystem has every subsite (Vol I, Vol II, TinyTorch, labs,
kits, slides, instructors, mlsysim, staffml, the unified landing)
emitting its own `<subsite>/sitemap.xml` because that's what Quarto
and Next produce automatically. Search engines, however, want a
single authoritative entry point per *domain*. Without an aggregated
index they end up either crawling the subsite sitemaps separately
(if they happen to discover them) or missing some entirely.
This commit adds the aggregator:
shared/scripts/build-sitemap.py
Walks a deployed gh-pages tree, discovers every sitemap.xml under
it (skipping the root one, legacy-backup snapshots, _archive,
_site, and the like), and writes a single sitemap-index.xml at
`<root>/sitemap.xml` that points at each subsite's sitemap as a
`<sitemap><loc>…</loc></sitemap>` entry. It also creates or
appends to `<root>/robots.txt` so the index is surfaced to
crawlers via the standard `Sitemap:` directive.
Optional `--include-subsite` allowlist (repeatable) for staged
rollouts where we want the index to advertise only the subsites
that have been verified live, even if other ones happen to be
deployed in the tree. Defaults to "everything found".
`--check` does discovery without writing.
.github/workflows/infra-build-sitemap.yml
Reusable workflow (`workflow_call`) wrapping the script so any
`*-publish-live` workflow can refresh the index as its final
step. Also `workflow_dispatch`-able for manual rebuilds. Joins
the existing `gh-pages-deploy` concurrency group so it never
races a publish push.
Uses sparse-checkout to grab just the script from `dev` (no need
to clone the whole monorepo into the runner) and a full clone of
`gh-pages` to do the work.
Wiring into per-subsite publish workflows happens in a follow-up
commit alongside the actual launch — this PR is "skeletons", and
the per-publish trigger is best landed when each subsite's launch
PR ships.
|
||
|
|
d6029d2c35 |
feat(site): landing freshness badge, lower nav collapse, mobile QA doc
Landing page (site/index.qmd, site/landing-v3.css): Add a small freshness indicator to the hero — pulsing green dot, "Actively maintained · Last updated April 2026 · Release notes" link. Communicates to first-time visitors that the textbook is a living project, not a shipped-and-forgotten resource. Pulse uses reduced-motion-friendly animation timing. Subscribe modal (site/_quarto.yml): Switch the script src from a cross-site /vol1/... URL to the local /assets/scripts/subscribe-modal.js — the marketing site now ships its own copy via the mirror sync (shared/scripts/sync-mirrors.sh), so it doesn't depend on the book bundle being reachable. Navbar breakpoint (shared/config/navbar-common.yml): Lower navbar.collapse-below from "xl" (1200px) to "lg" (992px) so 13-15in laptops keep the full nav visible instead of collapsing into the hamburger. Affects every subsite that includes the shared navbar config. Mobile QA (book/docs/MOBILE_QA.md): Manual checklist for verifying margin sidenotes, citations, navbar, and TOC behaviour on iOS / Android at common breakpoints. Used as a release gate; not part of automated CI yet. |
||
|
|
5213a16eda |
refactor(styles): centralize brand color tokens, document SCSS architecture
shared/styles/_brand.scss is the single source of truth for raw brand hex values (Harvard Crimson, ETH Blue, callout palette). Theme files consume these tokens to derive semantic variables ($accent, $accent-dark, $callout-*) used by the rest of the SCSS layer. Why: previously the same hex codes were duplicated across both theme files (and would have been duplicated again if a third volume were added). A rebrand currently requires touching the listed non-SCSS uses in shared/styles/BRAND.md (HTML meta theme-color, plain CSS, inline QMD styles, SVG fills, JS/Python/TSX constants). Centralizing the SCSS side at least removes duplication within the SCSS layer. book/quarto/assets/styles/_brand.scss is a tracked mirror of the canonical (kept in sync via shared/scripts/sync-mirrors.sh). Required because Sass resolves @import relative to the importing file's physical location, so the book's theme files need _brand.scss reachable as ../brand. shared/styles/README.md adds a file map, a Mermaid diagram of the import graph (consumer entrypoint -> theme -> brand -> base partials), per-subsite entrypoint table, and conventions for adding a new theme. |
||
|
|
2190968942 |
refactor: deduplicate subscribe-modal + socratiQ via mirror sync script
Quarto's resource-copy step preserves symlinks rather than dereferencing
them, which breaks both local builds (AlreadyExists on the second pass)
and gh-pages deploys (relative symlink targets fall outside _build/).
And Sass resolves @import relative to the importing file's physical
location, not the symlink target. So symlinks inside the resource path
are not a viable dedup mechanism.
Instead, keep real file copies in each consumer subsite and enforce
dedup at edit time with shared/scripts/sync-mirrors.sh:
- bash shared/scripts/sync-mirrors.sh # propagate canonicals
- bash shared/scripts/sync-mirrors.sh --check # CI: fail on drift
Mirror map (source | mirrors):
shared/scripts/subscribe-modal.js -> {site, book/quarto, labs, kits,
mlsysim/docs}/.../subscribe-modal.js
Intentional non-mirrors (left untouched, customized variants):
tinytorch/site-quarto/assets/scripts/subscribe-modal.js (TinyTorch-branded)
tinytorch/site/_static/subscribe-modal.js (legacy Sphinx)
Also dedupe the SocratiQ widget bundle via a symlink (safe here because
book/tools/ sits outside any Quarto project, so the resource walker
never touches it):
book/tools/scripts/socratiQ/bundle.js -> ../../../quarto/tools/scripts/socratiQ/bundle.js
The shared canonical (book/quarto/tools/scripts/socratiQ/bundle.js) is
the version actually referenced and served in production.
|
||
|
|
1f18ae2249 | Merge feat/instructors into dev (brand href + toggle position) | ||
|
|
e57fc922a0 |
fix(navbar): brand links to landing page + fix toggle position
Two fixes: 1. Clicking "Machine Learning Systems" title/logo now navigates to https://mlsysbook.ai/ (the main landing page) from any subsite. Set via shared navbar-common.yml href so all sites inherit it. 2. Fix dark mode toggle positioning when navbar collapses at half-screen width. The floating .top-right toggle is now fixed-positioned to align cleanly next to the search icon instead of floating awkwardly. |
||
|
|
6ebe1829b0 | Merge feat/instructors into dev (centralize navbar title dark mode) |