docs(website): the server half does not slice, and why
Found before writing any of it. §2.7.1 split the server extraction five ways by feature; it does not divide, because two contract rules compose. A mount prefix is claimed whole -- ownedByCore probes the live tier router and registerRoutes validates single-segment prefixes -- and /admin/shard is one 386-line router carrying 25 routes across atlas, clilocs, shard-ops, visibility, market and account links. Meanwhile a model cannot be shared across the boundary (§5.1), so it moves with the last route that consumes it. Take the closure and every prefix is in it: /public/atlas holds shardAtlas with /admin/shard, which holds shardState/shardEvents/shardMarket with /public/shard, which holds uoLinkConfig with /admin/uo-link, and shardClilocs/shardLinks with /player/shard. Landing any one of the old slices alone would either strand core importing modules/uo/ -- what acceptance criterion 2 forbids -- or delete routes core is still serving. Giving the admin routes their own prefixes would divide it and is rejected: it changes API URLs, which §1.2 promises not to do, and not hypothetically. The shipped Android app calls POST /api/v1/admin/shard/kick, /ban, /unban, /broadcast and the three /pages routes. So the table is now six slices: 0 the skeleton (done), 1 the whole server half as ONE PR per repo structured as five commits along the old slice lines, 2-4 the client halves, 5 close the phase. The client half is unaffected and still slices cleanly -- the registry takes routes per area, with no prefix atomicity and no shared models, the same asymmetry that let the two halves be separated at all. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -777,21 +777,59 @@ Each slice is one `module-uo` PR (adds), one `website` PR (deletes), and one `do
|
||||
|
||||
| # | Slice | Moves |
|
||||
| --- | --- | --- |
|
||||
| 0 | **The bundle skeleton** | `module.json`, both `package.json`s, `server/index.js` registering nothing, the Vite library build + the four shared-dep shims, an empty `schema.sql`/`purge.sql`, CI armed, the §5.1 zero-internal-imports check. `website` untouched. |
|
||||
| 1 | **Atlas + clilocs** | `shardAtlas`/`shardClilocs` models, `clilocParse`/`clilocSource`/`spawnAtlasParse`/`spawnAtlasSource`, `public/atlas.*`, `admin/shardAtlas.controller`/`shardClilocs.controller`, `scripts/importSpawnAtlas.js`, the art JSON |
|
||||
| 2 | **The live shard** | `shardState`/`shardEvents`/`shardVisibility`/`uoLinkConfig` models, `uoLinkClient`/`uoLinkSocket`/`shardIngest`/`shardBroadcast`/`shardPush`/`shardVisibility` utils, `config/shardStreams.js`, `public/shard.*`, `admin/shard.router`/`shardOps`/`shardVisibility`/`uoLink.*` |
|
||||
| 3 | **Market** | `shardMarket` model, `shardSales` util and their routes |
|
||||
| 4 | **Account links + the extension slot** | `shardLinks` model, `player/shard.*`, `admin/usersShard.*` minus `getUser` — the first real filling of `admin.users.detail` |
|
||||
| 5 | **The news leg** | `newsGump.js`, `shardAnnounce.js`, the town-crier `registerAnnounceLeg` |
|
||||
| 6 | **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 |
|
||||
| 7 | **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/*` |
|
||||
| 8 | **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 |
|
||||
| 9 | **Close the phase** | `module-uo`'s frozen route manifest and release workflow; `docs/modules/uo/` and `docs/modules/rust-dryrun.md` |
|
||||
| 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 |
|
||||
| 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
|
||||
|
||||
The table above used to run to ten slices, with the server half split five ways by feature. It does
|
||||
not divide, and the reason is that two contract rules compose:
|
||||
|
||||
- **A mount prefix is claimed whole.** `ownedByCore` probes the live tier router and `registerRoutes`
|
||||
validates single-segment prefixes, so `/admin/shard` moves as one unit — and it is a single
|
||||
386-line router carrying 25 routes that span atlas, clilocs, shard-ops, visibility, market *and*
|
||||
account links.
|
||||
- **A model cannot be shared across the boundary** (§5.1), so a model moves with the *last* route
|
||||
that consumes it.
|
||||
|
||||
Take the closure and every prefix is in it:
|
||||
|
||||
```
|
||||
/public/atlas ──shardAtlas── /admin/shard ──shardState,shardEvents,shardMarket── /public/shard
|
||||
│ │
|
||||
shardClilocs,shardLinks uoLinkConfig
|
||||
│ │
|
||||
/player/shard /admin/uo-link
|
||||
```
|
||||
|
||||
Landing any one of the old slices alone would either strand core importing `modules/uo/` — which is
|
||||
precisely what acceptance criterion 2 forbids — or delete routes core is still serving.
|
||||
|
||||
**Giving the admin routes their own prefixes would divide it, and is rejected.** `/admin/atlas` and
|
||||
`/admin/clilocs` alongside a slimmer `/admin/shard` would make the closure fall apart. It also
|
||||
changes API URLs, which §1.2 promises not to do — and not hypothetically: the shipped Android app
|
||||
calls `POST /api/v1/admin/shard/kick`, `/ban`, `/unban`, `/broadcast` and the three `/pages` routes
|
||||
(`data/api/AdminApi.kt`). A prefix rename is a client break, and the API surface is frozen for
|
||||
exactly this reason.
|
||||
|
||||
**So slice 1 is one PR per repo, structured as five commits** along the old slice lines, reviewable
|
||||
one at a time while landing atomically: atlas + clilocs · the live shard · market · account links and
|
||||
the `admin.users.detail` slot · the town-crier leg. The alternative considered was stacked PRs into a
|
||||
per-repo integration branch; it buys PR-level granularity for ten extra PRs and two long-lived
|
||||
branches, and commits give most of the same reading order for none of it.
|
||||
|
||||
**The client half is unaffected** and still slices cleanly: the client registry takes routes per
|
||||
*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.
|
||||
|
||||
**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.
|
||||
Slice 8 rewrites that copy anyway, because a core that still reads as a UO site is not the
|
||||
Slice 4 rewrites that copy anyway, because a core that still reads as a UO site is not the
|
||||
game-agnostic platform this workstream is for — but it is a deliberate piece of work with its own
|
||||
review, not an exemption hidden in a grep pattern.
|
||||
|
||||
@@ -827,7 +865,7 @@ of what to catch, and the entry point's comment explaining why a module must nev
|
||||
`require('express')`. A check that cannot survive being described is one people stop writing
|
||||
comments around, so it strips comments and template literals with a character walk rather than a
|
||||
regexp (a URL in a string contains a comment opener; a comment contains quotes) and carries its own
|
||||
test suite. The same applies to slice 8's §5.2 grep, which will be read by a codebase that discusses
|
||||
test suite. The same applies to slice 4's §5.2 grep, which will be read by a codebase that discusses
|
||||
modules constantly.
|
||||
|
||||
**One thing to know before running a module locally: the loader skips a *symlinked* module directory
|
||||
|
||||
Reference in New Issue
Block a user