a99ead4ee4df3c0e1754c6e5300a84799c807f84
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| a45a3d120a |
feat(modules): interleave module nav, derive moderator confinement
Phase 2, PR 8 of docs/website/MODULE_SYSTEM.md 2.7 - the nav half PR 7 deferred, plus the two seams 1.4 and 1.5 asked for. withModuleNav (client/src/modules/nav.js) merges an installed module's rows into core's three navs BEFORE the admin-override merge, and that ordering is the design. applyNavOverrides and buildPublicNav are keyed by `to` and drop any key their base array does not declare, so rows appended after the merge would be unorderable, unrelabellable and unhideable in Admin - Navigation. Today's UO rows are all three of those things, so appending would make the extraction a visible regression for anyone who has ever edited their nav. Merging first means a module row is an ordinary row downstream: nothing in navOverrides.js, NavEditor.jsx or the layouts knows a module exists. MOD_PATHS is gone. Moderator visibility and the redirect that confines a moderator both derive from each row's own `roles`, in the new plain-JS lib/adminNav.js (plain so the DOM-less runner can reach it). Two rows move, both toward what the server already permitted: Dashboard, whose roles had always named moderator, and My Characters, which is ungated self-service. That also fixes a defect predating the module system. The redirect was a THIRD hardcoded list - three path prefixes against MOD_PATHS' five paths - and they disagreed about /admin/houses, so a moderator who clicked Houses in their own sidebar was bounced back to Moderation. The derived allow-list is computed from the BASE nav, never the override-merged one: an override is presentation and must not move an authorization boundary either way. The feature seam (modules/features.jsx + modules/featureGate.js) resolves a row's `feature` against the provider its OWN module registered, so the namespace comes from the registration and no string carries a parsed prefix. Core registers useShardFlags under the owner id `core` - the client twin of registries.registerCore() - so the ten shard-gated header rows already run through the seam and Phase 3 deletes a registration instead of rewriting SiteHeader. Every unknown fails open: no provider, a null answer while a fetch is in flight, or a junk return all show the link, because the server is the gate and hiding a page from someone entitled to it is the worse mistake. 933 server tests (unchanged - this PR is client-only), 160 client tests (+37). routes.manifest.json unchanged at 230 routes; the OpenAPI spec regenerates byte-identical. Re-ran the MODULE_API.md 7.7 browser smoke, since this is the seam that rule exists for. A throwaway module registering nav in all three areas and a provider granting one flag and withholding another: the row lands inside core's Moderation group rather than an appended block, the withheld row does not render, a moderator reaches both /admin/houses and the module's admin page, and an admin can relabel a module row and have it persist and apply. Zero CSP reports, zero console errors. Co-Authored-By: Claude <noreply@anthropic.com> |
|||
| b517d7b2df |
feat(theming): dropdown sections and added links in the public header
Phase 10 of docs/website/THEMING_AND_NAV.md, asked for before the edge -> main
cutover. An admin can now create dropdown sections in the public header, organise
the coded entries into them, and add links of their own.
This deliberately amends §7, which said the override layer "cannot introduce a
`to` that is not already in the hardcoded NAV array". That stays true of every
CODED entry; an admin may now also add a link, restricted to a same-origin path —
no scheme, no protocol-relative //host. A link carries no gate of its own and
needs none: the page behind it enforces its own access, so an added link
advertises a route and never grants one.
The invariant is kept structurally rather than by vigilance. Coded entries live
in an `items` map whose keys must be routes the base array declares, so that map
cannot invent a route; everything that CAN name an arbitrary path lives in
`links`, which is the one place the path rule is applied — on both the write and
the read path.
nav_public therefore grew a { items, sections, links } wrapper. A bare map still
reads as the items map, and a nav with no sections still stores one, so this
changed nothing for a nav that does not use it. Free to do now because nothing
has shipped; after the cutover it would have needed a migration.
The Public tab gets its own editor. A public section is an entry in the
top-level order that the admin created and can drag among the pills, unlike the
admin sidebar's four coded sections, where only membership moves — that is a tree
rather than a list of groups. Deleting a section returns its entries to the top
level rather than removing them, which is the one destructive act this screen
could otherwise commit.
The dropdown opens on click and never on hover, and its trigger is not a link: a
hover menu is unusable on touch, and a trigger that navigates means tapping to
open takes you somewhere instead. Escape closes and returns focus, an outside
press closes, navigating closes, and Arrow Up/Down walk the items.
pruneNav applies the shard-feature gate inside a section and drops one it leaves
empty, so a dropdown never opens onto nothing.
Also fixes a bug this surfaced in the phase 6-8 code: the save path judged "does
this route still exist?" against the palette — the base array already filtered to
what the editing admin can see — so on the public header a feature-gated row's
override could never be carried through and would have been silently reset.
Membership is now judged against the full coded nav while the rows still come
from the palette.
Co-Authored-By: Claude <noreply@anthropic.com>
|
|||
| 32a3ff104a |
feat(theming): wire the three navs and add the admin nav builder
Phases 6-8 of docs/website/THEMING_AND_NAV.md. The public header, the admin sidebar and the player portal now read their override row, and /admin/navigation writes them: rename, reorder by drag, hide, and — on the admin sidebar — move a row into another existing section. The merge always runs BEFORE the role and shard-feature filters in the layouts, which are unchanged and remain the boundary. An override is presentation: it cannot introduce a route, cannot touch a `roles` or `feature` gate, and a stored `hidden: false` on a gated item shows nobody anything. The design scoped these phases as client work, but the server had no way to store a nav row: updateSettings validates and stringifies theme_visual and brand_assets and lets everything else through, so a nav object would have been written as "[object Object]" and read as absent for ever. utils/navOverrides.js mirrors utils/brandAssets.js — strict on write with the offending key named, forgiving on read. It validates shape only; whether a `to` exists is settled client-side at merge time, because the base NAV arrays are client constants and a server-side copy would be a second source of truth that drifts. The nav editor cannot be hidden — its own toggle is disabled, the write path drops `hidden` on that one `to`, and AdminLayout strips it again before merging, which also covers a row edited straight in the database. Orders are written only when the sequence actually differs from the code's, and the comparison is restricted to the rows the editing admin can see, so renaming one item does not pin the position of every other one and a role- or feature-gated item missing from their palette is not mistaken for a reorder. Co-Authored-By: Claude <noreply@anthropic.com> |