974 Commits
Author SHA1 Message Date
Vijay Janapa ReddiandGitHub e1e15702e6 Merge pull request #1911 from farhan523/fix/footer-hydration-utc
fix(staffml): format Footer build date in UTC to avoid hydration mismatch
2026-07-10 23:43:27 +02:00
Vijay Janapa ReddiandGitHub e7fbd500b1 Merge pull request #1912 from farhan523/fix/practice-stale-userAnswer
fix(staffml): keep practice-page Cmd+Enter handler fresh via latest-callback refs
2026-07-10 23:43:24 +02:00
Vijay Janapa ReddiandGitHub 6b9bdaf724 Merge pull request #1913 from farhan523/fix/stargate-clear-verify-timeout
fix(staffml): cancel StarGate's verify-delay timer on unmount
2026-07-10 23:43:21 +02:00
Vijay Janapa Reddi dbdae196ad chore(deps): bump ws, brace-expansion, @babel/core to patched versions
Resolve three Dependabot advisories via lockfile-only bumps:
- ws 8.20.1 -> 8.21.0 (GHSA-96hv-2xvq-fx4p, root, transitive via puppeteer-core)
- brace-expansion 5.0.5 -> 5.0.7 (GHSA-jxxr-4gwj-5jf2, interviews/staffml)
- @babel/core 7.29.0 -> 7.29.7 (GHSA-4x5r-pxfx-6jf8, interviews/staffml)
2026-07-03 22:04:45 +02:00
Vijay Janapa Reddi 6e93c4a007 Merge origin/dev into local dev (origin-pull 2026-07-02)
# Conflicts:
#	book/quarto/contents/vol1/ml_ops/ml_ops.qmd
2026-07-02 21:16:57 +02:00
Vijay Janapa Reddi 0cee335b77 fix(staffml): point EcosystemBar Subscribe at the newsletter page
The Subscribe link (desktop and mobile) targeted a non-existent #subscribe
anchor. Point both at https://mlsysbook.ai/newsletter/, matching the canonical
shared/config/navbar-common.yml, which documents why an internal #subscribe
anchor is avoided (Safari content blockers hide anchors containing 'subscribe').
Fixes #1844.
2026-07-02 18:33:12 +02:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
66a1977f11 chore(deps-dev): bump js-yaml from 4.1.1 to 4.2.0 in /interviews/staffml (#1915)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.2.0)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.2.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 17:49:50 +02:00
farhan 91fe269d91 fix(staffml): cancel StarGate's verify-delay timer on unmount
Both handleStar ("Star on GitHub") and handleAlreadyStarred ("I already
starred") fired `setTimeout(onVerified, 1500)` to let the user see the
"Thank you" confirmation panel before the parent dismisses the gate.
The returned id was never stored, so if the gate unmounted within that
1.5s window (parent route change, programmatic close, anything that
removes StarGate from the tree), the timer kept running and `onVerified`
fired later on an unmounted parent.

Today the only caller (practice/page.tsx) treats a stray onVerified call
as a no-op — it just sets `showStarGate=false` which was already false —
so this was a latent leak, not a current bug. But the contract becomes
accidentally correct rather than correct-by-design, and a future caller
where onVerified mutates state on a stale tree would surface it.

- Store the timer id in `verifyTimeoutRef`.
- Add a no-deps cleanup effect that clears `verifyTimeoutRef.current` on
  unmount (mirrors the existing focus-management cleanup in the same
  file).
- Both handlers assign into the same ref, which also implicitly handles
  the unlikely back-to-back click case (second click overwrites the ref;
  the first timer still runs but we have no path to reach that since the
  panel transitions to the "retired" state after the first click).

Regression test (3 cases): unmount-after-Star cancels onVerified,
unmount-after-Already-starred cancels onVerified, and the still-mounted
case still fires onVerified at 1.5s.
2026-06-28 17:52:43 +05:00
farhan 9ea0c6a7e0 fix(staffml): keep practice-page Cmd+Enter handler fresh via latest-callback refs
The global keyboard listener at practice/page.tsx is intentionally
stable — re-armed only when showAnswer/current/pickRandom change so it
doesn't churn on every keystroke. But the handlers it calls
(handleReveal, handleScore) close over state that DOES change between
listener re-arms:

- handleReveal closes over `userAnswer`. The user types in the
  textarea, presses Cmd/Ctrl+Enter, and the stale listener invokes the
  OLD handleReveal — which sees `userAnswer.trim().length === 0` even
  after 100+ chars are typed and the think-guard (`elapsedMs < 15000
  && charsTyped < 50`) misfires with a "are you sure?" modal AFTER the
  user has deliberated.
- handleScore closes over `effectiveMaxScore` (derived from
  `rubricItems` / `napkinResult`) and `questionsAnswered`. If the user
  ticks rubric checkboxes after revealing the answer, `effectiveMaxScore`
  changes — but the digit-key listener still calls a stale handleScore
  that caps the score at the OLD max.

The Reveal/score buttons are unaffected; they invoke a fresh closure
on click.

Fix uses the latest-callback ref pattern (same shape as
useVisibilityPoll from PR #1834): two refs holding the most-recent
handler, refreshed in a no-deps useEffect after every render, and the
keydown listener calls `ref.current(...)` instead of the function
directly. Listener stays stable, calls always see the latest closure.
2026-06-28 17:41:21 +05:00
farhan 1a1f24788c fix(staffml): format Footer build date in UTC to avoid hydration mismatch
Footer's `new Date(BUILD_DATE).toLocaleDateString("en-US", {...})` had no
`timeZone` option, so it formatted in the runtime's local timezone. With
Next's static export the HTML is generated at build time on the build
server (UTC in CI) and hydrates on the client whenever the user visits.
Near day/year boundaries the two TZs disagree on the formatted date:

  BUILD_DATE = "2026-12-31T23:30:00Z"
    build server (UTC): "Dec 31, 2026"
    client in UTC+1:    "Jan 1, 2027"

That's a hydration mismatch warning AND a wrong label (the build IS from
Dec 31 UTC, not Jan 1 in the viewer's TZ — the latter is misleading).

Same shape as PR #1843 for PaperCitationCard; this is the matching fix
for Footer.

- Add `timeZone: "UTC"` to the `toLocaleDateString` options so the label
  reflects the canonical build instant, not the viewer's clock.
- Regression test mocks `@/lib/stats` with `BUILD_DATE = 2025-01-01T01:00:00Z`
  (which is Dec 31 2024 in PT) and asserts the label reads "Jan 1, 2025"
  even when `vi.setSystemTime` puts "now" in a different year.
2026-06-28 17:33:47 +05:00
farhanandVijay Janapa Reddi dc054878ae fix(staffml): track and clear toast auto-dismiss timers
Each toast scheduled a 4s setTimeout to auto-dismiss, but the timer handle
was discarded, so it was never cleared. If the provider unmounted within 4s
the timer still fired setState on a dead tree, and a manual dismiss left the
timer running to a no-op tick.

Track timers in a ref keyed by toast id, cancel the timer on manual dismiss,
and clear all pending timers when the provider unmounts.

Adds a regression test asserting timers are cleared on unmount and on manual
dismiss (and that flushing timers afterward does not throw).

(cherry picked from commit aa682c9363)
2026-06-26 10:28:00 -04:00
farhanandVijay Janapa Reddi 54536f6d14 fix(staffml): remove invalid HTML button nested inside the explore SVG
The radial explorer's center zoom-out affordance was an HTML <button>
wrapping an SVG <circle>. A <button> is not valid SVG content: it doesn't
focus reliably across browsers and exposed no accessible name. Move the
click handler onto the <circle> directly and only show the pointer/hover
affordance when there is a parent to zoom out to.

The keyboard-accessible navigation is unchanged — the Breadcrumb above the
chart and the ExplorerPanel beside it provide real <button> controls for the
same focus changes, and the SVG is exposed to assistive tech as a single
labelled role="img".

Adds a regression test asserting the SVG contains no nested HTML <button>.

(cherry picked from commit 602dc80f25)
2026-06-26 10:28:00 -04:00
farhanandVijay Janapa Reddi e3bf3e7e21 fix(staffml): give the framework primitive-detail modal dialog semantics and focus management
The PrimitiveDetail overlay rendered as a plain <div>: no role="dialog",
aria-modal, or aria-labelledby, and no focus management — so screen readers
didn't announce it as a dialog and keyboard focus stayed in the dimmed page
behind it. (Escape was already handled by the page-level listener.)

Add dialog semantics labelled by the primitive name, move focus to the close
button on open and restore it on close, and trap Tab within the panel —
mirroring the existing KeyboardShortcutsOverlay / CommandPalette pattern.
PrimitiveDetail is exported so it can be tested in isolation.

Adds a regression test covering dialog attributes, labelling, mount focus,
and focus restoration.

(cherry picked from commit 0652502995)
2026-06-26 10:28:00 -04:00
farhanandVijay Janapa Reddi c391981258 fix(staffml): give StarGate proper dialog semantics and focus management
StarGate is a full-viewport blocking overlay but rendered as a plain <div>:
no role="dialog", aria-modal, or aria-labelledby; no Escape handler; and no
focus management, so the dimmed page behind it stayed keyboard-reachable and
screen readers didn't announce it as a dialog.

Mirror the existing KeyboardShortcutsOverlay / CommandPalette pattern: add
dialog semantics labelled by the heading, move focus to the primary CTA on
mount and restore it on unmount, trap Tab within the surface, and dismiss on
Escape (counts as a dismiss).

Adds a regression test covering dialog attributes, mount focus, Escape
dismiss, and focus restoration.

(cherry picked from commit 0b62e82bba)
2026-06-26 10:27:59 -04:00
farhanandVijay Janapa Reddi aabb7dcc18 fix(staffml): expose expanded state on Nav disclosure toggles
The Tools dropdown toggle and the mobile hamburger show/hide a menu but
exposed no open/closed state to assistive tech — the only cue was a visual
chevron rotation, so screen-reader users couldn't tell the menu was open.

Add aria-haspopup, aria-expanded, and aria-controls to both toggles, give
each controlled menu a matching id, and mark the decorative chevron/menu
icons aria-hidden.

Adds a regression test asserting the ARIA wiring toggles with state.

(cherry picked from commit ea6a46e09b)
2026-06-26 10:27:59 -04:00
farhanandVijay Janapa Reddi 2f7333c9ba fix(staffml): guard localStorage access in ThemeProvider against strict-storage browsers
getInitialTheme() read localStorage without a try/catch, while the matching
writer (toggleTheme) was already guarded. In browsers that block storage
(Safari 'Block all cookies', sandboxed iframes without allow-same-origin),
even accessing window.localStorage throws a SecurityError, so the mount effect
threw before reconciling and the theme stayed stuck at the SSR default.

Funnel every access through shared safeGet/safeSet helpers so reads and writes
share one guard and a future caller can't reintroduce an unguarded access.

Adds a regression test that mounts ThemeProvider with a throwing getItem.

(cherry picked from commit ddd5202c5a)
2026-06-26 10:27:59 -04:00
Vijay Janapa Reddi b933251ac9 docs: backfill missed contributor credits 2026-06-24 07:52:19 -04:00
Vijay Janapa Reddi 7d1c2fd544 fix: honor StaffML base path in announcement 2026-06-23 07:20:25 -04:00
dependabot[bot]andGitHub 6b6dd95b04 build(deps): bump undici and wrangler in /interviews/staffml-vault-worker (#1891) 2026-06-21 11:21:07 -04:00
dependabot[bot]andGitHub 9b3f831c0c build(deps): bump undici and wrangler in /interviews/staffml/worker (#1889) 2026-06-21 11:21:04 -04:00
dependabot[bot]andGitHub 1504c03369 build(deps): bump undici to 7.28.0 in /interviews/staffml (#1890) 2026-06-21 11:21:02 -04:00
Vijay Janapa ReddiandGitHub fefc61bd67 deps(vault-worker): bump vitest 4.1.5 -> 4.1.6 (#1881)
Resolves the lockfile conflict that blocked dependabot #1768 by
regenerating interviews/staffml-vault-worker/package-lock.json against
current dev.
2026-06-16 18:52:16 -04:00
Vijay Janapa ReddiandGitHub e06ad060d3 style(vault-cli): sort imports in legacy_export.py (ruff I001) (#1879)
Import block flagged by the pre-push ruff hook; landed via #1851 whose CI
did not run this local hook. Pure import reordering, no behavior change.
2026-06-16 18:43:01 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
32d37e2373 build(deps): bump vite from 8.0.6 to 8.0.16 in /interviews/staffml (#1861)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.6 to 8.0.16.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-15 21:27:35 -04:00
Farhan AsgharandGitHub 222683c57d feat(staffml): link each question to recommended textbook reading (#1851)
Derive a topic→chapter "Learn more" pointer for every question at build
time from schema/topic_chapter_map.yaml, closing the loop from practice
back into the textbook (issue #1822, Phase 1).

vault-cli:
- BookRefResolver joins topic→chapter, reads chapter titles from each
  chapter's .qmd H1, and link-checks every mapped chapter at build time
  (a missing .qmd fails the build — the old "defer until URLs stabilize"
  blocker becomes "URLs are enforced valid"). book_refs is emitted
  top-level so it rides into the summary bundle and renders synchronously.
- Regenerates corpus-summary.json: all 9,525 published questions now
  carry book_refs (100% topic coverage).

staffml:
- BookRefCard shows the primary chapter + authored "why" line + also_see
  chapters, after the attempt (go-deeper pointer, not an answer key).
- Supersedes ChapterLinks; retires chapter-map.json/chapters.ts.

Fixes live 404s: ChapterLinks built /contents/vol1/<ch>/ URLs that 404;
the verified-200 pattern is /vol1/contents/vol1/<ch>/<ch>.html.

Tests: adds test_book_refs.py (resolver + link-checker regression).
2026-06-15 21:20:52 -04:00
Farhan AsgharandGitHub b3ce47fdae fix(staffml): announce toasts to assistive tech and label the dismiss button (#1858)
Toasts (badge unlocks, streaks, success messages) were invisible to
screen readers: the container was a plain <div> with no live region, so
nothing was announced when a toast appeared. The icon-only dismiss
button also had no accessible name — screen readers read just "button".

Make the always-mounted container a polite live region
(role="region" + aria-label + aria-live="polite", aria-atomic="false"
so only the newly-added toast is announced, not the whole stack), and
give the dismiss button an aria-label, with the X icon marked
aria-hidden. Mirrors the existing VersionDriftToast pattern.

Adds a regression test asserting the live-region attributes and the
dismiss button's accessible name.
2026-06-15 21:20:49 -04:00
Farhan AsgharandGitHub 69c1be23c0 fix(staffml): keep command-palette active row in view during keyboard nav (#1857)
The Cmd+K result list is scrollable (max-h-[60vh]). Arrowing past the
fold updated aria-activedescendant and the highlight class but never
scrolled the active row back into the viewport, so keyboard-only users
lost the highlight off-screen on longer result sets.

Scroll the active row into view whenever activeIdx changes, using
block:"nearest" so an already-visible row (e.g. one set active via
onMouseEnter hover) never triggers a scroll jump.

Adds a regression test covering ArrowDown/ArrowUp navigation, plus a
jsdom scrollIntoView stub in the shared test setup (jsdom doesn't
implement it).
2026-06-15 21:20:46 -04:00
Farhan AsgharandGitHub fc3483571b fix(staffml): mount the command palette and shortcuts overlay (#1856)
CommandPalette and KeyboardShortcutsOverlay were added in the client-UX
overhaul (cefa8bfe63) with tests and docblocks stating they are "mounted
in app/layout.tsx" — but the mount line was never added. They have been
dead since introduction: the navbar search icon dispatches
`staffml:open-palette` into the void, and Cmd/Ctrl+K and `?` have no
listener mounted, so all three do nothing on every route.

Render both overlays once in RootLayout, inside <Providers> so the
palette can read the vault via useVault(). Adds a Playwright smoke test
covering the three previously-dead entry points (Ctrl+K, the navbar
search icon, and `?`).
2026-06-15 21:20:44 -04:00
Vijay Janapa ReddiandGitHub 93633198fd Merge #1845: trap focus inside the AskInterviewer waitlist modal
fix(staffml): trap focus inside the AskInterviewer waitlist modal
2026-06-10 15:13:24 -04:00
Vijay Janapa ReddiandGitHub 2773e2780c Merge #1834: pause Nav due-count poll on hidden tabs + cross-tab sync
fix(staffml): pause Nav due-count poll on hidden tabs + sync across tabs
2026-06-10 15:13:21 -04:00
Vijay Janapa ReddiandGitHub ea52761750 Merge #1843: derive PaperCitationCard year from buildDate (stable hydration)
fix(staffml): derive PaperCitationCard year from buildDate, not `new Date()`
2026-06-10 15:13:18 -04:00
farhan 3ad380f563 fix(staffml): trap focus inside the AskInterviewer waitlist modal
The waitlist modal carried `role="dialog" aria-modal="true"` but didn't
enforce the contract: Tab/Shift+Tab escaped into background controls
behind the backdrop, and the email input wasn't auto-focused on open —
keyboard users landed in the page body and had to hunt for the input.

- Capture `document.activeElement` on mount and restore it on unmount
  so focus returns to whatever the user was on when they closed the
  modal (the original trigger button).
- Auto-focus the email input on open via the same `setTimeout(0)`
  pattern used by CommandPalette and KeyboardShortcutsOverlay.
- Add a Tab/Shift+Tab cycle on the modal surface so focus stays inside
  the dialog while it's open. Mirrors the trap in CommandPalette so the
  two modals behave identically for keyboard users.
- Export `WaitlistModal` as a named export from AskInterviewer.tsx so
  the regression test can render it directly without spinning up the
  full AskInterviewer parent and triggering its open state.

Regression test (5 cases) covers: autofocus on open, focus restoration
on unmount, forward Tab cycle from last → first, backward Tab cycle
from first → last, and that Tab in the middle of the trap falls
through to the browser's native handling (we only block at the edges).
2026-06-06 08:07:06 +05:00
farhan d7a7c3841e fix(staffml): derive PaperCitationCard year from buildDate, not new Date()
The BibTeX entry took its year from `new Date().getFullYear()` — the
render-time year on the client. With Next's static export the HTML is
generated at build time and hydrates on the client whenever the user
visits, so server (build) and client (hydrate) could disagree on the
year:
  - around midnight UTC on Dec 31 / Jan 1, or
  - any time the site is viewed in a year after the build year.
Hydration mismatch warning either way, and the wrong anchor for a
citation regardless.

- Make `buildDate` required on PaperCitationCardProps (the only call
  site at app/about/page.tsx already passes it from `BUILD_DATE` in
  lib/stats). Type-level guarantee that callers provide the anchor.
- Derive the BibTeX year via `new Date(buildDate).getUTCFullYear()` so
  the year matches the build server's UTC reference regardless of where
  the viewer sits.
- `buildDateLabel` simplifies: no `buildDate ? ... : null` ternary, just
  the formatted string.

Regression test mocks the system clock to a different year than
buildDate and asserts the BibTeX still reflects buildDate. Also covers
re-render with a new buildDate and the timezone-stability case
(2025-01-01T01:00:00Z is Dec 31 2024 in PT but must read as 2025 UTC).
2026-06-06 07:15:14 +05:00
farhan 4f31ebda1a fix(staffml): pause Nav due-count poll on hidden tabs + sync across tabs
Nav polled getDueCount every 30s indefinitely via a bare setInterval,
even on backgrounded tabs. Cheap per call (it reads localStorage), but
unnecessary wakeups burn battery on mobile and add no value when no
one is looking.

- Extract useVisibilityPoll(callback, intervalMs) into src/lib/hooks/.
  Clears the interval on visibilitychange→hidden, re-arms it on visible
  with an immediate catch-up tick. The callback is held in a ref so a
  fresh closure on every render is picked up without tearing down and
  re-arming the interval — only intervalMs is a real dependency.
- Wrap each tick in try/catch INSIDE the hook. If the callback throws
  on the mount-time synchronous tick, the exception would otherwise
  propagate out of the effect body before React receives the cleanup,
  leaving the already-armed setInterval orphaned and re-throwing every
  intervalMs with no way to cancel. Logging in the hook means the worst
  case is per-tick console errors, not a leak.
- The hook is explicit in its docs that it is NOT a cross-tab sync
  primitive — it only catches up on visibility transitions, not on
  concurrent visible tabs. For that case Nav also adds a `storage`
  event listener so a write in one tab refreshes the badge in any
  other open tab instantly.
- Replace Nav's empty `catch {}` with `console.error(...)` so a real
  failure (corrupted localStorage, JSON parse error) surfaces in
  devtools instead of presenting as a frozen badge. The refresh logic
  is hoisted into a single useCallback so the poll callback and the
  storage handler share one error-logging path.

Regression test (8 cases) covers: immediate mount fire when visible,
cadence while visible, pause when hidden, immediate catch-up on resume,
no-start when mounted while already hidden, cleanup on unmount, latest
callback closure used (ref pattern), and throw-isolation (a throwing
callback at mount does not kill the interval or skip listener wire-up).
2026-06-03 11:20:03 +05:00
farhan 34d4a7fdb1 fix(staffml): pin bootstrap-icons CSS with SRI + crossorigin
EcosystemBar injected the bootstrap-icons stylesheet from jsDelivr with
no `integrity` attribute. If jsDelivr was ever compromised or its CDN
served modified bytes, the browser would happily apply the foreign CSS
on every staffml pageview.

- Add `integrity="sha384-..."` pinning the v1.11.3 bytes (hash computed
  from the actual CDN file via `curl | openssl dgst -sha384 -binary |
  openssl base64 -A`).
- Add `crossOrigin="anonymous"` — required for SRI to be enforced on
  cross-origin resources.
- Add an `onerror` handler that logs to console with the recompute
  command. Without it the only symptom of a stale hash (e.g. after a
  version bump) would be silently missing icons.
- Comment includes the recompute recipe so future version bumps update
  the hash deterministically.
2026-06-03 10:32:45 +05:00
Vijay Janapa Reddi ca14b4ca5d Merge PR #1819: staffml: reset QuestionVisual failed state on visual.path change 2026-06-01 08:16:34 -04:00
Vijay Janapa Reddi fbada3fb93 Merge PR #1820: staffml: drop misleading role=button from MetaTooltip; un-nest 2026-06-01 08:16:34 -04:00
Vijay Janapa Reddi a3e2761d6f Merge PR #1821: staffml: ChainStrip dots accessible names + current-step marker 2026-06-01 08:16:34 -04:00
Vijay Janapa Reddi c8b887249c docs: generalize internal rule-file references in comments
Replace pointers to the private project rules/docs tree (relative .claude/rules
and .claude/docs paths) in code comments and docstrings with neutral phrasing
("the project prose style guide", etc.). Load-bearing runtime paths that the
tooling reads or writes are left intact.
2026-05-30 17:32:19 -04:00
Vijay Janapa Reddi 4dbfe7152a fix(ci): repair labs WASM import + codespell typo surfaced by CI
- labs-validate-dev.yml: the Pyodide/WASM export smoke test imported the
  pre-taxonomy-refactor path `from mlsysim.core.engine import Engine`. The
  refactor moved Engine to mlsysim/engine/engine.py (exported at the package
  top level). Use the canonical public import `from mlsysim import Engine`.
  Fixes the "ModuleNotFoundError: No module named 'mlsysim.core.engine'" failure.
- LIVE_INTERVIEWER_PLAN.md: "pre-selects" -> "preselects" (codespell dictionary).
2026-05-29 23:56:21 -04:00
Vijay Janapa Reddi cfc64a436a Merge dev into feat/staffml-book-refs for retire 2026-05-29 10:06:05 -04:00
Vijay Janapa Reddi ed68fda58c StaffML: add draft topic→chapter map + recommended-reading design
First-pass topic_chapter_map.yaml (87 topics → book chapters, primary +
also_see + rationale) plus the design analysis for connecting each StaffML
question back to recommended textbook reading. Seeds the future work
tracked in harvard-edge/cs249r_book#1822. Not yet wired into the build.
2026-05-29 10:05:53 -04:00
Vijay Janapa Reddi daa2754707 Merge dev into fix/vault-placeholder-titles for retire 2026-05-29 09:44:37 -04:00
Vijay Janapa Reddi 14fc4ae3f0 Merge dev into fix/staffml-grading for retire 2026-05-29 09:44:00 -04:00
Vijay Janapa Reddi 72eb537ded feat(vault): make teaching_power gate Bloom-graded (recall is valid at L1)
StaffML is an assessment instrument, not a textbook: recall is a first-class
skill and L1/L2 questions are warm-up/screening (paper sec:levels). A flat
'must reason' bar wrongly rejects valid recall items. Grade the floor by Bloom
level instead — L1/L2 pass at tp>=1, L3 at tp>=2, L4/L5/L6+ at tp>=3 — so the
gate fails only a level/depth MISMATCH (recall masquerading as L5), not recall
doing its job. Re-applied to the backfill: 2,700 below-floor (28%, concentrated
at L4-L6+), down from a misleading flat 43%.
2026-05-29 09:39:01 -04:00
Vijay Janapa Reddi c7133a7ce4 feat(vault): add teaching_power gate to the publish pipeline
The pipeline validated correctness only (schema/format/level-fit/coherence/math)
— nothing checked whether a question TEACHES, so ~22% of published questions are
correct-but-vacuous (recall/vocab + decorative math). Add gate_teaching_power:
passes iff tp>=3 and not vacuous, calibrated against gold-standard exemplars (not
same-cell peers, the level_fit blind spot that lets vacuity propagate). Validated
on 146 labeled questions: 90% within 1pt of the independent scorer, 9/9 trusted
golds pass. Documented the bar in AUTHORING.md.
2026-05-29 08:41:00 -04:00
Vijay Janapa Reddi 3952a73377 fix(vault): replace 76 placeholder slug-titles with descriptive titles
The flagged titles were machine-generated slug stubs ("Cloud Gpu Virtualization
L3 0") leaking track prefix, level/index, and wrong acronym case. Regenerated
descriptive, scenario-grounded titles (e.g. "Partitioning an A100 MIG for 7B and
1B Models") via content-aware pass; spot-checked for accuracy against scenarios.
2026-05-29 08:36:07 -04:00
Vijay Janapa Reddi e8a2a85004 test(rendering): DOM render-verification for MCQ, math, currency, bold
Asserts the actual rendered output (jsdom + testing-library), not just types:
MCQ marks correct-green/wrong-red on reveal; $...$ renders as KaTeX; bare-$
currency renders verbatim (regression guard); bold survives the math-split pass.
2026-05-29 07:40:55 -04:00
Vijay Janapa Reddi 039eaf44e4 fix(rendering): don't treat bare-$ currency as math
The MATH_SPLIT regex matched any $...$ span on a line, so cost questions
using bare-$ currency ("Maintenance ($3M) ... ($50)") rendered the text
between two dollar signs as garbled KaTeX. ~5% of published vault questions
(544/9,525) hit this. Gate math on a LaTeX-signal in the body (backslash,
caret, underscore, brace); currency has none, real math always does.
Verified: 3 currency samples render as text, 3 real-math strings as math.
2026-05-29 07:32:57 -04:00
Vijay Janapa Reddi 01cbe05db1 feat(practice,gauntlet): MCQ self-check, markdown scenarios, wire unit-aware grading
A1 — MCQ self-check: new MCQOptions component renders selectable choices when a
question carries details.options + correct_index. Pre-reveal the learner picks
an option; on reveal the correct option is marked green and a wrong pick red,
keeping all distractors. Deliberately a self-check — NOT wired into the
SR/scoring pipeline; the open self-rating (Skip/Wrong/Partial/Nailed) stays
authoritative. Rendered in both practice and gauntlet flows. Non-MCQ
open-response path is unchanged.

A4 — scenario/question/answer text now routes through MarkdownText everywhere
(practice scenario was raw GlossaryText; gauntlet scenario, common_mistake,
realistic_solution, and the results-phase review were raw text), so **bold**
and $math$ render instead of showing literal markers.

Grading wiring — the live reveal path now gates numeric grading on
isNumericQuestion(current) (previously ungated) and calls gradeNapkinAnswer,
preserving the setNapkinResult shape and extending the display to show units
and an "→ equivalent" marker for unit-equivalent answers.
2026-05-29 07:28:45 -04:00