feat(rust): the Rust server list and one server's page (phase 5, Android leg A)

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
This commit is contained in:
2026-09-16 22:16:21 -05:00
parent ac2d75c3f9
commit a6b6c92c33
29 changed files with 3180 additions and 24 deletions

View File

@@ -44,6 +44,7 @@
<string name="menu_events">Events</string>
<string name="menu_wiki">Wiki</string>
<string name="menu_shard">Shard</string>
<string name="menu_rust">Rust servers</string>
<string name="menu_rules">Rules</string>
<string name="menu_atlas">Atlas</string>
<string name="menu_leaderboards">Leaderboards</string>
@@ -566,4 +567,42 @@
<string name="events_score">Score %1$s</string>
<string name="events_show_more">Show more</string>
<string name="events_loading">Loading…</string>
<!-- Rust module (M14, docs/modules/rust/PLAN.md §18) -->
<string name="rust_servers_empty">No Rust servers are configured on this site yet.</string>
<string name="rust_offline">Offline</string>
<string name="rust_online_count">%1$d / %2$d online</string>
<string name="rust_never_reported">has never reported</string>
<string name="rust_last_reported">last reported %1$s</string>
<string name="rust_last_reported_stale">last reported %1$s — out of date, so it is shown as offline</string>
<string name="rust_world_size">size %1$d</string>
<string name="rust_world_seed">seed %1$d</string>
<string name="rust_wiped_on">wiped %1$s</string>
<string name="rust_refresh_failed">Could not refresh just now. This is the last thing the site heard.</string>
<string name="rust_no_such_server">No such server</string>
<string name="rust_no_such_server_detail">This address does not name a server this site follows.</string>
<string name="rust_back_to_servers">Back to the server list</string>
<string name="rust_tab_feed">Feed</string>
<string name="rust_tab_leaderboard">Leaderboard</string>
<string name="rust_tab_online">Online</string>
<string name="rust_tab_wipes">Wipes</string>
<string name="rust_all_time">All time</string>
<string name="rust_wipe_current">%1$s (this wipe)</string>
<string name="rust_wipe_this_one">Current</string>
<string name="rust_wipe_selected">Showing</string>
<string name="rust_feed_empty">Nothing has happened on this server yet — or not during the wipe you are looking at.</string>
<string name="rust_leaderboard_empty">Nobody has scored here yet.</string>
<string name="rust_wipes_empty">This server has not reported a wipe yet.</string>
<string name="rust_nobody_on">The server is up and the island is empty. Somebody has to be first.</string>
<string name="rust_presence_offline">Presence is the one thing on this page that cannot be answered from the record — it is who is connected now, and nothing is.</string>
<string name="rust_presence_live">On the server right now.</string>
<string name="rust_presence_last_known">The last board this server sent. It is offline, so this is who was on then — not who is on now.</string>
<string name="rust_sleeping">Sleeping</string>
<string name="rust_col_player">Player</string>
<string name="rust_col_kills">Kills</string>
<string name="rust_col_deaths">Deaths</string>
<string name="rust_col_npc_kills">NPC</string>
<string name="rust_col_structures">Built</string>
<string name="rust_col_played">Played</string>
<string name="rust_online_badge">%1$d players online</string>
</resources>