Skip to content

Conversation

@stefandevo
Copy link
Collaborator

@stefandevo stefandevo commented Jan 11, 2026

Add comprehensive editor detection and selection system that allows users to configure their preferred IDE for opening branches and worktrees.

Server-side Changes

  • Add /api/worktree/available-editors endpoint to detect installed editors
  • Support detection via CLI commands (cursor, code, zed, subl, etc.)
  • Support detection via macOS app bundles in /Applications and ~/Applications
  • Detect editors: Cursor, VS Code, Zed, Sublime Text, Windsurf, Trae, Rider, WebStorm, Xcode, Android Studio, Antigravity, and file managers

UI Changes

Editor Icons

  • Add new editor-icons.tsx with SVG icons for all supported editors
  • Icons: Cursor, VS Code, Zed, Sublime Text, Windsurf, Trae, Rider, WebStorm, Xcode, Android Studio, Antigravity, Finder
  • getEditorIcon() helper maps editor commands to appropriate icons

Default IDE Setting

  • Add "Default IDE" selector in Settings > Account section
  • Options: Auto-detect (Cursor > VS Code > first available) or explicit choice
  • Setting persists via defaultEditorCommand in global settings

Worktree Dropdown Improvements

  • Implement split-button UX for "Open In" action
  • Click main area: opens directly in default IDE (single click)
  • Click chevron: shows submenu with other editors + Copy Path
  • Each editor shows with its branded icon

Type & Store Changes

  • Add defaultEditorCommand: string | null to GlobalSettings
  • Add to app-store with setDefaultEditorCommand action
  • Add to SETTINGS_FIELDS_TO_SYNC for persistence
  • Add useAvailableEditors hook for fetching detected editors

UI

Within the Kanban Board:

CleanShot 2026-01-11 at 16 30 00@2x

Added default setting in Account:

CleanShot 2026-01-11 at 16 30 58@2x

Summary by CodeRabbit

  • New Features

    • Multi-editor detection with a split "Open in" button: main action uses the effective default, submenu lists other editors and "Copy Path".
    • New Default IDE picker in Account settings with "Auto‑detect" option.
    • Editor icons displayed across the UI.
  • Other

    • If opening in the chosen editor fails, falls back to the system file manager.

✏️ Tip: You can customize this high-level summary in your review settings.

Add comprehensive editor detection and selection system that allows users
to configure their preferred IDE for opening branches and worktrees.

## Server-side Changes

- Add `/api/worktree/available-editors` endpoint to detect installed editors
- Support detection via CLI commands (cursor, code, zed, subl, etc.)
- Support detection via macOS app bundles in /Applications and ~/Applications
- Detect editors: Cursor, VS Code, Zed, Sublime Text, Windsurf, Trae,
  Rider, WebStorm, Xcode, Android Studio, Antigravity, and file managers

## UI Changes

### Editor Icons
- Add new `editor-icons.tsx` with SVG icons for all supported editors
- Icons: Cursor, VS Code, Zed, Sublime Text, Windsurf, Trae, Rider,
  WebStorm, Xcode, Android Studio, Antigravity, Finder
- `getEditorIcon()` helper maps editor commands to appropriate icons

### Default IDE Setting
- Add "Default IDE" selector in Settings > Account section
- Options: Auto-detect (Cursor > VS Code > first available) or explicit choice
- Setting persists via `defaultEditorCommand` in global settings

### Worktree Dropdown Improvements
- Implement split-button UX for "Open In" action
- Click main area: opens directly in default IDE (single click)
- Click chevron: shows submenu with other editors + Copy Path
- Each editor shows with its branded icon

## Type & Store Changes

- Add `defaultEditorCommand: string | null` to GlobalSettings
- Add to app-store with `setDefaultEditorCommand` action
- Add to SETTINGS_FIELDS_TO_SYNC for persistence
- Add `useAvailableEditors` hook for fetching detected editors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Adds backend editor detection and a GET /available-editors endpoint; expands open-in-editor to accept an optional editorCommand and safe execution with fallbacks. UI: new editor icons, hooks, settings picker, split "Open in" menu, persisted defaultEditorCommand, and updated types/mocks.

Changes

Cohort / File(s) Summary
Backend - Editor detection & API
apps/server/src/routes/worktree/routes/open-in-editor.ts, apps/server/src/routes/worktree/index.ts
New detection (CLI + macOS app checks), caching, detectAllEditors/detectDefaultEditor, createGetAvailableEditorsHandler() and route GET /available-editors; createOpenInEditorHandler() accepts optional editorCommand, resolves EditorInfo, executes via safe execFile and falls back to file manager.
UI - Editor icons
apps/ui/src/components/icons/editor-icons.tsx
New SVG icon components and getEditorIcon(command) mapper for editor commands and open -a patterns.
UI - Hooks & state
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts, apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts, apps/ui/src/store/app-store.ts, apps/ui/src/hooks/use-settings-sync.ts
New useAvailableEditors and useEffectiveDefaultEditor; handleOpenInEditor now accepts optional editorCommand; store adds defaultEditorCommand; settings sync includes the field.
UI - Components
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx, apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx, apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx
Replaces single "Open in…" with split button + submenu listing available editors + Copy Path; onOpenInEditor signature extended to (worktree, editorCommand?); removed defaultEditorName prop.
UI - Settings
apps/ui/src/components/views/settings-view/account/account-section.tsx
Adds Default IDE picker (Auto-detect + per-editor options) using icons and persisted defaultEditorCommand.
UI - API mocks & client/types
apps/ui/src/lib/electron.ts, apps/ui/src/lib/http-api-client.ts, apps/ui/src/types/electron.d.ts, libs/types/src/settings.ts
Mock openInEditor accepts optional editorCommand, adds getAvailableEditors(); HTTP client exposes getAvailableEditors; types updated; GlobalSettings adds defaultEditorCommand.
Types - EditorInfo
libs/types/src/editor.ts, libs/types/src/index.ts
New EditorInfo type exported ({ name: string; command: string }).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI
    participant Server
    participant FS

    Note over UI,Server: Detect & list editors
    User->>UI: Open editor picker
    UI->>Server: GET /available-editors
    Server->>FS: Check CLI commands & macOS app bundles
    FS-->>Server: Existence results
    Server->>Server: Cache & return editor list
    Server-->>UI: [{name, command}, ...]

    Note over UI,Server: Open in selected editor
    User->>UI: Select editor (or default)
    UI->>Server: POST /open-in-editor {path, editorCommand?}
    Server->>Server: Resolve EditorInfo
    Server->>FS: Exec editor command (safe execFile)
    alt launch succeeds
        FS-->>Server: Process started
        Server-->>UI: {success:true, editorName}
    else launch fails
        FS-->>Server: Error
        Server->>FS: Launch file-manager fallback
        FS-->>Server: Process started
        Server-->>UI: {success:true, editorName:"File Manager"}
    end
    UI->>User: Show result/notification
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Ready-To-Merge

Suggested reviewers

  • Shironex

Poem

🐰 I hopped through PATHs and Mac app trees,
Searched for editors beneath leaves and keys.
Pick one with a click or let auto-choose—
A rabbit cheers as your worktree blooms. 🌿✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.75% 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 accurately summarizes the main changes: adding a default IDE setting feature and multi-editor support with icons throughout the UI and backend.

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


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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @stefandevo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the user experience by introducing a comprehensive and user-configurable system for interacting with various code editors. It allows users to seamlessly open worktrees in their preferred development environment, offering both a default choice and the flexibility to select from all available options, all while providing clear visual cues through branded icons.

Highlights

  • Enhanced Editor Detection: The system now robustly detects installed IDEs and file managers across different operating systems (CLI commands and macOS app bundles), supporting a wide range of editors including Cursor, VS Code, Zed, Sublime Text, JetBrains IDEs, Xcode, Android Studio, and more.
  • Default IDE Setting: A new 'Default IDE' selector has been added to the Settings > Account section, allowing users to explicitly choose their preferred editor or rely on an auto-detection mechanism (prioritizing Cursor, then VS Code).
  • Multi-Editor Support in UI: The 'Open In' action in the Worktree Dropdown has been upgraded to a split-button UX. A direct click opens the worktree in the default IDE, while a chevron reveals a submenu listing all detected editors with their branded icons, along with a 'Copy Path' option.
  • Editor Icons: A new component editor-icons.tsx has been introduced, providing SVG icons for all supported editors, which are now displayed throughout the UI for better visual identification.
  • Backend API for Editors: A new server-side endpoint /api/worktree/available-editors has been implemented to expose the list of detected editors to the frontend.
  • Persistence of Default Editor: The user's chosen default editor is now persisted in GlobalSettings and synchronized across sessions.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive editor detection and selection system, which is a great enhancement for developer experience. The server-side detection is robust, covering both CLI commands and macOS app bundles. The UI changes, including the new editor icons, the 'Default IDE' setting, and the split-button for opening worktrees, are well-implemented and improve usability.

My review includes a few suggestions to improve performance and maintainability:

  • Parallelizing the editor detection on the server for a faster initial response.
  • Refactoring some duplicated logic in the UI components into a shared hook.
  • A minor simplification for rendering icons in React components.

Overall, this is a solid feature addition with well-thought-out details.

Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
apps/server/src/routes/worktree/routes/open-in-editor.ts (1)

178-203: Potential command injection vulnerability with worktreePath and editorCommand.

User-controlled input is interpolated directly into shell commands at Line 203: await execAsync(\${editor.command} "${worktreePath}"`). While worktreePathis quoted, a path containing"$(malicious)"or backticks could still execute arbitrary commands. TheeditorCommandis validated against detected editors (Line 196), which mitigates direct injection, butworktreePath` remains unvalidated.

Consider sanitizing the path or using spawn with argument arrays instead of string interpolation.

Proposed fix using spawn
-import { exec } from 'child_process';
+import { exec, spawn } from 'child_process';
 import { promisify } from 'util';

Then for the command execution:

-      await execAsync(`${editor.command} "${worktreePath}"`);
+      // Use spawn with arguments to avoid shell injection
+      const [cmd, ...args] = editor.command.split(' ');
+      await new Promise<void>((resolve, reject) => {
+        const child = spawn(cmd, [...args, worktreePath], { stdio: 'ignore', detached: true });
+        child.on('error', reject);
+        child.unref();
+        resolve();
+      });
🤖 Fix all issues with AI agents
In @apps/server/src/routes/worktree/routes/open-in-editor.ts:
- Around line 38-57: The check in findMacApp uses a literal '~' when
process.env.HOME is missing which doesn't expand in the shell; replace the
fallback with a real home directory (e.g., use os.homedir() or
require('os').homedir()) and build the app path with path.join (or string
concat) so execAsync(`test -d "${homeDir}/Applications/${appName}.app"`)
receives an absolute path; update the findMacApp function to import/use
os.homedir() (and optionally path.join) instead of the '~' fallback when calling
execAsync.

In @apps/ui/src/components/views/settings-view/account/account-section.tsx:
- Around line 23-42: The saved defaultEditorCommand may point to an editor not
present in editors, and getEffectiveDefaultEditor() uses editors[0] without
checking for emptiness; update getEffectiveDefaultEditor to first check if
editors.length === 0 and return null (or a safe fallback), and when
defaultEditorCommand is set but not found in editors normalize the bound Select
value to 'auto' so the UI stays consistent; specifically adjust
getEffectiveDefaultEditor, the logic that computes effectiveEditor, and where
the Select value is derived from defaultEditorCommand to map missing commands to
'auto'.

In @apps/ui/src/lib/electron.ts:
- Around line 1628-1638: The mock openInEditor currently maps only 'cursor' to
"Cursor" and defaults everything else to "VS Code", causing commands like 'open'
(Finder) to be reported incorrectly; update the openInEditor implementation to
map editorCommand values to their correct display names (e.g., 'cursor' ->
"Cursor", 'open' -> "Finder", 'code'|'vscode' -> "VS Code") using a small lookup
or switch, defaulting to a sensible fallback like the raw editorCommand or
"Unknown Editor", and then use that derived editorName in the returned message
and console.log.
🧹 Nitpick comments (4)
apps/ui/src/store/app-store.ts (1)

964-966: Consider aligning persistence behavior with other setters (sync vs no-sync).

setDefaultEditorCommand only updates Zustand state. If persistence is solely via useSettingsSync, that’s fine—but it’s inconsistent with many other setters here that call syncSettingsToServer(). Consider either (a) adding a brief comment noting it relies on useSettingsSync, or (b) mirroring the existing “sync + revert on failure” pattern if you want immediate durability.

Also applies to: 1970-1972

apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (1)

12-46: Avoid setState-after-unmount in async hook.
fetchAvailableEditors() can resolve after unmount and still call setEditors / setIsLoading.

One simple pattern
 export function useAvailableEditors() {
   const [editors, setEditors] = useState<EditorInfo[]>([]);
   const [isLoading, setIsLoading] = useState(true);
+  const [isMounted, setIsMounted] = useState(true);

   const fetchAvailableEditors = useCallback(async () => {
     try {
       const api = getElectronAPI();
       if (!api?.worktree?.getAvailableEditors) {
-        setIsLoading(false);
+        if (isMounted) setIsLoading(false);
         return;
       }
       const result = await api.worktree.getAvailableEditors();
-      if (result.success && result.result?.editors) {
+      if (isMounted && result.success && result.result?.editors) {
         setEditors(result.result.editors);
       }
     } catch (error) {
       logger.error('Failed to fetch available editors:', error);
     } finally {
-      setIsLoading(false);
+      if (isMounted) setIsLoading(false);
     }
-  }, []);
+  }, [isMounted]);

   useEffect(() => {
     fetchAvailableEditors();
+    return () => setIsMounted(false);
   }, [fetchAvailableEditors]);
apps/server/src/routes/worktree/routes/open-in-editor.ts (1)

140-155: Consider emitting events for editor detection operations.

As per coding guidelines, server operations should use createEventEmitter() from lib/events.ts to emit events that stream to the frontend via WebSocket. This endpoint could benefit from emitting events for editor detection progress, especially if the detection becomes slow.

apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)

