Records Phase 5 of THEMING_AND_NAV.md as landed and documents the new route
and the shell lifecycle in BACKEND_DESIGN.md.
Where the build differed from the design: the upload is one admin-only call
that writes the settings row too (rather than the generic staff upload plus a
PUT, which would leave unreferenced files and let editors change the site's
identity); brand_assets needed a validator of its own because these are the
only settings values written straight into HTML as URLs; the shell cache
carries a TTL as well as explicit invalidation because it is per process; and
the logo went into all six MoonDot surfaces rather than three.
Also notes what was deliberately left alone: the shell's title and description
still come from BRAND_NAME rather than the admin-set site_title, and fixing
that would change the served shell for instances with no brand_assets row —
which is exactly what the phase's acceptance criterion forbids.
Co-Authored-By: Claude <noreply@anthropic.com>
Records where the build diverged from the design and why.
- The presets do not live in theme.css as [data-theme] blocks. Section 6.2 is
marked superseded and a "Phases 3-4 as landed" section explains the inline
--accent precedence problem that forced server-side resolution.
- Section 4.5's accentInt fix is struck through: getPublic() never exposed
accentInt, and Discord embeds are colored by a separate process reading env,
so there was nothing per-request to recompute. Replaced with what was
actually done -- the bot fetching the effective accent.
- Phase 9 re-scoped. Applying Fantasy on a live instance showed the section 4.8
rgba literals carry a hue, not just a light/dark assumption, so the dark
presets need that promotion too.
- BACKEND_DESIGN.md: the new /settings/theme/options route, the `theme` block
on /public/settings, effective values in the brand block, theme_visual
validation on PUT /admin/settings, route count 225 -> 226.
- api-route-inventory.json regenerated from the manifest.
Co-Authored-By: Claude <noreply@anthropic.com>
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>
Corrects the design doc against the current codebase and locks the open
decisions, in the same shape as HERO_EDITOR.md (locked decisions ->
corrections to reality -> phased build).
Blocking gaps found in the design doc:
- no delete path exists for a settings row, which every "reset to
defaults" in the feature depends on
- editors/moderators/players have no endpoint to read their own nav
overrides (GET /admin/settings is admin-only)
- renderIndexHtml runs once at boot, not per request
- settings values are JSON strings, not objects
- getPublic().brand is a cross-repo contract the Android app and
Discord embeds theme from; new keys would silently bypass it
Locked: effective values resolved server-side into getPublic().brand;
radius + shadow tokens only (spacing/border cut); radius tokens seeded at
today's real values so the promotion is a no-op; three dark presets in v1
with Parchment deferred; 12-option font shortlist across 8 web families
in one request; PNG-only favicons.
Co-Authored-By: Claude <noreply@anthropic.com>