Repository navigation
UI Consistency: 1 issue found
apps/web/src/components/ChatView.tsx(L2311-2318): the newinteractionModeSelectionresolves the capability instance with a shorter chain (composerActiveProvider→activeThread.modelSelection) thanChatComposer's validated resolution (activeProvider→session.providerInstanceId→ thread → project default, each filtered byenabled && isAvailable). When the draft'sactiveProviderpoints at an instance that is no longer selectable (OMP disabled in Settings, or unavailable after a failed health check), the composer falls back to a plan-capable instance and renders the Plan toggle while this normalization keeps forcinginteractionModetodefaultfrom the stale OMP snapshot, leaving the toggle, the compact-menu entry and Shift+Tab as no-ops. Suggested fix: normalize from the composer's resolved selection (already exposed viagetSendContext().selectedModelSelection) or drop the display-level normalization and rely on the composer capability gate plussubmissionInteractionMode.
One inline comment was posted on the PR.
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/ChatView.tsx, components/Icons.tsx, components/chat/ChatComposer.tsx, components/chat/providerIconUtils.ts, components/settings/DiagnosticsSettings.tsx, components/settings/providerDriverMeta.ts, components/settings/ProviderSettingsForm.test.ts, composerDraftStore.ts, hooks/useHandleNewThread.ts, lib/contextWindow.ts(+test), providerModels.ts, session-logic.ts.
Checks performed and cleared:
OmpIconfollows the existingOpenCodeIcontheme pattern (dark:hidden/hidden dark:blockduplicated shapes with brand fills); no new global CSS, no raw control reconstruction, no primitive overrides in the diff.- Provider registration is consistent across
providerDriverMeta.ts(settings card + icon + schema),providerIconUtils.ts,session-logic.ts(pickerSidebarBadge: "new", matching OpenCode's treatment),lib/contextWindow.tsdisplay name, and all threecomposerDraftStoreprovider-key lists. - Model rows already render
subProvideron web (ModelListRow,modelSelection.toAppModelOption,modelPickerSearch), so OMP's upstream-provider disambiguation works without further changes. - Prior-run findings are addressed at this head: the Shift+Tab shortcut (
ChatComposer.tsxL2090) and the slash-command path (L1836) are now gated oncomposerProviderControls.showInteractionModeToggle, andinteractionModeSelectionnow prefers the composer draft'sactiveProvider. The remaining gap is only the unavailable/disabled-instance andsession.providerInstanceIddivergence described above. useHandleNewThreadresolves capability providers from an explicit sourceenvironmentIdrather than the globally active environment; destination normalization still happens at send time and on the server.
🔍 Agent activity — 22 tool calls
💭 I'm checking which files fall within scope, looking specifically at the changed web components like ChatView, Icons, ChatComposer, providerIconUtils, and the diagnostics settings files. Looking back at two previously flagged issues, I want to confirm they've actually been fixed. The ChatComposer Shift+Tab gating logic now correctly checks both the plan mode flag and interaction mode toggle before returning false, which addresses the earlier concern. Similarly, the ChatView interactionModeSelection ordering now prioritizes composerActiveProvider before falling back to the thread's modelSelection, which resolves the ordering issue raised in prior review rounds. Now I want to check if there's anything new worth flagging, particularly around theme handling in OmpIcon within Icons.tsx. It uses separate dark/light classes for showing different fills rather than a single currentColor approach, which seems inconsistent with how other provider icons like OpenAI or ClaudeAI likely handle theming — I should check those to confirm the convention. There's also a mobile version using isDarkMode conditionally, but that's outside the scope I'm reviewing.
1. git_tools — dark:hidden|hidden dark:block|currentColor|fill-foreground
2. git_tools — export const (OpenCodeIcon|GrokIcon|CursorIcon|ClaudeAI|OpenAI): Icon
💭 I'm noting that OmpIcon follows the same dark/light SVG pattern as OpenCodeIcon, which is consistent even if slightly duplicative. I'm also checking whether spreading props with className works correctly here, since the internal rects set their own className that could override any passed-in value. Now I want to shift focus to ChatComposer, particularly how selectedInstanceId relates to selectedProvider and the provider guidance logic, so I'll look at the composerProviderControls usage along with useHandleNewThread and the corresponding ChatView sections.
3. browse_code — apps/web/src/components/ChatView.tsx
4. browse_code — apps/web/src/components/chat/ChatComposer.tsx
💭 I'm tracing how ChatView's interactionModeSelection ties to composerActiveProvider, checking whether it resolves to an instanceId and how it relates to selectedProviderByThreadId, while also comparing it against how ChatComposer derives selectedInstanceId from candidateInstanceIds.
5. git_tools — composerActiveProvider
💭 I'm checking that interactionModeSelection now prefers composerActiveProvider, and want to verify this matches how ChatComposer derives selectedInstanceId — confirming the priority order of composer draft, then thread modelSelection, then project default.
6. git_tools — selectedInstanceId =
💭 I'm noticing a mismatch: ChatComposer prioritizes the active thread's running session providerInstanceId over thread.modelSelection and filters to enabled/available instances, while ChatView's interactionModeSelection only falls back to thread.modelSelection when composerActiveProvider isn't set—so these two paths could disagree on which provider is actually selected. I'm trying to pin down a concrete regression scenario: if composerActiveProvider references a disabled instance, ChatComposer might fall back to a different capable provider (showing the plan toggle), while ChatView normalizes using the stale disabled instance and forces the default mode—meaning the toggle could appear while the underlying interaction mode is stuck on default. This is worth flagging as a third comment since it's a narrower but still real edge case: the persisted composer draft store could retain an "omp" selection even after that instance gets disabled in settings, causing the divergence between what composer resolves to versus what ChatView resolves to. Actually since modelSelection updates only on send and session is created at first turn, this divergence scenario seems unlikely in practice. Given diminishing returns on that angle, I want to check useHandleNewThread more closely — it resolves providers from the found environment's serverConfig, and sourceEnvironmentId comes explicitly from route target or draft, which satisfies the environment-scoping rule. But I'm wondering if carryInteractionMode, computed from source providers, could get misapplied if the new thread targets a different environment than the source. The recurring findings so far cluster around UI consistency: shared controls, Tailwind ownership, scroll behavior, and environment routing. The interaction-mode gating issue fits as a legitimate UI consistency finding (toggle visible but functionally dead), and I've already flagged it twice. I want to look at the full useHandleNewThread implementation to confirm whether the new thread's target environment can actually differ from the source.
7. browse_code — apps/web/src/hooks/useHandleNewThread.ts
💭 I'm noticing a potential issue: when creating a new thread in a destination environment, normalizing
carryInteractionModeagainst the source environment's providers seems questionable since the destination's provider capabilities are what actually matter. Though this might be mitigated since the destination normalization also happens later at send time against the destination's provider state. I want to check ChatView around lines 5390-5430 and 5560-5580 to verify the submission gating change is consistent there.
8. browse_code — apps/web/src/components/ChatView.tsx
9. browse_code — apps/web/src/components/ChatView.tsx
💭 I'm noticing the plan follow-up submission path uses
followUp.interactionModedirectly without going through the normalization step, so I need to trace where that value originates to see if it could end up as "plan" for a provider that doesn't support it.
10. git_tools — resolvePlanFollowUp|followUp =|planFollowUp
💭 That plan follow-up prompt only applies when in plan mode, so it doesn't affect OMP here. I'm checking the diagnostics settings regex that detects agent processes by name, noting it now includes "omp" alongside codex, claude, opencode, and cursor — though grok is still missing — and wondering if such a short token risks false matches. The word boundary prevents false positives like matching inside "comp", so that's fine. The OmpIcon mapping, driver metadata, and provider options entries all look consistent with existing patterns. I want to verify that the removed toggle function has no remaining consumers, and check whether the composer draft store's hardcoded provider-key arrays were all updated to include "omp" or if some were missed — I should grep for those arrays.
11. git_tools — getProviderInteractionModeToggle
12. git_tools — "codex", "claudeAgent", "cursor", "opencode"
💭 I noticed the diff supposedly removed that function, but the grep against the head commit still shows it defined at line 56 in providerModels.ts, which seems contradictory. I should just open the file directly to check its actual current state.
13. browse_code — apps/web/src/providerModels.ts
💭 The grep result seems mismatched with the actual line content, so I should re-run the search with a more distinct pattern to confirm whether that function still exists.
14. git_tools — getProviderInteractionModeToggle
15. git_tools — showInteractionModeToggle
💭 Good, that earlier grep hit was stale, so all composer toggle paths are gated now. I should check other web surfaces too, like the new-thread or landing composer, to see if they render an interaction mode toggle or plan chips elsewhere by searching for usages of the toggle component.
16. git_tools — interactionMode
💭 I should also check for other thread creation paths outside ChatView, like project-thread start or CommandPalette flows that send interactionMode. I noticed Sidebar.logic.ts references thread.interactionMode for a plan badge indicator, but that's just a UI detail since the server normalizes it. Now I want to look at ChatView.logic.ts around line 162 where draftThread.interactionMode is likely used to build the start turn request, to confirm whether that path gets normalized too.
17. browse_code — apps/web/src/components/ChatView.logic.ts
💭 That sidebar plan indicator check looks fine since the server already normalizes stored state now. I want to also look at providerDriverMeta.ts to check the ordering there.
18. browse_code — apps/web/src/components/settings/providerDriverMeta.ts
19. browse_code — apps/web/src/session-logic.ts
💭 I'm checking whether OMP as a new driver should have a badge for consistency—it uses
pickerSidebarBadge: "new", matching opencode's treatment which also lacks abadgeLabel, so this isn't a finding. Now I'm moving on to look at ChatCom. Mobile added asubProviderline to the model row subtitle so upstream providers get disambiguated, but I'm checking whether web's model picker does the same. If web hasn't been updated to showsubProvider, OMP models with identical display names from different upstream providers—like two "Kimi K2.6" entries—could appear indistinguishable in the picker, which would be a cross-client consistency gap worth flagging. I need to look at how web builds its model options to confirm this.
20. git_tools — subProvider
21. git_tools — getAppModelOptionsForInstance
💭 Web already handles subProvider fine, so that's not a gap. Now I'm digging into whether the ChatView versus ChatComposer selection-resolution mismatch I flagged earlier is still a real discrepancy worth another comment, comparing how each resolves the active provider from composer state versus thread session data. Tracing through the divergence: if composerActiveProvider is set but not enabled/available, the composer falls back to session/thread/project selection while ChatView still trusts the stale provider. That means if the stale instance is incapable (like OMP) but the fallback is codex, the composer would show the Plan toggle even though ChatView forces interactionMode to "default" — clicking the toggle sets draft interactionMode to "plan," but ChatView immediately renormalizes it back to default, so the toggle snaps back and plan mode becomes effectively unreachable through the UI, even though submission normalization would use the composer's actual codex instance. For the reverse case, I'm considering when composerActiveProvider is null, the thread's modelSelection points to codex, but the session's providerInstanceId is omp — since composer resolution checks session first, it resolves to omp and hides the toggle entirely, while ChatView uses codex and permits plan mode. This creates the exact "trapped in plan mode" scenario the code comments warn about, since interactionMode could already be "plan" from the thread state with no toggle visible to exit it. I'm now checking whether session.providerInstanceId can genuinely diverge from thread.modelSelection.instanceId — plausible if a thread starts a session with omp, then the user switches to codex mid-thread, updating thread.modelSelection while the running session's providerInstanceId still reports omp until a restart occurs. Case (a), the persisted composer draft pointing at a disabled instance, feels more concrete and reachable than this speculative reverse scenario. I'm confirming that providerStatuses includes all configured instances regardless of enabled state, and that showInteractionModeToggle for OMP is set from the presentation config independent of the enabled flag — meaning a disabled instance's snapshot still carries showInteractionModeToggle: false, producing a rendered-but-dead Plan toggle. This does appear to be introduced by the changed lines, since the new ChatView normalization forces interactionMode to a default based on the resolved selection. I'm now weighing whether to post a third comment on the same line — the guidance says duplicates are acceptable when the code changed in direct response to the earlier flag, which it did, so a refined comment about the residual mismatch seems permissible. I still need to check whether this scenario is realistic enough: a user selects OMP for a thread, then disables that instance in Settings, leaving a persisted draft pointing at a now-unavailable provider. The likely culprit is
isAvailable— if OMP isn't installed or its binary goes missing, composer falls back to codex and shows the toggle, but ChatView keeps normalizing against the stale, unavailable OMP snapshot, permanently forcing Plan off. The cleanest fix is to have ChatView normalize from the composer's already-resolved selection instead of re-deriving it independently. I'm leaning toward leaving a single inline comment suggesting this, rather than a concrete diff, since removing ChatView's normalization outright would also strip its intended "reset stale plan state" behavior — better to have it derive from the same resolution the composer already uses. Given two prior comments already exist on that line, I'm weighing whether a third would be redundant, but the divergence still seems real enough to flag as a potential regression risk. Though on reflection, prioritizing composerActiveProvider matches what the composer already does at the top of its priority list, so the remaining cases may just be edge cases involving disabled states. Good, so with codex the composer does render normally, confirming this is worth flagging. I'll draft one concise comment for ChatView.tsx around lines 2311-2318, noting thatinteractionModeSelectionderives the capability instance from a shorter path thanChatComposer's validated resolution chain, which could cause a mismatch in edge cases. If the draft's provider becomes unavailable, the composer falls back to a plan-capable instance and shows the Plan toggle, but this normalization still forcesinteractionModeto default from a stale snapshot, making the toggle and Shift+Tab no-ops. I'll suggest either gating off the composer's already-exposedselectedModelSelection, or dropping the display-level normalization entirely in favor of the composer gate plussubmissionInteractionMode. I'll post this as a review comment specifying the exact line range and side needed for the tool call.
22. modify_pr — One UI consistency finding: the composer's Plan-toggle gate and ChatView's interaction-mode normalization still resolve the target provider instance through two different chains, so the toggle can render while Plan is permanently forced off. Details inline.
Agent Credits: 334 credits