86-109: Consider memoizing getEffectiveDefaultEditor to avoid recalculation on every render.

The function performs array searches on each render. While not a performance bottleneck with a small editor list, wrapping with useMemo would be cleaner and prevent unnecessary work when unrelated props change.

Proposed refactor
+import { useMemo } from 'react';

-  // Calculate effective default editor based on user setting or auto-detect (Cursor > VS Code > first)
-  const getEffectiveDefaultEditor = () => {
+  // Calculate effective default editor based on user setting or auto-detect (Cursor > VS Code > first)
+  const effectiveDefaultEditor = useMemo(() => {
     if (defaultEditorCommand) {
       const found = editors.find((e) => e.command === defaultEditorCommand);
       if (found) return found;
     }
     // Auto-detect: prefer Cursor, then VS Code, then first available
     const cursor = editors.find((e) => e.command === 'cursor');
     if (cursor) return cursor;
     const vscode = editors.find((e) => e.command === 'code');
     if (vscode) return vscode;
     return editors[0];
-  };
-
-  const effectiveDefaultEditor = getEffectiveDefaultEditor();
+  }, [editors, defaultEditorCommand]);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 299b838 and 32656a9.

📒 Files selected for processing (14)
  • apps/server/src/routes/worktree/index.ts
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/icons/editor-icons.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts
  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/store/app-store.ts
  • apps/ui/src/types/electron.d.ts
  • libs/types/src/settings.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts
  • libs/types/src/settings.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
  • apps/ui/src/store/app-store.ts
  • apps/server/src/routes/worktree/index.ts
  • apps/ui/src/types/electron.d.ts
  • apps/ui/src/components/icons/editor-icons.tsx
  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/lib/electron.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts
  • libs/types/src/settings.ts
  • apps/ui/src/lib/http-api-client.ts
  • apps/ui/src/hooks/use-settings-sync.ts
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
  • apps/ui/src/store/app-store.ts
  • apps/server/src/routes/worktree/index.ts
  • apps/ui/src/types/electron.d.ts
  • apps/ui/src/components/icons/editor-icons.tsx
  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/lib/electron.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/server/src/routes/worktree/index.ts
🧠 Learnings (1)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/ui/src/components/icons/editor-icons.tsx
  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
🧬 Code graph analysis (6)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx (3)
apps/ui/src/types/electron.d.ts (1)
  • WorktreeInfo (613-618)
libs/types/src/settings.ts (1)
  • WorktreeInfo (665-676)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
  • WorktreeInfo (9-18)
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts (4)
apps/ui/src/lib/electron.ts (2)
  • WorktreeInfo (191-191)
  • getElectronAPI (888-897)
apps/ui/src/types/electron.d.ts (1)
  • WorktreeInfo (613-618)
libs/types/src/settings.ts (1)
  • WorktreeInfo (665-676)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
  • WorktreeInfo (9-18)
apps/server/src/routes/worktree/routes/open-in-editor.ts (1)
apps/ui/src/lib/utils.ts (1)
  • isMac (121-126)
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (1)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (888-897)
apps/server/src/routes/worktree/index.ts (1)
apps/server/src/routes/worktree/routes/open-in-editor.ts (1)
  • createGetAvailableEditorsHandler (140-155)
apps/ui/src/components/views/settings-view/account/account-section.tsx (2)
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (1)
  • useAvailableEditors (12-46)
apps/ui/src/components/icons/editor-icons.tsx (1)
  • getEditorIcon (150-193)
🔇 Additional comments (20)
libs/types/src/settings.ts (2)

779-827: Good defaulting for new field.

Initializing DEFAULT_GLOBAL_SETTINGS.defaultEditorCommand to null matches the “auto-detect” behavior described in the PR summary and keeps the default state explicit.


568-571: No schema version bump needed. The SettingsService.getGlobalSettings() method uses a spread-merge pattern that automatically fills missing fields from DEFAULT_GLOBAL_SETTINGS. Old settings.json files without defaultEditorCommand will receive null on read, ensuring backward compatibility without explicit version-gated migration logic. This is the documented design pattern (see line 127-128: "Missing fields are filled in from DEFAULT_GLOBAL_SETTINGS for forward/backward compatibility") and is validated by the test suite (lines 62-74).

apps/ui/src/hooks/use-settings-sync.ts (2)

29-66: Sync coverage looks complete for defaultEditorCommand.

Adding defaultEditorCommand to SETTINGS_FIELDS_TO_SYNC ensures it participates in diffing + debounced updates.


374-422: Backward-compatible hydration for defaultEditorCommand.

defaultEditorCommand: serverSettings.defaultEditorCommand ?? null is the right fallback for older settings payloads.

apps/ui/src/store/app-store.ts (1)

588-590: State/default wiring for editor preference looks consistent.

defaultEditorCommand: null as the initial value aligns with the auto-detect behavior.

Also applies to: 1205-1206

apps/ui/src/lib/http-api-client.ts (1)

1557-1612: API additions are straightforward; verify shared typings and server validation.

openInEditor(..., editorCommand?) and getAvailableEditors() wiring looks good. Please verify WorktreeAPI / Electron types were updated accordingly and that the server validates/handles unsupported editorCommand values gracefully.

apps/server/src/routes/worktree/index.ts (1)

24-28: No action needed; auth protection and caching are already in place.

