Skip to content

feat: add --worktree flag toggle for Claude Code#578

Draft
ashalliants wants to merge 3 commits into
siteboon:mainfrom
ashalliants:feat/worktree-toggle
Draft

feat: add --worktree flag toggle for Claude Code#578
ashalliants wants to merge 3 commits into
siteboon:mainfrom
ashalliants:feat/worktree-toggle

Conversation

@ashalliants

@ashalliants ashalliants commented Mar 24, 2026

Copy link
Copy Markdown

Summary

  • Adds a toggleable worktree isolation option for Claude Code sessions (--worktree flag and SDK settings.worktree)
  • Detects and groups git worktrees as related projects in the sidebar with branch badges, expandable repo headers, and stale worktree detection
  • Passes CLI flags (--worktree, --dangerously-skip-permissions) through to the shell/PTY provider, with safety guards to avoid creating duplicate worktrees on resume
  • Adds worktree cleanup on project delete with a confirmation warning
  • Adds a resizable sidebar, visible new-session button, and simpler loading state
  • Fixes a bug where the SDK crashes with "nested session" error when the server is started from within a Claude Code session or PM2 inherits the CLAUDECODE env var

Changes

Worktree Toggle (Settings & SDK):

  • Added useWorktree: boolean to ClaudePermissionsState and ClaudeSettings interfaces
  • Updated getClaudeSettings() defaults and parsing to include useWorktree
  • Maps useWorktree to SDK settings: { worktree: {} } in mapCliOptionsToSDK()
  • UI toggle in Claude permissions panel with GitBranch icon (blue info styling)

Sidebar Worktree Grouping:

  • Detects worktree paths (/.claude/worktrees/) and groups them under their parent repo
  • SidebarRepoGroup component with expandable headers, branch badges, and group actions
  • Stale worktree detection and visual hierarchy improvements
  • New sidebar i18n keys for all 6 locales

Shell/PTY CLI Flags:

  • Builds claude --worktree --dangerously-skip-permissions flag string from claudeSettings
  • Only passes --worktree for new sessions and when not already inside a worktree directory
  • Worktree cleanup via git worktree remove when deleting a worktree project, with confirmation dialog

Resizable Sidebar & UX:

  • Sidebar width is draggable and persisted
  • New-session button always visible
  • Simpler loading state

SDK Nested-Session Fix:

  • Clears CLAUDECODE env var in server/load-env.js at startup
  • Prevents the Claude Agent SDK from refusing to spawn CLI subprocesses when the server inherits this variable from a parent Claude Code session or PM2

i18n:

  • Added permissions.useWorktree.label and permissions.useWorktree.description keys for all 6 locales (en, de, ja, ko, ru, zh-CN)
  • Added sidebar worktree grouping keys for all locales

Testing:

  • Set up vitest as the testing framework
  • Tests for mapCliOptionsToSDK (worktree mapping + existing SDK option behavior)
  • Tests for getClaudeSettings (localStorage parsing including useWorktree defaults)
  • Tests for worktree grouping logic
  • i18n locale completeness tests

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Git worktree isolation option for Claude Code execution across all settings.
    • Redesigned sidebar with repository grouping, linked worktree support, and improved session aggregation.
    • Made desktop sidebar resizable with persistent width settings.
    • Added per-session worktree configuration in chat composer.
  • Improvements

    • Enhanced project discovery with automatic worktree detection and organization.
    • Improved branch switching with automatic UI updates.
    • Added worktree awareness to search and filtering.
  • Internationalization

    • Added localization support for worktree features across six languages (English, German, Japanese, Korean, Russian, Simplified Chinese).
  • Tests

    • Added comprehensive test coverage for worktree functionality and localization validation.

Test plan

  • Toggle worktree on, start a new session - verify it creates a worktree
  • Resume an existing worktree session - verify no duplicate worktree created
  • Delete a worktree project - verify cleanup dialog and git worktree remove
  • Sidebar groups worktree projects under parent repo correctly
  • Stale worktree indicator appears for orphaned worktrees
  • Resize sidebar, refresh - width persists
  • Start server via PM2 from inside a Claude Code session - SDK chat works (no nested-session crash)
  • npm test passes all vitest suites

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

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.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: aeb9fde5-2653-400b-af37-97e87fbef86e

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds git worktree isolation support with a major sidebar redesign. It introduces worktree detection and project grouping by repository, new UI components for displaying branches and worktrees, a useWorktree boolean setting across the Claude configuration, improved project discovery with worktree metadata, and comprehensive multi-language i18n updates.

Changes

