docs(website): record slice 3, and MODULE_API 1.3.0

The client half's move: 35 files / 5,332 lines (the table said 51 / ~3,700 and
is corrected, not re-derived to match). The seven-kit-members-plus-format claim
held exactly, so the kit needed no additions.

Contract, 1.2.0 -> 1.3.0, all additive: `icon` on a nav item, a third slot
`player.invite.accepted`, and `api.BASE` — which §3.5 specified from the first
draft and shared.js had never published, because nothing needed it until a
module had to build an EventSource URL.

§2.3's narrowing note said game-signup policy was core's business. It was wrong
in both directions: the setting's help text names Bridge.cfg, and slice 1 had
shipped a controller calling a function the narrowing does not expose, so
POST /player/shard/account answered 500 for every caller until this slice.

§3.7 gains the rule the invite slot needed: core may ask whether a slot is
filled when the answer changes CONTROL FLOW, never when it changes decoration.
Decoration goes inside `<Slot wrap>` — that is the footer-separator bug.

§7.7 gains what running it against the real module found, rather than a
throwaway: the unguarded portal icon, the relative-MODULES_DIR trap, and the two
operational notes (a module needs its own npm ci; copy the directory, never
symlink it).

Also recorded: the nav-override cost is worse than "a hidden row may reappear" —
the review instance had the nine UO rows gathered into a dropdown section, and
the whole section is lost. And `api-route-inventory.json` was a slice stale at
228 routes; refreshed to the 158 core actually serves.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-11 19:00:43 -05:00
parent fc05412070
commit 931f77ef49
5 changed files with 204 additions and 288 deletions

View File

@@ -908,7 +908,7 @@ from the per-route **siteMode** middleware (§5), never from an auth gate.
| Method | Path | Notes |
|---|---|---|
| GET | `/settings` | whitelisted public keys, derived `registration`/`gameAccountSignup` flags, the per-shard **`brand`** block (name, `accent` color, logo/hero/favicon) a client themes itself from — one image runs as any shard, asset fields may be site-relative paths (resolve against the base URL); these are **effective** values, so an admin theme (`theme_visual`) beats `BRAND_ACCENT_COLOR` and an uploaded `brand_assets` asset beats its `BRAND_*` path — an optional **`theme`** block, the resolved CSS custom properties for that admin theme (absent when the instance was never themed, which is what makes it render from the shipped stylesheet unchanged) — and a **`push`** block `{ ntfyUrl }` (M7): the client-facing ntfy relay URL the app's embedded distributor registers its device topic against, from `NTFY_PUBLIC_URL` / first `NTFY_ALLOWED_ORIGINS` (never the internal `NTFY_BASE_URL`); `null` when push isn't configured for the shard. |
| GET | `/settings` | whitelisted public keys, the derived `registration` flags (`gameAccountSignup` was one of these until the module extraction moved game-account policy to module-uo — it is on that module's `GET /public/shard/features` now, and the `game_account_signup` settings row is unchanged), the per-shard **`brand`** block (name, `accent` color, logo/hero/favicon) a client themes itself from — one image runs as any shard, asset fields may be site-relative paths (resolve against the base URL); these are **effective** values, so an admin theme (`theme_visual`) beats `BRAND_ACCENT_COLOR` and an uploaded `brand_assets` asset beats its `BRAND_*` path — an optional **`theme`** block, the resolved CSS custom properties for that admin theme (absent when the instance was never themed, which is what makes it render from the shipped stylesheet unchanged) — and a **`push`** block `{ ntfyUrl }` (M7): the client-facing ntfy relay URL the app's embedded distributor registers its device topic against, from `NTFY_PUBLIC_URL` / first `NTFY_ALLOWED_ORIGINS` (never the internal `NTFY_BASE_URL`); `null` when push isn't configured for the shard. |
| GET | `/status` | status message + current mode, **plus a `version` block** (`{ service:'runic-gateway', api, server }`) so a client first-run probe recognizes the backend and can run a version-mismatch guard |
| GET | `/version` | lightweight, **DB-free** backend identity/version (`{ service, api, server }`) — the canonical target for the version guard and a cheap liveness check |
| GET | `/modules` | `{ modules: [{ id, name, version, capabilities }] }` — the modules this backend is currently **serving**, in scan order (module system, `MODULE_API.md` §2.9). A module that is disabled or failed to load is **absent**, not listed with a state: its routes and nav are absent too, so the client renders a site without that capability rather than advertising one that 503s. The recorded failure stage and reason are admin-panel detail and are never published here. `capabilities` are opaque strings the module declares — feature-detect against them and treat an unknown one as absent. Like `/status` and `/version` it is **DB-free and not site-mode gated**, so a client can still feature-detect during maintenance. It is *not* how a module's client chunk loads — `htmlShell` injects a `<script type="module">` per started module. |