The /available-editors endpoint is protected by the global authMiddleware (applied to all /api/* routes in the main app setup), and the handler implements module-level caching of the editor detection results. The UI hook fetches editors once on component mount with no polling loop, so the cached response prevents repeated system calls.

apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx (1)

40-40: Prop type widening looks correct.
This matches the new “Open In” split-button flow where callers may pass a specific editor command.

apps/ui/src/lib/electron.ts (1)

1651-1663: Mock getAvailableEditors LGTM.
Minimal set is fine, and the { name, command } shape matches the UI hook.

apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktree-actions.ts (1)

128-145: WorktreeAPI signature is properly aligned end-to-end—no action needed.

The optional editorCommand parameter is consistently defined across:

  • WorktreeAPI interface (electron.d.ts): openInEditor(worktreePath: string, editorCommand?: string)
  • Mock implementation (electron.ts): matching signature
  • HTTP client (http-api-client.ts): matching signature and parameter pass-through

The code change safely passes editorCommand through all layers with no regression risk.

apps/ui/src/types/electron.d.ts (2)

875-885: LGTM!

The openInEditor signature update with the optional editorCommand parameter is well-typed and aligns with the server-side handler that accepts an optional editor command in the request body.


897-907: LGTM!

The new getAvailableEditors method is properly typed with a consistent response structure matching other API methods in this interface.

apps/server/src/routes/worktree/routes/open-in-editor.ts (3)

25-32: LGTM!

The commandExists helper properly abstracts the platform-specific command check (where for Windows, which for Unix-like systems).


62-82: LGTM!

The tryAddEditor helper cleanly encapsulates the priority logic (CLI first, then macOS app bundle) and handles the open -a command format correctly for apps discovered via bundle path.


84-123: LGTM!

The detectAllEditors function provides comprehensive editor detection with appropriate caching and platform-specific file manager fallbacks.

apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (2)

9-12: LGTM!

The new imports are well-organized: submenu components from the dropdown-menu UI library, Copy icon from lucide-react, and the necessary hooks/store for editor functionality.

Also applies to: 26-27, 31-33


234-277: The split-button submenu UI is well-implemented.

The pattern of having the main click area open the default editor while the chevron triggers a submenu with alternatives is intuitive. The conditional rendering based on effectiveDefaultEditor and dynamic icon rendering via getEditorIcon are clean.

One minor UX note: the "Copy Path" action at Line 269 doesn't provide visual feedback. Consider adding a toast notification.

apps/ui/src/components/icons/editor-icons.tsx (3)

1-6: LGTM!

Clean type definitions using React's built-in types. The IconComponent type alias provides good reusability for the icon mapping.


10-145: LGTM!

The SVG icon components are well-structured with consistent patterns:

  • All accept and spread IconProps for flexibility
  • Use fill="currentColor" for theme compatibility
  • Proper viewBox attributes for scalability

The icon paths appear to be sourced from reputable icon libraries (LobeHub, Simple Icons).


150-193: LGTM!

The getEditorIcon function provides comprehensive command-to-icon mapping with good fallback logic:

  1. Direct CLI command lookup
  2. Pattern matching for macOS open -a style commands
  3. Default FolderOpen fallback

The case-insensitive matching via toLowerCase() at Line 176 is appropriate for handling varied app name formats.

Addresses feedback from gemini-code-assist and coderabbitai reviewers:

## Duplicate Code (High Priority)
- Extract `getEffectiveDefaultEditor` logic into shared `useEffectiveDefaultEditor` hook
- Both account-section.tsx and worktree-actions-dropdown.tsx now use the shared hook

## Performance (Medium Priority)
- Refactor `detectAllEditors` to use `Promise.all` for parallel editor detection
- Replace sequential `await tryAddEditor()` calls with parallel `findEditor()` checks

## Code Quality (Medium Priority)
- Remove verbose IIFE pattern for editor icon rendering
- Pre-compute icon components before JSX return statement

## Bug Fixes
- Use `os.homedir()` instead of `~` fallback which doesn't expand in shell
- Normalize Select value to 'auto' when saved editor command not found in editors
- Add defensive check for empty editors array in useEffectiveDefaultEditor
- Improve mock openInEditor to correctly map all editor commands to display names

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
apps/server/src/routes/worktree/routes/open-in-editor.ts (1)

196-241: Potential command injection via worktreePath.

The worktreePath from user input is directly interpolated into shell commands without sanitization. A malicious path like "; rm -rf / # could execute arbitrary commands.

Consider validating that worktreePath is an absolute path and doesn't contain shell metacharacters, or use execFile with argument arrays instead of string interpolation.

Safer approach using execFile
+import { execFile } from 'child_process';
+import { promisify } from 'util';
+
+const execFileAsync = promisify(execFile);

 // In the handler:
-await execAsync(`${editor.command} "${worktreePath}"`);
+// For simple commands like 'code', 'cursor':
+await execFileAsync(editor.command, [worktreePath]);

For open -a "AppName" style commands, you'd need to parse and handle them separately:

if (editor.command.startsWith('open -a')) {
  const appPath = editor.command.replace('open -a ', '').replace(/"/g, '');
  await execFileAsync('open', ['-a', appPath, worktreePath]);
} else {
  await execFileAsync(editor.command, [worktreePath]);
}
🧹 Nitpick comments (2)
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (1)

8-11: Consider using a shared type definition.

The EditorInfo interface is also defined server-side in open-in-editor.ts. To prevent drift between client and server definitions, consider moving this to a shared types package (e.g., @automaker/types).

apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)

37-37: Remove unused defaultEditorName prop from the component interface.

The prop is declared but never used in the component. The refactor to use useEffectiveDefaultEditor hook replaced its functionality entirely.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32656a9 and ac87594.

📒 Files selected for processing (5)
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/lib/electron.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/lib/electron.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/settings-view/account/account-section.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
🧬 Code graph analysis (3)
apps/server/src/routes/worktree/routes/open-in-editor.ts (2)
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (1)
  • EditorInfo (8-11)
apps/ui/src/lib/utils.ts (1)
  • isMac (121-126)
apps/ui/src/components/views/settings-view/account/account-section.tsx (3)
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (2)
  • useAvailableEditors (13-47)
  • useEffectiveDefaultEditor (53-74)
apps/ui/src/store/app-store.ts (1)
  • useAppStore (1250-3143)
apps/ui/src/components/icons/editor-icons.tsx (1)
  • getEditorIcon (150-193)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (4)
apps/ui/src/types/electron.d.ts (1)
  • WorktreeInfo (613-618)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
  • WorktreeInfo (9-18)
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (2)
  • useAvailableEditors (13-47)
  • useEffectiveDefaultEditor (53-74)
apps/ui/src/components/icons/editor-icons.tsx (1)
  • getEditorIcon (150-193)
🔇 Additional comments (13)
apps/ui/src/lib/electron.ts (2)

1628-1655: LGTM!

The mock implementation correctly handles the optional editorCommand parameter with a proper display name mapping and sensible fallback to VS Code when no command is specified or the command is unrecognized.


1668-1679: LGTM!

The mock getAvailableEditors implementation provides a minimal but functional set of editors for development/testing purposes, matching the expected response structure from the real server endpoint.

apps/server/src/routes/worktree/routes/open-in-editor.ts (5)

26-33: LGTM!

Simple and effective command existence check using platform-appropriate commands (where for Windows, which for Unix).


39-58: LGTM!

Good approach checking both /Applications and ~/Applications (used by JetBrains Toolbox). Using homedir() from the os module is the correct way to handle home directory resolution.


64-84: LGTM!

Clean abstraction that prioritizes CLI commands over macOS app bundles. The open -a command with the full path handles apps installed in non-standard locations.


86-128: Parallel editor detection is well-designed.

Good use of Promise.all for performance and proper filtering of null results. The file manager fallback ensures there's always at least one option available.


145-160: LGTM!

Clean handler implementation for the new endpoint with proper error handling.

apps/ui/src/components/views/settings-view/account/account-section.tsx (2)

26-40: LGTM!

Clean state management using the shared useEffectiveDefaultEditor hook. The normalization of selectValue to 'auto' when the saved editor isn't found ensures a consistent UI state.


75-128: LGTM!

Well-structured IDE selector UI with:

  • Proper disabled states during loading
  • Dynamic icon rendering for each editor option
  • Clear auto-detect option as the first choice
  • Visual indication when auto-detect is active via the "(Auto)" suffix
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (2)

85-98: LGTM!

Good use of the shared hooks and computing values outside JSX. The otherEditors filter correctly excludes the effective default to avoid duplication in the submenu.


222-262: LGTM!

The split-button UX is well-implemented:

  • Main click area opens in the effective default editor
  • Chevron reveals submenu with other editors and Copy Path
  • Conditional separator prevents visual oddities when no other editors exist
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts (2)

13-47: LGTM!

Clean hook implementation with:

  • Proper loading state management
  • Defensive API availability check
  • Error logging without crashing
  • Useful convenience properties (hasMultipleEditors, defaultEditor)

53-74: LGTM!

Well-designed fallback logic with clear priority order: user preference → Cursor → VS Code → first available. The useMemo correctly optimizes recomputation based on dependencies.

## Security Fix (Command Injection)
- Use `execFile` with argument arrays instead of string interpolation
- Add `safeOpenInEditor` helper that properly handles `open -a` commands
- Validate that worktreePath is an absolute path before execution
- Prevents shell metacharacter injection attacks

## Shared Type Definition
- Move `EditorInfo` interface to `@automaker/types` package
- Server and UI now import from shared package to prevent drift
- Re-export from use-available-editors.ts for convenience

## Remove Unused Code
- Remove unused `defaultEditorName` prop from WorktreeActionsDropdown
- Remove prop from WorktreeTab component interface
- Remove useDefaultEditor hook usage from WorktreePanel
- Export new hooks from hooks/index.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

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

🤖 Fix all issues with AI agents
In @apps/server/src/routes/worktree/routes/open-in-editor.ts:
- Around line 23-30: The commandExists function uses execAsync with a
shell-interpolated string; replace this with a shell-safe execFile call (or a
promisified child_process.execFile) to avoid command injection. Update
commandExists to call execFile/execFileAsync with the executable name ('which'
on POSIX or 'where' on Windows) and pass the command to check as an argument
rather than interpolating it into a shell string; adjust imports to use the
promisified execFile utility and keep the try/catch logic and return values the
same.
- Around line 36-55: The findMacApp function currently shells out with
execAsync("test -d ...") which is unsafe and unnecessary; replace those
execAsync checks with Node fs methods: use path.join(homedir(), 'Applications',
`${appName}.app`) and path.join('/Applications', `${appName}.app`) and verify
existence with fs.promises.access or fs.promises.stat (catching errors) instead
of spawning a shell, keeping the same return values (the absolute app path or
null) and preserving the process.platform === 'darwin' guard.

In
@apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx:
- Around line 251-257: The onClick handler directly calls
navigator.clipboard.writeText(worktree.path) without error handling; change this
to an async handler (e.g., handleCopyPath) that awaits
navigator.clipboard.writeText(worktree.path) inside a try/catch, and on success
show a user-facing confirmation (toast/notification) and on failure show an
error notification and optionally console.error the caught error; update the
DropdownMenuItem onClick to call this new handler so clipboard failures are
handled gracefully.
🧹 Nitpick comments (2)
apps/server/src/routes/worktree/routes/open-in-editor.ts (2)

17-18: Consider adding cache invalidation or TTL.

The cached editor information never expires. If a user installs a new editor, they must restart the server to detect it. Consider adding a TTL or manual refresh endpoint.

Example with TTL
let cachedEditor: EditorInfo | null = null;
let cachedEditors: EditorInfo[] | null = null;
let cacheTimestamp: number = 0;
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes

function isCacheValid(): boolean {
  return Date.now() - cacheTimestamp < CACHE_TTL_MS;
}

async function detectAllEditors(): Promise<EditorInfo[]> {
  if (cachedEditors && isCacheValid()) {
    return cachedEditors;
  }
  
  // ... detection logic ...
  
  cachedEditors = editors;
  cacheTimestamp = Date.now();
  return editors;
}

142-268: Consider emitting events for open-in-editor operations.

Per coding guidelines, server operations should use createEventEmitter() from lib/events.ts to stream events to the frontend via WebSocket. This would enable real-time feedback for editor launch success/failure.

As per coding guidelines: "Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket"

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac87594 and 33dd9ae.

📒 Files selected for processing (8)
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/index.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
  • apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx
  • libs/types/src/editor.ts
  • libs/types/src/index.ts
💤 Files with no reviewable changes (1)
  • apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx
✅ Files skipped from review due to trivial changes (1)
  • libs/types/src/editor.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/use-available-editors.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • libs/types/src/index.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • libs/types/src/index.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/hooks/index.ts
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
🧠 Learnings (1)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx
🧬 Code graph analysis (1)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx (3)
apps/ui/src/types/electron.d.ts (1)
  • WorktreeInfo (613-618)
libs/types/src/settings.ts (1)
  • WorktreeInfo (665-676)
apps/ui/src/components/views/board-view/worktree-panel/types.ts (1)
  • WorktreeInfo (9-18)
🔇 Additional comments (6)
libs/types/src/index.ts (1)

207-208: LGTM!

The EditorInfo type export is correctly placed and follows the established pattern for type exports in this file.

apps/server/src/routes/worktree/routes/open-in-editor.ts (2)

181-190: LGTM! Excellent use of execFile for security.

The safeOpenInEditor function correctly uses execFile instead of exec to prevent command injection. The handling of both macOS open -a commands and direct CLI commands is well implemented.


208-215: LGTM! Essential security validation.

Validating that worktreePath is an absolute path prevents path traversal and other path-based attacks.

apps/ui/src/components/views/board-view/worktree-panel/hooks/index.ts (1)

6-6: LGTM!

The updated exports correctly expose the new editor-related hooks.

apps/ui/src/components/views/board-view/worktree-panel/components/worktree-tab.tsx (1)

39-39: LGTM!

The interface update correctly adds the optional editorCommand parameter, enabling per-invocation editor selection.

apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (1)

220-260: LGTM! Excellent split-button UX pattern.

The implementation of the split-button with submenu provides a clean UX: default editor on main click, other editors in the chevron submenu. The dynamic icon rendering and integration with useAvailableEditors is well structured.

@Shironex Shironex changed the base branch from main to v0.10.0rc January 11, 2026 15:47
@Shironex Shironex added Enhancement Improvements to existing functionality or UI. Testers-Requested Request for others to test an enhancement or bug fix/etc. Do Not Merge Use this label if something should not be merged. labels Jan 11, 2026
@Shironex
Copy link
Collaborator

@stefandevo Hey, thanks for creating the PR. The E2E might fail in our v0.10.0-rc branch, but that’s fine. If you could just address the latest CodeRabbit comments, that would be great.

Security improvements in open-in-editor.ts:
- Use execFile with argument arrays instead of shell interpolation
  in commandExists() to prevent command injection
- Replace shell `test -d` commands with Node.js fs/promises access()
  in findMacApp() for safer file system checks
- Add cache TTL (5 minutes) for editor detection to prevent stale data

UX improvements in worktree-actions-dropdown.tsx:
- Add error handling for clipboard copy operation
- Show success toast when path is copied
- Show error toast if clipboard write fails

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@stefandevo
Copy link
Collaborator Author

@coderabbitai can you review again with latest changes?

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

@stefandevo: I'll review the latest changes in this PR.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@stefandevo
Copy link
Collaborator Author

@coderabbitai can you review again with latest changes?

@Shironex it seems that Coderabbit will not re-review my latest changes.

@coderabbitai
Copy link

coderabbitai bot commented Jan 11, 2026

Tip

For best results, initiate chat on the files or code changes.

@stefandevo: I understand your concern! Let me clarify how CodeRabbit's review system works:

CodeRabbit uses an incremental review approach, which means:

  • When you request a review, I review only the new commits that haven't been reviewed yet
  • I don't automatically re-review commits that were already reviewed previously
  • This helps keep reviews focused on the actual changes made since the last review

When you used the "review again" command, I triggered a review of any new commits. If all commits in the PR have already been reviewed, the incremental review won't produce new comments.

If you'd like a complete re-review of all changes from scratch, I can do that using the full review command. This will:

  • Re-examine all files and changes in the PR
  • Generate fresh review comments
  • Potentially flag items that were previously reviewed

Would you like me to perform a full re-review of all changes in this PR? Just let me know, and I'll trigger it for you! 🐰

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
apps/server/src/routes/worktree/routes/open-in-editor.ts (1)

248-273: Inconsistent command execution and missing timeout.

Line 265 directly uses execFileAsync instead of safeOpenInEditor, creating inconsistency in how commands are executed. Additionally, no timeout is specified, which could cause the fallback to hang.

♻️ Proposed fix
         if (platform === 'darwin') {
           fallbackCommand = 'open';
           fallbackName = 'Finder';
         } else if (platform === 'win32') {
           fallbackCommand = 'explorer';
           fallbackName = 'Explorer';
         } else {
           fallbackCommand = 'xdg-open';
           fallbackName = 'File Manager';
         }

-        await execFileAsync(fallbackCommand, [worktreePath]);
+        await execFileAsync(fallbackCommand, [worktreePath], { timeout: 10000 });
         res.json({
           success: true,
           result: {
             message: `Opened ${worktreePath} in ${fallbackName}`,
             editorName: fallbackName,
           },
         });

Note: Consider using safeOpenInEditor for consistency, though in this case the fallback commands are simple and don't need special parsing.

🤖 Fix all issues with AI agents
In @apps/server/src/routes/worktree/routes/open-in-editor.ts:
- Around line 17-25: The cache uses a single cacheTimestamp for both
cachedEditor and cachedEditors causing stale/invalid reads; change cache
management so cachedEditors is the primary source and its freshness is validated
via isCacheValid() (or give each cached value its own timestamp), update
detectDefaultEditor() to not trust cachedEditor directly but instead call
detectAllEditors() or read from cachedEditors (refreshing it when isCacheValid()
is false) and then compute the default editor from that list; ensure functions
referenced—cachedEditor, cachedEditors, cacheTimestamp, isCacheValid(),
detectDefaultEditor(), detectAllEditors()—are updated so cachedEditor is derived
from cachedEditors (or removed) and TTL checks use the appropriate timestamp.
- Around line 228-237: When editorCommand is provided but detectAllEditors()
doesn't find a matching EditorInfo (the specifiedEditor is undefined), don't
silently fall back to detectDefaultEditor(); instead surface the issue by either
returning an error response or logging a warning with details. Update the
open-in-editor route logic that sets editor (where specifiedEditor is computed)
to: if specifiedEditor is undefined, log a clear warning (including the
requested editorCommand and the list of available editors from
detectAllEditors()) or respond with a 400/422 error informing the caller that
the requested editor is unavailable; only call detectDefaultEditor() as an
explicit fallback or after notifying the user.
🧹 Nitpick comments (5)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (2)

91-91: Consider memoizing derived editor list.

The otherEditors array is recomputed on every render. Since it only depends on editors and effectiveDefaultEditor, consider wrapping it in useMemo to avoid unnecessary recalculations.

♻️ Proposed optimization
-  // Get other editors (excluding the default) for the submenu
-  const otherEditors = editors.filter((e) => e.command !== effectiveDefaultEditor?.command);
+  // Get other editors (excluding the default) for the submenu
+  const otherEditors = React.useMemo(
+    () => editors.filter((e) => e.command !== effectiveDefaultEditor?.command),
+    [editors, effectiveDefaultEditor]
+  );

222-268: Consider UX when no editors are detected.

The entire "Open In" menu is hidden when effectiveDefaultEditor is null. While this is functionally correct (can't open in an editor if none are detected), consider whether users should see a disabled menu item or a message explaining why the option is unavailable.

This is a minor UX consideration—the current approach is acceptable if the expectation is that at least one editor should always be detected in typical development environments.

apps/server/src/routes/worktree/routes/open-in-editor.ts (3)

31-39: Add timeout to prevent indefinite hangs.

The execFile call has no timeout, which could cause the detection to hang if the system command stalls.

♻️ Proposed fix
 async function commandExists(cmd: string): Promise<boolean> {
   try {
     const whichCmd = process.platform === 'win32' ? 'where' : 'which';
-    await execFileAsync(whichCmd, [cmd]);
+    await execFileAsync(whichCmd, [cmd], { timeout: 5000 });
     return true;
   } catch {
     return false;
   }
 }

71-91: Consider a more structured approach for macOS app commands.

Line 86 constructs the command as a string with embedded quotes (open -a "${appPath}"), which is then parsed in safeOpenInEditor() using string replacement. This approach is brittle and relies on string manipulation that could fail with unexpected app paths.

♻️ Alternative: Store command components separately

Consider extending EditorInfo to support structured commands:

export interface EditorInfo {
  name: string;
  command: string;
  args?: string[];  // Optional arguments
}

Then update findEditor:

     if (appPath) {
-      // Use 'open -a' with full path for apps not in /Applications
-      return { name, command: `open -a "${appPath}"` };
+      return { name, command: 'open', args: ['-a', appPath] };
     }

And update safeOpenInEditor to use the args if present:

 async function safeOpenInEditor(command: string, targetPath: string): Promise<void> {
-  if (command.startsWith('open -a ')) {
-    const appPath = command.replace('open -a ', '').replace(/"/g, '');
-    await execFileAsync('open', ['-a', appPath, targetPath]);
-  } else {
-    await execFileAsync(command, [targetPath]);
-  }
+  if (editor.args) {
+    await execFileAsync(editor.command, [...editor.args, targetPath]);
+  } else {
+    await execFileAsync(editor.command, [targetPath]);
+  }
 }

Note: This would require updating the EditorInfo type in libs/types/src/editor.ts.


192-201: Add timeout to prevent indefinite hangs.

Both execFileAsync calls lack a timeout, which could cause the operation to hang indefinitely if the editor fails to launch.

♻️ Proposed fix
 async function safeOpenInEditor(command: string, targetPath: string): Promise<void> {
   // Handle 'open -a "AppPath"' style commands (macOS)
   if (command.startsWith('open -a ')) {
     const appPath = command.replace('open -a ', '').replace(/"/g, '');
-    await execFileAsync('open', ['-a', appPath, targetPath]);
+    await execFileAsync('open', ['-a', appPath, targetPath], { timeout: 10000 });
   } else {
     // Simple commands like 'code', 'cursor', 'zed', etc.
-    await execFileAsync(command, [targetPath]);
+    await execFileAsync(command, [targetPath], { timeout: 10000 });
   }
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33dd9ae and fb3a849.

📒 Files selected for processing (2)
  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
apps/server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use createEventEmitter() from lib/events.ts for all server operations to emit events that stream to frontend via WebSocket

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/server/src/routes/worktree/routes/open-in-editor.ts
  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx
🧬 Code graph analysis (1)
apps/server/src/routes/worktree/routes/open-in-editor.ts (2)
libs/types/src/editor.ts (1)
  • EditorInfo (8-13)
libs/types/src/index.ts (1)
  • EditorInfo (208-208)
🔇 Additional comments (8)
apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx (5)

49-49: LGTM: Backward-compatible API extension.

The optional editorCommand parameter allows specifying which editor to use while maintaining backward compatibility with existing callers.


253-260: LGTM: Solid clipboard implementation.

The async clipboard copy includes proper error handling and user feedback via toast notifications for both success and error cases.


224-235: LGTM: Clean split-button pattern.

The split-button implementation with main action area and chevron submenu trigger provides good UX. The flex layout and visual border separation between the main item and trigger are well-executed.


239-246: No changes needed. The code is correct as written. getEditorIcon() always returns a valid React component (with FolderOpen as the fallback), so the direct rendering of EditorIcon on line 246 is safe. The conditional rendering of DefaultEditorIcon on line 230 exists because effectiveDefaultEditor itself can be null, not because getEditorIcon() returns null.


30-30: The relative import at line 30 is correct and necessary.

The types WorktreeInfo, DevServerInfo, PRInfo, and GitRepoStatus are not available in @automaker/types. While a WorktreeInfo interface exists in the shared types package, it is structurally different (lacks isCurrent, hasWorktree, and pr properties required by this component). DevServerInfo, PRInfo, and GitRepoStatus exist only as local domain-specific types for the worktree panel. The relative import is appropriate for local, component-scoped types.

apps/server/src/routes/worktree/routes/open-in-editor.ts (3)

45-65: LGTM!

The function correctly uses filesystem methods to detect macOS apps, checking both system and user directories without shell commands.


93-136: LGTM!

The parallel editor detection (lines 102-118) and platform-specific file manager fallback are well-implemented.


153-168: LGTM!

The handler correctly exposes the editor detection functionality via API with proper error handling.

Cache management improvements:
- Remove separate cachedEditor variable; derive default from cachedEditors
- Update isCacheValid() to check cachedEditors existence
- detectDefaultEditor() now always goes through detectAllEditors()
  to ensure cache TTL is respected consistently

Editor fallback improvements:
- Log warning when requested editorCommand is not found in available editors
- Include list of available editor commands in warning message
- Make fallback to default editor explicit rather than silent

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@stefandevo
Copy link
Collaborator Author

stefandevo commented Jan 11, 2026

@Shironex

CI Test Failure Explanation

The failing test (libs/prompts/tests/enhancement.test.ts:402) is unrelated to the changes in this PR.

Root Cause

This PR targets v0.10.0rc as the base branch. The v0.10.0rc branch added a 5th enhancement mode (ux-reviewer) to the EnhancementMode type:

// libs/types/src/enhancement.ts in v0.10.0rc
export type EnhancementMode = 'improve' | 'technical' | 'simplify' | 'acceptance' | 'ux-reviewer';

However, the test was not updated to reflect this change:

  // libs/prompts/tests/enhancement.test.ts:402
  expect(modes.length).toBe(4);  // Expects 4, but now returns 5  Verification
  • The test passes on the main branch (which has 4 modes)
  • The test passes when run against upstream main
  • None of the files changed in this PR touch the prompts/enhancement code

Suggested Resolution

The test in v0.10.0rc should be updated to expect 5 modes and include 'ux-reviewer' in the valid modes check. This fix should be made in a separate PR targeting v0.10.0rc, as it's unrelated to the editor/IDE selection feature implemented here.

Shironex and others added 7 commits January 11, 2026 17:34
…ionality

- Add libs/platform/src/editor.ts with cross-platform editor detection and launching
  - Handles Windows .cmd batch scripts (cursor.cmd, code.cmd, etc.)
  - Supports macOS app bundles in /Applications and ~/Applications
  - Includes caching with 5-minute TTL for performance
- Refactor open-in-editor.ts to use @automaker/platform utilities
- Add POST /api/worktree/refresh-editors endpoint to clear cache
- Add refresh button to Settings > Account for IDE selection
- Update useAvailableEditors hook with refresh() and isRefreshing

Fixes Windows issue where "Open in Editor" was falling back to Explorer
because execFile cannot run .cmd scripts without shell:true.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The xed command requires full Xcode.app, not just Command Line Tools.
This fix adds validation to ensure Xcode is properly configured before
offering it as an editor option.

Changes:
- Added isXcodeFullyInstalled() to check xcode-select points to Xcode.app
- Added helpful warning when Xcode is installed but xcode-select points to CLT
- Users see clear instructions on how to fix the configuration

Fixes issue where xed would fail with "tool 'xed' requires Xcode" error
when only Command Line Tools are configured via xcode-select.
Added support for two new editors:
- VS Code Insiders (code-insiders command)
- Kiro (kido command) - VS Code fork

Changes:
- Added editor definitions to SUPPORTED_EDITORS list
- Added VSCodeInsidersIcon (reuses VS Code icon)
- Added KiroIcon with custom SVG logo
- Updated getEditorIcon() to handle both new commands
- Fixed logger initialization to be lazy-loaded, preventing circular
  dependency error with isBrowser variable during module initialization

Both editors were tested and successfully open directories on macOS.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace createLogger with console.warn to prevent circular import
between @automaker/platform and @automaker/utils.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Shironex Shironex merged commit 43fc3de into AutoMaker-Org:v0.10.0rc Jan 11, 2026
4 of 6 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Use this label if something should not be merged. Enhancement Improvements to existing functionality or UI. Testers-Requested Request for others to test an enhancement or bug fix/etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants