Skip to content

feat(settings): add archived thread bulk actions - #3615

Closed
adityavardhansharma wants to merge 5 commits into
pingdotgg:mainfrom
adityavardhansharma:feat/settings-archived-threads-bulk-delete-clean
Closed

adityavardhansharma wants to merge 5 commits into
pingdotgg:mainfrom
adityavardhansharma:feat/settings-archived-threads-bulk-delete-clean

Conversation

@adityavardhansharma

@adityavardhansharma adityavardhansharma commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

What Changed

  • Settings → Archive: Added multi-select support for archived threads using environment-scoped thread keys, so duplicate thread IDs across connected environments remain distinct.
  • Bulk actions: Added bulk delete and bulk unarchive for selected archived threads. Delete reuses the existing scoped deletedThreadKeys behavior so batch deletion stays aligned with the sidebar flow.
  • UI: Added a slim inline control strip above archived thread groups: a left-aligned "Select all" checkbox anchored to the row checkbox column, with Delete / Unarchive buttons appearing on the right only when one or more threads are selected.
  • Reliability: Bulk delete and unarchive aggregate per-thread failures into a single summary toast instead of stacking one toast per failed thread.
  • Tests: Added scoped archive selection helper tests covering duplicate thread IDs across environments, select-all key generation, and stale-selection pruning.

Why

Adds bulk select, unarchive, and delete for archived threads — the panel previously only supported acting on one thread at a time. Built on the app's per-environment archive model (snapshots load per environment, actions route through scoped refs), with destructive actions kept predictable.

UI Changes

Old UI

image

New UI

image image

Video

t3code_bulk.mp4

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Open in Devin Review

Note

Medium Risk
Bulk permanent deletes and sequential thread mutations are user-data operations; behavior is mitigated by confirmations, scoped refs, and partial-failure retention, but mistakes or partial batch failures still matter.

Overview
Settings → Archived threads now supports multi-select and bulk Delete / Unarchive, using environment-scoped selection keys (environmentId:threadId) so the same thread ID in different environments stays distinct.

A Select all bar and per-row checkboxes drive bulk actions; confirmations use AlertDialog (bulk delete respects the existing confirm thread delete setting). Bulk runs call unarchiveThread / deleteThread per selection, keep failed rows selected, refresh snapshots, and show one summary error toast instead of one per failure. Bulk delete passes deletedThreadKeys and ignorePostDeleteCleanupFailure so batch behavior aligns with sidebar deletes without failing the whole batch on post-delete worktree cleanup.

Pure helpers (archivedThreadSelectionKey, buildArchivedThreadSelectionKeys, pruneArchivedThreadSelection) and unit tests cover cross-environment IDs and stale selection pruning.

Reviewed by Cursor Bugbot for commit afd5772. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add bulk select, unarchive, and delete actions to the archived threads settings panel

  • Adds multi-select checkboxes to each archived thread row, a "Select all" checkbox (indeterminate when partially selected), and bulk Unarchive/Delete buttons with item counts in SettingsPanels.tsx.
  • Bulk operations iterate over selected entries, call unarchiveThread or deleteThread per thread, retain failed selections, refresh the archived list, and show aggregated error toasts on failure.
  • Selection keys are scoped by environment and thread ID using new helpers archivedThreadSelectionKey, buildArchivedThreadSelectionKeys, and pruneArchivedThreadSelection in SettingsPanels.logic.ts; pruning keeps selections in sync as the archived snapshot changes.
  • Extends deleteThread in useThreadActions.ts to accept ignorePostDeleteCleanupFailure, so bulk delete treats post-delete cleanup errors as non-fatal (an error toast is still shown).
  • All per-thread controls and bulk action buttons are disabled while a bulk operation is pending.
📊 Macroscope summarized afd5772. 3 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. 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: 79f64bd5-9e28-46f8-82fb-92cbb09fa2a1

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
✨ 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.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Jun 30, 2026

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit afd5772. Configure here.

title: `Failed to unarchive ${failedCount} of ${totalCount} threads`,
description: lastErrorMessage,
}),
);

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.

Interrupted bulk failures stay silent

Medium Severity

In the new bulk unarchive and bulk delete loops, any per-thread command that fails with an interrupt-only result is kept in the selection but is excluded from failedCount, so the aggregated summary toast never runs. After a mixed batch, succeeded threads disappear while interrupted ones stay selected with no explanation.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit afd5772. Configure here.

lastErrorMessage = error instanceof Error ? error.message : "An error occurred.";
}
}
}

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.

Bulk actions ignore command interrupts

Medium Severity

In bulk unarchive and bulk delete, when a per-thread command returns an interrupted failure, the loop still processes the rest of the selection. Sidebar multi-delete stops on the first failure, including interrupts. Continuing after an interrupt can delete or unarchive threads the user did not intend to include in the batch.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit afd5772. Configure here.

);
}
})();
}}

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.

Context menu during bulk pending

Medium Severity

While a bulk delete or unarchive is in progress, row checkboxes and per-row Unarchive buttons are disabled, but the archived-thread context menu stays active. A user can still delete or unarchive the same threads in parallel with the in-flight bulk operation, causing overlapping mutations and inconsistent selection or archive state.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit afd5772. Configure here.

Comment on lines +1635 to +1659
const executeBulkDeleteArchivedThreads = useCallback(async () => {
if (selectedArchivedThreadEntries.length === 0 || bulkActionPendingRef.current !== null) {
return;
}
const totalCount = selectedArchivedThreadEntries.length;
const deletedThreadKeys = new Set(selectedArchivedThreadEntries.map((entry) => entry.key));
const unsuccessfulThreadKeys = new Set<string>();
let failedCount = 0;
let lastErrorMessage = "An error occurred.";
bulkActionPendingRef.current = "delete";
setBulkActionPending("delete");
try {
for (const entry of selectedArchivedThreadEntries) {
const result = await deleteThread(entry.threadRef, {
deletedThreadKeys,
ignorePostDeleteCleanupFailure: true,
});
if (result._tag === "Failure") {
unsuccessfulThreadKeys.add(entry.key);
if (!isAtomCommandInterrupted(result)) {
const error = squashAtomCommandFailure(result);
failedCount += 1;
lastErrorMessage = error instanceof Error ? error.message : "An error occurred.";
}
}

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.

🟡 Medium settings/SettingsPanels.tsx:1635

executeBulkDeleteArchivedThreads builds deletedThreadKeys from the full selection and passes that same set to every deleteThread call, even after an earlier delete fails. The failed thread is still in the set, so deleteThread excludes it from the surviving-threads list when computing orphaned worktrees — meaning a worktree still referenced by the failed thread can be incorrectly treated as orphaned and force-removed. Consider removing a thread's key from deletedThreadKeys when its deletion fails so later iterations only account for threads that were actually deleted.

-    const deletedThreadKeys = new Set(selectedArchivedThreadEntries.map((entry) => entry.key));
+    const deletedThreadKeys = new Set(selectedArchivedThreadEntries.map((entry) => entry.key));
     const unsuccessfulThreadKeys = new Set<string>();
     let failedCount = 0;
     let lastErrorMessage = "An error occurred.";
     bulkActionPendingRef.current = "delete";
     setBulkActionPending("delete");
     try {
       for (const entry of selectedArchivedThreadEntries) {
         const result = await deleteThread(entry.threadRef, {
           deletedThreadKeys,
           ignorePostDeleteCleanupFailure: true,
         });
         if (result._tag === "Failure") {
+          deletedThreadKeys.delete(entry.key);
           unsuccessfulThreadKeys.add(entry.key);
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/settings/SettingsPanels.tsx around lines 1635-1659:

`executeBulkDeleteArchivedThreads` builds `deletedThreadKeys` from the full selection and passes that same set to every `deleteThread` call, even after an earlier delete fails. The failed thread is still in the set, so `deleteThread` excludes it from the surviving-threads list when computing orphaned worktrees — meaning a worktree still referenced by the failed thread can be incorrectly treated as orphaned and force-removed. Consider removing a thread's key from `deletedThreadKeys` when its deletion fails so later iterations only account for threads that were actually deleted.

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.

🟡 Medium

When opts.ignorePostDeleteCleanupFailure is true, deleteThread still calls toastManager.add(...) before returning deleteResult. A bulk-delete caller passing this flag expects to handle errors with a single aggregated toast, but each thread that hits a worktree-removal failure still emits its own individual error toast, causing duplicate notifications. Consider guarding the toastManager.add call with !opts.ignorePostDeleteCleanupFailure so the caller owns the failure presentation.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/hooks/useThreadActions.ts around line 327:

When `opts.ignorePostDeleteCleanupFailure` is `true`, `deleteThread` still calls `toastManager.add(...)` before returning `deleteResult`. A bulk-delete caller passing this flag expects to handle errors with a single aggregated toast, but each thread that hits a worktree-removal failure still emits its own individual error toast, causing duplicate notifications. Consider guarding the `toastManager.add` call with `!opts.ignorePostDeleteCleanupFailure` so the caller owns the failure presentation.

@macroscopeapp

macroscopeapp Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces a new feature (bulk delete/unarchive for archived threads) with substantial new logic and state management. Additionally, there are 5 unresolved medium-severity review comments identifying bugs in the bulk action error handling, selection state, and race conditions that should be addressed before merging.

You can customize Macroscope's approvability policy. Learn more.

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant