106 lines
5.4 KiB
Markdown
106 lines
5.4 KiB
Markdown
# 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.
|