docs(modules): phase 5 as built — Android leg A, and the capability a client had to be given #257

Merged
whitlocktech merged 1 commits from docs/rust-phase-5 into main 2026-09-17 09:21:47 +00:00
Member

module-rust phase 5. The module's read path gets its second client, and the phase criterion — the app renders a Rust site it has never seen, and a UO site unchanged — was walked on one device against two running cores rather than asserted.

Two files:

  • modules/rust/PLAN.md §18 — the phase as built, and the phase-5 row marked done.
  • android/PLAN.md M14 — the app's own record, as every Android milestone has.

The decision worth the most words

D16. This module declared five capability strings and every one named a surfaceservers, killfeed, leaderboard, presence, wipes — while a client gating a whole navigation group needs one that names the module. module-uo has exactly that (shard); this had none.

Core flattens every started module's capabilities into one list, so gating on servers would let any future module declaring that generic word silently reveal these screens on a site that does not run Rust.

Gating on the module id was considered and rejected, in as many words in the doc: id is a mount prefix (§2.1 requires it to equal the directory core loads from), MODULE_API.md §2.9 forbids a client inferring a route from a capability, and letting a client gate on id makes the two the same value in practice — the day one builds /<id>/servers from it, the separation that lets this module move its own pages is gone.

So the module declares its own name as a sixth capability (Module-Rust#5), asserted in its own suite against manifest.id so the two cannot drift.

The other three decisions

  • D17 — poll every 20s while the screen is RESUMED: D14's Page Visibility gate, translated. STARTED was rejected because it keeps polling behind a partly obscured screen.
  • D18 — the three Rust repositories move to edge for the rest of the workstream, releases cut at the cutover. All four repos' pr-checks.yml already trigger on [main, edge], so it costs no CI.
  • D19 — the drawer row carries a live player count, and NavPaths learns /rust (without which an admin's override on the module's own nav row hands off to a browser).

Two things that had to be translated rather than copied

  • D15's footer slot has no analogue on a phone. §18.1 records where the count went instead and why it keeps all three of the web version's rules.
  • The app had only ever done loads, never refreshes. §18.2: the same wall this module hit with core's useAsync, one tier along. A refresh is invisible when it succeeds and keeps the rows when it fails.

What §18.3 and §18.4 record

The walk proved refreshInto against a genuinely dead backend, R12's arithmetic on a phone (all-time 59 = 41 + 18, and a player who appears only in the older wipe drops out rather than reading zero), every describe branch from real rows, and the calendar-day rule.

It also found three defects that 644 green tests did not, each because it is about what a screen looks like or when a number is re-read: a live count that resolved once per process, card text flush against its edge, and a player name touching its own kill count.

§18.6 is deliberately short

Two things are not proven and say so: the website's own pages have still not been read at phone width (§17.6 left that open and this phase did not close it), and the badge's non-zero case was shown with a seeded count rather than by people playing.

AI disclosure

  • This contribution was AI-assisted (Claude Code).

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

`module-rust` phase 5. The module's read path gets its **second client**, and the phase criterion — *the app renders a Rust site it has never seen, and a UO site unchanged* — was walked on one device against two running cores rather than asserted. Two files: - **`modules/rust/PLAN.md` §18** — the phase as built, and the phase-5 row marked done. - **`android/PLAN.md` M14** — the app's own record, as every Android milestone has. ## The decision worth the most words **D16.** This module declared five capability strings and every one named a **surface** — `servers`, `killfeed`, `leaderboard`, `presence`, `wipes` — while a client gating a whole navigation group needs one that names the **module**. `module-uo` has exactly that (`shard`); this had none. Core flattens every started module's capabilities into one list, so gating on `servers` would let any future module declaring that generic word silently reveal these screens on a site that does not run Rust. **Gating on the module `id` was considered and rejected**, in as many words in the doc: `id` is a *mount prefix* (§2.1 requires it to equal the directory core loads from), `MODULE_API.md` §2.9 forbids a client inferring a route from a capability, and letting a client gate on `id` makes the two the same value in practice — the day one builds `/<id>/servers` from it, the separation that lets this module move its own pages is gone. So the module declares its own name as a sixth capability (Module-Rust#5), asserted in its own suite **against `manifest.id`** so the two cannot drift. ## The other three decisions - **D17** — poll every 20s while the screen is RESUMED: D14's Page Visibility gate, translated. `STARTED` was rejected because it keeps polling behind a partly obscured screen. - **D18** — the three Rust repositories move to **`edge`** for the rest of the workstream, releases cut at the cutover. All four repos' `pr-checks.yml` already trigger on `[main, edge]`, so it costs no CI. - **D19** — the drawer row carries a live player count, and `NavPaths` learns `/rust` (without which an admin's override on the module's own nav row hands off to a browser). ## Two things that had to be translated rather than copied - **D15's footer slot has no analogue on a phone.** §18.1 records where the count went instead and why it keeps all three of the web version's rules. - **The app had only ever done loads, never refreshes.** §18.2: the same wall this module hit with core's `useAsync`, one tier along. A refresh is invisible when it succeeds and **keeps the rows when it fails**. ## What §18.3 and §18.4 record The walk proved `refreshInto` against a genuinely dead backend, R12's arithmetic on a phone (all-time 59 = 41 + 18, and a player who appears only in the older wipe drops out rather than reading zero), every `describe` branch from real rows, and the calendar-day rule. It also found **three defects that 644 green tests did not**, each because it is about what a screen looks like or when a number is re-read: a live count that resolved once per process, card text flush against its edge, and a player name touching its own kill count. ## §18.6 is deliberately short Two things are **not** proven and say so: the website's own pages have still not been read at phone width (§17.6 left that open and this phase did not close it), and the badge's non-zero case was shown with a seeded count rather than by people playing. ## AI disclosure - [x] This contribution was AI-assisted (Claude Code). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 1 commit 2026-09-17 08:45:28 +00:00
module-rust's surface gets its second client. `modules/rust/PLAN.md` §18 records
the phase; `android/PLAN.md` M14 records the app's half, as every Android
milestone does.

The decision worth the most words is D16. This module declared five capability
strings and every one named a SURFACE — `servers`, `killfeed`, `leaderboard`,
`presence`, `wipes` — while a client gating a navigation group needs one that
names the MODULE. Core flattens every started module's capabilities into a single
list, so `servers` is a word another module could declare tomorrow and silently
reveal these screens on a site that does not run Rust. Gating on the module `id`
was considered and rejected in as many words: `id` is a mount prefix, §2.9
forbids inferring a route from a capability, and letting a client gate on `id`
makes the two the same value in practice.

Also recorded: D17 (poll while RESUMED — the phone's Page Visibility gate), D18
(the Rust repositories move to `edge`, releases at the cutover), D19 (the drawer
badge, and NavPaths learning `/rust`), why D15's footer slot had to be translated
rather than copied, and §18.4's three defects — none of which a green suite of
644 could see, because each is about what a screen looks like or when a number is
re-read.

§18.6 is deliberately short and honest: the website's own pages have still not
been read at phone width, and the badge's non-zero case was shown with a seeded
count rather than by people playing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
whitlocktech merged commit 54b4059091 into main 2026-09-17 09:21:47 +00:00
whitlocktech deleted branch docs/rust-phase-5 2026-09-17 09:21:47 +00:00
Sign in to join this conversation.
No description provided.