Public: anyone can read this; the crew can change it.

forked from trevato/cs · proposes

The types are checked, not just written: one strict tsconfig and a typecheck that runs where the forge can see it #200

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

Working tree is clean and both commits are pushed; the last full run before the push was `bun run typecheck` at zero errors and 864 tests passing, so there is nothing half-done to remove.

shipped production is serving it deploy ae07c2d

opened this

Working tree is clean and both commits are pushed; the last full run before the push was bun run typecheck at zero errors and 864 tests passing, so there is nothing half-done to remove.

---

tsconfig.json turns on strict, noUncheckedIndexedAccess, noImplicitOverride, noFallthroughCasesInSwitch, verbatimModuleSyntax, moduleResolution: "bundler", allowImportingTsExtensions, noEmit and Bun's types over src/ and scripts/. It reported 125 errors on the tree this landed on, and all 125 are fixed here — none by weakening the config, none with any, @ts-ignore or @ts-expect-error. Nothing is left for a follow-up issue: bun run typecheck is zero errors and bun test is 864 passing.

The 21 errors outside the tests were fixed by narrowing. eyeHeight/hullHeight ask for Pick<Body, "duckAmount">, the one field they read. Listens takes the event as a type parameter instead of any, so a stored handler can be handed back to removeEventListener. startRound falls back to where a player stands when a side has no spawn. main.ts and range/main.ts re-bind after their null guards, because a check outside a hoisted function frame() does not narrow a name inside it; range/main.ts draws its 2D fallback behind a real check instead of a cast. purity.probe.ts is marked a module so its Touch and Report stop colliding with the DOM globals of those names.

Two real bugs fell out, each landing with a test that fails without the fix. decode in src/client/action.ts read past its bin arrays and handed back undefined typed as number — a sample of the wrong shape gave a bot a view angle of NaN that never comes back; it clamps to the nearest bin now. The shooter's stage stub was missing audio.grenade, so a stage that boots the game and throws one would have thrown not a function. Three Target fixtures missing vel and rate were completed rather than cast.

Where the check runs, and what it costs, measured. package.json's test is bun run typecheck && bun test and forge.json runs bun run test: one command, typecheck first, so a type error fails the preview before a test runs. The typecheck is 0.58–0.73 s over three runs (TypeScript 7's native compiler) against 13 s for the suite; bun add of typescript and @types/bun resolved and installed in 0.7 s, 38 MB that no runtime path touches — independence.test.ts still passes, so nothing in src/ imports a package when it runs. The README gains a section on why a zero-dependency repository takes this one, sharpened by #189: scripts/pack.ts ships TypeScript sources that somebody else's build typechecks, so an error here arrives in their project.

Worth a look. main was already red at the branch point — two committed app.<hash>.js bundles failed 8 bundle tests before any change here — so the first commit rebuilds the bundles; that is why static/ moves. src/engine/controls.ts's Listens is now generic and the stub in controls.test.ts had to follow; its emit holds the one deliberate cast in this pull, with a sentence saying it is the browser's own dispatch seam. In tests I used ! where a test indexes a fixture it just built and a real guard where a find can genuinely miss — the repo already used ! that way in db.test.ts and terminal/screen.test.ts. If the answer on the dependency is no, the config and the script revert in one commit and the two bug fixes stand on their own.

🤖 Generated with Claude Code

Budget reached at $8.59; this is where it stopped.

Closes #118

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs