feat(client): the whole client half (phase 3, slice 3) #4
Reference in New Issue
Block a user
No description provided.
Delete Branch "feature/module-extract-client"
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 3, slice 3 of
docs/website/MODULE_SYSTEM.md§2.7.1 — the client half arrives. Pairs with website#139 (which deletes it from core) and docs#138. Merge this one first, as every slice but 2.What moved
35 files / 5,332 lines: twelve public pages, seven admin views, two player views, eight components, two
data/leaves and threelib/ones, plus the two client tests that came with them. §2.7.1 estimated 51 files / ~3,700 lines; the table is corrected in the docs PR rather than the count massaged to match.The dependency claim held exactly. Every one of the 35 imports the seven §3.4 kit members plus
lib/format.jsand nothing else — so the kit needed no additions.client/src/core.jsis the port mechanism and, unlike the server's, a plain read:window.__rgis published before any module chunk evaluates, so there is no gap to defer around and a ported component keeps its ordinary import shape rather than being wrapped in an accessor that would cost it its identity.client/src/api.jsrebuilds the UO namespaces over the request primitive — same URLs, because §1.2 freezes the API surface and the shipped Android app calls seven of them.Only
ago()was vendored, not all six of core's date helpers: slice 1's vendoring line (pure leaf helpers may be copied, security controls may not), plus copy what you use.SPA paths changed; API paths did not
/site/shard→/uo/shard, and the admin paths lost their now-redundant prefixes:/admin/shard-ops→/admin/uo/ops,/admin/shard-visibility→/admin/uo/visibility,/admin/shard→/admin/uo/link. A clean break is the only moment that tidy-up is free. Every URL inapi.jsis byte-identical to the one core called.The server half of this PR: a defect slice 1 shipped
POST /api/v1/player/shard/accountand its staff twin have answered 500 for every caller since slice 1. The ported controller calledsettings.isGameAccountSignupEnabled(), which is a member of core's settings model and not ofctx.settings— three functions, deliberately. The call wasundefined(...), the TypeError landed in the catch, and no test reached the branch.The gate now lives on the side that uses it (
utils/gameSignup.js), which is also where the policy belongs: the setting's help text names Bridge.cfg and says the shard's SignupMode must agree, and core cannot own a sentence about a UO shard. The admin field moves to this module's Shard page and the derived flag onto/public/shard/features. The setting key is unchanged — renaminggame_account_signupwould silently reset every configured instance todisabled.Both regression tests were shown to fail against the bug before it was fixed.
Two checks that had never met a real chunk
checkExternals.jsrejected this build outright, naming a fragment of minified JSX as an imported specifier — a button reading "Approve and import" puts the token immediately before a quote, and no regexp distinguishes a keyword from the same letters inside a string. Same wall the server'scheckImports.jshit, answered the same way: a character walk. A mask rather than a rewrite, because a real import has its keyword outside a string and its specifier inside one.Writing the test for that false positive found the false negative underneath it: the pattern required whitespace after
import, so it could never seeimport{useState}from"react"— the one shape a minified build actually emits, and the likeliest way for a missed alias to reach production. It has never been able to see it.Verification
gameSignup.test.js+8,registration.test.js+8, the two ported +12)check:importsclean;check:externalsclean on the built chunkregistration.test.jsis new: a fakewindow.__rgwith a recording registry and the real React, importing the built chunk to read back what it registered — no DOM, because nothing renders. It holds the agreement that rots quietly (every nav row points at a route this module registered) rather than restating both lists./uo/*with core's chrome, nine header rows, six sidebar rows with their glyph inside core's Moderation and System groups,/admin/uo/linkwith the migrated signup field showing the live value, all three slots filled,/playerresolving to/player/uo/characters, and the invite flow end to end.The smoke found one defect, fixed in both repos: registering the player nav row without an icon blanked the entire portal with React error #130, because core rendered
<n.icon />unguarded there. This side ships the icon the row had before it moved and asserts one on every admin and player row.AI disclosure
Written with Claude Code.
Co-Authored-By: Claude noreply@anthropic.com
Three things, all about checks that had never met a real chunk. `checkExternals.js` rejected slice 3's build outright, naming a fragment of minified JSX as an imported specifier: a button reading "Approve and import" puts the token immediately before a quote, and no regexp can tell that from a statement. Same wall the server's `checkImports.js` hit, answered the same way — a character walk. A mask rather than a rewrite, because a real import has its keyword outside a string and its specifier inside one. Writing the test for that false positive found the false NEGATIVE underneath it: the pattern required whitespace after `import`, so it could not see `import{useState}from"react"` — the one shape a minified build actually emits, and the most likely way for a missed alias to reach production. It has never been able to see it. `registration.test.js` is new: stand up a fake `window.__rg` with a recording registry and the real React, import the BUILT chunk, and read back what it asked for. No DOM, because nothing renders. It holds the agreement that rots quietly — every nav row points at a route this module actually registered — rather than restating both lists. CI now builds before it tests, because both of those read `dist/entry.js` and skip without it. Run the other way round they are green and asking nothing. Co-Authored-By: Claude <noreply@anthropic.com>