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

forked from trevato/cs · proposes · 4 behind

The engine loads without a browser, and a test holds it to that #30

Merged opened this

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

Merged

`src/engine/purity.test.ts` now holds the engine to the second half of what makes it a package: it imports nothing outside itself (`boundary.test.ts`) and it needs nothing from a browser to load (this). The list of modules is `sources()` from `boundary.test.ts`, filtered to what ships, so a module added tomorrow is covered the day it lands; the imports happen in a second process because a dynamic…

shipped production is serving it deploy ae07c2d

opened this

src/engine/purity.test.ts now holds the engine to the second half of what makes it a package: it imports nothing outside itself (boundary.test.ts) and it needs nothing from a browser to load (this). The list of modules is sources() from boundary.test.ts, filtered to what ships, so a module added tomorrow is covered the day it lands; the imports happen in a second process because a dynamic import() is cached per process and the engine's own tests have already loaded every module by the time the runner gets here — a second import in-process would execute nothing and prove nothing.

That process is src/engine/purity.probe.ts: one Bun.spawn of a constant command (process.execPath run <probe beside the test>, no arguments from anywhere), the module names on stdin, each checked against a name pattern and rejected if it is not a plain relative .ts path, and one JSON line of report back. Before it imports anything it installs window, document, navigator, AudioContext, webkitAudioContext, requestAnimationFrame and localStorage as getters that record the access and answer undefined, so reading one and coping fails just as hard as throwing. It then reads AudioContext on purpose — the parent asserts that canary is in the report, so a trap that silently failed to install cannot report a clean run — restores every descriptor in a finally, and reports any global the undo did not put back; the parent asserts that list is empty and that its own globals were never touched.

Demonstrated both failures by hand and reverted them. A top-level const ctx = new AudioContext() in sound.ts failed two tests: failures came back [{module: "sound.ts", error: "undefined is not a constructor (evaluating 'new AudioContext')"}] and touches came back ["sound.ts: read AudioContext"]. The quieter version, const HAS_AUDIO = typeof AudioContext !== "undefined", throws nothing and still failed on ["sound.ts: read AudioContext"] — which is the case this exists for.

The engine needed no fixing: sound.ts already opens its context on first sound behind a Ctor parameter and controls.ts already takes its host, and the run is clean. README's shape section states both rules in one sentence each, side by side. bun test is green at 255 tests across 33 files in 3.7–4.0 s (it was 3.6–4.1 s before; the probe's process costs about 170 ms for the whole file, boundary's tests registering a third time included).

🤖 Generated with Claude Code

Closes #26

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs