Files
runicgateway.com/PLAN.md
wtclaude 96f77a87e1 docs(plan): the design of record for runicgateway.com
Reconnaissance across the nine RunicGateway checkouts, and the plan for the
public site + documentation experience built on top of them.

The site is a presentation layer over existing project knowledge: normative
contracts stay canonical in docs/ and are linked, never copied. What the site
authors is the journey — install, configure, administer, extend — which no
existing document owns end to end, because the repos are organised by
component and an operator is not.

Two things worth reading before the rest:

- Section 3 lists eleven places the repositories contradict each other,
  verified against origin/main rather than resolved quietly. The sharpest is
  that link/INTEGRATION.md documents the protocol-4 event kinds while its
  normative section 2 still declares X-UOLink-Version: 3 — one document
  disagreeing with itself. installer/INSTALL.md still hands operators
  "Protocol version 3", which is the value they paste into Admin -> Shard,
  and its own troubleshooting table says a stale number looks exactly like
  the shard going offline.

- Section 4 makes fixing those a Phase 0 that lands before the site quotes
  anything: four PRs across docs and .profile, none of which touch code or a
  contract. They correct documentation to match what main already does.

Section 1 also records a process failure and the rule taken from it. An
earlier draft claimed the platform was on protocol 3 with 4 "not cut over".
Every checkout was on a feature branch whose local main ref had never been
fetched, so git show main:<path> answered from a months-old blob. Every fact
in section 2 is now read from origin/main or the Gitea API, and section 7
turns that into a build-time check instead of a promise.

Section 9 leaves five questions open rather than assuming answers.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 15:07:30 -05:00

