Skip to content

fix(dashboard): keep the metrics readout's toggle on screen with no frame - #8105

Merged
bolichen97 merged 1 commit into
mainfrom
fix/topbar-metrics-toggle-7967
Sep 3, 2026
Merged

fix(dashboard): keep the metrics readout's toggle on screen with no frame#8105
bolichen97 merged 1 commit into
mainfrom
fix/topbar-metrics-toggle-7967

Conversation

@iamwhatever

@iamwhatever iamwhatever commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Problem / Motivation

Two of the three symptoms in #7967 on the macOS desktop app. The CSS half — the
collapse ladder blanking the capsule at narrow widths — was repaired by #7851; these
two were explicitly left standing.

  1. "the cpu, metrics etc. doesn't open" — clicking the CPU/MEM/DSK readout does
    nothing, because the control the click is aimed at is not on screen.
  2. "has something left in the background" — a stale blurred region in the top bar
    after the capsule's contents change.

Why it matters

The readout is the top bar's only surface for host pressure, and the toggle is its only
control. On a host where /api/system is slow or never produces a frame — the reporter's
diagnostics say kiro-cli: unavailable — the readout is stuck in a state the user cannot
see or leave: the preference says "show the readings", nothing renders, and there is no
button to press to turn it back off. It survives a reload, because the preference is
persisted.

What changed (motivation → approach → change)

Symptom 1 — the toggle disappears

The metrics readout is an inline capsule segment, not a popover: one button both shows
the readings and toggles them away. Its open branch pushed a segment only when the query
had ERRORED:

} else if (!sysMetrics) {
  if (sysMetricsError) segments.push(<button …/>)   // ← and no else
}

sysMetrics is undefined for the whole of the first fetch and for the retry window of
a failing one, because react-query sets isError only once its retries are spent. In that
third state — open, no frame, no error — the branch pushed nothing, the button left the DOM,
and the click landed on the capsule's background.

Every open state now pushes a toggle. The pending one carries an em dash per metric rather
than a spinner, which is the distinction the sibling usage segment already draws in this
same capsule
, for the same reason: a spinner asserts a fetch is about to land, and on a
host that never reports metrics that claim never comes true. It reuses the loaded branch's
own "no valid reading" glyph, so the two open states differ in opacity, not in shape. The
error segment is untouched.

A resolved-but-empty frame is deliberately not one of these states: {} is truthy, so it
already reaches the loaded branch and renders its dashes there. The gap was only ever the
undefined frame.

Symptom 2 — the stale backdrop

.topbar-glass carries backdrop-filter, and the two readout groups inside it carry
container-type: inline-size, which implies layout containment. When a capsule segment
mounted or unmounted inside one, the strip it vacated had to be re-sampled through that
blur — and damage from a contained subtree did not always reach a backdrop sharing a layer
with the content beneath it, so the old blurred pixels stayed on screen.

The glass is now promoted to its own compositing layer. That is the same remedy #7931
applied to the sandbox-doc frames, and the reason the notification popover portals to
<body> to escape this containing block rather than living inside it. Two spellings, on
purpose:
focus mode drives this element's transform from an inline style that wins over
the rule, so translateZ(0) is absent for exactly the case that animates hardest;
backface-visibility:hidden promotes on its own and nothing writes it.

The two halves meet

The first fix also removes the segment mount the second one is sensitive to. The capsule
used to gain a button and a divider when the frame landed; it now only re-renders text
inside a button that was already there.

No rung of the ladder #7851 repaired is touched. The capture script asserts the actions
group's content box — what the container queries measure — is identical across the absent,
pending and loaded scenes, and that nothing outside the capsule moves when the frame lands.

Tests

website/src/test/App.topbarMetricsPending.test.tsx (3)

  • renders a clickable toggle while no frame has arrivedapi.system never resolves, so
    sysMetrics stays undefined and isError stays false. Asserts the toggle exists, reports
    aria-pressed="true" (distinguishing it from the closed state's identically-named button),
    and still closes the readout and persists mc-topbar-metrics=0. Red before: Unable to
    find an accessible element with the role "button" and name /System metrics/.
  • holds one continuous toggle across the frame arriving — resolves a deferred frame
    mid-test and asserts the same pressed control is there before (dashes, no digits) and after
    (digits). Rules out a fix that swaps one disappearance for another. Red before.
  • still renders the error segment when the fetch fails — regression guard on the branch
    that already worked. Green before and after.

website/src/test/topbarGlassLayer.test.ts (3) — the promotion is asserted from the
stylesheet source, because jsdom does not load index.css and a computed-style assertion
would be vacuous (same reason as the existing topbarBadgeOverhang.test.ts). Pins both
spellings, and pins that containment stayed on the groups the ladder measures. First two
red before.

An earlier draft of the first file asserted a resolved-but-empty frame also lost the toggle.
It passed on base — {} is truthy — so the premise was wrong and it was replaced by the
continuity test above rather than kept as a vacuous guard.

Manual verification

website/scripts/capture-topbar-metrics-pending.mjs — new, extending the shared top-bar
capture entry with ?metrics=pending and ?metricsfix=off scenes. Run at two widths, one
either side of the base @container (max-width:530px) metrics rung. All assertions pass:

1900: before state has no metrics segment (the defect)
1900: capsule child count identical pending and loaded (5) — nothing mounts on arrival
1900: before state is short by 2 child/children (segment + divider)
1900: pending segment is not wider than loaded (127.4 <= 160.5px)
1900: actions-group content width identical across all three scenes
1900: 352 pixels differ before vs after (4160 raw incl. backdrop noise)
1900: 622 pixels differ pending vs loaded, all inside the capsule — no sibling moved
1280: … same, on the icon-collapsed rung

Local gates: tsc --noEmit clean · eslint src/ --max-warnings 597 clean (0 on changed
files) · npm run i18n:check exit 0 (no new keys — the pending state composes from
app.cpu/app.mem/app.dsk/app.system_metrics/app.click_to_hide) · full vitest
28242 passed / 1 failed, the failure being ArtifactBody.iframeBlob.test.tsx, which
passes in isolation on this branch and on main and sits in a path this diff cannot reach.

Still outstanding: a macOS reproduction of the artifact. See the scope note below.

Screenshots / video

The metrics segment across the three states, at 1900px where the readings themselves render
(cropped to the right of the header):

Before — open readout, no frame: no segment at all. The waveform toggle is simply gone.

capsule with no metrics segment

After — open readout, no frame: dimmed CPU — MEM — DSK —, clickable.

capsule with em-dash metrics segment

Frame landed — same button, real readings. No segment mounted; the text changed.

capsule with real metrics readings

Full-width headers, and the icon-collapsed rung at 1280px

The 1280px pair is the form the reporter's 243px crop was in: below the 530px group rung the
readings are hidden and the segment is the bare accent glyph — which is where a missing toggle
is least recoverable, because there is no text left to click either.

before
after

Full-width 1900px renders: before ·
after ·
loaded

The 12.2万/1万 counter and the neighbouring pill render missing-glyph boxes in these shots:
the capture entry hardcodes zh-CN strings and this Linux capture host has no CJK font. Not a
product defect and not touched by this diff.

What these shots cannot show: the background artifact. A promoted compositing layer and
an unpromoted one render the same correct pixels — the defect is a stale raster the
compositor fails to invalidate, so no screenshot of a correct render distinguishes the two.
What the shots and the capture assertions do establish is that the promotion changes nothing
about the header's geometry or appearance.

Related Issues

Closes #7967

Pattern harvest

Rule candidate: review-prompt
Pattern: a control that renders only in the states its data has, so the state with no
data has no control — and the surface it toggles cannot be dismissed.

This is the falsy-collapse family the sibling usage segment in this same capsule was already
hardened against (kiroUsageState distinguishes failed, api-key, warming and loaded, and
its comments say why). The generalizable rule is narrower than "handle loading states": when a
control is the ONLY affordance for leaving a persisted open state, its render must be
unconditional on the data, because the failure is not a missing readout but an unreachable
toggle. Worth a review prompt asking, of any conditionally-rendered toggle, what closes this
if the condition is false?

The compositing half is a second instance of #7931's class (backdrop-filter /
container-type containment stranding a backdrop) rather than a new pattern, and #7931
already filed the frames it did not cover as #8037.

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable) — capture entry's own param docs
  • No secrets, credentials, or internal references in the diff

…rame

The capsule's metrics readout is an inline segment, not a popover: one button
both shows the readings and toggles them away. Its open branch pushed a segment
only when the system-metrics query had ERRORED, so the third state -- open, no
frame, no error -- pushed nothing at all. `sysMetrics` is undefined for the whole
of the first fetch AND for the retry window of a failing one, because react-query
reports `isError` only once its retries are spent. On a host where /api/system
hangs or never produces a frame the readout was therefore logically open with its
toggle absent from the DOM, and the click aimed at it landed on the capsule's
background: the reported "the cpu, metrics etc. doesn't open".

Every open state now pushes a toggle. The pending one carries an em dash per
metric rather than a spinner, which is the distinction the sibling usage segment
already draws in this same capsule and for the same reason -- a spinner asserts a
fetch is about to land, and on a host that never reports metrics that claim never
comes true. It reuses the loaded branch's own "no valid reading" glyph, so the two
open states differ in opacity rather than in shape.

Second half, for the "something left in the background" artifact. The header
carries `backdrop-filter`, and the two readout groups inside it carry
`container-type: inline-size`, which implies layout containment. When a capsule
segment mounted or unmounted inside one, the strip it vacated had to be re-sampled
through that blur, and damage from a contained subtree did not always reach a
backdrop sharing a layer with the content beneath it -- the old blurred pixels
stayed on screen. `.topbar-glass` is now promoted to its own compositing layer,
the same remedy #7931 applied to the sandbox-doc frames, and the reason the
notification popover portals to <body> to escape this containing block rather
than living inside it. Two spellings, because focus mode drives this element's
`transform` from an inline style that wins over the rule:
`backface-visibility:hidden` promotes on its own and nothing writes it.

The two halves meet: the first one also removes the segment MOUNT the second one
is sensitive to. The capsule used to gain a button and a divider when the frame
landed; it now only re-renders text inside a button that was already there.

No rung in the collapse ladder #7851 repaired is touched. The capture script
asserts the actions group's content box -- what the container queries measure --
is byte-identical across the absent, pending and loaded scenes, and that nothing
outside the capsule moves when the frame arrives.

Scope is the two symptoms #7851 did not cover. The artifact half states the
mechanism it fixes rather than claiming the whole of the report: a promoted layer
and an unpromoted one render the same correct pixels, so no screenshot from this
Linux host can distinguish them, and the reporter's macOS reproduction is
outstanding. Its contract is pinned from the stylesheet source instead.

Closes #7967
@iamwhatever
iamwhatever requested a review from a team September 3, 2026 07:23
@iamwhatever
iamwhatever requested a review from a team as a code owner September 3, 2026 07:23
@iamwhatever
iamwhatever requested a review from patrigao September 3, 2026 07:23
@github-actions github-actions Bot added the readiness: checking Automated validation is still running label Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of 80efedc4a514c82e8d721a369b6531b226b92b9b and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 80efedc

False positive or not applicable? A repository writer can comment:
/ai-review override gpt 80efedc4a514c82e8d721a369b6531b226b92b9b: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of 80efedc4a514c82e8d721a369b6531b226b92b9b — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Root-cause fix — control presence now keyed to the open state, not the fetch state — with precedent-backed compositing remedy and regression pins for both halves.

[DESIGN-REVIEWED] 80efedc

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

UX-level review of 80efedc4a514c82e8d721a369b6531b226b92b9b — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

UX-Verdict: PASS

The pending readout reuses the capsule's own dash-and-dim vocabulary, keeps its toggle alive, and every state now names itself to hover and AT.

Suggestions

  • Pending button title is only app.system_metrics — app.click_to_hide; the dimmed dashes never say why. Add a short "waiting for readings" hint there, mirroring how the stale state explains itself via app.metrics_are_stale_latest_fetch_failed.

[UX-REVIEWED] 80efedc

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of 80efedc4a514c82e8d721a369b6531b226b92b9b — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

All checks are done. Every item traces to the reported defect, the conventions hold (temp-screenshots/ has 704+ committed PNGs from prior PRs, the capture entry's per-fix URL params are a documented pattern in that file), and the one container-type selector in the stylesheet sits entirely inside the promoted glass, so no sibling of the root cause is left unfixed.

First-Principles-Verdict: PASS

Both reported symptoms are fixed at the branch and the element that produced them, and every rider is declared, conventional evidence.

What this change ships

Intent: let a user whose host never produces a metrics frame see and close the open readout, and stop the top bar leaving stale blur — a FIX, and the diff does exactly the job the description claims.

  1. Open readout with no frame now shows a dimmed, clickable CPU — MEM — DSK — toggle — justified (reported defect [bug] Top Bar Broken #7967)
  2. Top bar glass no longer strands stale blurred pixels when capsule contents change — justified (second reported symptom)
  3. Frame arrival now re-renders text instead of mounting a segment — justified (removes the mount the blur bug is sensitive to)
  4. Three unit tests pinning the pending toggle and its continuity — justified
  5. Three stylesheet-source tests pinning the layer promotion — justified
  6. New capture script with pixel/box assertions — justified, matches ~dozens of sibling capture-*.mjs
  7. ?metrics= / ?metricsfix= params on the shared capture entry — declared; one consumer each, but the entry's documented per-fix pattern (?fix, ?budget)
  8. Nine PNGs + README.txt committed under temp-screenshots/ — declared; established convention (704+ PNGs already committed there)

Depth checks that hold: the pending-state fix sits at the cause (the branch that pushed nothing), not a guard at the click site; the glass promotion's true cause is a compositor behavior outside this codebase, and the single container-type selector in index.css (line 1774) lives inside the promoted element, so zero siblings remain; the mobile readout renders only when a frame exists and carries no toggle, so the defect has no mobile sibling. The diffPngs helper is a second copy of the one in capture-topbar-badge-overhang.mjs (2 copies counted), but these are self-contained one-shot evidence scripts and the sibling exports nothing — no existing symbol to point at.

[FIRST-PRINCIPLES-REVIEWED] 80efedc

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed 80efedc4a514c82e8d721a369b6531b226b92b9b — this comment is updated in place on each push.

Review details

This is a small, well-scoped frontend PR: it adds a pending-state toggle to the metrics readout in App.tsx, promotes .topbar-glass to its own compositing layer in index.css, and adds tests plus a dev capture script and screenshots.

The candidate list contains no candidates, so there is nothing to falsify in Step 1. Independently checking the one non-trivial risk (Step 2): the transform:translateZ(0) on .topbar-glass establishes a containing block for position:fixed descendants — but the rule already carries a non-none backdrop-filter, which per spec already establishes that same containing block, so the added transform changes nothing for fixed descendants. No observable wrong outcome is groundable. The App.tsx pending branch is added alongside (not in place of) the error branch, reuses key="metrics", mirrors the loaded branch's sr-only naming and persistence, and toggles identically. No grounded defect at confidence 80+.

No findings.

[OPUS-REVIEWED] 80efedc

Verdict parsed from the review's SHA-scoped output markers for commit 80efedc4a514c82e8d721a369b6531b226b92b9b.

False positive or not applicable? A repository writer can comment:
/ai-review override fable 80efedc4a514c82e8d721a369b6531b226b92b9b: <one-sentence reason>

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 3, 2026
@bolichen97
bolichen97 enabled auto-merge (squash) September 3, 2026 09:22

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving on the strength of a full readiness audit of every open PR against main, not a
line-by-line reading of this diff — recording that plainly so the next reader knows what this
stamp does and does not cover.

Verified against this exact head SHA:

  • readiness: passed present, and PR Readiness — the one required status context on main
    (ruleset protected-branches) — is success on this head.
  • No check run on this head is failure, cancelled, timed_out or still in flight. Skipped
    jobs are path-filtered conditionals, none of them required.
  • mergeable: true, and the head is not far enough behind main for its green CI to describe a
    base that no longer exists.
  • No surviving reviewer CHANGES_REQUESTED: any such review is on an older commit and therefore
    already dismissed by dismiss_stale_reviews_on_push.
  • Every issue comment, inline review comment and review thread was read and classified. Nothing
    left is an unresolved human change request — the remainder is bot review-lane output, resolved
    or outdated threads, explicitly non-blocking suggestions, and author status notes.

Auto-merge (squash) is armed, so this lands once every other ruleset requirement is met.

@bolichen97
bolichen97 merged commit cda2c5b into main Sep 3, 2026
70 of 73 checks passed
@bolichen97
bolichen97 deleted the fix/topbar-metrics-toggle-7967 branch September 3, 2026 09:26
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Top Bar Broken

2 participants