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.