fix(kit): everything the acceptance run found — Phase 5 slice 3
All checks were successful
PR Checks / prose (pull_request) Successful in -38s
PR Checks / template (pull_request) Successful in 29s

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>
This commit is contained in:
2026-08-12 14:40:02 -05:00
parent d5a8520ce0
commit f8f7014d53
16 changed files with 362 additions and 36 deletions

View File

@@ -47,6 +47,30 @@ cp -r template/ ~/my-module
cd ~/my-module
```
**Run every check on the untouched copy before you change a line.** Jump ahead to
*Build it* and run all of it — the tests, the build, the three guards — on the
template exactly as it arrived:
```bash
npm ci --prefix server && npm test --prefix server
npm run check:imports --prefix server
npm run check:swagger --prefix server
npm ci --prefix client && npm run build --prefix client
npm run check:externals --prefix client && npm test --prefix client
```
It takes two minutes and it buys you a **baseline**. Every one of those commands
is green on a pristine template, so from here on a red one is something you did —
and you will know which edit did it, because you were green a moment ago. Without
that, the first failure is ambiguous forever: is this my mistake, or was the
template already like this?
That is not a hypothetical. The kit's own acceptance run
([`kit-acceptance.md`][acceptance]) found `check:swagger` failing on an untouched
copy on Windows, with a message that blamed the reader's routes. It is fixed, and
the reason the run *found* it rather than being derailed by it is that it had a
baseline.
Your module id is the single most load-bearing string in it: it is the directory
core loads you from, the key in core's database, the URL segment every one of your
pages hangs under, and the prefix every one of your tables must carry. It must
@@ -242,3 +266,4 @@ module must never do.
[api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md
[issues]: https://gitea.whitlocktech.com/RunicGateway/Integration-kit/issues
[renamecheck]: ../scripts/checkRenameSites.js
[acceptance]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/kit-acceptance.md

View File

@@ -392,12 +392,34 @@ and empty states, the async hook every data page uses, and read-only access to t
session and site settings. Enough to build a page that looks like the site it is
installed in, and nothing else.
**`PublicLayout` needs a `shell`, and this is the one that will catch you.** The
layout is the *chrome* — header, footer, the flex column they sit in. The `shell`
prop is the *body*: the centred max-width column, the vertical padding, and the
element whose `flex: 1` is the only thing holding the footer at the bottom of the
viewport.
```jsx
<PublicLayout shell="narrow"> // 'narrow' · 'mid' · 'wide'
```
Omit it and your content starts hard against the left edge of the window with no
padding, and the footer climbs up underneath it. It reads as a stylesheet bug in
your module and it is not one — core's own pages write that wrapper by hand, and
before `MODULE_API_VERSION` 1.5.0 a module had no way to. **Name a width, never a
class:** the class names are core's stylesheet's and it is free to rename them,
which is exactly why they are not in the contract and this prop is.
That paragraph exists because the kit's acceptance run
([`kit-acceptance.md`][acceptance]) built a module by following this chapter to the
letter, and its page rendered outside the site. Everything else it wrote was right.
**It is curated and closed, not a re-export of core's component library.** Adding
to it is a minor version bump; changing a kit component's props is a major one.
to it is a minor version bump, and so is adding an optional prop to a member;
changing an existing prop is a major one.
That is a real constraint on core, and it is the price of the boundary being worth
anything.
So: when you want an eighth thing, bundle it. Tables, chips, tabs, editors — those
So: when you want something it does not have, bundle it. Tables, chips, tabs, editors — those
are yours, and your chunk carries them. Reaching into core's tree for a component
is the one thing that is never available, and `template/server/scripts/checkImports.js`
exists to make sure a moment of weakness fails the build instead of shipping.
@@ -433,10 +455,26 @@ nothing at runtime will ever say so.
The trap: **swagger-autogen reports a broken annotation and then prints
`Success`.** It logs a syntax error, drops that annotation, and exits zero. The
template's generator captures those diagnostics and fails on them — keep that.
Two ways an annotation breaks are an object literal one brace short, and a `"` or
a backtick inside a single-quoted description. A third is only visible in a
rendered page: an escaped apostrophe (`\'`) survives literally into the output,
because the annotation is never evaluated as JavaScript. Use a typographic ``.
The usual cause is an object literal one brace short.
**A quote character is worse, because it does not log anything.** These
annotations are evaluated as JavaScript literals, so a `'` or a `"` inside a
single-quoted description ends the string early — and for a `"` in the middle of
a sentence the result is not an error at all. The value is silently **truncated**
at that character:
```js
// #swagger.summary = 'A "quoted" world status'
// → "summary": "A \"" and swagger-autogen still prints Success
```
Nothing throws, so the generator's error capture has nothing to capture. The only
signal is `check:swagger` calling the fragment stale, with a message that blames
your routes. **If that check fires and your routes did not change, look for a
quote in an annotation first.** Backticks are safe — Markdown spans survive
verbatim. And an escaped apostrophe (`\'`) is a third case, visible only in a
rendered page: the annotation is never evaluated as JavaScript by the reader, so
Swagger UI shows the backslash. Use a typographic `` throughout.
## Packaging and release
@@ -485,3 +523,4 @@ it** — an outbound socket is not statically detectable the way an internal
the next chapter is for.
[api]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/website/MODULE_API.md
[acceptance]: https://gitea.whitlocktech.com/RunicGateway/docs/src/branch/main/modules/kit-acceptance.md