Skip to content

feat: add filter-based mass selection via search modal#12901

Open
RobinAngele wants to merge 35 commits intonextcloud:mainfrom
RobinAngele:feat/filter-mass-select
Open

feat: add filter-based mass selection via search modal#12901
RobinAngele wants to merge 35 commits intonextcloud:mainfrom
RobinAngele:feat/filter-mass-select

Conversation

@RobinAngele
Copy link
Copy Markdown

@RobinAngele RobinAngele commented May 7, 2026

Depends on: #12900

Closes #4285
Closes #7880
Refs #6070
Refs #7276
Refs #11526

Summary

Adds "Select all matching" as a primary button in the search dialog, allowing users to load every message matching the current filter across all pages and select them for bulk actions — completing the feature requested in #4285 and #7880.

Built on #12900 which centralised selection state in Mailbox.

Also adds full Priority inbox support: each section (Favorites, Follow-up, Important, Other) gets its own select-all flow with mutual exclusion — selecting in one section automatically clears any selection in a sibling section, since each section has an independent action toolbar and cross-section operations are not possible.

Screenshots

Search modal — new "Select all matching" primary button

Screenshot_20260507_114235_select_feat_Search parameters_ovelay

Mass-select result — N selected with action toolbar

Screenshot_20260510_102704_selft_filter_with_result_background

Priority inbox mutual exclusion — section cleared toast

Screenshot_20260510_103559_toast_priority_mailbox

Architecture

Component interaction

User clicks "Select all matching" in search modal
        │
        ▼
SearchMessages.vue
  emits 'select-all-matching' (query string)
        │
        ▼
MailboxThread.vue
  sets this.searchQuery = query          ← must happen before bus emit
  emits bus('select-all-matching')       ← broadcast to all Mailbox instances
        │
        ├─────────────────────────────────────────────┐
        ▼                                             ▼
Mailbox.vue (regular folder)          Mailbox.vue × 4 (Priority inbox)
  owns selection[]                      each owns its own selection[]
  fetches all pages, caps at 500        shares one mitt bus
  selects flatEnvelopeList              each fetches its own filtered pages
        │                               mutual exclusion via 'section-selected'
        ▼                                             │
EnvelopeList.vue                                      ▼
  receives selection[] as prop          EnvelopeList.vue (per section)
  emits update:selection (delta)          receives selection[] as prop
  emits select-range (flat indices)       emits update:selection / select-range

State flows down via props, changes flow up via Vue events, and cross-instance coordination uses the shared mitt bus.


Key design decisions

The regular folder flow (left side) is straightforward — one Mailbox, one bus listener, a linear page fetch. The Priority inbox (right side) is where the complexity lives: 4 Mailbox instances share one bus with no direct references between them, each computing a different filtered query. The decisions below address the non-obvious coordination problems this creates.

1. Capturing the settled query before loading
Priority inbox sections inject a query suffix one Vue render tick after the parent's searchQuery prop changes. Starting mass-select immediately would use a stale query and corrupt the cache key. The fix is to raise a lock flag (loadingAllMatching) before yielding — which simultaneously blocks the searchQuery watcher from triggering a competing reload — then read the settled, section-filtered query after the tick.

2. Mutual exclusion between sections
Each section has its own independent action toolbar, so a selection spanning two sections can't be acted upon in a single bulk operation. When a section's selection goes from empty to non-empty (user-initiated), it broadcasts a section-selected event on the shared bus. Any sibling holding a selection clears itself with an info toast.

3. Concurrent mass-selects must not interfere
The search modal triggers mass-select on all 4 sections simultaneously — each independently loads its own filtered pages. Mutual exclusion must not fire on completion: each finishing section would otherwise clear the others' just-loaded selections. So section-selected is emitted only on user-initiated transitions, never at the end of a programmatic mass-load.

4. OOM protection: 500-message cap
A unified mailbox fans out to N IMAP accounts per page fetch. Without a ceiling, large multi-account selections exhaust client memory. The loop halts at 500 and shows a visible orange warning rather than silently truncating.

5. Keeping the spinner visible during mass-select
Mass-select forces a cache invalidation to guarantee a fresh fetch. Without a guard, this triggers LoadingSkeleton, hiding the spinner mid-operation. A !loadingAllMatching condition in loadEnvelopes() suppresses the skeleton so the spinner stays visible throughout.

Changes

SearchMessages.vue

  • Added "Select all matching" as a primary NcDialog button; "Search" demoted to tertiary
  • selectAllMatching() sets match = 'allof', closes the modal, then emits select-all-matching with the full computed query string — intentionally no sendQueryEvent() to avoid a concurrent reload
  • closeSearchModal() has an idempotency guard — safe to call multiple times
  • Fixed filterData.mentions: coerces false to '' so mentions:false is never appended to the IMAP query string

MailboxThread.vue

  • Wires @select-all-matching="onSelectAllMatching" on SearchMessages
  • onSelectAllMatching(query) sets this.searchQuery = query before this.bus.emit('select-all-matching') so each Priority inbox section reads the correct parent query when it captures effectiveQuery after its own $nextTick

Mailbox.vue

  • isPriorityInbox prop — controls banner visibility and mutual exclusion behaviour
  • loadingAllMatching / selectAllMatching / selectionLimitReached data flags
  • onBusSelectAllMatching() — full mass-select lifecycle: raises flag, yields tick, clears cache, fetches all pages, caps at 500, selects all
  • loadEnvelopes()!loadingAllMatching guard suppresses LoadingSkeleton during mass-select
  • selection watcher — emits section-selected on 0 → non-zero transition, guarded by !loadingAllMatching
  • onBusSectionSelected(activeQuery) — clears sibling section selection with info toast; self-guard via activeQuery !== this.searchQuery
  • selectAllMatchingAction() — in-page banner entry point (calls onBusSelectAllMatching() directly, no bus round-trip)
  • selectAllLabel computed — context-aware label:
Condition Label
loadingAllMatching Selecting messages…
selectMode N selected
hasFilter Select N matching messages
!endReached && count > 0 Select N loaded messages
(default) Select all N messages
  • select-all-banner — prompts to load all matching when all visible are selected and more exist; hidden for Priority inbox
  • select-all-limit-warning — shown in warning colour when 500-cap is hit
  • onUpdateSelection() — merged into single array assignment (removes intermediate empty reactive state)
  • unselectAll() — also clears loadingAllMatching and expanded
  • bus.on/off('section-selected', ...) symmetric in created/unmounted
  • Imports priorityImportantQuery / priorityOtherQuery constants instead of hardcoding strings in hasFilter

EnvelopeList.vue

  • _localToggleInProgress guard on both selection and sortedEnvelops watchers — prevents watchers from overwriting flags.selected during a local click; reset via $nextTick to survive Vue's watcher flush

How to test

  • Open any folder → click the checkbox row → all visible messages selected, action toolbar appears
  • Scroll to the end of a large folder without selecting → label shows "Select N loaded messages"
  • Select all visible → banner appears offering to select all N → click it → all pages load and select
  • Open the search modal → set any filter → click "Select all matching" (primary button) → modal closes, spinner appears, all matching messages across all pages are selected
  • While messages are selected, click individual avatar circles to deselect/reselect one at a time → count updates correctly, no spurious multi-deselect
  • Shift-click across messages in different date groups → range selected correctly
  • Priority inbox → select messages in Important → verify Other section clears automatically with an info toast
  • Priority inbox → select in Important → click individual avatars to add/remove messages within the same section → sibling sections are not affected
  • On a folder with > 500 messages: mass-select stops at 500 and shows the cap warning in orange
  • Bulk actions (delete, mark read, move, tag) apply correctly to all selected messages

Known limitations

  • No cross-section bulk actions in Priority inbox. Selecting in Important and then clicking in Other clears the first section. There is no toolbar that spans sections, so a cross-section selection cannot be acted upon.
  • 500-message cap. Raising it requires backend pagination performance improvements first.
  • No server-side "select all" flag. Bulk operations iterate over the selection array and fire individual API calls. A future improvement would be a server-side endpoint accepting a filter query and performing the operation without enumerating IDs client-side

RobinAngele and others added 3 commits May 7, 2026 11:07
…ctions

- Add missing ImportantIcon import and component registration in
  EnvelopeList so the 'Mark as important' icon renders during
  bulk selection
- Fix favorite/unfavorite bulk action logic: rename methods to
  favoriteAll/unfavoriteAll and use explicit favFlag values
  (true/false) instead of inverted computed checks that failed
  when all selected messages shared the same state

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Add a 'Select all X messages' checkbox above the envelope list using
NcCheckboxRadioSwitch from @nextcloud/vue, allowing users to select
all visible messages at once.

Lift envelope selection state from individual EnvelopeList instances
up to the Mailbox parent component. This enables:
- Cross-group shift-click range selection via flat envelope indexing
- Consistent selection state across grouped envelope lists
- Global select-all / unselect-all from the parent level

Also pass the selection array as a prop to EnvelopeList and add
proper event handling for update:selection and select-range.

Closes: nextcloud#4285
Refs: nextcloud#7880, nextcloud#6070, nextcloud#7276

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Add a 'Select all matching' button to the search parameters dialog
and enable selecting all messages matching a filter across all pages.

Search modal:
- New 'Select all matching' button in SearchMessages.vue dialog
- Emits 'select-all-matching' event via the MailboxThread bus

Mass loading:
- Mailbox.vue onBusSelectAllMatching forces a fresh load, then
  iterates loadMore() until all pages are fetched
- Spinner + 'Selecting messages…' shown during loading
- Checkbox disabled while loading

Context-aware labels:
- 'Select N loaded messages' when more pages exist
- 'Select N matching messages' when a filter is active
- 'Select all N messages' when all pages are loaded
- Hint row: 'Scroll down to include more messages, use filter to
  refine, or click an avatar circle to select one at a time'

Depends on: nextcloud#12900 (select-all checkbox feature)
Closes: nextcloud#4285, nextcloud#7880
Refs: nextcloud#6070, nextcloud#7276, nextcloud#11526

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
RobinAngele added 3 commits May 8, 2026 08:01
Use selectMode (any selection) instead of allSelected (must match all
visible) for the checkbox checked state. This way the checkbox stays
checked when the user manually deselects individual messages via
avatar circles after using Select All.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
…s active

When a filter is already applied, the hint now shows:
'Scroll down to include more messages or click an avatar circle
to select one at a time' — removing the redundant filter advice.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
When in selectMode, the checkbox label now shows '{N} selected'
with the real count instead of the loaded count. This way when
the user deselects individual messages, the number updates
immediately from 20 to 19, etc.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

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

This PR lifts selection into Mailbox, adds a select-all UI and cross-page “select all matching” flow, converts EnvelopeList to prop-driven selection with flatIndex for global ranges, delegates range selection to Mailbox, updates favorite/unfavorite bulk actions, wires SearchMessages and MailboxThread to emit selection events, refactors envelope shift-click handling, and adds documentation and UI styles.

Suggested reviewers

  • kesselb
  • GretaD
  • ChristophWurst
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add filter-based mass selection via search modal' directly describes the main feature added—enabling users to select all matching messages through the search dialog.
Linked Issues check ✅ Passed The PR successfully addresses both #4285 and #7880 by implementing a select-all feature that allows bulk operations (move, delete) on all messages matching filter criteria across pages.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing filter-based mass selection: SearchMessages adds the select-all button, EnvelopeList handles selection state, Mailbox coordinates pagination/loading, and Envelope refines shift-click behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Mailbox.vue (1)

317-335: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset pagination and bulk-selection state when the source list changes.

These watchers clear selection, but they leave endReached / expanded behind and only partially reset the select-all-matching flags. If the previous view had already reached the end, the next mailbox/filter/sort starts in a stale “fully loaded” state, which can hide further pages and prevent the new result set from using the intended bulk-select flow.

Suggested reset
 		mailbox() {
 			this.selection = []
+			this.selectAllMatching = false
+			this.loadingAllMatching = false
+			this.endReached = false
+			this.expanded = false
 			this.loadEnvelopes()
 				.then(() => {
 					logger.debug(`syncing mailbox ${this.mailbox.databaseId} (${this.query}) after folder change`)
 					this.sync(false)
 				})
 		},

 		searchQuery() {
 			this.selection = []
 			this.selectAllMatching = false
 			this.loadingAllMatching = false
+			this.endReached = false
+			this.expanded = false
 			this.loadEnvelopes()
 		},

 		sortOrder() {
 			this.selection = []
+			this.selectAllMatching = false
+			this.loadingAllMatching = false
+			this.endReached = false
+			this.expanded = false
 			this.loadEnvelopes()
 		},
🤖 Prompt for 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.

In `@src/components/Mailbox.vue` around lines 317 - 335, The watchers mailbox(),
searchQuery(), and sortOrder() currently clear selection but leave pagination
and bulk-selection state stale; update each watcher to also reset endReached
(set to false), clear expanded state (reset the expanded map/array), and set
selectAllMatching and loadingAllMatching to false before calling loadEnvelopes()
(and before sync in mailbox()), so the new mailbox/filter/sort starts with fresh
pagination and bulk-selection state; reference the mailbox(), searchQuery(), and
sortOrder() watcher handlers and the component properties endReached, expanded,
selectAllMatching, loadingAllMatching, selection, loadEnvelopes(), and sync().
🤖 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 `@src/components/MailboxThread.vue`:
- Around line 607-609: The onSelectAllMatching handler currently emits a generic
'select-all-matching' on the shared mitt bus causing every Mailbox subscriber to
react; change it to emit a scoped payload (e.g., include mailboxId or query) or
target the specific Mailbox directly. Update the onSelectAllMatching method to
call this.bus.emit('select-all-matching', { mailboxId: this.mailboxId, query:
this.currentQuery }) (or invoke a direct method on the intended Mailbox
instance) and update Mailbox subscribers to check the payload (e.g., mailboxId
or query) before starting load/select so only the intended mailbox responds.
Ensure the event name remains 'select-all-matching' but listeners in Mailbox use
the identifying field to ignore unrelated events.

In `@src/components/SearchMessages.vue`:
- Around line 578-584: The selectAllMatching() method currently calls
sendQueryEvent() and emits 'select-all-matching' inside a single
this.$nextTick(), which can let Mailbox receive a stale
MailboxThread.searchQuery prop; change the flow in selectAllMatching to call
this.sendQueryEvent() inside the first this.$nextTick(), then call a second
this.$nextTick() before emitting this.$emit('select-all-matching') so
Mailbox.onBusSelectAllMatching() receives the updated prop value and avoids
corrupting the cache key logic.

---

Outside diff comments:
In `@src/components/Mailbox.vue`:
- Around line 317-335: The watchers mailbox(), searchQuery(), and sortOrder()
currently clear selection but leave pagination and bulk-selection state stale;
update each watcher to also reset endReached (set to false), clear expanded
state (reset the expanded map/array), and set selectAllMatching and
loadingAllMatching to false before calling loadEnvelopes() (and before sync in
mailbox()), so the new mailbox/filter/sort starts with fresh pagination and
bulk-selection state; reference the mailbox(), searchQuery(), and sortOrder()
watcher handlers and the component properties endReached, expanded,
selectAllMatching, loadingAllMatching, selection, loadEnvelopes(), and sync().
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 495a2697-7608-4c97-89cd-216e17ceac3f

📥 Commits

Reviewing files that changed from the base of the PR and between 217ec4d and cf302c5.

📒 Files selected for processing (4)
  • src/components/EnvelopeList.vue
  • src/components/Mailbox.vue
  • src/components/MailboxThread.vue
  • src/components/SearchMessages.vue

Comment thread src/components/MailboxThread.vue Outdated
Comment thread src/components/SearchMessages.vue Outdated
RobinAngele added 2 commits May 8, 2026 09:30
Added _localToggleInProgress guard that prevents the selection
watcher from overwriting flags.selected during a local click
toggle. This fixes a bug where clicking one avatar to deselect
would sometimes remove multiple messages at once.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
When loadingAllMatching is true, selectAllLabel now returns
'Selecting messages…' instead of the contextual label
(matching/loaded/all). This prevents showing stale filter
labels while loading is in progress.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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/components/Mailbox.vue (2)

830-846: ⚖️ Poor tradeoff

Consider adding a cancellation mechanism for long-running operations.

The while (!this.endReached) loop could run for a very long time on large mailboxes, especially over slow connections. If the user changes the filter, navigates away, or the component unmounts during loading, the loop continues until completion.

Consider:

  1. Adding an abort flag checked in the loop
  2. Implementing a page limit (e.g., max 50 pages)
  3. Checking component mount state before state updates
♻️ Example cancellation pattern
 async selectAllMatchingAction() {
     this.loadingAllMatching = true
     this.selectAllMatching = true
+    this._selectAllAborted = false

     try {
         // Load remaining pages until all envelopes are fetched
-        while (!this.endReached) {
+        while (!this.endReached && !this._selectAllAborted) {
             await this.loadMore()
         }
+        if (this._selectAllAborted) {
+            return
+        }
         // Now select all loaded envelopes
         this.selection = this.flatEnvelopeList.map((e) => e.databaseId)
     } catch (error) {
         logger.error('Failed to load all matching envelopes', { error })
     } finally {
         this.loadingAllMatching = false
     }
 },

Then set this._selectAllAborted = true in unselectAll() and watchers that reset state.

🤖 Prompt for 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.

In `@src/components/Mailbox.vue` around lines 830 - 846, The
selectAllMatchingAction can run indefinitely; add a cancellable pattern:
introduce a boolean flag (e.g. this._selectAllAborted) and a maxPage cap (e.g.
const MAX_SELECT_PAGES = 50) and modify selectAllMatchingAction to check both
the abort flag and page cap inside the while (!this.endReached) loop before each
await this.loadMore() and break if triggered; ensure you check the abort flag
before assigning this.selection or calling this.flatEnvelopeList, and clear
loadingAllMatching on abort; set this._selectAllAborted = true from
unselectAll(), relevant filter-change watchers, and the component
beforeDestroy/unmounted hook so the in-flight loop exits and avoids updating
state after unmount.

36-48: 💤 Low value

Dead code: loading icon inside the banner will never render.

The banner's v-if condition includes !selectAllMatching, but loadingAllMatching and selectAllMatching are always set together (both in selectAllMatchingAction and onBusSelectAllMatching). When loadingAllMatching is true, selectAllMatching is also true, so the banner won't render and the NcLoadingIcon on line 39 is unreachable.

Consider removing the loading icon from the banner or adjusting the banner's visibility condition if loading state should be shown here.

♻️ Suggested fix
 <div
     v-if="allSelected && !selectAllMatching && flatEnvelopeList.length < totalEnvelopeCount"
     class="select-all-banner">
-    <NcLoadingIcon v-if="loadingAllMatching" :size="16" />
     <span>{{ n('mail',
🤖 Prompt for 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.

In `@src/components/Mailbox.vue` around lines 36 - 48, The banner's v-if
(allSelected && !selectAllMatching && flatEnvelopeList.length <
totalEnvelopeCount) prevents loadingAllMatching from ever being visible because
selectAllMatching is set alongside loadingAllMatching in selectAllMatchingAction
and onBusSelectAllMatching; either remove the unreachable NcLoadingIcon or
change the banner condition to allow the loading state — e.g. update the v-if to
include loadingAllMatching (allSelected && (loadingAllMatching ||
!selectAllMatching) && flatEnvelopeList.length < totalEnvelopeCount) so
NcLoadingIcon can render while select-all is in progress, or simply delete the
NcLoadingIcon element if you prefer not to show loading there.
🤖 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 `@src/components/Mailbox.vue`:
- Around line 21-28: The checkbox is listening to the wrong event: change the
event handler on the NcCheckboxRadioSwitch from `@update`:checked to
`@update`:model-value so toggles actually invoke your handlers; update the
component invocation that references selectMode and the methods selectAll() /
unselectAll() to use `@update`:model-value="selectMode ? unselectAll() :
selectAll()" so the v-model update triggers those functions.

---

Nitpick comments:
In `@src/components/Mailbox.vue`:
- Around line 830-846: The selectAllMatchingAction can run indefinitely; add a
cancellable pattern: introduce a boolean flag (e.g. this._selectAllAborted) and
a maxPage cap (e.g. const MAX_SELECT_PAGES = 50) and modify
selectAllMatchingAction to check both the abort flag and page cap inside the
while (!this.endReached) loop before each await this.loadMore() and break if
triggered; ensure you check the abort flag before assigning this.selection or
calling this.flatEnvelopeList, and clear loadingAllMatching on abort; set
this._selectAllAborted = true from unselectAll(), relevant filter-change
watchers, and the component beforeDestroy/unmounted hook so the in-flight loop
exits and avoids updating state after unmount.
- Around line 36-48: The banner's v-if (allSelected && !selectAllMatching &&
flatEnvelopeList.length < totalEnvelopeCount) prevents loadingAllMatching from
ever being visible because selectAllMatching is set alongside loadingAllMatching
in selectAllMatchingAction and onBusSelectAllMatching; either remove the
unreachable NcLoadingIcon or change the banner condition to allow the loading
state — e.g. update the v-if to include loadingAllMatching (allSelected &&
(loadingAllMatching || !selectAllMatching) && flatEnvelopeList.length <
totalEnvelopeCount) so NcLoadingIcon can render while select-all is in progress,
or simply delete the NcLoadingIcon element if you prefer not to show loading
there.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e480ccd4-a6cf-4ccc-9a40-65b3f878b3f0

📥 Commits

Reviewing files that changed from the base of the PR and between cf302c5 and 43e3fb3.

📒 Files selected for processing (2)
  • src/components/EnvelopeList.vue
  • src/components/Mailbox.vue

Comment thread src/components/Mailbox.vue
Vue 2 batches watchers at end of tick. The previous synchronous
reset meant the watcher would fire after _localToggleInProgress
was already false, re-introducing the race condition.

Now reset via $nextTick so the guard remains active during
the watcher's flush.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/components/EnvelopeList.vue (1)

393-404: 💤 Low value

Consider removing redundant cleanup code.

The new cleanup logic (lines 395-399) correctly removes vanished envelope IDs from selection and emits the cleaned array. The older differenceWith cleanup (lines 400-403) sets flags.selected = false on envelopes that are no longer in sortedEnvelops, which has no practical effect since those envelopes are already removed from the rendered list. This code could be removed for clarity.

♻️ Suggested cleanup
 		sortedEnvelops(newVal, oldVal) {
 			// Unselect vanished envelopes by emitting cleaned selection
 			const newIds = new Set(newVal.map((env) => env.databaseId))
 			const cleanedSelection = this.selection.filter((id) => newIds.has(id))
 			if (cleanedSelection.length !== this.selection.length) {
 				this.$emit('update:selection', cleanedSelection, this.envelopes)
 			}
-			differenceWith((a, b) => a.databaseId === b.databaseId, oldVal, newVal)
-				.forEach((env) => {
-					env.flags.selected = false
-				})
 		},
🤖 Prompt for 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.

In `@src/components/EnvelopeList.vue` around lines 393 - 404, Remove the redundant
cleanup in the sortedEnvelops watcher: the initial block that builds newIds,
computes cleanedSelection from this.selection and emits update:selection already
cleans vanished IDs, so delete the subsequent differenceWith(...).forEach(env =>
env.flags.selected = false) code path (referenced as differenceWith in the
sortedEnvelops watcher which manipulates env.flags.selected) to avoid
dead/unused state changes while keeping the selection cleanup and
this.$emit('update:selection', cleanedSelection, this.envelopes) intact.
🤖 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.

Nitpick comments:
In `@src/components/EnvelopeList.vue`:
- Around line 393-404: Remove the redundant cleanup in the sortedEnvelops
watcher: the initial block that builds newIds, computes cleanedSelection from
this.selection and emits update:selection already cleans vanished IDs, so delete
the subsequent differenceWith(...).forEach(env => env.flags.selected = false)
code path (referenced as differenceWith in the sortedEnvelops watcher which
manipulates env.flags.selected) to avoid dead/unused state changes while keeping
the selection cleanup and this.$emit('update:selection', cleanedSelection,
this.envelopes) intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 32a554d8-9c2d-4cd5-bfdc-7a6234f76eed

📥 Commits

Reviewing files that changed from the base of the PR and between 43e3fb3 and 39d0247.

📒 Files selected for processing (1)
  • src/components/EnvelopeList.vue

Added event.shiftKey check in onSelectMultiple to prevent
range selection from firing on plain clicks. Also passes
$event from template to the handler.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Envelope.vue (1)

1-4: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update SPDX header to the required repository format.

Line 2 and Line 3 do not match the mandatory SPDX strings for Vue files in this repository (year/text and identifier key).

🔧 Proposed fix
 <!--
-  - SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
-  - SPDX-License-Identifier: AGPL-3.0-or-later
+  - SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+  - SPDX-Identifier: AGPL-3.0-or-later
 -->

As per coding guidelines "{lib,src,tests}/**/*.{php,js,vue}: Every file must include an SPDX license header" and "SPDX license header format must be: SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors and SPDX-Identifier: AGPL-3.0-or-later".

🤖 Prompt for 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.

In `@src/components/Envelope.vue` around lines 1 - 4, Update the SPDX header in
Envelope.vue to the repository-mandated format by replacing the existing two
header lines with the exact required strings: "SPDX-FileCopyrightText: 2026
Nextcloud GmbH and Nextcloud contributors" and "SPDX-Identifier:
AGPL-3.0-or-later"; locate the existing header comment at the top of
src/components/Envelope.vue and swap those two lines so the file matches the
project's SPDX header rules.
🤖 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.

Outside diff comments:
In `@src/components/Envelope.vue`:
- Around line 1-4: Update the SPDX header in Envelope.vue to the
repository-mandated format by replacing the existing two header lines with the
exact required strings: "SPDX-FileCopyrightText: 2026 Nextcloud GmbH and
Nextcloud contributors" and "SPDX-Identifier: AGPL-3.0-or-later"; locate the
existing header comment at the top of src/components/Envelope.vue and swap those
two lines so the file matches the project's SPDX header rules.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f1de2789-e604-4606-890e-aa1cb989082a

📥 Commits

Reviewing files that changed from the base of the PR and between 39d0247 and 2453c01.

📒 Files selected for processing (1)
  • src/components/Envelope.vue

…parison

Changed hasFilter check from !== 'match:allof' to .length > 12.
This handles cases where closeSearchModal emits a stale query
with residual whitespace or parameters. The default match:allof
query is exactly 12 chars; anything longer has a real filter.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
@RobinAngele RobinAngele force-pushed the feat/filter-mass-select branch from d96ac21 to c5a8cfd Compare May 8, 2026 08:33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Mailbox.vue (1)

323-341: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset pagination and mass-selection flags when the result set changes.

These watchers clear selection, but they leave endReached/expanded behind, and mailbox/sortOrder also leave the bulk-selection flags behind. After fully loading one folder or filter, the next one can start with endReached = true, which hides pagination and makes the new select-all flow stop early.

Suggested fix
 watch: {
 	mailbox() {
-		this.selection = []
+		this.unselectAll()
+		this.endReached = false
+		this.expanded = false
 		this.loadEnvelopes()
 			.then(() => {
 				logger.debug(`syncing mailbox ${this.mailbox.databaseId} (${this.query}) after folder change`)
 				this.sync(false)
 			})
 	},

 	searchQuery() {
-		this.selection = []
-		this.selectAllMatching = false
-		this.loadingAllMatching = false
+		this.unselectAll()
+		this.endReached = false
+		this.expanded = false
 		this.loadEnvelopes()
 	},

 	sortOrder() {
-		this.selection = []
+		this.unselectAll()
+		this.endReached = false
+		this.expanded = false
 		this.loadEnvelopes()
 	},
 },
🤖 Prompt for 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.

In `@src/components/Mailbox.vue` around lines 323 - 341, The mailbox(),
searchQuery(), and sortOrder() watchers currently only clear selection but must
also reset pagination and mass-selection flags to avoid carrying state into the
next result set; update each watcher (mailbox(), searchQuery(), sortOrder()) to
set endReached = false and expanded = false and also clear selectAllMatching =
false and loadingAllMatching = false before calling loadEnvelopes()/sync(),
ensuring any pagination cursor or offset state used by loadEnvelopes() is
effectively reset for the new folder/filter.
🤖 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 `@src/components/Mailbox.vue`:
- Around line 37-47: The banner/button currently uses totalEnvelopeCount as if
it were the full match total even when endReached is false; update the template
logic around the banner (the v-if using allSelected, selectAllMatching and
flatEnvelopeList.length < totalEnvelopeCount) and the copy/CTA (the span,
NcButton and their use of totalEnvelopeCount) so that exact "all matching"
counts are only shown when endReached === true—otherwise show a non-exact CTA
like "Select all visible messages" or "Select all matching messages" (no exact
number) and, if desired, show a progressive "Select all {visible} on this page"
with a secondary loader state (loadingAllMatching) that then resolves to the
exact count when endReached becomes true; apply the same change to the second
identical block that uses the same variables and selectAllMatchingAction.
- Around line 831-846: The selectAllMatchingAction loop can spin indefinitely
when loadMore() throws because endReached never flips; update
selectAllMatchingAction so that if loadMore() rejects you break the while loop
(or set endReached=true) before rethrowing or logging. Specifically, inside
selectAllMatchingAction around the while (!this.endReached) { await
this.loadMore() } call handle errors from loadMore() (or wrap each await
this.loadMore() in its own try/catch) and on error log via logger.error and then
break the loop so the method can proceed to set this.selection from
this.flatEnvelopeList and finish (preserve the existing finally that clears
this.loadingAllMatching and leave this.selectAllMatching true/false behavior
unchanged).

---

Outside diff comments:
In `@src/components/Mailbox.vue`:
- Around line 323-341: The mailbox(), searchQuery(), and sortOrder() watchers
currently only clear selection but must also reset pagination and mass-selection
flags to avoid carrying state into the next result set; update each watcher
(mailbox(), searchQuery(), sortOrder()) to set endReached = false and expanded =
false and also clear selectAllMatching = false and loadingAllMatching = false
before calling loadEnvelopes()/sync(), ensuring any pagination cursor or offset
state used by loadEnvelopes() is effectively reset for the new folder/filter.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a525e38-36d4-4e8e-8869-8d36f335f18c

📥 Commits

Reviewing files that changed from the base of the PR and between 2453c01 and d96ac21.

📒 Files selected for processing (1)
  • src/components/Mailbox.vue

Comment thread src/components/Mailbox.vue Outdated
Comment thread src/components/Mailbox.vue Outdated
@ChristophWurst
Copy link
Copy Markdown
Member

Sorry if the review rabbit caused any confusion. We enabled the integration but the config to turn auto reviews off was missing.

RobinAngele added 6 commits May 8, 2026 10:43
After a filter loads all pages, endReached stays true. When the
filter is cleared, loadEnvelopes() is called but doesn't reset
endReached, causing the label to skip the 'loaded' state and
show the default 'Select all N messages' instead.

Now the searchQuery watcher resets endReached before reloading.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
The previous .length > 12 check could fail if the default
match:allof query had extra whitespace or encoding artifacts.
Now uses /^match:allof\s*$/ regex for robust detection.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
mentionsMe is a boolean. After resetFilter(), it's false.
filterData returned the raw boolean, and searchQuery builder
added 'mentions:false' to the query because it only skipped
empty strings and null, not false.

Now returns empty string when mentionsMe is false.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Previously only loadingAllMatching prevented stale labels.
Now loadingEnvelopes (normal filter/mailbox change) also
shows 'Loading…' and disables the checkbox, preventing
stale counts from appearing during async loads.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
The sortedEnvelops watcher could fire during a local toggle
and clear flags.selected on active envelopes via differenceWith.
Added _localToggleInProgress guard to prevent this.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Normal envelope loading now only changes the label to 'Loading…'
without disabling the checkbox or showing the spinner row.
This prevents the stuck spinner when rapid filter changes cause
overlapping loadEnvelopes calls.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
RobinAngele added 7 commits May 8, 2026 17:44
…hods

- hasFilter as computed (single regex, reused by label + hint)
- closeSearchModal no longer emits (only explicit button clicks)
  Removes _closingProgrammatically guard entirely
- Merged selectAllMatchingAction into onBusSelectAllMatching

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
search-changed updates the searchQuery prop synchronously but Vue
batches prop updates. select-all-matching was firing before the
batch flushed, causing onBusSelectAllMatching to use the old query.

Now wrapped in $nextTick so the new prop value is available.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Instead of reading this.searchQuery prop (which might be stale
due to Vue batching), the query string is now passed through
the entire event chain: SearchMessages -> MailboxThread bus ->
Mailbox. This guarantees the correct query on first click.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
search-changed watcher was resetting loadingAllMatching and
selectAllMatching after onBusSelectAllMatching set them, causing
the spinner and mass selection to abort. Now the watcher checks
this.selectAllMatching and skips reset when the bus handler is
managing the state.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
…tAllMatching

selectAllMatching stays true after mass selection completes,
blocking all future watcher calls. _busHandlerActive is only
true during the bus handler's execution and cleared in finally.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Removing sendQueryEvent from closeSearchModal broke the normal
Search button - filters were never applied. Restored with the
_busHandlerActive guard still protecting against watcher overwrite.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
Vue batches prop updates. loadEnvelopes reads this.searchQuery
which may not be updated yet when onBusSelectAllMatching runs.
Now accepts an optional queryOverride parameter for the bus
handler to pass the correct query directly.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
@RobinAngele
Copy link
Copy Markdown
Author

Tested in production — all scenarios work. The two-event design handles filter changes and mass loading reliably. Ready for review when you have time.
@ChristophWurst

RobinAngele added 6 commits May 10, 2026 10:06
- Add isPriorityInbox prop; hide select-all banner in Priority inbox
  (each section only knows its own count, not the inbox total)
- Add section-selected bus event: selecting in one Priority inbox
  section clears siblings — cross-section bulk actions are not possible
  since each section has an independent toolbar
- Remove section-selected emit from finally block to prevent concurrent
  mass-selects from fighting each other
- Add !loadingAllMatching guard in loadEnvelopes() to suppress
  LoadingSkeleton during mass-select (keeps spinner visible)
- Remove dead loadingEnvelopes branch from selectAllLabel computed
- Merge onUpdateSelection into single array assignment (removes
  intermediate empty reactive state)
- Add select-all-limit-warning CSS with warning colour
- Set searchQuery before bus emit in onSelectAllMatching to ensure
  Priority inbox sections read the correct parent query after $nextTick

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
hasFilter compared against 'is:pi-important' and 'is:pi-other' as
literal strings. If those constants ever change in priorityInbox.js,
hasFilter would silently break. Import and use the exported constants.

AI-assisted: Cline (Claude)
Signed-off-by: RobinAngele <robin@robin4consulting.com>
NcCheckboxRadioSwitch emits update:model-value (Vue 3 v-model contract),
not update:checked. The previous binding silently did nothing when the
checkbox was toggled.

Signed-off-by: RobinAngele <robin@robin4consulting.com>
totalEnvelopeCount returns envelopes.length when endReached is false,
which is the store-cached count and may be lower than the actual total
on the server. The banner button was showing this partial count as if
it were the full match total (e.g. "Select all 40 messages" when there
are actually 200). Remove the count from the banner copy and button
to avoid the mismatch.

Signed-off-by: RobinAngele <robin@robin4consulting.com>
The Other section uses paginate='scroll' (no explicit paginate prop),
so endReached starts false on first load and the label shows
'Select 20 loaded messages'. Since Priority inbox sections have no
banner and mass-select is only available via the search modal, the
'loaded' qualifier is confusing — users have no obvious path to load
more before selecting. Skip the !endReached branch for isPriorityInbox
and fall through to 'Select all N messages'.

Signed-off-by: RobinAngele <robin@robin4consulting.com>
Signed-off-by: RobinAngele <robin@robin4consulting.com>
@RobinAngele
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

✅ Actions performed

Review triggered.

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@Nextcloud_Mail_Select_All_and_Mass_Selection.md`:
- Around line 168-177: Update the documented checkbox examples to match the
merged Mailbox.vue API: replace the deprecated `@update`:checked usage with
`@update`:model-value and show the new behavior where the handler toggles
selectMode to call selectAll() or unselectAll(); specifically reference the
NcCheckboxRadioSwitch component and the selectMode/selectAll/unselectAll
functions so readers understand to wire the checkbox's `@update`:model-value to
toggle selectMode which in turn invokes selectAll() or unselectAll().
- Around line 46-50: Add language identifiers to all unlabeled fenced code
blocks (e.g., the diagram block that starts with "Mailbox └── EnvelopeList (owns
`selection[]` locally)"), using text for ASCII diagrams and select vue, js, or
http for the other code snippets as appropriate; update each fence to start with
```text, ```vue, ```js or ```http so markdownlint MD040 warnings are cleared
across the file (apply the same change to the other unlabeled blocks mentioned
in the review).

In `@src/components/Mailbox.vue`:
- Around line 831-837: The onUpdateSelection merge leaves IDs that no longer
exist in the current visible mailbox, so update the merge to first build a Set
of current visible envelope IDs (e.g., from the component's visible/envelopes
array) and then filter the resulting selection against that set; specifically,
in onUpdateSelection replace the final assignment so it removes any id not
present in visibleIds after merging the filtered old selection and
childSelection, ensuring this.selection contains only IDs currently visible.
- Around line 244-249: flatEnvelopeList currently flattens from the original
source arrays (this.groupEnvelopes / this.envelopesToShow), which is out of sync
with the rendered sort order and causes shift-click range selection mismatches;
change flatEnvelopeList to return the flattened list derived from the
rendered/sorted source (use this.sortedEnvelops or the existing computed that
the UI uses for rendering) instead of the unsorted arrays so selections match
rendered rows — i.e. when a sorted/ordered computed like sortedEnvelops exists
return that flattened list (or map sorted entries to their envelope objects when
grouped), otherwise fall back to this.envelopesToShow.
- Around line 882-912: The mass-select flow in onBusSelectAllMatching sets
loadingAllMatching to true which causes the selection watcher to skip emitting
'section-selected', and it never gets emitted after completion; fix by, at the
end of onBusSelectAllMatching (after the finally sets this.loadingAllMatching =
false), explicitly emit the section-clearing event the watcher would have
emitted (i.e. emit the 'section-selected' event with the appropriate payload to
clear sibling section selection) so the Priority Inbox state is updated;
reference onBusSelectAllMatching, loadingAllMatching, the selection watcher, and
the 'section-selected' event when locating where to add the emit.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 821c2645-d20d-47bb-a8ca-d516cb257b79

📥 Commits

Reviewing files that changed from the base of the PR and between 2453c01 and d53d66e.

📒 Files selected for processing (5)
  • Nextcloud_Mail_Select_All_and_Mass_Selection.md
  • src/components/EnvelopeList.vue
  • src/components/Mailbox.vue
  • src/components/MailboxThread.vue
  • src/components/SearchMessages.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/MailboxThread.vue
  • src/components/SearchMessages.vue

Comment thread Nextcloud_Mail_Select_All_and_Mass_Selection.md Outdated
Comment thread Nextcloud_Mail_Select_All_and_Mass_Selection.md
Comment thread src/components/Mailbox.vue
Comment thread src/components/Mailbox.vue
Comment thread src/components/Mailbox.vue
@RobinAngele
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

✅ Actions performed

Review triggered.

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@src/components/Mailbox.vue`:
- Around line 903-913: The mass-select loop currently breaks on a failed
loadMore(effectiveQuery) but still proceeds to set this.selectAllMatching and
this.selection from this.flatEnvelopeList, which can silently produce a partial
selection and exceed MAX_SELECT_MESSAGES; change the flow so that if
loadMore(...) returns false you immediately clear/disable select-all mode (set
this.selectAllMatching = false), do not populate this.selection from partial
results, and set this.selectionLimitReached = true; additionally, after the
fetch loop always enforce a strict cap by truncating any assignment to
this.selection (e.g., use the first MAX_SELECT_MESSAGES of
this.flatEnvelopeList) and log the cap, and apply the same guard in the later
block that sets selection (the block around lines 916-919) so both places
consistently abort select-all on failed load and enforce the hard
MAX_SELECT_MESSAGES cap.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3db28979-8b9d-4a6a-a7bd-2896b8c76045

📥 Commits

Reviewing files that changed from the base of the PR and between d53d66e and 3ff0e5c.

📒 Files selected for processing (2)
  • Nextcloud_Mail_Select_All_and_Mass_Selection.md
  • src/components/Mailbox.vue

Comment thread src/components/Mailbox.vue
@RobinAngele
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

✅ Actions performed

Review triggered.

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.

RobinAngele and others added 2 commits May 10, 2026 11:52
- flatEnvelopeList now mirrors EnvelopeList sortedEnvelops order so
  shift-click range selection is correct when sortOrder === 'oldest'
- onUpdateSelection prunes phantom IDs via a visibleIds filter so the
  select-all bar can't stay checked after an envelope disappears
- onBusSelectAllMatching emits section-selected after mass-select
  completes so Priority inbox siblings are cleared (the selection watcher
  skips this emit while loadingAllMatching is true)
- loadMore infinite-loop finding: already fixed (returns false on error,
  loop already breaks on false) — no change needed
- Markdown doc: label ASCII-diagram fences as text, update stale
  @update:checked examples to @update:model-value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: RobinAngele <git@robin4consulting.com>
When loadMore() returns false (a page fetch error), the loop now sets a
loadFailed flag and aborts without populating this.selection from the
partial results. This prevents a silent partial selection being shown
as if it were complete.

Also enforce MAX_SELECT_MESSAGES as a hard cap on the final slice so
the selection never exceeds the limit regardless of the loop exit path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: RobinAngele <git@robin4consulting.com>
@RobinAngele RobinAngele force-pushed the feat/filter-mass-select branch from 9f79425 to af4769f Compare May 10, 2026 09:53
@RobinAngele
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

✅ Actions performed

Review triggered.

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.

@RobinAngele RobinAngele force-pushed the feat/filter-mass-select branch from 7fad4f3 to 9d1ac5f Compare May 10, 2026 09:57
The mailbox() and sortOrder() watchers already reset expanded, but
searchQuery() was missing it. After a filter change the component could
start in a stale "expanded" state, causing the cached page to be shown
instead of triggering a fresh fetch.

Signed-off-by: RobinAngele <robin@robin4consulting.com>
@RobinAngele RobinAngele force-pushed the feat/filter-mass-select branch from 9d1ac5f to ccc59be Compare May 10, 2026 09:58
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.

Implement a "Select All" menu item Select all messages in mailbox

2 participants