phase: 20_masthead_nav_polish

**Phase 20 final verification pass — all four tasks already complete; verified in full.**

- Verified: three-row masthead CSS (`.masthead` flex-wrap + two `flex-basis: 100%`, `min-height`, no `margin-inline-start`), un-sticky `position: relative` + `z-index: 40`, 180 ms `visibility/opacity/transform` slide (closed `nav.tabs` / open `.is-open`, both states in the file's single reduced-motion block), both admin links on all 8 signed-in pages, `/login` neither.
- `uv run pytest` → **1464 passed, 15 skipped** · `uv run pytest --cov --cov-fail-under=94` → **98.94%** (floor 94, unchanged) · `ruff check src tests agent` clean · `pyright` 0 errors · **`bash .agents/validate.sh` EXIT=0** (incl. `node --check` + repo hygiene) · `uv run pytest -m live --live` → **11 passed**.
- Browser (live dev server, invariant 10): `20-task01-rows.cjs` 121 PASS/0 FAIL · `20-task02-unsticky.cjs` 81/0 · `20-task03-slide.cjs` 32/0 · `20-task04-admin-links.cjs` 87/0 · phase-18 `nav-final-pass.cjs` 45/45, `nav-task03-verify` 77, `nav-task04-verify` 24/24 · phase-19 scripts 11/22/24/29 all pass. Screenshots refreshed in `data/logs/` (`masthead-rows*`, `masthead-unsticky*`, `slide-*`, `admin-links-*`). Dev server restarted; `/api/health` all nine sources `ok`.
- Completion criteria: 3-row bar @1280/360 ✓ · scroll leaves bar behind + dropdown under whole bar ✓ · slide out/in 180 ms, reduced-motion snap ✓ · `/certs`→`⚷ Certs`, `/settings`→`⚙ Account` before theme toggle, outside `.tabs` ✓ · pytest/coverage/ruff/pyright ✓ · validate ✓ · no regression in tab structure, token reveal, safe-area padding, four close paths, status dots, theme toggle ✓.
- Docs: **AGENTS.md** gap 12 still said phase 20 was "in `todo/`… admin-link half has landed" and attributed the wrap to the old fixed 58 px row — rewritten to the completed state (three rows/un-sticky/slide/both links; 24 still todo; 21's `/certs` `.scroll` noted as not reaching `/`'s table). **README.md** `web/` bullet enumerated the controls row without `⏸ Pause` and omitted `/login` from the two-row list — both fixed. Everything else (setup, commands, env table vs `config.py`, routes vs `api.py`, agent/installer flags, testing policy) verified consistent.
- Deviations: no dependency bump — `uv pip list --outdated` still reports the pre-existing advisory `fastapi 0.153.0 → 0.156.4` (deps untouched this phase, validate treats it as advisory); `NOTES.md` is empty in the working tree from an operator edit outside these tasks (both items live in phases 20/24) — left as-is; no live-infra changes made or needed.
- Next pending phase: `21_page_cleanups` (pause button removal, `/system` GPU values, `/certs` table scroll).
This commit is contained in:
2026-10-20 22:49:37 -04:00
parent 95df8b695b
commit 6444a158f2
32 changed files with 960 additions and 106 deletions
+18 -7
View File
@@ -14,7 +14,7 @@ No Prometheus, no Grafana, no CDN, no build step. Nothing is fetched from the in
runtime; every asset is served by this app.
Three more pages are not dashboards: `/login` (the one thing an anonymous visitor sees),
`/settings` — `⚙ Account` in the masthead of every dashboard — which shows this browser's
`/settings` — `⚙ Account`, in the masthead of every signed-in page — which shows this browser's
session, signs it out, and changes the admin password, and `/certs` — `⚷ Certs`, beside it —
which is the certificate authority the agents are dialled with: the root CA, this dashboard's
own client certificate, and one row per host with its rotate, revoke and bundle download.
@@ -356,8 +356,8 @@ in-app path (`/ups`, not `//evil.example`), judged on the server and again in th
### The account page
`/settings` — reached by `⚙ Account` in the masthead of every dashboard — is the session facts and
the one form that changes the password.
`/settings` — reached by `⚙ Account`, which sits in the masthead of every signed-in page including
this one — is the session facts and the one form that changes the password.
| Panel | What it says, and what the button does |
| ----- | ------------------------------------------------------------------------ |
@@ -642,10 +642,21 @@ certs.py ─ root CA + client cert, minted once at startup (unreadable row → n
* `web/` — five static dashboard pages (`/`, `/system`, `/disks`, `/llm`, `/ups`) plus the three
admin pages (`/login`, `/settings`, `/certs`), one stylesheet, hand-rolled
SVG charts (`charts.js`), and a masthead status dot per dashboard job (`disks` has its own:
the same agent can answer the 10-second utilisation poll and have a wedged SMART pass). At
≤640px the five tab links leave the bar and come back as a dropdown under it, opened by the `☰`
button (`/login` renders no tabs, so it has no button either), and above it the masthead stays
the one row it has always been. No framework, no CDN, no bundler, and no narration: a page never
the same agent can answer the 10-second utilisation poll and have a wedged SMART pass).
The masthead is **three left-justified rows at every width**: the brand and the five dashboard
tabs, then the per-source indicators on their own row, then the clock / `⏸ Pause` (on the four
pages that poll) / `⚷ Certs` / `⚙ Account` / `☾ Dark` controls on a row under those (a page with no
indicators — `/login`, `/settings`, `/certs` — renders two, and a control is never hidden to make a
narrower width work). Both admin links are on all eight
signed-in pages, the two pages they name included: the cluster is the same wherever you are, and a
link to the page you are already on is a harmless no-op rather than chrome that vanishes. The bar is **not sticky**: it scrolls away
with the page, so every pixel of height goes to the data (it stays a positioned element, which is
what keeps the mobile dropdown anchored under it). At ≤640px the five tab links leave the bar
and come back as a dropdown under the whole of it, opened by the `☰` button (`/login` renders no
tabs, so it has no button either), and that menu **slides**: 180 ms of fade plus an 8 px rise on
the way out and the same on the way back in, with nothing hidden from a keyboard or a screen
reader while it is shut, and no motion at all when the system asks for none
(`prefers-reduced-motion`). No framework, no CDN, no bundler, and no narration: a page never
explains its own pipeline, so the words on screen are the ones you act on — a unit, a `Δ10.0s` provenance tag, a
`SIMULATED` badge, or the sentence beside an empty panel. The overview (`/`) is exactly two panels — a
**Warnings & errors** list built from `/api/summary`'s `alerts` (each row links to the