feat(theming): settings-store, nav merge util and radius tokens (phases 0-2) #121
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/theming-nav-phase-0-2"
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?
What & why
Phases 0–2 of
docs/website/THEMING_AND_NAV.md(approved in docs#103). Groundwork only — no admin UI, no consumer wiring. An instance that never touches the new settings keys renders exactly as it does today, which is the acceptance criterion for all three phases.Targets
edge, notmain. The whole feature lands onmainas oneedge→mainmerge once every phase is in, so no release carries a half-wired theme engine. Docs pair: RunicGateway/docs#104.Phase 0 — settings-store groundwork
settingsDb.remove()+DELETE /api/v1/admin/settings/:key— the "reset to default" primitive. Defaults for these keys live inBRAND_*env,theme.cssand the hardcodedNAVarrays, so reset has to delete the row; writing a stored copy of a default would stop tracking the default forever. Allowlisted (DELETABLE_KEYS) to the five theming/nav keys plushero_layout_draft— an unrestricted DELETE would let a stray request dropsite_modeor the uo-link config, where an absent row means something else entirely. Admin-only, and idempotent: resetting a key that was never set succeeds.GET /api/v1/settings/navbehindrequireAuth, no role gate — §4.2's blocking gap.AdminLayoutrenders for editors and moderators andPlayerPortalLayoutfor players, and none of them can readGET /admin/settings, so without this route their nav override would silently never apply./publicis anonymous (and the admin nav's labels describe the shape of the admin surface),/admin/settingsisadminOnly,/playeris data scoped toreq.user.id. This is configuration that happens to need a login. Group gate isnoindex, requireAuth, declared insettings/index.jsahead of every mount.parseJsonSetting()inutils/settingsJson.js.settings.valueisTEXT, so every JSON-valued key arrives as a string; malformed, non-object, or validator-rejected values read as absent — never as an error, never half-applied. Same fail-safe posture as the client's existingparseLayout.theme_visual/brand_assets/nav_publicjoinPUBLIC_KEYS;nav_admin/nav_playerdeliberately do not. No migration seeds any of them — absence is the "use the default" state.Phase 1 —
client/src/lib/navOverrides.jsThe pure merge util, the one piece with real correctness risk. Presentation only: it may set
label,order,hiddenand (grouped navs)group, and nothing else. It cannot introduce ato, cannot touchroles/feature/icon/end, andhidden: falsecannot un-hide anything — hiding is subtractive, and the existing filters inSiteHeader/AdminLayoutrun afterward unchanged and remain the boundary.Three rules the design left open, settled here and locked by tests:
orderdoesn't scramble the rest. Explicit and implicit keys share one number line, so ties break explicit first ("0" means first), and equal explicit orders keep code order via a stable sort.group— accepted only when it names a title the base nav already declares, so an item can never land under a header that doesn't exist. Group order is not overridable.labeldoesn't discard a goodorderbeside it;hiddenis honored only as the literaltrue; unknowntovalues are dropped, so removing a route in code can't leave a dangling override.Phase 2 — radius tokens
23
border-radiusliterals promoted to four tokens at today's values, matching the §4.7 census exactly: 14×8px→--radius-input, 4×999px→--radius-pill, 4×10px→--radius-card, 1×12px→--radius-panel. The7px(.rte-btn) and6px(.rte-linkmenu-item) editor chrome and the two50%circles stay literal.--shadow-cardand--panel-gradturned out to already be tokens and already derived, so the shadow half of the phase was a no-op — bothbox-shadowdeclarations intheme.cssalready readvar(--shadow-card).How it was tested
cd server && npm test— 695 pass, 0 fail, including 16 new tests intest/settingsTheming.test.js: the allowlist (each theming key resettable;site_mode/uo_link_token/player_registration/hero_layoutrefused), reset-never-writes, idempotency, editor 403,/settings/navreachable by admin/editor/moderator/player and 401 anonymous,nav_admin/nav_playernever public, andparseJsonSetting's absent/malformed/validator cases.cd client && node --test— 67 pass, 0 fail, including 20 new tests intest/navOverrides.test.js. The load-bearing negatives: no override returns the same array reference, an unknowntois never added, gates survive verbatim, and hiding every item in a group leaves no orphaned header.npm run swaggerandnpm run routes:manifestregenerated; the manifest diff is exactly the two new routes.routeManifest.test.js's guard assertion now covers/settings/**alongside/admin/**and/player/**.cd client && npm run build— clean. Built CSS confirmed to carry--radius-pill: 999px,--radius-panel: 12px,--radius-card: 10px,--radius-input: 8px, i.e. the promotion resolves to the same pixels.Checklist
AI-assisted contributions (required)
Claude Code (Opus 5). I have reviewed and understandevery change, and take responsibility for it. AI-authored commits are
marked with a
Co-Authored-By/Assisted-Bytrailer.License
(GNU GPL v3.0 or later), and I have the right to contribute it.