Admin: tool registry, grouped nav, and an /admin/ hub

This commit is contained in:
jessikitty committed 2026-10-12 13:18:59 +11:00
1 parent 7fc2b02e6a
commit 7040d4f078
9 files changed
+412 -7

No files matched your search

@@ -0,0 +1,105 @@
# Admin Navigation Tidy-up
**Date:** 2026-10-12
**Project:** newbury-exhibit-v3
**Status:** written and checked offline; not yet opened on the deployed server
**Tags:** admin, navigation, ux, auth
## Context
The admin section had grown to eleven pages behind a nav bar that was a flat,
hand-maintained array of nine lowercase filenames. Asked to make it easier to
navigate; reading it first turned up three things worse than untidiness.
## What was actually wrong
- **`testsheet.html` was unreachable.** It existed, it worked, it was in no
nav list. The only way in was typing the URL.
- **`calibrate.html` was listed in the nav but never rendered it** — it imports
nothing from `admin.js`, so the link led to a page with no way back.
- **`calibrate.html` never called `requireAuth()`.** Every other admin page
does. In `final` mode it was the one admin page openable without the
password. That is a hole, not a nav problem, and it was only visible because
the nav work meant reading every page's imports.
- **`/admin/` had no `index.html`**, so the bare path 404s and there is no
landing point.
- **Labels were filenames**: `landing`, `plan`, `print`, `preview` say nothing
about what the page does, and the four physical-setup tools sat
interleaved with the content editors.
## Key Decisions
- **One registry, two consumers.** `TOOLS` in `admin.js` carries id, display
name, group and a one-line blurb. `nav()` and the new hub both generate from
it. The old bug class — a page existing in the folder but not in the bar —
needs someone to forget the registry rather than forget one of two lists.
- **`tools/check-admin-nav.mjs` closes the loop the registry cannot.** The
registry guarantees the bar and the hub agree with each other; it cannot
notice a page nobody registered. The check walks `public/admin/*.html` and
fails on anything unregistered, plus asserts `nav()` marks exactly one link
active and still roots at `<header>`.
*Rejected:* a rule that a tool's label must differ from its filename. It
fired on Layout, Characters, Playlist and Calibrate, which are all correctly
named. The labels that were genuinely bad were bad because they did not
describe the page, which no mechanical rule can tell.
- **Four groups: World, Viewer, Setup, Diagnostics.** World is what is in the
exhibit (layout, characters, playlist), Viewer is what visitors get (landing
page, virtual viewport), Setup is the physical work (crests, plan, A3 rig,
calibrate), Diagnostics is the error log. The group label doubles as the
separator in the bar, so the grouping survives wrapping onto a second row on
a phone rather than becoming a run of orphaned pipes.
- **`nav()`'s root stays `<header>`.** `print.html` hides `header` on print and
`plan.html` hides its `#hdr` wrapper; changing the element would have
silently printed the nav bar onto the crest sheets.
- **Additive CSS only.** Existing selectors in `styles` are untouched except
for `flex-wrap` on `header`; ten pages inject `styles` plus their own rules
and several style `h1` indirectly through it. New chrome is namespaced under
`.anav`, `.hub`, `.card`, `.chip`.
- **Two pages cannot host the bar.** `calibrate.html` is a full-bleed camera
view and `testsheet.html` must print edge to edge. Both carry
`chrome: false`. Calibrate gets a floating pill (`backLink()`, styled from
`BACK_CSS`, exported separately so the page need not pull in the body and
background rules that would wreck it). Testsheet gets an inline link in its
existing toolbar — a floating pill would have landed on top of the Print A3
button.
- **Hub status chips never block.** Each endpoint is fetched in parallel and a
failure drops its chip rather than the page. The hub's job is to get you
somewhere else quickly; it must not sit spinning because the error log is
unhappy.
## Technical Details
`public/admin/admin.js` — added `GROUPS`, `TOOLS`, `toolById`, `href`,
`BACK_CSS`, `backLink()`; rewrote `nav()`; extended `styles` additively.
`api()` and `requireAuth()` unchanged.
`public/admin/index.html` — new. Mode banner (dev vs final), then grouped
cards. Chips: crest and spawn counts from `/api/scene`, tracks/residents/
windows from `/api/playlist`, built models from `/api/characters`, roster size
from `/api/ghosts`, error count from `/api/client-errors`.
`public/admin/calibrate.html` — added `requireAuth()` and `backLink()`.
`public/admin/testsheet.html` — added an inline `← Admin` link to `.bar`.
## Open Questions / Next Steps
- **Nothing here has been opened against a running server.** The checks are
static: registry/folder agreement, `nav()` output, HTML tag balance, module
syntax. Worth confirming the hub's chips match reality on first load, since
the exact shape of `/api/characters` and `/api/ghosts` (array vs wrapper
object) is handled defensively rather than known.
- `calibrate.html` gaining `requireAuth()` means it now redirects to the login
screen in final mode. Expected, but it is a behaviour change on a page that
used to open straight to the camera.
- The hub is a plain page; a tablet propped next to the table would want the
virtual viewport, not this. Possibly worth a "kiosk" entry later.
- Still outstanding from 2026-10-07: on-device validation of the sub-pixel and
ROI work, re-tuning the 1€ filter and `MAX_REPROJ_PX` if `reproj` has
dropped, and carrying a raised crest into the real town layout.