Repository navigation
UI Consistency: 1 issue found
1 finding
apps/web/src/components/ChatView.tsx(line 6697):workspaceSkills={activeSkills}passes an already-fallback-resolved value (workspace query result oractiveProviderStatus?.skills) into ChatComposer. BecausecomposerSkillsprefers any non-emptyworkspaceSkillsoverselectedProviderStatus?.skills, the machine-scoped snapshot of ChatView's resolved instance can override the composer's own instance resolution. ChatView resolves the instance by id only, while ChatComposer also filters onenabled && isAvailableand locked driver/continuation group, so a persisted-but-disabled selection makes the$picker list another instance's skills. Suggested fix: passworkspaceCapabilitiesQuery.data?.skillsand let ChatComposer own the fallback, matching howworkspaceSlashCommandsis passed.
No styling, Tailwind ownership, CSS/theme, or scroll/virtualization issues were found — this PR introduces no class or CSS changes.
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 reviewed (in scope)
apps/web/src/components/ChatView.tsxapps/web/src/components/chat/ChatComposer.tsxapps/web/src/state/providerSkills.ts
Context consulted
packages/client-runtime/src/state/providerSkills.ts(atom family, stale/idle windows)packages/contracts/src/rpc.ts(providers.workspaceSkillspayload/result)apps/server/src/ws.ts,apps/server/src/provider/Layers/ProviderRegistry.ts,apps/server/src/provider/Drivers/ClaudeSkills.ts(workspace-scoped discovery returns user + project scopes, i.e. a superset of the snapshot; empty result means "no skills" or "instance not live")
Assessment notes
- No shared-primitive substitutions, class-string overrides, global CSS moves, theme-variant changes, or scroll/mask changes appear in the diff, so the styling and CSS-ownership rules are not engaged.
workspaceSlashCommandsuses the correct pattern: ChatView collapses empty toundefinedand ChatComposer owns the?? selectedProviderStatus?.slashCommandsfallback. Only the skills path duplicates the fallback ladder across two components, which is the source of the reported divergence.activeSkillsremains correct forMessagesTimeline, so the suggested fix is a single-line, self-contained change at the composer call site.- Recommended gate for the fix: typecheck plus a focused ChatComposer test covering picker contents when the persisted instance is disabled/unavailable.
🔍 Agent activity — 9 tool calls
💭 I'm checking the in-scope files—ChatView, ChatComposer, and providerSkills—for consistency issues, noting that ChatView and ChatComposer each implement their own fallback logic for active skills, which could lead to mismatched behavior. I want to dig into the actual code to see if this divergence causes real problems.
1. browse_code — apps/web/src/components/ChatView.tsx
2. browse_code — apps/web/src/components/ChatView.tsx
💭 I'm noticing ChatComposer has its own separate selectedProviderStatus that could diverge from ChatView's activeProviderStatus, so if the user switches providers within the composer itself, the skills fallback logic could reference a mismatched provider. There's also a subtler issue: when the RPC returns a legitimately empty skills array for the project,
activeSkillsbecomes[], which loses any user-scope snapshot skills — but ChatComposer's own length check would fall back toselectedProviderStatus.skillsand mostly recover, whereas MessagesTimeline receiving that same empty array has no such fallback, so it just goes without skills entirely. I want to verify whether workspace skill discovery already includes user-scope skills (checking that discovery function), since if it does, an empty result is genuinely "no skills anywhere" rather than a lost user-scope set. I also want to look more closely at the redundant fallback logic between ChatView and ChatComposer to see if the duplication actually causes divergent behavior.
3. browse_code — apps/server/src/provider/Drivers/ClaudeSkills.ts
4. git_tools — skills
💭 The grep results show a different implementation than the diff suggested—using a useProviderSkills hook instead of a workspaceSkills prop, which is confusing since I expected the diff's version. It seems the ref I specified might not have applied correctly, so I should check whether the git_grep rev parameter is actually pointing to the right commit before looking at specific line ranges in the file.
5. git_tools — workspaceSkills|useProviderSkills|composerSkills
6. browse_code — apps/server/src/provider/Drivers/ClaudeSkills.ts
💭 Since workspace-scoped discovery already includes user scope, its results form a superset, so an empty array genuinely means no skills exist. Falling back to a snapshot scan would typically also find nothing, since the snapshot uses the server's cwd (often the home directory), meaning it'd hit the same
~/.claude/skillspath as the user scope anyway — so the discrepancy is mostly harmless. Now I'm looking at whether ChatView's nullish-coalescing fallback and the composer's length-check fallback create a real bug, since the composer's own fallback toselectedProviderStatus?.skillswould only trigger on empty arrays. The real question is whetherselectedProviderStatusin the composer could point to a different provider instance than ChatView's active one, so I need to check how ChatComposer derivesselectedProviderStatus.
7. browse_code — apps/web/src/components/chat/ChatComposer.tsx
💭 I'm noticing these two provider IDs are derived through different resolution paths, so they usually align but could diverge if the composer's draft provider changes without updating the thread-level selection. Given the check's focus is UI consistency—shared components, CSS ownership, scroll behavior, visual preservation—and this PR is mostly data plumbing without styling changes, I'm trying to find any concrete violations. The skills fallback logic passed from ChatView into the composer could be a subtle mismatch if the composer's selected instance differs from ChatView's resolved active instance. Also there's an inconsistency between
activeSlashCommandsandactiveSkills—they use different fallback ladders, one resolved at the composer level and one at ChatView level, which feels like divergent patterns for handling the same kind of fallback logic. I'm checking whether MessagesTimeline's skills prop is used correctly and whether ChatView's query gating conditions properly account for draft threads, both of which seem fine on inspection. There's also the question of whetheractiveSkillsmaintains stable object identity across renders, since it's derived from query data each time. Since there are no CSS or styling changes in this diff, I'm leaning toward concluding this is likely an "all clear" case for the UI-consistency check, unless the skill-fallback mismatch between the composer picker and the workspace skills counts as a real finding worth reporting. The fix would be to pass the raw workspace query result directly and let ChatComposer own its own fallback, matching how workspaceSlashCommands is handled — that's a small, actionable finding I can raise as an inline comment on the relevant ChatView lines. I'm checking whether this is a real regression risk: when instance IDs match everything's fine, but when workspace data is missing or the composer's selected instance diverges from ChatView's active instance, the composer's picker could show skills for the wrong instance, which seems like a concrete, plausible bug worth flagging. Since composer filters by enabled/available and locked provider constraints while ChatView's active instance ID doesn't apply that same filtering, divergence is realistic if the persisted selection becomes unavailable — and the query itself is scoped to the potentially-wrong instance too, so both issues belong in one comment. I'll post this as a failing finding rather than "All clear," but before finalizing I want to verify how the composer syncs withselectedProviderByThreadIdin ChatView to be sure this divergence can genuinely happen.
8. git_tools — selectedProviderByThreadId
💭 I realize the composer applies availability/enabled filters that ChatView's selectedProviderInstanceId doesn't check, so a disabled or unavailable instance can still be queried. In that case the server returns empty skills/slash commands since it's not live, but activeSkills falls back to the stale snapshot from activeProviderStatus, meaning the composer ends up with skills from a disabled instance. This is a real bug worth flagging with an inline comment on ChatView around lines 2744-2747 (and referencing line 6697 as well). Since activeSkills is also used elsewhere in MessagesTimeline at line 6570, I can't propose a single self-contained inline fix that covers both usages cleanly, so I'll skip a diff and just describe the fix needed in the comment itself. A minimal one-line fix would work: pass the raw workspace query data directly to the composer via workspaceSkills at line 6697, instead of the already-fallback-resolved activeSkills. I'll verify the current content matches, then draft the comment explaining that activeSkills silently falls back to a different provider's snapshot skills, which then wins over the composer's own resolution because a non-empty workspaceSkills takes priority -- and that ChatView's resolution doesn't apply the same enabled/available/locked-group filters ChatComposer does, so a disabled or unavailable provider's skills can leak through. I'm confirming the indentation (28 spaces) matches sibling props like providerStatuses so the suggested diff line fits cleanly, then posting the review comment.
9. modify_pr — One finding: the composer's workspaceSkills prop receives an already-fallback-resolved value, which can make the $ picker show skills for a different provider instance than the one the composer selected. Details inline.
Agent Credits: 73 credits