Skip to content

fix(web): group unavailable capture settings

MacroscopeApp / Macroscope - UI Consistency failed Aug 24, 2026 in 9m 25s

UI Consistency: 2 issues found

  • apps/web/src/components/settings/WindowCaptureSettings.tsx — the shortcut row's SettingResetButton is not gated on the capture bridge, so it stays interactive inside the "Only available in the desktop app." group; because client settings persist through the local API, a browser click rewrites the desktop client's shortcut. IntegrationsSettings suppresses reset actions while a row is unavailable.
  • apps/web/src/components/settings/settingsLayout.tsx — the extraction of SettingsUnavailableGroup dropped the rationale comment that explained why the block is dimmed as a group, why the reason sits outside the dimmed area, and why client-only rows are disabled rather than hidden.

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: WindowCaptureSettings.tsx, settingsLayout.tsx (+ test), settingsSearch.ts, SettingsSidebarNav.tsx, IntegrationsSettings.tsx, chat/ChatComposer.tsx (capture card), chat/MessagesTimeline.tsx, chat/WindowCaptureAttachmentDetails.tsx, desktop/WindowCaptureCoordinator.tsx, desktop/WindowCaptureOnboardingDialog.tsx, CommandPalette.tsx, ChatView.tsx focus effect, lib/desktopWindowCapture.ts, index.css.

Checked against existing contracts: ui/dialog header/panel/footer composition (now used correctly), ComposerPreviewAnnotationCards as the sibling wide composer attachment card, Button/Switch/Kbd primitive usage, data-keybinding-capture contract in AppSidebarLayout, keyframe + animate-[…] + motion-reduce:animate-none ownership pattern used by prompt-stash-count-enter, and settings search/nav registration derived from SETTINGS_SECTION_LABELS.

Items considered and not reported: single-Kbd shortcut rendering vs. the local KeybindingPill in KeybindingsSettings (single-Kbd precedent exists in RightPanelTabs/CommandPalette, and KeybindingPill is not exported); dialog header icon tone/size differing slightly from RelayClientInstallDialog (aesthetic); upload-progress overlay spanning the wider capture card (does not overlap the metadata text at h-20); the shared window-event constant for composer focus (previously flagged, now owned by one module).


🔍 Agent activity — 30 tool calls

💭 I'm checking the current PR state since many earlier findings seem resolved, and now looking through the key changed web files—ChatView, CommandPalette, chat components, window capture coordinator and dialogs, and integration settings—for any remaining issues. The event dispatch for window capture is now defined as a shared constant, which partially addresses the earlier request for typed helpers, though it's still a bare event. Settings icon was removed and unavailable-group logic got extracted into settingsLayout as a reusable component, both good fixes. I want to verify SettingsUnavailableGroup's return type when there's no message, check the SettingsRow status prop, and confirm the dialog now uses DialogPanel. Looking at the composer capture card, its sizing changed from a fixed width to a flexible height-based layout. A few minor inconsistencies stand out: the "Capture now" row skips the searchableSetting wrapper other rows use, though that's likely fine since it's an action rather than a setting; the unavailable-group dimming styles look correct when no message is passed; and the Kbd component inside the button ties back to the earlier formatShortcutLabel comment, which seems resolved. The audio playback logic for capture sounds isn't really a UI consistency concern. I should pull up the actual files now to confirm these details around SettingsRow's props and the Switch/SettingResetButton components.

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

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

💭 With most prior issues fixed, I'm now scanning the current code for remaining inconsistencies—checking whether the "Capture now" row needs a search id, comparing how keyboard shortcuts render inside buttons elsewhere for consistency, and confirming the capture attribute is properly set. I'm also checking whether SettingsUnavailableGroup returning children as ReactNode is type-safe under React 19, verifying the onboarding dialog correctly gates on the desktop bridge and uses DialogPanel, and confirming the command palette's camera icon styling matches other items. Now I want to review the ChatComposer capture card layout, MessagesTimeline, and the global keyframes in index.css, looking closely at how the thumbnail and removal button are positioned within the card.

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

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

💭 I'm revisiting the earlier finding about WindowCaptureAttachmentDetails, where the app name text color inherits from the message bubble's foreground but sits on a different background surface, creating a mismatch with the sibling chips. Now I'm checking the composer's image thumbnail markup, which looks fine, and moving on to review other in-scope files: the WindowCaptureCoordinator, the onboarding dialog, IntegrationsSettings, SettingsSidebarNav, and the CSS keyframes. Looking at the onboarding dialog, it correctly gates on the desktop window capture bridge and relies on the dialog primitive's onOpenChange for escape handling. I want to verify whether other dialogs use a slightly different max-width convention (sm:max-w-md vs max-w-md), and check the header icon container's styling for consistency with other dialogs. I want to search for other dialog header icon patterns in the repo to see if this one matches an existing convention. Now checking the ChatView event listener for window capture focus—it uses a constant now exported from the desktop window capture module rather than a duplicated string literal, and the event name matches the app's namespace, so this earlier concern looks resolved without needing to re-flag it or the previewActionBus pattern. Now I'm scanning for whether other settings pages register similarly. I should verify whether the new section route needs to be added to other places like the sidebar nav ordering, section labels, mobile nav, or a settings search index, and check whether a search test enforces every search item id having a match. Since the diff only adds an icon to the section icons map, I need to check SettingsSidebarNav to confirm nav items are derived from the labels map and will pick up window-capture automatically.

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

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

💭 The desktopOnly flag matters because a browser result could point to an anchor that doesn't exist there — but window capture rows do render in the browser (just disabled within the SettingsUnavailableGroup), so leaving it unmarked as desktopOnly is fine since the anchors are present. Still, I want to check whether other desktop-only settings sections are handled consistently, since the window-capture section shows up in the web sidebar nav too. The Integrations page follows a similar pattern, showing a desktop-only group even though the page itself exists on web, which supports the SettingsUnavailableGroup approach as reasonably consistent. I don't see filtering logic in SETTINGS_NAV_ITEMS that hides entries for desktop-only features, so that seems fine as-is. Now I want to check whether the command palette maintains its own list of settings routes that could get out of sync, so I'll search for settings-related entries there.

7. git_tools — /settings/keybindings

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

💭 I should check whether DialogPopup accepts a className for width control and what the default is, plus look at how other dialogs handle header icons — let me peek at the dialog primitive file.

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

💭 The onboarding dialog composes properly now. For the header icon block, I want to check if there's an existing pattern for that rounded icon container style elsewhere in the codebase before finalizing it.

10. git_tools — size-(9|10|11|12) items-center justify-center rounded-(xl|lg|full)

💭 I notice a slight styling divergence from the other dialog—the icon color and border opacity don't quite match the existing pattern, but that's a minor aesthetic detail rather than a real issue. Moving on to look for more substantive problems: I'm checking whether SettingsUnavailableGroup's opacity dimming applies correctly to the status divs in WindowCaptureSettings, examining the composer capture card's layout to see if the padding properly accounts for the remove button's position, and verifying that the desktop window capture bridge call inside the command palette function happens safely on each render. I'm also checking whether an async run function on a command palette action is consistent with how other actions are typed, confirming the CSS keyframe animations follow the established convention for reusable global styles, and reviewing that the composer and timeline capture cards use consistent styling for window titles and grid layouts. The window capture coordinator's sound-playing logic isn't a UI concern, so I can set that aside. Now I'm considering the upload progress overlay: it's positioned to cover the bottom of the capture card, which worked fine over a small 64px thumbnail but on the newer wide capture card layout it would now stretch across and obscure the metadata text like the app name and title. This seems like a real but minor visual regression worth flagging, though I'm not fully certain uploads even apply to window captures.

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

💭 I'm checking whether there's already an existing wide-card pattern like ComposerPreviewAnnotationCard that shares geometry with thumbnail, text, and remove button, since that could mean the new capture card is duplicating an established layout rather than introducing something new.

12. git_tools — ComposerPreviewAnnotation|ComposerPendingElementContexts

13. browse_code — apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx

💭 Comparing the two composer attachment cards, I notice the window-capture variant reuses a full-width bottom overlay for upload progress instead of the inline text approach the annotation card uses. On a wide row layout, that overlay bar ends up covering the window title and details text, which is a real visual regression on the new card. I'm also checking the retry button positioning — it's fine since it sits over the fixed-size thumbnail — but the wide card's fixed height and thumbnail dimensions differ meaningfully from the annotation card's auto-sized square thumbnail, which is worth flagging as a geometry difference introduced by this change. The composer capture card's flex layout and truncation math for the details column both check out with sufficient padding, so I'm moving on to scan the remaining changed files for other issues I haven't caught yet.

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

💭 I'm noting that the palette action and "Capture now" button bypass the windowCaptureEnabled setting, though the flash behavior still ties into stored settings via configure. I also want to trace where settings comes from inside ChatComposer since it references windowCaptureAnimations.

15. git_tools — const settings =|useClientSettings(

16. git_tools — settings.windowCaptureAnimations

💭 I notice ChatComposer.tsx got skipped by grep because it's being treated as a binary file, even though I confirmed the windowCaptureAnimations reference exists at line 3227. I need to check how settings is accessed in that component instead.

17. browse_code — In apps/web/src/components/chat/ChatComposer.tsx (branch feat/window-capture, commit 7d5ce7e), inside the ChatComposer component, where does the settings variable used at settings.windowCaptureAnimations (around line 3227) come from? Show the declaration line(s) and the hook used (e.g. useClientSettings or useSettings), and whether the whole settings object is subscribed (causing re-render on any settings change) versus a selector. [{"url": "https://github.com/pingdotgg/t3code/tree/7d5ce7ea73d842e5f5046f69e6c5c7323d5bd790"}]

💭 The animation only fires on mount since it's keyed by attachment id, so a new capture triggers it correctly, which matches existing settings-driven animation patterns elsewhere. I'm now checking remaining candidates, starting with whether the upload progress overlay has any coverage gaps. Looking closer at the composer's window-capture card, I trace through the vertical geometry: the details area is vertically centered in an 80px card with a 28px icon, so content spans roughly the middle rows, while the bottom overlay only occupies about the last 14px. That means the overlay likely doesn't actually cover the title text, so this finding seems weak compared to the square 64px card case where the overlay was meant to sit over the thumbnail. I'm scanning for something more substantive. Checking WindowCaptureAttachmentDetails styling against the annotation card confirms consistent use of text-secondary-label, and the layout inside the message bubble looks fine with centered content and no extra padding. Contrast against the different background shades seems acceptable, accessibility on the composer's capture button looks fine with its aria-label, and the settings hook change returning a promise doesn't raise concerns either. Now I'm checking the keyframes placement in index.css against existing conventions, and reduced-motion handling looks properly applied at the call site with motion-reduce:animate-none. I'm also reviewing the SettingsUnavailableGroup refactor, noting it dropped an explanatory doc comment from the original DesktopOnlyBrowserDefaults that explained an important constraint — worth flagging since the guidelines call for preserving comments that explain platform-specific behavior. I've confirmed the current settingsLayout.tsx has no such comment where the shared primitive is defined, which strengthens the finding. I'm also noting the component's optional-message behavior, where it simply returns children when no message is provided, which seems like a reasonable API design.

18. git_tools — "(react|@types/react)":

💭 I'm checking whether returning ReactNode directly is consistent with React 19 conventions, then digging into git history to see if my earlier review comment about the disabled-row status pattern led to extracting shared logic like DesktopOnlyBrowserDefaults did in IntegrationsSettings.tsx. They did extract it, but dropped the doc comment explaining the rationale — that's a valid, minor finding worth flagging with a concrete fix, like moving the comment onto the shared helper. I also want to verify the removed InfoIcon and ReactNode imports in the IntegrationsSettings diff don't break compilation if ReactNode is still used elsewhere in the file.

19. git_tools — ReactNode|InfoIcon

💭 Good, the unused imports are removed properly. Checking the settingsLayout test assertion for the class string looks fine. I want to verify whether WindowCaptureSettings' recording button styling matches the pattern used in KeybindingsSettings' recorder.

20. git_tools — data-keybinding-capture

💭 I'm noticing the existing keybinding recorder uses an Input-based pill, while the new window-capture recorder uses a Button with a Kbd inside showing "Press shortcut…" — a slight UI divergence, though other settings rows also use Button size="sm" so it's reasonable. I want to check whether there's a KeybindingPill component for displaying shortcuts to see if it should be reused here instead. I'm wondering if nesting Kbd inside a Button might cause a doubled-border or control-in-control look, since Kbd likely has its own border and background styling that could clash with the button's outline. Let me search for other places where Kbd is used inside a Button to see if there's precedent for this pattern.

21. git_tools — <Kbd

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

💭 I'm noticing an inconsistency: KeybindingsSettings uses a proper KeybindingPill that splits shortcuts into separate Kbd elements within a KbdGroup, but the window-capture settings and onboarding dialog just dump the whole formatted shortcut string into a single Kbd instead. I want to check formatShortcutLabel itself in keybindings.ts to see what it returns and confirm how other usages render it.

23. git_tools — formatShortcutLabel|KeybindingPill

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

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

💭 I'm noticing an inconsistency: the settings page uses separate key chips (KeybindingPill with individual Kbd elements) while the new Window Capture row and onboarding dialog cram the whole shortcut string into one chip, so they look visually different from the established keybindings pattern. But I should check for counter-examples—RightPanelTabs and CommandPalette also use single Kbd elements for shortcut strings, which weakens the case a bit since precedent for single-chip display does exist elsewhere in the app, though arguably those are compact overlays rather than settings rows. I also need to consider that KeybindingPill expects a specific value format rather than a shortcut object, so reusing it isn't a trivial drop-in. Given that precedent exists for single-Kbd rendering elsewhere, this feels more like an aesthetic inconsistency than a hard violation, so I'm inclined to skip it. Instead I want to focus on a more concrete finding: the design-rationale comment that was lost when extracting DesktopOnlyBrowserDefaults into SettingsUnavailableGroup, since that comment explained a real constraint about layout and disabled-vs-hidden behavior. The fix here is to carry a condensed version of that rationale onto the shared component. I also want to double-check whether SettingsUnavailableGroup is still rendered unconditionally in IntegrationsSettings, or gated by an Electron check, since that affects whether the original constraint still holds.

26. browse_code — apps/web/src/components/settings/IntegrationsSettings.tsx

💭 I notice the IntegrationsSettings call site still keeps its ternary even though SettingsUnavailableGroup already handles the undefined-message case internally, so the "no group" logic ends up expressed two different ways—once via the ternary at Integrations, once via message={undefined} at WindowCapture. It's a minor consistency nit worth flagging, though not a big deal. The more important question is whether the removed doc comment counts as a real violation—the rules ask to preserve comments explaining Electron/browser constraints, and this one explained why the setting is disabled rather than hidden (a browser tab editing client settings would configure the wrong client). Some of that rationale survives in the remaining inline comments around lines 430 and 444-445, but the deleted portion also justified grouping controls together as a box rather than disabling them individually, which isn't fully captured elsewhere. I'm weighing whether to flag a single low-severity comment on SettingsUnavailableGroup for the lost rationale, since reporting anything at all fails the check—so I want to be sure it's a genuine issue before committing to that. Let me keep scanning for a clearer, more concrete UI problem, like checking the composer's window-capture card sizing where the thumbnail and details layout might overflow its flex-wrap container. I notice a possible mismatch when a capture card (taller) sits next to regular thumbnails (fixed shorter height) in the same wrapped row, causing uneven alignment—though this feels like a minor issue. Looking at MessagesTimeline's capture card layout, the grid pairs a fixed-width capture image with flexible details that stretch and center vertically, which seems fine, and non-capture images in the same grid just constrain their max height appropriately. The decorative alt text on the icon looks correct for accessibility. As for ChatView's new focus-scheduling effect using a global window event, it partially addresses a prior comment about having a single owner for the event name, though it stops short of typed dispatch/subscribe helpers -- since the substance was already addressed, re-flagging that would just be repetitive nagging, so I'll skip it. Now I want to verify the settings page shows "status" only on the first row correctly, with SettingsUnavailableGroup handling the browser case and desktop showing status only on the enabled row -- that looks right. I'll also check that SettingsUnavailableGroup returns children fine when there's no message, and confirm Switch components have consistent aria-label and title usage across settings rows like the other examples. Then I want to look at the CSS keyframes placement in index.css around the relevant lines.

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

💭 The keyframes lack an explanatory comment, minor stylistic inconsistency. I'm weighing whether the capture-card animation keyed to image.id but only remounting when the composer itself remounts is actually a real bug worth flagging. Motion-reduce fallback is present, so that's fine, and the overflow/scale transform inside the capture card doesn't create layout issues. Looking at the settings page more closely, I notice the shortcut reset button isn't gated on bridge availability like the other switches are — it's only disabled when the shortcut matches the default, which could create an inconsistency in the browser if a non-default shortcut was persisted from desktop. I'm tracing whether client settings are actually shared across desktop and browser sessions through the local server — the comment in IntegrationsSettings suggests editing from a browser tab could overwrite preferences belonging to a different client, implying these settings are stored server-side and shared rather than being purely local to each client instance. So the reset button breaks the expected pattern seen elsewhere: it stays clickable inside a dimmed "desktop only" group and would still write to the desktop client's shortcut setting from a browser. The fix is to gate its rendering or disabled state on the bridge flag, matching how BrowserAutoShowFloatingPreviewSetting suppresses its reset action — I want to check BrowserViewportSetting too to confirm this is the consistent pattern before finalizing the suggestion.

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

💭 I've confirmed reset actions are suppressed when the row is unavailable, which is a solid finding. Now I want to verify whether client settings can actually be non-default in the browser—since they're sourced from the shared local server, a desktop-set shortcut could appear in a browser tab with the reset button still live, letting it write another client's preference. I need to check the source of useClientSettings to confirm this.

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

💭 I've confirmed client settings persist through the local API shared with the desktop client, so the browser can show and reset the desktop's window-capture shortcut. I'm also considering whether to preserve the dropped rationale comment on the new SettingsUnavailableGroup component, but I want to be sparing—maybe just one behavioral comment plus this one, without being excessive. I'll keep the rationale from the deleted block since it explained a genuine client/browser constraint (why disabled rather than hidden), and I'll fold in a note about the redundant ternary at the Integrations call site since the shared helper now handles undefined values. Now I'm locating the exact line numbers to anchor these comments in the diff—starting with the resetAction block and disabled expression in WindowCaptureSettings.tsx, then moving to settingsLayout.ts. Writing indentation-matched suggestion... I also want to note that SettingResetButton rendering disabled still shows a dimmed ghost button, which fits this group's "disabled not hidden" approach even though Integrations opts to render null instead — either is fine, but I'll go with disabled here. For the second comment, I'm thinking about how the extracted helper dropped the rationale comment that DesktopOnlyBrowserDefaults had, and how the new optional-message contract affects the Integrations call site. I'll keep the comment-preservation point since the guideline explicitly calls for retaining comments about browser, virtualizer, theme, or Electron constraints. Since findings exist, this review should be marked as a failure with a short summary, then posted via github_pr_review.

30. modify_pr — Two findings on the settings surface introduced/changed here. The composer and timeline capture cards, the extracted SettingsUnavailableGroup, the keyframe ownership, and the onboarding dialog composition all look consistent with the existing primitives now.


Agent Credits: 386 credits