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

forked from trevato/cs · proposes

Not found is a page, not a fragment: the 404 and the 500 are served through the shell, and a favicon is not a miss #184

Merged opened this

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

Merged

`GET /nope` used to answer 433 bytes of unstyled markup and `error()` one bare `<h1>`; both are now whole documents served through `src/pages/shell.ts`, with the same head, header nav, environment pill, commit and footer every game page has.

shipped production is serving it deploy ae07c2d

opened this

GET /nope used to answer 433 bytes of unstyled markup and error() one bare <h1>; both are now whole documents served through src/pages/shell.ts, with the same head, header nav, environment pill, commit and footer every game page has.

What changed:

  • PageOptions.script is optional and the shell emits no <script> element when it is absent. The existing "the only script is the bundle" test still passes and has a sibling that a scriptless page has no <script> anywhere; the per-game tests asserting exactly one script on /, /range and /example are untouched and are the guard against a game shipping without its bundle.
  • New src/pages/miss.ts — the pages that are not a game. The 404 is a heading, the sentence and listHtml() from ./games.ts, with each link given the nav's 44px through the page's own extraStyle; the 500 says something went wrong and nothing else — it is never handed the error, so there is no path, stack, runtime or version to leak. listHtml() gained a class="games" so the miss page can size those links.
  • src/server.ts renders both once at start-up, like the old fragment, and /nope answers in about 0.5 ms at 7 kB. Status codes, text/html; charset=utf-8 and the whole security floor are unchanged, the 500 still console.errors the error, and the 404 now goes through withoutBodyIfHead so HEAD on a miss is the status and headers with nothing to read on both call paths (it was already that over HTTP; it is now that when the handler is called directly too).
  • The shell carries <link rel="icon" href="data:image/svg+xml,…"> — a crosshair on the page's background, under 400 bytes — so no browser goes looking for /favicon.ico. No file under static/, no route, no second request; img-src 'self' data: was already in the CSP.

Worth a look: src/pages/miss.test.ts (both pages checked as documents, and the 500 checked for the markers it must not carry) and the new onError export in src/server.ts, which is how the 500 is tested — called as a function with an error carrying a path and a stack, rather than leaving a broken route behind. I chose not to add a /favicon.ico route: a link in the document means the request is never made, and a route would be a second way to serve the same picture. bun test passes: 814 tests, 0 failures.

🤖 Generated with Claude Code

Closes #115

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs