How this is builtGroundworkHow this is built
24 / 26 written chapters

A5 · Part 3 · How it holds up

Current health: what is actually wrong

The checks pass. The debt is in a stuck framework version, a few very large files, repeated code, and documents that have drifted from the code.

6 min read1 diagram7 sections
07On this page
  1. What passes
  2. 1. The framework version is stuck, and it matters
  3. 2. A few files carry most of the weight
  4. 3. Duplication
  5. 4. Drift between the documents and the code
  6. 5. Known and accepted
  7. The one thing that looks broken and is not

What passes

CheckResult, latest local run
TypeScript, ESLint, Prettier, cspellclean
Vitest1630 of 1630
Build1,803 prerendered routes
Playwright, one smoke spec27 of 27
Lighthouse budgets, 3 URLs, 3 runs eachpass, last run before CI was removed
TODO, FIXME, HACK in source0
npm audit, production dependencies1 critical, 1 high

The zero on the TODO row needs a footnote. This codebase has no comments at all, and a pre-push hook enforces that, so there is nowhere in the code for a TODO to live. A zero here does not mean there is no debt. It means the debt has to be written down somewhere else, and this chapter is that place.

1. The framework version is stuck, and it matters

npm audit --omit=dev reports two things:

  • Next 16.3.0, critical. Advisories cover Next 16.0.0 to 16.3.2: remote code execution on Windows-hosted servers, and remote code execution in the Image Optimization API when AVIF files are used. The newest release is 16.3.6.
  • sharp, high. Vulnerabilities in libheif. The full audit, dev tools included, adds a high advisory against js-yaml.

The exposure here is probably narrow. The site imports next/image nowhere and does not run on Windows. But "probably not exploitable here" is a reason to be calm, not a reason to stay on the version.

The reason it has not moved is on the record. Dependabot opened a grouped Next update. Its CI run failed Lighthouse on one slow sample: 0.91, with 245 ms of blocking time, on a page that measures 100 and 0 ms locally. It was closed with the others. All 13 Dependabot pull requests so far have been closed, and none merged. Lighthouse now takes three samples, so the same update deserves another run.

Two other updates cannot land without a hand:

  • Pyodide fails a unit test until the version typed into lib/wasmAssets.ts is changed to match.
  • The dev-tooling group fails at install, because esbuild is pinned to exactly 0.25.12 and a newer Vite wants more.

2. A few files carry most of the weight

Line counts from wc -l as of 11 October 2026, for source outside content/ and for the two largest content files:

app/globals.css content/interview-data.ts content/topics.ts whiteboard/Board.tsx PracticeWorkspace.tsx whiteboard/whiteboard.module.css 7,288 4,745 4,626 1,993 1,825 1,636 lines as of 11 October 2026, one bar length per line count
One stylesheet is still the longest file, about half as long again as the next. The two yellow bars are content, which is expected to be long. The rest are components and their styles.

app/globals.css, about 7,400 lines, is the largest single piece of debt. The newer parts of the site (the mock interview, the whiteboard and the problems page) use CSS modules, and the older reader and editor styles still live in the global sheet. Every page downloads all of it, and nothing tells you which rules are dead.

The three big components each hold a whole feature in one file:

ComponentLinesuseStateuseEffectuseRef
app/whiteboard/Board.tsx1,993251724
components/practice/PracticeWorkspace.tsx1,82526815
components/practice/CodeEditor.tsx1,11822813

None of them has a unit test, and the browser smoke spec only touches the playground. A refactor of them is verified by hand. The pure parts have already been pulled out and are tested: lib/whiteboard/model.ts is 695 lines under 17 tests. The next useful split is the same move for the playground's file and run state.

3. Duplication

  • Three runners, one pattern. lib/runner.ts, lib/pythonRunner.ts and lib/scriptRunner.ts each keep their own module-level worker, their own getWorker and discardWorker, and their own timeout-and-terminate logic. The default timeouts disagree: 5 seconds for JavaScript, 20 for everything else. A fix to one does not reach the others.
  • Two share formats, two validators. The playground and the whiteboard share lib/compress.ts, but each validates its own payload. That one is reasonable, because the shapes differ. It is listed so it stays a choice rather than an accident.

4. Drift between the documents and the code

The code moved faster than what was written about it. Each row below was checked against the repository today.

WhereSaysIs
docs/ROADMAP.md602 prerendered pages1,803 prerendered routes in a local build
docs/ROADMAP.md, section 6A weekly cleanup keeping 5 and 5, with 24 hours of graceDaily, keeping 3 and 2, with 6 hours
docs/ROADMAP.md, section 6public/wasm/ is 18 MB1.4 MB; the runtimes come from jsDelivr
docs/ROADMAP.md, section 7No error service; /git shows crawlers 42 charactersSentry is wired behind a DSN; /git renders its body on the server
scripts/vercel-cleanup.mjsDefaults of 5, 5 and 24 hoursThe workflow overrides them with 3, 2 and 6
lib/wasmAssets.tsSix runtime versions typed in by handOnly Pyodide and sql.js are checked against package.json. wasmoon, Ruby, php-wasm and Clang are not dependencies, so Dependabot never sees them
lib/fonts.tsKalam at weights 300, 400 and 700The roadmap flagged the 300 weight as barely used; it still ships

The chapters of this topic had drifted the same way, and that is what the rewrite fixes. Only the figures that tests/claims.test.ts checks are protected from drifting again.

5. Known and accepted

IssueSeverityState
SQL runs on the main thread with no timeoutRealA runaway query freezes the reader's own tab. Moving sql.js into a worker would fix it.
The heaviest pages have no Lighthouse budgetReal/practice and the problem pages ship 422 KB of gzipped script and are not on the URL list.
The narrator depends on an unofficial serviceAcceptedmsedge-tts was a known risk when it went in. It degrades to no narration, not to a broken page.
The policy allows inline scripts and evalAcceptedNeeded by the theme script and the playground. Nothing a reader controls is rendered as HTML.
Progress lives in one browserAccepted, for nowNo accounts means no sync. See the roadmap chapter.

The one thing that looks broken and is not

Run the site with next start and every page logs two 404s, for /_vercel/insights/script.js and /_vercel/speed-insights/script.js. Those files exist only on Vercel's edge. Locally there is nothing to serve, so the analytics do not run. The smoke tests filter exactly these URLs. It is worth knowing, because it is the kind of thing that gets "fixed" by deleting the analytics.

A bug worth remembering

Two diagrams once lost every arrow. The arrows sat in their own group, and that group had the hand-drawn filter applied. When every child of a group is a straight line along one axis, the group's bounding box has no height or no width. The filter region is derived from that box, so the whole group was clipped away. Arrows now share a group with the boxes, which always has an area. It was invisible in the source and obvious in a screenshot.