Skip to content

Conversation

@juliangoetze
Copy link
Contributor

@juliangoetze juliangoetze commented Aug 27, 2025

Summary by CodeRabbit

  • New Features

    • Fuzzy file search across the workspace, usable from the server API and runtimes.
    • File-mention UI: search dropdown, selectable file chips, insertion into chat input, and file icons.
  • UX

    • Dropdown supports keyboard navigation, adaptive placement, loading/no-results states, and ESC now closes the mention dropdown.
  • Performance

    • Cached, refreshable file index for faster searches.
  • Chores

    • Added dependencies to enable fuzzy search and file indexing.

@cla-bot cla-bot bot added the cla-signed label Aug 27, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 27, 2025

Walkthrough

Adds fuzzy file-search end-to-end: new runtime types and APIs, Karton server procedure, Fuse-based implementations with cached indexes for Node and Mock runtimes, client-side chat UI for file mentions (dropdown, chips, icons, hotkey behavior), and related dependency/build updates.

Changes

Cohort / File(s) Summary
Client: Karton procedure
agent/client/src/Agent.ts
Registers server procedure fuzzyFileSearch(searchString) delegating to clientRuntime.fileSystem.fuzzySearch.
Runtime interface: types & API
agent/runtime-interface/package.json, agent/runtime-interface/src/file-operations.ts, agent/runtime-interface/src/index.ts
Adds dev dep fuse.js; introduces FileData, FuzzyFileSearchResult, FuzzyFileSearchInputSchema; extends IFileSystemProvider and BaseFileSystemProvider with fuzzySearch; re-exports new types.
Runtime implementations: Node & Mock
agent/runtime-node/package.json, agent/runtime-node/src/index.ts, agent/runtime-node/build.ts, agent/runtime-mock/package.json, agent/runtime-mock/src/mock-file-system.ts
Adds fuse.js and fast-glob; implements fuzzySearch with Fuse-based index, TTL cache (30s), concurrency guard, index builders (fast-glob / memfs traversal), weighting (filename>filepath>dirname), error handling, and exposes method on providers; build config marks externals.
Karton contract & metadata
packages/karton-contract/package.json, packages/karton-contract/src/index.ts, packages/karton-contract/src/metadata.ts
Adds dependency on @stagewise/agent-runtime-interface; exposes fuzzyFileSearch in KartonContract.serverProcedures; adds optional selectedFiles to userMessageMetadataSchema (filepath, filename).
Toolbar UI: chat + footer + state
toolbar/core/src/hooks/use-chat-state.tsx, toolbar/core/src/panels/chat/index.tsx, toolbar/core/src/panels/chat/panel-footer.tsx, toolbar/core/src/layouts/default.tsx, toolbar/core/src/components/file-mention-chip.tsx, toolbar/core/src/components/file-mention-dropdown.tsx, toolbar/core/src/components/hotkey-listener.tsx
Adds mention UI/state for file mentions: selectedFiles and isMentionDropdownOpen to chat state; ChatPanel gains optional position prop; ChatPanelFooter adds mention parsing, debounced fuzzy search, selection/insert/remove flows, dropdown wiring and chips; hotkey ESC behavior updated to close dropdown; new FileMentionChip and FileMentionDropdown components with ref API and keyboard navigation.
Toolbar UI: icons/utilities
toolbar/core/src/utils/file-icons.tsx
New file-icon utilities: FileIconData, getFileIcon, isProgrammingFile, getThemedFileIcon using lucide/react and react-icons mappings; used by mention components.
Toolbar deps
toolbar/core/package.json
Adds runtime deps @floating-ui/react and react-icons.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor ClientUI as Chat UI
  participant KartonServer as Karton Server
  participant Runtime as ClientRuntime.fileSystem
  participant Provider as FileSystemProvider
  participant Fuse as Fuse Index

  ClientUI->>KartonServer: request fuzzyFileSearch(searchString)
  KartonServer->>Runtime: fuzzySearch(searchString)
  Runtime->>Provider: fuzzySearch(searchString)
  activate Provider
  Provider->>Provider: ensureFileIndexBuilt() (TTL, guard)
  alt index build needed
    Provider->>Provider: buildFileIndex (fast-glob / memfs)
    Provider->>Fuse: new Fuse(files, options)
  end
  Provider->>Fuse: search(searchString)
  Fuse-->>Provider: FuseResult<FileData>[]
  deactivate Provider
  Provider-->>Runtime: FuzzyFileSearchResult
  Runtime-->>KartonServer: FuzzyFileSearchResult
  KartonServer-->>ClientUI: FuzzyFileSearchResult
Loading
sequenceDiagram
  autonumber
  participant User as User
  participant Chat as ChatPanelFooter
  participant Dropdown as FileMentionDropdown
  participant Server as Karton Server

  User->>Chat: types "@" + query
  Chat->>Server: fuzzyFileSearch(query) [debounced]
  Server-->>Chat: results
  Chat->>Dropdown: show results (open)
  User->>Dropdown: navigate/select
  Dropdown-->>Chat: selected FileData
  Chat->>Chat: insert mention token, add selectedFiles, close dropdown
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

I thump through trees of code and light,
My fuzzy nose finds paths at night.
With Fuse and glob and caches warm,
I fetch your files in playful form.
Hop, click, insert — the mention's done. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fuzzy-file-search

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vercel
Copy link

vercel bot commented Aug 27, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
stagewise-website Ready Ready Preview Aug 28, 2025 7:54pm

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
agent/runtime-interface/package.json (1)

24-31: Fix dependency placement: zod should be a runtime dependency; fuse.js can be dev-only (type-only import).

  • zod is used at runtime throughout this package; it must be in dependencies (not devDependencies).
  • fuse.js is only imported for types (import type { FuseResult }), so it can be moved to devDependencies to avoid forcing downstream installs.

Apply this diff:

   "devDependencies": {
     "rimraf": "^5.0.0",
-    "typescript": "^5.0.0",
-    "zod": "4.0.17"
+    "typescript": "^5.0.0"
   },
-  "dependencies": {
-    "fuse.js": "^7.1.0"
-  }
+  "dependencies": {
+    "zod": "4.0.17",
+    "fuse.js": "^7.1.0"
+  }
🧹 Nitpick comments (12)
agent/runtime-interface/src/file-operations.ts (3)

2-2: Avoid leaking fuse.js types in the public interface (decouple result type).

Depending on FuseResult in the interface locks consumers to fuse.js and makes future swaps harder. Define a small, library-agnostic result type instead.

Apply this diff:

-import type { FuseResult } from 'fuse.js';
+// Keep the interface independent of the fuzzy search engine implementation.
+// Providers can map their engine-specific results to this shape.
+
@@
-export type FuzzyFileSearchResult = FuseResult<FileData>[];
+export type FuzzyMatch<T> = {
+  item: T;
+  score: number;
+  // Optional highlights; providers may omit.
+  matches?: Array<{
+    key?: string;
+    indices: [number, number][];
+    value?: string;
+    arrayIndex?: number;
+  }>;
+  // Optional original index in provider’s corpus.
+  refIndex?: number;
+};
+export type FuzzyFileSearchResult = FuzzyMatch<FileData>[];

Also applies to: 14-14


4-9: Clarify path semantics in FileData (relative vs absolute).

filepath vs fullpath is ambiguous. Consider renaming for clarity (e.g., relativePath, absolutePath) or document semantics inline.

If you want to rename now, I can generate a follow-up diff across providers and consumers.


11-14: Add minimal input validation and export the inferred input type.

Prevent expensive searches on empty queries and expose the inferred type for reuse.

Apply this diff:

 export const FuzzyFileSearchInputSchema = z.object({
-  searchString: z.string(),
+  searchString: z.string().min(2, 'Use at least 2 characters for fuzzy search'),
 });
+export type FuzzyFileSearchInput = z.infer<typeof FuzzyFileSearchInputSchema>;
agent/client/src/Agent.ts (1)

295-297: Guard empty queries, cap results, and sanitize file paths in fuzzyFileSearch

We should prevent expensive searches on empty input, limit the number of returned entries, and strip absolute paths (fullpath) before sending results to clients.

• Trim and short-circuit empty queries.
• Cap the result list (e.g. 100 items) to bound payload size.
• Remove or omit the fullpath field (defined in agent/runtime-interface/src/file-operations.ts:8, and populated in agent/runtime-node/src/index.ts:782 and agent/runtime-mock/src/mock-file-system.ts:554) before returning to remote consumers.

-        fuzzyFileSearch: async (searchString) => {
-          return this.clientRuntime.fileSystem.fuzzySearch(searchString);
-        },
+        fuzzyFileSearch: async (searchString) => {
+          const q = String(searchString ?? '').trim();
+          if (!q) {
+            return [];
+          }
+
+          const results = await this.clientRuntime.fileSystem.fuzzySearch(q);
+          // Strip absolute paths before exposing to clients
+          const sanitized = results.map(({ filename, filepath, dirname }) => ({
+            filename,
+            filepath,
+            dirname,
+          }));
+          // Cap to a reasonable size for transport/UI (tune as needed)
+          return sanitized.slice(0, 100);
+        },
agent/runtime-node/src/index.ts (5)

38-43: Add cache invalidation on file mutations

TTL helps, but writes/moves/deletes can keep the index stale for up to 30s. Invalidate on mutations for fresher results.

// Add near other privates
private invalidateFileIndexCache(): void {
  this.fileSearchCache = null;
  // allow concurrent callers to rebuild
}

Apply calls after successful operations (examples):

@@ async writeFile(path: string, content: string) {
-      await fs.writeFile(fullPath, content, 'utf-8');
+      await fs.writeFile(fullPath, content, 'utf-8');
+      this.invalidateFileIndexCache();
@@ async deleteFile(path: string) {
-      await fs.unlink(fullPath);
+      await fs.unlink(fullPath);
+      this.invalidateFileIndexCache();
@@ async moveFile(source: string, destination: string) {
-      await fs.rename(sourcePath, destPath);
+      await fs.rename(sourcePath, destPath);
+      this.invalidateFileIndexCache();
@@ async copyFile(source: string, destination: string) {
-      await fs.copyFile(sourcePath, destPath);
+      await fs.copyFile(sourcePath, destPath);
+      this.invalidateFileIndexCache();

Optionally make CACHE_TTL configurable via provider config for large repos.

Also applies to: 49-53


712-724: Short-circuit empty query and bound result size

Avoid expensive broad matches and large payloads.

-  async fuzzySearch(searchString: string): Promise<FuzzyFileSearchResult> {
+  async fuzzySearch(searchString: string): Promise<FuzzyFileSearchResult> {
     // Build or refresh the file index if needed
     await this.ensureFileIndexBuilt();

     if (!this.fileSearchCache) {
       // This shouldn't happen after ensureFileIndexBuilt, but handle it gracefully
       return [];
     }

-    // Perform the fuzzy search
-    const results = this.fileSearchCache.fuse.search(searchString);
-    return results;
+    const q = String(searchString ?? '').trim();
+    if (!q) return [];
+    const results = this.fileSearchCache.fuse.search(q);
+    return results.slice(0, 200); // cap for transport/UI
   }

781-789: Consider not exposing absolute fullpath (privacy)

Indexing with fullpath is fine, but returning it may leak local paths. Prefer returning relative paths only and compute absolute paths inside the provider when needed.


792-810: Fuse options: consider enabling extended search for power users

Optional: useExtendedSearch: true allows ^=, $=, etc., which often helps file search. Threshold 0.4 is reasonable.


821-829: Replace console.error with a shared logger

Keeps logs consistent and testable; also consider debouncing rebuild failures.

agent/runtime-mock/src/mock-file-system.ts (3)

48-52: Add cache invalidation on mutations (mirror node runtime)

Same stale-index concern applies here; invalidate on write/move/delete.

private invalidateFileIndexCache(): void {
  this.fileSearchCache = null;
}

Call after successful writeFile/deleteFile/moveFile/copyFile operations.


484-496: Short-circuit empty query and cap results

Prevents broad matches and large payloads in tests/mocks too.

   async fuzzySearch(searchString: string): Promise<FuzzyFileSearchResult> {
     // Build or refresh the file index if needed
     await this.ensureFileIndexBuilt();

     if (!this.fileSearchCache) {
       // This shouldn't happen after ensureFileIndexBuilt, but handle it gracefully
       return [];
     }

     // Perform the fuzzy search
-    const results = this.fileSearchCache.fuse.search(searchString);
-    return results;
+    const q = String(searchString ?? '').trim();
+    if (!q) return [];
+    const results = this.fileSearchCache.fuse.search(q);
+    return results.slice(0, 200);
   }

524-611: Align ignore semantics with node runtime for parity

Mock uses a simple wildcard matcher; node uses gitignore semantics. Consider reusing the same ignore patterns/logic to keep test behavior aligned.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0b1b17d and c3b3ba1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • agent/client/src/Agent.ts (1 hunks)
  • agent/runtime-interface/package.json (1 hunks)
  • agent/runtime-interface/src/file-operations.ts (3 hunks)
  • agent/runtime-interface/src/index.ts (1 hunks)
  • agent/runtime-mock/package.json (1 hunks)
  • agent/runtime-mock/src/mock-file-system.ts (5 hunks)
  • agent/runtime-node/package.json (1 hunks)
  • agent/runtime-node/src/index.ts (4 hunks)
  • packages/karton-contract/package.json (1 hunks)
  • packages/karton-contract/src/index.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use any. NEVER use unknown unless absolutely necessary.

Files:

  • agent/runtime-interface/src/index.ts
  • agent/client/src/Agent.ts
  • agent/runtime-node/src/index.ts
  • packages/karton-contract/src/index.ts
  • agent/runtime-interface/src/file-operations.ts
  • agent/runtime-mock/src/mock-file-system.ts
**/package.json

📄 CodeRabbit inference engine (.cursor/rules/monorepo-guide.mdc)

Local packages can be linked using workspace protocol workspace:* in package.json files.

Use the scope "deps" for dependency updates in package.json or similar files.

Files:

  • agent/runtime-node/package.json
  • agent/runtime-mock/package.json
  • packages/karton-contract/package.json
  • agent/runtime-interface/package.json
packages/*/**

📄 CodeRabbit inference engine (.cursor/rules/precommit.mdc)

For changes in packages/*, use the package name as the scope (e.g., changes in packages/core use scope "core").

Files:

  • packages/karton-contract/package.json
  • packages/karton-contract/src/index.ts
🧠 Learnings (3)
📚 Learning: 2025-08-26T00:53:53.537Z
Learnt from: juliangoetze
PR: stagewise-io/stagewise#608
File: .changeset/public-chairs-bet.md:1-10
Timestamp: 2025-08-26T00:53:53.537Z
Learning: The stagewise/karton-contract package is not versioned and therefore doesn't need entries in changesets, even when its exports are modified.

Applied to files:

  • packages/karton-contract/package.json
📚 Learning: 2025-08-17T19:03:32.418Z
Learnt from: CR
PR: stagewise-io/stagewise#0
File: toolbar/core/CLAUDE.md:0-0
Timestamp: 2025-08-17T19:03:32.418Z
Learning: Build the `stagewise/agent-interface` package before building this package during development

Applied to files:

  • packages/karton-contract/package.json
📚 Learning: 2025-08-03T19:30:01.868Z
Learnt from: CR
PR: stagewise-io/stagewise#0
File: apps/cli/CLAUDE.md:0-0
Timestamp: 2025-08-03T19:30:01.868Z
Learning: Keep workspace packages (`stagewise/*`) external in the ESBuild bundle

Applied to files:

  • packages/karton-contract/package.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (8)
agent/runtime-node/package.json (1)

26-27: Deps for fuzzy search look correct (runtime deps).

Adding fast-glob and fuse.js under dependencies matches their runtime use in Node provider. No further action from my side.

agent/runtime-mock/package.json (1)

26-26: Mock runtime: fuse.js as a runtime dep is appropriate.

This aligns with the mock provider’s in-process fuzzy index.

packages/karton-contract/package.json (1)

22-22: Contract now depends on runtime-interface — confirm type-only usage to avoid runtime coupling.

If imports from @stagewise/agent-runtime-interface are type-only, consider keeping them strictly as type imports so the built JS doesn’t require this package at runtime.

Would you like me to scan src/index.ts to verify all imports from agent-runtime-interface are import type?

agent/runtime-interface/src/file-operations.ts (2)

139-145: Specify contract semantics for fuzzySearch in JSDoc.

Document whether:

  • Search scope is the workspace root.
  • .gitignore is respected by default.
  • Results are limited (default limit).
  • Returned paths are relative or absolute in FileData.

This avoids divergent behavior across providers.


439-439: All BaseFileSystemProvider subclasses already implement fuzzySearch

Both NodeFileSystemProvider and MockFileSystemProvider define an async fuzzySearch(searchString: string): Promise<FuzzyFileSearchResult> matching the new abstract method in BaseFileSystemProvider:

  • agent/runtime-node/src/index.ts:712–714 — NodeFileSystemProvider.fuzzySearch
  • agent/runtime-mock/src/mock-file-system.ts:484–486 — MockFileSystemProvider.fuzzySearch

No further action needed.

packages/karton-contract/src/index.ts (1)

3-3: Dependency & RPC wiring verified

  • @stagewise/agent-runtime-interface is declared under dependencies in packages/karton-contract/package.json (as workspace:*).
  • The fuzzyFileSearch procedure is exported in packages/karton-contract/src/index.ts (line 72) and implemented in agent/client/src/Agent.ts (lines 295–297).

No further action needed—changes look correctly wired and typed.

agent/runtime-interface/src/index.ts (1)

15-17: Types-only re-exports are correct

Exporting FileData and FuzzyFileSearchResult as type-only keeps runtime clean. LGTM.

agent/runtime-node/src/index.ts (1)

1-7: ESM imports for fast-glob and fuse look fine

No issues spotted with default/type imports. LGTM.

Comment on lines +3 to 5
import { basename, dirname } from 'node:path';
import Fuse, { type IFuseOptions } from 'fuse.js';
import {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use POSIX path helpers with memfs to avoid Windows separator issues

Memfs is POSIX-like; node:path may emit backslashes on Windows. Use node:path/posix for consistency across platforms.

-import { basename, dirname } from 'node:path';
+import { basename, dirname } from 'node:path/posix';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { basename, dirname } from 'node:path';
import Fuse, { type IFuseOptions } from 'fuse.js';
import {
import { basename, dirname } from 'node:path/posix';
import Fuse, { type IFuseOptions } from 'fuse.js';
import {
🤖 Prompt for AI Agents
In agent/runtime-mock/src/mock-file-system.ts around lines 3 to 5, the file
imports basename and dirname from 'node:path', which can produce backslashes on
Windows; replace that import with the POSIX path helpers by importing basename
and dirname from 'node:path/posix' (or import * as posix from 'node:path/posix'
and use posix.basename/posix.dirname) so memfs receives consistent POSIX-style
paths across platforms.

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: 7

🧹 Nitpick comments (11)
agent/runtime-node/build.ts (1)

45-45: Make keepNames dev-friendly (tie to watch).

Preserving names helps stack traces during watch; obfuscation belongs to release builds.

-  keepNames: false, // Don't preserve function/class names
+  keepNames: isWatch, // Preserve names in dev; strip in release

Optional (strongly recommended) to align other flags with watch vs release:

-  sourcemap: false, // Disable source maps for security
-  minify: true, // Always minify for published packages
+  sourcemap: isWatch ? 'inline' : false,
+  minify: !isWatch,
toolbar/core/src/layouts/default.tsx (1)

218-218: Propagating position into ChatPanel is correct; consider a shared type alias.

Define a shared PanelPosition type (e.g., in src/panels/types.ts) and reuse here and in ChatPanel/Footer to avoid inline duplicates.

toolbar/core/src/panels/chat/index.tsx (1)

14-21: Avoid repeating inline position type; extract a reusable alias.

Create a PanelPosition type and import it here and in callers/children to keep signatures consistent.

-export function ChatPanel({
-  position,
-}: {
-  position?: {
-    isTopHalf: boolean;
-    isLeftHalf: boolean;
-  };
-}) {
+import type { PanelPosition } from '@/panels/types';
+export function ChatPanel({ position }: { position?: PanelPosition }) {
toolbar/core/src/components/file-mention-chip.tsx (1)

21-49: Minor a11y polish for chip container.

Add an accessible label to the chip and a more descriptive title on the remove button.

-    <div
+    <div
       className={cn(
         'flex min-w-fit shrink-0 items-center gap-1 rounded-lg border border-border/20 bg-white/30 px-2 py-1 shadow-sm backdrop-blur-lg transition-all hover:border-border/40 hover:bg-white/80',
         className,
       )}
       title={filepath}
+      aria-label={`Selected file: ${filename}`}
     >
@@
-        <button
+        <button
           type="button"
           onClick={(e) => {
             e.stopPropagation();
             onRemove();
           }}
           className="text-muted-foreground transition-colors hover:text-red-500"
-          aria-label="Remove file mention"
+          aria-label={`Remove ${filename}`}
+          title={`Remove ${filename}`}
         >
toolbar/core/src/components/hotkey-listener.tsx (2)

38-55: ESC prioritization to close the mention dropdown is correct.

This matches expected UX and prevents unintended chat-close when the dropdown is open. Consider centralizing a “closeMentionDropdown” action in chat state to also reset related ephemeral state (query, selection) instead of only toggling the boolean, so both hotkey and input handlers use the same cleanup.


66-68: Nit: trim memo dependencies.

setIsMentionDropdownOpen is stable and doesn’t need to be in the deps array.

-        isMentionDropdownOpen,
-        setIsMentionDropdownOpen,
+        isMentionDropdownOpen,
toolbar/core/src/components/file-mention-dropdown.tsx (2)

109-121: Expose or remove no-op ref methods.

navigateUp/navigateDown are stubs. Either wire them to callbacks (so parents can delegate), or drop them from the ref until needed to avoid a misleading API surface.

 export interface FileMentionDropdownRef {
-  navigateUp: () => void;
-  navigateDown: () => void;
+  // Add when needed; currently unused
+  // navigateUp: () => void;
+  // navigateDown: () => void;
   selectCurrent: () => void;
 }
@@
-    useImperativeHandle(ref, () => ({
-      navigateUp: () => {
-        // Navigate up logic handled by parent
-      },
-      navigateDown: () => {
-        // Navigate down logic handled by parent
-      },
+    useImperativeHandle(ref, () => ({
       selectCurrent: () => {
         if (searchResults[selectedIndex]) {
           onSelect(searchResults[selectedIndex]);
         }
       },
     }));

180-205: Improve a11y semantics for list and options.

Use listbox/option roles with aria-selected and keyboard focusability.

-                  <div
-                    role="button"
+                  <div
+                    role="option"
+                    aria-selected={displaySelectedIndex === displayIndex}
+                    tabIndex={-1}

Optionally set role="listbox" on the scroll container and manage aria-activedescendant.

toolbar/core/src/hooks/use-chat-state.tsx (2)

167-173: Cleanup mention UI on stop.

Also close the dropdown when stopping prompt creation to avoid stale UI state on re-entry.

   const stopPromptCreation = useCallback(() => {
     setIsPromptCreationMode(false);
     // Always stop context selector when stopping prompt creation
     setIsContextSelectorMode(false);
     setDomContextElements([]);
     setSelectedFiles([]);
+    setIsMentionDropdownOpen(false);
     plugins.forEach((plugin) => {
       plugin.onPromptingAbort?.();
     });
   }, [plugins]);

330-335: Redundant reset.

setSelectedFiles([]) is immediately followed by stopPromptCreation(), which also clears selected files. Remove one.

-      clearFileAttachments();
-      setSelectedFiles([]);
-      stopPromptCreation(); // This also stops context selector
+      clearFileAttachments();
+      stopPromptCreation(); // This also clears selected files and stops context selector
toolbar/core/src/panels/chat/panel-footer.tsx (1)

223-271: Deduplication should key on filepath only.

Filename is redundant; filepath is unique and sufficient.

-      const alreadySelected = selectedFiles.some(
-        (f) =>
-          f.filepath === file.item.filepath &&
-          f.filename === file.item.filename,
-      );
+      const alreadySelected = selectedFiles.some(
+        (f) => f.filepath === file.item.filepath,
+      );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c3b3ba1 and dfed16b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • agent/runtime-interface/package.json (1 hunks)
  • agent/runtime-node/build.ts (1 hunks)
  • packages/karton-contract/src/metadata.ts (1 hunks)
  • toolbar/core/package.json (1 hunks)
  • toolbar/core/src/components/file-mention-chip.tsx (1 hunks)
  • toolbar/core/src/components/file-mention-dropdown.tsx (1 hunks)
  • toolbar/core/src/components/hotkey-listener.tsx (3 hunks)
  • toolbar/core/src/hooks/use-chat-state.tsx (9 hunks)
  • toolbar/core/src/layouts/default.tsx (1 hunks)
  • toolbar/core/src/panels/chat/index.tsx (2 hunks)
  • toolbar/core/src/panels/chat/panel-footer.tsx (8 hunks)
  • toolbar/core/src/utils/file-icons.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • agent/runtime-interface/package.json
🧰 Additional context used
📓 Path-based instructions (8)
packages/*/**

📄 CodeRabbit inference engine (.cursor/rules/precommit.mdc)

For changes in packages/*, use the package name as the scope (e.g., changes in packages/core use scope "core").

Files:

  • packages/karton-contract/src/metadata.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

NEVER use any. NEVER use unknown unless absolutely necessary.

Files:

  • packages/karton-contract/src/metadata.ts
  • toolbar/core/src/layouts/default.tsx
  • agent/runtime-node/build.ts
  • toolbar/core/src/utils/file-icons.tsx
  • toolbar/core/src/components/file-mention-chip.tsx
  • toolbar/core/src/panels/chat/index.tsx
  • toolbar/core/src/components/file-mention-dropdown.tsx
  • toolbar/core/src/components/hotkey-listener.tsx
  • toolbar/core/src/panels/chat/panel-footer.tsx
  • toolbar/core/src/hooks/use-chat-state.tsx
toolbar/*/**

📄 CodeRabbit inference engine (.cursor/rules/precommit.mdc)

For changes in toolbar/*, use the package name as the scope (e.g., changes in toolbar/extension use scope "extension").

Files:

  • toolbar/core/src/layouts/default.tsx
  • toolbar/core/package.json
  • toolbar/core/src/utils/file-icons.tsx
  • toolbar/core/src/components/file-mention-chip.tsx
  • toolbar/core/src/panels/chat/index.tsx
  • toolbar/core/src/components/file-mention-dropdown.tsx
  • toolbar/core/src/components/hotkey-listener.tsx
  • toolbar/core/src/panels/chat/panel-footer.tsx
  • toolbar/core/src/hooks/use-chat-state.tsx
toolbar/core/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (toolbar/core/CLAUDE.md)

All source code is written in TypeScript

Files:

  • toolbar/core/src/layouts/default.tsx
  • toolbar/core/src/utils/file-icons.tsx
  • toolbar/core/src/components/file-mention-chip.tsx
  • toolbar/core/src/panels/chat/index.tsx
  • toolbar/core/src/components/file-mention-dropdown.tsx
  • toolbar/core/src/components/hotkey-listener.tsx
  • toolbar/core/src/panels/chat/panel-footer.tsx
  • toolbar/core/src/hooks/use-chat-state.tsx
toolbar/core/src/layouts/**/*.{ts,tsx}

📄 CodeRabbit inference engine (toolbar/core/CLAUDE.md)

Place main UI layout definitions under src/layouts

Files:

  • toolbar/core/src/layouts/default.tsx
**/package.json

📄 CodeRabbit inference engine (.cursor/rules/monorepo-guide.mdc)

Local packages can be linked using workspace protocol workspace:* in package.json files.

Use the scope "deps" for dependency updates in package.json or similar files.

Files:

  • toolbar/core/package.json
toolbar/core/src/panels/**/*.{ts,tsx}

📄 CodeRabbit inference engine (toolbar/core/CLAUDE.md)

Place panel content (chat, settings, agent connection settings, etc.) under src/panels

Files:

  • toolbar/core/src/panels/chat/index.tsx
  • toolbar/core/src/panels/chat/panel-footer.tsx
toolbar/core/src/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (toolbar/core/CLAUDE.md)

Place application hooks under src/hooks

Files:

  • toolbar/core/src/hooks/use-chat-state.tsx
🧠 Learnings (5)
📚 Learning: 2025-08-17T19:03:32.418Z
Learnt from: CR
PR: stagewise-io/stagewise#0
File: toolbar/core/CLAUDE.md:0-0
Timestamp: 2025-08-17T19:03:32.418Z
Learning: Applies to toolbar/core/src/panels/**/*.{ts,tsx} : Place panel content (chat, settings, agent connection settings, etc.) under `src/panels`

Applied to files:

  • toolbar/core/src/layouts/default.tsx
  • toolbar/core/src/panels/chat/index.tsx
  • toolbar/core/src/panels/chat/panel-footer.tsx
📚 Learning: 2025-08-03T19:30:01.868Z
Learnt from: CR
PR: stagewise-io/stagewise#0
File: apps/cli/CLAUDE.md:0-0
Timestamp: 2025-08-03T19:30:01.868Z
Learning: Keep workspace packages (`stagewise/*`) external in the ESBuild bundle

Applied to files:

  • agent/runtime-node/build.ts
📚 Learning: 2025-08-03T19:30:01.868Z
Learnt from: CR
PR: stagewise-io/stagewise#0
File: apps/cli/CLAUDE.md:0-0
Timestamp: 2025-08-03T19:30:01.868Z
Learning: Bundle most dependencies (e.g., axios, chalk, commander, zod, express, winston) into the output file using ESBuild

Applied to files:

  • agent/runtime-node/build.ts
📚 Learning: 2025-08-17T19:03:32.418Z
Learnt from: CR
PR: stagewise-io/stagewise#0
File: toolbar/core/CLAUDE.md:0-0
Timestamp: 2025-08-17T19:03:32.418Z
Learning: Applies to toolbar/core/src/**/*.{ts,tsx} : All source code is written in TypeScript

Applied to files:

  • toolbar/core/src/utils/file-icons.tsx
📚 Learning: 2025-07-18T16:48:09.404Z
Learnt from: CR
PR: stagewise-io/stagewise#0
File: .cursor/rules/plugin-creation-guide.mdc:0-0
Timestamp: 2025-07-18T16:48:09.404Z
Learning: Applies to plugins/**/*.ts : Use the `onPromptingAbort` method to perform cleanup or state reset if the user cancels prompting.

Applied to files:

  • toolbar/core/src/hooks/use-chat-state.tsx
🧬 Code graph analysis (5)
toolbar/core/src/layouts/default.tsx (1)
toolbar/core/src/panels/chat/index.tsx (1)
  • ChatPanel (14-263)
toolbar/core/src/components/file-mention-chip.tsx (1)
toolbar/core/src/utils/file-icons.tsx (1)
  • getFileIcon (67-231)
toolbar/core/src/panels/chat/index.tsx (1)
toolbar/core/src/panels/chat/panel-footer.tsx (1)
  • ChatPanelFooter (33-598)
toolbar/core/src/components/file-mention-dropdown.tsx (1)
toolbar/core/src/utils/file-icons.tsx (1)
  • getFileIcon (67-231)
toolbar/core/src/panels/chat/panel-footer.tsx (3)
toolbar/core/src/hooks/use-chat-state.tsx (1)
  • useChatState (384-390)
toolbar/core/src/components/file-mention-dropdown.tsx (4)
  • FuseResult (24-27)
  • FileData (17-22)
  • FileMentionDropdownRef (39-43)
  • FileMentionDropdown (45-225)
toolbar/core/src/components/file-mention-chip.tsx (1)
  • FileMentionChip (12-50)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (11)
packages/karton-contract/src/metadata.ts (1)

168-178: selectedFiles is propagated into ChatMessage metadata
Verified in packages/toolbar/core/src/hooks/use-chat-state.tsx where metadata.selectedFiles = selectedFiles; (lines 259–261) and the ChatMessage object includes selectedFiles.

toolbar/core/src/panels/chat/index.tsx (1)

252-256: LGTM: Footer receives position for dropdown orientation.

Prop threading looks correct and matches Footer’s expectations.

toolbar/core/src/components/file-mention-chip.tsx (1)

12-20: LGTM: Clear API and correct icon/color usage.

Props are minimal, title shows full path, and remove handler stops propagation.

toolbar/core/src/components/hotkey-listener.tsx (1)

18-20: Dropdown state wiring looks correct.

No issues with adding isMentionDropdownOpen/setIsMentionDropdownOpen to the chat state consumption.

toolbar/core/src/components/file-mention-dropdown.tsx (1)

64-101: Floating UI config is solid.

Good use of offset/flip/shift/size with autoUpdate and dynamic placement based on toolbar position.

toolbar/core/src/hooks/use-chat-state.tsx (3)

53-58: Selected files state: OK.

Shape and exposure via context look good.


116-121: State initialization: OK.

No issues with selectedFiles/isMentionDropdownOpen defaults.


258-262: No action needed: selectedFiles is defined in the contract.
The UserMessageMetadata schema (packages/karton-contract/src/metadata.ts) includes an optional selectedFiles: z.array(…).optional(), and ChatMessage['metadata'] uses this type.

toolbar/core/src/panels/chat/panel-footer.tsx (3)

36-44: Position prop integration looks good.

Deriving isToolbarAtBottom from position.isTopHalf is straightforward and keeps layout concerns local.


115-156: Keyboard nav behavior: OK.

Arrow and Ctrl+N/P handling matches visual order (top/bottom toolbar). Guarded by isMentionDropdownOpen.


552-562: Dropdown wiring/render: OK.

Anchor, isOpen, loading and selection props are correctly forwarded.

Comment on lines +13 to +40
external: [
'vscode', // VSCode extension API should always be external
// Node.js built-in modules
'os',
'path',
'fs',
'util',
'stream',
'events',
'buffer',
'crypto',
'child_process',
'http',
'https',
'url',
'querystring',
'assert',
'zlib',
'tty',
'net',
'dns',
// Dependencies to prevent bundling issues
'fast-glob',
'fuse.js',
'ignore',
'minimatch',
'@stagewise/agent-runtime-interface',
],
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

External list: compute Node built-ins programmatically and bundle app deps (avoid over-externalizing).

  • Hardcoding built-ins is redundant and misses node:-prefixed specifiers (e.g., node:path) used by the new code; esbuild treats Node built-ins as external by default, but if you want explicitness, generate both forms from builtinModules.
  • Per our learning, keep workspace packages external, but bundle most third-party deps; externalizing fast-glob, fuse.js, ignore, minimatch shifts breakage to runtime and can cause “module not found” unless pinned in deps. The summary only mentions fast-glob/fuse.js added—please verify ignore/minimatch are declared if they’re truly needed externally.

Apply the following changes:

  1. Add this near the imports (outside the selected range):
import { builtinModules } from 'node:module';

const nodeBuiltins = Array.from(
  new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)])
);
  1. Replace the externals block to rely on the generated list and stop externalizing app deps:
-  external: [
-    'vscode', // VSCode extension API should always be external
-    // Node.js built-in modules
-    'os',
-    'path',
-    'fs',
-    'util',
-    'stream',
-    'events',
-    'buffer',
-    'crypto',
-    'child_process',
-    'http',
-    'https',
-    'url',
-    'querystring',
-    'assert',
-    'zlib',
-    'tty',
-    'net',
-    'dns',
-    // Dependencies to prevent bundling issues
-    'fast-glob',
-    'fuse.js',
-    'ignore',
-    'minimatch',
-    '@stagewise/agent-runtime-interface',
-  ],
+  external: [
+    'vscode',
+    ...nodeBuiltins,
+    '@stagewise/agent-runtime-interface',
+  ],

If you must keep any third-party lib external due to a demonstrated bundling issue, document the reason in a code comment and ensure it’s listed in dependencies.

Run to verify imports and deps:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Do we use node: built-ins?
rg -nP --type=ts 'from\s+["'\'']node:' | sed 's/^/node-prefix import: /'

# 2) Are externals declared as runtime-node deps?
RNPKG="$(fd -p package.json -t f agent/runtime-node | head -n1)"
echo "Using package.json: $RNPKG"
jq -r '.dependencies // {} | keys[]' "$RNPKG" | sort > /tmp/deps.txt
printf "%s\n" fast-glob fuse.js ignore minimatch | sort > /tmp/need.txt
echo "Missing externals in dependencies (if you keep them external):"
comm -13 /tmp/deps.txt /tmp/need.txt || true

Length of output: 11152


Generate Node built-ins dynamically and bundle third-party modules
Replace the hardcoded built-in list in agent/runtime-node/build.ts with a programmatic builtinModules import (including node: prefixes) and only externalize VSCode API and the workspace package; bundle everything else (removing fast-glob, fuse.js, ignore, minimatch).

--- a/agent/runtime-node/build.ts
+++ b/agent/runtime-node/build.ts
@@ –1,4 +1,9 @@
-import { build } from 'esbuild';
+import { build } from 'esbuild';
+import { builtinModules } from 'node:module';
+
+const nodeBuiltins = Array.from(
+  new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)])
+);

 export async function createBundle() {
   await build({
@@ –13,18 +18,7 @@ export async function createBundle() {
     // keep other options...
-    external: [
-      'vscode',
-      'os','path','fs','util','stream','events','buffer','crypto',
-      'child_process','http','https','url','querystring','assert',
-      'zlib','tty','net','dns',
-      'fast-glob','fuse.js','ignore','minimatch',
-      '@stagewise/agent-runtime-interface',
-    ],
+    external: ['vscode', ...nodeBuiltins, '@stagewise/agent-runtime-interface'],
   });
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
external: [
'vscode', // VSCode extension API should always be external
// Node.js built-in modules
'os',
'path',
'fs',
'util',
'stream',
'events',
'buffer',
'crypto',
'child_process',
'http',
'https',
'url',
'querystring',
'assert',
'zlib',
'tty',
'net',
'dns',
// Dependencies to prevent bundling issues
'fast-glob',
'fuse.js',
'ignore',
'minimatch',
'@stagewise/agent-runtime-interface',
],
import { build } from 'esbuild';
import { builtinModules } from 'node:module';
const nodeBuiltins = Array.from(
new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)])
);
export async function createBundle() {
await build({
// keep other options...
external: ['vscode', ...nodeBuiltins, '@stagewise/agent-runtime-interface'],
});
}
🤖 Prompt for AI Agents
In agent/runtime-node/build.ts around lines 13 to 40, replace the hardcoded list
of Node built-ins and third-party externals with a programmatic approach: import
builtinModules from 'module' (i.e., const { builtinModules } = require('module')
or ES import) and construct the external array to include only 'vscode' and the
workspace package '@stagewise/agent-runtime-interface' plus all Node built-ins
both with and without the 'node:' prefix (deduplicated); remove 'fast-glob',
'fuse.js', 'ignore', and 'minimatch' from externals so they are bundled, and set
the rollup/webpack external option to that generated array.

