79 Commits

Author SHA1 Message Date
Vijay Janapa Reddi
442dd3693b fix(security): reject non-http(s) hrefs in release-pill
The release pill assigned a data-link attribute straight to a.href, which
CodeQL flags as DOM XSS (js/xss-through-dom). Add a scheme allowlist so only
anchor, relative, and http(s) links pass; anything else such as javascript:
or data: falls back to #release.
2026-07-03 22:04:36 +02:00
Vijay Janapa Reddi
82b7679c3d Add end-of-chapter research questions 2026-07-01 12:23:48 -04:00
Vijay Janapa Reddi
2df92fd9d2 Merge dev into fix/site-qa-polish 2026-06-27 09:03:44 -04:00
Vijay Janapa Reddi
c53f3fe238 Site QA polish: finish MLSys.im brand migration, fix broken links, add memory-tier figure
- Complete the MLSYSIM -> MLSys.im display-name migration across mlsysim/docs,
  instructors, and shared config (code identifiers stay lowercase mlsysim)
- Fix broken TinyTorch module links (_ABOUT.html -> .html)
- Route the navbar Subscribe action to the newsletter page so Safari content
  blockers stop hiding the #subscribe anchor
- Add the Accelerator Memory Tiers figure to compute_infrastructure with a
  registry-driven log-log capacity/bandwidth scatter
- Add four sourced cloud accelerator specs (Groq LPU, Graphcore GC200,
  Untether speedAI240, d-Matrix Corsair) feeding the local-SRAM tier
- Remove the unshipped Coming Soon audio-lectures placeholder and related
  Binder/audio references
2026-06-27 08:14:00 -04:00
Vijay Janapa Reddi
c07b37eca6 fix(site): open subscribe modal from navbar CTA 2026-06-24 11:55:00 -04:00
Vijay Janapa Reddi
3123eaf53b fix(site): avoid Safari hiding subscribe nav link 2026-06-24 09:22:51 -04:00
Vijay Janapa Reddi
6b0e44cce5 Draft book release notes 2026-06-20 22:07:48 -04:00
Vijay Janapa Reddi
d091dfbed4 chore(scripts): tidy hook-script hygiene (top-level import, cwd-independent path)
check-internal-links.py: hoist 'import fnmatch' from inside the
if args.exclude block to the module import group.

check_workflow_fork_safety.py: resolve WORKFLOW_DIR relative to __file__
(.github/scripts/) instead of the caller's cwd, so the check works when
invoked standalone, not only when pre-commit runs it from the repo root.
2026-06-10 17:09:30 -04:00
Vijay Janapa Reddi
714eecebb4 fix: flatten Quarto's double-nested vol URL structure at deploy time
Quarto renders chapters at contents/vol{N}/{chapter}/{chapter}.html
(depth 3 from site root), which deployed as the double-nested URL
mlsysbook.ai/vol{N}/contents/vol{N}/{chapter}/{chapter}.html.

Fixes:
- Add .github/scripts/flatten-vol-urls.sh: post-build script that
  moves contents/vol{N}/* to site root, fixes relative asset paths
  (../../../ → ../), search.json hrefs, sitemap.xml, and absolute
  URL refs in moved HTML files.
- book-publish-live.yml: run flatten script after artifact download,
  before release manifests, for both vol1-site and vol2-site.
- book-preview-dev.yml: add flatten-vol-urls.sh to sparse checkout
  and run flatten before rewrite-dev-urls so depth is correct.
- cloudflare-redirects.json: update all 37 "to" values to clean
  /vol{N}/{chapter}/... form; add 33 backward-compat entries for
  existing /vol{N}/contents/vol{N}/... live URLs.
- instructors/*.qmd: update 77 chapter hrefs to clean URL form.
2026-06-08 21:05:05 -04:00
Vijay Janapa Reddi
ae801a0be4 fix: repair broken chapter URLs in instructors pages and add Cloudflare redirect rules
Source fixes (instructors/*.qmd):
- Replace all short-form /vol1/{slug}.html URLs with correct full paths
  /vol1/contents/vol1/{chapter}/{chapter}.html across course-map, foundations-
  syllabus, scale-syllabus, tinyml-syllabus, getting-started, ta-guide
- Fix renamed chapter slugs: dl_primer→nn_computation, efficient_ai→
  optimizations/model_compression, serving→model_serving, ops→ml_ops,
  responsible_ai→responsible_engr (vol1), on_device_inference→model_serving
- Same pattern for all /vol2/{slug}.html → /vol2/contents/vol2/...
- Update broken tinytorch/INSTRUCTOR.html link to tinytorch/ index

Cloudflare redirects (external inbound traffic):
- Add shared/config/cloudflare-redirects.json with 37 301 redirects covering
  all broken short-form chapter URLs
- Add infra-cloudflare-redirects.yml workflow that syncs the JSON to
  Cloudflare's Redirect Rules API on every push to dev/main; uses
  secrets.CLOUDFLARE_REDIRECTS_TOKEN (separate from Cache Purge token)

Link checker gap (root cause of missed 404s):
- Add shared/config/.lycheeignore-nightly without the blanket mlsysbook.ai
  exclusion that was silently swallowing all broken internal links
- Update infra-link-rot-nightly.yml to use .lycheeignore-nightly so the
  nightly sweep now catches broken mlsysbook.ai links that are live post-deploy
2026-06-08 20:26:39 -04:00
Vijay Janapa Reddi
2dd81eaa38 Add shared ecosystem cards to landing pages
Add shared/config/_ecosystem-cards.qmd: one source of truth for the
'Part of the MLSysBook Ecosystem' section (full 7-member set). Each landing
page includes it; a small script marks the current site 'You are here' by
reading that site's own release-manifest meta, so one block shows each page
its own state. Replace the divergent inline sections in kits (3 hand-written
cards) and labs (prose paragraph); add the section to slides, instructors,
and mlsysim.
2026-06-03 15:34:30 -04:00
Vijay Janapa Reddi
5a6c71911d Standardize shared footer and version pill across Quarto sites
Add shared/config/footer-common.yml (mirrors navbar-common.yml) and point
slides, labs, kits, and instructors at it via metadata-files, replacing their
drifted inline footers. The release pill now lives in the footer right slot
(not dangling below the page) as a fixed muted-gray chip, pinned so it looks
identical regardless of each site's theme accent. mlsysim and the book keep
their own footers (dual license / build stamp) but carry the same pill span.
release-pill.html is now assets-only and populates every data-release-pill
span from each site's own release-manifest.
2026-06-03 15:27:50 -04:00
Vijay Janapa Reddi
bd3b2302ae Recognize tutorial_module{1..4}.pdf as CI-injected artifacts
The mlsysim/docs/slides.qmd page links to tutorial_module1-4.pdf as
download buttons. These PDFs are not committed to the repo — they are
built from mlsysim/tutorial/slides/*.tex by mlsysim-build-pdfs.yml and
copied into mlsysim/ on gh-pages by mlsysim-update-pdfs.yml at deploy.

The check-internal-links hook already has CI_INJECTED_BASENAMES for
this exact pattern (mlsysim-paper.pdf is the established precedent).
Added the four tutorial-module basenames to the same set so the hook
stops flagging them as broken local links.

Comment updated to name both workflows that produce the artifacts, so
future maintainers can trace the build pipeline from this allowlist.
2026-05-28 21:20:07 -04:00
Vijay Janapa Reddi
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).
2026-05-27 08:25:12 -04:00
farhan
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.
2026-05-23 11:07:10 +05:00
Vijay Janapa Reddi
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.
2026-05-18 21:24:15 -07:00
Rocky
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.
2026-05-18 18:51:47 +05:30
Vijay Janapa Reddi
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.
2026-05-13 21:52:11 -04:00
Vijay Janapa Reddi
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.
2026-05-13 20:35:21 -04:00
Vijay Janapa Reddi
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.
2026-05-13 20:30:31 -04:00
Vijay Janapa Reddi
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.
2026-05-13 19:33:45 -04:00
Vijay Janapa Reddi
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.
2026-05-06 08:10:02 -04:00
Farhan Asghar
f70e47ca5e fix(instructors): keep theme toggle visible across breakpoints (#1599)
Co-authored-by: Vijay Janapa Reddi <vj@eecs.harvard.edu>
2026-04-29 10:09:10 -04:00
Vijay Janapa Reddi
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.
2026-04-29 08:47:26 -04:00
Vijay Janapa Reddi
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"
2026-04-29 08:47:26 -04:00
Vijay Janapa Reddi
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.
2026-04-29 07:36:55 -04:00
Vijay Janapa Reddi
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).
2026-04-29 07:29:47 -04:00
Vijay Janapa Reddi
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.
2026-04-29 07:14:11 -04:00
Vijay Janapa Reddi
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.
2026-04-28 18:06:07 -04:00
Vijay Janapa Reddi
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>
2026-04-28 16:45:26 -04:00
Vijay Janapa Reddi
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>
2026-04-27 19:59:50 -04:00
Vijay Janapa Reddi
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
2026-04-26 11:27:40 -04:00
Vijay Janapa Reddi
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).
2026-04-26 09:49:00 -04:00
Vijay Janapa Reddi
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.
2026-04-26 09:38:08 -04:00
Vijay Janapa Reddi
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.
2026-04-26 09:07:53 -04:00
Vijay Janapa Reddi
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.
2026-04-24 16:01:11 -04:00
Vijay Janapa Reddi
b011cd62a4 Merge pull request #1394 from harvard-edge/feat/socratiq
feat: add socratiq directory (excluding node_modules and dist)
2026-04-24 13:37:04 -04:00
Vijay Janapa Reddi
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.
2026-04-24 12:26:42 -04:00
Vijay Janapa Reddi
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.
2026-04-22 17:20:25 -04:00
Vijay Janapa Reddi
5f97cca590 Merge remote-tracking branch 'origin/dev' into dev 2026-04-22 16:16:00 -04:00
Vijay Janapa Reddi
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.
2026-04-22 15:54:52 -04:00
Vijay Janapa Reddi
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.
2026-04-22 15:16:39 -04:00
Vijay Janapa Reddi
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/.
2026-04-22 14:38:18 -04:00
kai4avaya
1e12b3474b refactor: document SocratiQ canonicals in sync-mirrors.sh 2026-04-21 19:32:32 -04:00
Vijay Janapa Reddi
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.
2026-04-21 17:41:22 -04:00
Vijay Janapa Reddi
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.
2026-04-21 17:41:22 -04:00
Vijay Janapa Reddi
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.
2026-04-21 16:43:42 -04:00
Vijay Janapa Reddi
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.
2026-04-20 13:36:36 -04:00
Vijay Janapa Reddi
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.
2026-04-20 13:26:23 -04:00
Vijay Janapa Reddi
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.
2026-04-20 13:10:35 -04:00