docs(modules): close Phase 5 — the acceptance run, and the page shell it found
Slice 3 of Phase 5 (MODULE_SYSTEM.md 2.11.1), and the phase's last slice. docs/modules/kit-acceptance.md is decision 5's deliverable: a cold agent given the Integration Kit and the documents it links to — never core's source, never module-uo — built a working module for a second game, which was then installed into a real core and taken through MODULE_API.md 7.7's browser smoke. Verdict recorded whichever way it went, and it went **yes, with caveats**: one pass, no core source, and three of the four normative documents never opened. The finding that justifies the two-stage shape is the one the agent structurally could not reach, because it had no core to render against. A module page built exactly as the kit teaches renders OUTSIDE the site: PublicLayout supplies the chrome and not the body, and the `shell-... page-body` wrapper every core public page writes for itself is two class names that appear in no contract. That is 3.4's own stated failure — "a module page that does not look like the site it is installed in" — reached by following 3.4. Fixed in core rather than documented at the reader, so the class names stay core's private business and the theming workstream keeps its freedom to rename them: PublicLayout takes an opt-in `shell` width, MODULE_API_VERSION 1.5.0 (website#148, merges first). - MODULE_API.md 1.1: 1.5.0's entry, and a new bump-table row — adding an OPTIONAL prop or argument is minor. "A member's signature changes" is major because a call already written changes meaning, and an optional prop changes none; the table now says what it means rather than leaving it to be argued. - MODULE_API.md 3.4: the shell prop, why a module names a width and never a class, and the eight-vs-seven miscount the run also turned up — the kit had faithfully carried it out of the contract into the template, which is the never-re-specify rule working exactly as designed on a wrong input. - rust-dryrun.md: coreApi ^1.3.0 -> ^1.5.0, as a dated correction per decision 33. It is the only complete module.json in the kit's reading path and nothing checks a JSON block inside a Markdown file, which is the reusable half. - MODULE_SYSTEM.md 2.11.1: slice 3 recorded, plus the third finding worth generalising — a check whose failure message asserts a diagnosis has to be right about it. `check:swagger` failed on a pristine template on Windows (CRLF) while blaming the routes, green on the Linux runner forever. - Decision 34: core owns the page body as well as the chrome. The banner does not come off. Decision 32 makes that a person's to remove, this run exercised the website-module half only (the module has no sidecar, so chapters 3 and 4 were never tested), and an agent does not skim or give up. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -26,13 +26,27 @@ here extends the contract first, in this file, before the module is written agai
|
||||
Core exports a single integer-major semver string from `server/src/modules/version.js`:
|
||||
|
||||
```js
|
||||
const MODULE_API_VERSION = '1.4.0'
|
||||
const MODULE_API_VERSION = '1.5.0'
|
||||
```
|
||||
|
||||
The client half carries the same number (`client/src/modules/version.js`) and a test asserts the two
|
||||
agree. Duplicated rather than fetched because the value has to be on `window.__rg` before the first
|
||||
module chunk evaluates, which is earlier than any network round trip could answer.
|
||||
|
||||
**1.5.0 — Phase 5 slice 3, the page shell.** `PublicLayout` takes an optional **`shell`** prop —
|
||||
`'narrow'`, `'mid'` or `'wide'` — that renders the page-body wrapper core's own pages have always
|
||||
written by hand (§3.4). Found by the acceptance run in
|
||||
[`../modules/kit-acceptance.md`](../modules/kit-acceptance.md): a module built by following the kit
|
||||
alone rendered *outside* the site's page column, because the wrapper's two class names belong to
|
||||
`theme.css` and appear in no contract. Omitting `shell` is 1.4.0's behaviour exactly, so core's own
|
||||
pages are untouched.
|
||||
|
||||
**Minor, and the table below is why that needs saying.** "A member's signature changes" is a major
|
||||
bump, and a prop is a signature — but the rule is about a call that *already exists* changing
|
||||
meaning, and an optional prop changes none. Read the table as being about what breaks, not about what
|
||||
is typed: adding an optional argument is an addition, and `module-uo`'s `coreApi: "^1.3.0"` still
|
||||
resolves.
|
||||
|
||||
**1.4.0 — Phase 5, the sidecar rule.** §2.7 gained one prohibition: a module does not open a
|
||||
connection to a game server from the website process. It talks to a **sidecar**, which owns the
|
||||
durable copy of the game's state. No member was added, removed or changed — the surface is identical
|
||||
@@ -68,6 +82,7 @@ requires a line of module code, and a mismatch fails that module loudly into `st
|
||||
| Change | Bump |
|
||||
| --- | --- |
|
||||
| A member is added to `ctx`, or a new `register*` call appears | minor |
|
||||
| An **optional** prop or argument is added to an existing member | minor |
|
||||
| A member is removed or its signature changes | major |
|
||||
| Behaviour of an existing member changes without a signature change | major |
|
||||
| A core-internal refactor behind an unchanged member | none |
|
||||
@@ -779,21 +794,52 @@ The kit is **curated and closed**, not a re-export of `components/`:
|
||||
|
||||
| Export | From | Why it is in the kit |
|
||||
| --- | --- | --- |
|
||||
| `PublicLayout` | `components/PublicLayout.jsx` | the public chrome; a module page without it is a bare page |
|
||||
| `PublicLayout` | `components/PublicLayout.jsx` | the public chrome **and**, via `shell`, the page body; a module page without it is a bare page |
|
||||
| `PageHeader` | `components/PageHeader.jsx` | title/subtitle furniture |
|
||||
| `Loading`, `ErrorState`, `EmptyState` | `components/PageState.jsx` | the three states every data page has |
|
||||
| `useAsync` | `lib/useAsync.js` | the fetch/loading/error hook every data page uses |
|
||||
| `useAuth`, `useSite` | `contexts/*` | read-only access to session and site settings |
|
||||
|
||||
Everything else — tables, chips, tabs, the tiptap editor, dnd-kit — a module bundles itself.
|
||||
Adding to the kit is a **minor** `MODULE_API_VERSION` bump; changing a kit component's props is a
|
||||
**major** one. That is a real constraint on core and it is the price of the boundary being worth
|
||||
Adding to the kit is a **minor** `MODULE_API_VERSION` bump; *changing* a kit component's existing
|
||||
props is a **major** one, because that breaks a call already written. Adding an **optional** prop is
|
||||
minor (§1.1). That is a real constraint on core and it is the price of the boundary being worth
|
||||
anything.
|
||||
|
||||
The kit is those **seven** members. An earlier draft of this table listed an eighth, `AdminPage`, and
|
||||
core has no such component — admin views are plain markup inside `AdminLayout`. It was struck in
|
||||
Phase 2 PR 7 rather than satisfied by inventing a core component with no consumer until Phase 3;
|
||||
adding it later costs a minor bump, which is the case this versioning exists for.
|
||||
The kit is those **eight exports** — five rows, because `PageState` contributes three. An earlier
|
||||
draft of this table listed a ninth, `AdminPage`, and core has no such component — admin views are
|
||||
plain markup inside `AdminLayout`. It was struck in Phase 2 PR 7 rather than satisfied by inventing a
|
||||
core component with no consumer until Phase 3; adding it later costs a minor bump, which is the case
|
||||
this versioning exists for.
|
||||
|
||||
#### `PublicLayout` gives you the chrome; `shell` gives you the body
|
||||
|
||||
`PublicLayout` renders the header, the footer and the `.page` flex column. It does **not**, by
|
||||
default, render the body wrapper that every core public page writes for itself:
|
||||
|
||||
```jsx
|
||||
<PublicLayout shell="narrow"> // 'narrow' (760px) · 'mid' (880px) · 'wide' (1280px)
|
||||
<PageHeader title="Our servers" />
|
||||
…
|
||||
</PublicLayout>
|
||||
```
|
||||
|
||||
Without a `shell`, content renders full-bleed from x=0 with no vertical padding, and the footer rides
|
||||
up underneath it instead of sitting at the bottom of the viewport. That last part is the one worth
|
||||
knowing, because it looks like a CSS bug in the module rather than a missing wrapper: the footer is
|
||||
pushed down by `flex: 1` on the body element, so a page with no body element has nothing pushing it.
|
||||
|
||||
**Name a width, never a class.** The classes those widths map to are `theme.css`'s and are *not*
|
||||
contract — the theming workstream owns that file and must stay free to rename them. A module that
|
||||
hardcoded `className="shell-narrow page-body"` would look right today and break silently on a rename,
|
||||
with nothing failing anywhere. An unrecognised width falls back to `narrow` rather than to no
|
||||
wrapper at all, because a page at the wrong width still looks like the site and a page with no
|
||||
wrapper does not.
|
||||
|
||||
This exists because the Integration Kit's acceptance run
|
||||
([`../modules/kit-acceptance.md`](../modules/kit-acceptance.md)) proved a module author cannot
|
||||
discover the wrapper: the kit hands them `PublicLayout` and describes it as the public chrome, which
|
||||
is true and was not enough. Core's own pages keep writing their wrapper by hand and are unaffected.
|
||||
|
||||
### 3.5 `api` — the request primitive
|
||||
|
||||
|
||||
Reference in New Issue
Block a user