Run 76 hung in `compileDebugKotlin` for thirteen minutes and was failed with no
error in its log, where the last good run finished that task in two. Nothing
about that reads as a compile error, and this repo's Gitea has no rerun
endpoint — so this empty commit is the re-run, to tell a transient runner
problem from a real one before bisecting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
**Unrelated to this PR's feature work**, and fixed here because it blocks
verifying it (org lead, 2026-09-17). PR #46 passed on this workflow yesterday;
every Android PR fails now.
`android-actions/setup-android@v3` is a floating tag and the action's `packages`
input defaults to `tools` — an obsolete package Google has since removed from the
SDK repository. So the step runs `sdkmanager tools`, gets `Warning: Failed to
find package 'tools'`, exits 1, and CI fails in **Set up Android SDK**, before a
line of this repo is compiled.
`packages: ''` turns that install off. It was always redundant here: the very
next step installs exactly what the build targets — `platform-tools`,
`platforms;android-35`, `build-tools;35.0.0` — precisely so the build never
depends on what some action decided to fetch.
Not addressed here, and worth its own decision: `@v3` is a floating major tag, so
the next upstream change can break CI the same way without warning.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
Three things, none of which a unit test could have seen.
**The drawer's live count resolved once per process.** It was keyed on the
capability answer alone, so it was read at connect and never again — which is
not what "live" means on a row somebody opens the drawer to look at. It now
refreshes on resume, beside the inbox's unread badge and for the same reason:
coming back to the app is exactly when a stale number would be noticed. Still
never on a timer, still nothing at all on a site without the module.
**Every card's text sat flush against its edge.** `ShardCard` is the themed
`Card` and carries no padding of its own — each caller pads its own content, and
these four did not. On a phone the first glyph of each line read as clipped.
**A name touched its own kill count.** Five numeric columns beside an
equal-weight name column left "Brannock" and "50" reading as one field. The name
now takes a wider share and ellipsizes, and the ACTIVE SORT is marked on the
header rather than by tinting a column of numbers — the header is the control,
and tinting the values says "these are special" instead of "this is what the
table is ordered by".
Walked against the phase-4 rig: a core with the module installed, one live
server and one that has never reported. Both halves of the phase criterion hold
on a phone — the Rust site renders every panel with its server unreachable, and
the same app against the UO core shows its five shard rows and no Rust row.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
The app's half of module-rust's read path — the leg R10 says trails the website
surface it consumes by one phase, so it is built against routes that exist.
Two screens, mirroring what phase 4 shipped: `/rust` is the server list (D12),
and one server is a single screen with four tabs (D13) rather than four
destinations. Both render entirely from the website's own tables, so the phase
criterion — a fleet that is entirely off still shows its maps, seeds, wipe
dates, killfeeds, leaderboards and last known presence — holds here for the same
reason it holds on the web.
What is new to the app rather than copied:
- **A poll that is not a load.** `PollWhileResumed` + `refreshInto` (D17): a
refresh is invisible when it succeeds and KEEPS the rows when it fails. The
app had one shape for a read — blank, ask, replace — which is right for opening
a screen and would clear the killfeed three times a minute here. Gated on
RESUMED, so a backgrounded app makes no requests at all and returning to it
refreshes at once.
- **A second game module in the drawer.** `Capability.RUST`, gating one row. It
deliberately does not gate on `servers`/`killfeed`/`leaderboard`/`presence`/
`wipes`: those name surfaces, core flattens every module's capabilities into
one list, and another module declaring `servers` would reveal these screens on
a site with no Rust. Module-Rust#5 adds the identity string.
- **`/rust` in NavPaths**, so an admin's nav override or an added link opens
natively instead of handing off to a browser (D19).
- **A live player count on the drawer row** (D19) — the phone's answer to D15's
footer slot, in the same badge slot the inbox count uses, with the same
screen-reader treatment. Zero renders nothing; a failed read keeps the last
number; it never polls.
Two things carried across from the website's own page walk rather than
rediscovered: "last reported" reads `lastSeenAt` and never `updatedAt` (a failed
poll moves the second), and a feed row from another calendar day carries its
date, or a row from a past wipe reads as this afternoon.
The four navigation tests that moved did so because APP_MENU gained a row and
the website's nav number line gained an index; each now says which.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4