Files
Integration-kit/template/README.md
wtclaude f8f7014d53
All checks were successful
PR Checks / prose (pull_request) Successful in -38s
PR Checks / template (pull_request) Successful in 29s
fix(kit): everything the acceptance run found — Phase 5 slice 3
A cold agent was given this repo and the documents it links to, and nothing
else — no core source, no module-uo — and asked to build a module for a second
game. It did, in one pass. The record is docs/modules/kit-acceptance.md; this is
the repair list, plus the two things it recommended that were not defects.

The one it could not find, because it had no core to render against: a module
page built exactly as this kit teaches renders OUTSIDE the site. PublicLayout is
the chrome, not the body. Core grew an opt-in `shell` prop for it
(MODULE_API_VERSION 1.5.0, website#148); the template passes shell="narrow" and
chapter 2 explains why you name a width and never a class.

Fixed:

- **F1, and the worst of them, because it lands in the first twenty minutes.**
  `npm run check:swagger` failed on a PRISTINE template on Windows: the check
  compared the committed fragment byte-for-byte and a default Windows clone is
  CRLF while the generator writes LF. The message blamed "the routes or their
  annotations". Now `template/.gitattributes` pins `eol=lf` and the comparison
  normalises line endings anyway — a check may only fail for the reason it names,
  and this one names a diagnosis.
- **F3** — `.gitea/workflows/release.yml` carries `gitea.example.com` and
  `your-org/your-module` under a literal `# CHANGE THESE`, was not in the rename
  checklist, and `checkRenameSites.js` could not match it, so CI was silent by
  construction. Row added, pattern widened. (The agent reported both workflow
  flavours; only the Gitea one is affected — GitHub supplies its own variables.
  Corrected in the record.) The near-miss is kept in the check's comments and its
  suite: the obvious widening is `example\.com`, which fires on a fixture URL in
  checkImports.test.js. Every alternative has to be a string that cannot occur by
  accident, which is the same rule that made the id `examplegame`.
- **F4** — the release bundle's include list was hardcoded, so adding
  `server/utils/` would have silently dropped it from every release while the
  bundle check stayed green. Inverted to an exclusion list, in both flavours, and
  run by hand because a release workflow never executes in CI.
- **F5** — the annotation-quoting warning was wrong in both directions, and the
  correction is measured rather than reasoned. A backtick is harmless (the
  template's own description has two spans and they survive). A `"` is not, and
  it does not throw: `'A "quoted" status'` is silently TRUNCATED to `A "` while
  swagger-autogen prints Success and the error capture sees nothing. The only
  signal is check:swagger blaming your routes.
- **F6** — `template/.gitignore`, so a copied template that is `git init`ed
  inherits ignore rules instead of nothing.
- **F7** — the UI kit is eight exports across five rows, not seven. The contract
  said seven and this kit had faithfully carried the miscount out of it.

Adopted, not defects:

- Chapter 1 now says to run every check on the untouched copy first. That is what
  found F1; without a baseline the first failure is ambiguous forever.
- The template ships the §2.7 self-check the agent wrote for itself. The rule has
  no CI in general — an outbound socket is not statically detectable — but a
  module can make a decidable claim about its own tree. Ported from its code with
  a header explaining how to NARROW it when a sidecar client arrives, since
  talking to your sidecar is the expected shape and is not what §2.7 forbids.

The pin moves to website edge 4ad8b2b, the 1.5.0 bump, and template/module.json
declares ^1.5.0 — so checkCoreApi's equality assertion still holds and the
template uses a member that exists only at that ref and later.

32 server + 18 client template tests, 21 kit-script tests, all four checks green.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-12 14:40:02 -05:00

147 lines
7.1 KiB
Markdown

# The template module
A Runic Gateway module that builds, loads, and does almost nothing. Copy it,
rename it, and you have a running module before you have read a chapter.
Installed into a core, it adds:
- **one public page** at `/examplegame/status`, and a nav row pointing at it;
- **one API route**, `GET /api/v1/public/world/status`, described in an OpenAPI
fragment core merges into its own `/api/docs`;
- **one table**, `examplegame_world_status`, created by an idempotent schema
fragment and dropped by a purge file;
- **both lifecycle hooks**, so there is something to see at boot and at shutdown.
That is deliberately less than your module will do. What it is *complete* about is
the shape: every seam a real module uses is here once, with the reasoning next to
it, and CI proves the whole thing still builds against a pinned core.
## The tree
```
module.json what core reads first — id, version, coreApi, mounts
server/
index.js register(ctx, api) — the entire server-side handshake
core.js the lazy accessors over ctx; read this second
boot.js onBoot / onShutdown
db/schema.sql idempotent, replayed every boot
db/purge.sql destructive, run only by an explicit admin purge
model/worldStatus/ the .db.js / .model.js pair
router/public/ one router, one controller, the #swagger annotations
swagger/doc.js tags and schemas the annotations refer to
scripts/checkImports.js the module boundary, enforced
scripts/swaggerFragment.js generates swagger-fragment.json from your own routes
test/ the suites — start with entry.test.js
client/
vite.config.js the library build: anchored aliases, external: []
src/entry.jsx registers routes and nav at evaluation time
src/core.js what core hands you: the UI kit (eight exports)
src/shim/ the four shared dependencies, re-exported from core
src/routes/public/ the page
scripts/checkExternals.js asks the BUILT chunk whether a bare import survived
test/ build.test.js and registration.test.js
.gitea/workflows/release.yml packaging CI — Gitea
.github/workflows/release.yml the same, for GitHub. Keep one, delete the other.
swagger-fragment.json generated; commit it
```
Neither workflow runs while it sits inside the kit — a workflow is only read from
a repository root. They arm themselves when your copy is a repository of its own.
## Build it
```bash
npm ci --prefix server
npm test --prefix server
npm run check:imports --prefix server
npm ci --prefix client
npm run build --prefix client # → client/dist/entry.js, the chunk that ships
npm run check:externals --prefix client
npm test --prefix client # build FIRST: two of these tests read the chunk
```
`npm test` in `client/` passes with no build, by skipping the tests that need one.
That is on purpose — the suite has to be runnable before the build — and it means
**a CI job that tests without building is a job asking nothing.** Build first.
Regenerate the OpenAPI fragment whenever a route or an annotation changes:
```bash
npm run swagger --prefix server # writes swagger-fragment.json
npm run check:swagger --prefix server # fails if it is stale
```
## Install it
Three supported ways, and none of them builds anything on the operator's machine:
1. **Admin → Modules**, pasting the URL of an install manifest — the JSON the
release workflow attaches beside the tarball. This is how an operator installs
your module.
2. **The `MODULES` environment variable**, `<id>@<version>=<manifest URL>`, for a
deployment that declares its module set rather than clicking it.
3. **A directory on the volume.** Copy this whole tree to `<website>/modules/<id>/`
and restart. The fastest loop while you are developing.
For (3): **copy, do not symlink.** The loader lists directory entries and a
symlink is not a directory, so a linked module is skipped in silence.
## Rename it
Change `id` in `module.json` first, then work down the list. Nothing here is
subtle, and the suites catch most of a half-finished job: `schema.test.js` fails
the moment a table name stops matching the id, and `registration.test.js` fails
when a nav row stops matching its route.
Your id must match `^[a-z][a-z0-9-]{1,31}$`, must equal the directory name core
loads you from, and becomes your table prefix — so **no hyphen unless you enjoy
backticking table names**.
<!-- rename-sites -->
| File | What to change |
| --- | --- |
| `module.json` | `id`, `name`, `version`, the `mounts` prefix, `capabilities` |
| `.gitea/workflows/release.yml` | `GITEA_HOST` and `REPO`, under the `# CHANGE THESE` banner — the only two, and they are wrong until you do. (The `.github/` flavour needs nothing: GitHub supplies `GITHUB_REPOSITORY` and friends.) |
| `server/package.json` | package `name` and `description` |
| `server/core.js` | the message every accessor throws |
| `server/boot.js` | the placeholder world name |
| `server/db/schema.sql` | every table name — the prefix must be your id |
| `server/db/purge.sql` | the same table names |
| `server/model/worldStatus/worldStatus.db.js` | the `TABLE` constant |
| `server/router/public/world.router.js` | the `#swagger.tags` name |
| `server/swagger/doc.js` | the tag, and the `Examplegame…` schema prefix |
| `server/scripts/swaggerFragment.js` | the generated fragment's `info.title` |
| `server/test/_fakes.js` | `ctx.moduleId` |
| `server/test/worldStatus.test.js` | the fixture's world name |
| `server/package-lock.json` | **regenerated**`npm install --prefix server` |
| `client/package.json` | package `name` and `description` |
| `client/vite.config.js` | the guard plugin's `name` |
| `client/src/core.js` | the console tag on the identity check |
| `client/src/shim/rg.js` | the console tag on the missing-global error |
| `client/src/entry.jsx` | `ID`, and every route path and nav `to` |
| `client/test/registration.test.js` | the example path in the comment |
| `client/package-lock.json` | **regenerated**`npm install --prefix client` |
| `swagger-fragment.json` | **regenerated**`npm run swagger --prefix server` |
<!-- /rename-sites -->
That table is checked. `scripts/checkRenameSites.js` at the root of this kit
compares it against the tree on every pull request: a file that still mentions the
placeholder and is not listed fails the build, and so does a listed file with
nothing left to rename. A checklist nobody verifies is a checklist that is wrong
by the second edit.
Two things you do **not** rename: the mount prefix `/world` need not be your id
(the server's prefix namespace is shared with core's, and `/status`, `/settings`,
`/version` and `/contact` are already taken), and the `world` / `worldStatus`
naming throughout is ordinary vocabulary you should replace with your own domain's
when you replace the feature.
## Licence
GPL-3.0-or-later, like everything else in this project — see
[LICENSE.md](LICENSE.md). This directory is meant to be copied and made yours; it
carries that licence, and so does anything derived from it.