Skip to content

fix(sidebar): dispatch workspace delete intent after menu close - #11646

Open
ax-dfcorp wants to merge 9 commits into
stablyai:mainfrom
ax-dfcorp:fix/sidebar-delayed-workspace-delete
Open

fix(sidebar): dispatch workspace delete intent after menu close#11646
ax-dfcorp wants to merge 9 commits into
stablyai:mainfrom
ax-dfcorp:fix/sidebar-delayed-workspace-delete

Conversation

@ax-dfcorp

@ax-dfcorp ax-dfcorp commented Jul 31, 2026

Copy link
Copy Markdown

Summary

Right-clicking Delete could silently do nothing because the context menu waited 50 ms before resolving the workspace from live state. A sidebar refresh during that arbitrary delay could temporarily remove the row.

This change uses an explicit delete intent and preserves it through execution:

  • capture the ID and instance ID of every selected workspace
  • close the controlled menu and dispatch the intent on the next macrotask
  • retain single, batch, and lineage identities through the confirmation dialog
  • revalidate the complete confirmed set immediately before deletion
  • revalidate each serialized target again before its backend delete
  • cancel safely with a workspace-list-changed message if any target vanished or its path belongs to a recreated workspace

Immediate ID-only callers retain their existing behavior. Folder deletion retains its separate route, and the backend deletion protocol is unchanged.

Screenshots

No visual design change. Electron validation confirmed that Delete opens the confirmation dialog after the menu closes; canceling leaves the selected workspace intact.

Testing

  • pnpm typecheck:web
  • 66 focused sidebar delete/context-menu/dialog tests
  • touched-file oxlint and oxfmt checks, including max-lines and React Doctor
  • pnpm run verify:localization-catalog
  • git diff --check
  • Electron context-menu delete confirmation validation

Compatibility

Renderer-only change. No IPC, remote wire, Git command, filesystem path, keyboard shortcut, or platform-specific behavior changed. The same flow applies to local, folder-workspace, and SSH-backed sidebar state.

Security

No new command execution, filesystem access, authentication, dependency, or IPC surface. Revalidating every captured instance identity at dispatch, confirmation, and queued execution prevents a delayed action from targeting a newly recreated workspace at the same path.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

The worktree deletion flow validates captured workspace identities against current store state before confirmation and execution. Stale or missing non-folder targets show a translated toast and stop deletion. Folder misses remain silent. Context-menu deletion now uses deferred typed intents for worktrees, batches, and folders. Git-status hydration runs in a dedicated hook. Batch deletion validates, orders, and executes targets in parallel. Tests cover stale, delayed, lineage, folder, batch, and deferred deletion cases.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
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.
Title check ✅ Passed The title clearly describes the primary change: dispatching the workspace delete intent after the menu closes.
Description check ✅ Passed The description covers the user-visible change, testing, security, and compatibility; AI review and notes are not separate sections.

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.

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes a race condition where right-clicking Delete on a workspace could silently no-op if the sidebar refreshed during the context menu's intentional 50 ms close delay. The fix passes the clicked Worktree snapshot into runWorktreeDelete, which uses it as a fallback only when the live store no longer contains that workspace ID.

  • runWorktreeDelete gains an optional targetSnapshot?: Worktree parameter; the live store is always authoritative and the snapshot is only consulted when the exact ID is absent from the map.
  • WorktreeContextMenu updates its useCallback dependency from worktree.id to the full worktree object so the captured closure always holds the complete snapshot at click time.
  • A new Vitest case covers the transient-refresh scenario: the row is removed from the map between snapshot capture and the timeout firing, and deletion still proceeds.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to the 50 ms context-menu close window and introduces no new risk to the batch-delete or primary-workspace paths.

The live store always takes priority; the snapshot is used only when the workspace ID is completely absent from the map and the snapshot ID matches exactly. All existing defensive guards apply naturally to the snapshot path. The only finding is a test filed under the wrong describe block, which is consistent with the pre-existing file convention.

Files Needing Attention: No files require special attention. The test-organization issue in delete-worktree-flow.test.ts is cosmetic and pre-dates this PR.

Important Files Changed

Filename Overview
src/renderer/src/components/sidebar/delete-worktree-flow.ts Adds optional targetSnapshot fallback to runWorktreeDelete; live store is always preferred, snapshot only used on exact ID match — logic is correct and well-guarded.
src/renderer/src/components/sidebar/WorktreeContextMenu.tsx Passes full worktree object to runWorktreeDelete and updates useCallback deps from worktree.id to worktree; dependency widening is intentional and necessary to capture the snapshot.
src/renderer/src/components/sidebar/delete-worktree-flow.test.ts New snapshot-fallback test is correct but nested inside describe('runWorktreeBatchDelete') while testing runWorktreeDelete — follows existing pattern but adds to test-organization debt.

Sequence Diagram

sequenceDiagram
    participant User
    participant ContextMenu as WorktreeContextMenu
    participant Store as AppStore
    participant Flow as runWorktreeDelete

    User->>ContextMenu: Right-click Delete
    ContextMenu->>ContextMenu: handleDelete schedules 50 ms timeout captures worktree snapshot in closure
    Note over Store: Sidebar refresh occurs worktree transiently absent from map
    ContextMenu->>Flow: setTimeout fires runWorktreeDelete worktree.id worktree
    Flow->>Store: getWorktreeMapFromState get worktreeId
    Store-->>Flow: undefined transiently missing
    Flow->>Flow: targetSnapshot id equals worktreeId
    alt snapshot ID matches new path
        Flow->>Flow: target equals targetSnapshot
        Flow->>Store: removeWorktree worktreeId false
    else no snapshot or ID mismatch existing guard
        Flow->>Flow: target null return fail closed
    end
Loading

Reviews (1): Last reviewed commit: "fix(sidebar): keep delayed workspace del..." | Re-trigger Greptile

Comment thread src/renderer/src/components/sidebar/delete-worktree-flow.test.ts Outdated
@AmethystLiang AmethystLiang added the P1 High priority: bug or day-to-day user frustration label Jul 31, 2026
ax-dfcorp and others added 2 commits August 4, 2026 14:27
…e no-op

runWorktreeDelete fails closed when the clicked row is no longer in the store
(concurrent delete, state reset, or a runtime re-pair that drops live rows).
The guard is right, but it returned with no feedback, so Delete looked broken.

Report the miss with the stale-list toast runWorktreeBatchDelete already uses,
extracted to a shared module so both paths share one description string.
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@brennanb2025

Copy link
Copy Markdown
Contributor

Thanks for this — the diagnosis is right. runWorktreeDelete can find nothing when the context menu runs Delete after its 50 ms close delay, and today it just returns, so the click looks like it did nothing.

I've pushed a change to your branch that takes a different route:

  • Kept the missing-record guard and reported the miss instead of deleting from the caller's copy. When the row really is gone, its tabs and PTYs have already been torn down, so proceeding is the riskier option. The flow now shows the same "the list looks stale, refresh and try again" toast that the multi-select delete already uses.
  • That covers every entry point — context menu, card quick action, palette, and the delete-current-workspace shortcut — not just the one call site.
  • Why not the snapshot: with the delete confirmation on (the default), it opens the confirm dialog with an id the store can't resolve, and the dialog immediately closes itself again, so the click still looked like a no-op for most users.
  • The single and multi-select paths now share one small toast helper. Folder workspaces are excluded, since they're never in the worktree map — otherwise the delete-current-workspace shortcut would claim a perfectly healthy workspace had vanished.
  • WorktreeContextMenu is back to unchanged.

New tests cover both the vanished-row case and the folder-workspace case, and both go red if the change is reverted.

Separately, and not for this PR: the workspace context menu in the agent map resolves its row from a wider source than this flow does, so Delete there can still do nothing for a hidden imported workspace.

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f76bbfad-0238-40bd-8d9b-b2c6c9d156e9

📥 Commits

Reviewing files that changed from the base of the PR and between 5bd2f59 and 6fc52fd.

📒 Files selected for processing (4)
  • src/renderer/src/components/sidebar/delete-worktree-flow.test.ts
  • src/renderer/src/components/sidebar/delete-worktree-flow.ts
  • src/renderer/src/components/sidebar/stale-workspace-list-toast.ts
  • src/renderer/src/i18n/locales/en.json

Comment thread src/renderer/src/components/sidebar/delete-worktree-flow.ts Outdated
@brennanb2025 brennanb2025 changed the title fix(sidebar): keep delayed workspace delete target stable fix(sidebar): dispatch workspace delete intent after menu close Aug 10, 2026

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 35e1972b-0e6d-4819-a860-09877f701b2d

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc52fd and 3a70b50.

📒 Files selected for processing (7)
  • src/renderer/src/components/sidebar/WorktreeContextMenu.tsx
  • src/renderer/src/components/sidebar/delete-worktree-flow.test.ts
  • src/renderer/src/components/sidebar/delete-worktree-flow.ts
  • src/renderer/src/components/sidebar/stale-workspace-list-toast.ts
  • src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts
  • src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.ts
  • src/renderer/src/i18n/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/components/sidebar/delete-worktree-flow.test.ts
  • src/renderer/src/components/sidebar/delete-worktree-flow.ts

Comment thread src/renderer/src/components/sidebar/WorktreeContextMenu.tsx

@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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: adc7c5a3-e6a2-4224-b9b7-979e53389890

📥 Commits

Reviewing files that changed from the base of the PR and between 3a70b50 and c1169c4.

📒 Files selected for processing (6)
  • src/renderer/src/components/sidebar/WorktreeContextMenu.tsx
  • src/renderer/src/components/sidebar/delete-worktree-flow.test.ts
  • src/renderer/src/components/sidebar/delete-worktree-flow.ts
  • src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts
  • src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.ts
  • src/renderer/src/components/sidebar/worktree-delete-request.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.test.ts
  • src/renderer/src/components/sidebar/worktree-context-menu-delete-intent.ts
  • src/renderer/src/components/sidebar/WorktreeContextMenu.tsx

Comment thread src/renderer/src/components/sidebar/delete-worktree-flow.ts

@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 (2)
src/renderer/src/components/sidebar/DeleteWorktreeDialog.test.tsx (1)

336-359: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the same coverage for the lineage confirmation path.

This test covers handleDelete only. handleDeleteAll in DeleteWorktreeDialog.tsx (Lines 319-329) resolves lineageDeleteIdentities through the same guard, but no test asserts that a stale descendant aborts the lineage deletion. Add a case that renders the dialog with lineageDeleteIdentities, replaces one descendant instance, clicks the destructive button, and asserts showWorkspaceListChangedToast runs and runWorktreeDeletesInParallel does not.

src/renderer/src/components/sidebar/DeleteWorktreeDialog.tsx (1)

63-64: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Memoize the parsed identities to keep the callbacks stable.

readWorktreeDeleteIdentities returns a new array on every render. Both arrays are dependencies of handleDelete (Line 313) and handleDeleteAll (Line 348), so those useCallback wrappers recreate on every render and provide no memoization.

♻️ Proposed change
-  const worktreeDeleteIdentities = readWorktreeDeleteIdentities(modalData.worktreeDeleteIdentities)
-  const lineageDeleteIdentities = readWorktreeDeleteIdentities(modalData.lineageDeleteIdentities)
+  const worktreeDeleteIdentities = useMemo(
+    () => readWorktreeDeleteIdentities(modalData.worktreeDeleteIdentities),
+    [modalData.worktreeDeleteIdentities]
+  )
+  const lineageDeleteIdentities = useMemo(
+    () => readWorktreeDeleteIdentities(modalData.lineageDeleteIdentities),
+    [modalData.lineageDeleteIdentities]
+  )

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d61e8703-2277-48e3-a9fe-863aea83fadb

📥 Commits

Reviewing files that changed from the base of the PR and between c1169c4 and 9ba1ee7.

📒 Files selected for processing (7)
  • src/renderer/src/components/sidebar/DeleteWorktreeDialog.test.tsx
  • src/renderer/src/components/sidebar/DeleteWorktreeDialog.tsx
  • src/renderer/src/components/sidebar/delete-worktree-flow.test.ts
  • src/renderer/src/components/sidebar/delete-worktree-flow.ts
  • src/renderer/src/components/sidebar/use-delete-worktree-status-hydration.ts
  • src/renderer/src/components/sidebar/worktree-delete-execution.ts
  • src/renderer/src/components/sidebar/worktree-delete-request.ts

Comment thread src/renderer/src/components/sidebar/use-delete-worktree-status-hydration.ts Outdated
@brennanb2025

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

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.

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

Labels

P1 High priority: bug or day-to-day user frustration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants