From 2bf236fd92a52fbfbb45273d2548bba850653447 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Wed, 21 Oct 2026 16:42:14 -0400 Subject: [PATCH] phase: 25_masthead_control_placement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Phase 25 final verification pass** (all 3 tasks were already in `complete/`, so this was the phase-level verify + docs pass) **Verified** - `#nav-status` first child of `.masthead-right` on all 7 signed-in templates (incl. `/certs`, `/settings`); `app.js`/`charts.js` untouched - `#nav-status` + `.masthead-right` both `justify-content: flex-end`, `margin-inline-start` still absent, row-break (`flex-wrap` + two `flex-basis: 100%`), non-sticky bar, dropdown slide + 4 close paths, `setInterval(refreshStatus, 15000)` all intact (dash.css diff = comments + the two alignment values) - Clock is the last control on all 7 templates; no `order:` anywhere in the bar **Fixed (defects found in this pass)** - `tests/test_api.py::test_every_page_links_to_both_admin_pages` — duplicated `tabs = html[...]` statement (task 03 edit artifact) - `certs.html` / `settings.html` comments said "all **eight** signed-in pages"; the signed-in set is seven (`/login` is the eighth, anonymous, and has no links/clock) - `AGENTS.md` — suite tally `1542 → 1543 passed`; "the three phase-20 tests" re-worded, since task 02 renamed one to `test_the_masthead_is_three_rows_right_justified_at_every_width` **Results** - `uv run pytest` → **1543 passed, 15 skipped**, 0 failed - `bash .agents/validate.sh` → **exit 0**: coverage **98.94 %** (floor 94 %), `ruff` All checks passed, `pyright` 0 errors, `node --check` clean, hygiene clean - `uv run pytest -m live --live` (`.env.dev` sourced) → **11 passed**; `./scripts/dev-server.sh status` → 9/9 sources `ok` - Browser: `node data/logs/25-task03-verify.cjs` → **235 checks, 0 FAIL**, exit 0 (`data/logs/25-task03-verify.out`, 16 `masthead-25-*.png`) **Completion criteria**: badges pinned+measured (green `system/disks/llm/ups` on both admin pages, 1280 px and 360 px) ✅ · both rows flush right, three rows + dropdown unchanged (pinned + measured) ✅ · clock farthest-right (pinned markup; measured right edge 1260.0 at 1280 px, ≤900 px hidden by the long-standing readout rule with source order asserted) ✅ · pytest/coverage/ruff/pyright ✅ · validate.sh ✅ · no behaviour change in completed work ✅ **Docs**: README reviewed end-to-end against the code (served-route audit vs documented routes, env table vs `config.py`, commands vs `scripts/`, public-path table, `web/` masthead bullet) — consistent, no changes needed beyond the phase's own edits. AGENTS.md — two fixes above; spot-checked every doc-named test, function, script, threshold, `install-agent.sh` sha256 (`ffbcd91f…` matches) and agent version (1.1.0). **Deviations**: left the pre-existing `fastapi 0.153.0 → 0.159.0` advisory (validate prints it as advisory; no dependency work in scope, AGENTS rule 8 triggers on touching deps). No live-infra changes. Overview's "all eight signed-in pages" for the clock is verified against seven templates + `/login` (which has no clock, pinned). **Next pending phase**: `.agents/phases/todo/26_ups_event_split/` (3 tasks). --- .../25_masthead_control_placement/00_phase.md | 0 .../01_status_dots_on_admin_pages.md | 0 .../02_right_justify_masthead_rows.md | 0 .../03_clock_rightmost.md | 0 ...asthead_control_placement__00_phase.a1.err | 0 ...masthead_control_placement__00_phase.a1.md | 25 ++++ ...d_control_placement__00_phase.a1.md.status | 1 + ...ad_control_placement__00_phase.a1.validate | 66 +++++++++++ ...ment__01_status_dots_on_admin_pages.a1.err | 0 ...ement__01_status_dots_on_admin_pages.a1.md | 25 ++++ ...01_status_dots_on_admin_pages.a1.md.status | 1 + ..._01_status_dots_on_admin_pages.a1.validate | 66 +++++++++++ ...ent__02_right_justify_masthead_rows.a1.err | 0 ...ment__02_right_justify_masthead_rows.a1.md | 15 +++ ...2_right_justify_masthead_rows.a1.md.status | 1 + ...02_right_justify_masthead_rows.a1.validate | 66 +++++++++++ ...ntrol_placement__03_clock_rightmost.a1.err | 0 ...ontrol_placement__03_clock_rightmost.a1.md | 16 +++ ...placement__03_clock_rightmost.a1.md.status | 1 + ..._placement__03_clock_rightmost.a1.validate | 66 +++++++++++ AGENTS.md | 82 ++++++++++--- README.md | 17 ++- src/dashboards/web/pages/certs.html | 5 +- src/dashboards/web/pages/disks.html | 2 +- src/dashboards/web/pages/index.html | 2 +- src/dashboards/web/pages/llm.html | 2 +- src/dashboards/web/pages/settings.html | 7 +- src/dashboards/web/pages/system.html | 2 +- src/dashboards/web/pages/ups.html | 2 +- src/dashboards/web/static/dash.css | 53 +++++---- tests/test_api.py | 111 +++++++++++++++--- 31 files changed, 559 insertions(+), 75 deletions(-) rename .agents/phases/{todo => complete}/25_masthead_control_placement/00_phase.md (100%) rename .agents/phases/{todo => complete}/25_masthead_control_placement/01_status_dots_on_admin_pages.md (100%) rename .agents/phases/{todo => complete}/25_masthead_control_placement/02_right_justify_masthead_rows.md (100%) rename .agents/phases/{todo => complete}/25_masthead_control_placement/03_clock_rightmost.md (100%) create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.err create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md.status create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.validate create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.err create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md.status create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.validate create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.err create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md.status create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.validate create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.err create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md.status create mode 100644 .agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.validate diff --git a/.agents/phases/todo/25_masthead_control_placement/00_phase.md b/.agents/phases/complete/25_masthead_control_placement/00_phase.md similarity index 100% rename from .agents/phases/todo/25_masthead_control_placement/00_phase.md rename to .agents/phases/complete/25_masthead_control_placement/00_phase.md diff --git a/.agents/phases/todo/25_masthead_control_placement/01_status_dots_on_admin_pages.md b/.agents/phases/complete/25_masthead_control_placement/01_status_dots_on_admin_pages.md similarity index 100% rename from .agents/phases/todo/25_masthead_control_placement/01_status_dots_on_admin_pages.md rename to .agents/phases/complete/25_masthead_control_placement/01_status_dots_on_admin_pages.md diff --git a/.agents/phases/todo/25_masthead_control_placement/02_right_justify_masthead_rows.md b/.agents/phases/complete/25_masthead_control_placement/02_right_justify_masthead_rows.md similarity index 100% rename from .agents/phases/todo/25_masthead_control_placement/02_right_justify_masthead_rows.md rename to .agents/phases/complete/25_masthead_control_placement/02_right_justify_masthead_rows.md diff --git a/.agents/phases/todo/25_masthead_control_placement/03_clock_rightmost.md b/.agents/phases/complete/25_masthead_control_placement/03_clock_rightmost.md similarity index 100% rename from .agents/phases/todo/25_masthead_control_placement/03_clock_rightmost.md rename to .agents/phases/complete/25_masthead_control_placement/03_clock_rightmost.md diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.err b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md new file mode 100644 index 0000000..a9e7a12 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md @@ -0,0 +1,25 @@ +**Phase 25 final verification pass** (all 3 tasks were already in `complete/`, so this was the phase-level verify + docs pass) + +**Verified** +- `#nav-status` first child of `.masthead-right` on all 7 signed-in templates (incl. `/certs`, `/settings`); `app.js`/`charts.js` untouched +- `#nav-status` + `.masthead-right` both `justify-content: flex-end`, `margin-inline-start` still absent, row-break (`flex-wrap` + two `flex-basis: 100%`), non-sticky bar, dropdown slide + 4 close paths, `setInterval(refreshStatus, 15000)` all intact (dash.css diff = comments + the two alignment values) +- Clock is the last control on all 7 templates; no `order:` anywhere in the bar + +**Fixed (defects found in this pass)** +- `tests/test_api.py::test_every_page_links_to_both_admin_pages` — duplicated `tabs = html[...]` statement (task 03 edit artifact) +- `certs.html` / `settings.html` comments said "all **eight** signed-in pages"; the signed-in set is seven (`/login` is the eighth, anonymous, and has no links/clock) +- `AGENTS.md` — suite tally `1542 → 1543 passed`; "the three phase-20 tests" re-worded, since task 02 renamed one to `test_the_masthead_is_three_rows_right_justified_at_every_width` + +**Results** +- `uv run pytest` → **1543 passed, 15 skipped**, 0 failed +- `bash .agents/validate.sh` → **exit 0**: coverage **98.94 %** (floor 94 %), `ruff` All checks passed, `pyright` 0 errors, `node --check` clean, hygiene clean +- `uv run pytest -m live --live` (`.env.dev` sourced) → **11 passed**; `./scripts/dev-server.sh status` → 9/9 sources `ok` +- Browser: `node data/logs/25-task03-verify.cjs` → **235 checks, 0 FAIL**, exit 0 (`data/logs/25-task03-verify.out`, 16 `masthead-25-*.png`) + +**Completion criteria**: badges pinned+measured (green `system/disks/llm/ups` on both admin pages, 1280 px and 360 px) ✅ · both rows flush right, three rows + dropdown unchanged (pinned + measured) ✅ · clock farthest-right (pinned markup; measured right edge 1260.0 at 1280 px, ≤900 px hidden by the long-standing readout rule with source order asserted) ✅ · pytest/coverage/ruff/pyright ✅ · validate.sh ✅ · no behaviour change in completed work ✅ + +**Docs**: README reviewed end-to-end against the code (served-route audit vs documented routes, env table vs `config.py`, commands vs `scripts/`, public-path table, `web/` masthead bullet) — consistent, no changes needed beyond the phase's own edits. AGENTS.md — two fixes above; spot-checked every doc-named test, function, script, threshold, `install-agent.sh` sha256 (`ffbcd91f…` matches) and agent version (1.1.0). + +**Deviations**: left the pre-existing `fastapi 0.153.0 → 0.159.0` advisory (validate prints it as advisory; no dependency work in scope, AGENTS rule 8 triggers on touching deps). No live-infra changes. Overview's "all eight signed-in pages" for the clock is verified against seven templates + `/login` (which has no clock, pinned). + +**Next pending phase**: `.agents/phases/todo/26_ups_event_split/` (3 tasks). diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md.status b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.validate b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.validate new file mode 100644 index 0000000..523270a --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__00_phase.a1.validate @@ -0,0 +1,66 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 13%] +........................................................................ [ 18%] +........................................................................ [ 23%] +..........................................................s............. [ 27%] +........................................................................ [ 32%] +........................................................................ [ 36%] +........................................................................ [ 41%] +........................................................................ [ 46%] +........................................................................ [ 50%] +........................................................................ [ 55%] +........................................................................ [ 60%] +........................................................................ [ 64%] +........................................................................ [ 69%] +.sssssssssss............................................................ [ 73%] +........................................................................ [ 78%] +........................................................................ [ 83%] +........................................................................ [ 87%] +........................................................................ [ 92%] +............................................................sss......... [ 97%] +.............................................. [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.14.4-final-0 ________________ + +Name Stmts Miss Cover Missing +------------------------------------------------------------------ +src/dashboards/__init__.py 3 0 100% +src/dashboards/__main__.py 14 1 93% 32 +src/dashboards/alerts.py 176 0 100% +src/dashboards/api.py 440 0 100% +src/dashboards/app.py 186 0 100% +src/dashboards/auth.py 265 0 100% +src/dashboards/certs.py 292 0 100% +src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307 +src/dashboards/config.py 120 0 100% +src/dashboards/debug.py 14 0 100% +src/dashboards/poller.py 165 3 98% 192-194, 279 +src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183 +src/dashboards/rates.py 73 0 100% +src/dashboards/severity.py 49 0 100% +src/dashboards/sources/__init__.py 0 0 100% +src/dashboards/sources/disks.py 134 0 100% +src/dashboards/sources/host.py 209 0 100% +src/dashboards/sources/llm.py 178 1 99% 285 +src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736 +src/dashboards/sources/nut_sim.py 134 0 100% +src/dashboards/store.py 313 2 99% 553-554 +------------------------------------------------------------------ +TOTAL 3495 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1543 passed, 15 skipped, 1 warning in 163.67s (0:02:43) +== uv run ruff check src tests agent == +All checks passed! +== uv run pyright == +0 errors, 0 warnings, 0 informations +== node --check src/dashboards/web/static/*.js == +advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose): + fastapi 0.153.0 0.159.0 wheel diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.err b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md new file mode 100644 index 0000000..a521363 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md @@ -0,0 +1,25 @@ +All green. Report: + +**Implemented (phase 25 task 01)** +- `src/dashboards/web/pages/certs.html` + `settings.html`: added `` as the first child of `.masthead-right` (same position as the five dashboards); deleted settings.html's "No `#nav-status`…" comment whose premise the operator overruled. +- `app.js`: **unchanged** — `initChrome()`'s `if (nav)` block builds the four badges from `/api/health` whenever the container exists. +- `tests/test_api.py`: flipped the two no-dots pins (`/settings`, `/certs`) to dots-present + re-worded docstrings/messages; added `test_every_signed_in_page_carries_the_fleet_badges` (one `MARKUP_PAGES` loop pinning the exact element as first child of `.masthead-right`, plus the `app.js` container guard, the four badge labels and the untouched 15 s refresh). +- Browser proof: `data/logs/25-task01-badges.cjs` (headless, `20-task01-rows.cjs` convention) → **ALL PASS (90 checks)**, transcript `data/logs/25-task01-badges.out`, screenshots `data/logs/25-task01-{overview,settings,certs}-badges.png` — badges populate `system=ok disks=ok llm=ok ups=ok` on `/certs` and `/settings`, identical to `/`, at 1280 px and 360 px; `/login` (unsigned) still has none. +- Dev server restarted; `./scripts/dev-server.sh status` = 9 sources `ok`. + +**Tests / lint / coverage** +- `uv run pytest` → `1543 passed, 15 skipped` (was 1542+15; +1 new test), exit 0 +- `uv run pytest --cov` → `TOTAL 3495 37 99%`, "Required test coverage of 94% reached. Total coverage: 98.94%" (no Python source lines added) +- `uv run ruff check src tests agent` → All checks passed · `uv run pyright` → 0 errors +- `bash .agents/validate.sh` → exit 0 (pytest+cov, ruff, pyright, `node --check`); only advisory: `fastapi 0.153.0 → 0.158.1` outdated — dependencies untouched by this task, so not bumped + +**Docs** +- `AGENTS.md`: `/settings` "no status dots" → the four badges (with the TODO/`initChrome` reason); `/certs` "no `#nav-status`" → badges like every masthead; the three-rows paragraph now says every masthead page renders three rows and `/login` remains the only two-row page. +- `README.md`: status dots are on **every** signed-in page; the "a page with no indicators — `/login`, `/settings`, `/certs`" clause narrowed to `/login` only. Left-alignment wording deliberately left for task 02. + +**Notes / deviations** +- Kept task 01 strictly placement-of-container: no CSS, no `app.js`, no order change (tasks 02/03 own right-justification and clock-rightmost). New pin asserts "first child", which survives task 03's clock move. +- Pre-existing working-tree change I did not make and left alone: `NOTES.md` is emptied (the two items consumed by phases 20/24). +- The interactive-browser hook fired but no visible session was needed — this phase's mandate is the repo's own headless `data/logs/*.cjs` verification, which ran. + +**Next pending task:** `25_masthead_control_placement/02_right_justify_masthead_rows.md` diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md.status b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.validate b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.validate new file mode 100644 index 0000000..684f4a5 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__01_status_dots_on_admin_pages.a1.validate @@ -0,0 +1,66 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 13%] +........................................................................ [ 18%] +........................................................................ [ 23%] +..........................................................s............. [ 27%] +........................................................................ [ 32%] +........................................................................ [ 36%] +........................................................................ [ 41%] +........................................................................ [ 46%] +........................................................................ [ 50%] +........................................................................ [ 55%] +........................................................................ [ 60%] +........................................................................ [ 64%] +........................................................................ [ 69%] +.sssssssssss............................................................ [ 73%] +........................................................................ [ 78%] +........................................................................ [ 83%] +........................................................................ [ 87%] +........................................................................ [ 92%] +............................................................sss......... [ 97%] +.............................................. [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.14.4-final-0 ________________ + +Name Stmts Miss Cover Missing +------------------------------------------------------------------ +src/dashboards/__init__.py 3 0 100% +src/dashboards/__main__.py 14 1 93% 32 +src/dashboards/alerts.py 176 0 100% +src/dashboards/api.py 440 0 100% +src/dashboards/app.py 186 0 100% +src/dashboards/auth.py 265 0 100% +src/dashboards/certs.py 292 0 100% +src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307 +src/dashboards/config.py 120 0 100% +src/dashboards/debug.py 14 0 100% +src/dashboards/poller.py 165 3 98% 192-194, 279 +src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183 +src/dashboards/rates.py 73 0 100% +src/dashboards/severity.py 49 0 100% +src/dashboards/sources/__init__.py 0 0 100% +src/dashboards/sources/disks.py 134 0 100% +src/dashboards/sources/host.py 209 0 100% +src/dashboards/sources/llm.py 178 1 99% 285 +src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736 +src/dashboards/sources/nut_sim.py 134 0 100% +src/dashboards/store.py 313 2 99% 553-554 +------------------------------------------------------------------ +TOTAL 3495 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1543 passed, 15 skipped, 1 warning in 161.44s (0:02:41) +== uv run ruff check src tests agent == +All checks passed! +== uv run pyright == +0 errors, 0 warnings, 0 informations +== node --check src/dashboards/web/static/*.js == +advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose): + fastapi 0.153.0 0.158.1 wheel diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.err b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md new file mode 100644 index 0000000..6493504 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md @@ -0,0 +1,15 @@ +**Implemented (phase 25 task 02)** +- `src/dashboards/web/static/dash.css`: `#nav-status` and `.masthead-right` `justify-content: flex-start` → `flex-end`; `margin-inline-start` still absent from the cluster; row-break (`flex-wrap` + both `flex-basis: 100%`), `min-height`, gaps and the whole ≤640px block (incl. `#nav-toggle { margin-inline-start: auto }`) untouched. +- Re-worded every comment that stated the old alignment: the header block, `Row 3:`, `Row 2:`, the mobile block intro, and the mobile "row alignment is the base rule's …" note. +- `tests/test_api.py`: renamed the pin to `test_the_masthead_is_three_rows_right_justified_at_every_width`, flipped only the two alignment assertions to `flex-end`, re-worded docstring/messages; all row-break/height/gap/no-`margin-inline-start` guards kept. The mobile-height and unsticky pins are unchanged and green. +- Measured half: `data/logs/25-task02-justify.cjs` (7 signed-in pages × 1280/360 px + unsigned `/login`) — both rows compute `flex-end` and sit flush with the bar's right padding edge (0.00 px gap), 4 badges per row, still three rows, dropdown still under the whole bar, no sideways scroll: **ALL CHECKS PASSED**; screenshots `data/logs/25-task02-{index,certs,settings}-{1280,360}.png`, `25-task02-login-1280.png`, transcript `25-task02-justify.out`. + +**Tests / lint / coverage** +- `bash .agents/validate.sh` → EXIT=0: `1543 passed, 15 skipped`, **coverage 98.94 %** (floor 94 %), `ruff check src tests agent` All checks passed, `pyright` 0 errors, `node --check web/static/*.js` clean. +- `uv run pytest -m live --live` (with `.env.dev`) → **11 passed**; `./scripts/dev-server.sh status` → 9/9 sources `ok`, served `/static/dash.css` carries the two `flex-end` declarations. + +**Docs**: `AGENTS.md` masthead paragraph now reads "three rows … the two control rows flush right", gained a phase-25-task-02 paragraph naming the renamed pin + the browser script, and gap 12's "three left-justified rows" wording fixed; `README.md`'s masthead sentence likewise. Clock-order sentences left for task 03. + +**Notes/deviations**: no scope change (clock move not started). Pre-existing, untouched: `validate.sh` advisory `fastapi 0.153.0 → 0.158.1` (no dependency work in this task) and `NOTES.md` already emptied by earlier tasks in the working tree. + +**Next pending task**: `.agents/phases/todo/25_masthead_control_placement/03_clock_rightmost.md`. diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md.status b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.validate b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.validate new file mode 100644 index 0000000..bd01086 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__02_right_justify_masthead_rows.a1.validate @@ -0,0 +1,66 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 13%] +........................................................................ [ 18%] +........................................................................ [ 23%] +..........................................................s............. [ 27%] +........................................................................ [ 32%] +........................................................................ [ 36%] +........................................................................ [ 41%] +........................................................................ [ 46%] +........................................................................ [ 50%] +........................................................................ [ 55%] +........................................................................ [ 60%] +........................................................................ [ 64%] +........................................................................ [ 69%] +.sssssssssss............................................................ [ 73%] +........................................................................ [ 78%] +........................................................................ [ 83%] +........................................................................ [ 87%] +........................................................................ [ 92%] +............................................................sss......... [ 97%] +.............................................. [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.14.4-final-0 ________________ + +Name Stmts Miss Cover Missing +------------------------------------------------------------------ +src/dashboards/__init__.py 3 0 100% +src/dashboards/__main__.py 14 1 93% 32 +src/dashboards/alerts.py 176 0 100% +src/dashboards/api.py 440 0 100% +src/dashboards/app.py 186 0 100% +src/dashboards/auth.py 265 0 100% +src/dashboards/certs.py 292 0 100% +src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307 +src/dashboards/config.py 120 0 100% +src/dashboards/debug.py 14 0 100% +src/dashboards/poller.py 165 3 98% 192-194, 279 +src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183 +src/dashboards/rates.py 73 0 100% +src/dashboards/severity.py 49 0 100% +src/dashboards/sources/__init__.py 0 0 100% +src/dashboards/sources/disks.py 134 0 100% +src/dashboards/sources/host.py 209 0 100% +src/dashboards/sources/llm.py 178 1 99% 285 +src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736 +src/dashboards/sources/nut_sim.py 134 0 100% +src/dashboards/store.py 313 2 99% 553-554 +------------------------------------------------------------------ +TOTAL 3495 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1543 passed, 15 skipped, 1 warning in 162.55s (0:02:42) +== uv run ruff check src tests agent == +All checks passed! +== uv run pyright == +0 errors, 0 warnings, 0 informations +== node --check src/dashboards/web/static/*.js == +advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose): + fastapi 0.153.0 0.158.1 wheel diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.err b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md new file mode 100644 index 0000000..cd45e13 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md @@ -0,0 +1,16 @@ +**Implemented (phase 25 task 03)** +- All 7 templates (`index/system/disks/llm/ups/settings/certs.html`): `` moved from first control to **last** child of `.masthead-right`, so the row reads `⚷ Certs` → `⚙ Account` → `☾ Dark` → clock; the seven control-row blocks are byte-identical (md5-verified), `#nav-status` stays first child. +- `dash.css`: header block + `Row 3:` comment re-worded to the new order and to say the clock's place is markup, not a CSS `order:`. No declaration changed. +- `tests/test_api.py::test_every_page_links_to_both_admin_pages`: docstring order sentence updated, both `before_theme` assertions kept, added `html.index('id="clock"') > html.index('id="theme-toggle"')` per page, plus a guard that `.masthead`/`.masthead-right`/`#nav-status`/`.clock` carry **no** `order:` declaration (the task's ASSUMPTION, made enforceable). `/login` half untouched. +- `data/logs/25-task03-verify.cjs` + `.out` + 16 screenshots `data/logs/masthead-25-*.png`: 7 pages × 1280/360 px + `/login` unsigned at both — three rows, both right-hand rows flush with the bar's right padding edge (measured on the chips' content, not the full-basis row box, and explicitly *not* flush left), clock the rightmost painted thing on the bar, cluster source order, four populated badges on `/certs`/`/settings`, 360 px dropdown still under the whole bar, no sideways scroll. + +**Tests / lint / coverage** +- `bash .agents/validate.sh` → **1543 passed, 15 skipped**, coverage **98.94 %** (floor 94 %), `ruff check src tests agent` All checks passed, `pyright` 0 errors, `node --check web/static/*.js` clean, hygiene clean (only the pre-existing advisory `fastapi 0.153.0 → 0.158.2`; no dependency work in this task). +- `uv run pytest -m live --live` (`.env.dev` sourced) → **11 passed** (first attempt had one transient `deskwork … /snapshot timed out`, clean on re-run); `./scripts/dev-server.sh status` → 9/9 sources `ok`. +- Browser pass → **ALL CHECKS PASSED** (235 checks, 0 failures). + +**Docs**: `AGENTS.md` — control-order sentence in the `/certs` paragraph, the row-3 description, a new phase-25-task-03 paragraph (markup-vs-`order:` rationale, the pin, the ≤900 px clock note, the script/screenshots); `README.md` — the `web/` masthead bullet now reads `⚷ Certs` / `⚙ Account` / `☾ Dark` / clock with "the time is the farthest-right thing on the bar". + +**Decisions/deviations**: the long-standing `@media (max-width: 900px){.clock{display:none}}` stays (task said no declaration changes; it's a readout, not a control), so the clock's pixel claim is measured at 1280 px and the 360 px leg asserts the clock is hidden *by that rule* and the source order still ends with it — if the operator wants the time on the phone too, that hide is the one line to drop. No live-infra changes; `TODO.md`/`NOTES.md` working-tree edits are pre-existing, not mine. + +**Next pending task**: `.agents/phases/todo/26_ups_event_split/01_page_side_event_split.md` (this task file is the last of phase 25). diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md.status b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.validate b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.validate new file mode 100644 index 0000000..8025c53 --- /dev/null +++ b/.agents/reports/25_masthead_control_placement/25_masthead_control_placement__03_clock_rightmost.a1.validate @@ -0,0 +1,66 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 13%] +........................................................................ [ 18%] +........................................................................ [ 23%] +..........................................................s............. [ 27%] +........................................................................ [ 32%] +........................................................................ [ 36%] +........................................................................ [ 41%] +........................................................................ [ 46%] +........................................................................ [ 50%] +........................................................................ [ 55%] +........................................................................ [ 60%] +........................................................................ [ 64%] +........................................................................ [ 69%] +.sssssssssss............................................................ [ 73%] +........................................................................ [ 78%] +........................................................................ [ 83%] +........................................................................ [ 87%] +........................................................................ [ 92%] +............................................................sss......... [ 97%] +.............................................. [100%] +=============================== warnings summary =============================== +.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/dashboards/.venv/lib64/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.14.4-final-0 ________________ + +Name Stmts Miss Cover Missing +------------------------------------------------------------------ +src/dashboards/__init__.py 3 0 100% +src/dashboards/__main__.py 14 1 93% 32 +src/dashboards/alerts.py 176 0 100% +src/dashboards/api.py 440 0 100% +src/dashboards/app.py 186 0 100% +src/dashboards/auth.py 265 0 100% +src/dashboards/certs.py 292 0 100% +src/dashboards/collectors.py 205 5 98% 284-285, 289-290, 307 +src/dashboards/config.py 120 0 100% +src/dashboards/debug.py 14 0 100% +src/dashboards/poller.py 165 3 98% 192-194, 279 +src/dashboards/prom.py 112 5 96% 126, 163, 177, 180, 183 +src/dashboards/rates.py 73 0 100% +src/dashboards/severity.py 49 0 100% +src/dashboards/sources/__init__.py 0 0 100% +src/dashboards/sources/disks.py 134 0 100% +src/dashboards/sources/host.py 209 0 100% +src/dashboards/sources/llm.py 178 1 99% 285 +src/dashboards/sources/nut.py 413 20 95% 153, 172, 186, 198, 204, 217, 225, 241, 319-320, 322, 579, 591, 626, 657, 661, 718-719, 724, 736 +src/dashboards/sources/nut_sim.py 134 0 100% +src/dashboards/store.py 313 2 99% 553-554 +------------------------------------------------------------------ +TOTAL 3495 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1543 passed, 15 skipped, 1 warning in 163.74s (0:02:43) +== uv run ruff check src tests agent == +All checks passed! +== uv run pyright == +0 errors, 0 warnings, 0 informations +== node --check src/dashboards/web/static/*.js == +advisory: dependencies are out of date (AGENTS.md rule 8 — update on purpose): + fastapi 0.153.0 0.159.0 wheel diff --git a/AGENTS.md b/AGENTS.md index 1c4b471..6efbd4e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -750,7 +750,10 @@ Responsibilities, one line each: unsigned because a signed-in `GET /login` is a `303` and would audit `/`'s markup under the wrong name. `/settings` (`pages/settings.html` + `static/page-settings.js`) is a **page and not a sixth dashboard**: same `` and masthead, the five tabs with **no** `aria-current` (it is - not one of them), no status dots, and it is reached from the `⚙ Account` link in + not one of them), the same four `#nav-status` badges as every dashboard (phase 25 task 01 — + `TODO.md:3`'s "hosts disks engines UPS disappear when in the certs/account page" was the + missing container, and `Dash.initChrome()` fills it from `/api/health` with no JS change), and + it is reached from the `⚙ Account` link in `.masthead-right` of every dashboard **including itself** — outside `.tabs`, so the pinned tab structure is untouched (`tests/test_api.py::test_every_page_links_to_both_admin_pages`). It prints `/api/auth/status` (name, expiry as a local timestamp *and* as time left), signs out through @@ -766,15 +769,17 @@ Responsibilities, one line each: `csrf` is now invalid — every later write, the UPS buttons included, 403s until a reload) and on any `401` bounce. `/certs` (`pages/certs.html` + `static/page-certs.js`) is the second of those admin pages, and it - is the same shape for the same reasons: five tabs with **no** `aria-current`, no `#nav-status`, - and both masthead links — `⚷ Certs` beside `⚙ Account` — exactly as the five dashboards render - them. **Both links are on both admin pages** (phase 20 task 04, `TODO.md:4`), which retires phase - 15's "no self-link" precedent: chrome that changes shape depending on where you already are reads + is the same shape for the same reasons: five tabs with **no** `aria-current`, the four fleet + badges in `#nav-status` like every other masthead (phase 25 task 01, same reasoning as + `/settings` above), and both masthead links — `⚷ Certs` beside `⚙ Account` — exactly as the five + dashboards render them. **Both links are on both admin pages** (phase 20 task 04, `TODO.md:4`), + which retires phase 15's "no self-link" precedent: chrome that changes shape depending on where you already are reads as a broken button, while a link to the page you are on is a no-op that answers with the page you were looking at (`data/logs/20-task04-admin-links.cjs` measures exactly that — one document response, no redirect, the same cluster afterwards). The two links therefore sit on all seven signed-in pages (the five dashboards plus `/settings` and `/certs`) in one order - (clock → `⚷ Certs` → `⚙ Account` → theme toggle), which is what lets + (`⚷ Certs` → `⚙ Account` → theme toggle → clock — phase 25 task 03 moved the clock to the end so + it is the farthest-right item on a `flex-end` row), which is what lets the pinned audit be one loop over `MARKUP_PAGES` instead of two over different tuples; `/login` is the eighth page and the one with neither. Everything on it is `GET /api/certs`: the Root CA block, the display-only Dashboard client line (that certificate is @@ -814,17 +819,21 @@ Responsibilities, one line each: `severity` verdict the API sent (`tests/test_api.py` fails if a page script names it again). A value the API should rate gets a row in `severity.py`, never a bound in JS. - **The masthead is three left-justified rows at every width** (phase 20 task 01, `TODO.md:5-6`): - row 1 the brand, `#nav-toggle` and the five tabs; row 2 the `#nav-status` per-source dots; row 3 - the `.masthead-right` controls (clock, `⚷ Certs`, `⚙ Account`, theme — phase 21 task 01 deleted - the `⏸ Pause` that used to sit between the clock and the two links, button, `Space` shortcut and + **The masthead is three rows at every width, the two control rows flush right** (phase 20 task + 01 made the rows, `TODO.md:5-6`; phase 25 task 02 re-justified them, `TODO.md:4` — "I made a + mistake earlier, I did want hosts disks engines UPS badges and certs/account/dark right + justified"): row 1 the brand, `#nav-toggle` and the five tabs; row 2 the `#nav-status` + per-source dots; row 3 + the `.masthead-right` controls (`⚷ Certs`, `⚙ Account`, theme, then the clock — phase 25 task 03 + moved the clock last for `TODO.md:5`'s "the time should show up as the farthest right item"; + phase 21 task 01 deleted the `⏸ Pause` that used to sit beside the links, button, `Space` shortcut and `setPaused`/`_paused` plumbing alike: it froze the numbers on screen without growing a `stale` label, and a backgrounded tab already idles its polls, which is the only stop this app needs). - That is CSS and + That was CSS and no markup change, which decides the mechanism: `#nav-status` is a **child** of `.masthead-right`, so there is no wrapper to promote, and the rows are line breaks — `.masthead` is `flex-wrap: wrap`, and both `#nav-status` and `.masthead-right` carry `flex-basis: 100%` with - `justify-content: flex-start` (line breaking uses a flex item's **base** size, before shrink, so + `justify-content: flex-end` (line breaking uses a flex item's **base** size, before shrink, so a 100 % item is alone on its line: the div's break starts row 2 of the bar and the dots' starts row 2 inside it, leaving the controls to wrap to row 3). `.masthead-right` lost the `margin-inline-start: auto` that pinned the cluster to the right edge of the one-row bar. @@ -832,12 +841,46 @@ Responsibilities, one line each: brand and the tabs on separate rows. `height: var(--header-h)` became **`min-height`** (three rows do not fit in 58 px and a fixed height clips the controls) and is declared **once** — the ≤640px block no longer restates `height`/`min-height`/`flex-wrap`, and that pair drifting is exactly what - would clip the bar on a phone only. A page with no dots (`/certs`, `/settings`) renders two rows, - `/login` two (brand, then the theme toggle). Pinned on the served bytes by the three phase-20 - tests in `tests/test_api.py` (the three rules' declarations, the mobile block's silence about - the bar's height, and the bar's `position`), and measured in a browser at 1280 px and 360 px by + would clip the bar on a phone only. Every page that renders the bar now renders three rows — + `/certs` and `/settings` grew the dots row in phase 25 task 01, where `#nav-status` is pinned as + the **first child** of `.masthead-right` on all seven signed-in pages (`app.js` is untouched: + the container's existence is what makes `initChrome` build the four badges, which is why the fix + was markup and not JS) — and `/login` remains the one page with no dots, rendering two rows + (brand, then the theme toggle). Pinned on the served bytes by phase 20's three tests in + `tests/test_api.py` — the row declarations (task 02 renamed that one to + `test_the_masthead_is_three_rows_right_justified_at_every_width`), the mobile block's silence + about the bar's height, and the bar's `position` — and measured in a browser at 1280 px and 360 px by `data/logs/20-task01-rows.cjs` (seven pages × two widths plus the dropdown; screenshots - `data/logs/masthead-rows*.png`). + `data/logs/masthead-rows*.png`). **Phase 25 task 02 moved the alignment of those two rows to + the other edge** (`TODO.md:4` — "I made a mistake earlier, I did want hosts disks engines UPS + badges and certs/account/dark right justified"): the two `justify-content` declarations are + `flex-end`, `margin-inline-start` is still absent from the cluster (a full-basis row aligned by + `flex-end` is the mechanism, not the old single-row right-pinning), and the ≤640px block still + restates neither the alignment nor the bar's height, so the phone's wrapped control row follows + the base rule. The pin is `tests/test_api.py::test_the_masthead_is_three_rows_right_justified_at_every_width` + (the row-break assertions are the same ones phase 20 wrote, deliberately: the re-justify must + not cost the rows), and the measured half is `data/logs/25-task02-justify.cjs` — seven pages × + two widths, each demanding both rows compute `flex-end` and sit flush with the bar's right + padding edge, the four badges present, three rows intact, the dropdown still under the whole + bar and no sideways scroll, plus `/login`'s two-row bar with no badges + (`data/logs/25-task02-justify.out`, screenshots `data/logs/25-task02-*.png`). **Phase 25 task 03 + then put the clock at the end of the control row** (`TODO.md:5` — "The time should show up as the + farthest right item on the navbar"): the `` moved from the first + control to the last one on all seven templates, so the row reads `⚷ Certs` → `⚙ Account` → theme + toggle → clock. It is a **markup** move and deliberately not a CSS `order:` declaration, because + on a `flex-end` row the last child *is* the farthest-right item and an `order:` override would be + something the served-byte pin cannot see — `test_every_page_links_to_both_admin_pages` therefore + asserts `html.index('id="clock"') > html.index('id="theme-toggle"')` on every page (the two + Certs/Account-before-theme assertions stay, and the `/login` half of the test is untouched). + The clock keeps its long-standing `@media (max-width: 900px) { .clock { display: none } }` hide: + it is a readout and not a control, so this is not the "no control is hidden to make the width + work" rule the masthead keeps. Measured by `data/logs/25-task03-verify.cjs` — seven pages × two + widths, each demanding three rows, both right-hand rows flush with the bar's right padding edge + and the chips' content (not the full-basis row box) measured to prove it is not flush left, the + clock the rightmost painted thing on the bar at 1280 px, the cluster's source order unchanged at + 360 px where the clock is hidden, and the four badges populated on `/certs` and `/settings` as on + the dashboards; plus the 360 px dropdown and `/login` unsigned at both widths + (`data/logs/25-task03-verify.out`, screenshots `data/logs/masthead-25-*.png`). **The bar is not sticky** (phase 20 task 02, `TODO.md:10` — "It shouldn't be sticky at all. We want to maximize the amount of screen space for data viewing"): `position: sticky; top: 0` became **`position: relative`** with no offset, so the whole masthead scrolls away with the page and the @@ -1186,7 +1229,7 @@ uv run python scripts/dev-certs-export.py --url http://127.0.0.1:8090 \ # GET /api/certs//bundle and writes # data/certs/.bundle at 0600 — mints nothing -uv run pytest # hermetic: 1542 passed + 15 skipped (11 live, +uv run pytest # hermetic: 1543 passed + 15 skipped (11 live, # 4 store_perf), no network uv run pytest --cov # coverage gate (98.94%; floor 94%) uv run pytest -m live --live # opt-in: real fleet / engines / upsd @@ -2362,7 +2405,8 @@ memory plot; the `cpu.usage_pct@coreN` series is still stored and still queryabl shape applied to one table and does not reach this one; `27_mobile_tables_and_audit` is that pass, still in `todo/`. Of the two operator TODO items that sat beside it, `20_masthead_nav_polish` has landed in full (now complete): the bar is three - left-justified rows at every width, it is no longer sticky, the phone menu slides, and both + rows at every width (flush right on the two control rows since phase 25 task 02), it is no + longer sticky, the phone menu slides, and both `⚙ Account` and `⚷ Certs` render on all seven signed-in pages including the two they name (task 04, reversing phase 15's "no self-link" rule at the operator's request). The other, `24_dev_mtls_rollout` (never accepting a non-mTLS agent) is closed: its fleet half landed on diff --git a/README.md b/README.md index c92a8c3..455d603 100644 --- a/README.md +++ b/README.md @@ -751,13 +751,18 @@ certs.py ─ root CA + client cert, minted once at startup (unreadable row → n that row count, so `/` and `/disks` can never describe two different fleets. * `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: + SVG charts (`charts.js`), and a masthead status dot per dashboard job on **every** signed-in + page — the two admin pages included (`disks` has its own: 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 / `⚷ 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). There is no control that stops the polling (phase 21): the bar used to carry + The masthead is **three rows at every width, the two control rows flush right**: the brand and the five dashboard + tabs, then the per-source indicators on their own row, then the `⚷ Certs` / `⚙ Account` / + `☾ Dark` / clock controls on a row under those, in that order, so the **time is the farthest-right + thing on the bar** (phase 25: the badges and the cluster were re-justified right, then the clock + moved to the end of the markup — `flex-end` makes the last child the rightmost item, and it stays + a markup fact rather than a CSS `order:` so the pin can read it; the clock is hidden below 900 px + as it has always been, being a readout and not a control). Only `/login` renders no + indicators, and so two rows, and a control is never hidden to make a + narrower width work. There is no control that stops the polling (phase 21): the bar used to carry a `⏸ Pause` button with a `Space` shortcut, and it left the numbers on screen looking current with no `stale` label on them. Both admin links are on all seven signed-in pages — the five dashboards plus `/settings` and `/certs`, the two they name included — diff --git a/src/dashboards/web/pages/certs.html b/src/dashboards/web/pages/certs.html index c1ac06b..de8a22a 100644 --- a/src/dashboards/web/pages/certs.html +++ b/src/dashboards/web/pages/certs.html @@ -14,7 +14,7 @@ that marks a tab this page is not would be a lie about the navigation. The masthead carries both admin links — `⚷ Certs` and `⚙ Account` — outside `.tabs`, so the tab structure stays five links deep, and it carries them **here too** (`TODO.md:4`): the - controls are the same on all eight signed-in pages, and a link to the page you are on is + controls are the same on all seven signed-in pages, and a link to the page you are on is a no-op the operator can press without being surprised, which beats chrome that disappears when it is most being used. --> + --:--:-- diff --git a/src/dashboards/web/pages/disks.html b/src/dashboards/web/pages/disks.html index 3ea8eb9..02171e7 100644 --- a/src/dashboards/web/pages/disks.html +++ b/src/dashboards/web/pages/disks.html @@ -21,10 +21,10 @@
- --:--:-- + --:--:--
diff --git a/src/dashboards/web/pages/index.html b/src/dashboards/web/pages/index.html index 58ed3da..99109d0 100644 --- a/src/dashboards/web/pages/index.html +++ b/src/dashboards/web/pages/index.html @@ -21,10 +21,10 @@
- --:--:-- + --:--:--
diff --git a/src/dashboards/web/pages/llm.html b/src/dashboards/web/pages/llm.html index f13464a..e0cfb65 100644 --- a/src/dashboards/web/pages/llm.html +++ b/src/dashboards/web/pages/llm.html @@ -21,10 +21,10 @@
- --:--:-- + --:--:--
diff --git a/src/dashboards/web/pages/settings.html b/src/dashboards/web/pages/settings.html index 52dfa02..3fea10c 100644 --- a/src/dashboards/web/pages/settings.html +++ b/src/dashboards/web/pages/settings.html @@ -13,7 +13,7 @@ @@ -25,12 +25,11 @@ UPS
- - --:--:-- + + --:--:--
diff --git a/src/dashboards/web/pages/system.html b/src/dashboards/web/pages/system.html index ed47d7d..f74e92f 100644 --- a/src/dashboards/web/pages/system.html +++ b/src/dashboards/web/pages/system.html @@ -21,10 +21,10 @@
- --:--:-- + --:--:--
diff --git a/src/dashboards/web/pages/ups.html b/src/dashboards/web/pages/ups.html index 8aab7b7..39782f5 100644 --- a/src/dashboards/web/pages/ups.html +++ b/src/dashboards/web/pages/ups.html @@ -21,10 +21,10 @@
- --:--:-- + --:--:--
diff --git a/src/dashboards/web/static/dash.css b/src/dashboards/web/static/dash.css index acefec9..c9c7ca8 100644 --- a/src/dashboards/web/static/dash.css +++ b/src/dashboards/web/static/dash.css @@ -96,11 +96,16 @@ a:hover { text-decoration: underline; } :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; } /* ---- header ------------------------------------------------------------- */ -/* The masthead is **three left-justified rows at every width** (phase 20 task 01, `TODO.md:5-6` - * — "the hosts disks engines and UPS indicators … left justified in their own row", "the - * ⚷ Certs ⚙ Account ☾ Dark buttons … in their own row under" them): row 1 the brand, +/* The masthead is **three rows at every width, the two control rows flush right** (phase 20 + * task 01 made the rows, `TODO.md:5-6` — "the hosts disks engines and UPS indicators … in + * their own row", "the ⚷ Certs ⚙ Account ☾ Dark buttons … in their own row under" them; phase + * 25 task 02 re-justified them right, `TODO.md:4` — "I made a mistake earlier, I did want + * hosts disks engines UPS badges and certs/account/dark right justified"): row 1 the brand, * `#nav-toggle` and the five tabs; row 2 the `#nav-status` reachability dots; row 3 the - * `.masthead-right` controls (clock, `⚷ Certs`, `⚙ Account`, theme). + * `.masthead-right` controls — `⚷ Certs`, `⚙ Account`, the theme toggle, then the clock, in + * that markup order because on a `flex-end` row the **last** child is the farthest-right item + * (phase 25 task 03, `TODO.md:5` — "The time should show up as the farthest right item on the + * navbar"; nothing is reordered in CSS, so the pin can read the markup). * * No markup changed to get them, and that decides the mechanism. `#nav-status` is the first * child of `.masthead-right`, so there is no wrapper to promote: in a `flex-wrap: wrap` @@ -169,31 +174,34 @@ nav.tabs a { nav.tabs a:hover { background: var(--bg-sunken); color: var(--text); text-decoration: none; } nav.tabs a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); } -/* Row 3: the controls, left-justified under the indicators. `margin-inline-start: auto` is - * what pinned this cluster to the right edge of the old single-row bar; in a row of its own it - * pushes against nothing, and `justify-content: flex-start` is the TODO's word for where the - * buttons go. `flex-basis: 100%` is the row break (see `.masthead` above) and `flex-wrap` is - * what a phone gets when clock + two links + theme will not share a line — phase 18's - * rule, and no control is ever hidden to make the arithmetic work. */ +/* Row 3: the controls, flush right under the indicators. `margin-inline-start: auto` is what + * pinned this cluster to the right edge of the old single-row bar and stays **absent**: on a + * row of its own it pushes against nothing, so the alignment is `justify-content: flex-end` + * (phase 25 task 02 — the operator overruled phase 20's `flex-start`; the badges and the + * cluster are right-justified). `flex-basis: 100%` is the row break (see `.masthead` above) + * and `flex-wrap` is what a phone gets when `⚷ Certs` + `⚙ Account` + theme will not share a line + * (the clock is the last child and hides below 900px on its own — a readout, not a control, so + * that is not the "no control is hidden to fit the width" rule) — phase 18's rule, and nothing in + * this block hides a button to make the arithmetic work. */ .masthead-right { flex-basis: 100%; display: flex; flex-wrap: wrap; align-items: center; - justify-content: flex-start; + justify-content: flex-end; gap: .35rem .55rem; } -/* Row 2: the per-source indicators, left-justified on a line of their own, so a readout is - * never mistaken for one more button beside it. `.gap` already makes this a flex row; the - * `flex-basis` is the break inside `.masthead-right`, `justify-content` the alignment, and the - * gap is restated at .55rem so the dots read as a row of their own rather than as the tail of - * the cluster above. */ +/* Row 2: the per-source indicators, flush right on a line of their own, so a readout lines up + * with the controls under it and is never mistaken for one more button beside them. `.gap` + * already makes this a flex row; the `flex-basis` is the break inside `.masthead-right`, + * `justify-content` the alignment, and the gap is restated at .55rem so the dots read as a row + * of their own rather than as the tail of the cluster below. */ #nav-status { flex-basis: 100%; display: flex; align-items: center; - justify-content: flex-start; + justify-content: flex-end; gap: .55rem; } @@ -247,9 +255,10 @@ button.ghost:disabled { opacity: .45; cursor: not-allowed; } * and since task 03, it owns the slide too: `nav.tabs` is the closed menu and `.is-open` its * visible counterpart, with a 180 ms `opacity`/`transform` transition on both. * - * Above 640px nothing here applies, which is the point: the same three left-justified rows — - * brand + toggle, indicators, controls — with the tabs inline on the first one and the button - * hidden. What a phone changes is only that the tabs become a menu and the gaps tighten. + * Above 640px nothing here applies, which is the point: the same three rows — brand + toggle, + * indicators, controls, the last two flush right — with the tabs inline on the first one and + * the button hidden. What a phone changes is only that the tabs become a menu and the gaps + * tighten. */ @media (max-width: 640px) { /* The bar's vertical rhythm, and nothing structural. The three rows come from the base @@ -284,8 +293,8 @@ button.ghost:disabled { opacity: .45; cursor: not-allowed; } * still wraps *within itself*: `⚷ Certs`, `⚙ Account` and the theme button do not fit in * 320px once over on the narrowest phones, and nothing here hides a control to make * the arithmetic work — a phone gets the same words every other width does. The row - * alignment is the base rule's `flex-start`, restated nowhere: phase 20 replaced the - * right-justified cluster, so this block only tightens the gap. */ + * alignment is the base rule's `flex-end` (phase 25 task 02), restated nowhere: this block + * only tightens the gap, so the two rows cannot disagree about which edge they sit on. */ .masthead-right { flex-wrap: wrap; gap: .4rem; } .masthead-right > * { flex: none; white-space: nowrap; } #nav-status { flex-wrap: nowrap; } diff --git a/tests/test_api.py b/tests/test_api.py index 1b972da..819afd9 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1046,7 +1046,10 @@ def test_the_settings_page_is_not_one_of_the_dashboards(client) -> None: assert 'aria-current="page"' not in html for path in PAGES: assert f'href="{path}"' in html, f"/settings cannot navigate to {path}" - assert 'id="nav-status"' not in html, "/settings grew fleet dots it has nothing to fill" + # The fleet badges belong here too (`TODO.md:3`): the container is what made them appear on + # the five dashboards, and a page that renders the masthead without it is the page where they + # "disappear". `Dash.initChrome()` fills it from `/api/health` exactly as it fills the others. + assert 'id="nav-status"' in html, "/settings lost the fleet badges the operator asked for" def test_the_settings_page_refuses_a_caller_with_no_session(tmp_path: Path) -> None: @@ -1075,10 +1078,9 @@ def test_the_settings_page_refuses_a_caller_with_no_session(tmp_path: Path) -> N def test_every_page_links_to_both_admin_pages(client, tmp_path: Path) -> None: """`⚙ Account` and `⚷ Certs` are the only way to the two admin pages, so they are everywhere. - One loop over all seven signed-in pages (`MARKUP_PAGES`), because the controls are now the *same* - on all of - them: clock → `⚷ Certs` → `⚙ Account` → theme toggle, in `.masthead-right` and **outside** - `.tabs` (the tab strip's structure — five dashboards, one of them current — is pinned by + One loop over all seven signed-in pages (`MARKUP_PAGES`), because the control row is the *same* + on all of them: `⚷ Certs` → `⚙ Account` → theme toggle → clock, in `.masthead-right` and + **outside** `.tabs` (the tab strip's structure — five dashboards, one of them current — is pinned by `test_the_disks_tab_is_in_the_masthead_of_every_page`, and a sixth entry with no `aria-current` would break what that marker means). @@ -1087,6 +1089,15 @@ def test_every_page_links_to_both_admin_pages(client, tmp_path: Path) -> None: button to disappear … These should remain visible"): masthead chrome that changes shape depending on where you already are reads as a broken control, while a link to the page you are on is a no-op that answers with the page you were looking at. + + The clock last is phase 25 task 03 (`TODO.md:5` — "The time should show up as the farthest + right item on the navbar"). It is pinned as **markup**, not as a CSS `order` property, because + that is what makes the claim true: task 02 left the control row `justify-content: flex-end`, so + the last child of `.masthead-right` *is* the farthest-right item, and an `order:` override the + pin could not see would leave this test green over a bar that says something else. (Below 900 px + the clock is hidden by the long-standing `.clock` media rule — a readout, not a control, so + hiding it is not the "no control is hidden to fit the width" rule this app keeps — and the + markup order this test reads is width-independent.) """ account_open = '' certs_link = f'{certs_open} title="Agent certificates">⚷ Certs' theme = '