← All decisions

Standing rule: use every tool's type safety to the maximum extent available

type-safetytypescriptdenopolicy

Context

tools/browser_check.mjs (decision 0012) was written in plain JavaScript. The user flagged this as a project-wide concern, not a one-off nitpick: wherever a tool offers type safety, this project should use it to the maximum extent possible, and asked for the browser-check script specifically to be translated accordingly, researched and chosen deliberately rather than defaulted to.

Options considered

Rewrite in Gleam — rejected
No Gleam ecosystem exists for driving real browser automation (Chrome DevTools Protocol / Playwright's API) -- unlike the decisions.jsonl CLI (decision 0009), where gleam/json + simplifile + argv covered the need natively, there is no equivalent native or bound Playwright/CDP library for Gleam. Building one from scratch would be a large, disproportionate undertaking for a single dev-tooling script and was not attempted.
Keep plain JavaScript (.mjs), rely on JSDoc comments for informal typing — rejected
JSDoc types are checked, if at all, on a best-effort basis and don't block execution the way a real compiler does -- weaker than what's actually available here for free.
TypeScript under Deno, using npm:playwright-core's own bundled .d.ts declarations — chosen
Deno runs TypeScript natively and can real-type-check it (`deno check` / `deno run --check`) against the real TypeScript compiler; playwright-core ships full first-party type declarations, which Deno's npm: specifier support resolves automatically with zero extra configuration -- confirmed directly: a deliberately-wrong type assignment in a throwaway probe file was correctly rejected by `deno check`, and Browser/Page/BoundingBox-null types all resolved correctly against the real Playwright API. This gets compile-time verification of the exact library being driven, not just of hand-written logic.

Decision

tools/browser_check.mjs was rewritten as tools/browser_check.ts. Its documented invocation now always includes `deno run --check ...` (not bare `deno run`, which strips TypeScript types without checking them) so a type error actually blocks execution rather than being silently ignored. Going forward, any new standalone tooling script in this project should default to TypeScript-under-Deno (or Gleam, where a native/bound library actually exists for the task, per decision 0009's precedent) rather than plain JavaScript, unless a concrete reason rules it out.

Verification

`deno check tools/browser_check.ts` passes clean. The rewrite caught one real latent issue plain JS had silently allowed: Playwright's `Locator.boundingBox()` types as `BoundingBox | null` (element might not be found/visible), and the original script dereferenced `box.x` without checking for null -- now handled explicitly. Re-ran the full script end-to-end after the rewrite (`deno run --check ...`): identical behavior to the .mjs version, PASS with real rendered pixel counts and click-to-spawn confirmed.

Consequences

The one Deno-side DOM-typing wrinkle worth remembering: `page.evaluate()` callbacks execute inside the real browser page, but Deno's own ambient types have no DOM lib (server-side runtime) -- needs a file-scoped `/// <reference lib="dom" />` directive (not a global deno.json compilerOptions change, which would also apply to -- and risk affecting how `gleam test --target javascript` treats -- Gleam's own generated JS output, since deno.json is shared project-wide). Any future page.evaluate-based script in this project should expect the same fix.

References