docs(website): client extension slots, and the client half is one slice
Two findings from surveying the client half, and one org-lead decision that turns them into the same piece of work. The client half does not divide into public and admin after all. Routes slice by area, but useShardFeed and shardEvents are imported by eight public pages AND three admin views, and a shared leaf moves with its last consumer -- the same rule the server half taught. Of the three ways out, two trade a real cost for a boundary that lasts one review, so the client half is one slice. The nav rows keep their feature provider as a consequence (rows without it fail open, so disabled surfaces would advertise themselves again), and VendorSales turns out to have no public consumer at all. Its three consumers include core's own UserDetail, which renders six UO sections with nowhere to put them: the server got declareSlot/registerExtension in phase 2 PR 4 and the client never got the twin. SiteFooter's link to a URL the extraction deletes is the same gap one component over. So slice 2 is core-only and adds rather than moves: client extension slots (API 3.7), the site.footer.status and admin.users.detail slots, core filling both itself. MODULE_API_VERSION goes to 1.2.0. A slot is named for a PLACE, never for a meaning -- core supplies the position and the styling, the module owns the label, the target, the data and whether it renders at all. Typing a slot by its content would put game semantics back into core, which is what this phase removes. This is also the one slice where core merges before module-uo, because a chunk cannot call registerExtension before the function exists. Harmless here: the slice only adds, and core fills both slots with its own components under owner id `core`, so the page is unchanged and the mechanism is proved before anything moves. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -779,9 +779,9 @@ Each slice is one `module-uo` PR (adds), one `website` PR (deletes), and one `do
|
||||
| --- | --- | --- |
|
||||
| 0 | **The bundle skeleton** | `module.json`, both `package.json`s, `server/index.js` registering nothing, the Vite library build + the four shared-dep shims, CI armed, the §5.1 zero-internal-imports check. `website` untouched. |
|
||||
| 1 | **The whole server half** | 40 files / ~9,674 lines, 25 of core's 82 test files, 27 of its 68 tables — every UO model, util, router and controller, `config/shardStreams.js`, `scripts/importSpawnAtlas.js` and the art JSON. **One merge, five commits** (below). |
|
||||
| 2 | **Public pages** | `Shard`, `ShardActivity`, `Rules`, `Atlas`, `AtlasCreature`, `ChampSpawns`, `Market`, `MarketVendor`, `Governors`, `Guilds`, `Houses`, `Leaderboards`, `PlayersOnline`, `VendorSales`, `data/cityCrests.js`, `lib/shardEvents.js`, `lib/useShardFeed.js`, and their public nav rows — under `/uo/*` per §2.8 |
|
||||
| 3 | **Admin + player pages** | `ShardAdmin`, `ShardOps`, `ShardVisibility`, `SpawnAtlas`, `HousesAdmin`, `AdminCharacter(s)`, `PlayerCharacter(s)`, `GameAccounts`, `CharacterSheet`, `CharacterStats`, `ShardAccountActions`, `CreateGameAccountForm`, `useShardFeatures` and the `useShardFlags` feature provider — under `/admin/uo/*` and `/player/uo/*` |
|
||||
| 4 | **De-UO core's copy** | `About`, `Screenshots`, `Website`, `SiteFooter`, `heroLayout`'s defaults, `api/client.js`'s `shard`/`atlas` namespaces, and the comments in `navOverrides.js` — plus the §5.2 CI grep that keeps them out |
|
||||
| 2 | **Client extension slots** | Core only, and the one slice that adds rather than moves: the client twin of `declareSlot`/`registerExtension` (API §3.7), the `site.footer.status` and `admin.users.detail` slots, and core filling both itself. `module-uo` untouched. |
|
||||
| 3 | **The whole client half** | 51 files / ~3,700 lines — all twelve public pages (`Shard`, `ShardActivity`, `Rules`, `Atlas`, `AtlasCreature`, `ChampSpawns`, `Market`, `MarketVendor`, `Governors`, `Guilds`, `Houses`, `Leaderboards`) under `/uo/*`, every admin and player view under `/admin/uo/*` and `/player/uo/*`, `PlayersOnline`, `VendorSales`, `CharacterStats`, `GameAccounts`, the `data/` and `lib/` UO leaves, the public nav rows, the feature provider, and both slot fills. |
|
||||
| 4 | **De-UO core's copy** | `About`, `Screenshots`, `Website`, `SiteFooter`'s prose, `heroLayout`'s defaults, `api/client.js`'s `shard`/`atlas` namespaces, and the comments in `navOverrides.js` — plus the §5.2 CI grep that keeps them out |
|
||||
| 5 | **Close the phase** | `module-uo`'s frozen route manifest and release workflow; `docs/modules/uo/` and `docs/modules/rust-dryrun.md` |
|
||||
|
||||
##### Why the server half cannot be sliced — found 2026-08-11, before writing any of it
|
||||
@@ -826,6 +826,62 @@ branches, and commits give most of the same reading order for none of it.
|
||||
*area*, with no prefix atomicity and no shared models — which is the same asymmetry that let the two
|
||||
halves be separated in the first place.
|
||||
|
||||
##### Why the client half is one slice after all — settled 2026-08-11
|
||||
|
||||
The paragraph above is right about the *mechanism* and wrong about the outcome. Routes do slice by
|
||||
area, but the files behind them do not divide along that line, and the reason is the same rule the
|
||||
server half taught: **a shared leaf moves with its last consumer.**
|
||||
|
||||
`lib/useShardFeed.js` and `lib/shardEvents.js` are imported by eight of the public pages *and* by
|
||||
three admin views. Splitting public from admin means the module needs them one slice before core is
|
||||
finished with them, and there are only three ways out — the module vendors a copy for one slice,
|
||||
public keeps only the four pages that never touch the live feed, or the two slices become one. The
|
||||
first two both trade a real cost for a boundary that lasts one review, so the client half is **one
|
||||
slice**: all twelve public pages, every admin and player view, and the leaves under them, in one
|
||||
`module-uo` PR and one `website` PR.
|
||||
|
||||
Two things that were separately tabled fold into it as a consequence, and both are improvements:
|
||||
|
||||
- **The nav rows and their feature provider stay one unit.** The nine UO rows in the public header
|
||||
carry `feature` gates resolved by `useShardFlags`, which core registers under namespace `uo`.
|
||||
Since resolution is by the *registering* module (§1.5), rows that move without their provider
|
||||
resolve against a namespace nothing answers for — and everything fails open, so nine rows an
|
||||
operator may have disabled or gated to staff would advertise themselves again for the length of a
|
||||
slice. `useShardFeatures.js` already says as much in its own closing comment.
|
||||
- **`VendorSales` was tabled with the public pages and has no public consumer at all.** Its three are
|
||||
`AdminCharacters`, `PlayerCharacters` and core's own `UserDetail` — which is the next finding.
|
||||
|
||||
##### Core's user-detail page needs a client extension slot — and so does the footer
|
||||
|
||||
`UserDetail.jsx` renders a core header, core's `SecurityAdmin`, and then six UO sections. The server
|
||||
half already had somewhere to put that: Phase 2 PR 4 declared the `admin.users.detail` slot and moved
|
||||
the six `/admin/users/:id/shard/*` routes behind it. **The client never got the twin** — `registry`
|
||||
takes routes, nav and feature providers, and nothing else — so the client half had nowhere to put the
|
||||
same page's other half.
|
||||
|
||||
The same gap shows up one component over. `SiteFooter` links to `/site/shard`, a URL the extraction
|
||||
deletes, and the footer is not nav so no registry answers for it.
|
||||
|
||||
Both are the same missing mechanism, and it is **slice 2**, core-only: core declares a slot, at most
|
||||
one module fills it, and core renders `<Slot>` — nothing when unfilled. The contract is
|
||||
[API §3.7](MODULE_API.md#37-extension-slots--module-content-inside-a-core-page); `MODULE_API_VERSION`
|
||||
goes to **1.2.0**.
|
||||
|
||||
Two decisions inside it, both settled with the org lead 2026-08-11:
|
||||
|
||||
- **The footer slot is named for a place, not for a meaning.** The idea started as "make shard status
|
||||
a hook any module can use", which is right, and the only refinement is that core must not learn
|
||||
what a game server's status *is*. `site.footer.status` is a position and a bit of styling; the
|
||||
label, the target, the data and whether anything renders at all are the module's. A slot typed by
|
||||
its content would put game semantics back in core, which is the thing this phase removes.
|
||||
- **This slice inverts the phase's merge order, once.** Everywhere else `module-uo` merges before
|
||||
`website` so `edge` is never missing a feature. Here core must go first, because a module chunk
|
||||
cannot call `registry.registerExtension` before the function exists. That is harmless precisely
|
||||
because this slice only *adds*: core declares both slots and fills them with its own existing
|
||||
components under owner id `core` — the same trick `useShardFlags` and the server's
|
||||
`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.
|
||||
|
||||
**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.
|
||||
|
||||
Reference in New Issue
Block a user