Skip to content

fix(provider): resolve skills and slash commands against the workspac…

MacroscopeApp / Macroscope - UI Consistency failed Aug 22, 2026 in 2m 12s

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 or activeProviderStatus?.skills) into ChatComposer. Because composerSkills prefers any non-empty workspaceSkills over selectedProviderStatus?.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 on enabled && isAvailable and locked driver/continuation group, so a persisted-but-disabled selection makes the $ picker list another instance's skills. Suggested fix: pass workspaceCapabilitiesQuery.data?.skills and let ChatComposer own the fallback, matching how workspaceSlashCommands is 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.tsx
  • apps/web/src/components/chat/ChatComposer.tsx
  • apps/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.workspaceSkills payload/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.
  • workspaceSlashCommands uses the correct pattern: ChatView collapses empty to undefined and ChatComposer owns the ?? selectedProviderStatus?.slashCommands fallback. Only the skills path duplicates the fallback ladder across two components, which is the source of the reported divergence.
  • activeSkills remains correct for MessagesTimeline, 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, activeSkills becomes [], which loses any user-scope snapshot skills — but ChatComposer's own length check would fall back to selectedProviderStatus.skills and 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/skills path 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 to selectedProviderStatus?.skills would only trigger on empty arrays. The real question is whether selectedProviderStatus in the composer could point to a different provider instance than ChatView's active one, so I need to check how ChatComposer derives selectedProviderStatus.

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 activeSlashCommands and activeSkills—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 whether activeSkills maintains 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 with selectedProviderByThreadId in 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