docs(website): demo mode step 4 built — DM19, DM20 (the app)

- DM19: the app shows a refusal as one snackbar with the server's sentence;
  lines that blame the role say the demo sentence; ErrorKind.DEMO_LOCKED for
  load screens. §3.1 rewritten to match.
- DM20: the app shows the banner only; its strip names the site's sign-in page
  for the passwords (§6).
- §10 step 4 built as Android-app#61.
- android/PLAN.md §7: the demo's refusal and strip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-26 06:30:10 -05:00
parent cf5fe5cf75
commit 076eebee24
2 changed files with 20 additions and 4 deletions

View File

@@ -776,6 +776,11 @@ Mirrors the website's "degrade gracefully" invariant:
shard as **offline**, keep the rest of the app usable.
- **Site maintenance** (`/public/status` = maintenance) → show the maintenance page; public shard
widgets may still render (they're not maintenance-gated server-side).
- **The public demo's refusal** (`403` with `code: "DEMO_LOCKED"`, `../website/DEMO_MODE.md` §3.1) →
one app-wide snackbar with the server's sentence, spotted by an interceptor on the main client;
`HttpError` carries the body's `code`, `ErrorKind.DEMO_LOCKED` covers load screens (no Retry), and
a screen whose `403` line blames the role says the demo sentence instead (DM19). Home shows the
demo's strip from `/public/settings`' `demo` block (DM20).
- **Offline caching is not a v1 requirement** (decided). The app assumes connectivity and shows clean
loading/error/retry states; it does **not** ship a Room cache in v1. Cached read-only content can be
added later without reworking the repository layer (its typed results already isolate the UI from the

View File

@@ -47,8 +47,11 @@ answers:
```
The web client shows the message as a toast wherever an action fails with that code. The Android
app maps the code to a new error kind with the same wording; today it would show the generic
"Something went wrong", which is misleading for a reviewer.
app does the same with a snackbar (DM19): an interceptor on its one HTTP client spots the code on
any `403` and reports the server's sentence, so a module's own wording (a refused console command)
reaches the screen as written. The screen still shows its own failure line, except where that line
blames the role ("You don't have permission for that action", on the admin screens), which says the
demo sentence instead. Load screens get a `DEMO_LOCKED` error kind, with no Retry button.
Reads are never locked, with one exception: the bridge's config (§3.5). Every page can be opened
and every form filled in; only the save is refused. That keeps the demo useful as a tour of the admin panel.
@@ -256,7 +259,8 @@ address with (§5), and the static logins cover every role.
The seed writes both into one settings row, `demo_state`, which `PUT /admin/settings` cannot touch
(§3.3). The web client shows a thin strip on every page: "Demo site — anything you change is wiped at
14:00 UTC. Sign-ins: demo-admin / demo-player … — passwords on the sign-in page". The app shows the same
strip on Home. Both read `resetsAt`, so neither needs to know the schedule. The time is said in UTC,
strip on Home, but it does not list the passwords: its strip names the site's sign-in page instead
("passwords at demo.runicgateway.com/account/login"), DM20. Both read `resetsAt`, so neither needs to know the schedule. The time is said in UTC,
not converted, so it matches the schedule published on `/beta`.
**The toast.** Every web request goes through one request primitive (`client/src/api/client.js`),
@@ -422,6 +426,8 @@ down.
| DM16 | (build) Settings are open, so `player_registration` can be switched on. Lock registration? | Yes: **`POST /auth/register`** is a Tier A lock. Invites stay open (§3.3) |
| DM17 | (build) The server cannot tell which mobile session is the caller's own. What does the other-sessions lock do? | **`DELETE /auth/me/sessions/:id` is locked outright on a demo account**; `POST /auth/mobile/logout` stays open (§3.3) |
| DM18 | (build, step 2) Saving an event is core's route, and core never asks a module about a step's params. How is a denied command refused at save? | **A new core hook:** an event action's optional `demoRefuses(params)`, asked at create, save and publish on the demo, refusing `403 DEMO_LOCKED` (§3.4). `MODULE_API` 1.19.0; Module-Rust then needs `^1.19.0` |
| DM19 | (build, step 4) Each app screen writes its own failure line, and the admin screens turn every `403` into "You don't have permission for that action". How does the app show a refusal? | **One app-wide snackbar** with the server's sentence, like the web's toast, and the screens whose `403` line blames the role say the demo sentence instead. `ErrorKind.DEMO_LOCKED` for load screens (§3.1) |
| DM20 | (build, step 4) Should the app's sign-in screen list the logins, as the web's does? | **No, the banner only.** The Home strip names the site's sign-in page for the passwords; Google's reviewer gets `play-review`'s from the Play Console (§6) |
DNS and HTTPS for `demo.runicgateway.com` come from an external reverse proxy, which the org lead is
setting up. The app sits behind it on plain HTTP; core already trusts one proxy hop for
@@ -465,7 +471,12 @@ through that proxy.
also covers `onDemoSeed` (DM15), and chapter 5 covers `demoRefuses` (DM18). Both hooks were
decided after this list was written. Module-Rust's own pin moved to the same sha in
`Module-Rust#63`.
4. **App** (`Android-app`): the `DEMO_LOCKED` error kind and the banner on Home. One PR.
4. **App** (`Android-app`): the `DEMO_LOCKED` error kind and the banner on Home. One PR. **Built
2026-10-26** as `Android-app#61`, into `edge`, with the snackbar (DM19) and the banner only (DM20).
The refusal is read in `core/result/DemoLocked.kt` and spotted by `core/net/DemoLockInterceptor`;
the strip is `ui/demo/DemoBanner`. Checked on an emulator against `website` `main` with
`DEMO_MODE=true` and `seed:demo`: the strip, a shared login's username change refused, and the
admin site-mode switch refused with the demo sentence in place of the permission line.
5. **Deployment** (`website`): `deploy/demo/`, `demo-wipe.yml` (with §7.5's bundle step) and
`demo-baseline.yml`; the first deploy on `demo-site`; setting up the default set and capturing
the baseline. Then a walk of every static login, every Tier A lock and the upload lock, in the