# Overview Scroll Stutter Investigation Status: draft context for external review Date: 2026-06-20 Related issue: https://github.com/steipete/CodexBar/issues/1674 Candidate PRs: - Lite row: https://github.com/steipete/CodexBar/pull/1675 - Rich row: https://github.com/steipete/CodexBar/pull/1676 ## Purpose This document explains the motivation, evidence, design split, current PR state, and open review questions for the CodexBar Overview menu scroll-stutter work. It is written as a handoff document for a second reviewer, especially Claude, to review both the reasoning and the patch directions without needing to reconstruct the whole GitHub thread. The core question is not only "does either patch compile?" The real question is whether we chose the right boundary for reducing scroll-time work inside an `NSMenu` that hosts rich SwiftUI rows. ## Original Problem The user-reported symptom is severe scroll jank in the CodexBar home / Overview menu. The problem appears on recent CodexBar builds and on multiple remote latest versions, not just one local install. Observed environment from the local sample: ```text CodexBar 0.37.0 (90) macOS 27.0 (26A5353q) Main-thread sample duration: 8 seconds, 1 ms interval Main-thread sample count: 3440 Physical footprint: 211.4M, peak 255.3M ``` The user described the behavior as "每次滚动的时候都会卡" while the Overview tab has multiple providers enabled. The screenshot shows the Overview tab with rich provider cards, usage bars, quota sections, cost/token history, and multiple provider entries below. ## Evidence From The Sample The redacted sample summary points at menu tracking and SwiftUI row rendering/layout, not provider refresh or token-cost scanning. Relevant sample counts: ```text 2222 -[NSMenuTrackingSession startRunningMenuEventLoop:] 91 -[NSContextMenuImpl _reloadData] 19 -[NSContextMenuImpl _menuBackingViewDidChangeIntrinsicSizeWithAnimation:] 23 ViewGraphRootValueUpdater.render 12 NSHostingView.hitTest 9 LazyVGridLayout lengthAndSpacing 6 -[NSView scrollWheel:] ``` The important interpretation is that scroll input seems to stay inside the `NSMenuTrackingSession` hot path, where AppKit repeatedly re-enters hosted SwiftUI row layout/render/hit-test work. That fits the visible symptom: jank occurs on every scroll event, even without a provider refresh being initiated. Two caveats a reviewer should keep in mind about how strong this sample is: - The `2222 startRunningMenuEventLoop` frame is the **parent** frame of all menu-tracking activity (including idle waiting), not a leaf hotspot. The meaningful work is in the much smaller leaf counts (`_reloadData 91`, `render 23`, `hitTest 12`, `LazyVGridLayout 9`). Relative to 3440 total samples, the main thread is not pegged continuously, so the jank is more consistent with **bursty per-scroll-event frame hitches** than sustained CPU saturation. This is suggestive, not conclusive — an Instruments time-profiler trace correlating scroll events with dropped frames would be the stronger proof (see "What Is Not Proven"). - Reading `StatusItemController+OverviewScroll.swift` makes the likely mechanism concrete. `postOverviewScrollNavigation` does two things per highlight step: it calls `self.menu(menu, willHighlight: target)` to advance highlight state immediately, **and then** posts a synthetic `.mouseMoved` event over the target row's center. On `main`, the highlight-state flip re-renders the full SwiftUI row through `MenuCardSectionContainerView` (`.environment(\.menuItemHighlighted)` + `.foregroundStyle(primary(highlighted))` + a conditional background), and the synthetic mouse-move makes AppKit re-run hit-testing down into the hosted SwiftUI tree (`NSHostingView.hitTest`). So each scroll step costs roughly **two full rich-row re-renders (old + new highlight) plus a hit-test descent**, and a single flick emits up to three steps. That double-re-render-per-step is the most plausible source of the stutter, and it is exactly the link both PRs cut at different points. ## Source Mapping The sample stack maps cleanly onto current `origin/main` around commit `8c4bdd63f3d6d1432fcdb50add7ed6988a2b5734`. Key source paths: - `Sources/CodexBar/StatusItemController+Menu.swift` - `addOverviewRows` builds each Overview provider row as a custom menu card. - It installs provider-detail submenus and click handling. - `Sources/CodexBar/StatusItemController+MenuTypes.swift` - `OverviewMenuCardRowView` is the SwiftUI view rendered inside each Overview row. - It subscribes to menu highlight environment. - `Sources/CodexBar/MenuCardView.swift` - `UsageMenuCardUsageSectionView` renders usage content. - It resolves live menu-card models through `MenuCardRefreshMonitor`. - `Sources/CodexBar/InlineUsageDashboardContent.swift` - Uses `LazyVGrid` and mini chart/bar content. - This matches the `LazyVGridLayout lengthAndSpacing` sample fragment. - `Sources/CodexBar/StatusItemController+OverviewScroll.swift` - Handles scroll-wheel navigation on Overview rows. - Moves highlight by calling menu highlight paths and posting synthetic mouse movement over row views. This led to the working hypothesis: > The Overview tab keeps several rich SwiftUI provider cards inside an `NSMenu`; scroll/highlight/hit-test re-enters layout and rendering for those hosted rows. The hot path is row presentation and menu tracking, not provider data fetching. ## Issue Work We opened issue #1674: https://github.com/steipete/CodexBar/issues/1674 Title: ```text v0.37.0: Overview menu stutters on every scroll event with multiple providers on macOS 27 ``` The issue includes: - Repro environment. - Sample summary. - Source mapping from sample stack to current main. - Two proposed fix directions. - Explicit note that the full sample file was not uploaded because it includes local machine paths. We also cc'd two earlier participants, `@Astro-Han` and `@elkaix`, in a comment on #1674 because both had described detailed menu lag in earlier issues. To be precise about attribution: `@Astro-Han` commented on #1196 (which was authored by `@vekovius`), and `@elkaix` authored #1414. The mention was intentionally limited to people with directly related prior reports, following maintainer-radar guidance. Note one internal inconsistency in the thread: the #1674 issue body cites the older reports as #1196, #1371, and #1387, while the cc comment (and the framing above) pairs #1196 with #1414. Both reference real prior lag reports; the difference is only which subset each surface lists. ClawSweeper kept the issue open. The full current label set (as of this update) is: - `P2` - `clawsweeper:needs-live-repro` - `clawsweeper:needs-maintainer-review` - `clawsweeper:needs-product-decision` - `clawsweeper:no-new-fix-pr` - `issue-rating: 🐚 platinum hermit` - `impact:other` The `clawsweeper:no-new-fix-pr` label is worth calling out for a reviewer: ClawSweeper does **not** recommend queueing an *automated* fix PR for this issue. The two draft PRs below are human-authored exploratory directions opened deliberately for the maintainer product decision, not automated fixes — so they are consistent with, not contradicted by, that label. Its acceptance criteria included: ```text swift test --filter StatusMenuOverviewScrollTests swift test --filter MenuCardViewRecyclingTests swift test --filter StatusMenuOverviewSubmenuTests make check On macOS 27, run a freshly built app with multiple Overview providers and capture before/after scroll profiling or visual proof. ``` ## Why Two PRs There are two plausible fixes, but they make different product and engineering tradeoffs. Mixing them in one patch would make review unclear. So we opened two draft PRs as alternatives, not as cumulative patches: 1. #1675: Lite row 2. #1676: Rich row with AppKit boundary Both PRs are draft PRs because maintainers still need to choose the desired UI/performance direction before merge. ## PR #1675: Lite Row PR: https://github.com/steipete/CodexBar/pull/1675 Branch: `codex/overview-lite-row` Latest head at the time of this document: `6f680eeb18e37fb329cf7c26b956ded8c967a076` ### Motivation If the root problem is too much hosted SwiftUI content inside the Overview menu, the lowest-risk performance path is to render less content in each Overview row. The Lite row direction keeps Overview as a quick provider summary: - Provider identity. - Updated/subtitle state. - Plan/account/storage text where relevant. - A compact quota summary. - Existing click and submenu behavior. It intentionally moves rich charts/details out of the Overview row and leaves them in provider detail surfaces. ### Implementation Summary Changed files: - `Sources/CodexBar/StatusItemController+MenuTypes.swift` - `Tests/CodexBarTests/OverviewMenuCardRowViewTests.swift` Key implementation points: - Replaced the rich `UsageMenuCardHeaderSectionView` + `UsageMenuCardUsageSectionView` Overview composition with a compact summary row. - Added `LiteSummary`, which derives a bounded summary from precomputed `UsageMenuCardView.Model` fields. - Explicitly avoids rendering `InlineUsageDashboardContent` in Overview rows. - Preserves provider click behavior and provider-detail submenus. - Preserves live subtitle and model refresh semantics through `MenuCardRefreshMonitor`. ### ClawSweeper Finding And Fix ClawSweeper's concrete code finding was: > Use the live model for the compact summary. The first Lite row patch updated the subtitle through `MenuCardRefreshMonitor`, but built the compact summary and progress tint from `self.model`. That could produce a stale quota/progress summary while the subtitle had already refreshed. Follow-up fix: - Added `resolvedLiveModel(refreshMonitor:)`. - Header, compact summary, and progress tint now all derive from the same monitor-resolved model. - Added focused coverage proving a stale row resolves refreshed progress through the monitor without rebuilding the menu. New focused test: ```text overview lite summary uses monitor resolved refreshed model ``` ### Validation Local validation run for the Lite row direction: ```text swift test --filter OverviewMenuCardRowViewTests swift test --filter "overview row" swift build make check git diff --check ``` The PR body and follow-up comment were updated, and `@clawsweeper re-review` was requested. ClawSweeper acknowledged the re-review command for head `6f680eeb`. ### Risks The Lite row direction changes the product feel of Overview. It likely reduces scroll-time work, but it also removes rich chart/detail content from the Overview list. This needs maintainer/product approval. It still lacks an interactive after-fix scroll profile or recording. ## PR #1676: Rich Row With AppKit Boundary PR: https://github.com/steipete/CodexBar/pull/1676 Branch: `codex/overview-rich-row` Latest head at the time of this document: `963ed4cf9941cc98300650c5532e7ffcebf1b618` ### Motivation If maintainers want to preserve the current rich Overview UI, we should reduce scroll/hover overhead without removing content. The suspected expensive interaction is SwiftUI highlight/hit-test/layout work during `NSMenu` tracking. The rich-row direction therefore keeps the SwiftUI content but moves row-level hover/highlight/hit-test handling to a narrow AppKit wrapper. ### AppKit Boundary This direction uses a small AppKit bridge: - SwiftUI still owns the row content and model rendering. - SwiftUI still receives `menuCardRefreshMonitor`. - AppKit owns only row-level: - `hitTest` - hover background via `CALayer` - measured/fixed row height behavior - recycling support - click glue This follows the `build-macos-apps:appkit-interop` guidance: cross only the narrow platform boundary needed for menus instead of rewriting the feature in raw AppKit. ### Implementation Summary Changed files: - `Sources/CodexBar/StatusItemController+Menu.swift` - `Sources/CodexBar/StatusItemController+MenuCardItems.swift` - `Sources/CodexBar/StatusItemController+MenuPresentation.swift` - `Sources/CodexBar/StatusItemController+MenuTypes.swift` - `Tests/CodexBarTests/MenuCardViewRecyclingTests.swift` Key implementation points: - Added `OverviewMenuRowHostingView`, an AppKit host for Overview rows only. - Added `makeOverviewMenuRowItem` so normal menu cards keep the existing path. - Moved row highlight to an AppKit `CALayer`. - Made `hitTest` stop at the Overview row container boundary. - Preserved provider click behavior and provider-detail submenus. - Preserved `MenuCardRefreshMonitor` injection through `OverviewMenuRowContainerView`. - Guarded repeated intrinsic-size invalidation when row height does not change. ### Follow-up Test ClawSweeper did not report a concrete code defect for #1676. It mainly requested real behavior proof. We still added one lifecycle regression test after re-reviewing the AppKit bridge: ```text recycled overview row keeps hosting view and clears appkit highlight state ``` This verifies that: - The hosting view is recycled instead of rebuilt. - Stale AppKit highlight state is cleared during reuse. ### Validation Local validation run for the Rich row direction: ```text swift test --filter "overview row" swift test --filter "highlight" swift build make check git diff --check CODEXBAR_SIGNING=adhoc ./Scripts/package_app.sh debug codesign --verify --deep --strict --verbose=2 CodexBar.app ``` The follow-up validation was run on: ```text macOS 27.0 (26A5353q) Apple Swift 6.4 arm64 ``` The PR body and follow-up comment were updated, and `@clawsweeper re-review` was requested. ClawSweeper acknowledged the re-review command for head `963ed4cf`. ### Risks The Rich row direction preserves UI density but adds a new AppKit hosting boundary. The bridge is intentionally narrow, but it touches primary menu rendering and interaction. The main residual risk is runtime behavior under a real `NSMenuTrackingSession`. Two specific risks a reviewer should weigh, beyond runtime behavior: - **Highlight appearance changes (an undisclosed visual regression).** The new `makeOverviewMenuRowItem` wraps content in `OverviewMenuRowContainerView`, which injects only `menuCardRefreshMonitor` — it does **not** inject `menuItemHighlighted`, does not apply `foregroundStyle(primary(highlighted))`, and does not draw the SwiftUI selection background that `MenuCardSectionContainerView` provides on the existing path. Because the rich header/usage subviews all read `@Environment(\.menuItemHighlighted)` (see `MenuCardView.swift`), that environment now stays `false`, so the row text **no longer inverts on highlight**. Highlight is conveyed only by the `CALayer` background (`selectedContentBackgroundColor` at alpha `0.16`). Net effect: an Overview row highlights as a faint translucent background with dark text, while every other row/submenu in the same menu still uses the standard solid-blue-with-white-text selection — an inconsistent, weaker highlight. The captured `cgColor` is also static and will not follow accent-color or light/dark changes. This is a product-visible change that the PR body currently frames as a neutral "move hover highlight to a cheap CALayer"; it should be disclosed and accepted explicitly. - **Duplicated hosting infrastructure.** `OverviewMenuRowHostingView` re-implements much of the existing `MenuCardItemHostingView` (`installClickRecognizer`, `acceptsFirstMouse`, `measuredHeight`, `prepareForReuse`, `allowsVibrancy`), creating two parallel menu-hosting classes to maintain. Reusing or parameterizing the existing host would be a lower-divergence design. It still lacks an interactive after-fix scroll profile or recording. ## Current GitHub State As of the latest local `gh` check: - Issue #1674 is open. - PR #1675 is open and draft. - PR #1676 is open and draft. - Both PRs are mergeable. - Both PRs have follow-up commits pushed. - Both PRs have ClawSweeper re-review queued and acknowledged. - GitGuardian has passed on both updated heads. - Other GitHub Actions checks may still be queued or not yet refreshed for the latest heads. We intentionally did not wait synchronously for all external checks, because the local proof, PR body updates, and ClawSweeper re-review requests are already done. ## What Is Proven The issue is high-quality enough to keep open: - The user supplied a concrete symptom and sample command. - The sample maps to current source. - The suspect stack is tied to menu tracking and hosted SwiftUI row layout/render/hit-test. - The current source has not meaningfully changed in the implicated files after v0.37.0. The Lite PR proves: - Overview row content can be made lightweight. - Dashboard-heavy SwiftUI content is avoided in Overview rows. - The compact summary uses the same monitor-resolved live model as existing usage-row logic. - Click/submenu behavior remains covered by existing Overview tests. The Rich PR proves: - Overview can keep the rich row UI while moving highlight/hit-test to AppKit. - AppKit highlight state is cleared on reuse. - Overview row hosting views can be recycled. - Existing overview row action, submenu, rendered-mode, storage text, and scroll targeting tests continue to pass. - A debug app package can be produced and codesigned locally on macOS 27. ## What Is Not Proven We still do not have the strongest proof ClawSweeper asked for: - No controlled before/after interactive scroll recording. - No after-fix `sample` output captured while scrolling a provider-heavy Overview menu. - No Instruments trace showing frame-time or main-thread stack reduction. This is important because unit tests can prove the row model and bridge lifecycle, but they cannot fully simulate `NSMenuTrackingSession` behavior under real trackpad/mouse-wheel input. ## Smaller Third Direction (Candidate) Both PRs are fairly large for a performance fix. Given the mechanism in "Evidence From The Sample" (each scroll step costs two full rich-row re-renders plus a hit-test descent), there are two smaller, lower-commitment experiments worth trying before settling on either PR: 1. **Decouple highlight from content re-render, SwiftUI-only.** Make the selection background the *only* thing that depends on the highlight flag, and stop re-coloring the whole subtree via `foregroundStyle(primary(highlighted))` at the container level. This is the SwiftUI-subset of what the Rich PR does in AppKit: it cuts the per-highlight re-render cost without introducing a new `NSView` host class and without changing the highlight's visual style (the background can keep using the standard selection color). 2. **Drop the redundant synthetic mouse-move.** `postOverviewScrollNavigation` already advances highlight explicitly via `self.menu(menu, willHighlight:)`; the subsequent synthetic `.mouseMoved` then re-drives hit-testing and highlighting (the `NSHostingView.hitTest` / `scrollWheel` work in the sample). The code comment says the mouse-move preserves native highlight/submenu behavior, so this needs a targeted experiment to confirm it is actually redundant — but if it is, removing it is a ~10-line change that hits the hit-test path directly. Both still require the same controlled macOS 27 interactive profiling to confirm. The Rich PR is essentially the industrial-strength version of option 1; option 2 is orthogonal and could stack with either PR. ## Design Tradeoff The maintainer decision is likely between these two philosophies: ### Choose Lite Row If - Overview should be a fast high-level summary. - Full charts/details can live in provider detail surfaces. - Reducing SwiftUI content is preferred over preserving exact current density. - The simplest performance path is preferred. ### Choose Rich Row If - Overview should preserve the current information density. - The performance problem is mostly hover/hit-test/highlight propagation. - A narrow AppKit menu boundary is acceptable. - Maintainers want a lower visual-change patch. These PRs should not both merge. If maintainers choose one direction, the other should be closed or parked. ## Recommended Next Steps 1. Wait for ClawSweeper re-review to finish on both PRs. 2. If ClawSweeper clears the concrete Lite finding, compare remaining comments. 3. Run one controlled interactive proof on macOS 27: - Build a fresh app from the chosen branch. - Enable multiple Overview providers. - Open Overview. - Scroll while recording a short video or running `sample`. - Redact local paths/account info before posting summary. 4. Ask maintainers to choose Lite vs Rich based on UI preference and proof. 5. Convert only the selected PR from draft to ready. ## Questions For Claude Review Please review the two PR directions and this reasoning with these questions in mind: 1. Is the root-cause interpretation coherent from the sample stack and source mapping? 2. Does the Lite row PR fix ClawSweeper's live-model critique completely, or is there another stale-model path? 3. Does the Rich row AppKit bridge cross the smallest reasonable boundary, or does it introduce hidden lifecycle risk? 4. Are there tests missing that would materially increase confidence without requiring a real interactive menu session? 5. Between Lite and Rich, which direction is more likely to be accepted by maintainers, and why? 6. Is there a third direction that is smaller than both PRs and still addresses the scroll hot path? 7. Are the PR bodies honest and precise about what is proven versus what remains unproven? ## Review Pointers The symbols and test files below are introduced by the PR branches and do **not** exist on `main` (where this document lives). Check out the relevant branch or read the PR diff before following these pointers: - Lite row: `git checkout codex/overview-lite-row` (PR #1675) — adds `LiteSummary`, `resolvedLiveModel(refreshMonitor:)`, and `OverviewMenuCardRowViewTests`. - Rich row: `git checkout codex/overview-rich-row` (PR #1676) — adds `OverviewMenuRowHostingView`, `makeOverviewMenuRowItem`, and `OverviewMenuRowContainerView`. On `main`, only the unchanged baseline symbols (`addOverviewRows`, `OverviewMenuCardRowView`, `UsageMenuCardUsageSectionView`) are present. For Lite row review: - Start with `OverviewMenuCardRowView` in `Sources/CodexBar/StatusItemController+MenuTypes.swift`. - Check `resolvedLiveModel(refreshMonitor:)`. - Check `LiteSummary.make(for:)`. - Check `OverviewMenuCardRowViewTests`. For Rich row review: - Start with `OverviewMenuRowHostingView` in `Sources/CodexBar/StatusItemController+MenuPresentation.swift`. - Check `makeOverviewMenuRowItem` in `Sources/CodexBar/StatusItemController+MenuCardItems.swift`. - Check `addOverviewRows` in `Sources/CodexBar/StatusItemController+Menu.swift`. - Check `MenuCardViewRecyclingTests`. For shared behavior: - Check `StatusMenuOverviewScrollTests`. - Check `StatusMenuOverviewSubmenuTests`. - Check existing menu-card recycling behavior. ## Bottom Line The motivation is solid: a real user-visible scroll stutter maps to a plausible current-source hot path in `NSMenu` plus hosted SwiftUI Overview rows. The progress is also concrete: the issue is public, two alternative draft PRs exist, ClawSweeper feedback was addressed where it identified real code problems, and both branches have focused tests plus local validation. The main unresolved question is not "can we patch something?" It is which product/performance tradeoff maintainers want, and whether a controlled macOS 27 interactive scroll proof confirms the chosen direction. --- ## Update 2026-06-24: Measured root cause + a third (implemented) direction This section adds quantitative evidence collected on macOS 27 (Swift 6.4, debug) and a third implementation that supersedes the lite/rich split for the render half of the problem. ### Headless benchmark of one highlight step Scrolling the Overview does not scroll pixels — `handleOverviewScrollWheel` converts the wheel into discrete "move the highlighted row" steps. So the per-scroll cost is the cost of toggling a row's selection. A headless benchmark hosted a real `OverviewMenuCardRowView` through the production hosting path and measured `setHighlighted → layout → display → runloop-flush` over 200 toggles: ```text A baseline (SwiftUI recolor via menuItemHighlighted) avg ~2.4–10ms max ~7–27ms B + .drawingGroup() (Metal offscreen rasterization) avg ~2.2–10ms max ~9–32ms (~10–28% only) C content pinned, highlight fully decoupled avg ~0.01–0.06ms D container highlight modifiers only, content pinned avg ~3–8ms E GPU CIColorMatrix tint + AppKit selection layer avg ~0.05ms max ~1–2ms ``` Findings: - The spikes in `A` (~25ms) line up with the dropped frames in the user's recording (120fps capture, ~57fps effective, worst frame gaps ~25ms). - `D` shows the cost is dominated by re-rasterizing the content subtree whenever the container's highlight-dependent modifiers change — not by leaf body evaluations. - `B` proves **Metal alone is insufficient**: `.drawingGroup()` speeds rasterization but the SwiftUI body/transaction pass still runs, so it only buys ~10–28% and still misses the 8.3ms/120Hz budget. - `E`/`C` show the only way to the 120Hz budget is to take the selection off the SwiftUI graph. ### Implemented direction: AppKit/GPU selection (`GPUSelectionHostingView`) `Sources/CodexBar/MenuCardGPUSelectionView.swift` renders the selected look without any SwiftUI work: 1. an `NSVisualEffectView(.selection)` background (the existing in-repo pattern from `PersistentRefreshMenuView`), crossfaded via Core Animation so the highlight glides between rows instead of teleporting, and 2. a `CIColorMatrix` content filter that maps the row's pixels to the selected text color — which matches the existing design where every selected element already becomes `selectedMenuItemTextColor`. Core Image runs on the GPU (Metal), so the toggle is a layer change. It is opt-in via `makeMenuCardItem(usesGPUSelection:)` and currently wired only for Overview rows in `addOverviewRows`. It deliberately does **not** override `hitTest`, avoiding the embedded-control regression ClawSweeper flagged on the rich-row PR. Measured production path: **2.35ms → 0.044ms** average per toggle, max well under one 120Hz frame. ### The second, orthogonal problem: discrete navigation feel Even at 0.05ms/step, the motion is not "hand-following" because the wheel is quantized. Driving the real `handleOverviewScrollWheel` with continuous gestures showed: ```text slow swipe: 240px finger travel -> one row jump every 24px (teleport, nothing in between) fast flick: 200px (intent ~8 rows) -> only 3 rows registered (per-event cap + remainder discarded) post-flick 20px nudge -> 0 steps (accumulator was zeroed, so the follow-up felt dead) ``` Interaction changes in `StatusItemController+OverviewScroll.swift` (merged with a parallel Codex worktree that independently reached the same GPU-selection design): - **Precise trackpad scrolls are passed through to AppKit's native menu scroller** instead of being converted into thresholded row-highlight jumps (`guard !event.hasPreciseScrollingDeltas { … return false }`). Trackpads are continuous devices; native menu scrolling follows the finger, which is the real fix for the "不跟手" feel — making highlight toggles cheap (above) does not by itself remove the discrete-jump model. Classic notched scroll wheels keep the row-to-row highlight navigation. - The crossfade in `GPUSelectionHostingView` softens the remaining wheel-driven highlight transitions. A deterministic regression test (`gpu selection highlight bypasses swiftui highlight state`) asserts that highlighting a GPU row marks the AppKit view highlighted while the hosted `MenuCardHighlightState.isHighlighted` stays `false`, proving selection never re-invalidates the SwiftUI graph. ### Test status `swift build` clean; the updated `StatusMenuOverviewScrollTests` (precise pass-through cases) and the menu-card recycling/highlight suites — including the new GPU bypass test — pass.