feat(modules): PublicLayout takes a shell, MODULE_API_VERSION 1.5.0 #148

Merged
whitlocktech merged 1 commits from fix/public-layout-shell into edge 2026-08-12 19:26:44 +00:00
Member

Phase 5 slice 3 (MODULE_SYSTEM.md §2.11.1). First of four PRs — this one has to merge before Integration-kit can move its pinned ref, because the kit's checkCoreApi asserts equality with core's MODULE_API_VERSION.

Why

The kit's acceptance run put a cold agent in front of the Integration Kit alone — never core's source, never module-uo — and asked it to build a module for a second game. It built one that works. Then I installed it into a real core and opened it in a browser, and the page rendered outside the site:

PublicLayout supplies the chrome and not the body. Every core public page wraps its own content in <div className="shell-… page-body"> — the centred column, the top and bottom padding, and (through page-body { flex: 1 }) the thing that pushes the footer to the bottom of the viewport. Nine of nine core pages do it, so the omission has never shown.

A module cannot. It receives PublicLayout through the UI kit and those two class names appear in no contract, in no chapter, and in nothing the template does. The result was content at x=0 with the footer riding up under it — the exact failure MODULE_API.md §3.4 says the kit exists to prevent, "a module page that does not look like the site it is installed in".

What

<PublicLayout shell="narrow">   // or "mid" / "wide"
  • Opt-in. Omitting shell is 1.4.0's behaviour exactly, so core's nine pages are untouched and keep their own wrapper; a page wanting an unusual body still writes one.
  • Core keeps its class names private. The theming workstream owns theme.css; documenting shell-narrow as a contract surface would mean a rename silently breaks every module's look with nothing failing.
  • An unrecognised width falls back to narrow, never to nothing. A module page at the wrong width still looks like the site; a page with no wrapper does not. The value can arrive from a module built against a different version of this list.

1.5.0 is minor, not major. §3.4 makes changing a kit component's props major because that breaks a call already written; adding an optional one breaks nothing. module-uo's coreApi: "^1.3.0" still resolves and needs no change.

The width map and its fallback live in client/src/lib/pageShell.js rather than in the component, for the reason lib/adminNav.js does: the client runner has no DOM and cannot import .jsx at all (client/test/moduleRegistry.test.js:26 says the same about shared.js), so a rule inside a component is a rule no test can reach.

Also from the same run: modules/shared.js called the UI kit "seven" members while exporting eight (§3.4's table has five rows because PageState contributes three), and its note said AdminPage "appears in §3.4's table" when the table dropped it in Phase 2 PR 7.

Verification

  • 742 server + 192 client tests pass (+5, all on the new pageShell.js, including that every width it offers is a class theme.css actually defines — so a rename fails here instead of silently in someone's page).
  • routes.manifest.json and swagger-output.json regenerate byte-identical. No route changed.
  • Browser, against the acceptance module (MODULE_API.md §7.7) — the only place this seam is visible, and where the defect was found. Before: full-bleed at x=0. After: wrapperClass: "shell-narrow page-body", 760px wide, 580px left margin in a 1920 viewport, footer at 803 of 911. window.__rg.version reads 1.5.0, the chunk logs registered against core API 1.5.0, zero console errors and zero CSP violations.

Follow-ups in this slice

docs (contract §3.4 + §1.1, the acceptance record), Integration-kit (template + chapter 2 use shell, plus six other findings, and the pin moves to this commit), .profile.


  • AI-assisted: written with Claude Code (Claude Opus 5); commits carry a Co-Authored-By trailer.
Phase 5 slice 3 (`MODULE_SYSTEM.md` §2.11.1). **First of four PRs** — this one has to merge before `Integration-kit` can move its pinned ref, because the kit's `checkCoreApi` asserts *equality* with core's `MODULE_API_VERSION`. ## Why The kit's acceptance run put a cold agent in front of the Integration Kit alone — never core's source, never `module-uo` — and asked it to build a module for a second game. It built one that works. Then I installed it into a real core and opened it in a browser, and the page rendered **outside the site**: `PublicLayout` supplies the chrome and *not* the body. Every core public page wraps its own content in `<div className="shell-… page-body">` — the centred column, the top and bottom padding, and (through `page-body { flex: 1 }`) the thing that pushes the footer to the bottom of the viewport. Nine of nine core pages do it, so the omission has never shown. A module cannot. It receives `PublicLayout` through the UI kit and those two class names appear in no contract, in no chapter, and in nothing the template does. The result was content at x=0 with the footer riding up under it — the exact failure `MODULE_API.md` §3.4 says the kit exists to prevent, *"a module page that does not look like the site it is installed in"*. ## What ```jsx <PublicLayout shell="narrow"> // or "mid" / "wide" ``` - **Opt-in.** Omitting `shell` is 1.4.0's behaviour exactly, so core's nine pages are untouched and keep their own wrapper; a page wanting an unusual body still writes one. - **Core keeps its class names private.** The theming workstream owns `theme.css`; documenting `shell-narrow` as a contract surface would mean a rename silently breaks every module's look with nothing failing. - **An unrecognised width falls back to `narrow`, never to nothing.** A module page at the wrong width still looks like the site; a page with no wrapper does not. The value can arrive from a module built against a different version of this list. **1.5.0 is minor, not major.** §3.4 makes *changing* a kit component's props major because that breaks a call already written; adding an optional one breaks nothing. `module-uo`'s `coreApi: "^1.3.0"` still resolves and needs no change. The width map and its fallback live in `client/src/lib/pageShell.js` rather than in the component, for the reason `lib/adminNav.js` does: the client runner has no DOM and cannot import `.jsx` at all (`client/test/moduleRegistry.test.js:26` says the same about `shared.js`), so a rule inside a component is a rule no test can reach. Also from the same run: `modules/shared.js` called the UI kit **"seven"** members while exporting **eight** (§3.4's table has five rows because `PageState` contributes three), and its note said `AdminPage` *"appears in §3.4's table"* when the table dropped it in Phase 2 PR 7. ## Verification - **742 server + 192 client tests pass** (+5, all on the new `pageShell.js`, including that every width it offers is a class `theme.css` actually defines — so a rename fails here instead of silently in someone's page). - `routes.manifest.json` and `swagger-output.json` regenerate **byte-identical**. No route changed. - **Browser, against the acceptance module** (`MODULE_API.md` §7.7) — the only place this seam is visible, and where the defect was found. Before: full-bleed at x=0. After: `wrapperClass: "shell-narrow page-body"`, 760px wide, 580px left margin in a 1920 viewport, footer at 803 of 911. `window.__rg.version` reads `1.5.0`, the chunk logs `registered against core API 1.5.0`, zero console errors and zero CSP violations. ## Follow-ups in this slice `docs` (contract §3.4 + §1.1, the acceptance record), `Integration-kit` (template + chapter 2 use `shell`, plus six other findings, and the pin moves to this commit), `.profile`. --- - [x] AI-assisted: written with Claude Code (Claude Opus 5); commits carry a `Co-Authored-By` trailer.
wtclaude added 1 commit 2026-08-12 19:24:08 +00:00
feat(modules): PublicLayout takes a shell, MODULE_API_VERSION 1.5.0
All checks were successful
PR Checks / bot-install (pull_request) Successful in 17s
PR Checks / client-build (pull_request) Successful in 23s
PR Checks / server-tests (pull_request) Successful in 8m59s
1433b60d6c
The Integration Kit's acceptance run (Phase 5 slice 3) put a cold agent in front
of the kit alone and asked it to build a module for a second game. It built one
that works — and its page rendered outside the site.

PublicLayout supplies the chrome and not the body. Every core public page wraps
its own content in `<div className="shell-... page-body">`: the centred column,
the top and bottom padding, and — through `page-body { flex: 1 }` — the thing
that pushes the footer to the bottom of the viewport. Nine of nine core pages do
it, so the omission has never shown. A module cannot do it: it receives
PublicLayout through the UI kit and those two class names appear in no contract.
The result was a page at x=0 with the footer riding up under the content, which
is the exact failure MODULE_API.md §3.4 says the kit exists to prevent.

So the wrapper moves behind the component a module already has:

  <PublicLayout shell="narrow">   // or "mid" / "wide"

`shell` is opt-in and omitting it is 1.4.0's behaviour exactly, so core's nine
pages are untouched and keep their own wrapper. An unrecognised width falls back
to narrow rather than to nothing — a module page at the wrong width still looks
like the site; a page with no wrapper does not.

1.5.0 is minor, not major. §3.4 makes *changing* a kit component's props major
because that breaks a call already written; adding an optional one breaks
nothing. module-uo's `coreApi: "^1.3.0"` still resolves.

The width map and its fallback live in client/src/lib/pageShell.js rather than in
the component, for the reason lib/adminNav.js does: the client runner has no DOM
and cannot import .jsx at all, so a rule inside a component is a rule no test can
reach. Five tests cover it, including that every width it offers is a class
theme.css actually defines — the contract now names those widths to module
authors, so a rename has to fail here instead of silently in someone's page.

Also from the same run: modules/shared.js called the UI kit "seven" members while
exporting eight (§3.4's table has five rows because PageState contributes three),
and its note said AdminPage "appears in §3.4's table" when the table dropped it in
Phase 2 PR 7.

742 server + 192 client tests pass (+5). routes.manifest.json and the OpenAPI
spec regenerate byte-identical — no route changed.

Verified in a browser against the acceptance module (MODULE_API.md §7.7), which
is the only place this seam is visible: the untouched build renders full-bleed,
and shell="narrow" lands the page in the same column as core's own.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 4ad8b2bb0e into edge 2026-08-12 19:26:44 +00:00
whitlocktech deleted branch fix/public-layout-shell 2026-08-12 19:26:45 +00:00
Sign in to join this conversation.
No description provided.