351 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# runicgateway.com — design of record
**Status:** proposal, awaiting org-lead approval. Nothing is built.
**Date:** 2026-08-19
**Repo:** `RunicGateway/runicgateway.com`
**AI-assisted:** yes — drafted by Claude, per the org's AI-usage disclosure policy.
The public website and documentation site for Runic Gateway. Two audiences: **server
administrators** who want to understand and install the platform, and **developers** who want to
build modules and integrations for it.
This document is the plan. It is deliberately written to be argued with — §9 lists the questions
that are still open, and §2 lists the things the existing repos disagree about rather than picking
a winner quietly.
---
## Contents
1. [The principle](#1-the-principle)
2. [Verified platform state](#2-verified-platform-state)
3. [What the repositories disagree about](#3-what-the-repositories-disagree-about)
4. [Phase 0 — fix the documentation the site will quote](#4-phase-0--fix-the-documentation-the-site-will-quote)
5. [Information architecture](#5-information-architecture)
6. [Visual direction](#6-visual-direction)
7. [Accuracy machinery](#7-accuracy-machinery)
8. [Build phases](#8-build-phases)
9. [Open questions](#9-open-questions)
---
## 1. The principle
**The site is a presentation layer over existing project knowledge, never a competing source of
technical truth.**
Concretely:
- The normative contracts stay where they are and are **linked, never copied**:
[`MODULE_API.md`][api], [`MODULE_SYSTEM.md`][system], [`link/INTEGRATION.md`][int],
[`link/v3.md`][v3], [`link/v4.md`][v4], [`website/TEAMS.md`][teams],
[`website/BACKEND_DESIGN.md`][backend], [`installer/INSTALL.md`][install].
- The site authors the **journey** — install, configure, administer, extend — which is the thing no
existing document owns end to end, because the repos are organised by component and an operator
is not.
- Where marketing language and technical documentation conflict, **the technical documentation
wins**, and the marketing language changes.
- No feature is advertised that does not exist. §2 records what does; the "not built" list is as
load-bearing as the rest.
### A process rule, learned the hard way
An earlier draft of this report stated the platform was on **protocol 3**, with protocol 4
"feature-complete on `edge`, not cut over". That was wrong — protocol 4 is on `main` and released.
The cause: all nine checkouts in the workspace sat on feature branches whose local `main` ref had
**never been fetched**, so `git show main:<path>` answered confidently from a months-old blob. Local
`main` was 13 commits behind `origin/main` in seven of nine repos.
**Rule for this repo:** every version, protocol number or capability claim that reaches the website
is verified against `origin/<default-branch>` or the Gitea API *at the moment it is written*, never
against a local working tree. §7 makes that a build-time check rather than a promise, because a
promise is exactly what failed.
---
## 2. Verified platform state
All values read from `origin/main` or the Gitea API on 2026-08-19.
### Versions
| Component | Value | Authority |
|---|---|---|
| **Wire protocol** | **4** | `link/sidecar/src/main.rs` `PROTOCOL_VERSION`; `servuo-plugins/overlay.toml` `protocol = 4` |
| **Module API** | **1.6.0** | `website/server/src/modules/version.js` |
| **Current bundle** | **2026.08.19** (protocol 4) | `installer` branch `bundles`, `current.json` |
| uo-link sidecar | **v2.0.0** | release; in bundle 2026.08.19 |
| Plugin overlay | **v1.0.0** | release; in bundle 2026.08.19 |
| Installer | **v0.1.0** | release |
| `module-uo` | **v1.0.1** | release (`module.json` on `main` reads `0.3.0`; the release version is tag-derived) |
| Android app | **v0.5.0** | release |
| ServUO | **57.4** — the only version the patch tier is verified against | `overlay.toml` |
### What exists
Confirmed present on `main`, not inferred from design docs.
**Core (game-agnostic):** accounts and sessions; wiki; posts across four categories (news,
five-on-friday, newsletter, screenshots); CMS pages and a page builder; navigation editor;
appearance/theming; branding as data via `BRAND_*`; invites; moderation and appeals; content
reports; append-only activity log; bot-scoring with automatic IP bans; TOTP two-factor with trusted
devices and recovery codes; SSO over OAuth2/OIDC (Google, Discord, generic — **link-only**, never
auto-provisioned); mobile bearer auth with rotated hashed refresh tokens; push through self-hosted
**ntfy**; the module loader and the admin Modules screen; OpenAPI 3.0 at `/api/docs`.
**Teams** — merged to `main` (PR #161, cutover 4/6). Team core and roster; forums with
announcements, discussion threads, replies, an edit window, post moderation and member-raised abuse
reports; notifications across web, push and email; Discord slash commands; one voice channel per
Team, granted by role.
**`module-uo` (72 routes)** — capabilities `shard · atlas · market · governors · guilds · houses ·
champs · cliloc`: shard status, economy, houses and IDOC decay, character sheets, the player-vendor
marketplace, a spawn atlas built from the shard's own spawn tables, champion boards, points/loyalty
leaderboards, town crier, account linking, and the staff control consoles.
**The bridge** — ServUO plugin → loopback TCP `127.0.0.1:7788` (newline-delimited JSON, the shard
dials out) → uo-link sidecar (Rust, SQLite) → website over authenticated WebSocket + REST. The
installer deploys the shard side as a protocol-checked bundle and registers a service under an
unprivileged account.
### What does NOT exist — and will not be advertised
- **Matrix.** [`TEAMS.md`][teams] §8 is explicit research, not a roadmap item: Matrix has no
channel-with-overwrites, no role object, no voice channel (voice is a MatrixRTC session needing a
LiveKit SFU the homeserver does not ship), and no slash-command registration. Of the five
capabilities a shared interface would name, a Matrix implementation could honestly provide two.
The settled outcome was a **capability contract**, not an integration. The original brief for this
site listed Matrix as a feature; it is not one.
- **Multi-module deployments.** [`TEAMS.md`][teams] Part 1 puts this explicitly out of scope — one
active module per deployment. `module_id` columns exist to keep it later-friendly; nothing
exercises them.
- **A second game module.** `module-rust` is a paper dry-run (`docs/modules/rust-dryrun.md`),
deliberately unimplemented — it exists to test that the contract generalises.
- **A finished Integration Kit.** It describes itself as a draft: "finished when someone outside
this project builds a working module for a new game by following it alone. That has not happened
yet."
- **macOS and Windows-on-ARM installer builds.** Deliberately absent — the shard and sidecar must
share a host, and no ServUO host is either.
---
## 3. What the repositories disagree about
Verified against `origin/main`. None of these are caused by this project; all of them would be
**inherited and amplified** by a website that quotes them.
| # | Conflict | Location | Severity |
|---|---|---|---|
| 1 | **`INTEGRATION.md` contradicts itself.** Its event catalog fully documents the protocol-4 kinds — `guild.roster`, `guild.leave`, a subsection headed "On Protocol 4" — while its normative §2 still declares `X-UOLink-Version: 3`, `"protocol": 3` in `/health` and `ws.hello`, and a worked client example sending `"3"`. | `docs/link/INTEGRATION.md` lines 36, 5863, 95, 118, 1015 | **High** — this is the contract a third-party integrator implements against |
| 2 | **`INSTALL.md` still tells operators protocol 3.** Line 413 is the "Protocol version 3" value in the four-value handoff an operator pastes into Admin → Shard. The document's own troubleshooting table says a stale number is answered with `409` and "looks exactly like your shard going offline". | `docs/installer/INSTALL.md` lines 159, 163, 164, 413, 516, 726, 862 | **High** — actively misconfigures a real install |
| 3 | **`v4.md` says the cutover has not happened.** "Status: Feature-complete on `edge``edge``main` is the v4 cutover" — but link v2.0.0 and overlay v1.0.0 are released from `main` and bundle 2026.08.19 pairs them at protocol 4. | `docs/link/v4.md` line 3 | Medium |
| 4 | **The org landing page prints protocol 3** in the installer's four-value handoff block. | `.profile/README.md` line 179 | Medium |
| 5 | **Stale component versions throughout `INSTALL.md`** — bundle `2026.08.04`, overlay `v0.1.1`, sidecar `1.1.0`, including in copy-pasteable `curl` commands in Appendix A. | `docs/installer/INSTALL.md` lines 119, 512, 522523, 657658, 705706, 725, 862 | Medium |
| 6 | **Two architecture diagrams disagree.** `docs/website/ARCHITECTURE.md` places `shardIngest.js` and `uoLinkClient.js` *inside* the website backend; `website/README.md` places them in the module. ARCHITECTURE.md predates the module system. | `docs/website/ARCHITECTURE.md` | Medium |
| 7 | **`website-README.md` has drifted from the live README** — the snapshot lacks "Three ways in, and none of them is a build" (admin panel / `MODULES` env / by hand), which is now the primary module-install story. | `docs/website/website-README.md` | Low |
| 8 | **`docs/README.md`'s index is stale** — no entry for `TEAMS.md`, `ARCHITECTURE.md`, `TRUSTED_DEVICES_MFA.md` or `MODERATION_APPEALS.md`. | `docs/README.md` | Low |
| 9 | **`BACKEND_DESIGN.md` is titled "UOMysticmoon Website — Backend Design"** although core is game-agnostic and UO is a module. | `docs/website/BACKEND_DESIGN.md` | Low |
| 10 | **`Module-uo/README.md` shows Phase 4 as `⬜`** though the module system shipped 2026-08-12 and the admin Modules screen exists. | `Module-uo/README.md` line 41 | Low |
| 11 | **Product name spelling.** All prose is "Runic Gateway"; identifiers are `RunicGateway`. **Settled by the org lead: keep "Runic Gateway" in prose.** Recorded because the site's original brief mandated the opposite. | everywhere | Settled |
---
## 4. Phase 0 — fix the documentation the site will quote
Conflicts 15 are protocol-accuracy bugs that exist independently of this website, and the site
cannot be written honestly on top of them: "Connect a game server" has to tell an operator which
number to type into Admin → Shard, and today the canonical guide says the wrong one.
**Phase 0 lands before the site quotes anything.** Each is a separate PR in its own repo, using the
verified values from §2.
| PR | Repo | Change |
|---|---|---|
| 0.1 | `docs` | `link/INTEGRATION.md` — protocol 3 → 4 in §2 and every normative example (`X-UOLink-Version`, `/health`, `ws.hello`, the JS client). Resolves the self-contradiction with its own protocol-4 event catalog. |
| 0.2 | `docs` | `link/v4.md` — status line: the `edge``main` cutover is done; released as link v2.0.0 + overlay v1.0.0 in bundle 2026.08.19. |
| 0.3 | `docs` | `installer/INSTALL.md` — protocol 3 → 4 and the stale component versions → the current bundle, including Appendix A's `curl` commands. |
| 0.4 | `.profile` | `README.md` — protocol 3 → 4 in the handoff block. |
Conflicts 610 are documentation hygiene rather than operator-facing errors. They are **not**
blockers and are proposed as an optional follow-up batch — see §9, Q2.
**Nothing in Phase 0 changes code or a contract.** Each PR corrects documentation to match what the
code already does, and each cites the source of truth in its description.
---
## 5. Information architecture
Organised by what a reader is trying to do. A reader should never need to know that `link`,
`servuo-plugins` and `installer` are three repositories in order to connect a game server.
### Marketing pages
| Route | Purpose |
|---|---|
| `/` | Hero, the data path, grouped capabilities, the self-hosted argument, get-started CTA |
| `/features/` | Grouped capability presentation, explicit about what is core and what a module supplies |
| `/architecture/` | The system explained visually, for a technical evaluator deciding whether to run it |
| `/modules/` | What a module is, `module-uo` as the worked example, writing your own, the Integration Kit |
| `/integrations/` | Discord, mobile + ntfy push, SSO — with an explicit "not built" list |
**Feature grouping**, using project terminology:
- **Community** — Teams, Team forums, notifications, wiki, news and newsletter, player self-service
- **Game intelligence** *(module-supplied; `module-uo` today)* — shard status, economy, player-vendor
marketplace, houses and IDOCs, character sheets, spawn atlas, champion boards, points leaderboards
- **Administration** — roles, moderation and appeals, content reports, audit log, bot scoring and IP
bans, module management, the shard connection
- **Integration** — modules, the sidecar bridge, Discord (slash commands, notifications, voice),
mobile and push, SSO
- **Infrastructure** — self-hosted, Docker, prebuilt pull-only images, branding as data, OpenAPI
### Documentation
```
Getting started What is Runic Gateway? · Requirements · Install the site ·
First run · Install a game module · Connect a game server ·
Verify the whole stack
Administration Configuration · Branding and theming · Navigation and pages ·
Users and roles · Authentication · Teams · Moderation ·
Notifications and email · Managing modules ·
The shard connection · Maintenance and upgrades · Troubleshooting
Modules The module system · Installing modules · Module lifecycle ·
The module manifest · The module API · Building a module ·
The Integration Kit · Testing and release
Architecture System architecture · The bridge · Authentication architecture ·
Teams architecture · Protocol versions
Reference Environment variables · Installer CLI · sidecar.toml ·
Bridge.cfg · HTTP API · Event catalog · Canonical documents
```
Roughly 37 pages. Every Reference page is a **navigable summary plus a link to the canonical
document** — never a re-specification, per §1.
### The installation path
The repos treat these as separate deployments; this site is the first place they appear as one
sequence. Worth stating plainly, because it is a real trap: **a "Runic Gateway install" is two
independent installs.** The installer binary sets up the *shard side only* and never contacts the
website; the website is a separate Docker deployment.
1. Requirements
2. Install the site — Docker Compose, pull-only
3. First run — first admin, maintenance → live
4. Install a game module — admin panel, `MODULES` env, or by hand
5. Connect a game server — the installer binary on the shard host (ServUO-specific today)
6. Paste the four values into Admin → Shard
7. Verify — `[bridge status` in game, `/health` reporting `plugin_connected: true`, then `doctor`
8. Configure authentication and integrations
Each step states what the operator should expect to see, and links the failure modes to
Troubleshooting.
---
## 6. Visual direction
**"Modern infrastructure software with an arcane identity."** Dark-first. Marketing pages are
single-theme by design; the docs honour the reader's light/dark preference.
- **The palette descends from the product's own tokens** (`website/client/src/styles/theme.css`), so
the site and the thing it describes read as one family: near-black ground, a steel-blue accent
derived from the product's `#7f99bd`, and the product's existing live/maintenance signal colours.
One secondary — a muted violet — reserved **exclusively** for the runic motif (hairlines,
gradients, diagram glow) and never used for text.
- **Type:** Cinzel — already the project's display face — for the wordmark and hero only; a modern
variable sans for everything else. Self-hosted, so there are no external font requests.
- **Motif:** hand-drawn SVG geometry — a gateway glyph, thin luminous topology lines, layered
panels. Subtle, structural, used where it explains something. **No AI-generated rune artwork
anywhere.**
### On the existing emblem
`website/client/public/assets/img/runic-emblem.png` is an ornate gold-and-gems medallion. It reads
as instance branding for UOMysticmoon, and as exactly the fantasy-decoration register the site is
meant to avoid. **Recommendation: draw a new stroke-only geometric gateway mark** that scales down
to a favicon. See §9, Q3.
---
## 7. Accuracy machinery
The site quotes versions, protocol numbers and capability lists. Given §1's process rule, that needs
a mechanism rather than diligence:
- **`src/data/platform.json`** — one file holding every externally-sourced fact: protocol version,
Module API version, current bundle tag, component versions, `module-uo`'s capability list. Every
page reads from it. **No version number is ever hardcoded in prose.**
- **`scripts/checkFacts.mjs`** — fetches the authority for each fact (Gitea raw `overlay.toml`,
`version.js`, `bundles/current.json`, release tags) and **fails the build on any disagreement.**
Same mechanism and the same intent as the Integration Kit's `checkCoreApi.js`: when the platform
moves, this repo goes red so someone updates the site. That failure is the feature.
- **`scripts/checkLinks.mjs`** — every internal link resolves; every outbound link into a
`RunicGateway` repo points at a branch path, not a commit permalink.
- `astro check` plus a production build, in CI on every PR.
---
## 8. Build phases
| Phase | Deliverable |
|---|---|
| **0** | The documentation fixes in §4 — four PRs across `docs` and `.profile` |
| **1** | Foundation: scaffold, design tokens, typography, layout shell, shared header/footer, the SVG mark, docs theming, sidebar, `platform.json` + `checkFacts.mjs` |
| **2** | Homepage: hero, the data-path diagram as inline SVG, grouped capability sections, CTA |
| **3** | Marketing: `/features/`, `/architecture/`, `/modules/`, `/integrations/` |
| **4** | Docs — the journey: Getting started (7) + Administration (12). The installation path is the priority of the whole project |
| **5** | Docs — builder and reference: Modules (8) + Architecture (5) + Reference (7) |
| **6** | Polish: responsive, accessibility, SEO/OpenGraph/sitemap/robots, full-text search, screenshot components |
| **7** | Validation: `astro check`, production build, link check, fact check, mobile layout verified in a real browser |
| **8** | Delivery: Dockerfile (static build → static server), `docker-compose.yml`, Gitea Actions workflow, README, CONTRIBUTING with the AI-disclosure requirement |
**Stack:** Astro + Starlight — Astro for the marketing pages, Starlight for `/docs` (sidebar,
breadcrumbs, previous/next, automatic table of contents, offline full-text search). Static output,
Vite underneath, so it stays inside the org's existing tooling family.
**Deploy:** a Docker image behind the org lead's reverse proxy. This repo ships the image and the
compose file; the deployment endpoint is wired on the host.
---
## 9. Open questions
**Q1 — Does Phase 0 land as PRs from here, or as issues?**
§4 proposes four PRs correcting protocol and version references in `docs` and `.profile`. The
alternative is filing issues and letting the site launch alongside documentation that contradicts
it. *Recommendation: PRs — the docs are wrong regardless of whether this site is ever built.*
**Q2 — Do conflicts 610 get fixed too?**
Documentation hygiene, not operator-facing errors: the stale architecture diagram, the drifted
README snapshot, the stale docs index, the UOMysticmoon title, the `⬜` phase table. Cheap to fix,
outside the site's scope, and each is one small PR. Fix now, later, or never?
**Q3 — Screenshots.** There are **zero web-UI captures** anywhere in the workspace. The only real
product images are 14 Android screenshots in `docs/android/screenshots/`. Options: (a) stand up the
local review stack and capture real web screenshots, (b) ship clearly-labelled placeholder
components to fill later, (c) launch with the Android captures and diagrams only.
**Q4 — The product mark.** Draw a new geometric SVG gateway mark, or keep `runic-emblem.png`?
**Q5 — How direct should the docs be about maturity?** The Integration Kit calls itself a draft,
protocol 4 is days old, the installer is v0.1.0. The brief asks the site to look like an established
product. A "draft" badge on the Integration Kit pages is honest and costs little credibility; saying
nothing is more polished and less true. Preference?
---
[api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md
[system]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_SYSTEM.md
[int]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/INTEGRATION.md
[v3]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v3.md
[v4]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/link/v4.md
[teams]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/TEAMS.md
[backend]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/BACKEND_DESIGN.md
[install]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/installer/INSTALL.md