Skip to content

fix for the list not being updated after emails were deleted in search bar #2611

Open
csfercoci wants to merge 10 commits into
Foundry376:masterfrom
csfercoci:master
Open

fix for the list not being updated after emails were deleted in search bar #2611
csfercoci wants to merge 10 commits into
Foundry376:masterfrom
csfercoci:master

Conversation

@csfercoci
Copy link
Copy Markdown

-issue was that after email were deleted they were still persisted in that selection , in filter

Copilot AI and others added 4 commits February 8, 2026 10:00
… are queued (#2)

* feat: optimistically remove threads from list view when deletion/move tasks are queued

Co-authored-by: csfercoci <3026315+csfercoci@users.noreply.github.com>

* Merge branch 'master' into copilot/fix-message-deletion-ui
@indent-staging
Copy link
Copy Markdown
Contributor

indent-staging Bot commented Feb 14, 2026

Issues

1 potential issue found:

  • Autofix The search perspective's tasksForRemovingItems was changed from per-account logic (archive for Gmail-style accounts, folder move for others) to always using TaskFactory.tasksForMovingToTrash. This changes behavior for Gmail users: removing threads from search results will now trash them instead of archiving, which is a more destructive action and differs from the behavior in other views.
Resolved (4 issues)
  • _onGlobalDeleteKeyDown in thread-list.tsx uses window.addEventListener('keydown', ..., true) (capture phase) and calls preventDefault()/stopPropagation() without checking if the event target is a text input. This intercepts the Delete key in search bars, compose windows, and all text fields, preventing text editing and instead deleting email threads. The existing mousetrap keymap system has a stopCallback that blocks plain keys in text inputs, but this raw handler bypasses it entirely. (fixed by commit ecf0aaa)
  • Undo tasks for ChangeFolderTask incorrectly trigger optimistic removal. _threadIdsForRemovalTask does not check task.isUndo, so when a user undoes a folder move (e.g., undoing "move to trash"), the undo task -- which is also a ChangeFolderTask with the same threadIds -- will optimistically remove threads from the view instead of keeping them. The database reconciliation will restore them, but the user sees threads briefly disappear and reappear. (fixed by commit 483da65)
  • Every ChangeFolderTask triggers optimistic removal regardless of move direction. Tasks that move threads INTO the current view (e.g., "Move to Inbox" while viewing Inbox, or "Mark not spam") will incorrectly remove those threads. The database reconciliation restores them, but causes a visible flicker. (fixed by commit 483da65)
  • Optimistic removal applies to all Thread-type subscriptions, not just the one for the affected folder/label. If multiple Thread views are active (e.g., a search result panel alongside the main thread list), threads will be removed from all of them, even views where the threads should remain visible. (fixed by commit 483da65)

Summary

This PR adds optimistic UI removal of threads from list views when move/delete tasks are queued, adds a global Delete key handler, simplifies search perspective thread removal, and handles non-evaluable matchers in change record processing. The optimistic removal logic was refined across commits to scope removal to matching subscriptions, skip undo tasks, and check move direction.

  • QuerySubscriptionPool listens to Actions.queueTask/Actions.queueTasks and optimistically removes threads from subscriptions whose query matchers match the task's source category (via new _subscriptionMatchesCategory). Undo tasks are skipped.
  • QuerySubscription gains optimisticallyRemoveItemsById and a guard for non-evaluable matchers (search queries) in _processChangeRecords.
  • thread-list.tsx adds a global capture-phase keydown listener for the Delete key that dispatches core:remove-from-view.
  • thread-toolbar-buttons.tsx adds _itemsForRemove helper that falls back to focused thread or selection when props.items is empty.
  • search-mailbox-perspective.tsx simplifies tasksForRemovingItems to always use TaskFactory.tasksForMovingToTrash (previously had per-account archive/trash logic).
  • base.json adds "delete" to the core:remove-from-view keybinding array.
  • Tests added/updated for optimistic removal, subscription scoping, undo handling, and category matching.

CI Checks

Waiting for CI checks...

Rule Checks 3 rules evaluated, 3 passed, 0 failed

Passing rules

Passing This is a longer title to see what happens when they are too long to fit
Passing B
Passing Ben Rule

Autofix All

Comment thread app/src/flux/models/query-subscription-pool.ts Outdated
Comment thread app/src/flux/models/query-subscription-pool.ts Outdated
Comment thread app/src/flux/models/query-subscription-pool.ts Outdated
Comment thread app/internal_packages/thread-list/lib/thread-list.tsx Outdated
return false;
}

tasksForRemovingItems(threads, source?: string) {
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.

Behavioral change for Gmail users: The previous implementation checked account.preferredRemovalDestination() and would archive (remove inbox label) for Gmail-style accounts. This replacement always moves to trash via TaskFactory.tasksForMovingToTrash. For Gmail users, pressing Delete/Backspace on search results will now trash threads instead of archiving them, which is more destructive and inconsistent with the archive-by-default behavior in other views.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm I think this comment is correct, there are sort of separate terms - "remove" vs "trash" vs "archive", and in places where a key binding or helper is tied to "remove", the behavior is meant to be configured by preferredRemovalDestination.

I think for most users clicking the "Backspace" key in a gmail search result list should archive messages and not move them to the trash.

return dataSource.selection.items() as Thread[];
}

return [];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm this is an interesting change, it seems like the thinking here is that if you have both a selection and a focused item, the selection should take precedence? It'd be helpful to have a repro case if you had one in mind - I wonder if this makes more sense in the vertical split view?

I think that this might be a good behavior improvement but it seems odd to do it for just the generic remove action and not the core:archive-item key binding (ArchiveButton) or the TrashButton?

return false;
}

tasksForRemovingItems(threads, source?: string) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm I think this comment is correct, there are sort of separate terms - "remove" vs "trash" vs "archive", and in places where a key binding or helper is tied to "remove", the behavior is meant to be configured by preferredRemovalDestination.

I think for most users clicking the "Backspace" key in a gmail search result list should archive messages and not move them to the trash.

}
}
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is super interesting - If the issue happens specifically with search, it'd be nice to move this logic down into the SearchQuerySubscription subclass, because we use this QuerySubscription class a lot, and for other model types besides threads. I think that the problem may actually be that the SearchQuerySubscription doesn't refresh like others, but I'm not 100% sure? It's been many years :-)

Comment on lines +141 to +146
if (hasNonEvaluableMatchers) {
if (itemIsInSet) {
this._set.updateModel(item);
}
unknownImpacts += 1;
continue;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems like a separate + pretty consequential change, curious what you were seeing here.

In general this is sort of delicate code because it's used for pretty much everything.

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.

3 participants