Skip to content

Overlay chrome for the 1.0.0 card: run label, More menu, status row - #23

Merged
Harrys76 merged 7 commits into
mainfrom
wp28/overlay-chrome
Oct 26, 2026
Merged

Harrys76 merged 7 commits into
mainfrom
wp28/overlay-chrome

Conversation

@Harrys76

@Harrys76 Harrys76 commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Third PR of the 1.0.0 overlay stack, on top of #22. It changes the card's chrome: the header, the More menu, the status row and the card width. The issue cards themselves change in the next PR. No detection rule changes.

What changes

  • Run label. The header shows "Debug · VM" or "Profile · no VM" in place of the VM+, FRAME and DBG badges. It steps down by measuring its text with the chrome-clamped text scaler: without the "Sleuth" name, then the mode only, then the dot only. The dot is filled with the VM and a ring without it, so the state does not rest on colour. A screen reader hears "Debug run, no VM link" at every size.

  • More menu. A More button replaces the theme button. It opens a panel inside the card over a scrim, because the overlay sits outside the app's Navigator. The rows, in order:

    • Startup (TTFF);
    • Rebuild stats (the route's count, opening the drilldown);
    • Hidden issues (with a count, absent at 0);
    • Encyclopedia;
    • Guide;
    • Copy snapshot;
    • Theme as Auto, Light and Dark segments.

    Empty rows read "Not measured yet" and "No counts yet" and are disabled. Values are read once on open, and the panel is built only while it is open.

  • Closing, focus and semantics.

    • Back and Escape close a page, then the menu, then the card. Escape reaches the menu even when an app text field had focus before it opened.
    • A tap on the scrim closes the menu and is consumed. A tap on the app closes it and still reaches the app, through a global pointer route that exists only while the menu is open. A header drag, a resize start, minimize and maximize also close it.
    • While it is open, SleuthOverlay blocks the app's semantics and the card body sits in ExcludeSemantics.
    • Opening moves keyboard focus to the first row that can act and screen reader focus to the panel. Closing returns both to More. A page opened from the menu gives keyboard focus back to the app node that had it, else to More, and screen reader focus to More.
    • More, the rows and the theme segments draw a 2 px checkboxActive outline while the keyboard drives focus (SleuthFocusInkWell). On iOS the selected theme segment carries the selected flag, as RawRadio does.
    • Tab, Shift+Tab, Enter and Space work in the card and its pages. The overlay sits above the app's WidgetsApp, so its default shortcuts never reached the overlay. The card's key handler, which already handled Escape, now moves focus and invokes ActivateIntent while focus is in the card. It leaves the keys to a WidgetsApp when one hosts the card, and leaves Enter and Space to a focused text field.
  • Removed from the front: the footer, the startup banner, the rebuild stats banner, the confidence caption and the frame-rate detail row.

  • Status row. The severity chips lead it ("2 critical", a filled icon while on, an outlined one and a textQuaternary outline while off), with the frame rate at the end. The row keeps one line and its chips scroll sideways when they do not fit. "Showing X of Y" appears only under a severity filter, as a live region, with Show all; Show all and Reset move screen reader focus to the first chip. Hidden cards are counted in the menu.

  • Width and grip. The card is at least 320 px wide, capped at the safe-area width less 16 px margins. Stored widths below that are raised at layout and on commit. Narrower is not offered at the minimum, nor Wider at the maximum. A maximized card draws only the grip's dots, under IgnorePointer, and the list ends with 48 px of padding.

  • Theme. cardBackground is opaque in every preset and in fromColorScheme. New cardDivider token: dark 0xFF4B5563, light 0xFFB4BAC4, border in the high-contrast presets, outlineVariant from a scheme. Badge tokens the overlay no longer reads are documented as kept for custom themes.

  • Copy snapshot failure offers Try again.

  • Denylist. TweenAnimationBuilder (no longer used by the overlay) leaves the framework section, so an app's own uses count. A new audit test fails if the framework section grows past its 52 entries.

Different from the plan

  • The header has a fourth step, the dot alone, for when even the short label does not fit (five header buttons at 320 px with large text).
  • The theme segments are InkWells rather than GestureDetectors, so they take keyboard focus like the other rows.
  • The focus outline is new. Both design audits found that an InkWell's own focus fill comes from ThemeData.fallback() above the app's MaterialApp and measures 1.04:1 on the dark card. Only the controls that take keyboard focus draw it: header buttons other than More and the chips are touch targets without focus, as before.
  • The guide entries for what this PR removes (footer, Export button, theme button, summary bar, rebuild banner) are rewritten here, with one line of the capture procedure and the rebuild demo text, so the guide does not describe missing controls until the docs PR. The rest of the guide rewrite stays in step 18.
  • Build-cost benchmark. Real scans cleared the seeded issues during the run, and the 800 x 600 surface left the list too little room to expand three cards. It now runs without scans on a 390 x 844 view. On this harness the PR2 card builds 5 issue cards per title change and this one builds 6, because the smaller chrome leaves the list room for one more row. Card builds are 0 in every case. Budgets: 0/0, 0/300, 0/0, 0/300.

Verification

  • fvm flutter analyze: 0 issues. Format check clean. fvm dart doc --dry-run: 0 warnings.
  • fvm flutter test --exclude-tags benchmark: 4,865 passed.
  • Benchmarks (--tags benchmark --concurrency=1): 44 passed.
  • Floor lane on Flutter 3.32.8: analyze clean, 4,865 passed.
  • Example app tests: 348 passed. Sidecar: analyze clean, 653 passed.
  • New tests cover:
    • the menu rows and their empty states;
    • back and Escape order, including an app text field focused before opening;
    • scrim and app taps;
    • keyboard and screen reader focus on open and close, Tab, Shift+Tab and Enter through SleuthOverlay above the app's MaterialApp, Space left to an overlay text field, the outline in keyboard mode only;
    • app semantics blocked while open;
    • the theme segments, including the iOS selected flag;
    • Copy snapshot's Try again;
    • the header step-down at 1.0x, 1.3x and 2.0x, and again after a theme change while open;
    • the run label dot;
    • the 320 px minimum on 320 and 300 px screens;
    • drags through a maximized grip;
    • the live "Showing" line and Show all focus;
    • contrast rows for the off-chip outline, the run label dots and the focus outline;
    • pinned cardDivider ratios.

Review

Codex reviewed the branch four times.

  • The first round found two defects, fixed in ee63046:
    • Tab and Enter had no shortcuts in the overlay, since it sits above the app's WidgetsApp. The tests that hosted the card in a MaterialApp hid it. The new tests drive the keys through SleuthOverlay.
    • The header's measured fit was cached without the theme, so a larger label font from Sleuth.updateTheme kept the old step and could overflow.
  • The second round found one, fixed in e4c945b. With a severity off, a scan that removed that severity's last issue flipped the "Showing X of Y" row, and the list was rebuilt from scratch, losing its scroll offset and card state. The list now keeps its place in the tree. The regression test fails without the fix.
  • The third round found two, fixed in 9fccb8e. Each regression test fails without its fix:
    • The overlay recorded the app's focused node only at the first menu opening, so after a tap on another app field closed the menu, closing the dashboard later focused the old field. Each opening now records the node focused then.
    • A 220 px width stored by an older release showed at 320 px, but the first Wider step computed from 220 and changed nothing. A resize now starts from the stored width clamped to the current limits.
  • The fourth round found no issues.

Not in this PR

  • The device parity walk (step 10) ran on the finished stack; 1.0.0 release prep: docs, sidecar 0.10.0, CHANGELOG #28 has the results.
  • The new collapsed and expanded issue cards come in the next PR.
  • A focus outline for the header's other buttons, the chips and the issue cards would need those controls to take keyboard focus first. They do not today; left for a later decision.

Fixes from the iPhone walk

  • The last chip with cards stays on. OK has no chip without OK cards, but it stayed in the filter, so both visible chips could be turned off and the list emptied. Turning a chip off is refused when it would leave no card; effects that surface once their cause's severity is off count. The "No issues match the severity filter" state is still reachable when the filtered cards go away.
  • Frame rate node. The frame rate's Semantics was not a container, so its label landed on the overlay's full-screen node and VoiceOver read a screen-sized "60 frames per second". It is now its own node.
  • Rebuild stats "Off". In debug runs, and in profile runs without enableDeepDebugInstrumentation, the row said "No counts yet" forever. It reads "Off" there (SleuthController.countsRouteRebuilds).

Doc comments (doc audit follow-up)

The theme switch moved from the header to the More menu (Auto, Light, Dark), but the public API docs still described a header toggle: the library doc, Sleuth.updateTheme, SleuthThemeMode, OverlayUiState.themeMode and SleuthThemeData.brightness, plus three comments in SleuthController and one in SleuthOverlay. They now name Theme in the More menu. The library's feature list also promised a live FPS chart the overlay does not draw; it now says the frame rate.

🤖 Generated with Claude Code

Harrys76 and others added 6 commits October 26, 2026 23:29
The header shows a run label ("Debug · VM", "Profile · no VM") in place
of the VM+, FRAME and DBG badges, and steps down by measuring its text:
without the "Sleuth" name, then the mode only, then the dot, which is
filled with the VM and a ring without it.

A More button replaces the theme button. Its panel inside the card holds
Startup, Rebuild stats, Hidden issues, Encyclopedia, Guide, Copy snapshot
and the Auto/Light/Dark theme segments, with values read once when it
opens. Back and Escape close a page, then the menu, then the card; a tap
on the scrim or the app closes it; the app and the card body are hidden
from screen readers while it is open. Keyboard focus moves to the first
row that can act and returns to More; the controls that take keyboard
focus draw an outline in keyboard mode.

The footer, the startup and rebuild stats banners, the confidence caption
and the frame-rate detail row are gone. The severity chips lead the
status row, with the frame rate at its end, and "Showing X of Y" follows
the severity filter only. The card is at least 320 px wide (less on
narrow screens), its background is opaque, and a new cardDivider token
draws the line under the status row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…view

Real scans cleared the seeded issues during the run, and the 800 x 600
surface left the list too little room to expand three cards. The
benchmark now runs with no scan, on a 390 x 844 view, and reveals each
card before tapping it. On this harness the previous card built 5 issue
cards per title change and the new one builds 6, because the smaller
chrome leaves the list room for one more row; the budgets are recorded
from the new card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The overlay sits above the app's WidgetsApp, so the default shortcuts
for Tab, Shift+Tab, Enter and Space never reached it: focus moved into
the More menu but could not move on or activate a row. The card's key
handler now moves focus and invokes ActivateIntent while focus is in the
card or its pages, leaves the keys to a WidgetsApp when one hosts the
card, and leaves Enter and Space to a focused text field.

The header's measured fit was cached by width, scale and run state only,
so a theme change through Sleuth.updateTheme that grew the label font
kept the old step and could overflow. The cache key now includes the
theme, the text scaler and the inherited text style.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a severity off, a scan that adds or removes that severity's last
issue flips the "Showing X of Y" row while the shown cards stay the
same. The list switched between standing alone and sitting in a Column
under the row, so it was rebuilt from scratch: the scroll offset and
each card's state were lost. The list now always sits in the Column,
keyed, and only the row is inserted or removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he shown width

The overlay recorded the app's focused node only at the first menu
opening, so after a tap on another app field closed the menu, reopening
it kept the old node and closing the dashboard focused that one. Each
opening now records the app node focused at that moment; with focus
already in the overlay, the earlier node stays.

A width stored by an older release (220 px) shows at the 320 px minimum
but stayed 220 until the next commit, so the first Wider step or drag
computed from 220 and changed nothing. A resize now starts from the
stored width clamped to the current minimum and maximum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From an iPhone walk of the 1.0.0 overlay:

- Both visible severity chips could be turned off. OK has no chip
  without OK cards but stayed in the filter, so the list emptied while
  every chip read off. Turning a chip off is now refused when it would
  leave no card. Effects that surface once their cause's severity is off
  count as cards.
- The frame rate's Semantics was not a container, so its label landed
  on the overlay's full-screen node and a screen reader read a
  screen-sized "60 frames per second". It is now its own node.
- Rebuild stats read "No counts yet" in runs that never count rebuilds
  per route: debug runs, and profile runs without
  enableDeepDebugInstrumentation. It reads "Off" there
  (SleuthController.countsRouteRebuilds).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Harrys76
Harrys76 force-pushed the wp28/overlay-chrome branch from af9ac70 to 15ca6a5 Compare October 26, 2026 16:45
The theme switch moved from the header to the More menu, where it reads
Auto, Light and Dark, but the library doc, Sleuth.updateTheme,
SleuthThemeMode, OverlayUiState.themeMode and SleuthThemeData.brightness
still described a header toggle. The library's feature list also
promised a live FPS chart the overlay does not draw; it shows the frame
rate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from wp27/overlay-data to main October 26, 2026 19:02
@Harrys76
Harrys76 merged commit 96e07ed into main Oct 26, 2026
5 checks passed
@Harrys76
Harrys76 deleted the wp28/overlay-chrome branch October 26, 2026 19:02
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.

1 participant