phase: 27_mobile_tables_and_audit
The audit re-ran green (exit 0). Now checking the docs for defects introduced by the phase.
This commit is contained in:
@@ -800,11 +800,15 @@ Responsibilities, one line each:
|
||||
`GET /api/certs` row in both directions (the `SLOT_FIELDS` device), so a rename on either side
|
||||
fails a test instead of printing `n/a` beside a healthy fleet. The two CNs are markup, and pinned
|
||||
to `certs.ROOT_CN`/`certs.CLIENT_CN` the way `/settings` pins its password floor. The agents
|
||||
table is `table.data.scroll` — the repo's one scroll mechanism (`TODO.md:9`: at phone width the
|
||||
table is `table.data.scroll` — the class-level scroll mechanism (`TODO.md:9`: at phone width the
|
||||
serial / bundle / `san` / controls tail was clipped by `.card { overflow: hidden }` with no way
|
||||
to reach it), and the class on the served table and the served `table.data.scroll` rule are
|
||||
pinned **together** in `tests/test_api.py`, because the mechanism exists only while both name
|
||||
it (measured at 360 px and 1280 px by `data/logs/21-task03-scroll.cjs`).
|
||||
it (measured at 360 px and 1280 px by `data/logs/21-task03-scroll.cjs`). Since phase 27 task 01
|
||||
it is no longer the only thing that scrolls — at ≤640px every `table.data` does — and `.scroll`
|
||||
out-ranks that rule (two classes to one), so this table keeps its both-axis box and its 320 px
|
||||
height cap on a phone exactly as on a desktop (both halves measured at both widths by
|
||||
`data/logs/27-task01-scroll.cjs`).
|
||||
`app.js`'s masthead status dots are one per dashboard job —
|
||||
including `disks`, because the same agent can answer the 10-second utilisation poll and
|
||||
have a wedged SMART pass, and one dot for both would be right about neither. `app.js` owns the
|
||||
@@ -1229,7 +1233,7 @@ uv run python scripts/dev-certs-export.py --url http://127.0.0.1:8090 \
|
||||
# GET /api/certs/<name>/bundle and writes
|
||||
# data/certs/<name>.bundle at 0600 — mints nothing
|
||||
|
||||
uv run pytest # hermetic: 1545 passed + 15 skipped (11 live,
|
||||
uv run pytest # hermetic: 1549 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
|
||||
@@ -1582,6 +1586,54 @@ rules a removed panel made dead (`.grid.wide` went with the overview's fleet+eve
|
||||
component vocabulary the pages are still being converted to (`.hero-bar`, the grid and bar
|
||||
variants) stays even while only one page uses it.
|
||||
|
||||
**A wide table scrolls sideways inside its own card, and only inside its card** (phase 27 task 01,
|
||||
`TODO.md:7`). At ≤640px `table.data` is `display: block; overflow-x: auto` — no `max-height`,
|
||||
because `.scroll`'s 320 px cap is that modifier's vertical-use contract and a phone does not need
|
||||
a height-capped table on top of a horizontal one — and `.body.stack > *` carries `min-width: 0`,
|
||||
without which the first rule is inert: a card body is a one-column grid, a grid item's automatic
|
||||
minimum size is its content's min-content width, and every cell is `white-space: nowrap`, so a
|
||||
wide table grew the column to 520 px inside a 320 px card instead of overflowing its own box
|
||||
(measured on the live `/system` disclosures, where `scrollWidth === clientWidth` said there was
|
||||
nothing to scroll). One class rule reaches all seven tables — `#fleet-table`, `#agents-table`, the
|
||||
four `page-system.js` tables inside "More detail" and the NUT-variables table in `page-ups.js` —
|
||||
which is why no table carries a hand-placed `.scroll`; `tests/test_api.py` therefore pins the pair
|
||||
in the served ≤640px block, pins the all-widths `table.data`/`table.data.scroll` rules unchanged,
|
||||
and pins that every `<table>` on every served page and in every page script still carries `data`
|
||||
(a table outside the selector would clip again in silence). The scroll container is the table,
|
||||
never `main`: phase 18's "the document does not scroll sideways at a phone width" still holds, and
|
||||
`data/logs/27-task01-scroll.cjs` measures both halves at 360 px and 1280 px.
|
||||
|
||||
**Nothing is allowed to be painted past a card edge and eaten** (phase 27 task 03, the audit's
|
||||
findings). `.card { overflow: hidden }` is what makes three separate layout facts fatal, and the
|
||||
three fixes are one declaration each — all of them *at every width*, because a clipped fact is a
|
||||
bug at 1280 px too and each is inert where the content already fits:
|
||||
|
||||
- **`.card > header { flex-wrap: wrap }`** (+ `min-width: 0; max-width: 100%` on the header's `h3`
|
||||
and chips, `white-space: normal` on the chip). A header is a flex line of `nowrap` facts, and a
|
||||
line that cannot fit *shrinks* its one shrinkable child instead of wrapping: measured at 1280 px
|
||||
`/system`'s `.updated` stamp was squeezed to 67 px and rendered `updated` / `4s` on two torn
|
||||
lines, while at 360 px the OS/model chip pushed the stamp 18-191 px past the card so the age of
|
||||
every number disappeared. Wrapping costs lines (74 px instead of 62 px on a host header, 104 px
|
||||
on the `apc` card at 360 px) and costs no content. `.chip` itself keeps `white-space: nowrap` —
|
||||
this is an exception for header chips, not a change to the chip vocabulary.
|
||||
- **`.card > .body.stack > details { overflow-x: auto }`**. A disclosure is a stack item whose
|
||||
automatic minimum size is its content's min-content width, so one wide table grew the whole item
|
||||
to 520 px inside a 404 px card at 1280 px — and because the card clips, the GPU table's last
|
||||
column *and* the `charts-box` item stretched to the same 520 px were painted under the next card
|
||||
(tail right 554 px, next card at 438 px), reachable by no gesture. The disclosure is the
|
||||
scroller rather than the table because hoisting task 01's `display: block` would make a
|
||||
block-level table shrink-to-fit, un-stretching every table that fits its card.
|
||||
- task 01's `table.data` / `.body.stack > *` pair at ≤640px, unchanged.
|
||||
|
||||
The pair is pinned on served bytes by
|
||||
`tests/test_api.py::test_a_card_header_wraps_its_facts_instead_of_losing_its_tail` and
|
||||
`::test_a_card_disclosure_scrolls_what_it_reveals_at_every_width` (the second also demands no
|
||||
`max-height`/`overflow-y`/`overflow` shorthand — a height cap is `.scroll`'s contract — and that
|
||||
the rule is *not* inside the ≤640px block, since the defect it repairs is at the control width);
|
||||
`data/logs/27-task03-verify.cjs` measures both fixes and states what each one costs, and
|
||||
`data/logs/27-task03-control.cjs` re-creates every first-run offender with the declarations
|
||||
neutralised, so neither rule is decorative and the audit's reachability check is not vacuous.
|
||||
|
||||
A big number is `Dash.hero()` on `.hero` / `.hero-bar` (`dash.css`): 2.4rem tabular numerals,
|
||||
muted unit, mono `.sub` footnote (`Δ2.0s` on `/llm`'s rates since phase 23, "at the current load"
|
||||
on `/ups`), coloured by the payload's `severity` through the existing `.ok` / `.warn` / `.fail`
|
||||
@@ -2428,21 +2480,107 @@ memory plot; the `cpu.usage_pct@coreN` series is still stored and still queryabl
|
||||
this gap. What the fleet now *never* does is answer an agent over plain `http` or accept a
|
||||
bearer token on the certificate path — the TODO's "the client should never allow non-mtls
|
||||
communication" is the shipped state, and a new host has to be registered the same way.
|
||||
12. **The nav collapses on a phone; the page content below it does not** (phase 18's verification
|
||||
pass, 2026-10-20). At 360px `/`'s fleet table measures 450-510 px inside a 320 px
|
||||
`section.card`, and `.card { overflow: hidden }` clips the tail columns (`WORST DISK`,
|
||||
`GPU BUSY`, `TEMP`, `UP`) with no scroll container to reach them — `table.data` only scrolls
|
||||
with its `.scroll` modifier, which that table does not carry. Measured as pre-existing by
|
||||
serving the `git show HEAD:` bytes of both `dash.css` and `pages/index.html` over a live
|
||||
session: same clipping (a clipped card never un-clips when the document scrolls sideways).
|
||||
What phase 18 did change is that the document no longer scrolls sideways at a phone width at
|
||||
all — 360 px where it was 750 px — because the bar that overflowed its fixed 58 px row now
|
||||
breaks into rows, 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 — `21_page_cleanups`' `.scroll` on the `/certs` table is the same
|
||||
shape applied to one table and does not reach this one; `27_mobile_tables_and_audit` is that
|
||||
pass, still in `todo/`. Of the two operator TODO items that sat
|
||||
12. **Closed: the nav collapses on a phone, every table scrolls inside its own card, and no card
|
||||
edge eats a fact** (the clipping is phase 18's verification measurement of 2026-10-20; the
|
||||
phone half was closed by `27_mobile_tables_and_audit` task 01 and everything the audit found
|
||||
after it by task 03, both on 2026-10-21).
|
||||
What was wrong: at 360px `/`'s fleet table measured 450-510 px inside a 320 px `section.card`,
|
||||
and `.card { overflow: hidden }` clipped the tail columns (`WORST DISK`, `GPU BUSY`, `TEMP`,
|
||||
`UP`) with no scroll container to reach them — `table.data` scrolled only with its `.scroll`
|
||||
modifier, which that table does not carry. It was measured as pre-existing by serving the
|
||||
`git show HEAD:` bytes of both `dash.css` and `pages/index.html` over a live session: same
|
||||
clipping (a clipped card never un-clips when the document scrolls sideways). Phase 18 had
|
||||
already stopped the *document* scrolling sideways at a phone width — 360 px where it was
|
||||
750 px — by breaking the bar into rows, so the masthead was complete and only the table tail
|
||||
was out of reach.
|
||||
|
||||
**The phone half is closed**, and not by the shape gap 12 proposed (`.scroll` on the wide
|
||||
tables, or a per-host card layout): one ≤640px rule makes every `table.data` a horizontal
|
||||
scroll container inside the card, with no height cap, plus `min-width: 0` on the items of
|
||||
`.body.stack` — the second declaration is what the first one needed, because a card body is a
|
||||
one-column grid whose items may not be narrower than their `nowrap` content by default (before
|
||||
it, `/system`'s "More detail" tables measured 520 px in a 320 px card with
|
||||
`scrollWidth === clientWidth`: nothing to scroll, tail clipped). Pinned on the served
|
||||
stylesheet by `tests/test_api.py::test_every_table_data_scrolls_sideways_at_the_phone_breakpoint`
|
||||
and on every `<table>` still carrying `data`, and measured by `data/logs/27-task01-scroll.cjs`
|
||||
(+ `data/logs/27-task01-*.png`): `/`, `/system` and `/ups` reach their tail columns at 360 px,
|
||||
`/certs` keeps its `.scroll` box, no page scrolls sideways, and `table.data` is a plain table
|
||||
with no scrollbar at 1280 px.
|
||||
|
||||
Task 02 wrote that audit: `data/logs/27-task02-mobile-audit.cjs`
|
||||
(`NODE_PATH=<playwright node_modules> node …`, `DASH_BASE`/`DASH_PW`, transcript
|
||||
`27-task02-mobile-audit.out`, screenshots `data/logs/mobile-27-<page>-<width>.png`) sweeps
|
||||
**every page the app serves** — the seven signed-in ones plus `/login` unsigned, eight in
|
||||
total, not the nine the phase file counted — at 360 px and 1280 px, and per page demands: no
|
||||
sideways document scroll, no `table.data` clipped (its `overflow-x` must be `auto`/`scroll`
|
||||
when its content is wider, and its tail column measured reachable), no element past the
|
||||
viewport except inside a horizontal scroll container, the three-row masthead with its controls
|
||||
on screen and the dropdown under the whole bar, plus each expanded disclosure opened at **both**
|
||||
widths (`/system`'s "More detail", `/ups`'s NUT variables — a closed `<details>` still lays its
|
||||
content out in Chrome, so a table behind one is measured only once revealed) and phase 26's
|
||||
`/ups` two-card leg, whose `hidden` state is checked against the page's own
|
||||
`POWER_EVENT_KINDS`. Its first run (2026-10-21, live fleet; 280 PASS / 6 FAIL / 1 FINDING,
|
||||
identical on a re-run) is green on **every table claim at both widths** — task 01's rule holds,
|
||||
disclosures included — and its findings list is the pre-existing header defect: `.card > header`
|
||||
is `display: flex` with `nowrap` content, so at a 320 px card the header's tail is pushed past
|
||||
the card's content edge and clipped — the `.updated` age stamp on `/system` (right 388-415 px),
|
||||
`/llm` (374-393) and `/ups` (392-531) against a 360 px viewport, plus `.chip.model` (376) and
|
||||
`.chip.health` (463) on the `apc` card. The one FINDING is the control-width half of the table
|
||||
defect: all four of the opened host's `/system` "More detail" tables are 520 px inside a 404 px
|
||||
card at 1280 px. The script exits non-zero on any FAIL, and task 03 brought it to **exit 0** at
|
||||
both widths.
|
||||
|
||||
**Task 03 closed both findings**, with two CSS declarations and no markup change, and the
|
||||
audit's sweep came out stricter rather than weaker.
|
||||
|
||||
*The 6 FAILs — a card header's tail was eaten at a phone width.* `.card > header` was a single
|
||||
`nowrap` flex line, so the `.updated` age stamp stood 18-191 px past its card on `/system`,
|
||||
`/llm` and `/ups` and the `apc` card's `.chip.model` / `.chip.health` did too, all of it
|
||||
swallowed by `.card { overflow: hidden }`. The fix is the row, not the content:
|
||||
`flex-wrap: wrap` plus `min-width: 0; max-width: 100%` on the header's `h3` and chips and
|
||||
`white-space: normal` on a header chip (`.chip` elsewhere keeps `nowrap`). Wrapping costs
|
||||
lines, which is stated rather than hidden: a host header is 74 px instead of 62 px, the `apc`
|
||||
card's 104 px at 360 px, and at 320 px the 46-character model chip renders in full inside its
|
||||
own pill. What the fix also exposed is that "inert at the control width" was the wrong
|
||||
expectation: at 1280 px `/system`'s headers *did* move, because a nowrap line that cannot fit
|
||||
does not overflow, it **shrinks** its one shrinkable child, so `updated 5s` was rendering as
|
||||
`updated` / `5s` torn across two lines inside a 67 px box. `Range.getClientRects()` counts
|
||||
those line boxes, which is how `data/logs/27-task03-verify.cjs` separates a header that gained
|
||||
a line it needed (3 of 4 on `/system`) from one moved for nothing (0 on every other page).
|
||||
|
||||
*The 1 FINDING — the control-width half of the table defect, which was worse than clipped.* A
|
||||
disclosure is a `.body.stack` grid item whose automatic minimum size is its content's
|
||||
min-content width, so `/system`'s "More detail" grew to 520 px inside a 404 px card at 1280 px
|
||||
and the GPU table's last column — `97.8 GiB`, tail right 554 px against a card content edge of
|
||||
424 px — was painted **under the next card**, which starts at 438 px; the `charts-box` item
|
||||
beside it was stretched to the same 520 px, so a chart's newest edge went with it. One
|
||||
declaration fixes it: `.card > .body.stack > details { overflow-x: auto }` — the *disclosure*
|
||||
is the scroll container, because hoisting task 01's `display: block` to every width would make
|
||||
a block-level table shrink-to-fit and un-stretch every table that fits its card today
|
||||
(measured: the three tables that fit are 377 px and fill 377 px, the one that does not is 520
|
||||
px and scrolls, its tail landing at 411 px inside the card). It is deliberately **not** inside
|
||||
the ≤640px block, because the defect it repairs is at 1280 px.
|
||||
|
||||
*The audit changed shape too, and only upward.* Check (2b) had asserted geometry ("no table box
|
||||
past the card's content edge"), which is not what gap 12 is about and which a scroll container
|
||||
satisfies only by accident; both widths now assert **reachability** — a table standing past its
|
||||
card must have a scroll container between it and that card, and scrolling it must land the
|
||||
tail cell inside the card — and an unreachable spill FAILs at either width. Two new checks
|
||||
came with it: no card header's facts past the card (both widths; the viewport-only scan cannot
|
||||
see a 1280 px clip) and every table that fits still fills its container (the no-layout-shift
|
||||
half, measured rather than assumed). The final run over the live fleet is **332 PASS /
|
||||
0 FAIL / 0 FINDING** (9 skips, each one a check that width cannot answer — the clock, hidden
|
||||
below 900 px) on all eight served pages at both widths, exit 0
|
||||
(`data/logs/27-task02-mobile-audit.out`, screenshots `mobile-27-*.png`; the first run is kept
|
||||
as `27-task02-first-run.out`), and `data/logs/27-task03-control.cjs` proves the new check is
|
||||
not vacuous by neutralising both declarations and re-creating the whole first-run findings list
|
||||
(3/4/2 header offenders at 360 px, 8 stranded disclosure tables at 1280 px). The served-byte
|
||||
pins are `test_a_card_header_wraps_its_facts_instead_of_losing_its_tail` and
|
||||
`test_a_card_disclosure_scrolls_what_it_reveals_at_every_width`, beside task 01's
|
||||
`test_every_table_data_scrolls_sideways_at_the_phone_breakpoint` and
|
||||
`test_every_table_on_every_page_carries_the_class_the_rule_targets`.
|
||||
|
||||
Of the two operator TODO items that sat
|
||||
beside it, `20_masthead_nav_polish` has landed in full (now complete): the bar is three
|
||||
rows at every width (flush right on the two control rows since phase 25 task 02), it is no
|
||||
longer sticky, the phone menu slides, and both
|
||||
|
||||
Reference in New Issue
Block a user