Cohort / File(s) Summary
Settings & Types
src/components/chat/types/types.ts, src/components/settings/types/types.ts, src/components/shell/types/types.ts, src/types/app.ts, src/types/global.d.ts
Added useWorktree: boolean to ClaudeSettings and ClaudePermissionsState; extended ShellInitMessage with optional claudeSettings object; introduced new WorktreeInfo and RepoGroup types; added worktreeInfo, repoGroup, repoGroupSize, isMainWorktree, and isStale optional properties to Project; added updateProjectBranch optional function to Window interface.
Chat Settings & Storage
src/components/chat/utils/chatStorage.ts, src/components/settings/hooks/useSettingsController.ts, src/components/settings/view/tabs/agents-settings/sections/AgentCategoryContentSection.tsx, src/components/settings/view/tabs/agents-settings/sections/content/PermissionsContent.tsx
Updated getClaudeSettings() to include useWorktree field with default/fallback handling; extended settings persistence to track useWorktree; added UI checkbox and text input in permissions section for worktree isolation and name configuration.
Server Worktree & Project Discovery
server/projects.js, server/routes/git.js
Added worktree awareness during project discovery: getWorktreeInfo() helper detects linked worktrees; synthesizes worktree info from path convention; groups projects by mainRepoRoot; adds repoGroup, repoGroupSize, isMainWorktree, isStale annotations; normalizes display names via fixWorktreeDisplayNames; updates deletion to clean up git worktrees; exports worktree synthesis and cleanup helpers. Added getWorktreeInfo() export to validate checkout targets and reject linked worktrees.
SDK & CLI Mapping
server/claude-sdk.js
Extended mapCliOptionsToSDK to conditionally inject SDK settings.worktree when useWorktree is truthy, constructing { name: String(worktreeName) } when provided; exported mapCliOptionsToSDK function.
Server Index & Command Construction
server/index.js, server/load-env.js
Updated Claude command construction to derive claudeFlags from claudeSettings including --worktree with optional name for new sessions; injected flags into both new and resumed session commands (including PowerShell fallback); prevented CLAUDECODE environment variable from persisting via .env.
Shell & Session Initialization
src/components/shell/hooks/useShellConnection.ts
Extended WebSocket init message to include claudeSettings object with skipPermissions and useWorktree fields derived from getClaudeSettings().
Chat Composer & UI Wiring
src/components/chat/hooks/useChatComposerState.ts, src/components/chat/view/ChatInterface.tsx, src/components/chat/view/subcomponents/ChatMessagesPane.tsx, src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
Added per-session worktree state (useWorktreeForSession, worktreeName) to composer state; reset worktree controls when project changes; apply worktree settings only for new sessions; exposed worktree fields/setters in hook return and threaded through component hierarchy; added UI section in provider selection with checkbox and optional name input.
Sidebar Type & Utility System
src/components/sidebar/types/types.ts, src/components/sidebar/utils/utils.ts, src/components/sidebar/utils/repoAggregates.ts
Introduced RepoGroup type with discriminant __type: 'repo-group'; added groupProjectsByRepo() function to organize projects and groups; added isRepoGroup() type guard; added repo-level session aggregation (getRepoSessions, getRepoSessionTotal) and deterministic branch color mapping (branchChipColorIndex); extended search in filterProjects to match branch names.
Sidebar New Components
src/components/sidebar/view/subcomponents/BranchChip.tsx, src/components/sidebar/view/subcomponents/NewSessionRow.tsx, src/components/sidebar/view/subcomponents/RecentSessions.tsx, src/components/sidebar/view/subcomponents/RepoCard.tsx, src/components/sidebar/view/subcomponents/WorktreeRow.tsx
Added BranchChip component rendering branch with deterministic color and icon; added NewSessionRow button component; added RecentSessions collapsible section showing recent cross-repo sessions sorted by activity; added RepoCard collapsible repo section with main project, session rows, and worktree subsection; added WorktreeRow component for individual worktree display with activity label and actions.
Sidebar Removed Components
src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx, src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx, src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx
Deleted legacy SidebarProjectItem, SidebarProjectSessions, and SidebarSessionItem components; replaced with new repo-card-based rendering.
Sidebar Updated Components
src/components/sidebar/view/Sidebar.tsx, src/components/sidebar/view/subcomponents/SidebarContent.tsx, src/components/sidebar/view/subcomponents/SidebarProjectList.tsx, src/components/sidebar/view/subcomponents/SidebarModals.tsx, src/components/sidebar/view/subcomponents/SidebarProjectsState.tsx
Updated SidebarProjectList to use groupProjectsByRepo() and render RepoCard instead of SidebarProjectItem; simplified props by removing editing/session/star handlers; forwarded additionalSessions into RepoCard; added worktree deletion warning in confirmation modal; removed numeric progress display in loading UI; removed fixed width styling; threaded additionalSessions through component tree.
App Layout & Project State
src/components/app/AppContent.tsx, src/hooks/useProjectsState.ts, src/components/git-panel/hooks/useGitPanelController.ts
Added resizable sidebar width persistence via localStorage with mouse drag and double-click reset; added updateProjectBranch() callback to sync worktree branch name changes; updated branch switch error handling and added fetchBranches() call after successful checkout; wired updateProjectBranch to global window function.
Internationalization - Settings
src/i18n/locales/{en,de,ja,ko,ru,zh-CN}/settings.json
Added permissions.useWorktree translation entries with label and description across all supported locales describing git worktree isolation and --worktree flag equivalence.
Internationalization - Sidebar
src/i18n/locales/{en,de,ja,ko,ru,zh-CN}/sidebar.json
Added extensive translation keys for worktree/repo grouping UI: branch, repo groups, worktree sections, archived/stale/empty states, session counts (pluralized), pagination, and tooltips for new session in worktree and delete worktree actions; added deleteConfirmation.worktreeWarning key.
Internationalization - Chat
src/i18n/locales/en/chat.json
Added providerSelection.worktree localization group with checkbox label, description, and name input placeholder for session-level worktree options.
Testing - SDK & Storage
server/__tests__/claude-sdk.test.js, src/components/chat/utils/__tests__/chatStorage.test.ts
Added comprehensive Vitest test suite for mapCliOptionsToSDK function covering default behavior, model overrides, permission modes, and conditional worktree settings injection; added test suite for getClaudeSettings() covering default values, JSON parsing, useWorktree coercion, and fallback handling.
Testing - Worktree & Repo
server/__tests__/worktree-grouping.test.js, src/components/sidebar/utils/__tests__/repoAggregates.test.ts
Added test suite for synthesizeWorktreeInfoFromPath and fixWorktreeDisplayNames helpers; added test suite for getRepoSessions, getRepoSessionTotal, and branchChipColorIndex utilities validating session aggregation, total computation, and deterministic color mapping.
Testing - i18n Validation
src/i18n/__tests__/locales.test.ts
Added Vitest test suite validating locale file completeness and structure for all supported languages, ensuring required permission keys exist and contain valid label/description strings.
Build & Configuration
package.json, vitest.config.js
Added test and test:watch scripts via Vitest; added vitest ^4.1.1 to devDependencies; configured Vitest with global test APIs enabled.
Documentation
docs/superpowers/plans/2026-04-25-sidebar-worktree-redesign.md, docs/superpowers/specs/2026-04-25-sidebar-worktree-redesign-design.md
Added comprehensive implementation plan documenting new utilities, components, and migration steps for sidebar redesign; added design specification detailing RepoCard layout, RECENT/WORKTREES sections, branch chips, stale worktree styling, and acceptance criteria.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Chat/Settings UI
    participant ComposerState as useChatComposerState
    participant Shell as useShellConnection
    participant Server as server/index.js
    participant SDK as server/claude-sdk.js
    participant Git as git commands

    User->>UI: Enable worktree option & enter worktree name
    UI->>ComposerState: updateUseWorktreeForSession(), setWorktreeName()
    User->>UI: Submit new session
    ComposerState->>ComposerState: Build claudeToolsSettings with useWorktree flag
    ComposerState->>Shell: Send message with claudeSettings
    Shell->>Server: WebSocket init with claudeSettings {useWorktree, skipPermissions}
    Server->>SDK: mapCliOptionsToSDK(cliOptions, claudeSettings)
    SDK->>SDK: Build sdkOptions.settings.worktree = {name: worktreeName}
    Server->>Git: git worktree add || git checkout
    Git-->>Server: Success
    Server->>User: Session initialized in isolated git worktree
Loading
sequenceDiagram
    participant Server as server/projects.js
    participant Git as git commands
    participant FS as File System
    participant Sidebar as SidebarProjectList

    Server->>Git: Discover projects in workspace
    Server->>Git: getWorktreeInfo(projectPath)
    Git-->>Server: {isWorktree, mainRepoRoot, branchName, worktreeRoot}
    Server->>FS: fs.access() to check if stale
    Server->>Server: Group by mainRepoRoot
    Server->>Server: Synthesize parent "main" project if needed
    Server->>Server: Annotate with repoGroup, repoGroupSize, isMainWorktree
    Server-->>Sidebar: Projects with worktreeInfo & repo grouping
    Sidebar->>Sidebar: groupProjectsByRepo(projects)
    Sidebar->>Sidebar: Render RepoCard per group
    Sidebar->>Sidebar: Show branch chips & worktree rows
Loading

Possibly Related PRs

Suggested Reviewers

  • blackmammoth
  • viper151

Poem

🐰 Worktrees bloom, branches split and branch again,
Repos grouped in harmony, no more tangled den!
Sidebars dance with fresh cards and sessions bright,
The warren's redesigned—a beautiful sight! ✨🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main feature: adding a --worktree flag toggle for Claude Code. It directly reflects the central objective of this large changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/i18n/locales/ru/settings.json (1)

335-338: Localize the new RU strings to avoid mixed-language UI.

The key structure is correct, but this introduces English copy in the Russian locale and causes inconsistent UX in the settings panel.

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

In `@src/i18n/locales/ru/settings.json` around lines 335 - 338, The Russian locale
contains English text for the "useWorktree" setting; update the
"useWorktree.label" and "useWorktree.description" values in
src/i18n/locales/ru/settings.json to proper Russian translations (replace the
current English strings for the keys "useWorktree.label" and
"useWorktree.description") so the settings panel no longer mixes languages and
displays fully localized RU text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/i18n/locales/ko/settings.json`:
- Around line 356-359: The Korean locale currently contains English placeholders
for the new useWorktree keys; update the useWorktree object by translating the
"label" and "description" into Korean (i.e., replace the English strings for
useWorktree.label and useWorktree.description with appropriate Korean
translations) so the UI is fully localized.

---

Nitpick comments:
In `@src/i18n/locales/ru/settings.json`:
- Around line 335-338: The Russian locale contains English text for the
"useWorktree" setting; update the "useWorktree.label" and
"useWorktree.description" values in src/i18n/locales/ru/settings.json to proper
Russian translations (replace the current English strings for the keys
"useWorktree.label" and "useWorktree.description") so the settings panel no
longer mixes languages and displays fully localized RU text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 97ed8830-1a0f-448f-beb3-5dacd26d6b9e

📥 Commits

Reviewing files that changed from the base of the PR and between b54cdf8 and 162902f.

📒 Files selected for processing (13)
  • server/claude-sdk.js
  • src/components/chat/types/types.ts
  • src/components/chat/utils/chatStorage.ts
  • src/components/settings/hooks/useSettingsController.ts
  • src/components/settings/types/types.ts
  • src/components/settings/view/tabs/agents-settings/sections/AgentCategoryContentSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/content/PermissionsContent.tsx
  • src/i18n/locales/de/settings.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/ja/settings.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/ru/settings.json
  • src/i18n/locales/zh-CN/settings.json

Comment thread src/i18n/locales/ko/settings.json

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/components/chat/utils/__tests__/chatStorage.test.ts (1)

4-12: Consider making clear consistent with other mock methods.

The clear method is a plain function while getItem, setItem, and removeItem use vi.fn(). This doesn't affect test functionality but could be made consistent if you want to assert on clear calls in future tests.

🔧 Optional: Make clear a vi.fn() for consistency
 const localStorageMock = (() => {
   let store: Record<string, string> = {};
   return {
     getItem: vi.fn((key: string) => store[key] ?? null),
     setItem: vi.fn((key: string, value: string) => { store[key] = value; }),
     removeItem: vi.fn((key: string) => { delete store[key]; }),
-    clear: () => { store = {}; },
+    clear: vi.fn(() => { store = {}; }),
   };
 })();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/chat/utils/__tests__/chatStorage.test.ts` around lines 4 - 12,
The localStorageMock's clear method is a plain function while
getItem/setItem/removeItem use vi.fn(); make clear consistent by replacing its
implementation with a spy: change clear to vi.fn(() => { store = {}; }) while
keeping the same closure over store so tests can now assert calls on
localStorageMock.clear just like they do for localStorageMock.getItem, setItem,
and removeItem.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/claude-sdk.js`:
- Around line 174-177: The current code sets sdkOptions.worktree = {} which is
not a valid ClaudeAgentOptions field; instead, when settings.useWorktree is
true, remove the invalid assignment and configure the agent to use isolation and
the worktree tools/events: set the agent option isolation: "worktree" (e.g.,
when constructing the agent options object), and register/use the built-in tools
EnterWorktree and ExitWorktree plus hooks/events WorktreeCreate and
WorktreeRemove for any custom VCS setup, so replace references to
sdkOptions.worktree with these correct mechanisms (check where sdkOptions is
assembled and where the agent is created to add isolation:"worktree" and
hook/tool registration).

In `@src/i18n/__tests__/locales.test.ts`:
- Line 5: The test uses the LOCALES_DIR constant which relies on __dirname but
__dirname isn't defined in ES modules; add the standard ES-module definition at
the top of this test file by importing fileURLToPath from 'url' and dirname from
'path', then define const __dirname = dirname(fileURLToPath(import.meta.url));
so LOCALES_DIR = path.resolve(__dirname, '../locales') works as expected; place
these imports/definition before the LOCALES_DIR declaration to mirror the
pattern used in other files.

---

Nitpick comments:
In `@src/components/chat/utils/__tests__/chatStorage.test.ts`:
- Around line 4-12: The localStorageMock's clear method is a plain function
while getItem/setItem/removeItem use vi.fn(); make clear consistent by replacing
its implementation with a spy: change clear to vi.fn(() => { store = {}; })
while keeping the same closure over store so tests can now assert calls on
localStorageMock.clear just like they do for localStorageMock.getItem, setItem,
and removeItem.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 18456a1e-3581-4510-bbe4-f932fcf4040c

📥 Commits

Reviewing files that changed from the base of the PR and between 162902f and 3b8775a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • package.json
  • server/__tests__/claude-sdk.test.js
  • server/claude-sdk.js
  • src/components/chat/utils/__tests__/chatStorage.test.ts
  • src/i18n/__tests__/locales.test.ts
  • vitest.config.js

Comment thread server/claude-sdk.js Outdated
Comment thread src/i18n/__tests__/locales.test.ts
@ashalliants ashalliants force-pushed the feat/worktree-toggle branch from 08278ed to 8707776 Compare April 8, 2026 20:46
@ashalliants

Copy link
Copy Markdown
Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Reviews resumed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
src/i18n/__tests__/locales.test.ts (1)

14-19: Expand locale key checks to cover nested fields used by PermissionsContent.

Current assertions only validate object presence for skipPermissions, allowedTools, and blockedTools. The UI reads deeper keys (e.g., labels/descriptions/placeholders), so missing translations can still ship undetected.

Also applies to: 43-59

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

In `@src/i18n/__tests__/locales.test.ts` around lines 14 - 19, The test currently
only asserts presence of top-level keys in REQUIRED_PERMISSION_KEYS but must
also verify nested translation fields that PermissionsContent reads (e.g.,
labels, descriptions, placeholders, and any enum/item labels under
'allowedTools'/'blockedTools'); update the test in
src/i18n/__tests__/locales.test.ts to iterate each locale and for each key in
REQUIRED_PERMISSION_KEYS assert the presence and non-empty string values for the
deeper properties referenced by PermissionsContent (explicitly check label,
description, placeholder and any per-tool display names), and apply the same
deeper checks for the other assertions around lines where the current permission
keys are validated (the block covering the other assertions referenced in the
comment).
server/index.js (1)

1800-1801: Consider using a more robust worktree path detection.

The string check projectPath.includes('/.claude/worktrees/') works for the standard Claude worktree location but could miss edge cases on Windows (backslashes) or non-standard configurations.

🔧 Optional: Use path-based check for cross-platform support
-const isAlreadyInWorktree = projectPath.includes('/.claude/worktrees/');
+const isAlreadyInWorktree = projectPath.includes(`${path.sep}.claude${path.sep}worktrees${path.sep}`) ||
+                            projectPath.includes('/.claude/worktrees/');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 1800 - 1801, The current detection uses a raw
string include on projectPath (isAlreadyInWorktree) which fails on
Windows/backslashes or non-standard locations; update the check to normalize and
inspect path segments instead: require('path'), use path.normalize(projectPath)
or split path by path.sep and verify that the segment sequence
['.claude','worktrees'] appears, then derive isAlreadyInWorktree from that
normalized/segment-based test and keep shouldCreateWorktree as
claudeSettings.useWorktree && !hasSession && !isAlreadyInWorktree so the logic
uses cross-platform path checks (referencing projectPath, isAlreadyInWorktree,
shouldCreateWorktree, claudeSettings.useWorktree, hasSession).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/load-env.js`:
- Around line 11-16: The deletion of CLAUDECODE (delete process.env.CLAUDECODE)
is being undone by the .env loader; move the removal to after the environment is
loaded (or remove CLAUDECODE from the parsed .env object before merging) so the
variable cannot be reintroduced. Locate the delete process.env.CLAUDECODE
statement and ensure it runs after dotenv/config or whatever env-loading call is
used (also apply the same change for the other occurrence around the second
block at lines 23-25), or strip CLAUDECODE from the result of the env loader
before assigning to process.env.

In `@server/projects.js`:
- Around line 1271-1302: The cleanupWorktreeDirectory function currently calls
execFileAsync('git', ['worktree', 'remove', worktreePath, '--force'], ...) which
can discard uncommitted/untracked changes and swallows errors; update it to
first verify mainRepoRoot is a git repo (e.g. run execFileAsync('git',
['rev-parse', '--show-toplevel'], {cwd: mainRepoRoot}) and bail if that fails),
then attempt git worktree remove without '--force' and only fall back to
'--force' if the first remove fails and you log the specific error;
additionally, in both the outer catch (around execFileAsync removal) and the
inner catch (around fs.rm and prune), log detailed errors including worktreePath
and mainRepoRoot via console.error or processLogger (referencing
cleanupWorktreeDirectory, execFileAsync, fs.rm, and git worktree prune) instead
of silently ignoring them so failures are visible for debugging.
- Around line 698-705: When creating manual project entries, add the same
fallback worktree synthesis used for Claude-discovered projects whenever
getWorktreeInfo(actualProjectDir) returns null (i.e., after the try/catch that
sets project.worktreeInfo: if null, call the same synthesize/fallback routine
used for Claude-discovered projects to populate project.worktreeInfo so deleted
worktrees still group with their parent). Also initialize project.isStale on the
manual project object (set project.isStale = false or the same initial value
used for Claude-discovered projects) before pushing the project to projects to
ensure consistent behavior with Claude-discovered entries.

