For agents, on top of README.md, which they read first.
- Jev and Workers AI calls are not
Send(they hold JS values), and axum handlers must be.streamtherefore spawns the work on the isolate's executor and feeds the response from a channel. Do not await such a call inside a handler. - The archive's lookup-then-start must not await. In
Archive::once, from readingversionsto inserting intorunningthere is no.await, so no second ask can run in between and send the same request. An await there brings the duplicate back. - The call runs under
wait_until, not under the ask that started it. A visitor who leaves cancels their ask; the call still finishes and is kept. versionsis keyed by the body itself, not a hash of it. The page'scall_idis a hash, and only names a call; the archive never looks a response up by it.- The archive's schema changes only by adding a step to
MIGRATIONS. A step that has shipped has run on the live object and will not run again; editing it changes nothing there and breaks a fresh one. - What the home page shows is kept in the archive object's memory
(
Shelf::home) until something it is made of changes. Anything new that writesaskedorcountsmust callchanged, or the home page shows the old numbers until the object next sleeps. Rows read are metered (five million a day on Workers Free; three million were gone by mid-morning on 2026-10-03), so do not read a whole table on a path every visitor takes. view.rsstays free ofworkertypes so its tests run natively.- Text from the visitor or the LLM reaches the page only through maud's escaping. Option labels and level descriptions are the LLM's words and are as untrusted as the input.
- Do not use workers-rs's
Ai::run. It goes through serde-wasm-bindgen, which makes a JSMapof every JSON object that is not a Rust struct.ai.rspasses JSON text throughJSON.parseand back throughJSON.stringify, which is also what lets the page show the reply untouched. - Maud attribute names with a dot are written as string literals
(
"data-on:input__debounce.300ms"). Bare, the dot does not parse. - The look is typesafe.ai's, on purpose (the user, 2026-10-02): near-black
on white, one pink band, ordered-dither dot fields, old operating system
windows, pixel type for labels.
page.css's header says what was copied. Their two display faces are commercial; Inter Tight and VT323 stand in. Do not "modernise" it with rounded corners, shadows or a dark theme. - The dither is drawn into CSS variables, not into elements. The transcript is replaced wholesale on every event, and a canvas inside it would be wiped.
- The
/livesockets are the archive's, accepted withaccept_web_socket(hibernation). Do not hold them in a field or await on them: a hibernating object keeps them only throughget_websockets, and an object kept awake by a socket is billed for every second of it. - A toast shows a question only if the feed may (
listed). Anything else is "someone asked". Keep it that way: the toast reaches strangers. - The storage diagrams in README.md are kept beside
MIGRATIONS. A test (archive::tests) fails if the archive'serDiagramlacks a column the migrations make, so a new column changes the diagram in the same commit. The budgets' diagram has no such test: its keys are inbudget::Which::keyandmeter.rs. - A page's place is the country and city Cloudflare gives the
/liverequest, and lives only on its socket (serialize_attachment). The Worker setsarchive::COUNTRY/CITYitself, removing any the page sent; never take a place from anywhere the page controls, or anyone can put words in every visitor's top bar. The same goes for the event the Worker passes with it (archive::EVENT). - A socket the new Worker did not place is closed with 1012 after
REPLACE_AFTER_MS(Seen::stale), so a page that reconnected through an old Worker during a deploy gets its place. The Worker setsPLACEDon every/liveit forwards, place or not; drop that header and every page reconnects every ten seconds forever. - "N online" goes out on the alarm, not per socket (
Archive::soon): a page that connects is told the state at once, everyone else withinONLINE_EVERY_MS. Broadcasting on every connect is pages² messages when a deploy reconnects them all. - The
/livesocket is the SharedWorker's (live.js), one per browser per build. It replays only what a joining tab cannot render itself (the build, the count, the places) and passes everything else on as it came; do not replay toasts or feed patches, a joining tab would show them twice or apply stale ones. Every page that has the nav must loadpage.js(/rulesdid not until 2026-10-02, so its badge never lit).