feat(modules): PublicLayout takes a shell, MODULE_API_VERSION 1.5.0 #148
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/public-layout-shell"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 5 slice 3 (
MODULE_SYSTEM.md§2.11.1). First of four PRs — this one has to merge beforeIntegration-kitcan move its pinned ref, because the kit'scheckCoreApiasserts equality with core'sMODULE_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:PublicLayoutsupplies 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 (throughpage-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
PublicLayoutthrough 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 failureMODULE_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
shellis 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.theme.css; documentingshell-narrowas a contract surface would mean a rename silently breaks every module's look with nothing failing.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'scoreApi: "^1.3.0"still resolves and needs no change.The width map and its fallback live in
client/src/lib/pageShell.jsrather than in the component, for the reasonlib/adminNav.jsdoes: the client runner has no DOM and cannot import.jsxat all (client/test/moduleRegistry.test.js:26says the same aboutshared.js), so a rule inside a component is a rule no test can reach.Also from the same run:
modules/shared.jscalled the UI kit "seven" members while exporting eight (§3.4's table has five rows becausePageStatecontributes three), and its note saidAdminPage"appears in §3.4's table" when the table dropped it in Phase 2 PR 7.Verification
pageShell.js, including that every width it offers is a classtheme.cssactually defines — so a rename fails here instead of silently in someone's page).routes.manifest.jsonandswagger-output.jsonregenerate byte-identical. No route changed.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.versionreads1.5.0, the chunk logsregistered 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 useshell, plus six other findings, and the pin moves to this commit),.profile.Co-Authored-Bytrailer.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>