feat(events): the app's events screens, and the module rows that were never gated (Phase 14b) #44
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/events-p14b-app"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Events Phase 14b. One of two:
android-app,docs(docs#225), both ontoedge. Recorded as M13 indocs/android/PLAN.md, written before the code as the plan requires.MODULE_API_VERSIONis untouched — nothing about the module contract changed.Phase 14 was split on the repo line: the app cannot be walked against a page that does not exist yet, so this waited for 14a (website#196) to merge. Four screens on the four routes it shipped — the public calendar, an event page carrying
?run=, an arc, and participation history.Six org-lead decisions. One departed from the recommendation: an inbox link to an event opens the app rather than a Custom Tab.
The prerequisite fix is the larger half
The app could not tell a module that is not installed from a lookup that failed.
ShardFeaturesRepositorymapped every failure tonullandcanSee(null, …)answerstrue, so on a site with nouomodule/public/shard/features404s, every shard row rendered, and every one of them 404'd when tapped.Absence of an answer is not an answer of absence — three states where there was one:
/public/modulesread that does not name the string hides the rows;Capability and feature are two gates that compose, and they refresh on different clocks because they answer different questions: capability asks is the module installed (per host, resolved beside the appearance, invalidated on a server switch); feature asks does this shard publish this surface to this viewer (per viewer, resolved on session change). Core's capability list and a module's stay separate lists, per MODULE_API §2.9.
A second, older defect, fixed here on the org lead's call
The website path → route table had been wrong since the module-system cutover on 2026-08-12. It quoted a sixteen-row
NAV; core's is now eight, and the nine shard rows belong to module-uo at/uo/shard,/uo/champs, … because core mounts a module's pages at/<id>/<path>. So a nav override on any shard row was ignored, an added link to one handed off to a browser instead of opening natively, andWEB_ROUTE_ORDER— the number line a storedorderis compared against — was a sixteen-row line against a differently numbered nav.Two tests in the existing suite had been passing vacuously since that day: they asserted a section came out empty, and it did, because the unmapped item never joined it.
The nine
/uo/*paths are hardcoded and documented as coupling to one module. Deriving/<id>/…from the module list is what §2.9 forbids and would hide the same coupling.An inbox link to an event opens the app
Through
resolveWebPathrather than a second link-routing mechanism — which forces the one rule that function exists to enforce, a path carrying a query hands off, to gain an exception. It is exactly one key on one path:?run=on an event page. A second parameter, anything else in the query, or any fragment still hands off, so an admin who writes?utm=mailgets the browser that honours it.Three defects, and 563 green tests found none of them
The rig's site had no
uomodule installed, which is what made two of them visible at all.My characters/vendors/housesread/player/shard/*— the same module's player mount — and rendered on a module-less backend, answering "This content couldn't be found" with a Retry button. The gate had been applied to the five public shard rows only. The test meant to catch this asked whether every row with afeaturedeclared the capability, which is true and insufficient: those three carry no feature, because they are gated by ownership rather than by the visibility framework. It now asks by route.scoreisDECIMAL(18,4)and was declared an integer. The first history row the walk read scored318.5; kotlinx does not round a fraction into aLong, it refuses the entire body, so a200reached the screen as "Something went wrong on the server." The same field is on the public results table — any event whose module scores fractionally would have broken the event page for every anonymous visitor.navigateTopLevelsaves and restores back-stack state, so the entry keeps itsViewModelStoreand a view model that loads only ininitnever runs again. Signing out of the admin and back in as a player showed the player the admin's participation history, with no request made at all. It now keys on the signed-in account id, and the shell sends a signed-out caller home rather than letting the screen ask and render a 401.InboxViewModelhas the identical shape and was deliberately left alone — it is engagement Phase 8's and outside this phase; worth a decision of its own.Two decisions worth stating
account/events, notevents/mine.events/mineandevents/{slug}are both two segments, and a static-versus-argument race between two NavHost patterns is exactly the bug Phase 13 found one tier along, where a staticevents/newoutrankedevents/:idin React Router and made creating an event impossible for seven phases.One history row at
SIGNED_IN, not two./player/events/historyisrequireAuthalone and self-scoped; the website mounts it twice only becauseRequirePlayerguards/accountthere. Deliberately notPLAYER— event history is not game-linked data, and a shard with no game module still has events.Checks
npm-equivalent: 570 tests, 0 failures (563 before, 54 new; the delta is net of the six NavPaths cases added and the stale assertions corrected). The app builds.Walked on an emulator against a local website running the 14a code, at three rungs — anonymous, player and admin. Every shard row absent on the module-less backend while Events remained; a cancelled occurrence three days out reading Cancelled rather than Did not happen on both the calendar and the event page; projections labelled; the event's own zone beside the reader's own day; an arc;
?run=surviving the NavHost pattern to reach the server; an inbox event link opening natively while a forum link still opened Chrome'sCustomTabActivity(checked indumpsys); and participation history self-scoped, proved with two accounts rather than asserted.🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4