Repository navigation
UI Consistency: 1 issue found
apps/web/src/components/ChatView.tsx (L2312-L2324) — resolveInteractionModeProviderSelection resolves the Plan-capability instance from an unsorted entry list with a plain ready-first fallback, while ChatComposer (which renders the toggle) uses sortProviderInstanceEntries(...) and prefers requestedDriverKind entries. With an explicit omp_* instance configured and a stale/disabled stored selection, the composer can show the Plan toggle while ChatView normalizes the mode back to default, leaving a visible but dead control. Suggested fix: share the same sorted list and driver-kind preference, or let the composer own the effective-mode normalization.
No other findings in scope: the new OmpIcon follows the existing OpenCodeIcon light/dark rect pattern, and the provider icon map, settings driver metadata, picker badge, and the composer's toggle/slash-command/Shift+Tab gating are all consistent with the shared contracts.
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 include patterns):
apps/web/src/components/Icons.tsx— newOmpIcon; compared againstOpenCodeIcon'sdark:hidden/hidden dark:blockbrand-fill pattern; non-square viewBox has precedent (OpenCode 32x40).apps/web/src/components/chat/providerIconUtils.ts,apps/web/src/components/settings/providerDriverMeta.ts,apps/web/src/session-logic.ts— icon/label/badge registration consistent with existing drivers.apps/web/src/components/chat/ChatComposer.tsx— verifiedcomposerProviderControls.showInteractionModeTogglenow gates the footer toggle, the compact controls menu,/planand/defaultslash items, the slash handler, and the Shift+Tab shortcut (single owner, wired to both footer variants at L3524-L3554).apps/web/src/components/ChatView.logic.ts/.tsx— compared the new helper againstresolveSelectableProviderInstanceEntry,isProviderInstancePickerReady,deriveProviderInstanceEntries(documented server ordering),applyProviderInstanceSettings, andsortProviderInstanceEntries; send path is separately normalized againstctxSelectedModelSelection, so the residual impact is the toggle/effective-mode disagreement described above.apps/web/src/providerModels.ts— removal ofgetProviderInteractionModeTogglehas no remaining callers.apps/web/src/hooks/useHandleNewThread.ts,apps/web/src/composerDraftStore.ts,apps/web/src/lib/contextWindow.ts,apps/web/src/components/settings/DiagnosticsSettings.tsx— provider-list additions only, no primitive or Tailwind ownership changes.
No CSS files were touched by this PR, so no emitted-CSS or theme-variant review was required.
🔍 Agent activity — 27 tool calls
💭 I'm going through the list of changed files scoped to the web app, noting updates to ChatView, its logic and tests, Icons, ChatComposer, and provider icon utilities. Now I want to check theme handling conventions in Icons.tsx — the new OmpIcon uses
dark:hiddenandhidden dark:blockclasses for switching between light/dark variants, so I should verify whether other icons follow this same pattern or use different approaches likecurrentColorfor automatic theme adaptation. I'm also noticing that OmpIcon hardcodes a fill color rather than usingcurrentColoror a semantic color token, which could be an inconsistency worth flagging since other icons might rely on theme-aware coloring instead of fixed hex values. I'm also aware that ChatView.logic.ts and ChatView.tsx have been flagged repeatedly across many previous review iterations (going back through commits f3412e4, e429f07, f3a3536, d45fe9e, and 50c5148), so I need to check whether the current code actually addresses the most recent feedback before deciding if raising it again is warranted — only if there's evidence the code changed in direct response to that comment.
1. git_tools — dark:hidden|hidden dark:block|currentColor
2. browse_code — apps/web/src/components/Icons.tsx
💭 The OmpIcon matches the existing OpenCodeIcon pattern with light/dark variants, so that's consistent. I'm now rechecking whether the previously flagged mismatch between ChatView.logic.ts and ChatComposer's selection resolution still exists at the current head state. Recalling that prior comments described a mismatch between the resolved provider instance in ChatView versus ChatComposer, causing a Plan toggle that renders but doesn't actually function—that's a real behavioral consistency issue with the shared control worth verifying in the current code.
3. browse_code — apps/web/src/components/ChatView.logic.ts
4. git_tools — resolveSelectableProviderInstanceEntry|isProviderInstancePickerReady
💭 I'm checking providerInstances.ts to review the resolveSelectableProviderInstanceEntry function and the isProviderInstancePickerReady logic.
5. browse_code — apps/web/src/providerInstances.ts
6. browse_code — apps/web/src/providerInstances.ts
💭 I'm noticing that resolveInteractionModeProviderSelection essentially reimplements the shared resolveSelectableProviderInstanceEntry logic locally, just with a candidate list and locked-provider filter added in. The fallback tiers now align with ChatComposer, but this duplication of the shared helper could still be flagged against the "prefer the smallest durable contract" principle. I want to check ChatComposer's selectedInstanceId resolution around lines 871-910 to compare its candidate chain against ChatView's.
7. browse_code — apps/web/src/components/chat/ChatComposer.tsx
💭 I'm comparing ChatComposer's provider selection logic against ChatView's interactionModeSelection — both use similar candidate chains but ChatComposer adds a lockedContinuationGroupKey filter and prefers requestedDriverKind in its fallback, while ChatView just filters by lockedProvider and falls back to ready-then-non-error entries. Since showInteractionModeToggle derives from a driver-level presentation constant, two instances of the same driver kind will share the capability, so the continuation-group mismatch likely doesn't matter — but the differing fallback logic (requestedDriverKind-preference vs. first-ready) could pick different driver kinds and thus different capability values in edge cases. But the actual submission path re-normalizes interaction mode against the composer's real selection before sending, so the toggle mismatch wouldn't affect what's actually sent — it might just cause a brief visual inconsistency in whether the plan toggle shows in the composer footer versus what ChatView thinks the interaction mode is. The remaining differences around lockedContinuationGroupKey and requestedDriverKind fallback preference are subtle edge cases limited to multi-instance different-driver fallback scenarios, and don't really touch driver-level capability. The bigger concern is that ChatView duplicates provider-instance selection logic instead of reusing the composer's shared resolution helper, which violates having a single source of truth for this contract. I also notice the interaction mode selection in ChatView -- computed by deriving provider instance entries and applying settings -- runs inline on every render without memoization, which is a minor performance concern but not a correctness issue.
8. browse_code — apps/web/src/components/ChatView.tsx
💭 I'm narrowing down the last real discrepancy: the fallback logic differs because ChatComposer prefers the requestedDriverKind while ChatView just picks the first ready one, and since toggle visibility depends on driver kind, this mismatch could cause the toggle to show or hide inconsistently. I'm weighing whether this edge case is significant enough to flag again given it's already been raised several times. Since the fallback path only triggers when no candidate is selectable, and submissions get renormalized to the composer's actual selection anyway, the practical risk seems limited to the toggle's visual state rather than any wrong action being sent. I need to check whether interactionMode is actually rendered somewhere visible in ChatComposer when the toggle is hidden, to confirm if there's a genuine user-facing bug here. Given this area's already been flagged multiple times with the author converging on fixes, raising this narrower nuance again risks feeling repetitive rather than adding a clear, concrete violation. I want to look at one more clearer candidate: whether the same showInteractionModeToggle gating that affects slash commands and the shortcut was already addressed at a specific line, since that was flagged in a prior run. Now I want to check if there are other places in the web app that render plan mode state without the same capability gate—like a header indicator or model picker—so I should search for other consumers of interactionMode display outside the composer.
9. git_tools — interactionMode
💭 I'm checking those other plain interactionMode references around lines 5374, 5398, and 5431 to see if they're follow-up sends or scheduled tasks that also need updating to submissionInteractionMode, and also want to check the ScheduledTasksSettings file for similar usage.
10. browse_code — apps/web/src/components/ChatView.tsx
11. browse_code — apps/web/src/components/ChatView.tsx
💭 The send path relies on the composer's actual selected model as the source of truth, meaning interactionModeSelection in ChatView really just drives the displayed interactionMode passed into ChatComposer and its change handler. Now I'm checking whether ChatComposer's toggle visibility, based on selectedInstanceId, stays consistent with this. Since ChatComposer already resolves its own selectedModelSelection and exposes getSendContext(), the duplicated resolution logic in ChatView is likely the real source of divergence, particularly around lockedContinuationGroupKey and the requestedDriverKind fallback. I'm leaning toward flagging this inconsistency and recommending a shared resolver rather than two separate resolution paths, though I recall this same area has been flagged in prior passes already. Now I'm trying to determine if this is a genuine, reachable bug rather than a theoretical concern -- specifically whether ChatView's fallback could ever select a different driver kind than ChatComposer's selectedInstanceId in a state a user could actually hit. When the thread has a locked provider, both components filter identically by that provider, so they'd agree. The divergence only matters when lockedProvider is null, in a fresh draft thread, where ChatComposer falls back through requestedDriverEntries logic while ChatView's fallback picks the overall ready-first candidate instead -- these could genuinely disagree. If a draft's active provider becomes disabled but another instance of the same driver kind is still enabled, ChatComposer might resolve to that same driver kind while ChatView's ready-first ordering could pick a different one entirely, causing the toggle to hide while the interaction mode stays stuck on "plan." The practical impact seems limited though -- other UI elements like the sidebar plan badge read from thread.interactionMode directly rather than this derived state, and sending normalizes the mode anyway, so the visible consequence is likely just a stale "plan" value in persisted draft state rather than anything user-facing. Given that severity is low and this has already been flagged in prior passes, raising yet another narrow divergence here would be diminishing-return nitpicking rather than a genuine finding. I should move on to checking the other in-scope changes for real UI-consistency issues instead -- the diagnostics regex change, provider icon utilities, provider driver metadata additions, and the session logic provider options all look fine on inspection. With the non-square viewBox scaled into a square box, the icon would letterbox vertically, but I realize OpenCode already uses a non-square portrait viewBox similarly, so this mixed-aspect-ratio approach is already precedented and not a real issue. I'm now moving on to check the provider ordering in providerDriverMeta.ts.
12. browse_code — apps/web/src/components/settings/providerDriverMeta.ts
💭 I notice
session-logic.tsbadges omp with "new" via pickerSidebarBadge, while providerDriverMeta uses badgeLabel for early-access—so I want to check how other recent providers like cursor, grok, and opencode are badged there for consistency.
13. browse_code — apps/web/src/session-logic.ts
14. git_tools — pickerSidebarBadge
💭 The badge inconsistency isn't really a violation since OpenCode also lacks a badgeLabel, so that decision seems fine. I want to check providerIconUtils.ts next to confirm the icon map and label consistency. I should verify wherever provider icons render, that omp entries get an icon through the updated PROVIDER_ICON_BY_PROVIDER map, and also check whether removing
getProviderInteractionModeTogglefrom web's providerModels.ts left any dangling callers.
15. git_tools — getProviderInteractionModeToggle
16. browse_code — apps/web/src/components/chat/providerIconUtils.ts
💭 I'm confused because the diff indicated that function was removed, yet the grep still shows it defined at line 56 in providerModels.ts. I need to actually view that file section directly to check whether it was reintroduced or the diff range doesn't match what I expected.
17. browse_code — apps/web/src/providerModels.ts
💭 The grep result seems mismatched with the actual line content, so I'm wondering if it pulled from a stale revision instead of the commit SHA I specified in the URL—possibly the tool defaulted to the clone commit rather than respecting my tree reference. Regardless, confirming the head revision no longer contains that removed function is what matters here. I'm now checking whether
getProviderModelCapabilities's plan-mode handling, which strips opencode's plan agent option when legacy plan mode is disabled, has any bearing on OMP—but since that plan-agent option is opencode-specific, it doesn't apply. Similarly,ChatComposer's provider state computation strips plan-related options based on planModeEnabled alone, which again is irrelevant for OMP. I'm also double-checking howCompactComposerControlsMenuconsumes interactionMode. I need to trace whether the plan/build radio in the compact menu is gated bycomposerProviderControls.showInteractionModeToggle, and separately confirm that the footer toggle prop at line 327 is wired from that same source rather than some other planModeUiEnabled flag—so I'm searching ChatComposer for all references to showInteractionModeToggle to verify the wiring is consistent.
18. git_tools — showInteractionModeToggle
💭 I need to find where showInteractionModeToggle is passed into ComposerFooterModeControls and CompactComposerControlsMenu, but the grep output got cut off at a binary marker in ChatComposer.tsx, so I'll retry with a more targeted search for that exact prop pattern.
19. git_tools — showInteractionModeToggle=
💭 The grep hit a binary marker, likely a stray NUL byte in the file that's blocking search past that point. I'll switch to viewing specific line ranges directly to find where ComposerFooterModeControls and CompactComposerControlsMenu are used, since the file is too large to grep through fully.
20. browse_code — In apps/web/src/components/chat/ChatComposer.tsx (branch autopilot/omp-provider-20260825170003, commit fbc81fb), show the JSX where ComposerFooterModeControls and CompactComposerControlsMenu are rendered, including the exact value passed to their showInteractionModeToggle props, with line numbers.
💭 I'm checking whether other surfaces still render the Plan/Build toggle for an OMP-selected instance—ChatView's draft composer reuses ChatComposer, and the sidebar plan badge is thread-based, so I'm now looking at ScheduledTasksSettings to see if it gates a plan/default control per provider based on interactionMode. Since the patch introduces a provider whose plan mode is unsupported, if scheduled tasks let a user select OMP with Plan mode, the UI would offer a mode that's silently normalized away server-side by ProviderCommandReactor—an offered-but-ineffective control. That's a real gap the patch makes relevant, and since the file is in scope, I want to inspect it to see if it exposes a plan mode selector without capability gating.
21. git_tools — interactionMode|planMode
💭 Odd that this scoped grep returns nothing when the earlier broader search found matches in ScheduledTasksSettings. The path glob might not be matching correctly, so I should just view the file directly instead.
22. browse_code — apps/web/src/components/settings/ScheduledTasksSettings.tsx
💭 That's odd, the file was found at that ref before. Let me try globbing instead to track down the issue.
23. browse_code — */ScheduledTasks
💭 The glob search comes up empty, which is odd since the earlier grep found that ScheduledTasksSettings.tsx file. Maybe it's a branch mismatch or tool flakiness, so I'll try the glob again with the plain repo URL instead.
24. browse_code — apps/web/src/components/settings/*.tsx
💭 I'm confused about that file not existing at HEAD despite the earlier grep result — probably a tool inconsistency, but it's outside this PR's scope so I'll drop it and move on. Now weighing whether the ChatView/ChatComposer provider-instance divergence still needs flagging. Most of the earlier issues have converged after five rounds, and the remaining gap (requestedDriverKind fallback vs composer's selection) feels narrow, but I want to substantiate it concretely before dismissing it, so I'm checking whether ChatComposer's unlockedSelectedProvider can resolve to different instances than ChatView's ready-first logic.
25. browse_code — apps/web/src/components/chat/ChatComposer.tsx
💭 I notice ChatComposer sorts entries via sortProviderInstanceEntries before resolving, but ChatView.tsx applies the settings without that sort step. Since the "first ready" fallback tier is order-dependent, this means ChatView could end up picking a different default instance than ChatComposer even with identical fallback logic—just because the input ordering differs. That's a concrete, fixable bug. I'm also considering whether ordering actually matters practically: deriveProviderInstanceEntries follows the raw server snapshot order, while the sorted version is default-first per driver kind. If the wire order happened to put a custom omp instance ahead of codex, the two components could genuinely diverge in which instance gets selected when there's no clear candidate—something that would only surface in edge cases like a brand-new install with both omp and codex enabled and no active draft selection yet. That case turns out to be exactly what a fresh draft thread with no stored selection looks like—no composer activeProvider, no thread, no project default. Both components fall through to their fallback logic, but ChatComposer resolves to a sorted default (say codex), while ChatView resolves to whichever entry is first-ready in wire order (possibly omp). Tracing the toggle behavior through, I see that clicking Plan calls handleInteractionModeChange, which compares against the current interactionMode and, if different, updates the draft context's interactionMode—so this discrepancy could manifest as a toggle that behaves inconsistently depending on which provider each component silently picked. Now I'm checking whether that ordering mismatch is actually reachable in practice — I need to look at how sortProviderInstanceEntries and deriveProviderInstanceEntries construct their lists to see if omp could realistically land first in ChatView's unsorted order.
26. browse_code — apps/web/src/providerInstances.ts
💭 I've confirmed that explicit provider instances are emitted before synthesized defaults, which means ChatView's ready-first fallback could pick an explicit instance like omp_work while ChatComposer's logic, which prefers requestedDriverKind, ends up selecting codex instead—creating a mismatch when a user has both an explicit omp instance configured and codex as their intended driver. ChatView also converges on omp_work, so no divergence there. The real gap surfaces when the stored selection points at a stale or disabled candidate like a deleted codex instance: ChatComposer's requestedDriverKind resolution could still favor codex and surface a ready codex default with a toggle shown, while ChatView's unsorted ready-first logic lands on omp_work and normalizes plan back to default — producing that dead-toggle scenario when a stale selection coexists with an explicit omp instance. There's also a simpler possibility: since ChatView skips sorting, its "first entry" read could diverge purely due to ordering differences from ChatComposer. I think a single precise inline comment is warranted: the helper's fallback tier ignores the requested driver kind and works from an unsorted list, so it can end up resolving a different instance — and thus a different Plan capability — than what ChatComposer actually renders via selectedInstanceId. The smallest fix is to pass the same sorted list and prefer the composer's requested driver kind, or better, have ChatComposer be the single source of truth for the effective capability. The cleanest option would be deriving interactionMode normalization from ChatComposer's own selection rather than re-resolving it in ChatView, but getSendContext is imperative and not reactive for render — so an alternative is having ChatComposer publish its selected instance upward via a callback. I'll place this as one inline comment on the ChatView.tsx call site around lines 2312-2324, framed as a suggestion about the sorting and driver-kind divergence rather than a concrete code diff, since the fix touches multiple files. I need to make sure the line range falls within the added diff hunk (2309-2329), so I'll anchor the comment at line 2324 with start_line 2312.
27. modify_pr — One consistency concern on the new Plan-mode capability wiring in the web composer. Everything else in scope (the OmpIcon dark/light pattern matching OpenCodeIcon, icon/label/badge registration in providerIconUtils, providerDriverMeta, session-logic, and the composer's toggle/slash-command/shortcut gating) looks consistent.
Agent Credits: 446 credits