Skip to content

feat: load ide skills#456

Open
0x0a0d wants to merge 16 commits intositeboon:mainfrom
0x0a0d:feat/load-skills
Open

feat: load ide skills#456
0x0a0d wants to merge 16 commits intositeboon:mainfrom
0x0a0d:feat/load-skills

Conversation

@0x0a0d
Copy link
Contributor

@0x0a0d 0x0a0d commented Feb 28, 2026

load skills /<skill-name> arg1 arg2

display skills in list

image

display placeholder argument hint

image

hover /skill in composer display Skill Info Dialog

image

reaload skills in Quick Settings

image

on mobile

image

press View icon show Skill Info Dialog with Form Usage. Enter args or none then press usage will insert /skill args... at cursor

image

Summary by CodeRabbit

  • New Features

    • Skills integrated into chat: inline decorations, hover tooltips, Skill Info dialog, and a "view info" action in the command menu.
    • Provider-aware skills: prefetch/reload per provider (quick settings button and triggered on provider selection).
    • Command list can include provider-specific skills and updates counts.
    • Chat input shows improved "Drop images here" hint.
  • Localization

    • Added translations for new skill/command UI and settings in English, Japanese, Korean, and Simplified Chinese.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds provider-scoped skills: server-side skill discovery/caching and API extensions plus frontend wiring to prefetch, reload, decorate, and inspect skills; expands slash/command hooks and chat UI to support skill tokens, tooltips, and a skill info dialog; adds i18n keys and settings UI for reloading skills.

Changes

Cohort / File(s) Summary
Backend: skills loading & API
server/routes/commands.js, server/utils/loadSkillsList.js
New loadSkillsList(agentName) utility; provider→agent mapping, skillsCache, normalizeSkills, getSkillsForProvider; POST /api/commands/list accepts provider, includeSkills, forceReloadSkills and returns normalized skills alongside existing commands.
Composer, Slash hooks & mentions
src/components/chat/hooks/useChatComposerState.ts, src/components/chat/hooks/useSlashCommands.ts, src/components/chat/hooks/useFileMentions.tsx
Adds Skill types and token parsing/decoration, skill dialog/tooltip state and handlers; makes command history and command/skill caches provider-scoped; introduces prefetchSkillsForProvider and reloadSkillsForProvider; exposes cursorPosition from file mentions.
Chat UI wiring & settings
src/components/chat/view/.../ChatInterface.tsx, src/components/chat/view/subcomponents/ChatComposer.tsx, src/components/QuickSettingsPanel.jsx, src/components/chat/view/subcomponents/ChatMessagesPane.tsx
Threads skill props/handlers through UI, replaces mention rendering with skill decorations, surfaces tooltips/dialogs, and passes provider + reloadSkillsForProvider to QuickSettingsPanel; ChatMessagesPane gets prefetchSkillsForProvider.
Command menu & new skill components
src/components/chat/view/subcomponents/CommandMenu.tsx, src/components/chat/view/subcomponents/SkillInfoDialog.tsx, src/components/chat/view/subcomponents/SkillInfoTooltip.tsx
CommandMenu uses i18n and adds onViewSkillInfo/skill-action button; introduces SkillInfoDialog (menu-mobile/token-touch modes) and SkillInfoTooltip with metadata formatting and usage input handling.
Provider selection
src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
Calls prefetchSkillsForProvider(provider) when a provider is chosen to begin background skill loading.
i18n / locale updates
src/i18n/locales/.../chat.json, src/i18n/locales/.../settings.json
Adds input.dropImagesHere, commandMenu keys, skillInfoDialog keys, and quickSettings.dragHandle.reloadSkills entries across en/ja/ko/zh-CN locales.

Sequence Diagram

sequenceDiagram
    participant User
    participant ChatUI as Chat Interface
    participant Composer as Composer Hook
    participant Slash as Slash Hook
    participant Server
    participant Cache as Skills Cache

    User->>ChatUI: Select provider
    ChatUI->>Slash: prefetchSkillsForProvider(provider)
    Slash->>Cache: check cached skills for provider
    alt not cached
        Slash->>Server: POST /api/commands/list (provider, includeSkills=true)
        Server->>Server: loadSkillsList(agent), normalize skills
        Server->>Cache: cache skills by provider
        Server-->>Slash: return commands + skills
    else cached
        Cache-->>Slash: return cached skills
    end

    User->>ChatUI: Type "/" or touch token
    ChatUI->>Composer: renderInputWithSkillDecorations(text)
    Composer-->>ChatUI: decorated input, activeSkillTooltip
    User->>ChatUI: View skill info
    ChatUI->>Composer: openSkillInfoDialogFromMenu / token
    Composer-->>ChatUI: SkillInfoDialog rendered

    User->>ChatUI: Click reload in settings
    ChatUI->>Slash: reloadSkillsForProvider(provider, forceReload=true)
    Slash->>Server: POST /api/commands/list (forceReloadSkills=true)
    Server->>Cache: clear & reload skills
    Server-->>Slash: return refreshed skills
Loading

Possibly related PRs

Suggested Reviewers

  • blackmammoth
  • viper151

Poem

🐰 I nibble through the code with care,
I fetch new skills from places rare,
Tokens gleam and tooltips sing,
Dialogs open — bells do ring,
Reload, prefetch — hop, repair. 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: load ide skills' clearly and concisely describes the main feature addition—loading IDE skills for use in the application—which aligns with the substantial changes across skill loading, UI integration, and user interaction flows.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (5)
server/utils/loadSkillsList.js (2)

36-410: Consider removing commented-out agent configurations.

Over 350 lines of commented-out code for unsupported agents makes the file harder to maintain. If these are planned for future implementation, consider:

  1. Moving them to a separate reference file or documentation
  2. Adding a TODO comment with a tracking issue
  3. Removing them entirely and re-adding when needed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/loadSkillsList.js` around lines 36 - 410, The file's agents
object contains >350 lines of commented-out agent configs (e.g., commented keys
around 'claude-code', 'codex', 'cursor') which clutter loadSkillsList.js; remove
these commented-out agent blocks and either move them to a new separate
reference file (e.g., create server/utils/agents-reference.js or agents/README)
or convert them into concise TODO entries with tracking links, and keep only the
active agent entries inside the agents object ('claude-code', 'codex', 'cursor')
so loadSkillsList.js contains only live configuration and a brief comment
pointing to the reference file or issue.

437-458: Add error handling for filesystem operations.

The readdirSync call on line 442 and readFileSync on line 448 can throw exceptions (e.g., permission errors, race conditions if directory is deleted between existsSync and read). While the outer function doesn't explicitly handle these, consider wrapping in try-catch to prevent a single bad skill directory from breaking the entire skills load.

🛡️ Proposed error handling
   for (const skillsDir of skillsDirs) {
     for (const subDir of subSkillsDir) {
       const fullSkillsDir = join(skillsDir, subDir);
       if (!existsSync(fullSkillsDir)) continue;

-      for (const f of readdirSync(fullSkillsDir)) {
-        const skillDir = join(fullSkillsDir, f)
-        // find /SKILL.md files in skillDir
-        const skillFileMdPath = join(skillDir, 'SKILL.md');
-        if (!existsSync(skillFileMdPath)) continue;
-
-        const fileContents = readFileSync(skillFileMdPath, 'utf-8');
-        const { data } = matter(fileContents);
-        if (!data.name) continue;
-
-        // TODO: handle `.user-invocable`, `.disable-model-invocation`?
-        loadedSkills.push({
-          ...data,
-          skillDir,
-        })
-      }
+      try {
+        for (const f of readdirSync(fullSkillsDir)) {
+          try {
+            const skillDir = join(fullSkillsDir, f)
+            // find /SKILL.md files in skillDir
+            const skillFileMdPath = join(skillDir, 'SKILL.md');
+            if (!existsSync(skillFileMdPath)) continue;
+
+            const fileContents = readFileSync(skillFileMdPath, 'utf-8');
+            const { data } = matter(fileContents);
+            if (!data.name) continue;
+
+            // TODO: handle `.user-invocable`, `.disable-model-invocation`?
+            loadedSkills.push({
+              ...data,
+              skillDir,
+            })
+          } catch (err) {
+            console.warn(`Error loading skill from ${f}:`, err.message);
+          }
+        }
+      } catch (err) {
+        console.warn(`Error reading skills directory ${fullSkillsDir}:`, err.message);
+      }
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/loadSkillsList.js` around lines 437 - 458, Wrap the filesystem
operations that can throw inside the nested loops — specifically the
readdirSync(fullSkillsDir) and readFileSync(skillFileMdPath) calls used to
populate loadedSkills — with try/catch so a single bad skillDir or transient FS
error doesn't abort the whole load; on error log the issue (including skillDir
and skillFileMdPath) and continue to the next entry, and only parse/use
matter(fileContents) when readFileSync succeeds and returns a string before
pushing into loadedSkills.
src/i18n/locales/ja/chat.json (1)

238-242: Consider translating action button labels to Japanese.

The action button labels (clear, usage, close) are in English while other UI elements are translated to Japanese. This appears inconsistent with other localized action buttons in this file (e.g., input.send → "送信", input.stop → "中止").

If the English labels are intentional for consistency with technical terminology, consider adding a comment or documentation to clarify this decision.

💡 Suggested Japanese translations
     "actions": {
-      "clear": "Clear",
-      "usage": "Usage",
-      "close": "Close"
+      "clear": "クリア",
+      "usage": "使用方法",
+      "close": "閉じる"
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/i18n/locales/ja/chat.json` around lines 238 - 242, The action labels in
the "actions" object (`clear`, `usage`, `close`) are still in English; update
their string values to Japanese to match the rest of the locale (e.g., change
"clear", "usage", "close" to appropriate Japanese strings such as 消去/クリア,
使用量/ヘルプ, 閉じる), replacing the values under the "actions" key so keys remain
unchanged; if leaving any English is intentional, add a brief inline comment
explaining the decision for future maintainers.
src/i18n/locales/ko/chat.json (1)

253-257: Consider translating action button labels to Korean.

Similar to the Japanese locale, the action button labels are in English while other UI elements are translated. For consistency, consider:

💡 Suggested Korean translations
     "actions": {
-      "clear": "Clear",
-      "usage": "Usage",
-      "close": "Close"
+      "clear": "지우기",
+      "usage": "사용법",
+      "close": "닫기"
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/i18n/locales/ko/chat.json` around lines 253 - 257, The action button
labels inside the "actions" object ("clear", "usage", "close") are still in
English; update their string values to Korean (e.g., replace "Clear" with a
suitable Korean term like "지우기" or "초기화", "Usage" with "사용량", and "Close" with
"닫기") in the src/i18n/locales/ko/chat.json file so the "actions" object values
are fully localized.
server/routes/commands.js (1)

451-496: Consider validating the provider parameter.

The provider parameter is used directly without validation. While there's a fallback to claude in getSkillsForProvider, consider validating against known providers for clearer error messages.

💡 Optional validation
 router.post('/list', async (req, res) => {
   try {
     const {
       projectPath,
       provider = 'claude',
       includeSkills = true,
       forceReloadSkills = false,
     } = req.body;
+
+    const validProviders = Object.keys(providerToAgentMap);
+    const normalizedProvider = validProviders.includes(provider) ? provider : 'claude';
+
     const allCommands = [...builtInCommands];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/commands.js` around lines 451 - 496, Validate the provider
parameter before calling getSkillsForProvider: define an allowedProviders list
(or derive it from an existing source if available) and check the extracted
provider variable; if it's not in allowedProviders, return a 400 response with a
clear error message instead of continuing, otherwise proceed to call
getSkillsForProvider({ provider, forceReloadSkills }). Update the route handler
(router.post('/list', ...)) to perform this check right after destructuring
provider from req.body and before computing skills; reference the provider
variable, the getSkillsForProvider call, and the router.post('/list' handler)
when locating the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/utils/loadSkillsList.js`:
- Around line 9-24: The function findConfigHome references the variable home
before it is declared which causes a ReferenceError; fix it by moving the
declaration const home = homedir() above findConfigHome (or refactor
findConfigHome to accept home as an argument) and then compute configHome and
codexHome after that; update references to use the hoisted home so
findConfigHome, configHome and codexHome all see a defined home value.

In `@src/components/chat/hooks/useChatComposerState.ts`:
- Around line 569-571: The hook currently derives skillCommands using only
command.type === 'skill' which diverges from the menu path that recognizes
skills by command.namespace === 'skills'; update the skill detection predicate
used in useChatComposerState (the skillCommands useMemo and the similar check
around lines ~678-680) to a unified predicate (e.g., treat a command as a skill
if command.type === 'skill' || command.namespace === 'skills') so both token
highlighting/hints and menu-driven skill info will work for namespace-only
payloads; apply the same predicate to all spots that branch on skills in this
file.
- Around line 787-790: The trailing-space check currently depends on hasUsage
and can merge tokens when usageText is empty; update the needsTrailingSpace
computation in useChatComposerState so it does not reference hasUsage. Replace
the line computing needsTrailingSpace with: const needsTrailingSpace =
suffixedInput.length > 0 && !/^\s/.test(suffixedInput); leaving
needsLeadingSpace, insertedText and nextValue logic unchanged so prefixedInput,
insertedText, and suffixedInput remain properly separated.

In `@src/components/chat/hooks/useSlashCommands.ts`:
- Around line 125-131: The prefetch path is overwriting the currently visible
slashCommands by calling setSlashCommands with results for a non-active
targetProvider; update the logic in buildSortedCommands usage and in
prefetchSkillsForProvider so you only call setSlashCommands when targetProvider
=== activeProvider (or derive activeProvider from state/props), and when
updating skillsByProviderRef ensure async results are gated by the same
activeProvider check (e.g., compare targetProvider to currentProvider before
setSlashCommands). Specifically modify places referencing buildSortedCommands,
baseCommandsRef.current, skillsByProviderRef.current[targetProvider],
setSlashCommands, and prefetchSkillsForProvider to guard writes so stale
prefetch responses cannot replace the active provider’s command list.
- Around line 347-364: selectCommandFromKeyboard currently executes non-skill
commands but doesn't call trackCommandUsage, so keyboard selections aren't
tracked; update selectCommandFromKeyboard to call trackCommandUsage(command) for
non-skill commands (e.g., after resetCommandMenuState() and before calling
onExecuteCommand(command)) so keyboard-invoked commands contribute to
history/frequent sorting; keep existing promise handling for the executionResult
unchanged.

In `@src/components/chat/view/subcomponents/CommandMenu.tsx`:
- Around line 231-235: The icon-only skill info button in CommandMenu is missing
an accessible name; update the button JSX (the element with onMouseDown handling
the skill info) to include an explicit aria-label (use the same translated
string as title, e.g. t('commandMenu.viewSkillInfo')) so screen readers announce
the control; keep title as-is and ensure aria-label matches for accessibility.

In `@src/components/chat/view/subcomponents/SkillInfoDialog.tsx`:
- Around line 136-142: The label in SkillInfoDialog.tsx is not bound to its
textarea which breaks accessibility; add a matching id on the textarea (e.g.,
"skill-usage-textarea" or a generated unique id) and set the label's htmlFor to
that id so screen readers associate the label with the textarea used by
state.usageText and onUsageChange (and keep the existing placeholder/state
binding intact).
- Around line 89-93: The dialog with role="dialog" in SkillInfoDialog.tsx lacks
an accessible name; fix by connecting the dialog to the visible title: give the
title element (the div showing state.info.commandName) a stable id (e.g.,
commandNameLabel) and add aria-labelledby="commandNameLabel" to the dialog
container, or alternatively set aria-label={state.info.commandName} on the
dialog; update the JSX around the role="dialog" element and the title div
accordingly so assistive tech can announce the dialog name.

In `@src/i18n/locales/zh-CN/chat.json`:
- Around line 254-256: The "usage" label in the zh-CN locale file is still
English; update the value for the "usage" key in
src/i18n/locales/zh-CN/chat.json (the JSON object containing "clear", "usage",
"close") to a proper Chinese translation (e.g., "使用情况" or "用量") so the button
group is fully localized; ensure you only change the string value for the
"usage" key and keep JSON formatting intact.

---

Nitpick comments:
In `@server/routes/commands.js`:
- Around line 451-496: Validate the provider parameter before calling
getSkillsForProvider: define an allowedProviders list (or derive it from an
existing source if available) and check the extracted provider variable; if it's
not in allowedProviders, return a 400 response with a clear error message
instead of continuing, otherwise proceed to call getSkillsForProvider({
provider, forceReloadSkills }). Update the route handler (router.post('/list',
...)) to perform this check right after destructuring provider from req.body and
before computing skills; reference the provider variable, the
getSkillsForProvider call, and the router.post('/list' handler) when locating
the change.

In `@server/utils/loadSkillsList.js`:
- Around line 36-410: The file's agents object contains >350 lines of
commented-out agent configs (e.g., commented keys around 'claude-code', 'codex',
'cursor') which clutter loadSkillsList.js; remove these commented-out agent
blocks and either move them to a new separate reference file (e.g., create
server/utils/agents-reference.js or agents/README) or convert them into concise
TODO entries with tracking links, and keep only the active agent entries inside
the agents object ('claude-code', 'codex', 'cursor') so loadSkillsList.js
contains only live configuration and a brief comment pointing to the reference
file or issue.
- Around line 437-458: Wrap the filesystem operations that can throw inside the
nested loops — specifically the readdirSync(fullSkillsDir) and
readFileSync(skillFileMdPath) calls used to populate loadedSkills — with
try/catch so a single bad skillDir or transient FS error doesn't abort the whole
load; on error log the issue (including skillDir and skillFileMdPath) and
continue to the next entry, and only parse/use matter(fileContents) when
readFileSync succeeds and returns a string before pushing into loadedSkills.

In `@src/i18n/locales/ja/chat.json`:
- Around line 238-242: The action labels in the "actions" object (`clear`,
`usage`, `close`) are still in English; update their string values to Japanese
to match the rest of the locale (e.g., change "clear", "usage", "close" to
appropriate Japanese strings such as 消去/クリア, 使用量/ヘルプ, 閉じる), replacing the values
under the "actions" key so keys remain unchanged; if leaving any English is
intentional, add a brief inline comment explaining the decision for future
maintainers.

In `@src/i18n/locales/ko/chat.json`:
- Around line 253-257: The action button labels inside the "actions" object
("clear", "usage", "close") are still in English; update their string values to
Korean (e.g., replace "Clear" with a suitable Korean term like "지우기" or "초기화",
"Usage" with "사용량", and "Close" with "닫기") in the src/i18n/locales/ko/chat.json
file so the "actions" object values are fully localized.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e22f42 and 54940f0.

📒 Files selected for processing (21)
  • server/routes/commands.js
  • server/utils/loadSkillsList.js
  • src/components/QuickSettingsPanel.jsx
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useFileMentions.tsx
  • src/components/chat/hooks/useSlashCommands.ts
  • src/components/chat/view/ChatInterface.tsx
  • src/components/chat/view/subcomponents/ChatComposer.tsx
  • src/components/chat/view/subcomponents/ChatMessagesPane.tsx
  • src/components/chat/view/subcomponents/CommandMenu.tsx
  • src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
  • src/components/chat/view/subcomponents/SkillInfoDialog.tsx
  • src/components/chat/view/subcomponents/SkillInfoTooltip.tsx
  • src/i18n/locales/en/chat.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/ja/chat.json
  • src/i18n/locales/ja/settings.json
  • src/i18n/locales/ko/chat.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/zh-CN/chat.json
  • src/i18n/locales/zh-CN/settings.json

…ing while syncing slash commands only for the active provider
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/chat/hooks/useChatComposerState.ts`:
- Around line 169-177: findSkillTokenRanges currently rebuilds a dynamic
alternation RegExp from skillNames on every call (using escapeRegExp and
sortedSkillNames), which is executed in render/decoration hot paths and causes
responsiveness issues; change the implementation to avoid per-call regex
construction by precomputing and memoizing a lookup/tokenizer when skillNames
change (e.g., build the RegExp once or construct a Set/trie of normalized skill
tokens) and update findSkillTokenRanges to accept the precompiled pattern or
lookup structure instead of raw skillNames, ensuring callers (the composer
decoration flow and the hook that invokes findSkillTokenRanges) pass the
memoized object (useMemo or cached singleton keyed by skill list) so matching
uses cheap Set/trie lookups or a reused RegExp instead of rebuilding on every
render.
- Around line 606-613: Change the matching logic to prefer an exact skill name
match before falling back to prefix disambiguation: first search skillCommands
for a command whose name strictly equals fullToken and return that single
command if found; only if no exact match exists, compute matchingCommands =
skillCommands.filter(c => c.name.startsWith(fullToken)) and proceed with the
existing length check and selection (the existing variables to update are
matchingCommands, fullToken, and the use where command is assigned).

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54940f0 and b171679.

📒 Files selected for processing (5)
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/hooks/useSlashCommands.ts
  • src/components/chat/view/subcomponents/CommandMenu.tsx
  • src/components/chat/view/subcomponents/SkillInfoDialog.tsx
  • src/i18n/locales/zh-CN/chat.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/chat/view/subcomponents/SkillInfoDialog.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/components/chat/hooks/useChatComposerState.ts (2)

813-814: Use a guarded fallback before trimming around slashPosition.

At Line 813, slashPosition is always passed as fallback. If no local slash token is near the caret, this can cause replacement behavior based on stale menu state instead of pure cursor insertion. Guard the fallback (or disable it) unless it still points to the active token context.

Proposed adjustment
-    const { nextInput: trimmedInput, nextCursor } = trimSkillTokenAroundCursor(baseInput, currentCursor, slashPosition);
+    const safeFallbackSlash =
+      slashPosition >= 0 &&
+      slashPosition < baseInput.length &&
+      baseInput[slashPosition] === '/' &&
+      slashPosition <= currentCursor &&
+      !/\s/.test(baseInput.slice(slashPosition, currentCursor))
+        ? slashPosition
+        : -1;
+    const { nextInput: trimmedInput, nextCursor } = trimSkillTokenAroundCursor(
+      baseInput,
+      currentCursor,
+      safeFallbackSlash,
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/chat/hooks/useChatComposerState.ts` around lines 813 - 814,
The call to trimSkillTokenAroundCursor currently always passes slashPosition,
which can cause stale menu-state replacement instead of a plain caret insertion;
change the caller so it only passes slashPosition when it actually points to the
active local slash token context (e.g., compute a boolean like
isActiveSlashToken or validate that slashPosition is within the currently
detected token bounds) and otherwise pass undefined/null as the fallback so
trimSkillTokenAroundCursor performs a pure cursor insertion; update the
invocation around baseInput/currentCursor/trimSkillTokenAroundCursor to use the
guarded value and keep using nextInput/nextCursor as before.

866-895: Inline-hint element construction is repeated in three branches.

The same hint overlay/spacer/text structure appears multiple times. Extracting a small helper renderer would reduce drift risk and make future UI tweaks safer.

Also applies to: 910-962, 1004-1056

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/chat/hooks/useChatComposerState.ts` around lines 866 - 895,
Extract the repeated inline-hint overlay/span structure into a small helper
renderer (e.g., renderInlineSkillHintOverlay) and replace the three duplicate
blocks that build the hint spacer/overlay/text and surrounding before/after
spans (the code that uses inlineSkillArgumentHint, activeTypedSkillToken,
hintWidthCh, renderInputWithMentions and createElement keys like
'skill-inline-hint-overlay'/'skill-inline-hint-spacer'/'skill-inline-hint-text')
with calls to that helper; the helper should accept the hint string, computed
hintWidthCh, the before/after text (or the full text and cursor) and return the
same array of createElement spans so UI structure and keys remain stable while
centralizing styling/markup for future tweaks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/chat/hooks/useChatComposerState.ts`:
- Around line 813-814: The call to trimSkillTokenAroundCursor currently always
passes slashPosition, which can cause stale menu-state replacement instead of a
plain caret insertion; change the caller so it only passes slashPosition when it
actually points to the active local slash token context (e.g., compute a boolean
like isActiveSlashToken or validate that slashPosition is within the currently
detected token bounds) and otherwise pass undefined/null as the fallback so
trimSkillTokenAroundCursor performs a pure cursor insertion; update the
invocation around baseInput/currentCursor/trimSkillTokenAroundCursor to use the
guarded value and keep using nextInput/nextCursor as before.
- Around line 866-895: Extract the repeated inline-hint overlay/span structure
into a small helper renderer (e.g., renderInlineSkillHintOverlay) and replace
the three duplicate blocks that build the hint spacer/overlay/text and
surrounding before/after spans (the code that uses inlineSkillArgumentHint,
activeTypedSkillToken, hintWidthCh, renderInputWithMentions and createElement
keys like
'skill-inline-hint-overlay'/'skill-inline-hint-spacer'/'skill-inline-hint-text')
with calls to that helper; the helper should accept the hint string, computed
hintWidthCh, the before/after text (or the full text and cursor) and return the
same array of createElement spans so UI structure and keys remain stable while
centralizing styling/markup for future tweaks.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b171679 and b797489.

📒 Files selected for processing (1)
  • src/components/chat/hooks/useChatComposerState.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/components/chat/hooks/useChatComposerState.ts (1)

865-897: Extract repeated inline-hint rendering into a helper.

The same inline-hint overlay block appears in three paths. Pulling this into one helper will reduce drift and make future UI changes safer.

Also applies to: 912-967, 1008-1063

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/chat/hooks/useChatComposerState.ts` around lines 865 - 897,
Extract the repeated inline-hint overlay JSX into a single helper (e.g.,
renderInlineSkillArgumentHint) and use it in all three places inside
useChatComposerState where the overlay is duplicated (the blocks that reference
inlineSkillArgumentHint, activeTypedSkillToken, renderInputWithMentions and use
createElement with keys like 'skill-inline-hint-overlay',
'skill-inline-hint-spacer', 'skill-inline-hint-text'); the helper should accept
the hint text and return the same createElement structure (preserving classes,
spacer/text spans and keys or derive keys from a passed suffix) so you replace
the three inline copies with calls to
renderInlineSkillArgumentHint(inlineSkillArgumentHint) while keeping surrounding
before/after renderInputWithMentions slices intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/chat/view/subcomponents/SkillInfoDialog.tsx`:
- Around line 59-72: The dialog currently only listens for Escape but doesn't
move focus into the modal, trap Tab/Shift+Tab, or restore the previously focused
element on close; update the SkillInfoDialog component to add a ref for the
dialog container, and in the useEffect that depends on state.open and onClose:
when opening, save document.activeElement, move focus to the first focusable
element inside the dialog (or the dialog container), and add a keydown handler
that handles Escape (calls onClose) and Tab/Shift+Tab to loop focus within the
dialog; on cleanup (or when closing) remove the keydown listener and restore
focus to the saved element. Ensure the effect references the dialog ref and
cleans up all listeners and saved state so focus is reliably trapped and
restored.

---

Nitpick comments:
In `@src/components/chat/hooks/useChatComposerState.ts`:
- Around line 865-897: Extract the repeated inline-hint overlay JSX into a
single helper (e.g., renderInlineSkillArgumentHint) and use it in all three
places inside useChatComposerState where the overlay is duplicated (the blocks
that reference inlineSkillArgumentHint, activeTypedSkillToken,
renderInputWithMentions and use createElement with keys like
'skill-inline-hint-overlay', 'skill-inline-hint-spacer',
'skill-inline-hint-text'); the helper should accept the hint text and return the
same createElement structure (preserving classes, spacer/text spans and keys or
derive keys from a passed suffix) so you replace the three inline copies with
calls to renderInlineSkillArgumentHint(inlineSkillArgumentHint) while keeping
surrounding before/after renderInputWithMentions slices intact.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b797489 and f869e02.

📒 Files selected for processing (3)
  • src/components/chat/hooks/useChatComposerState.ts
  • src/components/chat/view/subcomponents/SkillInfoDialog.tsx
  • src/components/chat/view/subcomponents/SkillInfoTooltip.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/chat/view/subcomponents/SkillInfoTooltip.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/chat/view/subcomponents/SkillInfoDialog.tsx`:
- Around line 67-72: The focusableSelector used by getFocusableElements omits
the interactive <summary> element, so keyboard users can be skipped; update the
focusableSelector (and any other place building that selector) to include
summary:not([disabled]) (or ensure the rendered <summary> has a tabindex) so
querySelectorAll in getFocusableElements will return the summary control and the
focus trap cycles correctly; update references to focusableSelector and verify
the interactive <summary> rendered in SkillInfoDialog is discoverable.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f869e02 and c243532.

📒 Files selected for processing (1)
  • src/components/chat/view/subcomponents/SkillInfoDialog.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant