diff --git a/.agents/phases/todo/19_ups_events_expandable/00_phase.md b/.agents/phases/complete/19_ups_events_expandable/00_phase.md similarity index 100% rename from .agents/phases/todo/19_ups_events_expandable/00_phase.md rename to .agents/phases/complete/19_ups_events_expandable/00_phase.md diff --git a/.agents/phases/todo/19_ups_events_expandable/01_events_disclosure.md b/.agents/phases/complete/19_ups_events_expandable/01_events_disclosure.md similarity index 100% rename from .agents/phases/todo/19_ups_events_expandable/01_events_disclosure.md rename to .agents/phases/complete/19_ups_events_expandable/01_events_disclosure.md diff --git a/.agents/phases/todo/19_ups_events_expandable/02_events_lazy_loading.md b/.agents/phases/complete/19_ups_events_expandable/02_events_lazy_loading.md similarity index 100% rename from .agents/phases/todo/19_ups_events_expandable/02_events_lazy_loading.md rename to .agents/phases/complete/19_ups_events_expandable/02_events_lazy_loading.md diff --git a/.agents/phases/todo/19_ups_events_expandable/03_disclosure_css.md b/.agents/phases/complete/19_ups_events_expandable/03_disclosure_css.md similarity index 100% rename from .agents/phases/todo/19_ups_events_expandable/03_disclosure_css.md rename to .agents/phases/complete/19_ups_events_expandable/03_disclosure_css.md diff --git a/.agents/phases/todo/19_ups_events_expandable/04_tests_and_verification.md b/.agents/phases/complete/19_ups_events_expandable/04_tests_and_verification.md similarity index 100% rename from .agents/phases/todo/19_ups_events_expandable/04_tests_and_verification.md rename to .agents/phases/complete/19_ups_events_expandable/04_tests_and_verification.md diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.err b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.md b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.md new file mode 100644 index 0000000..e252c1a --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.md @@ -0,0 +1,12 @@ +**Phase 19 final verification pass — all tasks already complete; everything green, no code changes needed.** + +- **Verified (all 4 task files present in `complete/`)**: `details.events-box` disclosure on `/ups` with no `open` attribute and `#events` inside it; lazy fetch (`if (state.events.open) await refreshEvents()`), toggle stale rule on `state.pollMs`, success-path-only `painted`/`fetchedAt`; `charts-box`-shaped CSS with the `> .body` padding restatement; 4 new served-byte pins in `tests/test_api.py`. +- **Criterion 1 (collapsed on load, no first-poll fetch)**: PASS — re-ran `data/logs/19-task04-verify.cjs` against the live dev server (real upsd, 2 UPSes): **28/28 PASS**, `0` `/api/events` requests across 2 closed polls vs 4 snapshot polls; expand → exactly 1 request, 50 rows with `Oct 20, 07:11` timestamps + severity chips + `1–50 of 200` pager. +- **Criterion 2 (cache/stale/poll-while-open)**: PASS — fresh re-open 2→2 requests, stale re-open 2→3, loop refreshes while open, close asks for nothing, failed fetch keeps rows and doesn't stamp the cache. +- **Criterion 3**: `uv run pytest` → **1458 passed, 15 skipped**; `uv run pytest --cov` → **98.94%** (floor 94, unchanged); `uv run ruff check src tests agent` → clean; `uv run pyright` → **0 errors**. +- **Criterion 4**: `bash .agents/validate.sh` → **exit 0** (incl. `node --check` of all page scripts + repo-hygiene gates). +- **Criterion 5**: `data/logs/ups-events-closed.png` (collapsed `▸ POWER EVENTS`, 0 events requests noted in `ups-events-verify.out`) and `ups-events-open.png` (`▾`, bounded 320px list, pager strip) regenerated and inspected this pass. +- **Criterion 6 (no regressions)**: full suite green incl. UPS contract/pager pins; `uv run pytest -m live --live` (with `.env.dev`) → **11 passed**; phase 18's `nav-final-pass.cjs` sweep re-run → **45/45** (this phase's markup/CSS didn't touch the masthead). +- **Documentation**: full README.md (980 L) + AGENTS.md (1992 L) review against the code — verified env templates vs `config.py`, API route table vs `api.py`, installer flags vs `deploy/install-agent.sh`, agent routes vs `host_agent.py`, test/coverage/live tallies, `data/logs` artifacts, and live fleet facts (`HOST_TARGETS` all `http://`, all three agents `version 1.0.0`, nine sources `ok`). AGENTS.md UI conventions and README's events paragraph already carry the disclosure/lazy/stale sentences from tasks 01–04. **Verified consistent — no changes needed.** +- **Notable**: made zero repo edits (working tree holds only this phase's task work). `uv pip list --outdated` couldn't complete (PyPI 503 for the project name) — no dependency moved this pass, so rule 8 is not triggered; validate.sh's earlier advisory still lists `fastapi 0.153.0 → 0.156.3`, deliberately not upgraded in a UI/verification phase. +- **Next pending phase**: `.agents/phases/todo/20_masthead_nav_polish/` (tasks 01–04). diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.md.status b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.validate b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.validate new file mode 100644 index 0000000..a61e138 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__00_phase.a1.validate @@ -0,0 +1,65 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 14%] +........................................................................ [ 19%] +........................................................................ [ 24%] +..................................s..................................... [ 29%] +........................................................................ [ 34%] +........................................................................ [ 39%] +........................................................................ [ 43%] +........................................................................ [ 48%] +........................................................................ [ 53%] +........................................................................ [ 58%] +........................................................................ [ 63%] +.......................................................................s [ 68%] +ssssssssss.............................................................. [ 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 436 0 100% +src/dashboards/app.py 185 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 3490 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1458 passed, 15 skipped, 1 warning in 153.66s (0:02:33) +== 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.156.4 wheel diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.err b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.md b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.md new file mode 100644 index 0000000..f32fe1e --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.md @@ -0,0 +1,13 @@ +Done — task 01 complete. + +- **`src/dashboards/web/pages/ups.html`**: the `Power events` card body moved inside a `
` with `Power events` — no `open` attribute, `#events`/`ul.events`/`loading…` placeholder unchanged, rest of the page byte-identical. +- **`tests/test_api.py::test_ups_power_events_feed_sits_behind_a_collapsed_disclosure`**: served-byte pin (`details.events-box` present, no `open` attr, summary label, `#events` ul + placeholder *inside*, no duplicate `

`, page's other ids intact). Verified it bites: mutations (added `open`, ul moved out, summary renamed) each fail it. +- `bash .agents/validate.sh` → **exit 0**: `uv run pytest --cov --cov-fail-under=94` = **1455 passed, 15 skipped**, coverage **98.94 %** (floor 94, unchanged); `ruff check src tests agent` "All checks passed"; `pyright` "0 errors"; `node --check` of `web/static/*.js` clean. +- **Browser proof (live dev server, `data/logs/19-task01-disclosure.cjs`)**: 10/10 checks PASS — `data/logs/19-ups-events-closed.png` (card paints one 21 px `Power events` summary line; `::details-content` is `content-visibility: hidden`) and `-open.png` (expand draws the bounded 320 px list, 50 rows, date/time rows intact). +- **Docs**: `AGENTS.md` UI-conventions event-feed paragraph gains the `/ups` disclosure sentence (collapsed by default, `#events` stays inside the disclosure, pinned on served bytes); `README.md` `/ups` feed paragraph says the card is a closed `Power events` summary until opened. +- **Decisions / notes**: + - Markup only, per the task file — no JS/CSS touched, so `/ups` still fetches the feed on every poll while closed (that is task 02's lazy gate) and the open body currently has `.body` padding 0 because `.card > .body` no longer matches (task 03's `details.events-box` CSS closes it). Both intermediate states render correctly and every test is green. + - Pinned the summary as the plain word `Power events` (no count chip — an unfetched count would be rule 2's invented number) and no `data-k` attribute, as the task's ASSUMPTIONs state. + - `uv pip list --outdated` advisory (fastapi 0.153→0.156) left untouched: no dependency moved in this task, same handling as phases 15/17/18. + +Next pending task: `.agents/phases/todo/19_ups_events_expandable/02_events_lazy_loading.md` (lazy fetch / poll-while-open in `page-ups.js`). diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.md.status b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.validate b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.validate new file mode 100644 index 0000000..b02b24b --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__01_events_disclosure.a1.validate @@ -0,0 +1,65 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 14%] +........................................................................ [ 19%] +........................................................................ [ 24%] +...............................s........................................ [ 29%] +........................................................................ [ 34%] +........................................................................ [ 39%] +........................................................................ [ 44%] +........................................................................ [ 48%] +........................................................................ [ 53%] +........................................................................ [ 58%] +........................................................................ [ 63%] +....................................................................ssss [ 68%] +sssssss................................................................. [ 73%] +........................................................................ [ 78%] +........................................................................ [ 83%] +........................................................................ [ 88%] +........................................................................ [ 93%] +............................................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 436 0 100% +src/dashboards/app.py 185 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 3490 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1455 passed, 15 skipped, 1 warning in 154.08s (0:02:34) +== 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.156.2 wheel diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.err b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.md b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.md new file mode 100644 index 0000000..7198499 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.md @@ -0,0 +1,19 @@ +Task 02 complete. + +**Implemented** +- `src/dashboards/web/static/page-ups.js`: `state.events {open,painted,fetchedAt}`; `refresh()` now does `if (state.events.open) await refreshEvents();` (poll loop feeds the list only while open); `refreshEvents()` stamps `fetchedAt`/`painted` **after** `Dash.eventsPager()` on the success path only (a failure keeps the rows and re-fetches on next open); `toggle` listener bound before `poll()` — first expand fetches, re-expand inside one `state.pollMs` paints the cached rows, older than that re-fetches; header comment documents the lazy rule. +- `tests/test_api.py`: `test_ups_events_are_fetched_only_while_the_disclosure_is_open` — served-script pins for the state shape, the `refresh()` gate (incl. no surviving ungated `await refreshEvents()`), the success-path-only stamp ordering, and the toggle's stale rule bound before the poll starts. +- `data/logs/19-task02-lazy.cjs` (+ `.out`): live browser proof, 21 checks — 0 `/api/events` requests across 2 closed polls, exactly 1 on first expand, loop refreshes while open, stops when closed, stale re-open re-fetches, fresh re-open makes no request (rows + pager footer + scroll identical), failed fetch keeps 50 rows and never moves the stamp. Screenshots: `19-ups-events-card-closed.png` (one summary line), `19-ups-events-card-open.png`, `19-ups-events-{closed,open,fetch-failed}.png`. + +**Tests / lint / coverage** (all green) +- `uv run pytest` → 1456 passed, 15 skipped, exit 0 +- `uv run pytest -q --cov --cov-fail-under=94` → Total coverage: 98.94% (unchanged) +- `uv run ruff check src tests agent` → All checks passed; `uv run pyright` → 0 errors, 0 warnings +- `bash .agents/validate.sh` → exit 0 (incl. `node --check` of `page-ups.js`); only the pre-existing advisory `fastapi 0.153.0 → 0.156.2` (no dependency touched by this task) +- Dev server http://127.0.0.1:8090 still `status: ok` (all 9 sources) + +**Docs**: AGENTS.md UI conventions (event-feed paragraph) and README's `/ups` expandable bullet now state the lazy rule — closed = not fetched, first expand fetches, re-open inside one poll interval redraws what is there. + +**Decisions**: stale bound = one `state.pollMs` (task's ASSUMPTION); stamp placed after the pager call so a pager throw can't mark an unpainted list as cached. Live infrastructure untouched (repo-side + local browser only). + +**Next pending task**: `19_ups_events_expandable/03_disclosure_css.md`. diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.md.status b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.validate b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.validate new file mode 100644 index 0000000..862fa98 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__02_events_lazy_loading.a1.validate @@ -0,0 +1,65 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 14%] +........................................................................ [ 19%] +........................................................................ [ 24%] +................................s....................................... [ 29%] +........................................................................ [ 34%] +........................................................................ [ 39%] +........................................................................ [ 44%] +........................................................................ [ 48%] +........................................................................ [ 53%] +........................................................................ [ 58%] +........................................................................ [ 63%] +.....................................................................sss [ 68%] +ssssssss................................................................ [ 73%] +........................................................................ [ 78%] +........................................................................ [ 83%] +........................................................................ [ 88%] +........................................................................ [ 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 436 0 100% +src/dashboards/app.py 185 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 3490 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1456 passed, 15 skipped, 1 warning in 164.27s (0:02:44) +== 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.156.2 wheel diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.err b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.md b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.md new file mode 100644 index 0000000..1cbe1d9 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.md @@ -0,0 +1,16 @@ +Everything is green. Report: + +**Implemented** +- `src/dashboards/web/static/dash.css` — new `details.events-box` family beside `details.charts-box`: `min-width:0`, `> summary` (cursor, `list-style:none`, webkit marker hidden, `::before` `▸` / `[open] ::before` `▾` in `--text-faint`, hover, open-state de-emphasis), all tokens declared in `:root`. +- Shape deviation (deliberate, documented in the block's comment): this `` *replaces* the card's `header`, so it takes `.card > header`'s exact rhythm — `padding:.6rem .8rem`, the h3's `12.5px/650` uppercase type, `--text-muted`, `var(--border)` bottom rule, `color-mix` header ground — and the caret is a flex item (as in `charts-box`), not the snippet's `position:absolute` (which sits off the text's centre line). No `margin-bottom` when open: `.body` supplies the gap. +- **Defect fixed from task 01**: `.card > .body` is a *child* selector, so once the body moved inside `
` it lost its padding. Added `details.events-box > .body { padding: .8rem; }` (restatement, measured equal to another card's). +- No new rules for `ul.events` / `.events-pager` — verified they resolve inside the disclosure (320px bounded scroll, `overscroll-behavior:contain`, flex pager with its 1px top rule, `1–50 of 200`, both arrows). +- `AGENTS.md` UI conventions: the disclosure's styling sentence + the `.card > .body` child-selector gotcha. + +**Tests / lint** (`bash .agents/validate.sh`, rc=0): `uv run pytest --cov --cov-fail-under=94` → **1456 passed, 15 skipped, coverage 98.94%**; `ruff check src tests agent` → All checks passed; `pyright` → 0 errors; `node --check` all JS clean. `uv run pytest -m live --live` → **11 passed**. Browser proof: `data/logs/19-task03-css.cjs` → **24/24 PASS** (`19-task03-css.out`), screenshots `19-task03-{closed,open}.png` + `19-task03-dark-{closed,open}.png` against the live dev server (both palettes; other cards and battery charts unchanged). + +**Docs**: AGENTS.md updated; README no change needed (task 02's paragraph already covers the user-visible behaviour). + +**Notes**: the served-stylesheet pin is deferred to task 04 by the task file, so I did not pre-empt it. Advisory only: `fastapi 0.153.0 → 0.156.3` available; dependencies untouched by this task, so not pulled. + +**Next pending task**: `.agents/phases/todo/19_ups_events_expandable/04_tests_and_verification.md`. diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.md.status b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.md.status new file mode 100644 index 0000000..9766475 --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.md.status @@ -0,0 +1 @@ +ok diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.validate b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.validate new file mode 100644 index 0000000..a60bcfe --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__03_disclosure_css.a1.validate @@ -0,0 +1,65 @@ +== uv run pytest --cov (hermetic: no network, live tests skip) == +........................................................................ [ 4%] +........................................................................ [ 9%] +........................................................................ [ 14%] +........................................................................ [ 19%] +........................................................................ [ 24%] +................................s....................................... [ 29%] +........................................................................ [ 34%] +........................................................................ [ 39%] +........................................................................ [ 44%] +........................................................................ [ 48%] +........................................................................ [ 53%] +........................................................................ [ 58%] +........................................................................ [ 63%] +.....................................................................sss [ 68%] +ssssssss................................................................ [ 73%] +........................................................................ [ 78%] +........................................................................ [ 83%] +........................................................................ [ 88%] +........................................................................ [ 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 436 0 100% +src/dashboards/app.py 185 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 3490 37 99% +Required test coverage of 94% reached. Total coverage: 98.94% +1456 passed, 15 skipped, 1 warning in 158.48s (0:02:38) +== 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.156.3 wheel diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__04_tests_and_verification.a1.err b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__04_tests_and_verification.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__04_tests_and_verification.a1.md b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__04_tests_and_verification.a1.md new file mode 100644 index 0000000..1f9df6f --- /dev/null +++ b/.agents/reports/19_ups_events_expandable/19_ups_events_expandable__04_tests_and_verification.a1.md @@ -0,0 +1,19 @@ +Everything is green. Final state verified: + +**Implemented (task 04 — pins + browser verification)** +- `tests/test_api.py::test_the_ups_event_rows_keep_the_date_time_and_severity_shape` — served-script pin of the row template: `

" not in html, "the card heads its own body twice" + # the rest of the page is untouched by the disclosure + for still_there in ('id="ups-cards"', 'id="chart-charge"', 'id="chart-load"', + 'id="confirm-dialog"'): + assert still_there in client.get("/ups").text, f"/ups lost {still_there}" + + +def test_ups_events_are_fetched_only_while_the_disclosure_is_open(client) -> None: + """A closed Power events card costs the poll loop *nothing* — no fetch, no rows. + + Hiding a list that is still fetched every 5 s is the half-measure this pins out: the + disclosure is only worth having if `refresh()` stops asking `/api/events` while it is shut + (the `/system` Charts rule — "nothing is built or drawn while the stack is shut"), and the + expand still has to fill the list, so the toggle needs the two facts a re-open is judged + on: has it ever painted, and how old is what it painted. Pinned on the served script + because there is no JS test runner (a locked anchor). + """ + js = _code(client.get("/static/page-ups.js").text) + + # The state the two paths share: closed, nothing painted yet, nothing fetched yet. + decl = js[js.index("const state = {"):js.index("const MARKS")] + assert "events: { open: false, painted: false, fetchedAt: 0 }" in decl, \ + "the disclosure lost the state its lazy rule reads" + + # The poll path asks only while the card is open. + refresh = js[js.index("async function refresh("):js.index("async function refreshEvents(")] + assert "if (state.events.open) await refreshEvents();" in refresh, \ + "the poll loop fetches the event feed while the disclosure is shut" + assert not re.search(r"^\s*await refreshEvents();", refresh, flags=re.M), \ + "an ungated refreshEvents() call survived in the poll path" + + # …and the fetch stamps its own freshness, on the success path only: a failed fetch must + # leave `painted`/`fetchedAt` alone so the next open re-fetches (invariant 4's shape). + feed = _event_feed_path(js) + assert "state.events.fetchedAt = Date.now();" in feed and "state.events.painted = true;" in feed, \ + "a successful fetch does not stamp the cache the re-expand rule depends on" + assert feed.index("Dash.eventsPager(") < feed.index("state.events.fetchedAt = Date.now();"), \ + "the cache is stamped before the rows are drawn" + assert feed.index("state.events.painted = true;") < feed.index("} catch (err)"), \ + "a failed fetch would mark the list as painted" + + # The toggle is bound before the loop starts, so the first expand of the session is wired. + boot = js[js.index("document.addEventListener('DOMContentLoaded'"):] + handler = boot[boot.index("const eventsBox = $('.events-box');"):boot.index("state.poller = poll(")] + assert boot.index("const eventsBox =") < boot.index("state.poller = poll("), \ + "the disclosure is wired after the first poll" + assert "state.events.open = eventsBox.open;" in handler, \ + "the toggle never learns whether the card is open" + assert "if (!eventsBox.open) return;" in handler, \ + "closing the card takes the fetch gate off" + # first expand fetches; a re-expand paints the cache while it is one poll interval old + assert "if (!state.events.painted || Date.now() - state.events.fetchedAt > state.pollMs) {" in handler, \ + "the expand lost its stale rule (one poll interval, the page's own cadence)" + assert "refreshEvents();" in handler[handler.index("if (!state.events.painted"):], \ + "an open disclosure no longer fetches on a stale or empty cache" + + +def test_the_ups_event_rows_keep_the_date_time_and_severity_shape(client) -> None: + """`TODO.md:4`'s other half: an expandable list is only *clear* if each row says when. + + The disclosure is the new part of this phase; this is the half that had to survive it + untouched. Every row still opens with a `