Skip to content

Stop And Delete Sessions#535

Merged
arul28 merged 3 commits into
mainfrom
ade/stop-and-delete-sessions-7ecf5212
Jun 7, 2026
Merged

Stop And Delete Sessions#535
arul28 merged 3 commits into
mainfrom
ade/stop-and-delete-sessions-7ecf5212

Conversation

@arul28

@arul28 arul28 commented Jun 7, 2026

Copy link
Copy Markdown
Owner

What

Adds Stop and delete for runtime-backed sessions in the Work tab.

Single session

  • Right-clicking a running CLI or shell session — on the sidebar card, the work-area header (right-click is newly wired), or via the session info popover — now offers Stop and delete: it stops the runtime and deletes the session in one confirmed step, alongside the existing two-step Stop runtime → Delete session flow.
  • Chats keep their direct Delete chat (they have no runtime to stop).

Multi-select

  • A Stop & delete bulk action appears whenever the selection includes a running runtime. It stops everything that needs stopping, then deletes the whole selection.
  • Fixes the bug where a mixed selection (e.g. a running CLI + a chat) only deleted the chat, because the running CLI couldn't be deleted without first being stopped.

Both flows are gated by a styled confirmation dialog (reusing useConfirmDialog/ConfirmDialog).

How it works

The session-delete service (deleteTerminalSessionWithRuntimeCleanup) already stops a running runtime before removing the record, so a single sessions.delete call covers both steps for CLI/shell. The bulk handler routes chats → agentChat.delete and everything else → sessions.delete.

Tests

  • TerminalsPage: single stop-and-delete (happy path + cancel), mixed-selection bulk stop-and-delete.
  • SessionListPane: bulk button visibility rule (shown iff selection has a running non-chat session); added afterEach(cleanup) for isolation.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

New Features

  • Added "Stop and delete" functionality for individual sessions via context menu and session info
  • Added bulk "Stop and delete" action for multiple selected sessions
  • Added confirmation dialog for destructive session operations

Greptile Summary

This PR adds a "Stop & delete" action for runtime-backed sessions, available from the sidebar context menu, the work-area header (right-click now wired), the session info popover, and as a bulk multi-select action. It also fixes a pre-existing bug where a mixed selection containing a running CLI session couldn't be bulk-deleted in one step.

  • Single session flow: handleStopAndDeleteSession shows a styled ConfirmDialog, then delegates to window.ade.sessions.delete, which already stops the runtime server-side before removing the record — so one call covers both steps.
  • Bulk flow: handleBulkStopAndDeleteSelected routes chats to agentChat.delete and everything else to sessions.delete, resolves them concurrently via allSettledWithConcurrency, and clears the selection on success. Both flows guard the destructive action with the shared stopAndDeleteConfirm dialog instance.
  • Tests cover the happy path and cancel path for single context-menu deletion, plus the mixed-selection bulk case and the button visibility rule in SessionListPane.

Confidence Score: 5/5

Safe to merge. The new stop-and-delete paths follow the same error handling and local-state cleanup patterns as the existing bulk-delete and single-delete handlers, and both flows are gated behind a styled confirmation dialog.

The new handlers are structurally consistent with existing delete handlers: they use allSettledWithConcurrency for bulk operations, clear selections and close menus on success, log errors without swallowing them, and rely on the server-side sessions.delete to handle runtime teardown atomically. No state mutation bypasses, unchecked branches, or missing cleanup steps were found in the changed code.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx Adds handleStopAndDeleteSession and handleBulkStopAndDeleteSelected with useConfirmDialog gating, wires both handlers to SessionContextMenu and SessionInfoPopover, renders shared ConfirmDialog at page root — logic is consistent with existing bulk-delete patterns.
apps/desktop/src/renderer/components/terminals/SessionContextMenu.tsx Adds onStopAndDelete prop and a new Stop & delete button (visible for running non-chat sessions with a ptyId); upgrades two sibling buttons from ASCII ... to Unicode ellipsis for consistency.
apps/desktop/src/renderer/components/terminals/SessionInfoPopover.tsx Adds onStopAndDelete prop and a danger Button with tooltip, gated by session.status === running && ptyId && !isChat — mirrors the context-menu condition.
apps/desktop/src/renderer/components/terminals/SessionListPane.tsx Adds onBulkStopAndDelete prop and a Stop & delete N bulk button shown when selectedRunningCount > 0; selectedCount correctly matches the confirmed scope.
apps/desktop/src/renderer/components/terminals/TerminalsPage.test.tsx Adds three new tests covering context-menu stop-and-delete (confirm + cancel) and bulk mixed-selection, and updates mocks to expose the new actions.
apps/desktop/src/renderer/components/terminals/SessionListPane.test.tsx Adds afterEach(cleanup) for isolation and two tests verifying Stop & delete button visibility.
apps/desktop/src/renderer/components/terminals/CliSessionWorkSurfaceHeader.tsx Wires the new onContextMenu prop through to WorkSurfaceHeader with event.preventDefault() — adds right-click parity with the sidebar card.
apps/desktop/src/renderer/components/work/WorkSurfaceHeader.tsx Adds optional onContextMenu prop and attaches it to the outer header div — minimal, safe change.

