From 6a39ef63c65df35784b0a5c7877a3fe26caa3d16 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Mon, 10 Aug 2026 22:03:35 -0500 Subject: [PATCH] docs(website): settle what core publishes about an installed module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 2, PR 6 of MODULE_SYSTEM.md 2.7 — GET /api/v1/public/modules. MODULE_API.md gains 2.9, the normative shape: four fields (id, name, version, capabilities) in scan order, and what is deliberately absent. Only started modules appear, so a disabled or failed one is absent exactly as 4.4 already leaves its routes and nav; no state, failure_stage or failure_reason reaches an anonymous caller; no client chunk URL, because htmlShell hands the browser the tag; an empty array is a real answer, but the 7.6 guard is a 500 and never []. Also records why it owns the /modules prefix rather than sitting in the root-mounted site router, where the loader's collision probe could not see it. 6.7 amends MODULE_SYSTEM 2.6 step 4, which said the SPA reads the endpoint "to learn what to load" — step 3 of the same list had already answered loading a different way, and 3.1.3 is the normative version. The endpoint feature-detects. 2.6 step 4 now says so, and Part 6's intro no longer claims a fixed count of amendments. MODULE_SYSTEM.md 2.7 records PR 6 and restates the phase exit criterion honestly: no EXISTING URL moves, and PR 6 is the single deliberate addition in Phase 2. Status line updated — the design is in implementation, not unimplemented. BACKEND_DESIGN.md documents the route in the /public table and the router in the folder map, and the published api-route-inventory.json mirror is refreshed to 228 public routes (the prose count was stale at 226). Co-Authored-By: Claude --- website/BACKEND_DESIGN.md | 9 ++++- website/MODULE_API.md | 57 +++++++++++++++++++++++++++++-- website/MODULE_SYSTEM.md | 58 +++++++++++++++++++++++++------- website/api-route-inventory.json | 4 +++ 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/website/BACKEND_DESIGN.md b/website/BACKEND_DESIGN.md index 6a57392..e344f47 100644 --- a/website/BACKEND_DESIGN.md +++ b/website/BACKEND_DESIGN.md @@ -105,6 +105,12 @@ server/ NOT under /shard: nothing here touches the sidecar, and unlike /shard it IS site-mode gated + modules.router.js (1) /public/modules — the installed-module + list a client feature-detects against. + A real prefix and not a fifth singleton + below, so the module loader's + collision probe (which skips + root-mounted layers) sees it site.router.js (4) /settings /status /version /contact — the group-root singletons; declares no router-level middleware @@ -715,7 +721,7 @@ are authoritative, and they answer different questions: | Artifact | Source of truth for | Generated by | |---|---|---| -| `server/routes.manifest.json` — mirrored as [api-route-inventory.json](./api-route-inventory.json) | **What URLs exist.** 226 public routes + 2 on the internal listener, sorted, method + path only. | `npm run routes:manifest`, by walking the live Express stack | +| `server/routes.manifest.json` — mirrored as [api-route-inventory.json](./api-route-inventory.json) | **What URLs exist.** 228 public routes + 2 on the internal listener, sorted, method + path only. | `npm run routes:manifest`, by walking the live Express stack | | `server/swagger/swagger-output.json` — served at `/api/docs` | **What each route means.** Parameters, bodies, response codes, security. | `npm run swagger`, from `#swagger.*` annotations | The split is deliberate: Swagger is annotation-derived, so an unannotated route is invisible in it and @@ -898,6 +904,7 @@ from the per-route **siteMode** middleware (§5), never from an auth gate. | 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 | `/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 `