phase: 19_ups_events_expandable
**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).
This commit is contained in:
@@ -997,7 +997,7 @@ set -a; . ./.env.dev; set +a # dev dials the fleet the prod way, no
|
||||
./scripts/dev-server.sh status # per-source ok/DOWN — unauthenticated: /api/health
|
||||
./scripts/dev-server.sh logs
|
||||
|
||||
uv run pytest # hermetic: 1454 passed + 15 skipped (11 live,
|
||||
uv run pytest # hermetic: 1458 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
|
||||
@@ -1390,7 +1390,28 @@ a `WeakMap` keyed by the `<ul>`: it survives a poll (so page 3 is still there tw
|
||||
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.
|
||||
what keeps the reader's scroll position inside the container. The `/ups` feed is also
|
||||
**expandable** (phase 19, `TODO.md:4`): its card body is a `details.events-box` disclosure with
|
||||
no `open` attribute, so that card is one `Power events` summary line until the operator opens it
|
||||
— a closed `<details>` renders no content, which is `/system`'s Charts rule applied to a feed,
|
||||
and the `#events` id stays *inside* the disclosure so the pager and its bounded scroll keep their
|
||||
target (pinned on the served bytes). The disclosure is also what makes it **lazy**: `refresh()`
|
||||
fetches the feed only while `state.events.open` is true, so a card nobody is looking at costs the
|
||||
poll loop nothing (a hidden-but-still-polled list is the half-measure this is). The first expand
|
||||
fetches; a re-expand paints the rows already in the `<ul>` while they are younger than one poll
|
||||
interval (`state.pollMs`, the page's own cadence) and re-fetches only past it — a `<details>`
|
||||
keeps its children, so a fresh re-open costs no request and loses neither the pager's page nor
|
||||
the reader's scroll. `painted`/`fetchedAt` are stamped on the success path alone, which is the
|
||||
invariant-4 shape here: a failed fetch leaves the rows on screen, leaves the stamp where it was,
|
||||
and the next open re-fetches. Its styling is `details.charts-box`'s shape with one structural
|
||||
difference: that toggle sits *inside* a card body, while this `<summary>` **replaces** the card's
|
||||
`header` — so it carries the header's type, padding, sunken ground and bottom rule (a closed card
|
||||
reads as its heading, an open one puts the feed under the same rule every other card draws) and
|
||||
no `margin-bottom` when open, because the `.body` below carries that spacing. That is also why
|
||||
`details.events-box > .body` restates the `.8rem`: `.card > .body` is a **child** selector, so a
|
||||
body moved inside a disclosure stops being padded until something says so — while `ul.events`
|
||||
and `.events-pager` are class-based and needed no new rule to keep the 320px scroll and the
|
||||
`1–50 of N` strip working inside it.
|
||||
|
||||
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).
|
||||
@@ -1932,10 +1953,11 @@ memory plot; the `cpu.usage_pct@coreN` series is still stored and still queryabl
|
||||
wraps, so nothing is lost and nothing gained: the masthead is complete at 360 px and the
|
||||
table tail is still out of reach. The fix is a responsive-table pass (`.scroll` on the wide
|
||||
tables, or a per-host card layout at ≤640px) and it touches pinned page shape on `/`, so it is
|
||||
a phase rather than a cleanup. Two operator TODO items are unphased alongside it: never
|
||||
accepting a non-mTLS agent (fleet gap 11's rollout, half of it), and keeping both
|
||||
`⚙ Account` / `⚷ Certs` masthead links visible on the admin pages that today do not link to
|
||||
themselves (phase 15's deliberate rule, now questioned).
|
||||
a phase rather than a cleanup. The two operator TODO items that sat alongside it are phased
|
||||
now, in `todo/`: `24_dev_mtls_rollout` (never accepting a non-mTLS agent — fleet gap 11's
|
||||
rollout, half of it) and `20_masthead_nav_polish` (keeping both `⚙ Account` / `⚷ Certs`
|
||||
masthead links visible on the admin pages that today do not link to themselves — phase 15's
|
||||
deliberate rule, which that phase is the decision on).
|
||||
|
||||
## Locked decisions (and why)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user