5.4 KiB
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.htmlwas unreachable. It existed, it worked, it was in no nav list. The only way in was typing the URL.calibrate.htmlwas listed in the nav but never rendered it — it imports nothing fromadmin.js, so the link led to a page with no way back.calibrate.htmlnever calledrequireAuth(). Every other admin page does. Infinalmode 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 noindex.html, so the bare path 404s and there is no landing point.- Labels were filenames:
landing,plan,print,previewsay nothing about what the page does, and the four physical-setup tools sat interleaved with the content editors.
Key Decisions
-
One registry, two consumers.
TOOLSinadmin.jscarries 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.mjscloses 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 walkspublic/admin/*.htmland fails on anything unregistered, plus assertsnav()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.htmlhidesheaderon print andplan.htmlhides its#hdrwrapper; changing the element would have silently printed the nav bar onto the crest sheets. -
Additive CSS only. Existing selectors in
stylesare untouched except forflex-wraponheader; ten pages injectstylesplus their own rules and several styleh1indirectly through it. New chrome is namespaced under.anav,.hub,.card,.chip. -
Two pages cannot host the bar.
calibrate.htmlis a full-bleed camera view andtestsheet.htmlmust print edge to edge. Both carrychrome: false. Calibrate gets a floating pill (backLink(), styled fromBACK_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/charactersand/api/ghosts(array vs wrapper object) is handled defensively rather than known. calibrate.htmlgainingrequireAuth()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_PXifreprojhas dropped, and carrying a raised crest into the real town layout.