Uh oh!
There was an error while loading. Please reload this page.
feat(settings): add archived thread bulk actions - #3615
feat(settings): add archived thread bulk actions#3615adityavardhansharma wants to merge 5 commits into
Conversation
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ 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, | ||
| }), | ||
| ); |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit afd5772. Configure here.
| lastErrorMessage = error instanceof Error ? error.message : "An error occurred."; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit afd5772. Configure here.
| ); | ||
| } | ||
| })(); | ||
| }} |
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit afd5772. Configure here.
| 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."; | ||
| } | ||
| } |
There was a problem hiding this comment.
🟡 Mediumsettings/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.
There was a problem hiding this comment.
🟡 Medium
t3code/apps/web/src/hooks/useThreadActions.ts
Line 327 in afd5772
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.
ApprovabilityVerdict: 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. |


What Changed
deletedThreadKeysbehavior so batch deletion stays aligned with the sidebar flow.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
New UI
Video
t3code_bulk.mp4
Checklist
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/deleteThreadper selection, keep failed rows selected, refresh snapshots, and show one summary error toast instead of one per failure. Bulk delete passesdeletedThreadKeysandignorePostDeleteCleanupFailureso 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
unarchiveThreadordeleteThreadper thread, retain failed selections, refresh the archived list, and show aggregated error toasts on failure.archivedThreadSelectionKey,buildArchivedThreadSelectionKeys, andpruneArchivedThreadSelectionin SettingsPanels.logic.ts; pruning keeps selections in sync as the archived snapshot changes.deleteThreadin useThreadActions.ts to acceptignorePostDeleteCleanupFailure, so bulk delete treats post-delete cleanup errors as non-fatal (an error toast is still shown).📊 Macroscope summarized afd5772. 3 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.