In `@src/i18n/__tests__/locales.test.ts`:
- Around line 24-29: The current loop that reads and JSON.parses locale files at
module load time (using SUPPORTED_LOCALES, LOCALES_DIR, and populating
localeData) causes the test suite to crash before assertions if a settings.json
is missing/invalid; move the file I/O into test runtime (e.g., within each test
case or a beforeEach) and wrap reads/parses in existence checks and try/catch so
the tests can assert presence/validity (for example, check
fs.existsSync(filePath) and then JSON.parse inside a try block) instead of
parsing during suite definition.
- Around line 45-47: The test dereferences nested properties directly
(localeData[locale].permissions and permissions[key]) which can throw TypeError
if a parent key is missing; before asserting about permissions[key] in the test
(variables: localeData, locale, permissions, key), first assert that
localeData[locale] is defined and that permissions (e.g., const permissions =
(localeData[locale] as ...).permissions) is defined and is an object, then
perform the existing assertions about permissions[key]; apply the same guarding
pattern to the similar block referenced around lines 52-58 so the tests fail
with clear assertion messages rather than throwing.

---

Nitpick comments:
In `@server/index.js`:
- Around line 1800-1801: The current detection uses a raw string include on
projectPath (isAlreadyInWorktree) which fails on Windows/backslashes or
non-standard locations; update the check to normalize and inspect path segments
instead: require('path'), use path.normalize(projectPath) or split path by
path.sep and verify that the segment sequence ['.claude','worktrees'] appears,
then derive isAlreadyInWorktree from that normalized/segment-based test and keep
shouldCreateWorktree as claudeSettings.useWorktree && !hasSession &&
!isAlreadyInWorktree so the logic uses cross-platform path checks (referencing
projectPath, isAlreadyInWorktree, shouldCreateWorktree,
claudeSettings.useWorktree, hasSession).

In `@src/i18n/__tests__/locales.test.ts`:
- Around line 14-19: The test currently only asserts presence of top-level keys
in REQUIRED_PERMISSION_KEYS but must also verify nested translation fields that
PermissionsContent reads (e.g., labels, descriptions, placeholders, and any
enum/item labels under 'allowedTools'/'blockedTools'); update the test in
src/i18n/__tests__/locales.test.ts to iterate each locale and for each key in
REQUIRED_PERMISSION_KEYS assert the presence and non-empty string values for the
deeper properties referenced by PermissionsContent (explicitly check label,
description, placeholder and any per-tool display names), and apply the same
deeper checks for the other assertions around lines where the current permission
keys are validated (the block covering the other assertions referenced in the
comment).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2ffc08c0-1c67-4361-bfdd-a11361cc96fd

📥 Commits

Reviewing files that changed from the base of the PR and between 6ade4cf and 8707776.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (44)
  • package.json
  • server/__tests__/claude-sdk.test.js
  • server/__tests__/worktree-grouping.test.js
  • server/claude-sdk.js
  • server/index.js
  • server/load-env.js
  • server/projects.js
  • server/routes/git.js
  • src/components/app/AppContent.tsx
  • src/components/chat/types/types.ts
  • src/components/chat/utils/__tests__/chatStorage.test.ts
  • src/components/chat/utils/chatStorage.ts
  • src/components/git-panel/hooks/useGitPanelController.ts
  • src/components/settings/hooks/useSettingsController.ts
  • src/components/settings/types/types.ts
  • src/components/settings/view/tabs/agents-settings/sections/AgentCategoryContentSection.tsx
  • src/components/settings/view/tabs/agents-settings/sections/content/PermissionsContent.tsx
  • src/components/shell/hooks/useShellConnection.ts
  • src/components/shell/types/types.ts
  • src/components/sidebar/types/types.ts
  • src/components/sidebar/utils/utils.ts
  • src/components/sidebar/view/subcomponents/SidebarContent.tsx
  • src/components/sidebar/view/subcomponents/SidebarModals.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectList.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectsState.tsx
  • src/components/sidebar/view/subcomponents/SidebarRepoGroup.tsx
  • src/hooks/useProjectsState.ts
  • src/i18n/__tests__/locales.test.ts
  • src/i18n/locales/de/settings.json
  • src/i18n/locales/de/sidebar.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/en/sidebar.json
  • src/i18n/locales/ja/settings.json
  • src/i18n/locales/ja/sidebar.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/ko/sidebar.json
  • src/i18n/locales/ru/settings.json
  • src/i18n/locales/ru/sidebar.json
  • src/i18n/locales/zh-CN/settings.json
  • src/i18n/locales/zh-CN/sidebar.json
  • src/types/app.ts
  • src/types/global.d.ts
  • vitest.config.js
✅ Files skipped from review due to trivial changes (18)
  • src/types/global.d.ts
  • package.json
  • src/i18n/locales/de/settings.json
  • src/i18n/locales/en/settings.json
  • vitest.config.js
  • src/components/shell/types/types.ts
  • src/i18n/locales/de/sidebar.json
  • src/components/settings/hooks/useSettingsController.ts
  • src/i18n/locales/zh-CN/settings.json
  • src/components/sidebar/types/types.ts
  • src/i18n/locales/en/sidebar.json
  • src/i18n/locales/ru/settings.json
  • src/i18n/locales/ru/sidebar.json
  • src/i18n/locales/ja/sidebar.json
  • src/i18n/locales/ko/settings.json
  • src/components/settings/view/tabs/agents-settings/sections/content/PermissionsContent.tsx
  • src/i18n/locales/ja/settings.json
  • src/components/chat/utils/chatStorage.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/components/chat/types/types.ts
  • src/components/settings/types/types.ts
  • server/claude-sdk.js
  • src/components/sidebar/view/subcomponents/SidebarProjectList.tsx
  • src/i18n/locales/zh-CN/sidebar.json
  • src/types/app.ts
  • src/components/chat/utils/tests/chatStorage.test.ts
  • src/i18n/locales/ko/sidebar.json
  • server/tests/claude-sdk.test.js

Comment thread server/load-env.js
Comment thread server/projects.js Outdated
Comment thread server/projects.js Outdated
Comment thread src/i18n/__tests__/locales.test.ts Outdated
Comment thread src/i18n/__tests__/locales.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
server/projects.js (1)

1297-1308: ⚠️ Potential issue | 🟠 Major

Avoid force-removing the worktree on the first delete attempt.

git worktree remove refuses dirty worktrees unless --force is used. Calling it with --force immediately here, then falling back to fs.rm(), means deleting a project can discard uncommitted or untracked work without a second destructive opt-in. Please try a plain remove first and only escalate to a forced delete when the caller explicitly confirms that data loss is acceptable. (git-scm.com)

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

In `@server/projects.js` around lines 1297 - 1308, The current removal always
calls execFileAsync('git', ['worktree', 'remove', worktreePath, '--force'], ...)
which can discard local changes; change the logic in the block that uses
execFileAsync, fs.rm, worktreePath and mainRepoRoot so it first attempts
execFileAsync('git', ['worktree', 'remove', worktreePath'], ...) without
--force, and only if that call fails with the specific “worktree dirty”/refusal
error (or an explicit force flag passed into the surrounding function, e.g., a
forceDelete parameter) retry with --force; preserve the existing fallback to
fs.rm only when forced or when git is unavailable, and update the
console.warn/process logs to clearly indicate when a forced destructive delete
was used versus a safe non-forced removal.
🧹 Nitpick comments (1)
src/i18n/__tests__/locales.test.ts (1)

21-45: Consider using beforeAll for cleaner test isolation.

The current approach populates localeData[locale] as a side effect within the second test (line 35). Subsequent tests depend on this test having run and succeeded. While the guards (expect(root).toBeDefined()) ensure clear failures, using a beforeAll hook would make tests more independent and allow selective test execution (e.g., it.only) without breaking dependent tests.

♻️ Optional refactor for test isolation
   for (const locale of SUPPORTED_LOCALES) {
     describe(`locale: ${locale}`, () => {
       const filePath = path.join(LOCALES_DIR, locale, 'settings.json');
+      let localeContent: Record<string, unknown> | undefined;
+
+      beforeAll(() => {
+        if (fs.existsSync(filePath)) {
+          try {
+            const raw = fs.readFileSync(filePath, 'utf-8');
+            localeContent = JSON.parse(raw);
+            localeData[locale] = localeContent;
+          } catch {
+            // Let tests assert the failure
+          }
+        }
+      });
 
       it('has a settings.json file', () => {
         expect(fs.existsSync(filePath)).toBe(true);
       });
 
       it('has valid settings.json content', () => {
-        expect(() => {
-          const raw = fs.readFileSync(filePath, 'utf-8');
-          localeData[locale] = JSON.parse(raw);
-        }).not.toThrow();
+        expect(localeContent).toBeDefined();
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/i18n/__tests__/locales.test.ts` around lines 21 - 45, The tests populate
localeData[locale] as a side-effect inside the `'has valid settings.json
content'` it block, causing later tests (like the permissions check) to depend
on that test having run; move the file read + JSON.parse for each locale into a
beforeAll hook inside the describe(`locale: ${locale}`) block so
localeData[locale] is populated before any it tests run (use the same filePath
and JSON parsing logic currently in the validity test), keep the existing expect
guards in the it blocks but read/parse in beforeAll to allow running individual
it tests (e.g., it.only) without breaking them, and retain references to
SUPPORTED_LOCALES, LOCALES_DIR, and settings.json when locating the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/projects.js`:
- Around line 394-407: The path parser is Windows-unsafe because it only looks
for '/.claude/worktrees/'; create a small helper (e.g., normalizePathForParsing)
that returns the input path with backslashes converted to forward slashes, use
that in synthesizeWorktreeInfoFromPath to find the marker '/.claude/worktrees/'
and compute markerIdx, afterMarker and worktreeName from the normalized path,
then map results back to the original string when building
worktreeRoot/mainRepoRoot/branchName if you need original separators; also
replace the same logic in the other helper that uses the same marker (the
cleanupWorktreeDirectory-related function around the other occurrence) so both
are separator-agnostic.
- Around line 417-430: The function fixWorktreeDisplayNames is incorrectly
treating an arbitrary grouped[0] (mainProject) that may itself be a worktree as
the repo's real main checkout, causing siblings with displayName === branchName
to be rewritten to that child's name; change the logic to detect whether
mainProject is actually a worktree (e.g. const mainIsWorktree =
!!mainProject?.worktreeInfo) and only perform the
branch-name-to-mainProject.displayName rewrite when mainIsWorktree is false
(i.e., when you have a real main checkout). Apply the same guard to the
equivalent branch-name rewrite at the other occurrence referenced in the
comment.

---

Duplicate comments:
In `@server/projects.js`:
- Around line 1297-1308: The current removal always calls execFileAsync('git',
['worktree', 'remove', worktreePath, '--force'], ...) which can discard local
changes; change the logic in the block that uses execFileAsync, fs.rm,
worktreePath and mainRepoRoot so it first attempts execFileAsync('git',
['worktree', 'remove', worktreePath'], ...) without --force, and only if that
call fails with the specific “worktree dirty”/refusal error (or an explicit
force flag passed into the surrounding function, e.g., a forceDelete parameter)
retry with --force; preserve the existing fallback to fs.rm only when forced or
when git is unavailable, and update the console.warn/process logs to clearly
indicate when a forced destructive delete was used versus a safe non-forced
removal.

---

Nitpick comments:
In `@src/i18n/__tests__/locales.test.ts`:
- Around line 21-45: The tests populate localeData[locale] as a side-effect
inside the `'has valid settings.json content'` it block, causing later tests
(like the permissions check) to depend on that test having run; move the file
read + JSON.parse for each locale into a beforeAll hook inside the
describe(`locale: ${locale}`) block so localeData[locale] is populated before
any it tests run (use the same filePath and JSON parsing logic currently in the
validity test), keep the existing expect guards in the it blocks but read/parse
in beforeAll to allow running individual it tests (e.g., it.only) without
breaking them, and retain references to SUPPORTED_LOCALES, LOCALES_DIR, and
settings.json when locating the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3a1671e0-06b7-4f12-8197-8695fe264a3e

📥 Commits

Reviewing files that changed from the base of the PR and between 8707776 and 4984040.

📒 Files selected for processing (3)
  • server/load-env.js
  • server/projects.js
  • src/i18n/__tests__/locales.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/load-env.js

Comment thread server/projects.js Outdated
Comment thread server/projects.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@server/projects.js`:
- Around line 731-739: Normalize the repo-group key before bucketing: compute a
normalizedRepoRoot from project.worktreeInfo?.mainRepoRoot (use Node's
path.normalize to unify separators and, on Windows, lowercase the path for
case-insensitive matching) and use that normalizedRepoRoot when checking/setting
repoGroups instead of the raw mainRepoRoot; this ensures outputs of
getWorktreeInfo() and synthesizeWorktreeInfoFromPath() map to the same bucket.
Reference repoGroups and project.worktreeInfo?.mainRepoRoot when implementing
the change and add a path import if not present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 012c3687-c77c-4cf8-83c8-b716a9a801a1

📥 Commits

Reviewing files that changed from the base of the PR and between 4984040 and 4c7fe74.

📒 Files selected for processing (1)
  • server/projects.js

Comment thread server/projects.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (7)
server/projects.js (1)

299-310: Use path.join for the synthesized worktree path.

${repoRoot}/.claude/worktrees/${worktreeName} hardcodes forward slashes. On Windows, repoRoot (resolved from the parent project) typically contains backslashes, so the constructed path mixes separators. Node FS tolerates this, but path.join is more idiomatic and consistent with the rest of the file (e.g., line 312, 79).

♻️ Proposed fix
-      const repoRoot = await extractProjectDirectory(parentProjectName);
-      const candidatePath = `${repoRoot}/.claude/worktrees/${worktreeName}`;
+      const repoRoot = await extractProjectDirectory(parentProjectName);
+      const candidatePath = path.join(repoRoot, '.claude', 'worktrees', worktreeName);
       projectDirectoryCache.set(projectName, candidatePath);
       return candidatePath;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/projects.js` around lines 299 - 310, The synthesized worktree path
currently concatenates strings with forward slashes; update the construction to
use path.join to produce a platform-safe path. In the block handling
worktreeMarker (variables: worktreeMarker, markerIdx, parentProjectName,
worktreeName) where repoRoot is obtained from
extractProjectDirectory(parentProjectName), replace the literal
`${repoRoot}/.claude/worktrees/${worktreeName}` with a path.join(repoRoot,
'.claude', 'worktrees', worktreeName) result before setting
projectDirectoryCache.set(projectName, ...) and returning it so paths are
correct on Windows and consistent with other uses in this file.
docs/superpowers/specs/2026-04-25-sidebar-worktree-redesign-design.md (1)

29-29: Add a language tag to the fenced code blocks (markdownlint MD040).

Both ASCII-art blocks are tagged as plain fences. Adding text (or plaintext) silences the linter and is a common convention for non-language content.

📝 Proposed fix
-```
+```text
 ┌──────────────────────────────────────────────┐
-```
+```text
 SidebarProjectList.tsx

Also applies to: 123-123

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

In `@docs/superpowers/specs/2026-04-25-sidebar-worktree-redesign-design.md` at
line 29, The fenced code blocks containing ASCII-art and the
"SidebarProjectList.tsx" snippet are currently untagged and trigger markdownlint
MD040; update those fence openings to include a plaintext tag (e.g., ```text or
```plaintext) for the two ASCII-art blocks shown and any other similar plain
fences (the other occurrence noted at the document). Locate the untagged
triple-backtick blocks in the sidebar-worktree-redesign-design.md file and add
the language tag to each opening fence so the linter recognizes them as
non-code/plaintext.
src/components/sidebar/view/subcomponents/BranchChip.tsx (1)

5-30: Tie palette length to the hash modulus to avoid silent drift.

branchChipColorIndex uses PALETTE_SIZE = 5 in repoAggregates.ts, and this file independently defines a 5-entry PALETTE. If one is changed, the chip will silently fall back to NEUTRAL for some branches. Either export the palette length from repoAggregates (and use it there) or assert the relationship at module load.

Optional refactor
-import { branchChipColorIndex } from '../../utils/repoAggregates';
+import { branchChipColorIndex } from '../../utils/repoAggregates';
+
+// Keep in sync with PALETTE_SIZE in repoAggregates.ts
 const PALETTE = [
   ...
 ];
+if (process.env.NODE_ENV !== 'production') {
+  // Sanity: hash modulus and palette length must match.
+  // (Or export PALETTE_SIZE and derive both from it.)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/sidebar/view/subcomponents/BranchChip.tsx` around lines 5 -
30, PALETTE in BranchChip.tsx is independently sized while branchChipColorIndex
in repoAggregates.ts uses a hardcoded PALETTE_SIZE=5, which can cause silent
fallback to NEUTRAL if they diverge; fix by either importing an exported palette
size (e.g., export PALETTE_SIZE or getPaletteLength) from repoAggregates and use
that to derive the palette or by adding a runtime assertion at module load in
BranchChip.tsx that checks PALETTE.length === branchChipColorIndex's modulus
constant (referencing PALETTE and branchChipColorIndex) and throw/log a clear
error if they differ so the relationship cannot silently drift.
src/components/sidebar/view/subcomponents/RepoCard.tsx (2)

158-177: Performance: per-worktree getRepoSessions/getRepoSessionTotal runs on every render.

For each WorktreeRow, both getRepoSessionTotal([wt]) (Line 163) and lastActivityLabelFor(wt, additionalSessions, currentTime, t) (Line 164) are computed inline on every render, and lastActivityLabelFor itself calls getRepoSessions([project], additionalSessions) which clones, annotates, and sorts that worktree's sessions. With many worktrees and a currentTime ticking each second (typical for relative-time labels), this is wasted work on the hot path.

Consider a single memo that produces Map<name, { sessionCount, lastActivityLabel }> keyed off [projects, additionalSessions, currentTime]:

Suggested refactor
+  const worktreeStats = useMemo(() => {
+    const map = new Map<string, { sessionCount: number; lastActivityLabel: string | null }>();
+    for (const wt of linkedWorktrees) {
+      const sessions = getRepoSessions([wt], additionalSessions);
+      map.set(wt.name, {
+        sessionCount: getRepoSessionTotal([wt]),
+        lastActivityLabel: sessions.length === 0
+          ? null
+          : labelForDate(new Date(sessions[0].lastActivity || sessions[0].createdAt || 0), currentTime, t),
+      });
+    }
+    return map;
+  }, [linkedWorktrees, additionalSessions, currentTime, t]);

…and consume worktreeStats.get(wt.name) in both the active and archived WorktreeRow mappings.

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

In `@src/components/sidebar/view/subcomponents/RepoCard.tsx` around lines 158 -
177, Rendering calls compute getRepoSessionTotal and lastActivityLabelFor (which
itself calls getRepoSessions) for every WorktreeRow on each render, causing
wasted work when currentTime updates; fix by computing a memoized Map keyed by
[activeWorktrees, additionalSessions, currentTime] (e.g., worktreeStats:
Map<name, { sessionCount, lastActivityLabel }>) using useMemo, populate it by
iterating activeWorktrees and calling
getRepoSessions/getRepoSessionTotal/lastActivityLabelFor once per worktree, then
replace inline calls in the WorktreeRow props (sessionCount, lastActivityLabel)
with lookups like worktreeStats.get(wt.name) so re-renders reuse the precomputed
values.

112-122: Conditional defaultValue for plurals is ineffective.

Same pattern as in WorktreeRow.tsx: t('projects.worktrees', { defaultValue: linkedWorktrees.length === 1 ? 'worktree' : 'worktrees', count: linkedWorktrees.length })defaultValue is only used when the key is missing; with count, i18next selects worktrees_one / worktrees_other from the bundle. Either define plural keys (worktrees_one/worktrees_other) and drop the conditional, or use distinct t() calls. The same applies to projects.sessionsShort on Line 112.

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

In `@src/components/sidebar/view/subcomponents/RepoCard.tsx` around lines 112 -
122, The current conditional defaultValue on t() is ineffective for plurals;
update the code to use i18next pluralization properly by adding plural keys
(projects.worktrees_one / projects.worktrees_other and
projects.sessionsShort_one / projects.sessionsShort_other) in your locale bundle
and then call t('projects.worktrees', { count: linkedWorktrees.length }) and
t('projects.sessionsShort', { count: sessionTotal }) in RepoCard (same pattern
applies in WorktreeRow.tsx) so you can remove the linkedWorktrees.length === 1
conditional and let i18next pick the correct plural form.
src/components/sidebar/view/subcomponents/RecentSessions.tsx (1)

122-134: formatRelative returns "0m ago" for the 0–59 s window when lastActivity is slightly in the past but rounds down.

Line 128 already short-circuits minutes < 1 to "just now", so this is fine for the common path. But note the "1 minute ago" case will render as "1m ago" rather than the singular time.oneMinuteAgo key that exists in the locale files (e.g., de/sidebar.json has "oneMinuteAgo": "vor 1 Min."). Consider routing 1m and 1h through the singular keys for higher-quality localized output.

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

In `@src/components/sidebar/view/subcomponents/RecentSessions.tsx` around lines
122 - 134, formatRelative currently returns pluralized minute/hour translations
(e.g., "1m ago") because it only short-circuits for <1 minute; update
formatRelative to detect the singular cases and call the singular i18n keys:
when minutes === 1 use t('time.oneMinuteAgo', { defaultValue: '1m ago' }) and
when hours === 1 use t('time.oneHourAgo', { defaultValue: '1h ago' }), keeping
the existing branches for <1 minute, minutes <60, hours <24 and days; apply
these checks using the same computed values (date from session.lastActivity ||
session.createdAt, minutes, hours) so localization uses the dedicated singular
keys.
src/components/sidebar/view/subcomponents/WorktreeRow.tsx (1)

32-39: Conditional defaultValue in i18next is ignored when the translation key exists.

The bundles define sessionsShort_one and sessionsShort_other across all locales, so i18next uses count to select the correct plural form from the bundle rather than from the conditional defaultValue. The ternary expression doesn't control pluralization and only adds confusion.

Suggested fix
-      : `${sessionCount} ${t('projects.sessionsShort', {
-          defaultValue: sessionCount === 1 ? 'session' : 'sessions',
-          count: sessionCount,
-        })}${lastActivityLabel ? ` · ${lastActivityLabel}` : ''}`;
+      : `${sessionCount} ${t('projects.sessionsShort', {
+          defaultValue: 'sessions',
+          count: sessionCount,
+        })}${lastActivityLabel ? ` · ${lastActivityLabel}` : ''}`;

This aligns with how the same pattern is already implemented in RepoCard.tsx.

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

In `@src/components/sidebar/view/subcomponents/WorktreeRow.tsx` around lines 32 -
39, The metaText uses a ternary defaultValue to pick singular/plural for
t('projects.sessionsShort') but i18next will ignore that when plural keys exist;
replace the ternary + conditional defaultValue with the same pattern used in
RepoCard.tsx: always call t('projects.sessionsShort', { count: sessionCount,
defaultValue: sessionCount === 1 ? 'session' : 'sessions' }) (keeping the
`${sessionCount} ${...}${lastActivityLabel ? \` · ${lastActivityLabel}\` : ''}`
composition) so pluralization is driven by the count param and the code is
simplified; update the metaText expression in WorktreeRow.tsx accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/projects.js`:
- Around line 800-805: The synthetic worktreeInfo sets branchName to null which
violates the WorktreeInfo.branchName: string contract; fix by changing the
object literal to use branchName: '' (empty string) instead of null in the
worktreeInfo construction (the object containing isWorktree, worktreeRoot,
mainRepoRoot, branchName), or alternatively update the WorktreeInfo type
declaration to accept string | null (i.e., change the branchName type to allow
null) so the runtime value matches the declared type.

In `@src/components/sidebar/utils/repoAggregates.ts`:
- Around line 33-46: getRepoSessionTotal currently skips adding
cursor/codex/gemini sessions when project.sessionMeta?.total is a number,
causing non-Claude sessions to be undercounted; fix by removing the early
continue and always summing Claude count plus other provider counts.
Specifically, in getRepoSessionTotal compute per-projectTotal =
(project.sessionMeta?.total ?? 0) + (project.sessions?.length ?? 0) +
(project.cursorSessions?.length ?? 0) + (project.codexSessions?.length ?? 0) +
(project.geminiSessions?.length ?? 0) and add that to total (replace the
existing if/continue branch with this combined sum).

In `@src/components/sidebar/view/subcomponents/RecentSessions.tsx`:
- Around line 33-117: The "Show all" button is one-way because showAll is only
set to true; change the toggle to flip showAll (use setShowAll(prev => !prev))
and update the button label to show either t('projects.showAll') with
sessions.length when showAll is false or a "Show less" label (e.g.,
t('projects.showLess', { defaultValue: 'Show less' })) when showAll is true;
keep the button rendered whenever expanded && sessions.length > DEFAULT_VISIBLE
(instead of hidden > 0) so users can collapse back to the compact view; adjust
any text/count shown to reflect the current state and keep visible/hidden
calculation based on showAll and DEFAULT_VISIBLE as before.

In `@src/components/sidebar/view/subcomponents/RepoCard.tsx`:
- Around line 228-245: The lastActivityLabelFor function currently returns "0m
ago" for events under one minute; update lastActivityLabelFor to short-circuit
when minutes < 1 and return t('time.justNow', { defaultValue: 'just now' })
(mirror the behavior in RecentSessions.tsx's formatRelative) before the minutes
< 60 branch—use the existing minutes calculation and keep the rest of the
branches intact.

In `@src/components/sidebar/view/subcomponents/WorktreeRow.tsx`:
- Line 60: The BranchChip fallback currently uses project.displayName which
mislabels non-branch worktrees; update the rendering in WorktreeRow so that if
project.worktreeInfo?.branchName exists you render <BranchChip
branchName={project.worktreeInfo.branchName} /> and otherwise render a plain
<span> (e.g., <span className="worktree-name">{project.displayName}</span>)
instead of BranchChip so the directory/display name is shown without the
Git/Home branch styling or icon; locate the conditional around BranchChip in
WorktreeRow and replace the fallback with a simple span.

In `@src/i18n/locales/de/sidebar.json`:
- Around line 66-67: The German locale contains untranslated tooltip strings:
the keys "newSessionInWorktree" and "deleteWorktree" currently have English
values; update their values to proper German translations (e.g., "Neue Sitzung
in diesem Worktree" for newSessionInWorktree and "Worktree löschen" for
deleteWorktree) so the de sidebar bundle is fully localized.
- Around line 24-35: Several keys in the German locale file are still English
and one key duplicates another with inconsistent casing; update the values for
"recent", "showAll", "emptyWorktree", "staleWorktree", "messages",
"sessionsShort_one", "sessionsShort_other", and "older" to their German
translations (e.g. "recent" -> "Zuletzt"/"Letzte", "showAll" -> "Alle anzeigen",
"emptyWorktree" -> "leer · zum Starten klicken", "staleWorktree" ->
"archiviert", "messages" -> "Nachrichten", "sessionsShort_one" -> "Sitzung",
"sessionsShort_other" -> "Sitzungen", "older" -> "Älter") and remove or
consolidate the duplicate "archived" vs "staleWorktree" so there's a single,
consistently cased source of truth; then audit the same keys in the
corresponding locale files for ja, ko, ru, and zh-CN and provide proper
translations or mark for translator review if missing.

In `@src/i18n/locales/ja/sidebar.json`:
- Around line 27-35: Several sidebar JSON keys remain in English; update the
Japanese translations for the keys "recent", "worktreesUpper", "showAll",
"emptyWorktree", "staleWorktree", "messages", "sessionsShort_one",
"sessionsShort_other", "older", plus "newSessionInWorktree" and "deleteWorktree"
referenced later, replacing the English values with appropriate Japanese strings
so the ja locale is fully localized (you can ignore "sessionsShort_one" for
plural behavior since i18next uses only "other" for ja). Locate those keys in
src/i18n/locales/ja/sidebar.json and provide natural Japanese translations for
each value.

In `@src/i18n/locales/ko/sidebar.json`:
- Around line 27-35: Several sidebar i18n keys in ko/sidebar.json remain in
English (e.g., "recent", "worktreesUpper", "showAll", "emptyWorktree",
"staleWorktree", "messages", "sessionsShort_one", "sessionsShort_other",
"older", plus "newSessionInWorktree" and "deleteWorktree") causing a
mixed-language UI; replace the English values with appropriate Korean
translations for those keys (keep "sessionsShort_one" if you prefer even though
i18next for ko only uses the "other" plural form) so the sidebar is fully
localized—update the values for the listed keys in the JSON (matching the exact
key names) to their Korean equivalents and ensure JSON syntax remains valid.

---

Nitpick comments:
In `@docs/superpowers/specs/2026-04-25-sidebar-worktree-redesign-design.md`:
- Line 29: The fenced code blocks containing ASCII-art and the
"SidebarProjectList.tsx" snippet are currently untagged and trigger markdownlint
MD040; update those fence openings to include a plaintext tag (e.g., ```text or
```plaintext) for the two ASCII-art blocks shown and any other similar plain
fences (the other occurrence noted at the document). Locate the untagged
triple-backtick blocks in the sidebar-worktree-redesign-design.md file and add
the language tag to each opening fence so the linter recognizes them as
non-code/plaintext.

In `@server/projects.js`:
- Around line 299-310: The synthesized worktree path currently concatenates
strings with forward slashes; update the construction to use path.join to
produce a platform-safe path. In the block handling worktreeMarker (variables:
worktreeMarker, markerIdx, parentProjectName, worktreeName) where repoRoot is
obtained from extractProjectDirectory(parentProjectName), replace the literal
`${repoRoot}/.claude/worktrees/${worktreeName}` with a path.join(repoRoot,
'.claude', 'worktrees', worktreeName) result before setting
projectDirectoryCache.set(projectName, ...) and returning it so paths are
correct on Windows and consistent with other uses in this file.

In `@src/components/sidebar/view/subcomponents/BranchChip.tsx`:
- Around line 5-30: PALETTE in BranchChip.tsx is independently sized while
branchChipColorIndex in repoAggregates.ts uses a hardcoded PALETTE_SIZE=5, which
can cause silent fallback to NEUTRAL if they diverge; fix by either importing an
exported palette size (e.g., export PALETTE_SIZE or getPaletteLength) from
repoAggregates and use that to derive the palette or by adding a runtime
assertion at module load in BranchChip.tsx that checks PALETTE.length ===
branchChipColorIndex's modulus constant (referencing PALETTE and
branchChipColorIndex) and throw/log a clear error if they differ so the
relationship cannot silently drift.

In `@src/components/sidebar/view/subcomponents/RecentSessions.tsx`:
- Around line 122-134: formatRelative currently returns pluralized minute/hour
translations (e.g., "1m ago") because it only short-circuits for <1 minute;
update formatRelative to detect the singular cases and call the singular i18n
keys: when minutes === 1 use t('time.oneMinuteAgo', { defaultValue: '1m ago' })
and when hours === 1 use t('time.oneHourAgo', { defaultValue: '1h ago' }),
keeping the existing branches for <1 minute, minutes <60, hours <24 and days;
apply these checks using the same computed values (date from
session.lastActivity || session.createdAt, minutes, hours) so localization uses
the dedicated singular keys.

In `@src/components/sidebar/view/subcomponents/RepoCard.tsx`:
- Around line 158-177: Rendering calls compute getRepoSessionTotal and
lastActivityLabelFor (which itself calls getRepoSessions) for every WorktreeRow
on each render, causing wasted work when currentTime updates; fix by computing a
memoized Map keyed by [activeWorktrees, additionalSessions, currentTime] (e.g.,
worktreeStats: Map<name, { sessionCount, lastActivityLabel }>) using useMemo,
populate it by iterating activeWorktrees and calling
getRepoSessions/getRepoSessionTotal/lastActivityLabelFor once per worktree, then
replace inline calls in the WorktreeRow props (sessionCount, lastActivityLabel)
with lookups like worktreeStats.get(wt.name) so re-renders reuse the precomputed
values.
- Around line 112-122: The current conditional defaultValue on t() is
ineffective for plurals; update the code to use i18next pluralization properly
by adding plural keys (projects.worktrees_one / projects.worktrees_other and
projects.sessionsShort_one / projects.sessionsShort_other) in your locale bundle
and then call t('projects.worktrees', { count: linkedWorktrees.length }) and
t('projects.sessionsShort', { count: sessionTotal }) in RepoCard (same pattern
applies in WorktreeRow.tsx) so you can remove the linkedWorktrees.length === 1
conditional and let i18next pick the correct plural form.

In `@src/components/sidebar/view/subcomponents/WorktreeRow.tsx`:
- Around line 32-39: The metaText uses a ternary defaultValue to pick
singular/plural for t('projects.sessionsShort') but i18next will ignore that
when plural keys exist; replace the ternary + conditional defaultValue with the
same pattern used in RepoCard.tsx: always call t('projects.sessionsShort', {
count: sessionCount, defaultValue: sessionCount === 1 ? 'session' : 'sessions'
}) (keeping the `${sessionCount} ${...}${lastActivityLabel ? \` ·
${lastActivityLabel}\` : ''}` composition) so pluralization is driven by the
count param and the code is simplified; update the metaText expression in
WorktreeRow.tsx accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ae73fb08-2f74-4cef-a118-2fb935f5c2f0

📥 Commits

Reviewing files that changed from the base of the PR and between 8cf916b and d097804.

📒 Files selected for processing (22)
  • docs/superpowers/plans/2026-04-25-sidebar-worktree-redesign.md
  • docs/superpowers/specs/2026-04-25-sidebar-worktree-redesign-design.md
  • server/projects.js
  • src/components/sidebar/utils/__tests__/repoAggregates.test.ts
  • src/components/sidebar/utils/repoAggregates.ts
  • src/components/sidebar/view/Sidebar.tsx
  • src/components/sidebar/view/subcomponents/BranchChip.tsx
  • src/components/sidebar/view/subcomponents/NewSessionRow.tsx
  • src/components/sidebar/view/subcomponents/RecentSessions.tsx
  • src/components/sidebar/view/subcomponents/RepoCard.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectList.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx
  • src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx
  • src/components/sidebar/view/subcomponents/WorktreeRow.tsx
  • src/hooks/useProjectsState.ts
  • src/i18n/locales/de/sidebar.json
  • src/i18n/locales/en/sidebar.json
  • src/i18n/locales/ja/sidebar.json
  • src/i18n/locales/ko/sidebar.json
  • src/i18n/locales/ru/sidebar.json
  • src/i18n/locales/zh-CN/sidebar.json
💤 Files with no reviewable changes (3)
  • src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx
  • src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx
✅ Files skipped from review due to trivial changes (2)
  • src/i18n/locales/ru/sidebar.json
  • docs/superpowers/plans/2026-04-25-sidebar-worktree-redesign.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/i18n/locales/zh-CN/sidebar.json
  • src/components/sidebar/view/subcomponents/SidebarProjectList.tsx

Comment thread server/projects.js Outdated
Comment thread src/components/sidebar/utils/repoAggregates.ts
Comment thread src/components/sidebar/view/subcomponents/RecentSessions.tsx
Comment thread src/components/sidebar/view/subcomponents/RepoCard.tsx
Comment thread src/components/sidebar/view/subcomponents/WorktreeRow.tsx Outdated
Comment thread src/i18n/locales/de/sidebar.json Outdated
Comment thread src/i18n/locales/de/sidebar.json Outdated
Comment thread src/i18n/locales/ja/sidebar.json Outdated
Comment thread src/i18n/locales/ko/sidebar.json Outdated
@ashalliants ashalliants force-pushed the feat/worktree-toggle branch from d097804 to a64785c Compare April 26, 2026 10:17
@ashalliants ashalliants force-pushed the feat/worktree-toggle branch 3 times, most recently from 6078b30 to b09f081 Compare May 11, 2026 08:57
@ashalliants ashalliants mentioned this pull request May 22, 2026
@ashalliants ashalliants force-pushed the feat/worktree-toggle branch from b09f081 to d4a1b95 Compare May 27, 2026 09:05
@blackmammoth

Copy link
Copy Markdown
Member

hey @ashalliants, was gonna merge it now but it had merge conflicts. Can you update the PR and I'll make sure to review it.

ashalliants and others added 3 commits June 4, 2026 17:54
Introduces per-session git worktree control and a redesigned sidebar
that surfaces worktrees as first-class citizens of their parent repo.

Worktrees:
- Per-session worktree toggle in the new-session flow
- Server-side worktree detection via git rev-parse, falling back to
  the .claude/worktrees/<name> path convention
- Repo-grouping logic that synthesizes a parent project for repos
  that have only worktrees discovered so far
- Cleanup hook removes the git worktree directory on project delete
- Fast-path resolves a worktree's directory from its encoded project
  name (preserving dashes in repo names like "developer-tracker"),
  bypassing cwd-vote heuristics that subagent cwds would otherwise
  poison

Sidebar redesign (docs/superpowers/specs/2026-04-25-sidebar-worktree-
redesign-design.md, /plans/2026-04-25-sidebar-worktree-redesign.md):
- Repo header (RepoCard) with current-branch chip, replacing the
  three-level RepoGroup → ProjectItem → Sessions nesting
- "+ New session" full-width primary CTA at top of expanded body
- RECENT section: recency-sorted sessions across main + worktrees
  with branch chips; "Show all N" inline expansion
- WORKTREES section: collapsible, with active worktrees plus a
  separate "Archived (N)" sub-toggle for stale ones
- BranchChip primitive with deterministic per-branch color and
  Home/GitBranch icon variants for main vs worktree origin
- Hover-revealed "+" (new session here) and trash (delete worktree)
  on each worktree row
- Header click toggles expansion only — no implicit project select,
  so mobile users can browse without losing the sidebar
- Single-worktree projects render with no WORKTREES section

Tests:
- src/components/sidebar/utils/__tests__/repoAggregates.test.ts
- server/__tests__/worktree-grouping.test.js
- server/__tests__/claude-sdk.test.js
- src/components/chat/utils/__tests__/chatStorage.test.ts
- WorktreeRow: don't fall back to displayName for the branch chip so
  stale worktrees no longer render a non-branch label as a branch
- RepoCard: add a just-now branch to lastActivityLabelFor so sub-minute
  events stop showing "0m ago" (parity with RecentSessions.formatRelative)
- RecentSessions: add a "Show less" affordance to collapse an expanded
  session list back to the top 5
- i18n: translate the new sidebar strings (recent, showAll, emptyWorktree,
  staleWorktree, messages, sessionsShort, older, worktreesUpper) for de,
  ja, ko, ru and zh-CN; add showLess across all locales

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The /git/pull and /git/push catch blocks matched against error.message,
but spawnAsync puts git's actual output on error.stderr — error.message
is only "Command failed: git <cmd>". As a result none of the friendly
branches (CONFLICT, rejected, divergent branches, etc.) ever fired, and
the UI always showed a generic "Pull failed"/"Push failed" with an
unhelpful detail string.

- Add gitErrorText() to combine stderr/stdout/message for matching
- Match against that combined output in both handlers
- Default the detail to git's stderr so even unmatched errors are useful
- Catch both "diverged" and "divergent" and give actionable guidance
  (rebase/merge) for divergent branches

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ashalliants ashalliants force-pushed the feat/worktree-toggle branch from fbfd344 to e4b3e10 Compare June 4, 2026 16:55
@blackmammoth blackmammoth added the enhancement New feature or request label Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants