phase: 21_page_cleanups
**Phase 21 final verification pass** (all 3 task files already in `complete/`, working tree carries the implementation)
- **Verified** task 01: `#pause`/`Space`/`setPaused`/`_paused` gone — `grep -ri pause src/dashboards/web/` empty, 0 pause hits in all 8 served pages + 10 static assets; re-ran `data/logs/21-task01-nopause.cjs` → 30/30 PASS (hidden tab: 0 data fetches in 22 s, one poll 2 ms after return).
- **Verified** task 02: re-ran `21-task02-gpu-values.cjs` on the live fleet → all PASS (deskwork/driveripper show Model/VRAM/Pool/Temp at 16.3 px; gateway = 5 heroes, slot `hidden`, no `n/a`; hero order unchanged; GPU history chart still builds per host).
- **Verified** task 03: re-ran `21-task03-scroll.cjs` → 14/14 PASS (`class="data scroll"`, scrollWidth 1021 vs clientWidth 292 at 360 px, Rotate/Revoke/bundle reachable and hit-testable; unchanged at 1280 px). Screenshots refreshed in `data/logs/`.
- **Gate**: `uv run pytest` → **1471 passed, 15 skipped**; `--cov --cov-fail-under=94` → **98.94%**; `ruff check src tests agent` clean; `pyright` **0 errors**; `bash .agents/validate.sh` → **EXIT 0** (incl. `node --check`); `uv run pytest -m live --live` → **11 passed**; `/api/health` all 9 sources `ok`.
- **Docs fixed** (pre-existing + this phase): "all **eight** signed-in pages" was a miscount of `MARKUP_PAGES` (7 = 5 dashboards + `/settings` + `/certs`; `/login` is the 8th, unsigned) → corrected in `README.md`, `AGENTS.md` (×2) and the `tests/test_api.py` docstring/comment; `AGENTS.md` Commands hermetic tally 1470 → **1471**.
- **Docs reviewed, consistent**: Quickstart/commands/flags (`install-agent.sh`, `capture-fixtures.sh`, `dev-server.sh`), the env table vs `config.py`, every documented route vs the served route table, masthead/`/system` GPU/`/certs` scroll descriptions vs code, and all `data/logs/*` + `tests/fixtures/*` references.
- **Deviation**: `validate.sh`'s advisory `fastapi 0.153.0 → 0.156.4` left alone — no dependency was touched this phase, so AGENTS rule 8 ("check after touching dependencies") is not triggered; upgrading here would be an unrelated behaviour change.
- No live-infra changes made (repo-side + browser verification only).
- **Next pending phase**: `22_disks_hide_unconfigured`.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
# Phase 21 — Page cleanups: pause, GPU values, certs scroll
|
||||
|
||||
**Source:** `TODO.md` L7, L9, L12 — "remove the pause button, GPU data as its own values, /certs table horizontal scroll"
|
||||
**Story:** n/a
|
||||
**Context:** `src/dashboards/web/pages/{disks,llm,system,ups}.html` line 25 (the `#pause` button, the only one in the served markup — `/`, `/certs` and `/settings` have none), `src/dashboards/web/static/app.js` (lines 587–615 the `poll()` `_paused` plumbing, lines 773–783 the `#pause` binding plus the `Space` shortcut), `src/dashboards/web/static/dash.css` line 203 (the comment naming the pause button), `src/dashboards/web/static/page-system.js` (lines 150–156 `gpuSub()` — the dense sub-line the TODO quotes, lines 164–188 `renderGpuSlot()`, line 72 the `data-k="gpus"` hero slot), `tests/test_api.py` (line 1425 — the six-heroes pin: "exactly six heroes: CPU, MEM, TEMP, DISK, GPU, NET", asserted over the three captures: discrete GPU, APU, no-GPU), `src/dashboards/web/pages/certs.html` line 81 (`<table class="data" id="agents-table">` — no `.scroll` modifier; `dash.css` gives `table.data` a scroll container only with the `.scroll` class), `src/dashboards/web/static/page-certs.js` (lines 55–56 — the table element references)
|
||||
|
||||
## Objective
|
||||
|
||||
Three small page-level cleanups, one per workstream: the pause button is gone from every page (the hidden-tab skip stays — a background tab idling its polls is not the operator's pause), the `/system` GPU data is split out of the dense sub-line into its own labelled values and a host with no GPU **omits** the slot instead of printing `n/a`, and the `/certs` agents table scrolls horizontally so the right-hand columns are reachable on a phone. No API surface changes; the pins that name the old behaviour (six heroes incl. an `n/a` GPU slot, the unpinned pause button) are rewritten, not left rotting.
|
||||
|
||||
## Dependencies
|
||||
- `20_masthead_nav_polish` (todo) — the pause button lives in the masthead row task 01.01 reflows, and the `/system` hero row is re-measured under the new bar; landing 20 first keeps 21's diff confined to the page bodies.
|
||||
|
||||
## Tasks
|
||||
1. `01_remove_pause_button.md` — delete the button, the `Space` shortcut, the `setPaused`/`_paused` plumbing and the CSS comment; the hidden-tab skip survives
|
||||
2. `02_gpu_own_values.md` — the dense `gpuSub()` line becomes four labelled values per card; a GPU-less host omits the slot (five heroes, no `n/a`)
|
||||
3. `03_certs_table_scroll.md` — `#agents-table` gets the `.scroll` modifier so its right-hand columns are reachable at phone width
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: `tests/test_api.py` — the served-page pins: the pause button absent from the four pages (a return of `#pause` fails a test), the six-heroes pin rewritten (GPU-present host: six, GPU-absent host: five with no `data-k="gpus"` slot), the `#agents-table` `.scroll` pin; `tests/test_system_contract.py` + `tests/card_contract.py` stay green (the API fields are unchanged — only the page's reading of them moves)
|
||||
- Coverage: **>90%** on new/modified code (the phase is markup + JS + CSS; the gate applies to the changed Python test files)
|
||||
- The phase is "Complete" only when the **entire** project test suite passes 100% (every test in the project, not just this phase's)
|
||||
|
||||
## Documentation & Consistency
|
||||
|
||||
- The final pass reviews `README.md` and `AGENTS.md` in full against the project as it stands after this phase — fixing every inconsistency found, **including pre-existing ones not caused by this phase**. AGENTS.md's `/system` paragraph ("a missing number or verdict — gateway's CPU temp, any host's absent GPU — prints `n/a` **uncoloured**") is the one rule this phase inverts for the GPU slot (the CPU-temp `n/a` stays — the TODO quotes the GPU line only); the sentence is split, the GPU half rewritten to the omission rule, and the pause button's mention in the masthead comment dropped.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] no page serves a `#pause` button or binds the `Space` key; a hidden tab still skips its polls (served-bytes pin + a CDP `visibilitychange` check)
|
||||
- [ ] `/system`'s GPU slot prints model / VRAM / pool / temp as four labelled values; a GPU-less host (the no-GPU capture, gateway) renders five heroes with no GPU slot and no `n/a` in it (screenshots in `data/logs/`)
|
||||
- [ ] at 360 px the `/certs` agents table scrolls horizontally to its last column (screenshot in `data/logs/`)
|
||||
- [ ] `uv run pytest` green, coverage not down, `ruff` + `pyright` clean
|
||||
- [ ] `bash .agents/validate.sh` green (including `node --check` of the modified page scripts)
|
||||
- [ ] no behavior change in completed work: the six-hero order on GPU hosts, the hidden-tab skip, the GPU history charts, the `/certs` row contents and the event pager are all unchanged
|
||||
@@ -0,0 +1,27 @@
|
||||
# Task 01 — Remove the pause button
|
||||
|
||||
**Phase:** `21_page_cleanups` · **Source:** `TODO.md:7` — "Completely remove the pause button"
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
|
||||
The `⏸ Pause` control is gone from the served markup, the page scripts and the shared poller: no button, no `Space`-key shortcut, no `setPaused`/`_paused` plumbing. The **hidden-tab** behaviour survives — `document.hidden` still skips a poll while the tab is backgrounded and `visibilitychange` still fires one on return; that is idle hygiene, not the operator's pause, and the TODO names the button only.
|
||||
|
||||
## Work
|
||||
|
||||
1. `src/dashboards/web/pages/disks.html`, `src/dashboards/web/pages/llm.html`, `src/dashboards/web/pages/system.html`, `src/dashboards/web/pages/ups.html` — delete the line-25 `<button class="ghost" id="pause" type="button" aria-pressed="false">⏸ Pause</button>` from each of the four (the only served occurrences — `/`, `/certs`, `/settings`, `/login` have none; a grep of `web/pages` for `pause` must come back empty when done).
|
||||
2. `src/dashboards/web/static/app.js` — delete the `#pause` binding block (lines 773–783: the button lookup, the `aria-pressed` paint, the click handler, and the `keydown` `Space` handler with its `event.preventDefault()`); in `poll()` (lines 587–615) delete the `_paused` state, `setPaused()`, the `paused` getter and the `state._paused` checks in `tick()`/`schedule()`/`visibilitychange` — the `document.hidden` guard and the `visibilitychange` refire stay, minus the `&& !state._paused` clause; the docstring at line 587 ("pausing when the tab is hidden or the user hit pause") drops the pause half.
|
||||
3. `src/dashboards/web/static/dash.css` — line 203's comment (which lists "status dots, pause, `⚷ Certs`, `⚙ Account`, theme" as the 360 px wrap's tenants) drops the pause mention.
|
||||
4. `tests/test_api.py` — add the served-bytes pin: `id="pause"` is **absent** from all eight served pages (a return of the button fails a test) and `⏸` appears nowhere in the served scripts.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: `tests/test_api.py` — the absence pin above; the existing poller tests (the `Dash.poll` hidden-tab behaviour, if any pin it — grep `hidden` in the test files first) stay green
|
||||
- Coverage: **>90%** on this task's new/modified code
|
||||
- Browser verification (invariant 10): `/` and `/system` — no pause control in the masthead, the `Space` key does nothing on the page body; hide the tab (CDP `Emulation.setDocumentHidden` or the window manager) and confirm the poll loop idles, then refire on return (the phase-18 verification script pattern)
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `grep -ri pause src/dashboards/web/` comes back empty (code, markup and comments all clean)
|
||||
- [ ] a hidden tab skips polls and a visible tab fires one on return (verified, not deduced)
|
||||
- [ ] `tests/test_api.py` green including the new absence pin
|
||||
- [ ] full test suite green
|
||||
- [ ] no behavior change in completed work: the range chips, the event pagers, the status dots and every other masthead control behave exactly as before
|
||||
@@ -0,0 +1,29 @@
|
||||
# Task 02 — GPU data as its own values; GPU-less hosts omit the slot
|
||||
|
||||
**Phase:** `21_page_cleanups` · **Source:** `TODO.md:12` — "The GPU data is wrapped poorly - Radeon 8060S Graphics · VRAM 180.2 MiB / 512.0 MiB · pool 79% · 69 °C is too much info for such small text and should be its own values. Boxes without a GPU should not report N/A but instead should omit these values."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
|
||||
The `/system` hero card's GPU slot stops stuffing model · VRAM · pool · temp into one small sub-line: each becomes its own labelled value on the card, at a readable size. A host with no GPU (gateway, the no-GPU capture) no longer renders an `n/a` hero slot — the slot is **omitted** and the hero row simply has five. The API is untouched: `gpus[]` keeps every field (`model`, `vram_used_bytes`, `vram_total_bytes`, `mem_used_pct`, `temp_c`), and the per-card history (`gpu.busy_pct@N`, `gpu.temp_c@N`) and the collapsed GPU chart are unchanged.
|
||||
|
||||
## Work
|
||||
|
||||
1. `src/dashboards/web/static/page-system.js` — replace `gpuSub()` (lines 150–156) with a `gpuValues(gpu)` that renders four labelled cells — **Model** (mono, the `gpu.model` string), **VRAM** (`used / total` via the existing `bytes()` formatter, `n/a` if either side is absent), **Pool** (`mem_used_pct` %), **°C** (`temp_c`) — laid out as a compact `.gpu-values` grid under the busy hero (the hero itself keeps only the `busy_pct` number + its label; colour rules stay: the GPU is uncoloured, phase 06's "a busy GPU is the job" rule — no severity token, no bar). A missing field on a **present** card prints `n/a` in its own cell (rule 2: the field is absent, the card is real); a `null` verdict cell draws faint, the existing `Dash.hero` convention.
|
||||
2. `src/dashboards/web/static/page-system.js` — `renderGpuSlot()` (lines 164–188): when `gpus` is empty, do **not** render the slot's fallback `n/a` content — render nothing (empty the slot's container and drop its hero styling so the row reflows to five heroes; the `data-k="gpus"` wrapper can stay as an empty, zero-footprint element or be removed — pick one and pin it). The multi-GPU case: one value grid per card, the cards' `GPU i` labels unchanged.
|
||||
3. `src/dashboards/web/static/dash.css` — the `.gpu-values` grid: a compact 2×2 (or 4×1 on wide) of labelled cells reusing the existing token vocabulary (`.metric`/`.faint`/`.mono` shapes, no new palette — the app's colour rule), sized so a 20-character model string wraps rather than truncates.
|
||||
4. `tests/test_api.py` — rewrite the six-heroes pin (line 1425): a host whose snapshot carries `gpus` still renders exactly six slots **in the existing order** (CPU, MEM, TEMP, DISK, GPU, NET); a host with no GPU renders exactly five with **no** `data-k="gpus"` slot (or the pinned empty variant) and no `n/a` in the hero row; the `gpu-values` cells (Model / VRAM / Pool / °C) are present in the served script. `tests/test_system_contract.py` and `tests/card_contract.py` stay green untouched (API fields unchanged).
|
||||
5. `AGENTS.md` — (Documentation & Consistency pass) the `/system` paragraph's "a missing number or verdict — gateway's CPU temp, any host's absent GPU — prints `n/a` **uncoloured**" is split: the CPU-temp half keeps the `n/a` rule, the GPU half becomes the omission rule (the TODO is the decision), and the dense-sub-line description of the GPU slot is rewritten to the four-value layout.
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: `tests/test_api.py` — the rewritten hero pin over the three captures (discrete GPU, APU, no-GPU) **and** over HTTP (the `self_test`-style app harness the contract tests use); `tests/test_system_contract.py` + `tests/card_contract.py` green untouched
|
||||
- Coverage: **>90%** on this task's new/modified code
|
||||
- Browser verification (invariant 10): `/system` against the live fleet — deskwork's Strix Halo card shows the four values at readable size (the TODO's quoted string is gone from the sub-line), gateway's card shows five heroes with no GPU slot and no `n/a`; the collapsed GPU charts still draw for the GPU hosts; screenshots in `data/logs/`
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] a GPU host's card shows Model / VRAM / Pool / °C as four labelled values; the dense sub-line string from the TODO is gone
|
||||
- [ ] a GPU-less host renders five heroes with no GPU slot and no `n/a` in the hero row (served-bytes pin + screenshot)
|
||||
- [ ] the GPU slot stays uncoloured (no `ok|warn|fail` token in the new markup — the phase-06 rule)
|
||||
- [ ] `tests/test_api.py` + `tests/test_system_contract.py` + `tests/card_contract.py` green
|
||||
- [ ] full test suite green
|
||||
- [ ] no behavior change in completed work: the hero order on GPU hosts, the `gpu.busy_pct@N` history, the GPU chart slot create/destroy and the "More detail" disclosure are untouched
|
||||
@@ -0,0 +1,27 @@
|
||||
# Task 03 — The `/certs` agents table scrolls horizontally
|
||||
|
||||
**Phase:** `21_page_cleanups` · **Source:** `TODO.md:9` — "In the certs page I can't horizontal scroll in the agents table to see the certs installed on the hosts."
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
|
||||
The `#agents-table` on `/certs` becomes horizontally reachable at phone width: its right-hand columns (the serial, expiry, `san` list and the Rotate / Revoke / bundle controls) are reachable by scrolling instead of being clipped. The repo's own mechanism is the `.scroll` modifier — `dash.css` gives `table.data` a scroll container **only** with that class (the same rule gap 12 measured on `/`'s fleet table), so this is the class, not a second mechanism. Row contents, the `COLUMNS` contract and every served pin are unchanged.
|
||||
|
||||
## Work
|
||||
|
||||
1. `src/dashboards/web/pages/certs.html` — line 81: `<table class="data" id="agents-table">` gains the `scroll` modifier (`class="data scroll"`), or is wrapped in `<div class="scroll">` — check which form `dash.css`'s selector actually matches (grep `\.scroll` in the file first; use the one it styles, do not invent a third).
|
||||
2. `src/dashboards/web/static/dash.css` — if the `.scroll` selector matches `table.data.scroll`, nothing to add; if it matches a `div.scroll > table.data` wrapper, verify the wrapper's max-height/overflow behaviour against the card's padding so the scroll container does not clip the row's own bottom border (measure in a browser, do not deduce). No new token, no new palette.
|
||||
3. `tests/test_api.py` — the served-HTML pin: `#agents-table` (or its wrapper, whichever shipped) carries the `.scroll` mechanism in the served bytes; the `COLUMNS`-resolution test (the one that reads the served `COLUMNS` table against a live `GET /api/certs` row in both directions) stays green untouched.
|
||||
4. `src/dashboards/web/static/page-certs.js` — lines 55–56 (`table`/`body` lookups): no change expected — the id and the `tbody` selector are untouched by the wrapper/class; verify the page still renders its rows (a wrapper that breaks `getElementById('agents-table')` would be caught by the existing render path).
|
||||
|
||||
## Testing & Quality
|
||||
- Unit/integration: `tests/test_api.py` — the pin above; the unchanged `COLUMNS` contract test
|
||||
- Coverage: **>90%** on this task's new/modified code (one class / one wrapper; the gate applies to the changed test file)
|
||||
- Browser verification (invariant 10): at 360 px on `/certs`, the table's scroll container is reachable — a CDP check that the container's `scrollWidth > clientWidth` and that scrolling to the end exposes the last column (the controls column), plus a screenshot mid-scroll in `data/logs/`; at 1280 px the table renders as before (no scrollbar on a wide viewport with three short rows)
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] at 360 px the `#agents-table` scrolls horizontally and its last column is reachable (screenshot in `data/logs/`)
|
||||
- [ ] at 1280 px the table is visually unchanged
|
||||
- [ ] `tests/test_api.py` green including the new pin and the unchanged `COLUMNS` test
|
||||
- [ ] full test suite green
|
||||
- [ ] no behavior change in completed work: the row contents, the status chips, the Rotate / Revoke / bundle controls and the `restart_required` outcome line behave exactly as before
|
||||
Reference in New Issue
Block a user