Files
Integration-kit/book
wtclaude cffd525bdf
All checks were successful
PR Checks / template (pull_request) Successful in 6s
PR Checks / links (pull_request) Successful in 7s
docs: scaffold the Integration Kit — front page, outline, and the checks
Phase 5 slice 0 (MODULE_SYSTEM.md §2.11.1). The repo's governance, the front
page, the book's outline, and the CI that keeps the whole thing from rotting.

README.md
  What the reader is building, all three parts, and the draft banner: the kit is
  finished when someone outside this project builds a working module by
  following it alone, and that has not happened. Says the sidecar rule plainly
  (MODULE_API.md §2.7) rather than leaving it to chapter 3, because a reader who
  skims the front page and starts coding should still get that one right.

book/README.md
  The outline of four chapters, landed before the prose so the shape can be
  argued with. Chapters are named but NOT linked — a link to a file that does
  not exist is what the link check is for, and an outline should not be the
  first thing to fail it.

CONTRIBUTING.md
  The rule that governs every change here: the kit never re-specifies a
  contract. Also the prose conventions, and why the pinned ref points at core's
  `edge` rather than `main`.

SECURITY.md
  Scoped for a repo that runs nothing: the two things that ARE reportable are a
  template that teaches an insecure pattern (it is meant to be copied) and a
  chapter that teaches something dangerous.

scripts/checkLinks.js
  Relative links resolve; anchors match a real heading; no link pins a reader to
  a commit snapshot of a moving document. Nothing is fetched — a self-hosted
  Gitea would fail on a credential-less runner and teach us to ignore red.
  Fences and code spans are stripped by a line walk, not a regexp.

  Its first run found a real one: a PR template's relative links resolve from
  the REPO ROOT, because that is where their text ends up when Gitea inlines
  them into a pull request body. Encoded, with the reason.

scripts/checkCoreApi.js
  The anti-rot check. Asserts template/module.json's `coreApi` EQUALS the pinned
  core's MODULE_API_VERSION — equality, not "satisfies", because a range check
  stays green across a contract bump and green would then mean "the template
  still loads" instead of "someone has re-read the book". Both failure branches
  and the pass were exercised against a real core checkout.

ci/core-ref.json
  The pin, same convention as Module-uo's. Points at `edge`: core's `main` has
  no server/src/modules/ until the cutover, and that pin is one of the things
  the cutover has to revisit.

.gitea/workflows/pr-checks.yml
  Two jobs. `links` always runs; `template` is conditional on
  template/module.json existing, so the repo is gated now and the job arms
  itself when slice 1 lands, with no edit to the workflow. Same guard Module-uo
  used through its planning phase.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-12 09:47:22 -05:00
..

The book

Four chapters, in the order the work happens. None of them are written yet — this is the outline, landed first so the shape can be argued with before the prose exists. Chapter status is in the table; a chapter that is not there yet is not there yet, rather than a stub that reads like an answer.

Read the dry run before any of them.

# Chapter File Status
1 Your first module in twenty minutes 01-first-module.md not written
2 The website module 02-website-module.md not written
3 The sidecar 03-sidecar.md not written
4 The game-side plugin 04-game-plugin.md not written

They are named but not linked on purpose: a link to a file that does not exist is the thing this repo's link check is for, and an outline should not be the first thing to fail it.

1. Your first module in twenty minutes

Copy template/, rename it, build it, install it, see a page. No theory. The point is to reach a working module before learning anything, so that everything after it is a change to something that already runs rather than a step toward something that might.

  • What the pieces of template/ are, one paragraph each.
  • module.json: the fields you must change, and coreApi.
  • Building the client chunk. Why a module ships prebuilt and an operator never builds anything.
  • Installing it: the admin panel, the MODULES environment variable, or a directory on the volume.
  • Reading the state your module lands in, and the four ways it can fail to load.

2. The website module

The bulk of the kit.

  • module.json — every field, and which are load-bearing at boot.
  • The server entry point. register(ctx, api); what ctx hands you and why each member is handed rather than imported; the lazy-accessor pattern that lets a ported file keep a file-scope require, and the require-order rule that comes with it.
  • The register* calls — routes per tier, notification streams, announce legs, post hooks, extension slots. Worked examples of each, with the distinctions that are easy to get wrong (a leg is one-shot delivery with retry; a post hook is idempotent state that also runs on delete).
  • The schema fragment. Idempotent, replayed every boot, leading-verb allowlist, the table-prefix rule, and why there is no migration runner anywhere in this project. What belongs in purge.sql instead.
  • The client half. The prebuilt ESM chunk; window.__rg; the shared-dependency rule (core owns React and hands it over — a module that resolves its own gets two Reacts and a broken page); the Vite library build with anchored aliases and external: [], and why that combination rather than the obvious one.
  • Routes, nav and features on the client, and how a module's nav row becomes an ordinary row an operator can reorder, relabel or hide.
  • The UI kit — seven members, closed on purpose. What to do about the eighth thing you want.
  • The OpenAPI fragment, and how to generate it from your own registrations.
  • Packaging and release CI: the tarball, the install manifest, the checksum, and the version living in module.json.
  • Boundaries. What a module must not do, each with the failure it prevents.

3. The sidecar

Why it exists, why it is not optional, and what "thin" means for a game that already speaks a remote-control protocol.

  • The invariant: your game is never network-reachable; it dials out, the sidecar listens, and only the website's backend talks to the sidecar.
  • Persist before you forward. The sidecar owns the durable copy — event history, the latest snapshot of every board, whatever a page must still be able to render when the game or the website is down. A live feed is allowed to be lossy because the store is not.
  • The wire as a versioned compatibility contract rather than a build dependency: a version on every response, a mismatch refused rather than mis-parsed, and what a bump obliges you to change in the same commit.
  • Auth, and why the sidecar is the only exposed part.
  • uo-link as the worked example, and what a thin sidecar for an RCON-style game keeps and drops.

4. The game-side plugin

The chapter with the least code and the highest stakes: a plugin that gets this wrong takes the game down when the sidecar wedges.

  • Never block the game thread. Enqueue and return; a bounded, drop-oldest queue; a dedicated writer thread that drains it. Dropping the oldest event is correct, and stalling the game to avoid it is not.
  • Read the world only on the game's own thread, and hand plain data to the writer.
  • Reconnect, backoff, and what to send on connect so the sidecar can rebuild its picture without asking.
  • What to emit at all: the difference between an event stream and a state snapshot, and why both exist.
  • servuo-plugins as the worked example. The constraints are general; the C# is not.