1@README.md 2 3- **Jev and Workers AI calls are not `Send`** (they hold JS values), and axum 4 handlers must be. `stream` therefore spawns the work on the isolate's 5 executor and feeds the response from a channel. Do not await such a call 6 inside a handler. 7- **The archive's lookup-then-start must not await.** In `Archive::once`, 8 from reading `versions` to inserting into `running` there is no `.await`, so 9 no second ask can run in between and send the same request. An await there 10 brings the duplicate back. 11- **The call runs under `wait_until`, not under the ask that started it.** A 12 visitor who leaves cancels their ask; the call still finishes and is kept. 13- **`versions` is keyed by the body itself, not a hash of it.** The page's 14 `call_id` is a hash, and only names a call; the archive never looks a 15 response up by it. 16- **The archive's schema changes only by adding a step to `MIGRATIONS`.** A 17 step that has shipped has run on the live object and will not run again; 18 editing it changes nothing there and breaks a fresh one. 19- **What the home page shows is kept in the archive object's memory** 20 (`Shelf::home`) until something it is made of changes. Anything new that 21 writes `asked` or `counts` must call `changed`, or the home page shows 22 the old numbers until the object next sleeps. Rows read are metered 23 (five million a day on Workers Free; three million were gone by 24 mid-morning on 2026-10-03), so do not read a whole table on a path every 25 visitor takes. 26- **`view.rs` stays free of `worker` types** so its tests run natively. 27- **Text from the visitor or the LLM reaches the page only through maud's 28 escaping.** Option labels and level descriptions are the LLM's words and 29 are as untrusted as the input. 30- **Do not use workers-rs's `Ai::run`.** It goes through serde-wasm-bindgen, 31 which makes a JS `Map` of every JSON object that is not a Rust struct. 32 `ai.rs` passes JSON text through `JSON.parse` and back through 33 `JSON.stringify`, which is also what lets the page show the reply untouched. 34- **Maud attribute names with a dot are written as string literals** 35 (`"data-on:input__debounce.300ms"`). Bare, the dot does not parse. 36- **The look is typesafe.ai's, on purpose** (the user, 2026-10-02): near-black 37 on white, one pink band, ordered-dither dot fields, old operating system 38 windows, pixel type for labels. `page.css`'s header says what was copied. 39 Their two display faces are commercial; Inter Tight and VT323 stand in. 40 Do not "modernise" it with rounded corners, shadows or a dark theme. 41- **The dither is drawn into CSS variables, not into elements.** The 42 transcript is replaced wholesale on every event, and a canvas inside it 43 would be wiped. 44- **The `/live` sockets are the archive's, accepted with `accept_web_socket`** 45 (hibernation). Do not hold them in a field or await on them: a 46 hibernating object keeps them only through `get_websockets`, and an 47 object kept awake by a socket is billed for every second of it. 48- **A toast shows a question only if the feed may** (`listed`). Anything 49 else is "someone asked". Keep it that way: the toast reaches strangers. 50- **The storage diagrams in README.md are kept beside `MIGRATIONS`.** A 51 test (`archive::tests`) fails if the archive's `erDiagram` lacks a column 52 the migrations make, so a new column changes the diagram in the same 53 commit. The budgets' diagram has no such test: its keys are in 54 `budget::Which::key` and `meter.rs`. 55- **A page's place is the country and city Cloudflare gives the `/live` 56 request, and lives only on its socket** (`serialize_attachment`). The 57 Worker sets `archive::COUNTRY`/`CITY` itself, removing any the page sent; 58 never take a place from anywhere the page controls, or anyone can put 59 words in every visitor's top bar. The same goes for the event the Worker 60 passes with it (`archive::EVENT`). 61- **A socket the new Worker did not place is closed with 1012 after 62 `REPLACE_AFTER_MS`** (`Seen::stale`), so a page that reconnected through 63 an old Worker during a deploy gets its place. The Worker sets `PLACED` on 64 every `/live` it forwards, place or not; drop that header and every page 65 reconnects every ten seconds forever. 66- **"N online" goes out on the alarm, not per socket** (`Archive::soon`): a 67 page that connects is told the state at once, everyone else within 68 `ONLINE_EVERY_MS`. Broadcasting on every connect is pages² messages when a 69 deploy reconnects them all. 70- **The `/live` socket is the SharedWorker's (`live.js`), one per browser 71 per build.** It replays only what a joining tab cannot render itself (the 72 build, the count, the places) and passes everything else on as it came; 73 do not replay toasts or feed patches, a joining tab would show them twice 74 or apply stale ones. Every page that has the nav must load `page.js` 75 (`/rules` did not until 2026-10-02, so its badge never lit).