docs(android): the app's events screens, and the module rows that were never gated (Phase 14b)

Events Phase 14b, docs half. `docs/android/PLAN.md` gains **M13** — written before
the code, as the plan requires — and `EVENTS_PLAN.md` and `EVENTS.md` record 14b
as built.

M13 records the six org-lead decisions, the two defects the milestone fixes, and
the three the emulator walk found.

The prerequisite fix is the larger half of the milestone: the app could not tell a
module that is not installed from a lookup that failed, so on a site with no `uo`
module every shard row rendered and every one of them 404'd. **Absence of an
answer is not an answer of absence** — a successful module list that omits the
string hides the rows, a failed read keeps the last answer the host gave, and a
host that has never answered leaves the gate open. Capability and feature are two
gates that compose and answer different questions.

A second, older defect is recorded and fixed: the website path → route table had
been wrong since the module-system cutover on 2026-08-12, because core's `NAV` is
eight rows rather than sixteen and the nine shard rows moved to `/uo/*`. Two tests
in the app's suite had been passing vacuously since that day.

§5 gains the capability gate beside the feature gate, with the two failure
directions spelled out; §6.1 gains the three public event reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
2026-09-08 13:09:02 -05:00
parent b6fefc5466
commit 0a61915e50
3 changed files with 221 additions and 12 deletions

View File

@@ -1475,7 +1475,7 @@ with no `roles` — every account has a participation history and the server sco
| **Leaderboards** | A run publishes a result table; it does not create a leaderboard entity. module-uo's `/points` boards are read-only mirrors of ServUO's and must not be written to. **`core.results.publish` is that publication** (Phase 10): an ordinary `inspect` step an author places, which ranks the run's participants over a total order and stamps `event_runs.results_published_at`. Idempotent, so a retry re-writes the same numbers. | by publication |
| **Rewards** | **An ordinary module action — optional, and shaped by whatever the game can actually give.** module-uo grants an item from a plugin-declared allowlist whose labels and icons come from `shard_clilocs` and `item_id`, already imported for the market pages, so the authoring catalog costs nothing new. A module that grants nothing declares no such action and core has no empty reward feature. | optional, per module |
| **Points** | **Still out.** No core points system exists and no write path to any loyalty board exists — both halves are missing, not one. | out |
| **Mobile** | `GET /api/v1/public/events`, `/events/:slug`, and an `events` capability string — **all three built in Phase 14a**, the capability in the `version` block that `/public/version` returns and `/public/status` embeds, separate from a module's because core is not a module. **The app half is Phase 14b**, with its prerequisite: the app hardcodes `api/v1/public/shard/stream` — a module path — reads `/public/modules` nowhere, and `ShardFeaturesRepository` maps a failed `/public/shard/features` lookup to "unknown", which `canSee` treats as visible. On a site with no `uo` module that route 404s, so **every** shard entry renders and every one of them 404s. Failing open is right *within* a module that is installed and wrong for one that is not; the fix gates the group on a capability string rather than on a module id. | server built, app is 14b |
| **Mobile** | `GET /api/v1/public/events`, `/events/:slug`, and an `events` capability string — **all three built in Phase 14a**, the capability in the `version` block that `/public/version` returns and `/public/status` embeds, separate from a module's because core is not a module. **The app half was built in Phase 14b** (`android-app` M13), with its prerequisite: the app read `/public/modules` nowhere, and `ShardFeaturesRepository` mapped a failed `/public/shard/features` lookup to "unknown", which `canSee` treats as visible. On a site with no `uo` module that route 404s, so **every** shard entry rendered and every one of them 404d. Failing open is right *within* a module that is installed and wrong for one that is not; the fix gates on a capability string rather than on a module id, and its governing rule is that **absence of an answer is not an answer of absence** — a successful module list that omits the string hides the rows, a failed read keeps the last answer, and a host that never answered leaves the gate open. Core's `events` gates the two event rows. The app's four screens are the calendar, an event page (carrying `?run=`), an arc and participation history — the last as **one** row for any signed-in account, because the route is `requireAuth` alone and self-scoped and only the web's own `/account` guard needed a second mount. | built |
| **Admin audit** | `ctx.activity.log` for every edit, publish and manual transition. The runtime record is `event_run_log`, because `activity_log.detail` is `TEXT` and cannot be queried by phase or step. | exists |
---