Comment on lines +87 to 91
},
"dependencies": {
"@floating-ui/react": "^0.27.16",
"react-icons": "^5.5.0"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add deps scope and verify React 19 compatibility before merging.

  • Follow guideline: use "deps" scope for dependency updates. Suggest commit message like: deps(core): add @floating-ui/react and react-icons.
  • Potential bundle impact: we now ship both lucide-react and react-icons. If file-icons can use a single icon source, consider consolidating to reduce size.

To confirm compatibility and avoid surprises with React 19.1.0, please run these web checks:

Optionally, inspect how many icons we import to estimate bundle cost:


🏁 Script executed:

#!/bin/bash
# Count react-icons imports and sites
rg -nP "from 'react-icons/" -C2 toolbar/core/src | wc -l
rg -nP "from 'react-icons/" -C2 toolbar/core/src

Length of output: 406


🌐 Web query:

Do @floating-ui/react v0.27.x and react-icons v5.5.0 officially support React 19? What are their peerDependencies?

💡 Result:

Short answer: Yes — both are compatible with React 19 based on their published package metadata.

Details & peerDependencies:

  • @floating-ui/react v0.27.x — peerDependencies: "react": ">=17.0.0", "react-dom": ">=17.0.0" (so React 19 satisfies this). [1].
  • react-icons v5.5.0 — the package states it "Requires React 16.3 or higher" and does not restrict to a specific major (effectively compatible with React 19); community/package snapshots show [email protected] listing react as peerDependency "*" in some registries. [2][3].

Sources:

  1. @floating-ui/react package.json (v0.27.5) — peerDependencies: react >=17.0.0, react-dom >=17.0.0. [https://unpkg.com/@floating-ui/[email protected]/files/package.json] [1]
  2. react-icons v5.5.0 npm page — "Requires React 16.3 or higher." (package metadata). [https://www.npmjs.com/package/react-icons/v/5.5.0] [2]
  3. Example registry/package-lock snapshot showing [email protected] peerDependencies: { "react": "*" }. [3]

If you want, I can fetch the exact package.json file for [email protected] (registry/unpkg) and paste the peerDependencies line.


Use deps scope for this commit
Commit message should be

deps(core): add @floating-ui/react and react-icons

Both packages officially support React 19 (peerDependencies ≥17.0.0 and ≥16.3.0).
Optional: consolidate icon sources—toolbar/core/src/utils/file-icons.tsx currently imports 5 icons from react-icons—to reduce bundle size.

🤖 Prompt for AI Agents
In toolbar/core/package.json around lines 87-91, the commit message for adding
the two deps needs to use the deps scope; amend the change so the commit message
is exactly "deps(core): add @floating-ui/react and react-icons" (use git commit
--amend -m "...", or create a new commit with that message), ensure the two
packages remain in dependencies as shown, and optionally reduce bundle size by
consolidating react-icons imports in toolbar/core/src/utils/file-icons.tsx
(import only the used icons or replace multiple imports with a single
specific-subpackage import) to minimize footprint.

Comment on lines +81 to +84
const mentionDropdownRef = useRef<FileMentionDropdownRef>(null);
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const fuzzyFileSearch = useKartonProcedure((p) => p.fuzzyFileSearch);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix timer ref type for browser/TS compatibility.

Prefer ReturnType to avoid NodeJS.Timeout mismatches in DOM builds.

-  const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const mentionDropdownRef = useRef<FileMentionDropdownRef>(null);
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const fuzzyFileSearch = useKartonProcedure((p) => p.fuzzyFileSearch);
const mentionDropdownRef = useRef<FileMentionDropdownRef>(null);
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const fuzzyFileSearch = useKartonProcedure((p) => p.fuzzyFileSearch);
🤖 Prompt for AI Agents
In toolbar/core/src/panels/chat/panel-footer.tsx around lines 81 to 84, the
searchTimeoutRef is typed as NodeJS.Timeout | null which can cause type
mismatches in browser/DOM builds; change its type to ReturnType<typeof
setTimeout> | null (i.e. const searchTimeoutRef = useRef<ReturnType<typeof
setTimeout> | null>(null)) so the timeout works across Node and browser
environments without NodeJS types.

Comment on lines +296 to +304
const currentMentions = extractFileMentions(value);
const updatedSelectedFiles = selectedFiles.filter((file) =>
currentMentions.some((mention) => mention.startsWith(file.filepath)),
);

// Only update if there's a difference
if (updatedSelectedFiles.length !== selectedFiles.length) {
setSelectedFiles(updatedSelectedFiles);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Tighten syncing logic: use exact match over startsWith.

Using startsWith can incorrectly retain chips when a different, longer path is mentioned. Exact equality is safer.

-      const updatedSelectedFiles = selectedFiles.filter((file) =>
-        currentMentions.some((mention) => mention.startsWith(file.filepath)),
-      );
+      const updatedSelectedFiles = selectedFiles.filter((file) =>
+        currentMentions.some((mention) => mention === file.filepath),
+      );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const currentMentions = extractFileMentions(value);
const updatedSelectedFiles = selectedFiles.filter((file) =>
currentMentions.some((mention) => mention.startsWith(file.filepath)),
);
// Only update if there's a difference
if (updatedSelectedFiles.length !== selectedFiles.length) {
setSelectedFiles(updatedSelectedFiles);
}
const currentMentions = extractFileMentions(value);
- const updatedSelectedFiles = selectedFiles.filter((file) =>
- currentMentions.some((mention) => mention.startsWith(file.filepath)),
const updatedSelectedFiles = selectedFiles.filter((file) =>
currentMentions.some((mention) => mention === file.filepath),
);
// Only update if there's a difference
if (updatedSelectedFiles.length !== selectedFiles.length) {
setSelectedFiles(updatedSelectedFiles);
}
🤖 Prompt for AI Agents
In toolbar/core/src/panels/chat/panel-footer.tsx around lines 296 to 304, the
sync logic currently uses startsWith to match mentions to selectedFiles which
can falsely preserve files when a longer path is mentioned; change the predicate
to use exact equality (compare mention === file.filepath) when building
updatedSelectedFiles so only exact path matches are kept, then keep the existing
conditional that calls setSelectedFiles only if the length differs.

Comment on lines +378 to +398
const performFuzzySearch = useCallback(
async (query: string) => {
if (!query || query.length === 0) {
setMentionSearchResults([]);
return;
}

setIsSearchingFiles(true);
try {
const results = await fuzzyFileSearch(query);
// Limit to top 10 results
setMentionSearchResults(results.slice(0, 10));
} catch (error) {
console.error('Failed to search files:', error);
setMentionSearchResults([]);
} finally {
setIsSearchingFiles(false);
}
},
[fuzzyFileSearch],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Prevent stale async results from overwriting newer searches.

Race-condition: multiple in-flight fuzzy searches can resolve out of order. Gate state updates with a monotonically increasing request id.

-  const performFuzzySearch = useCallback(
+  const latestSearchIdRef = useRef(0);
+
+  const performFuzzySearch = useCallback(
     async (query: string) => {
       if (!query || query.length === 0) {
         setMentionSearchResults([]);
         return;
       }
 
       setIsSearchingFiles(true);
       try {
-        const results = await fuzzyFileSearch(query);
+        const reqId = ++latestSearchIdRef.current;
+        const results = await fuzzyFileSearch(query);
+        if (reqId !== latestSearchIdRef.current) {
+          // A newer search completed; ignore these stale results
+          return;
+        }
         // Limit to top 10 results
         setMentionSearchResults(results.slice(0, 10));
       } catch (error) {
         console.error('Failed to search files:', error);
         setMentionSearchResults([]);
       } finally {
         setIsSearchingFiles(false);
       }
     },
     [fuzzyFileSearch],
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const performFuzzySearch = useCallback(
async (query: string) => {
if (!query || query.length === 0) {
setMentionSearchResults([]);
return;
}
setIsSearchingFiles(true);
try {
const results = await fuzzyFileSearch(query);
// Limit to top 10 results
setMentionSearchResults(results.slice(0, 10));
} catch (error) {
console.error('Failed to search files:', error);
setMentionSearchResults([]);
} finally {
setIsSearchingFiles(false);
}
},
[fuzzyFileSearch],
);
// Prevent stale async results from overwriting newer searches
const latestSearchIdRef = useRef(0);
const performFuzzySearch = useCallback(
async (query: string) => {
if (!query || query.length === 0) {
setMentionSearchResults([]);
return;
}
setIsSearchingFiles(true);
try {
const reqId = ++latestSearchIdRef.current;
const results = await fuzzyFileSearch(query);
if (reqId !== latestSearchIdRef.current) {
// A newer search completed; ignore these stale results
return;
}
// Limit to top 10 results
setMentionSearchResults(results.slice(0, 10));
} catch (error) {
console.error('Failed to search files:', error);
setMentionSearchResults([]);
} finally {
setIsSearchingFiles(false);
}
},
[fuzzyFileSearch],
);
🤖 Prompt for AI Agents
In toolbar/core/src/panels/chat/panel-footer.tsx around lines 378 to 398,
asynchronous fuzzyFileSearch calls can resolve out of order and overwrite newer
results; add a monotonically increasing request id (useRef counter) that you
increment for each call, capture the current id in the closure before awaiting,
and only call setMentionSearchResults and setIsSearchingFiles if the captured id
matches the latest ref value (thus ignoring stale responses); ensure the ref is
not included in the hook dependency array and that the guard is applied in both
success, catch and finally blocks so stale errors or loading flags cannot
overwrite newer state.

Comment on lines +21 to +22
SiSharp,
SiPhp,
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Invalid icon import: SiSharp doesn’t exist in react-icons/si. Use SiCsharp.

This will fail type-check and at runtime. Swap the import and the usage.

-  SiSharp,
+  SiCsharp,
@@
-    cs: { Icon: SiSharp, color: '#239120' },
+    cs: { Icon: SiCsharp, color: '#239120' },

Also applies to: 176-177

🤖 Prompt for AI Agents
In toolbar/core/src/utils/file-icons.tsx around lines 21-22 (and again around
176-177) the icon import and usage reference SiSharp which does not exist in
react-icons/si; replace the import name with SiCsharp and update any
corresponding JSX usages to use SiCsharp instead of SiSharp, ensuring the import
statement and all occurrences across the file are renamed consistently so the
type-check and runtime import resolve correctly.

Comment on lines +57 to +60
export interface FileIconData {
Icon: ComponentType<{ className?: string; style?: React.CSSProperties }>;
color?: string;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type error: React.CSSProperties is referenced without importing React types.

Import CSSProperties from react and use it directly to avoid “Cannot find name 'React'” in TS.

-import type { ComponentType } from 'react';
+import type { ComponentType, CSSProperties } from 'react';
@@
 export interface FileIconData {
-  Icon: ComponentType<{ className?: string; style?: React.CSSProperties }>;
+  Icon: ComponentType<{ className?: string; style?: CSSProperties }>;
   color?: string;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface FileIconData {
Icon: ComponentType<{ className?: string; style?: React.CSSProperties }>;
color?: string;
}
import type { ComponentType, CSSProperties } from 'react';
export interface FileIconData {
Icon: ComponentType<{ className?: string; style?: CSSProperties }>;
color?: string;
}
🤖 Prompt for AI Agents
In toolbar/core/src/utils/file-icons.tsx around lines 57 to 60, the interface
references React.CSSProperties but React types aren't imported; import the
needed types from 'react' (e.g. ComponentType and CSSProperties) at the top of
the file and change the interface to use CSSProperties directly so the type
resolves (ensure the import line includes ComponentType and CSSProperties).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants