phase: 08_events_paging_and_copy

All green. Final report:

**Verified (phase 08 — both task files already in `complete/`; this was the final verification pass)**
- `Dash.eventsPager()` in `app.js` (50/page, `1–50 of N` strip, aria-labelled `‹ prev`/`next ›` disabled at the ends, `WeakMap` page state, reset only when the event *set* changes, `EVENT_FEED_LIMIT`=200) with three call sites; `ul.events` bounded to 320px + `overflow-y:auto`; `/api/events` untouched (`le=500`, no silent clamp).
- Live browser against the real fleet: `/system` `1–50 of 121`, `/llm` `1–50 of 200`, `/ups` `1–50 of 200`; container 320px vs scrollHeight 1543/2368; page 2 → `51–100 of 200` and survived two polls; no empty `.sub`/`.faint` container visible on any page; footers carry only measurements (`rates over Δ11.9s · poll 10s`). Fresh artifacts: `data/logs/08-final-{system-head,llm-page,index-page,ups-feed-page2}.png` (32 phase screenshots already present).
- Grep of live served bytes for all 13 deleted flavour fragments + `actions = -`: none survive in HTML or JS.
- `AGENTS.md` carries both new UI-convention rules (flavour-text rule with the `TODO.md:7` cited examples; the event-feed-window rule) and the updated test count.

**Tests / lint (full project 100% green)**
- `bash .agents/validate.sh` → **EXIT=0**: `uv run pytest --cov --cov-fail-under=94` → **624 passed, 9 skipped, coverage 97.91%**; `ruff check src tests agent` → All checks passed; `pyright` → 0 errors; `node --check` ×5 clean; repo hygiene clean.
- `uv run pytest -m live --live` (`.env.dev` sourced) → **9 passed**; `uv pip list --outdated` → empty. Re-ran pytest/ruff/pyright/node after the doc edits → all exit 0.

**Completion criteria**: bounded scroll + 50/page + count footer ✅ (all three feeds, live) · no removed flavour string ✅ · AGENTS.md rule ✅ · `validate.sh` green + screenshots in `data/logs/` ✅.

**Documentation**: full README/AGENTS review against code (env table vs `config.py`/`.env*.example`, script flags, Containerfile uid 10001/8090, chart heights 120/110/160, test count, coverage) — one inaccuracy fixed: README said "Every page's event feed" (the overview has none) → now "The three event feeds (`/system`, `/llm`, `/ups`)"; also restored the missing trailing newline in `TODO.md`. Everything else verified consistent.

**Notes**: paging stays client-side (no API change); the task-02 defect fix in `/ups` setup copy (`NUT_HOST`/`NUT_PORT`, `LISTEN 127.0.0.1`, one `instcmds` per command, never `actions = -`) re-verified against README and live bytes. Pre-existing items intentionally left to their own phases: `ups.test.result: "Done and passed"` rated `warn` (operator's `TODO.md` entry) and the dev-DB `database is locked` retention issue.

**Next pending phase**: `.agents/phases/todo/09_disks_smartctl/`
This commit is contained in:
2026-10-17 19:32:36 -04:00
parent 3d5ccd7a39
commit 19cfedfddf
29 changed files with 721 additions and 74 deletions
@@ -0,0 +1,44 @@
# Phase 08 — Event feeds page; the flavour text goes
**Source:** `TODO.md` L6, L7 — "page the event tables; strip the flavour text": "All the events tables need to be scrollable and paginated. They are causing the page to be massive." · "Get rid of most of the flavor text and add a note to AGENTS.md to not add it unless necessary. It just comes off as LLM generated slop, not helpful information."
**Story:** n/a
**Context:** the three `ul.events` feeds (`src/dashboards/web/pages/system.html` L58, `llm.html` L103, `ups.html` L70-72), `Dash.eventText()` in `src/dashboards/web/static/app.js`, the page-head sub-lines with the quoted flavour text (`system.html` L45, `index.html` L73), `AGENTS.md` → *UI conventions*, `tests/test_api.py` (served-bytes pins, orphan-`id` rule)
## Objective
A dead host's 500-event history must not make `/system` take a page to scroll: every
event feed becomes a bounded, scrollable, paginated window. The descriptive copy that
explains the app to the operator ("agent-reported values; rates are measured over the Δ
window…") goes away page by page, and `AGENTS.md` records the rule so the slop does not
crawl back in.
## Dependencies
- `07_llm_monitor_style_pages` (todo) — same page files and `app.js`/`dash.css`; this
phase lands on the restructured `/llm` page, not the phase-04 one.
- `06_system_page_gpu_verdicts` (todo) — rewrites `/system`'s panel structure; event-feed
work must not fight it for the same markup.
## Tasks
1. `01_paginate_event_feeds.md` — one shared pager: the three `ul.events` become scrollable containers with a 50-per-page window and a prev/next + count footer; no API change.
2. `02_strip_flavor_text.md` — audit all four pages and the page scripts for descriptive copy, delete what is not functional, and pin the rule in `AGENTS.md`'s UI conventions.
## Testing & Quality
- Hermetic: `tests/test_api.py` served-bytes pins (pager markup, no removed flavour
strings still present), `node --check` on touched scripts, the orphan-`id` rule
(the pager introduces `id`s — every one must be referenced).
- Live: `uv run pytest -m live --live` — the event endpoints still answer; a feed with
more than 50 stored events (the real event table has history) paginates instead of
dumping.
- Browser (DoD): screenshots in `data/logs/` — a long feed bounded and scrollable, page
2 reachable, the removed sub-lines gone from the page heads.
- Coverage: **>90%** on modified code, ≥94% overall gate maintained.
## Completion Criteria
- [ ] all three feeds scroll in a bounded container and paginate 50-per-page with a count footer
- [ ] no removed flavour string survives in served HTML or JS
- [ ] `AGENTS.md` carries the flavour-text rule under UI conventions
- [ ] `bash .agents/validate.sh` green; screenshots in `data/logs/`
@@ -0,0 +1,56 @@
# Task 01 — Event feeds: scrollable and paginated
**Phase:** `08_events_paging_and_copy` · **Source:** `TODO.md:6` — "All the events tables need to be scrollable and paginated. They are causing the page to be massive."
**Story:** n/a
## Objective
Every event feed (`/system`, `/llm`, `/ups`) renders a bounded, scrollable window over the
events instead of a growing `ul` that makes the page take a scroll to read. One shared
helper, three call sites — a pager that only works on one page is the bug this shape
prevents.
## Work
1. `src/dashboards/web/static/app.js` — add `Dash.eventsPager(el, events, opts)`: renders
the visible page of `events` into the feed's `ul` (keeping the existing
`Dash.eventText()` row rendering and per-row subject-once rule untouched), with a
footer: `1–50 of N` and prev/next controls (disabled at the ends, `aria`-labelled).
Page size defaults to 50; the feed container is the scrollable element, the page
stays short.
2. Call sites — `page-system.js`, `page-llm.js`, `page-upts.js` → `page-ups.js`: each
replaces its direct `ul#events` population with the pager (the "loading…" / empty /
not-configured states keep their current behaviour; pagination only applies when there
is more than one page of data). On a refresh that brings new events, the pager resets
to the first page (an operator reading page 3 of stale history should not silently land
on page 3 of the new set).
3. `src/dashboards/web/static/dash.css` — `.events` gets a bounded height (scrollable
container, existing token vocabulary); the pager footer styled as a quiet `.faint`
strip. No new palette.
4. No API change: `/api/events` keeps answering exactly what it does (server-side list,
no `page`/`limit` params added — the feeds are page-local and the endpoint's
contract is pinned by `tests/test_api.py`).
5. `tests/test_api.py` — pin the served markup (the three feeds keep their `id`s; the
pager's new `id`s/`aria-*` are all referenced — the orphan rule stays green), and
`node --check` on all touched scripts.
- ASSUMPTION: **client-side** pagination (50-per-page, scrollable container) is the
confirmed reading — the event tables grow with history and the server already serves
the list; a server-side `page`/`limit` API change would re-open a pinned endpoint
contract for a page-local problem.
## Testing & Quality
- Unit: `tests/test_api.py` (markup pins), `node --check`; a fixture-events render check
(page 1 of 3 pages, footer counts, disabled prev on page 1).
- Live: `uv run pytest -m live --live` — event endpoints unchanged.
- Browser (DoD): screenshot in `data/logs/` — a feed with >50 events bounded and
scrollable, page 2 showing rows 51–100.
- Coverage: **>90%** on modified code; overall gate (94%) maintained.
## Completion Criteria
- [ ] all three feeds paginate 50-per-page with a `1–50 of N` footer and prev/next
- [ ] the page height no longer grows with event history; the container scrolls
- [ ] refresh resets to page 1; loading/empty/not-configured states unchanged
- [ ] `uv run pytest` green; `uv run ruff check src tests agent && uv run pyright` clean
@@ -0,0 +1,56 @@
# Task 02 — Strip the flavour text; pin the rule in AGENTS.md
**Phase:** `08_events_paging_and_copy` · **Source:** `TODO.md:7` — "Get rid of most of the flavor text and add a note to AGENTS.md to not add it unless necessary. It just comes off as LLM generated slop, not helpful information. Like 'agent-reported values; rates are measured over the Δ window in the footer, and each panel's charts cover only that host' or 'every number is scraped from the box it belongs to · null renders as n/a'"
**Story:** n/a
## Objective
The pages stop narrating themselves. Copy that explains the app's own design to the
operator ("agent-reported values; rates are measured over the Δ window…", "every number is
scraped from the box it belongs to · null renders as n/a") is deleted; copy that is
*functional* — a source tag, a `Δ10.0s` provenance, a "not configured" explanation, the
SIMULATED badge, an error that says why a panel is empty — stays. `AGENTS.md` records
the rule so it is a convention, not a mood.
## Work
1. Audit, page by page: `src/dashboards/web/pages/index.html`, `system.html`, `llm.html`,
`ups.html` and the `src/dashboards/web/static/page-*.js` / `app.js` strings. The two
lines the TODO quotes (the `system.html` L45 sub-line, the `index.html` L73 sub-line)
are confirmed deletions; every other candidate is judged on one test: *would the
operator act differently without it?* No → delete. Kept by default: the `Δwindow`
footnotes (measurements, rule: "a Δ label is a measurement, never a config value"),
the per-rate provenance tags ("scraped / measured Δcounter/Δt / estimated" — dropping a
tag is a regression, not a simplification), `n/a`-and-why states, the "queued (deferred)
requests are not in /slots" note, and any string a test pins as functional.
2. Delete the confirmed flavour from markup *and* from JS that injects the same copy;
a sub-line that becomes empty removes its container, not an empty `<span>`.
3. `tests/test_api.py` — where a deleted string was pinned, update the pin; add an
assertion that the two quoted strings (and the other confirmed deletions) are **absent**
from served bytes, so the slop cannot creep back in silently.
4. `AGENTS.md` → *UI conventions* — add the rule: **Flavour text is not helpful
information.** Pages do not narrate their own data pipeline or design; copy exists
only when the operator needs it to act (a why-this-panel-is-empty, a provenance tag,
a measurement label). The two deleted lines are the cited examples. (DoD: AGENTS.md
updates with rule changes — this is the task that does it, not the conversion.)
5. `node --check` on every touched script.
- ASSUMPTION: "most of the flavour text" = the audit rule above (act-differently test),
with the two quoted lines as the floor of deletions — the operator's examples, not the
ceiling. If a borderline string survives the audit, it survives because it is a
measurement or a why, and the audit notes say which.
## Testing & Quality
- Unit: `tests/test_api.py` (absence pins for the deleted strings; served-bytes shape
unchanged otherwise), `node --check`.
- Browser (DoD): screenshot in `data/logs/` — the four page heads without the removed
sub-lines; no empty containers.
- Coverage: **>90%** on modified code; overall gate (94%) maintained.
## Completion Criteria
- [ ] the two quoted lines (and the rest of the audit's deletions) are gone from served HTML and JS
- [ ] functional copy (provenance tags, Δ footnotes, empty-state explanations) intact
- [ ] `AGENTS.md` carries the flavour-text rule with the deleted lines as cited examples
- [ ] `uv run pytest` green; `uv run ruff check src tests agent && uv run pyright` clean