refactor(client): delete the UO client half (phase 3, slice 3)
35 files and 5,332 lines out — twelve public pages, seven admin views, two player views, eight components, the two `data/` leaves and the three `lib/` ones, plus the two tests that came with them. §2.7.1's estimate of 51 files / ~3,700 lines was measured differently and is corrected in the docs PR. The seams core keeps, each smaller than what it replaced: Nine rows leave the public header and six leave the admin sidebar, and both lists are now free of `feature` gates and of `IconShard`. `moduleTitle` already handled a module page's heading, so the six TITLES entries and the `/admin/characters` branch of `sectionTitle` simply go. `/player` had `PlayerCharacters` as its index — a UO page — and rather than name a replacement or invent a landing screen it now resolves to the first row of the portal nav this viewer can reach (`firstDestinationFor`, beside `allowedPathsFor` and reading the BASE nav for the same reason: an override is presentation and where everybody lands is behaviour). With the module installed that is still Characters, so a player's first screen after signing in does not change. Deliberately generic and deliberately not in the portal layout — the admin index is the same question with a hardcoded answer, and if the two logged-in areas ever become one this is what serves both. `game_account_signup` goes with the rest of core's UO prose: the mode list, the derived public flag, the validation and a Site Settings field whose help text named Bridge.cfg. The row itself is untouched and module-uo reads it through ctx.settings — the data stays, the semantics move. KNOWN BREAK, accepted by the org lead: the shipped Android app reads `gameAccountSignup` off `/public/settings` (PublicDto.kt:80). The field has a `= false` default so nothing crashes; the app silently stops offering game-account creation until it reads the module's `/public/shard/features` instead. Out of scope here, recorded in the Android plan, and it lands well before this workstream's cutover reaches `main`. 620 server + 161 client tests. Manifest 158 public + 2 internal, unchanged; routes.guards unchanged. The OpenAPI spec loses exactly one property, and only because it was hand-written in swagger.js — regeneration alone would have left the spec documenting a field core no longer returns. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { navItemVisibleTo, allowedPathsFor, isAllowedPath } from '../src/lib/adminNav.js'
|
||||
import { navItemVisibleTo, allowedPathsFor, isAllowedPath, firstDestinationFor } from '../src/lib/adminNav.js'
|
||||
|
||||
// Moderator confinement, derived from each row's `roles` (Phase 2 PR 8 —
|
||||
// MODULE_SYSTEM.md §1.4). This replaced two hardcoded path lists that had
|
||||
@@ -123,3 +123,50 @@ test('a nav that is not there does not throw', () => {
|
||||
assert.deepEqual(allowedPathsFor(null, 'moderator'), [])
|
||||
assert.equal(isAllowedPath('/admin', undefined), false)
|
||||
})
|
||||
|
||||
// ── firstDestinationFor: where an area's index goes (Phase 3 slice 3) ────────
|
||||
//
|
||||
// `/player` used to be `PlayerCharacters`, a UO page. When the client half was
|
||||
// extracted the portal had no index at all, and rather than pick a fixed page or
|
||||
// invent a core landing screen, the index resolves to the first row this viewer
|
||||
// can reach. The interesting properties are that it follows the ROLE and that it
|
||||
// reads the base nav, not the merged one.
|
||||
|
||||
const PORTAL_NAV = [
|
||||
{ to: '/account/appeals', label: 'Appeals' },
|
||||
{ to: '/account', label: 'Account', end: true },
|
||||
]
|
||||
|
||||
test('the index is the first row a viewer can actually reach', () => {
|
||||
assert.equal(firstDestinationFor(PORTAL_NAV, 'player', '/account'), '/account/appeals')
|
||||
})
|
||||
|
||||
test('a module row registered at the front becomes the index', () => {
|
||||
// The behaviour that makes this a non-regression: with module-uo installed,
|
||||
// Characters is the first row again and a player still lands on it.
|
||||
const withModule = [{ to: '/player/uo/characters', label: 'Characters', moduleId: 'uo' }, ...PORTAL_NAV]
|
||||
assert.equal(firstDestinationFor(withModule, 'player', '/account'), '/player/uo/characters')
|
||||
})
|
||||
|
||||
test('a row this role cannot see is skipped, not landed on', () => {
|
||||
const gated = [{ to: '/player/uo/staff', label: 'Staff', roles: ['admin'] }, ...PORTAL_NAV]
|
||||
assert.equal(firstDestinationFor(gated, 'player', '/account'), '/account/appeals')
|
||||
assert.equal(firstDestinationFor(gated, 'admin', '/account'), '/player/uo/staff')
|
||||
})
|
||||
|
||||
test('an empty or all-gated nav falls back rather than resolving to nothing', () => {
|
||||
assert.equal(firstDestinationFor([], 'player', '/account'), '/account')
|
||||
assert.equal(firstDestinationFor(null, 'player', '/account'), '/account')
|
||||
const allGated = [{ to: '/x', label: 'X', roles: ['admin'] }]
|
||||
assert.equal(firstDestinationFor(allGated, 'player', '/account'), '/account')
|
||||
})
|
||||
|
||||
test('it reads the grouped admin nav too, flattening in order', () => {
|
||||
// Same function for both areas, which is the point: the admin index is a
|
||||
// hardcoded Dashboard today, and if the two logged-in areas ever merge this is
|
||||
// what answers for the result.
|
||||
assert.equal(firstDestinationFor(NAV, 'moderator', '/admin/account'), '/admin')
|
||||
// An editor cannot see Dashboard's neighbours in Moderation, so they land on
|
||||
// the first row they can see wherever it is.
|
||||
assert.equal(firstDestinationFor(NAV, 'editor', '/admin/account'), '/admin')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user