Files
newbury-exhibit-v3/.claude-context/sessions/2026-10-12-admin-navigation.md
T

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.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.