feat(site): phase 17 - per-game module pages, /runicnpc/, and checkTerms over the built site
All checks were successful
PR checks / checks (pull_request) Successful in 2m37s

PLAN.md §16 phase 17, built on the org lead's answers of 2026-10-22,
recorded as D75-D79 (count of record 79).

- D75: core marketing pages describe what a game module adds in core's
  words. "Game intelligence" became "What a game module adds"; each
  game's own list moved to src/data/games/ and renders only on the new
  /modules/uo/ and /modules/rust/. /modules/ is a card per game.
- The page-side capability check is per game (assertGameCoverage), the
  item phase 13 moved here.
- D76: the homepage keeps its game screenshot, labelled. screens.mjs
  entries may carry `game` (D67); Screenshot.astro and the app grid
  print the game's name and mark the figure data-game.
- D77: /app/ describes the Rust screens (released the same day).
- D78: /runicnpc/, the standalone page, is in the header and footer;
  the narrow header was re-measured so five links keep two rows.
- D79: checkTerms.mjs --built reads every built page's visible text
  after the build; game routes and data-game elements allow their
  game's words. Proven against ten plants. Still the twelfth check.
- Re-read fixes: /architecture/'s "two machines is also the maximum",
  Discord "guild" on /integrations/, and collection.mjs against what
  Steam sign-in and the Rust module keep (PLAY_DATA_SAFETY.md
  regenerated; the Play answers are unchanged).

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-22 20:10:51 -05:00
parent ec65b2d5aa
commit 56473d4d51
32 changed files with 1924 additions and 451 deletions

109
PLAN.md
View File

@@ -237,7 +237,7 @@ Taken by the org lead (Colby Whitlock) on 2026-08-19. Recorded so they are not r
**Decisions after D13 are recorded where they were taken**, in the section describing the phase that
raised them, rather than appended here — a decision is only re-litigated when its reasoning is
somewhere other than the thing it decided. The count of record is **seventy-four**:
somewhere other than the thing it decided. The count of record is **seventy-nine**:
| # | Where | What it settled |
|---|---|---|
@@ -260,6 +260,7 @@ somewhere other than the thing it decided. The count of record is **seventy-four
| D68–D69 | §16, "How phase 14 built it" | A game's real example on a core page is a tagged, labelled block; *Verify the whole stack* stays in Getting started, game-neutral (amending D60) |
| D70–D71 | §16, "How phase 15 built it" | The Rust group is fifteen pages, one per admin screen where it can be; the Rust event kinds are checked against the module's own classification |
| D72–D74 | §16, "How phase 16 built it" | The RunicNPC subgroup is eight pages with a shared profile-field reference; the API check compares signatures and every data shape's field names; the site's API page carries the current API only, with no decision numbers and no version history |
| D75–D79 | §16, "How phase 17 built it" | Core marketing pages describe what a game module adds in core's words and link to each game's page; the homepage keeps its game screenshot, labelled; `/app/` describes the Rust screens, released the same day; `/runicnpc/` is in the header; `checkTerms.mjs` gains a pass over the built pages |
---
@@ -1675,8 +1676,10 @@ a mechanism rather than diligence:
- **`scripts/checkTokens.mjs`** — no colour literal outside the token file (§7).
- **`scripts/checkTerms.mjs`** — added in phase 14 for D63 and D64, the twelfth check. No game's
vocabulary outside that game's section: its docs group, its sidebar group, or a
`<GameExample>` block (D68). Reads the docs and both sidebar trees; phase 17 widens it to all of
`src/`.
`<GameExample>` block (D68). Reads the docs and both sidebar trees before the build; since
phase 17 (D79) a second pass, `--built`, reads every built page's visible text after it, so
the marketing pages and the words their data renders are covered too. A game's own routes, and
an element marked `data-game`, allow that game's words.
- `astro check` plus a production build, in CI on every PR.
---
@@ -2262,6 +2265,106 @@ exception to "every page links out".
The check parses them line by line and was not affected. Fixed at the org lead's ask in `docs`
#371, which removes the two blank lines and changes nothing else.
### How phase 17 built it
Five decisions, taken by the org lead on 2026-10-22 before any page was written, each from a
concrete example. The count of record is now **seventy-nine**.
**D75 — core marketing pages describe what a game module adds in core's words, and link to
the games.** The homepage, `/features/`, `/architecture/` and `/app/` name both games and use
neither game's vocabulary. The module-supplied group became *What a game module adds*: seven
things both modules supply, each in its own way (live server status, a live feed, leaderboards,
maps, Teams from the game, players' own game accounts, game steps for events), with a button
to each game's page. Each game's real list, in its own words, lives in `src/data/games/` and
renders only on `/modules/uo/` and `/modules/rust/`. The alternative was D68 applied to
marketing — game-labelled blocks carrying each game's real list on the core pages. It was
declined as longer, and as the front door carrying two games' jargon at once.
**D76 — the homepage's "What it looks like" keeps its game screenshot, labelled.** This is
D67's choice, made. The capture stays the Ultima Online live-status screen, now printing
"Ultima Online" beside it, because the claim under the data-path diagram is live game state
on a public page and no core screen shows that. Phase 18 adds a labelled Rust capture beside
it. The alternative, a core screen such as the admin dashboard, was declined.
**D77 — `/app/` describes the app's Rust screens.** When the phase began they were on
`Android-app` `edge` only, 26 commits ahead of `main` and in no release. The org lead chose to
describe them and release them the same day, rather than hold the page or say "next release".
`/app/`'s game section is now *The game, live*: one item per game, titled with the game's name
and written in core's words, plus Rust's half of *Your own side of the game*.
**D78 — `/runicnpc/` is in the header.** The top navigation is now five items, so a Rust
server owner who runs no Runic Gateway finds the plugin from any page (D61's reason). It is in
the footer's Product column and on the Rust module page's RunicNPC card too. The alternative
was the footer and the card only.
**D79 — `checkTerms.mjs` reads the built pages.** D64 had the check widen to all of `src/`. A
source scan of the marketing pages would see expressions rather than the words they render —
a capability label, a screenshot caption, a value from `platform.json` — and would take ids
like `shard-status` and slugs like `guilds` for prose. So the check gained a second pass,
`--built`, run after the build the way `checkLinks.mjs` is. It reads every page's visible text
plus `alt`, `title`, `aria-label`, `placeholder`, the `<title>` and the meta description, and
not link targets, scripts, styles or comments. A game's routes (`GAME_ROUTES`: its docs group,
its module page, and `/runicnpc/` for Rust) allow its words, and so does any element marked
`data-game` — `<GameExample>` and, new this phase, a game-tagged `<Screenshot>`. Starlight's
sidebar and the previous/next links are skipped, because the source pass reads the sidebar as
data and each neighbour's title is checked on its own page. The source pass is unchanged and
still runs first, naming a file and a line. Failures from the built pass name a route and a
snippet. It is still the twelfth check: one script, one word list, one allowlist (whose entry
now carries a `route` as well as a `file`). The alternative was a source scan with per-file
exemptions.
**The check was proven before it was trusted.** Ten plants went into the built HTML. Six
failed, each naming where: a word in a core page's text, in an `alt`, in a `title`, a
UO word inside a block marked Rust, a word split across two syntax-highlighting `<span>`s, and a
UO word on a Rust page. Four passed: a word only in an `href`, one in a `<script>`, UO words on
`/modules/uo/`, and a UO block on a core page. On the build as it stood before this phase, the
pass found 49 words on seven marketing pages; all are gone.
**What was built.**
- **`/modules/`** is core now. Its Ultima Online worked example became a card per game, from
`src/data/games/index.mjs`, and what the modules share — a sidecar, their own tables, their
own releases — is said once beneath them.
- **`/modules/uo/`** carries the Ultima Online list that used to be *Game intelligence*, the
three pieces with their versions, and the five screenshots `/features/` showed until now.
- **`/modules/rust/`** carries eight features for players and four for staff, written from the
Rust docs group phase 15 walked, the four pieces (the module, rust-link, `RunicGateway.cs`,
RunicNPC) and the RunicNPC card D61 asked for. It has no screenshots and no slots for them
(D65, D4).
- **`/runicnpc/`** is written for a server owner with no site: what it does, the two ways to run
it, and where to get it. Every number is read — release, API version and floors from
`platform.json`, the counts of calls and hooks from `runicnpc.mjs`, which phase 16's check
holds to `API.md`.
- **The page-side capability check is per game**, closing the item phase 13 moved here.
`assertGameCoverage()` holds each game's list to its module's declared capabilities in both
directions. The homepage, `/features/` and each game page call it, and
`assertDetailCoverage()` now reads the games' lists too.
- **The supported-games strip** (D63) is `src/components/SupportedGames.astro`, on the homepage,
`/features/` and `/modules/`.
- **D67 is in the components.** `screens.mjs` entries may carry `game`; nine are tagged `uo`.
`Screenshot.astro` and `/app/`'s phone grid print the game's name beside each tagged capture
and mark it `data-game`.
**What the re-read found**, fixed here:
- **`/architecture/` said two machines is "the minimum and also the maximum"**, false since Rust
shipped: one sidecar serves one server, so a community with six servers runs six. The caption
now says two is the minimum and a sidecar sits beside each game server, and its game host
section no longer says the installer prints "four values" (that count was one game's).
- **`/integrations/` and the Discord capability called a Discord server a "guild"** — Discord's
API word, but read on this site as a game's. They say *Discord server* now, as phase 14 made
the docs say.
- **`collection.mjs` was re-read against what the Rust module and Steam sign-in keep**, as the
plan required, before anything was claimed. *Account records* now names linked sign-in
identities, Steam's display name and avatar address included (`user_identities`). *Game data
from the connected server* names, for Rust, each player's Steam id and in-game name, their
statistics, who is online, and the event record, which includes chat, player reports and the
IP addresses on sign-in attempts and bans, readable by staff only. Its retention now states
the module's 30 days for that record and the sidecar's 14-day default. The Play Data Safety
answers did not change: the app's Rust screens read from the deployment and send it only a
one-time link code, which the existing "everything you read and post" row already covers. Its
wording lost "shard", so `PLAY_DATA_SAFETY.md` was regenerated.
---
[api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md