Skip to content

fix(web): prevent lost window captures

MacroscopeApp / Macroscope - UI Consistency failed Aug 24, 2026 in 10m 44s

UI Consistency: 2 issues found

Two consistency findings on the new window-capture settings page:

  • apps/web/src/components/settings/WindowCaptureSettings.tsx (rows 166-201): without a desktop capture bridge each control is individually disabled, but only the first row carries the explanatory status. The shortcut/sound/flash/animation rows render at full label strength with no reason he DesktopOnlyBrowserDefaults framing in IntegrationsSettings.tsx exists specifically because per-control disabled alone still reads as editable. Suggested reusing that block (lifted into a shared settings component) for the group.
  • apps/web/src/components/settings/WindowCaptureSettings.tsx:113: the section heading icon inherits text-foreground at size-4.5, while the only existing SettingsSection icon convention is size-4 text-muted-foreground (and no other top-level settings page carries a heading icon), so this heading reads heavier than its peers.

Both were posted as inline review comments.

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: WindowCaptureSettings.tsx, WindowCaptureOnboardingDialog.tsx, WindowCaptureCoordinator.tsx, WindowCaptureAttachmentDetails.tsx, ChatComposer.tsx (attachment card), MessagesTimeline.tsx (attachment card), CommandPalette.tsx, SettingsSidebarNav.tsx, settingsSearch.ts, lib/desktopWindowCapture.ts, hooks/useSettings.ts, composerDraftStore.ts, routes/__root.tsx, routes/settings.window-capture.tsx, index.css.

Verified against shared contracts: settingsLayout (SettingsSection/SettingsRow/SettingResetButton prop use is correct), ui/dialog (header/panel/footer composition now matches the primitive contract, close button default retained), ui/kbd (single-Kbd whole-shortcut label matches CommandPalette/RightPanelTabs usage), ui/button/ui/switch (no core geometry or focus-ring overrides), AppSidebarLayout's [data-keybinding-capture] capture-phase contract (now honoured by the shortcut recorder, Tab passes through), and the index.css keyframe convention (prompt-stash-count-enter) with a call-site animate-[...] plus motion-reduce:animate-none.

Items flagged in previous runs and confirmed resolved: the composer focus event now lives in one module (WINDOW_CAPTURE_FOCUS_EVENT in lib/desktopWindowCapture.ts) instead of duplicated literals; the onboarding dialog composes DialogHeader/DialogPanel/DialogFooter and gates on getDesktopWindowCaptureBridge(); sound and animation switches are gated on the same !bridge condition as their siblings; the capture card no longer pins a fixed w-56 that starved the metadata column; the animation is a named keyframe rather than non-existent animate-in/fade-in/zoom-in-95 utilities.


🔍 Agent activity — 37 tool calls

💭 I'm checking the current PR diff to confirm the event name literal issue was resolved—it looks like it now lives in the shared desktopWindowCapture module. Now I'm scanning the remaining files in scope for any other lingering issues. Several previously flagged problems appear fixed: the onboarding dialog now uses DialogPanel, sound and animation switches are properly disabled without a bridge, tab handling and shortcut capture attributes are corrected, and the CSS keyframes are properly named. I do notice the ChatComposer attachment card sizing changed, so I want to verify that's intentional.

1. browse_code — apps/web/src/components/desktop/WindowCaptureOnboardingDialog.tsx

2. browse_code — apps/web/src/components/settings/WindowCaptureSettings.tsx

💭 With most prior findings addressed, I want to check remaining issues: whether SettingsRow properly accepts title/description props from searchableSetting, how other settings pages use the status prop, and whether the text-foreground styling issue on WindowCaptureAttachmentDetails within the chat composer capture card has actually been fixed. The current text-foreground usage anchors correctly against the background, which resolves the earlier concern. I should verify text-secondary-label is a valid token in this project, and confirm the keyframes convention in index.css matches existing patterns. I also want to check the composer card's geometry more closely, particularly whether the remove button is positioned absolutely within the thumbnail and details layout.

3. browse_code — apps/web/src/components/chat/ChatComposer.tsx

4. git_tools — secondary-label

💭 I'm checking the composer's window-capture card sizing—the details column reserves space for the remove button, and the thumbnail width seems fine. There's a minor edge case where a missing preview URL could cause layout inconsistency in the fallback state, but it's not critical, so I'll move on to look at the MessagesTimeline area next.

5. browse_code — apps/web/src/components/chat/MessagesTimeline.tsx

💭 The appName in WindowCaptureAttachmentDetails correctly anchors to text-foreground within the message bubble, matching the earlier suggestion. Next I want to check the CommandPalette's "Capture window" action item to make sure it uses ITEM_ICON_CLASS consistently and has proper search terms. I should also verify WindowCaptureCoordinator's toast usage — whether stackedThreadToast is the right helper here or if there's a plain toast API meant for non-thread notifications, plus check the onboarding dialog's DialogPopup sizing and whether Kbd rendered inside Button inherits text styles correctly.

6. browse_code — apps/web/src/components/CommandPalette.tsx

7. git_tools — export function stackedThreadToast|export const stackedThreadToast|export function toast|errorToast

💭 The command palette action seems fine, since window capture's global shortcut isn't a keybinding command. Now I want to check whether the settings section is properly registered in the nav list—the icon was added to SETTINGS_SECTION_ICONS, but I need to verify the sidebar nav actually derives its order from SETTINGS_SECTION_LABELS in settingsSearch.ts.

8. browse_code — apps/web/src/components/settings/SettingsSidebarNav.tsx

9. browse_code — apps/web/src/components/settings/settingsSearch.ts

💭 I'm checking whether the desktopOnly flag on SettingsSearchItem actually matters here, since WindowCaptureSettings renders its rows in the browser too (just marked "Available in the desktop app"), so search results wouldn't be pointing to a missing anchor. Still, I need to consider whether showing the "Window Capture" section in the browser sidebar nav makes sense at all. Let me check whether there's precedent for other desktop-only settings sections and how they're gated, by grepping for isElectron usage across settings components.

10. browse_code — apps/web/src/components/settings/settingsSearch.ts

11. git_tools — isElectron|desktopOnly

💭 I'm noticing the browser shows an entirely disabled section labeled "Available in the desktop app," which raises a question about whether that's a UI consistency issue given the scope covers shared components and behavior. I want to check how other sections like keybindings handle the non-electron case, since desktopOnly items seem to be filtered differently there. Since the rows still render with an explanatory status rather than disappearing, I don't think this is worth flagging—it looks like an intentional product decision. I'm shifting focus to look for actual UI consistency problems: checking the onboarding dialog's use of DialogPopup against other dialog implementations, and examining whether its hand-rolled icon container styling matches existing patterns used elsewhere for dialog header icons. I also want to verify the SettingsRow component actually supports the props being passed in WindowCaptureSettings, and whether the icon sizing in SettingsSection matches conventions used in other settings pages. I'll check settingsLayout and comparable settings pages to confirm consistency.

12. git_tools — export function SettingsRow|export function SettingsSection|status?:|export function SettingResetButton

13. browse_code — apps/web/src/components/settings/settingsLayout*

14. browse_code — apps/web/src/components/settings/settingsLayout.tsx

💭 The SettingsRow usage looks fine. I should check other settings pages like AppearanceSettings or the keybindings route to see how they structure their sections and page headers within SettingsPageContainer, and confirm whether rows typically use dividers. I also notice the "Capture now" row uses a plain title string instead of a searchableSetting. That's intentional since it's not meant to appear in the search catalog, and other pages likely have similar plain-title rows. I want to verify how Kbd renders inside Button — checking whether KeybindingsSettings uses the same Kbd-inside-Button pattern for its shortcut recorder to keep things consistent.

15. git_tools — data-keybinding-capture

💭 The keybindings recorder uses an Input while window capture uses a Button with Kbd — different but likely justified since they're different controls, and prior runs already addressed this concern. I still need to check other in-scope details, like where the keyframes at the end of the CSS file sit relative to the others. For WindowCaptureAttachmentDetails, the icon sizing and empty alt text look fine. In WindowCaptureCoordinator, the sound playback isn't UI-related, but I want to check the stackedThreadToast helper to confirm its signature and whether it's intended for thread-scoped toasts.

16. browse_code — apps/web/src/components/ui/toastHelpers.ts

17. browse_code — apps/web/src/index.css

💭 The keyframes convention looks fine. I'm now checking the composer window-capture card layout—looking at how the thumbnail and details flex items are sized within the flex-wrap container to spot any width or overflow issues. The details section uses flex-1 with min-w-0 and max-w-72, which under shrink-to-fit sizing contributes its max-content width but stays capped, so with max-w-full on the card this should render correctly without overflow. The padding-right override for the remove button also looks intentional. Next I'm moving on to check the MessagesTimeline card sizing. For MessagesTimeline, the thumbnail is a fixed 28-width shrink-0 element with details getting horizontal padding inside a two-column grid capped at 420px, which seems fine. I'm noting a minor inconsistency where the fallback preview div for window-capture attachments lacks flex-1 while the card itself does, though it's likely negligible. I also want to confirm WindowCaptureCoordinator's null render placement in the root file, verify the settings persistence function now returns a promise correctly, and check whether the onboarding dialog needs a modal prop or specific DialogHeader/close-button conventions by comparing it against SshPasswordPromptDialog and the dialog primitive.

18. browse_code — apps/web/src/components/ui/dialog.tsx

💭 The onboarding dialog's header icon block is hand-built with custom styling rather than using a shared pattern, so I want to check other dialogs to see if there's an existing convention for header icons before flagging this.

19. git_tools — DialogHeader>

20. browse_code — apps/web/src/components/cloud/ConnectOnboardingDialog.tsx

💭 The onboarding dialog's info row is hand-rolled but consistent with other dialogs, so that's fine. Now I'm checking actual regressions in the changed lines, starting with whether the ChatComposer's window-capture thumbnail button lost its fixed square sizing. Bigger concern: the card's width change means the pre-existing absolute overlays (progress bar, alert icon, retry) that used to span just the 64px thumbnail now stretch across the entire wide card, potentially covering the metadata text like the window title. That seems like a real visual regression introduced by widening the card without adjusting the overlay positioning. Given the metadata lines sit roughly 22-58px within an 80px card and the overlay strip only covers the bottom ~14px, there's probably no actual visual overlap — so I'll treat that as low severity and skip it. I'm also checking a few smaller things: the window-capture card layout inside the grid looks fine, the settings.windowCaptureAnimations reference in ChatComposer needs confirming it's actually in scope there, motion-reduce:animate-none is correctly applied, the composer's attachment details classes look fine, and the "Capture window" thumbnail button already has an aria-label so accessibility is covered. I'm also going through the shortcut-recording button's blur/keydown handling, the Kbd component inside the button, duplicated toast code in WindowCaptureCoordinator, and the onboarding dialog showing regardless of platform support — mostly minor issues, not real UI inconsistencies. I want to check what's changed since the last reviewed commit to focus my review better.

21. git_tools — apps/web/src/** b615c1a..30669c2

💭 I should look at the current state instead of digging through commit history after the force-push. I want to check ChatComposer's settings usage and confirm whether it's subscribing to the whole client settings object or using a selector, so I'll search for where settings is defined there.

22. git_tools — useClientSettings|const settings

23. browse_code — apps/web/src/components/chat/ChatComposer.tsx

💭 My grep search didn't find the settings definition in ChatComposer.tsx since the file got flagged as binary partway through, cutting the search short. I noticed settings.windowCaptureAnimations referenced around line 3227, so I should look directly near that area of the component to trace where settings comes from.

24. browse_code — In apps/web/src/components/chat/ChatComposer.tsx, where does the settings variable used at line ~3227 (settings.windowCaptureAnimations) come from? Show the declaration line and hook used (e.g. useClientSettings). Also list how other components subscribe to a single client setting (selector vs whole object).

💭 I'm checking whether MessagesTimeline respects the windowCaptureAnimations setting for historic messages, then looking at WindowCaptureOnboardingDialog's use of client settings and shortcut formatting, and now examining whether the Kbd component nests cleanly inside Button given its own padding and background styling. I'm comparing the "capture card" styling between composer and timeline — slight sizing differences in thumbnail widths and padding seem intentional given the different contexts. I'm also checking the fallback rendering path in MessagesTimeline for window-capture cards without a preview URL, confirming it sizes acceptably as a flex item alongside the details, and now moving to review the composer's non-window-capture rendering. I'm also checking accessibility and styling tokens like text-secondary-label and the app-icon fallback colors, which look fine since explicit foreground colors are set. Then I want to look at how the window capture focus event is implemented — it currently uses raw window.addEventListener with a shared constant rather than the typed pub/sub helpers used elsewhere in the codebase, which seems like a reasonable middle ground rather than a real issue. The prior review comment about the duplicated literal is already resolved, so flagging the missing bus module now would just be nitpicking — I'll skip that. Instead I want to check the remaining diff files I haven't reviewed yet: the composer draft store schema, the settings hook, and how the two new components are placed in the root route relative to other desktop-only components, so I'll look at that section of the file.

25. browse_code — apps/web/src/routes/__root.tsx

26. browse_code — apps/web/src/components/ui/kbd.tsx

💭 I'm noticing the Kbd nested inside the outline Button looks fine visually, but I want to check whether other components like KeybindingsSettings use a dedicated KeybindingPill pattern instead, to make sure I'm being consistent with existing conventions.

27. git_tools — KeybindingPill|formatShortcutLabel

28. browse_code — apps/web/src/components/settings/KeybindingsSettings.tsx

29. browse_code — apps/web/src/keybindings.ts

💭 I want to check whether other places use combined shortcut labels with a single Kbd component, like CommandShortcut or MenuShortcut, since shortcutLabelForCommand is widely used across tooltips and menus.

30. git_tools — <Kbd

31. git_tools — Kbd>|Kbd

💭 Nothing strong found so far, so I'm digging into WindowCaptureSettings—the recording button's onKeyDown handling and how its aria-label overrides the visible Kbd shortcut text seem worth checking further. Then I'm noticing that captureStatus reads window.desktopBridge directly at render time without the same SSR guard used elsewhere, which could throw if the component is ever rendered outside a DOM environment. I want to check whether any tests render settings pages in a non-DOM context to see if this is actually exercised. But since this is a client-only SPA without real SSR, that risk seems low. I keep looking for a more substantial layout regression in the composer capture card, but the flex sizing with shrink-0 thumbnail and truncating metadata seems to hold up fine. I'm noticing that mixed-height items in the same wrap row (64px thumbnails next to 80px capture cards) could look slightly misaligned since fixed-height flex items don't stretch, but that's a minor cosmetic issue, not a real bug. I'm now checking the MessagesTimeline grid layout for similar spacing concerns. I don't see a clear violation yet, so I'm doing one more targeted pass through the ChatComposer and ChatView files, checking things like whether a raw button is being used where the shared Button component should be, since that's the kind of pattern the guidelines call out. The text-foreground usage in the appName label checks out against the semi-transparent card background, no issue there. More significant is that the onboarding dialog surfaces for every desktop user with a capture-capable bridge regardless of platform, which seems like it could catch existing users unexpectedly rather than just new ones. For the dialog closing behavior, I want to check whether persistClientSettings updates the settings snapshot synchronously — if it doesn't, the dialog might flicker before closing since open depends on that snapshot reflecting the change immediately after close() fires.

32. browse_code — apps/web/src/hooks/useSettings.ts

💭 The onboarding dialog's open state and settings row search targets look fine as is. I'm now checking whether the window capture settings page is properly wrapped in a settings page container. Actually, other pages like KeybindingsSettings do use SettingsPageContainer plus SettingsSection consistently, so I want to compare whether other settings sections use similar icon styling conventions.

33. git_tools — icon={<|size-4.5

💭 I'm curious how other settings sections handle icons, so let me check how SettingsSection is used elsewhere with an icon prop.

34. git_tools — <SettingsSection

35. git_tools — SettingsSection$|SettingsSection\b

💭 I notice none of the top-level settings sections currently use an icon on their SettingsSection heading—only diagnostic sub-sections and archived project favicons do. The new Window Capture page breaks that pattern by adding a camera icon at a different size than the existing convention, which is inconsistent with the rest of the settings pages. The simplest fix would be to drop the icon or match the existing muted convention. It's a mild inconsistency though, so I'm weighing whether it's flaggable or just aesthetic preference—leaning toward flaggable since it diverges from a shared component's established usage pattern across sibling pages. I'm also looking at whether the remove button on the window capture attachment card properly clears the reserved padding space. The image thumbnail cropping looks intentional, so that's fine. I'm now wondering whether the Window Capture settings section should really appear for browser users without a desktop bridge — comparing it to how IntegrationsSettings handles that same desktop-only scenario with an explanatory overlay, it seems like WindowCaptureSettings might be missing that same treatment.

36. git_tools — function DesktopOnlyBrowserDefaults

💭 There's an existing repo convention for exactly this case: client-local settings only editable in the desktop app, disabled individually but with a single explanatory status note rather than repeated per-row. The Window Capture page follows that pattern, disabling all 5 controls but placing the explanation only on the first row. That's worth flagging: reuse the desktop-only framing so all rows carry the reason, ideally by promoting DesktopOnlyBrowserDefaults from IntegrationsSettings into a shared component. There's also a second, smaller inconsistency worth checking — top-level settings sections never use header icons, while diagnostics sub-sections do, and this new page's heading seems to break that pattern too. So the icon still stands out since the heading itself uses full foreground color, meaning it inherits that strength rather than the muted tone every other section icon uses—worth a small suggestion to match the muted convention. I also want to confirm the desktop-only status behavior: only the first row gets the "Available in the desktop app" status while rows 2-5 just show disabled controls with full-strength text, and there's a further case where an older desktop preload without capture methods disables everything with an update prompt. Now I'm figuring out where to anchor my review comment for this file—since it's all new code, any line works, so I'm considering anchoring around the sound/flash/animation rows with a broader line range to cover the group-level issue rather than a single disabled prop. For the fix, I'll note there's no direct diff fix since this needs a cross-file change—promoting the desktop-only defaults into a shared settings layout—so I'll describe the smallest viable fix rather than providing a diff. I'm also settling on marking this check as a failure since the consistency issue is moderate enough to warrant it, consistent with what I'd expect from prior runs.

37. modify_pr — Reviewed the changed web UI surfaces (window-capture settings page, onboarding dialog, composer/timeline attachment cards, command palette entry, index.css keyframe). Earlier rounds' items (shared focus-event constant, DialogPanel composition, data-keybinding-capture, Tab handling, named keyframe animation, uniformly gated switches) all look resolved. Two remaining consistency points on the new settings page.


Agent Credits: 472 credits