docs(website): theming & nav phases 0-2 as built
Matches RunicGateway/website's phases 0-2 of THEMING_AND_NAV.md. BACKEND_DESIGN.md: - The new /settings router group and its one route, plus why it is a fifth group rather than a route on an existing one. - DELETE /admin/settings/:key in the admin route table, with the allowlist and why reset deletes instead of writing. - The five unseeded theming/nav keys under the settings schema: absence of the row is the "use the default" state, values are TEXT so consumers parse, and malformed reads as absent. - Route count 215 -> 225. THEMING_AND_NAV.md: - Phases 0-2 marked landed, with an "as landed" section recording the three things the design left open: where /settings/nav lives, where parseJsonSetting lives, and the exact 23-declaration radius promotion. - The nav merge util's ordering rules, settled by the implementation: an untouched item keeps its index as its sort key, an explicit order wins a tie against a coincidental index, equal explicit orders keep code order, and `group` is honored only when it names an existing section. - All four PR pairs target `edge`; the feature reaches `main` as one merge. api-route-inventory.json: resynced from server/routes.manifest.json. Picks up the two new routes plus eight that were already missing from the mirror since the Protocol 3.0 cutover (shard clilocs, market, points). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -479,6 +479,31 @@ function applyNavOverrides(baseNav, overrides) {
|
||||
`overrides` absent → return `baseNav` unchanged. This is the "respect defaults"
|
||||
path and is the single most important case to test.
|
||||
|
||||
**As built (Phase 1).** Two shapes are handled by the one function — flat
|
||||
(`SiteHeader`, `PlayerPortalLayout`) and grouped (`AdminLayout`) — detected by
|
||||
whether every entry carries an `items` array. Three rules the doc left open,
|
||||
settled by the implementation and locked by tests:
|
||||
|
||||
- **Ordering.** An item the admin never reordered keeps its index in the base
|
||||
array as its sort key, so setting one `order` does not scramble the rest.
|
||||
Explicit and implicit keys therefore share one number line and can collide;
|
||||
ties break **explicit first** (an admin who said "0" means first, not
|
||||
"wherever the untouched item at index 0 already sits"), and two explicit
|
||||
equal orders keep code order via a stable sort. The editor writes an order for
|
||||
every item in a list the way drag-and-drop does, so ties are the stale-row
|
||||
case, not the normal one — they just have to resolve predictably.
|
||||
- **`group`.** Accepted only when it names a title the base nav already
|
||||
declares; anything else is dropped, so an item can never land under a header
|
||||
that does not exist. Group *order* is not overridable — sections stay in code
|
||||
order, only membership and within-group order move.
|
||||
- **Field-by-field validation.** A bad `label` does not discard a good `order`
|
||||
beside it, and `hidden` is honored only as the literal boolean `true`.
|
||||
Everything unrecognized is ignored rather than rejected, so a hand-edited row
|
||||
degrades to the code default instead of rendering a broken nav.
|
||||
|
||||
`hidden: false` cannot un-hide anything: hiding here is subtractive only, and
|
||||
the role/feature filters still run afterward, unchanged.
|
||||
|
||||
## 8. Build phases
|
||||
|
||||
Each phase is independently shippable and leaves the site rendering identically to
|
||||
@@ -486,9 +511,9 @@ today until the admin acts.
|
||||
|
||||
| Phase | Work |
|
||||
|---|---|
|
||||
| **0 — Settings-store groundwork** | `settingsDb.remove()`; `DELETE /admin/settings/:key` with key allowlist; `GET /settings/nav` (§4.2); `parseJsonSetting()` helper; register the five keys; three into `PUBLIC_KEYS`. Swagger + route-manifest regen |
|
||||
| **1 — `navOverrides.js` + tests** | The pure merge util, unit-tested in isolation. **The one piece with real correctness risk** |
|
||||
| **2 — Radius/shadow token groundwork** | Promote the literals in `theme.css` to the four tokens of §4.7, values unchanged. Verify zero visual diff before any admin UI exists |
|
||||
| **0 — Settings-store groundwork** ✅ | `settingsDb.remove()`; `DELETE /admin/settings/:key` with key allowlist; `GET /settings/nav` (§4.2); `parseJsonSetting()` helper; register the five keys; three into `PUBLIC_KEYS`. Swagger + route-manifest regen |
|
||||
| **1 — `navOverrides.js` + tests** ✅ | The pure merge util, unit-tested in isolation. **The one piece with real correctness risk** |
|
||||
| **2 — Radius/shadow token groundwork** ✅ | Promote the literals in `theme.css` to the four tokens of §4.7, values unchanged. Verify zero visual diff before any admin UI exists |
|
||||
| **3 — Theme engine** | Three preset blocks, the combined Google Fonts link, `SiteContext` extension, and the effective-value resolution in `getPublic().brand` (§4.5) |
|
||||
| **4 — Admin theme UI** | `/admin/appearance` view + route in `App.jsx` + `NAV`/`TITLES` entries in `AdminLayout.jsx` |
|
||||
| **5 — Brand assets** | Cached-shell rewrite in `app.js` (§4.3); upload endpoint on the existing multer config; `<img>` logo slot beside `MoonDot` in the three shells; `heroImage` chain extension |
|
||||
@@ -498,7 +523,36 @@ today until the admin acts.
|
||||
| **9 — Parchment (optional)** | Light-mode port per §4.8 — its own contrast pass across every component |
|
||||
|
||||
Phases 0–2 are one PR pair (website + docs), 3–4 a second, 5 a third, 6–8 a
|
||||
fourth.
|
||||
fourth. **All four PR pairs target `edge`, not `main`** — the feature reaches
|
||||
`main` as one `edge` → `main` merge once every phase is in, so no release ever
|
||||
carries a half-wired theme engine.
|
||||
|
||||
### Phases 0–2 as landed
|
||||
|
||||
- **`/api/v1/settings` is a fifth router group**, not a route bolted onto an
|
||||
existing one. §4.2 named the URL but not where it lives, and the domain split
|
||||
leaves no group it fits: `/public` is anonymous, `/admin/settings` is
|
||||
`adminOnly` while `AdminLayout` renders for editors and moderators, and
|
||||
`/player` is data scoped to `req.user.id`. The group carries
|
||||
`noindex, requireAuth` and no role gate. The route-manifest guard test that
|
||||
asserts every `/admin/**` and `/player/**` route sits behind `requireAuth` now
|
||||
covers `/settings/**` too.
|
||||
- **Reset is `DELETE /api/v1/admin/settings/:key`** with the allowlist in
|
||||
`settings.model.js` (`DELETABLE_KEYS`), which is what stops a stray request
|
||||
from dropping `site_mode` or the uo-link config. It is admin-only and
|
||||
idempotent, and a test asserts it never writes a row.
|
||||
- **`parseJsonSetting` lives at `server/src/utils/settingsJson.js`.** Non-object
|
||||
JSON (`4`, `"x"`, `null`, `[]`) is treated as absent alongside syntax errors,
|
||||
and a validator rejection discards the whole object rather than half-applying
|
||||
it. The client keeps `parseLayout`; a client-side counterpart arrives with its
|
||||
first consumer in Phase 3.
|
||||
- **Phase 2 was a 23-declaration promotion** — 14×`8px` → `--radius-input`,
|
||||
4×`999px` → `--radius-pill`, 4×`10px` → `--radius-card`, 1×`12px` →
|
||||
`--radius-panel` — matching the §4.7 census exactly. The `7px`/`6px` editor
|
||||
chrome and the two `50%` circles stay literal. `--shadow-card` and
|
||||
`--panel-grad` were **already** tokens and already derived, so the shadow half
|
||||
of the phase was a no-op; the only two `box-shadow` declarations in
|
||||
`theme.css` both already read `var(--shadow-card)`.
|
||||
|
||||
### 8.1 Admin builder UI notes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user