Skip to content

fix(desktop): bound release notes and manage preview focus

MacroscopeApp / Macroscope - UI Consistency failed Aug 29, 2026 in 2h 6m 23s

UI Consistency: 1 issue found

apps/web/src/components/sidebar/SidebarUpdatePill.tsx — the new hover-opened release-notes Popover contains interactive links and a scrollable list, but its PopoverTrigger uses openOnHover with Base UI's default closeDelay (0). The pointer must cross the 4px sideOffset gap to reach the links, and the intent the old tooltip encoded explicitly (pointer-events-auto so "the release notes stay open (and scrollable) when the cursor moves into them") is not carried over. Every other hover popover in this codebase whose content is meant to be reached sets an explicit close delay (ContextWindowMeter 150 with a dedicated test, PullRequestDetailPanel 120, ConnectionsSettings 100); the ones that omit it are pointer-inert tooltips. Suggested fix posted inline.

No other findings in the in-scope web files.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.

Files examined (in scope)

  • apps/web/src/components/sidebar/SidebarUpdatePill.tsx
  • apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx
  • apps/web/src/components/sidebar/SidebarUpdatePill.test.tsx, SidebarUpdateReleaseNotes.test.tsx
  • apps/web/src/components/desktopUpdate.toast.tsx, desktopUpdate.logic.ts (+ tests)
  • apps/web/src/state/desktopUpdate.test.ts

Context read outside scope: packages/contracts/src/ipc.ts, apps/desktop/src/updates/*, apps/web/src/components/ui/{popover,tooltip}.tsx, apps/web/src/index.css (dropdown-glass), apps/desktop/src/window/DesktopWindow.ts.

Checks performed and found clean

  • Prior-run findings resolved. The earlier keyboard-exit and focus-ring comments are addressed: the tooltip was replaced with a non-modal Popover (focus guards allow Tab into the popup, Escape closes and restores focus), and ReleaseLink now uses outline-none focus-visible:ring-2 focus-visible:ring-ring, matching the update button next to it.
  • Primitive usage. Popover/PopoverTrigger/PopoverPopup with tooltipStyle matches the existing hover-popover pattern (ContextWindowMeter, GitActionsControl, ThreadTerminalDrawer). The raw <button> trigger and Separator usage are pre-existing/unchanged; there is no shared link primitive to prefer over the raw <a>.
  • Surface styling. dropdown-glass supplies the 1px border, so the call-site borderColor: var(--update-foreground) still applies; shadow-xl shadow-black/25 merges over the primitive's shadow-md/5 via cn. The update-tinted glass inline style is now duplicated across the popover and tooltip branches of the same file — noted but not reported, since it is a literal carry-over rather than a behavioral change.
  • Layout/overflow. --available-height inherits from the Base UI positioner, the flex column sets min-h-0, and the inner overflow-y-auto region sits inside the viewport padding, so the popup scrolls without clipping the scrollbar lane.
  • External links. <a href> with preventDefault + shell.openExternal is safe in the desktop shell: DesktopWindow denies window-open and routes safe URLs to openExternal, so middle/modified clicks do not navigate the app window.
  • Environment routing. No environment-scoped RPCs or OS-derived labels are involved; the release URLs are static GitHub links.

🔍 Agent activity — 21 tool calls

💭 I want to check the current state of the PR's new files against the previous run to see what's changed between commit e93bbea and head a9c460d.

1. git_tools — e93bbea..a9c460d

2. browse_code — apps/web/src/components/sidebar/SidebarUpdateReleaseNotes.tsx

💭 Checking the prior review comments against the current code, it looks like both issues are already resolved—the focus ring was added to ReleaseLink, and the tooltip was replaced with a proper Popover component that includes accessibility attributes like aria-label and initialFocus handling. Now I want to dig into the current head for anything new, so I'm going to check the Popover primitive's implementation details like tooltipStyle, PopoverCreateHandle, and openOnHover, plus take a look at SidebarUpdatePill.tsx.

3. browse_code — apps/web/src/components/ui/popover.tsx

4. browse_code — apps/web/src/components/sidebar/SidebarUpdatePill.tsx

💭 I'm comparing how the Popover switches its glass styling versus the previous Tooltip approach, checking whether the dropdown-glass class with the inline background override matches the old glass variant. I also want to verify pointer-events behavior differs correctly since Popover's positioner doesn't need the pointer-events-none that Tooltip required. I'm also worried the border color inline style might have no visible effect if dropdown-glass doesn't include a border class the way the tooltip's glass variant did — that would silently drop the colored border ring. And I need to check whether overflow and max-height calculations still work correctly, since the popup relies on --available-height being inherited from the Positioner element, which could break the scrolling behavior in the release notes panel.

5. browse_code — apps/web/src/components/ui/tooltip.tsx

6. git_tools — dropdown-glass

💭 I'm checking that the glass border styling still works correctly with the inline borderColor, and comparing how the tooltip and popover variants handle the inner highlight pseudo-element differently — the tooltip hides it while the popover keeps it, which seems like an intentional design choice rather than a regression. I'm also verifying that replacing the glass variant with explicit shadow classes at the call site preserves the same visual shadow effect. Now I'm working through the height/scroll math between the positioner, viewport, and content — checking that the padding and max-height calculations still add up correctly across the nested containers. I'm noting that the release notes popup's aria-label looks fine, but I want to flag that the update button rendered inside the popover branch (via PopoverTrigger) may be missing the tooltip wrapper that the non-popover branch gets through TooltipTrigger. I'm also examining the openOnHover behavior on the Popover — checking whether Base UI's default hover/close delays interact properly with the release notes popup, and whether keyboard interactions like Tab and forced opens are handled consistently with the trigger-press reason logic. I should verify whether the typing actually accepts a boolean by checking other usages in the repo. Also worth checking modality defaults—Base UI Popover defaults to modal={false}, so outside interactions aren't blocked, which seems fine here. But removing the Tooltip entirely for the release notes popover means hovering over that area no longer triggers tooltip behavior, which could be a meaningful UX change worth flagging. Actually, floating-ui's FloatingPortal renders focus guards before and after the floating element, redirecting Tab from the trigger into the portal content, so this should work fine. That likely resolves the earlier review concern about keyboard navigation. Now I want to check whether the keydown handler attached to the update button in the Tooltip branch has an early return when notes aren't showing. Next I want to check the test file for the sidebar update pill, and look at how the existing release notes link in the desktop toast component handles anchors versus buttons for external links, to make sure the new ReleaseLink component follows the same pattern. I also want to search the repo for other uses of hover-triggered popovers to see if there's already an established primitive I should reuse instead of introducing a new pattern.

7. git_tools — openOnHover|initialFocus|PopoverCreateHandle

8. browse_code — apps/web/src/components/desktopUpdate.toast.tsx

💭 I'm weighing whether switching from a button to an anchor with preventDefault could cause issues in Electron—middle-click or cmd-click might bypass preventDefault and trigger real navigation, which could break the app since Electron renderer windows navigate away on link clicks by default. I want to check if the repo already has a pattern for external links, like using href with an openExternal handler, and confirm cursor styling works fine automatically with an anchor tag.

9. git_tools — openExternal

💭 I'm noticing there's an established contract for external links in openPullRequestLink.ts, particularly around how buttons without hrefs still route through openExternal for context menu and middle-click support. There's also an externalLinkContextMenu used in chat markdown anchors, though adding that to a new anchor is likely outside the current scope. Let me check the relevant section of openPullRequestLink.ts around lines 240-280.

10. browse_code — apps/web/src/lib/openPullRequestLink.ts

11. browse_code — apps/web/src/components/pullRequest/PullRequestMarkdown.tsx

💭 I'm noticing this ReleaseLink only renders in Electron, so the shell bridge is always window.desktopBridge, but I should check whether preventDefault on the anchor's click handler still allows middle-click or Cmd-click to open a new tab/window unexpectedly. Given that middle-click likely routes through Electron's window-open handler which already denies and redirects to openExternal, this isn't really an issue. Instead I want to focus on UI-consistency findings, like how the hover-only popover has no keyboard/touch fallback, so focusing the button won't surface the "Update reads" tooltip. I'm also questioning whether initialFocus={false} is even a valid type for the Base UI Popover, since its type signature expects a ref or callback rather than a boolean—I need to check the actual base-ui version installed to see if this would fail typechecking. Since I can't inspect node_modules to verify the exact typing, I'll just trust it's valid and move on — escape already handles keyboard dismissal by closing the popover and returning focus to the trigger. Now I'm checking the shadow styling classes applied to the popup. Checking how tailwind-merge resolves conflicting shadow utilities between the tooltip style and popup className — since both fall in the same shadow group, the later class in the merged string wins, so shadow-xl and shadow-black/25 end up applying correctly, and the max-width constraints on the positioner and popup look consistent too. Now I'm looking at the focus ring styling on ReleaseLink. size-full sizes the popup to full width and height of its auto-sized container, which seems fine. I'm also noting that the Viewport wrapper only affects children marked with data-current, and the Popover positioner lacks pointer-events-none so that's not a concern. The more real issue is that the call site duplicates the glass background/border styling inline rather than reusing the primitive's variant — a pre-existing pattern from the tooltip branch. Now this same inline style object is copy-pasted across both the Popover and Tooltip branches, which is a mild consistency issue worth flagging — maybe suggesting extraction into a shared constant, though it's borderline whether that counts as a concrete violation worth reporting. I'm also checking whether the disabled state interplay and keyboard handling still work correctly with the render prop merging in the trigger, and confirming the tooltip omission for non-nightly hover cases is acceptable. The omittedReleaseCount link and header logic also look fine as written. For the SidebarUpdateReleaseNotes scroll behavior, I'm verifying the inner and outer max-height constraints work together with min-h-0 to properly contain the scroll area, and checking that the fade geometry and native scrollbar styling stay consistent within the rounded popover viewport's clipping. I also want to check if there's a shared ScrollArea primitive convention I should be following instead of the raw overflow-y-auto div, and compare against how ContextWindowMeter and PullRequestDetailPanel handle similar hover-card popover patterns to make sure I'm consistent with existing conventions in the codebase.

12. browse_code — apps/web/src/components/chat/ContextWindowMeter.tsx

13. browse_code — apps/web/src/components/pullRequest/PullRequestDetailPanel.tsx

💭 I'm checking whether SidebarUpdatePill follows the established hover popover convention—it's missing the delay/closeDelay props others use, so I'm working out Base UI's defaults (300ms delay, 0 closeDelay) and whether the small sideOffset gap could cause the popover to close prematurely as the pointer moves toward it. Since this popover contains interactive release-notes links, that closing-too-fast issue would make them unreachable, similar to how ContextWindowMeter uses a longer closeDelay specifically when its popup has an interactive Compact button. That original tooltip's removed comment even explicitly said the pointer-events override was there so the cursor could move into the release notes and keep them open and scrollable. I still need to confirm whether Base UI's popover hover handling includes safePolygon-style gap tolerance, or whether that gap really is the failure point—let me check how GitActionsControl handles this.

14. browse_code — apps/web/src/components/GitActionsControl.tsx

15. browse_code — apps/web/src/components/ThreadTerminalDrawer.tsx

💭 I notice that ContextWindowMeter sets a closeDelay only when its popup contains an interactive Compact button, effectively signaling that interactive content needs a delay to allow pointer traversal across the gap. Since our new popover also has interactive links, this precedent suggests it should follow the same pattern. I'm trying to recall whether Base UI's popover hover interaction uses safePolygon internally, which would make closeDelay less critical since pointer movement toward the popup would keep it open regardless. Given my uncertainty about the underlying implementation, I don't want to flag the missing closeDelay as a definite bug without verifying against the actual Base UI source, so I should check whether it's vendored in the repo. Turning to another possible issue, I'm examining how the tooltip-style popover viewport handles overflow and max-height compared to the wrapper, tracing how the --available-height CSS variable set by Base UI's positioner would inherit down to the content and whether the scrolling behavior stays consistent between the viewport and its inner content. Then I consider role semantics: since Base UI's Popover.Popup renders with role="dialog" rather than "tooltip", hovering the update pill now surfaces dialog-like accessibility behavior instead of a tooltip, which combined with initialFocus={false} and hover-triggered opening seems like an acceptable but notably different interaction pattern for screen readers. Actually the escape/exit keyboard handling ties into how Base UI's Popover manages focus guards via FloatingFocusManager — with modal={false}, it uses non-modal focus management so tabbing naturally continues from the trigger into the portal content, which is the documented floating-ui behavior for allowing keyboard exit without trapping focus. I'm now considering whether there's a genuine issue with using flushSync inside a keydown handler that opens the popover before native browser behavior kicks in. I check whether the forward-tab handler firing repeatedly while the popover is already open could cause problems, but it seems harmless. I trace through the focus flow when Tab opens the popover and Shift+Tab returns focus to the trigger, and consider whether the handle's open API could behave unexpectedly in this context. I'm now looking at what happens when showReleaseNotesPopover flips false while the popover is open — since it's conditionally rendered, unmounting could strand focus on the body even though the effect calls close, so the timing between the state flip and the unmount matters. Then I shift to checking the call site's className and inline style overrides against the rule about not overriding a primitive's core visual properties, since the shadow, max-width, and background overrides look like potential violations worth flagging. That duplication feels like a low-value finding rather than a real bug. I want to compare the actual styling classes between the old tooltip popup and the new popover popup more carefully—checking rounding, spacing, and text treatment differences to see if there's a genuine visual regression in the release notes popup. I notice the tooltip uses exit animation states that the popover lacks, though that's minor. I'm also considering the popup's width sizing and whether Base UI's modal default affects scroll locking behavior for the popover versus tooltip. More importantly, since the popover stays open on hover, clicking a button inside it while it's hovered-open won't close the popover due to trigger-press cancellation — that seems acceptable behavior. I want to check the closeDelay handling more carefully now, since that's the biggest user-facing risk: if someone hovers the pill and moves slightly into the popup, does it stay open? I should check the Base UI package version and see if there's a patch affecting hover behavior.

16. git_tools — base-ui

17. git_tools — closeDelay

💭 I recall there's a test in ContextWindowMeter.test.tsx specifically checking closeDelay behavior, so I want to check its rationale to confirm that hover popovers with interactive content need that delay in this codebase.

18. browse_code — apps/web/src/components/chat/ContextWindowMeter.test.tsx

💭 I notice the codebase convention: hover popovers with interactive controls inside set closeDelay={150} so the pointer can move to a nested button without closing early. This new release-notes popover has interactive links and a scrollable list but currently lacks that closeDelay, plus it's using default spacing settings, so I should check if it needs the same treatment. The old implementation was a Tooltip with an explicit comment noting the release notes should "stay open (and scrollable) when the cursor moves into them" — that affordance seems to have been dropped in favor of Base UI defaults, which feels like a real regression worth flagging with a suggested fix to add closeDelay. Skipping the duplicated glass style object as low-value, and the max-height combos on SidebarUpdateReleaseNotes look fine as-is. I'm now looking at whether the ReleaseLink anchor's lack of target/rel could cause issues in Electron — a middle-click might bypass preventDefault and open a bare BrowserWindow via Chromium's new-window behavior, so I want to check if the desktop app has a setWindowOpenHandler or will-navigate listener that would catch this, even though that file itself is outside the current scope.

19. git_tools — setWindowOpenHandler|will-navigate

💭 The window open handler is safe since it denies and opens externally. The main finding is that the PopoverTrigger lacks a closeDelay while the popup is interactive, which the ContextWindowMeter precedent (using a 150ms closeDelay) suggests is necessary to avoid the popover closing prematurely as the pointer moves. I'm also noting that the button no longer has any tooltip at all in the popover branch, which seems like a potential second issue worth flagging. Checking other edge cases now — the SidebarUpdateReleaseNotes fallback is unreachable given the guard so that's fine, and the aria-label on PopoverPopup seems fine too. I'm trying to recall whether Base UI's Popover.Popup actually supports initialFocus={false} as a boolean, since the docs I remember describe it accepting a ref or callback — though I do recall a release note mentioning boolean support was added specifically for hover-opened popovers. Since verifying the exact type signature isn't practical here and CI typecheck would catch a mismatch anyway, this isn't worth flagging as a UI consistency issue. I'm also checking whether there's a lint rule against raw <button> elements instead of the Button primitive, but the update pill button was just moved, not newly created, so that's not a new finding either. Now I'm looking at whether the docs claim about showing eight changes across six releases actually matches the MAX_RELEASE_NOTE_ITEMS_PER_GROUP truncation logic in releaseNotes.ts. I'll focus on posting just the closeDelay comment to stay precise. Let me also check whether ReleaseLink in SidebarUpdateReleaseNotes.tsx duplicates a link treatment that already exists as a shared ui primitive, so I'll search for an existing link or button variant component.

20. browse_code — apps/web/src/components/ui/*.tsx

💭 I've settled on my single finding: the hover popover trigger with interactive content is missing a closeDelay. I need to locate line 339 in the diff, where openOnHover appears in the added block, so I can attach an inline suggestion adding closeDelay={150} right after it with matching indentation. Now I'm weighing whether this warrants a failure state on the check. Since the old implementation explicitly preserved cursor movement into the notes and the new one drops that safeguard, I'm leaning toward treating this as a real regression risk, and since the check defaults to failure when there are findings, I'll mark it as failure. Checking other hover popovers confirms the pattern: those with interactive content set an explicit close delay, while the ones omitting it are pointer-inert tooltips. This new popover is the only interactive-content case without a delay, which is a real consistency finding, so I'll mark this as a failure per the check's default guidance. I want to verify one more possible issue before finalizing: whether initialFocus={false} combined with Tab-triggered opening could cause a focus conflict, but this looks intentional since native Tab still moves focus into the popup through the guard correctly. The scroll and unmount behaviors also check out fine, so I'm ready to write up the comment about the pointer travel distance across the popover's side offset. I'm noting that this component omits a closeDelay unlike ContextWindowMeter, which sets one specifically to keep the hover popover open while the pointer travels to reach interactive content — I should flag this as the fix, suggesting closeDelay={150} alongside openOnHover. I'll place this comment at line 339 on the right side of the diff, making sure to include all the required schema fields like start_line and side for the review comment. Writing the suggestion block...

21. modify_pr — One finding on the new hover-driven release-notes popover. Everything else in the changed web files (the popover/tooltip split, the SidebarUpdateReleaseNotes extraction, the shared openDesktopUpdateReleaseNotes helper, and the release-link focus ring) looks consistent with the existing primitives.


Agent Credits: 184 credits

Token usage: 26 uncached input · 993,053 cache-read input · 115,736 cache-write input · 21,097 output
Agent Credits may also include non-token charges from external tools such as web research.