From 96b364b0bc2119dd636189daaf6800bbb812ebe5 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Tue, 11 Aug 2026 16:46:11 -0500 Subject: [PATCH] docs(website): record slice 2, and what the browser changed about it The contract as written had core branch on hasExtension to decide about its own decoration around a slot. That is right when nothing is installed and wrong when something is installed and fails: the slot IS filled, so the separator renders, and the component then throws into the boundary and leaves the separator behind on its own. Core decorates through now, inside the boundary, and hasExtension is gone rather than kept as a trap for the next caller. Every unit test passed both before and after -- the 7.7 browser smoke is what saw it. Also recorded, neither a defect: core's own fill occupies a slot, so a module cannot fill either one until the client half deletes core's (worth stating, because a module written against 1.2.0 today cannot use them); and core's UO sections on the user-detail page now fail to load, which is slice 1 removing the routes rather than anything this slice did. Co-Authored-By: Claude --- website/MODULE_API.md | 24 +++++++++++++++++++++--- website/MODULE_SYSTEM.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/website/MODULE_API.md b/website/MODULE_API.md index 259364f..2cf6c9c 100644 --- a/website/MODULE_API.md +++ b/website/MODULE_API.md @@ -782,6 +782,19 @@ those props — or **nothing at all** when the slot is unfilled. An instance wit therefore renders exactly what it renders today, which is the same "untouched path" guarantee `withModuleNav` makes for nav. +**Core decorates a slot with ``, never by asking whether it is filled.** `wrap` is called +with the extension's element and rendered *inside* the boundary, so core's own markup around an +extension — a separator, a heading, a rule — shares the extension's fate: + +```jsx + <> · {link}} /> +``` + +There is deliberately no `hasExtension` for a layout to branch on. Branching is right about the +unfilled case and wrong about the failed one: the slot *is* filled, so the separator renders, and +the component then throws into the boundary and leaves the separator behind on its own. Found in a +browser with this exact footer separator, which is the only place it could have been found. + The slots in 1.2.0: | Slot | Rendered in | Props core passes | @@ -830,10 +843,15 @@ already uses for the feature seam. It means the mechanism is exercised by core's moment it lands, and the extraction becomes a deletion rather than a rewrite made under extraction pressure. -`declareSlot` and `hasExtension` are module exports of `modules/registry.js` and deliberately **not** +**While core fills a slot, no module can** — first fill wins, and core registers first. In 1.2.0 that +means both slots are occupied and a module's fill is rejected, loudly, naming `core`. This is +scaffolding, not the steady state: core's two fills and the two files behind them are deleted by the +client half of Phase 3, in the same change that registers the module's. Neither slot is one core +intends to fill permanently, and a slot core *does* intend to fill is a slot that should not exist. + +`declareSlot` and `extensionFor` are module exports of `modules/registry.js` and deliberately **not** members of the `registry` object handed to modules, for the same reason `featureProviders()` is not: -declaring is core's, and a module asks about a slot it knows the name of rather than enumerating what -everyone else filled. +declaring is core's, and so is reading back who filled what. --- diff --git a/website/MODULE_SYSTEM.md b/website/MODULE_SYSTEM.md index 85b4e29..6922eca 100644 --- a/website/MODULE_SYSTEM.md +++ b/website/MODULE_SYSTEM.md @@ -882,6 +882,35 @@ Two decisions inside it, both settled with the org lead 2026-08-11: `registries.registerCore()` already use — so the rendered page is unchanged and the mechanism is proved by core's own content before a line of it moves. +#### Slice 2 — the slots (website#138, 2026-08-11) + +`modules/registry.js` grows `declareSlot` / `registerExtension` / `extensionFor`; `modules/Slot.jsx` +is the read side and core's only error boundary. `SiteFooter`'s link becomes `ShardStatusLink.jsx` +and `UserDetail`'s six UO sections become `UserShardSections.jsx`, both registered by `main.jsx` +under owner id `core`. 616 server + **169 client** tests (+9), manifest still 158, swagger +byte-identical. + +**The §7.7 browser smoke earned its place again, and this time by finding something no test would +have.** A throwaway module filled both slots: the footer rendered the module's own label and target +in core's `linkStyle`, the admin page received `userId`, and a deliberate render failure was +contained to its own spot with the slot named in the console — but the footer was left showing +`email · · Admin`. The separator was rendered *beside* the slot, guarded on `hasExtension`, which is +right when nothing is installed and wrong when something is installed and fails. Core now decorates +through ``, inside the boundary, and `hasExtension` is gone rather than kept as a trap for +the next caller. Every unit test passed both before and after. + +Two more things the smoke settled, neither of them a defect: + +- **Core's own fill occupies the slot, so a module cannot fill it** — first fill wins and core + registers first, so the smoke module's call was rejected naming `core`. That is correct and + temporary: the client half deletes core's two fills in the change that registers the module's. It + is worth stating in the contract, because a module written against 1.2.0 today cannot use either + slot. +- **Core's UO sections on the user-detail page now fail to load, and that is slice 1's doing, not + this slice's** — core stopped serving `/admin/users/:id/shard/*` when the server half left, and + with no module installed there is nothing at the other end. Identical before and after this change; + the client half is what puts a live module behind it. + **Criterion 1 is a grep over code, not over prose** — see [API §5.2](MODULE_API.md#52-zero-uo-identifiers-in-core-ci-website-repo) for what that means precisely. Core's marketing copy says "shard" in a dozen places, and a literal word grep would have made every one of them a CI failure while proving nothing about the boundary.