Reviews (2): Last reviewed commit: "ship: iteration 1 — unify 'Stop & delete..." | Re-trigger Greptile

arul28 and others added 2 commits June 6, 2026 20:37
…lk action

Right-clicking a CLI or shell session (sidebar card OR work-area header) now
offers a 'Stop and delete' option that stops the runtime and deletes the
session in one confirmed step, alongside the existing two-step Stop runtime →
Delete session flow. Chats keep their direct delete.

For multi-select, a 'Stop & delete' bulk action appears whenever the selection
includes a running runtime. It stops everything that needs stopping and then
deletes every selected session — fixing the case where a mixed CLI+chat
selection only deleted the chat. Both flows gate on a styled confirmation
dialog.

Header right-click is newly wired through WorkSurfaceHeader so it opens the
same session context menu as the kebab and the sidebar card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mintlify

mintlify Bot commented Jun 7, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
ade-ac1c6011 🟢 Ready View Preview Jun 7, 2026, 12:54 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@vercel

vercel Bot commented Jun 7, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview Jun 7, 2026 1:11am

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds "stop and delete" confirmation flows for CLI sessions in the Terminals page. It wires context-menu support through header components, adds UI controls across context menus, popovers, and selection toolbars, implements single and bulk deletion handlers with confirmation dialogs, and provides comprehensive test coverage for both success and cancellation paths.

Changes

Stop & Delete Confirmation Flow for CLI Sessions

Layer / File(s) Summary
Header Context Menu Foundation
apps/desktop/src/renderer/components/work/WorkSurfaceHeader.tsx
WorkSurfaceHeader now accepts an optional onContextMenu prop and attaches it to the header container; type imports updated to support React mouse events.
CLI Session Header Integration
apps/desktop/src/renderer/components/terminals/CliSessionWorkSurfaceHeader.tsx
CliSessionWorkSurfaceHeader wires the parent's onContextMenu callback to WorkSurfaceHeader, preventing default behavior and forwarding the event with session context.
Individual Session Stop & Delete UI
apps/desktop/src/renderer/components/terminals/SessionContextMenu.tsx, SessionInfoPopover.tsx
SessionContextMenu and SessionInfoPopover both add "stop & delete" action buttons for running non-chat sessions, accepting an onStopAndDelete callback and displaying loading states via deletingSessionId.
Bulk Selection Stop & Delete UI
apps/desktop/src/renderer/components/terminals/SessionListPane.tsx
SessionListPane replaces "Delete selected" with a conditional "Stop & delete" toolbar button that appears when selected sessions include running runtimes and triggers onBulkStopAndDelete.
Core Stop & Delete Logic & Wiring
apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx
TerminalsPage imports ConfirmDialog and useConfirmDialog, creates a confirmation controller, implements handleStopAndDeleteSession for individual sessions and handleBulkStopAndDeleteSelected for bulk operations with concurrent deletion (chat sessions via agentChat.delete, others via sessions.delete), and wires handlers to all three component UIs; both handlers clean up local state, close tabs, invalidate caches, and handle errors.
Test Infrastructure & Coverage
apps/desktop/src/renderer/components/terminals/SessionListPane.test.tsx, TerminalsPage.test.tsx
Added afterEach cleanup hooks, expanded mock implementations for session list and context menu with new buttons and callbacks, and added three new test cases: single CLI session deletion via context menu (confirming service calls and list removal), confirmation cancellation (verifying no deletion occurs), and bulk deletion of mixed CLI+chat selections (asserting both service calls execute).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

desktop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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
Title check ✅ Passed The title 'Stop And Delete Sessions' directly summarizes the main change—adding a stop-and-delete action for sessions across multiple UI surfaces (sidebar, context menu, bulk selection).
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/stop-and-delete-sessions-7ecf5212

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.

@arul28

arul28 commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

@copilot review but do not make fixes

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/renderer/components/terminals/SessionContextMenu.tsx`:
- Around line 166-174: The label in SessionContextMenu.tsx is inconsistent with
SessionInfoPopover.tsx: change the button text in the conditional rendering
block (the button that calls onStopAndDelete(session) and onClose()) from "Stop
and delete" to exactly "Stop & delete" so the user-facing action wording matches
SessionInfoPopover; locate the JSX branch that checks isRunning && session.ptyId
&& !isChat and update the displayed string (also ensure the deletingSessionId
conditional remains intact).

In `@apps/desktop/src/renderer/components/terminals/SessionInfoPopover.tsx`:
- Around line 295-307: Session label mismatch: in SessionInfoPopover (render
branch checking session.status === "running" && session.ptyId && !isChat) update
the action copy so both SmartTooltip label and the Button text use the
consistent phrase "Stop and delete" (change the Button text currently "Stop &
delete" and confirm SmartTooltip label is also "Stop and delete") to match the
label used in SessionContextMenu and keep
onStopAndDelete/deletingSessionId/session.id logic unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 86e1ef48-1514-471c-90e6-4524adcc5dcf

📥 Commits

Reviewing files that changed from the base of the PR and between 97589d7 and fabf4e3.

📒 Files selected for processing (8)
  • apps/desktop/src/renderer/components/terminals/CliSessionWorkSurfaceHeader.tsx
  • apps/desktop/src/renderer/components/terminals/SessionContextMenu.tsx
  • apps/desktop/src/renderer/components/terminals/SessionInfoPopover.tsx
  • apps/desktop/src/renderer/components/terminals/SessionListPane.test.tsx
  • apps/desktop/src/renderer/components/terminals/SessionListPane.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalsPage.test.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx
  • apps/desktop/src/renderer/components/work/WorkSurfaceHeader.tsx

Comment thread apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx
Comment on lines 539 to 667
expect(confirmSpy).toHaveBeenCalledWith(expect.stringContaining("Delete 2 selected sessions?"));
confirmSpy.mockRestore();
});

it("stops and deletes a single running CLI session via the context menu", async () => {
const runningCli = workMocks.makeTerminalSession("cli-single", "lane-primary", "codex");
const sessionDelete = vi.fn().mockResolvedValue(undefined);
const agentChatDelete = vi.fn().mockResolvedValue(undefined);

Object.defineProperty(window, "ade", {
configurable: true,
value: {
agentChat: { delete: agentChatDelete },
builtInBrowser: { onEvent: vi.fn(() => vi.fn()) },
sessions: { delete: sessionDelete },
},
});
workMocks.currentWork = {
...workMocks.baseWork,
sessions: [runningCli],
visibleSessions: [runningCli],
runningFiltered: [runningCli],
runningSessions: [runningCli],
filtered: [runningCli],
sessionsGroupedByLane: new Map([["lane-primary", [runningCli]]]),
closingPtyIds: new Set<string>(),
};

render(<TerminalsPage />);

// Open the context menu for the session, then trigger stop-and-delete.
fireEvent.click(await screen.findByRole("button", { name: "context menu cli-single" }));
fireEvent.click(await screen.findByRole("button", { name: "context stop and delete cli-single" }));

// The styled confirmation dialog must gate the destructive single-session action.
fireEvent.click(await screen.findByRole("button", { name: "Stop & delete" }));

await waitFor(() => {
// The session-delete service stops the runtime and removes the record in one call;
// the chat-delete path must not be touched for a non-chat session.
expect(sessionDelete).toHaveBeenCalledWith({ sessionId: "cli-single" });
expect(workMocks.currentWork.removeSessionFromList).toHaveBeenCalledWith("cli-single");
expect(workMocks.currentWork.closeTab).toHaveBeenCalledWith("cli-single");
});
expect(agentChatDelete).not.toHaveBeenCalled();
});

it("does not delete when the stop-and-delete confirmation is dismissed", async () => {
const runningCli = workMocks.makeTerminalSession("cli-cancel", "lane-primary", "codex");
const sessionDelete = vi.fn().mockResolvedValue(undefined);

Object.defineProperty(window, "ade", {
configurable: true,
value: {
agentChat: { delete: vi.fn() },
builtInBrowser: { onEvent: vi.fn(() => vi.fn()) },
sessions: { delete: sessionDelete },
},
});
workMocks.currentWork = {
...workMocks.baseWork,
sessions: [runningCli],
visibleSessions: [runningCli],
runningFiltered: [runningCli],
runningSessions: [runningCli],
filtered: [runningCli],
sessionsGroupedByLane: new Map([["lane-primary", [runningCli]]]),
closingPtyIds: new Set<string>(),
};

render(<TerminalsPage />);

fireEvent.click(await screen.findByRole("button", { name: "context menu cli-cancel" }));
fireEvent.click(await screen.findByRole("button", { name: "context stop and delete cli-cancel" }));
fireEvent.click(await screen.findByRole("button", { name: "CANCEL" }));

await waitFor(() =>
expect(screen.queryByRole("button", { name: "Stop & delete" })).toBeNull(),
);
expect(sessionDelete).not.toHaveBeenCalled();
expect(workMocks.currentWork.removeSessionFromList).not.toHaveBeenCalled();
});

it("stops and deletes a mixed selection of running CLI and chat sessions", async () => {
const runningCli = workMocks.makeTerminalSession("cli-running", "lane-primary", "codex");
const runningChat = workMocks.makeTerminalSession("chat-running", "lane-primary", "codex-chat", {
ptyId: null,
});
const agentChatDelete = vi.fn().mockResolvedValue(undefined);
const sessionDelete = vi.fn().mockResolvedValue(undefined);

Object.defineProperty(window, "ade", {
configurable: true,
value: {
agentChat: { delete: agentChatDelete },
builtInBrowser: { onEvent: vi.fn(() => vi.fn()) },
sessions: { delete: sessionDelete },
},
});
workMocks.currentWork = {
...workMocks.baseWork,
sessions: [runningCli, runningChat],
visibleSessions: [runningCli, runningChat],
runningFiltered: [runningCli, runningChat],
runningSessions: [runningCli, runningChat],
filtered: [runningCli, runningChat],
sessionsGroupedByLane: new Map([["lane-primary", [runningCli, runningChat]]]),
closingPtyIds: new Set<string>(),
};

render(<TerminalsPage />);

fireEvent.click(await screen.findByRole("button", { name: "select cli-running" }), { metaKey: true });
fireEvent.click(await screen.findByRole("button", { name: "select chat-running" }), { metaKey: true });
fireEvent.click(await screen.findByRole("button", { name: "bulk stop and delete" }));

// The styled confirmation dialog gates the destructive action.
fireEvent.click(await screen.findByRole("button", { name: "Stop & delete" }));

await waitFor(() => {
// The running CLI session is stopped+deleted via the session-delete service,
// and the chat is removed via the chat delete flow — both in one action.
expect(sessionDelete).toHaveBeenCalledWith({ sessionId: "cli-running" });
expect(agentChatDelete).toHaveBeenCalledWith({ sessionId: "chat-running" });
expect(workMocks.currentWork.removeSessionFromList).toHaveBeenCalledWith("cli-running");
expect(workMocks.currentWork.removeSessionFromList).toHaveBeenCalledWith("chat-running");
});
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 No test coverage for the info-popover stop-and-delete path

The three new TerminalsPage tests exercise the context-menu path and the bulk path, but there is no test verifying that clicking "Stop & delete" inside SessionInfoPopover triggers the same handler and shows the confirmation dialog. The popover mock (vi.mock("./SessionInfoPopover", ...)) currently renders null, so that surface is untested. A short test mirroring the context-menu confirm/cancel test but opening the popover first would catch any future divergence.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/terminals/TerminalsPage.test.tsx
Line: 539-667

Comment:
**No test coverage for the info-popover stop-and-delete path**

The three new `TerminalsPage` tests exercise the context-menu path and the bulk path, but there is no test verifying that clicking "Stop & delete" inside `SessionInfoPopover` triggers the same handler and shows the confirmation dialog. The popover mock (`vi.mock("./SessionInfoPopover", ...)`) currently renders `null`, so that surface is untested. A short test mirroring the context-menu confirm/cancel test but opening the popover first would catch any future divergence.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The info-popover path invokes the exact same handleStopAndDeleteSession handler that the context-menu confirm/cancel tests already exercise, and the popover→handler wiring is a typechecked one-liner. A render test of SessionInfoPopover would import TerminalView (xterm) and the localStorage-dependent terminal harness, making it brittle for near-zero added signal — so it's intentionally not added. No change.

disabled={deletingSessionId === session.id}
onClick={() => { onStopAndDelete(session); onClose(); }}
>
{deletingSessionId === session.id ? "Deleting..." : "Stop and delete"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The disabled state prevents re-clicking during deletion, but the "Deleting..." text uses an ASCII ellipsis while the sibling SessionInfoPopover uses "Deleting…" (Unicode). Using the same character keeps the two surfaces consistent.

Suggested change
{deletingSessionId === session.id ? "Deleting..." : "Stop and delete"}
{deletingSessionId === session.id ? "Deleting" : "Stop and delete"}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/terminals/SessionContextMenu.tsx
Line: 172

Comment:
The `disabled` state prevents re-clicking during deletion, but the `"Deleting..."` text uses an ASCII ellipsis while the sibling `SessionInfoPopover` uses `"Deleting…"` (Unicode). Using the same character keeps the two surfaces consistent.

```suggestion
            {deletingSessionId === session.id ? "Deleting…" : "Stop and delete"}
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

…u/popover

Addresses CodeRabbit + Greptile review nits: standardize the action label to
'Stop & delete' and the in-progress text to 'Deleting…' (unicode) across the
session context menu and info popover so all surfaces match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@arul28

arul28 commented Jun 7, 2026

Copy link
Copy Markdown
Owner Author

@copilot review but do not make fixes

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant