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:
@@ -256,7 +256,7 @@ cp .env.dev.example .env.dev # dev-only: ssh tunnels + live targets
|
||||
./scripts/dev-server.sh status # per-source ok/DOWN
|
||||
./scripts/dev-server.sh logs
|
||||
|
||||
uv run pytest # hermetic: 604 passed + 9 live-skipped, no network
|
||||
uv run pytest # hermetic: 624 passed + 9 live-skipped, no network
|
||||
uv run pytest --cov # coverage gate (97.9%; floor 94%)
|
||||
uv run pytest -m live --live # opt-in: real fleet / engines / upsd
|
||||
uv run ruff check src tests agent && uv run pyright
|
||||
@@ -343,6 +343,25 @@ Window chips (5m…30d on `/system` and `/llm`, 1h…90d on `/ups`; the API acce
|
||||
panel that cannot get data says why in the panel — the operator should never have to open
|
||||
devtools to learn that a source is unconfigured. Escape anything upstream-provided through `DashCharts.escapeHtml`.
|
||||
|
||||
**Flavour text is not helpful information.** A page does not narrate its own data pipeline or
|
||||
its own design; copy exists only where the operator needs it to act — a why-this-panel-is-empty
|
||||
sentence, a provenance tag, a measured `Δ<window>` label, a chart's unit, a `SIMULATED` badge,
|
||||
an error that names what failed. The audit is one question per string: *would the operator act
|
||||
differently without it?* No → delete it, container and all. Deleted in phase 08 (the cited
|
||||
examples, from `TODO.md:7`): "every number is scraped from the box it belongs to · `null`
|
||||
renders as `n/a`" (`/`'s footer), "agent-reported values; rates are measured over the Δ window
|
||||
in the footer, and each panel's charts cover only that host" (`/system`'s *Per host* sub-line),
|
||||
and the same genre — `/system`'s "numbers are measured, not inferred", `/llm`'s "each throughput
|
||||
number carries its provenance", `/ups`'s "commands are the only write path in this app", each
|
||||
event feed's content caption (the empty-state sentence already says it), and the design
|
||||
clauses inside empty-state prose. What survived the audit did so because it is a measurement or
|
||||
a why: every `Δ<window>` footnote, `provenance()` on each rate, `fleet.seams`, *sums only
|
||||
engines answering now*, the queued-request note under the slot grid, the SIMULATED banners, and
|
||||
the setup instructions in every empty state. `tests/test_api.py` pins the deleted sentences
|
||||
**absent** from every served page and script (a sentence that migrates to another page is the
|
||||
same slop), the kept functional strings **present**, and that no removed sub-line left an empty
|
||||
`.sub`/`.faint` container behind.
|
||||
|
||||
The overview is exactly two panels — **Warnings & errors** (`ul.alerts`, one row per
|
||||
`/api/summary` alert: verdict class, source linked to its dashboard via the alert's own
|
||||
`href`, message, age) and **Fleet**. A non-empty list floats the alerts panel above the
|
||||
@@ -432,6 +451,27 @@ the UPS rows used to `replace()` it anywhere and mangled "Quick battery test sen
|
||||
This is presentation only — the stored event and `/api/events` keep every word, and the raw
|
||||
error string still surfaces in `/api/health`.
|
||||
|
||||
An event feed is a **window**, not a printout (`Dash.eventsPager(el, events, opts)` in
|
||||
`app.js`, one helper with three call sites — a pager that works on one page is the bug this
|
||||
shape prevents). `ul.events` is a bounded 320px scroll container (`overscroll-behavior:
|
||||
contain`) and the pager puts `EVENT_PAGE_SIZE` = **50** events in it, with a `.events-pager`
|
||||
strip under the list: `1–50 of 200` between `‹ prev` and `next ›`, both `aria-label`led for
|
||||
direction (the feed is newest-first, so `prev` walks towards now) and `disabled` at the ends.
|
||||
The strip is `hidden` when the feed fits on one page — every row on screen *is* the whole
|
||||
story, and a disabled arrow says "more history" when there is none. Each page keeps its own
|
||||
`row(event)` (so `/ups` keeps its longer timestamp and runtime footnote, and escaping stays
|
||||
the page's job — the pager has no default renderer and says so in a row when a caller forgets
|
||||
one) and its own `empty` sentence, so the `loading…` and empty states read exactly as they
|
||||
did before paging existed. Two numbers are load-bearing and pinned together: the fetch
|
||||
(`EVENT_FEED_LIMIT` = 200, `/api/events?limit=…`) must exceed one page, because a limit at or
|
||||
below `EVENT_PAGE_SIZE` is a pager that silently never appears; `/api/events` itself is
|
||||
**unchanged** — no `page`/`limit` vocabulary grew for one page's layout. Paging state lives in
|
||||
a `WeakMap` keyed by the `<ul>`: it survives a poll (so page 3 is still there two seconds
|
||||
later — resetting every refresh would make page 2 unreadable) and resets to page 1 when the
|
||||
event *set* changes (an operator reading page 3 of yesterday's history must not land on page 3
|
||||
of a list that just changed). Rows are re-written only when their markup changed, which is
|
||||
what keeps the reader's scroll position inside the container.
|
||||
|
||||
The `/system` host card is one row of six heroes — CPU, MEM, TEMP, DISK, GPU, NET (the
|
||||
`.heroes` grid; `tests/test_api.py` pins the slots and their order on the served script).
|
||||
Colour comes from the payload's `severity` dicts and nowhere else; a missing number or
|
||||
|
||||
Reference in New Issue
Block a user