Skip to content

[PM-37808] Bulk Selection + Batch Bar for Desktop#21308

Open
nick-livefront wants to merge 15 commits into
mainfrom
vault/pm-37808/desktop-vault-export
Open

[PM-37808] Bulk Selection + Batch Bar for Desktop#21308
nick-livefront wants to merge 15 commits into
mainfrom
vault/pm-37808/desktop-vault-export

Conversation

@nick-livefront

@nick-livefront nick-livefront commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

🎟️ Tracking

PM-37808

📔 Objective

Wires up the vault batch bar (multi-select) for the desktop vault behind the PM37785_VaultBatchBar and PM37785_DesktopVaultBatchBar feature flags.

Includes a desktop-specific BulkDeleteDialogDesktopComponent because the collection deletion logic imports CollectionService from @bitwarden/admin-console, which libs/vault cannot depend on. This prevents the web dialog from being refactored into a shared location, so each app provides its own implementation via the BULK_DELETE_DIALOG injection token.

📸 Screenshots

Screen.Recording.2026-06-24.at.1.18.56.PM.mov

@nick-livefront nick-livefront requested a review from a team as a code owner June 16, 2026 17:01
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the desktop bulk-selection batch bar wiring behind the PM37785_VaultBatchBar and PM37785_DesktopVaultBatchBar feature flags, the new BulkDeleteDialogDesktopAdapter / AssignCollectionsDesktopDialogAdapter, the selection logic in vault-list.component.ts, and the locale additions across browser/web/desktop. The desktop adapters faithfully mirror the already-reviewed web adapters, the feature flags exist and correctly gate the UI, and the new checkbox rows have test coverage.

Code Review Details

No new findings. The two previously posted findings (collections silently dropped; British spelling) were addressed by the author, and the remaining open threads reflect intentional desktop behavior (cipher-only "select all", local-state cleanup after backend deletion) confirmed in discussion.

Notes considered and intentionally not raised as findings:

  • The new { provide: ... } entries in vault.component.ts use raw provider objects rather than safeProvider(), but this matches the file's existing provider convention.
  • confirmAndDeleteMixed and the delete copy strings are an exact copy of the approved web adapter; consistency is preferred over divergence here.

@nick-livefront nick-livefront added the ai-review-vnext Request a Claude code review using the vNext workflow label Jun 16, 2026
@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 2.77778% with 105 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.28%. Comparing base (b1487da) to head (4cb083c).
⚠️ Report is 4 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...tion-dialogs/bulk-delete-dialog-desktop.adapter.ts 0.00% 58 Missing ⚠️
...top/src/vault/app/vault-v3/vault-list.component.ts 0.00% 20 Missing ⚠️
...alogs/assign-collections-desktop-dialog.adapter.ts 0.00% 10 Missing ⚠️
.../desktop/src/vault/app/vault-v3/vault.component.ts 0.00% 9 Missing ⚠️
...t-v3/vault-items/vault-collection-row.component.ts 0.00% 5 Missing ⚠️
libs/vault/src/services/vault-batch-bar.service.ts 0.00% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #21308      +/-   ##
==========================================
- Coverage   49.32%   49.28%   -0.04%     
==========================================
  Files        4096     4098       +2     
  Lines      128869   128974     +105     
  Branches    19776    19814      +38     
==========================================
+ Hits        63567    63568       +1     
- Misses      60592    60692     +100     
- Partials     4710     4714       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jaasen-livefront jaasen-livefront left a comment

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.

Looks great! Just a couple comments. ;)

Comment on lines +89 to +99
if (this.collections.length) {
const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
await this.collectionService.delete(
this.collections.map((c) => c.id as CollectionId),
userId,
);
this.toastService.showToast({
variant: "success",
message: this.i18nService.t("deletedCollections"),
});
}

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.

❓ Is this necessary since we're already awaiting the deleteCollections above?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes it is, a tad bit confusing. It's identical to the web implementation here. The first set of promises is deleting them from the backend and this block here is removing them from the local state.

type="checkbox"
bitCheckbox
[disabled]="disabled() || isEmpty()"
(change)="$event ? toggleAll() : null"

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.

🎨 I believe $event is always truthy so this check shouldn't be required.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

return cipherItems.length > 0 && cipherItems.every((i) => this.selection.isSelected(i));
}

protected toggleAll(): void {

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.

❓ Just to verify we only want to select ciphers here not collections?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Correct, on the desktop there isn't a view that shows you only collections like there is on the web vault. This is more defensive than anything.

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.

I think we can use the opportunity to align to the new dialog designs. I already started with this for the web e1868e7

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added e7fd8c2. You have BulkDeleteService in the apps/web, could it be moved to libs/vault so that I could reuse that as well?

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.

Yeah that makes sense

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.

Moved here 4964f6a

@nick-livefront nick-livefront requested a review from a team as a code owner June 23, 2026 15:59
@willmartian willmartian self-requested a review June 23, 2026 16:02
@nick-livefront nick-livefront added the t:feature Change Type - Feature Development label Jun 24, 2026
Comment on lines +59 to +82
protected readonly submit = async () => {
const deletePromises: Promise<void>[] = [];

if (this.unassignedCiphers.length && this.organization?.canEditUnassignedCiphers) {
deletePromises.push(this.deleteCiphersAdmin(this.unassignedCiphers));
}
if (this.cipherIds.length) {
if (!this.organization || !this.organization.canEditAllCiphers) {
deletePromises.push(this.deleteCiphers());
} else {
deletePromises.push(this.deleteCiphersAdmin(this.cipherIds));
}
}

await Promise.all(deletePromises);

if (this.totalCiphersCount) {
this.toastService.showToast({
variant: "success",
message: this.i18nService.t(this.permanent ? "permanentlyDeletedItems" : "deletedItems"),
});
}
this.close(BulkDeleteDialogResult.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.

CRITICAL: Selected collections are silently dropped — the dialog reports success but never deletes them.

Details and fix

VaultBatchBarService.bulkDelete() passes collections into this dialog (bulk-delete-dialog.token.ts includes collections?: CollectionView[]), and collection rows are individually selectable on desktop (vault-list.component.html wires (checkboxChange)="selection.toggle(item)" on collection rows, and collectionsToDisplay is populated into the data source). canDelete() evaluates collections too, so the delete action is offered when a collection is selected.

However, this component only handles cipherIds / unassignedCiphers. The collections param is never read or deleted. Result:

  • Select a collection (with or without ciphers) → click delete → submit runs, ignores collections, returns BulkDeleteDialogResult.Deleted, selection clears and the list refreshes.
  • If only a collection is selected, totalCiphersCount === 0, so no toast even fires — the user sees the row vanish from the refreshed list region with no error, but the collection still exists on the server.

The web equivalent (apps/web/.../bulk-delete-dialog/bulk-delete-dialog.component.ts) deletes collections via deleteCollections(). The desktop app already imports CollectionService (vault.component.ts:177), so this dialog can implement the same path — read this.dialogData.collections, delete via CollectionService/ApiService.deleteManyCollections, and surface the deletedCollections / collectionsDeleted toast (keys were added in this PR but are currently unused).

Alternatively, if desktop intentionally does not support collection deletion yet, prevent collections from being selectable on desktop so the delete action is never offered for them.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Deletion of collections is not available on the desktop but this was resolved from 267bd0f which implements the same behavior as the web

gbubemismith
gbubemismith previously approved these changes Jun 24, 2026
@nick-livefront nick-livefront marked this pull request as draft June 24, 2026 17:47
@nick-livefront nick-livefront added the desktop Desktop Application label Jun 24, 2026
@nick-livefront nick-livefront marked this pull request as ready for review June 24, 2026 18:21
@nick-livefront

Copy link
Copy Markdown
Collaborator Author

@gbubemismith I merged up with main and resolved some of the translation work that you did your branch.

Comment on lines -1815 to -1823
"copyPrivateKey": {
"message": "Copy private key"
},
"copyPublicKey": {
"message": "Copy public key"
},
"copyFingerprint": {
"message": "Copy fingerprint"
},

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

These were duplicate keys in the file

@BryanCunningham BryanCunningham removed request for a team and BryanCunningham June 26, 2026 13:54
gbubemismith
gbubemismith previously approved these changes Jun 26, 2026
"message": "1 item will be permanently transferred to the selected organization. You will no longer own this item."
},
"personalItemsTransferWarningPlural": {
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organisation. You will no longer own these items.",

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.

🎨 SUGGESTED: British spelling "organisation" is inconsistent with the rest of the en-US locale.

Details and fix

Every other string in this file (and the matching personalItemsTransferWarningPlural key in apps/web/src/locales/en/messages.json:12318) uses the American spelling "organization". This is the only "organisation" in the file.

Suggested change
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organisation. You will no longer own these items.",
"message": "$PERSONAL_ITEMS_COUNT$ items will be permanently transferred to the selected organization. You will no longer own these items.",

This keeps the desktop copy in sync with the web copy for the same key.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@nick-livefront

Copy link
Copy Markdown
Collaborator Author

@gbubemismith - I addressed Claude's comment: #21308 (comment)

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

ai-review-vnext Request a Claude code review using the vNext workflow desktop Desktop Application t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants