Repository: bitwarden/clients
Pull request #14737: [PM-21395] Vault Nudges Bugs
Author: login: Jingo88
Created: 2025-05-12T16:12:16Z | Merged: 2025-05-13T20:49:42Z

## 🎟️ Tracking

[PM-21395](https://bitwarden.atlassian.net/browse/PM-21395) - Update for vault nudges flows
[PM-21396](https://bitwarden.atlassian.net/browse/PM-21396) - Update copy to have bullet list

## 📔 Objective

* Welcome Nudge Spotlight should have bulleted list

Below are the four flows discussed in the ticket for the Vault Nudges

1. New user with empty vault sees import nudge and welcome nudge when Items are added to vault
2. Existing user (Account older than 30 days) with items will NOT see import nudge or welcome nudge
3. Existing user (Account older than 30 days) with an empty vault will see the import nudge, they won't see the welcome nudge when items are added to vault
4. Existing user (Account older than 30 days) with items will NOT see the import nudge or the welcome nudge when deleting all/re-adding data. 

Below are screen recordings for each flow

## 📸 Screen Recordings

1 - New User

https://github.com/user-attachments/assets/d588f3bd-6ff4-4bd8-a755-3c6e4bf7b60f


2 - Existing User No Nudges

https://github.com/user-attachments/assets/dd8f7147-c787-4ae7-beab-3c842da86c63


3 - Existing User Empty Vault

https://github.com/user-attachments/assets/5e26621e-9eae-4849-9454-92a9de334391


4 - Existing User Has Items, Deletes and Re-adds

https://github.com/user-attachments/assets/f1c889d0-4079-406a-aae2-e91577bbf8b5



## ⏰ Reminders before review

- Contributor guidelines followed
- All formatters and local linters executed and passed
- Written new unit and / or integration tests where applicable
- Protected functional changes with optionality (feature flags)
- Used internationalization (i18n) for all UI strings
- CI builds passed
- Communicated to DevOps any deployment requirements
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

## 🦮 Reviewer guidelines

<!-- Suggested interactions but feel free to use (or not) as you desire! -->

- 👍 (`:+1:`) or similar for great changes
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
- ❓ (`:question:`) for questions
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
- 🎨 (`:art:`) for suggestions / improvements
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt
- ⛏ (`:pick:`) for minor or nitpick changes


[PM-21395]: https://bitwarden.atlassian.net/browse/PM-21395?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[PM-21396]: https://bitwarden.atlassian.net/browse/PM-21396?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index dcdfe7df4d61..fa300b4253b7 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -5273,8 +5273,14 @@
   "hasItemsVaultNudgeTitle": {
     "message": "Welcome to your vault!"
   },
-  "hasItemsVaultNudgeBody": {
-      "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else"
+  "hasItemsVaultNudgeBodyOne": {
+    "message": "Autofill items for the current page"
+  },
+  "hasItemsVaultNudgeBodyTwo": {
+    "message": "Favorite items for easy access"
+  },
+  "hasItemsVaultNudgeBodyThree": {
+    "message": "Search your vault for something else"
   },
   "newLoginNudgeTitle": {
     "message": "Save time with autofill"
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html
index 894f27245b2c..b46002d645e7 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html
+++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html
@@ -44,9 +44,13 @@
       <div class="tw-mb-4" *ngIf="showHasItemsVaultSpotlight$ | async">
         <bit-spotlight
           [title]="'hasItemsVaultNudgeTitle' | i18n"
-          [subtitle]="'hasItemsVaultNudgeBody' | i18n"
           (onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.HasVaultItems)"
         >
+          <ul class="tw-pl-4 tw-text-main" bitTypography="body2">
+            <li>{{ "hasItemsVaultNudgeBodyOne" | i18n }}</li>
+            <li>{{ "hasItemsVaultNudgeBodyTwo" | i18n }}</li>
+            <li>{{ "hasItemsVaultNudgeBodyThree" | i18n }}</li>
+          </ul>
         </bit-spotlight>
       </div>
       <vault-at-risk-password-callout
diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts
index 4a8625f982c9..58a6ba0000b1 100644
--- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts
+++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts
@@ -19,10 +19,17 @@ import { JslibModule } from "@bitwarden/angular/jslib.module";
 import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
 import { getUserId } from "@bitwarden/common/auth/services/account.service";
 import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
 import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
 import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
 import { CipherType } from "@bitwarden/common/vault/enums";
-import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components";
+import {
+  ButtonModule,
+  DialogService,
+  Icons,
+  NoItemsModule,
+  TypographyModule,
+} from "@bitwarden/components";
 import {
   DecryptionFailureDialogComponent,
   SpotlightComponent,
@@ -86,6 +93,7 @@ enum VaultState {
     NewSettingsCalloutComponent,
     SpotlightComponent,
     RouterModule,
+    TypographyModule,
   ],
   providers: [VaultPageService],
 })
@@ -158,6 +166,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
     private introCarouselService: IntroCarouselService,
     private vaultNudgesService: VaultNudgesService,
     private router: Router,
+    private i18nService: I18nService,
   ) {
     combineLatest([
       this.vaultPopupItemsService.emptyVault$,
diff --git a/libs/vault/src/components/spotlight/spotlight.component.html b/libs/vault/src/components/spotlight/spotlight.component.html
index 0c6a37914d86..3aa9a09a7e1b 100644
--- a/libs/vault/src/components/spotlight/spotlight.component.html
+++ b/libs/vault/src/components/spotlight/spotlight.component.html
@@ -4,7 +4,13 @@
   <div class="tw-flex tw-justify-between tw-items-start tw-flex-grow">
     <div>
       <h2 bitTypography="h4" class="tw-font-semibold !tw-mb-1">{{ title }}</h2>
-      <p class="tw-text-main tw-mb-0" bitTypography="body2" [innerHTML]="subtitle"></p>
+      <p
+        *ngIf="subtitle"
+        class="tw-text-main tw-mb-0"
+        bitTypography="body2"
+        [innerHTML]="subtitle"
+      ></p>
+      <ng-content *ngIf="!subtitle"></ng-content>
     </div>
     <button
       type="button"
diff --git a/libs/vault/src/components/spotlight/spotlight.component.ts b/libs/vault/src/components/spotlight/spotlight.component.ts
index 8639fe7947f6..a2e2a13a468b 100644
--- a/libs/vault/src/components/spotlight/spotlight.component.ts
+++ b/libs/vault/src/components/spotlight/spotlight.component.ts
@@ -14,7 +14,7 @@ export class SpotlightComponent {
   // The title of the component
   @Input({ required: true }) title: string | null = null;
   // The subtitle of the component
-  @Input({ required: true }) subtitle: string | null = null;
+  @Input() subtitle?: string | null = null;
   // The text to display on the button
   @Input() buttonText?: string;
   // Wheter the component can be dismissed, if true, the component will not show a close button
diff --git a/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts
index 65533daa9b21..3d1617bd8c1f 100644
--- a/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts
+++ b/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts
@@ -28,7 +28,10 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService {
       this.collectionService.decryptedCollections$,
     ]).pipe(
       switchMap(([nudgeStatus, ciphers, orgs, collections]) => {
-        const vaultHasContents = !(ciphers == null || ciphers.length === 0);
+        const filteredCiphers = ciphers?.filter((cipher) => {
+          return cipher.deletedDate == null;
+        });
+        const vaultHasContents = !(filteredCiphers == null || filteredCiphers.length === 0);
         if (orgs == null || orgs.length === 0) {
           return nudgeStatus.hasBadgeDismissed || nudgeStatus.hasSpotlightDismissed
             ? of(nudgeStatus)
diff --git a/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts
index 6b5ac7eba003..bdcf7da59f2a 100644
--- a/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts
+++ b/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts
@@ -1,5 +1,6 @@
 import { inject, Injectable } from "@angular/core";
-import { combineLatest, Observable, switchMap } from "rxjs";
+import { combineLatest, from, Observable, of, switchMap } from "rxjs";
+import { catchError } from "rxjs/operators";
 
 import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
 import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -9,6 +10,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
 import { DefaultSingleNudgeService } from "../default-single-nudge.service";
 import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service";
 
+const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
+
 /**
  * Custom Nudge Service Checking Nudge Status For Welcome Nudge With Populated Vault
  */
@@ -21,27 +24,42 @@ export class HasItemsNudgeService extends DefaultSingleNudgeService {
   logService = inject(LogService);
 
   nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable<NudgeStatus> {
+    const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe(
+      catchError(() => {
+        this.logService.error("Error getting profile creation date");
+        // Default to today to ensure we show the nudge
+        return of(new Date());
+      }),
+    );
+
     return combineLatest([
       this.cipherService.cipherViews$(userId),
       this.getNudgeStatus$(nudgeType, userId),
+      profileDate$,
+      of(Date.now() - THIRTY_DAYS_MS),
     ]).pipe(
-      switchMap(async ([ciphers, nudgeStatus]) => {
-        try {
-          const creationDate = await this.vaultProfileService.getProfileCreationDate(userId);
-          const thirtyDays = new Date(new Date().getTime() - 30 * 24 * 60 * 60 * 1000);
-          const isRecentAcct = creationDate >= thirtyDays;
+      switchMap(async ([ciphers, nudgeStatus, profileDate, profileCutoff]) => {
+        const profileOlderThanCutoff = profileDate.getTime() < profileCutoff;
+        const filteredCiphers = ciphers?.filter((cipher) => {
+          return cipher.deletedDate == null;
+        });
 
-          if (!isRecentAcct || nudgeStatus.hasSpotlightDismissed) {
-            return nudgeStatus;
-          } else {
-            return {
-              hasBadgeDismissed: ciphers == null || ciphers.length === 0,
-              hasSpotlightDismissed: ciphers == null || ciphers.length === 0,
-            };
-          }
-        } catch (error) {
-          this.logService.error("Failed to fetch profile creation date: ", error);
+        if (profileOlderThanCutoff && filteredCiphers.length > 0) {
+          const dismissedStatus = {
+            hasSpotlightDismissed: true,
+            hasBadgeDismissed: true,
+          };
+          // permanently dismiss both the Empty Vault Nudge and Has Items Vault Nudge if the profile is older than 30 days
+          await this.setNudgeStatus(nudgeType, dismissedStatus, userId);
+          await this.setNudgeStatus(VaultNudgeType.EmptyVaultNudge, dismissedStatus, userId);
+          return dismissedStatus;
+        } else if (nudgeStatus.hasSpotlightDismissed) {
           return nudgeStatus;
+        } else {
+          return {
+            hasBadgeDismissed: filteredCiphers == null || filteredCiphers.length === 0,
+            hasSpotlightDismissed: filteredCiphers == null || filteredCiphers.length === 0,
+          };
         }
       }),
     );


Reviews:
user: login: nick-livefront
body: Functionality is working as described, only a accessibility concern with the list markup to address
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-13T15:19:53Z
user: login: Jingo88
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T20:23:42Z
user: login: nick-livefront
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-13T20:47:40Z

Review comments:
diff_hunk: @@ -101,7 +103,13 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
   );
   showHasItemsVaultSpotlight$: Observable<boolean> = this.activeUserId$.pipe(
     switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)),
-    map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed),
+    map((nudgeStatus) => {
+      const lineOne = this.i18nService.t("hasItemsVaultNudgeBodyOne");
+      const lineTwo = this.i18nService.t("hasItemsVaultNudgeBodyTwo");
+      const lineThree = this.i18nService.t("hasItemsVaultNudgeBodyThree");
+      this.hasItemsVaultNudgeBody = `<li>${lineOne}</li><li>${lineTwo}</li><li>${lineThree}</li>`;
path: apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts
user: login: nick-livefront
body: 🎨: The HTML isn't dependent on the `nudgeStatus`, it can be placed inline where `hasItemsVaultNudgeBody` is defined. 

```ts
  hasItemsVaultNudgeBody = `<li>${this.i18nService.t("hasItemsVaultNudgeBodyOne")}</li><li>${this.i18nService.t("hasItemsVaultNudgeBodyTwo")}</li><li>${this.i18nService.t("hasItemsVaultNudgeBodyThree")}</li>`;

```
created_at: 2025-05-13T14:55:29Z
author_association: COLLABORATOR
diff_hunk: @@ -101,7 +103,13 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
   );
   showHasItemsVaultSpotlight$: Observable<boolean> = this.activeUserId$.pipe(
     switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)),
-    map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed),
+    map((nudgeStatus) => {
+      const lineOne = this.i18nService.t("hasItemsVaultNudgeBodyOne");
+      const lineTwo = this.i18nService.t("hasItemsVaultNudgeBodyTwo");
+      const lineThree = this.i18nService.t("hasItemsVaultNudgeBodyThree");
+      this.hasItemsVaultNudgeBody = `<li>${lineOne}</li><li>${lineTwo}</li><li>${lineThree}</li>`;
path: apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts
user: login: nick-livefront
body: ⚠️  Using `li` to display the list causes an accessibility concern as the parent element is not a `ol` or `ul`.  As written the ending markup will be:

```html
<p>
  <li />
  <li />
  <li />
<p>
```


An approach I could see which would require a refactor to the component would be to project the content rather than pass it as an Input. That way each implementation could control the markup desired. But there are probably other approaches as well!

```html
 <bit-spotlight
  [title]="'hasItemsVaultNudgeTitle' | i18n"
  (onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.HasVaultItems)"
>
  <ul>
    <li>"hasItemsVaultNudgeBodyOne" | i18n</li>. 
    <li>"hasItemsVaultNudgeBodyTwo" | i18n</li>. 
    <li>"hasItemsVaultNudgeBodyThree" | i18n</li>
  </ul>
</bit-spotlight>
```
created_at: 2025-05-13T15:17:12Z
author_association: COLLABORATOR
diff_hunk: @@ -9,6 +10,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
 import { DefaultSingleNudgeService } from "../default-single-nudge.service";
 import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service";
 
+const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
path: libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts
user: login: nick-livefront
body: 👍 Good idea moving this to a constant
created_at: 2025-05-13T15:18:00Z
author_association: COLLABORATOR
diff_hunk: @@ -101,7 +103,13 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy {
   );
   showHasItemsVaultSpotlight$: Observable<boolean> = this.activeUserId$.pipe(
     switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)),
-    map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed),
+    map((nudgeStatus) => {
+      const lineOne = this.i18nService.t("hasItemsVaultNudgeBodyOne");
+      const lineTwo = this.i18nService.t("hasItemsVaultNudgeBodyTwo");
+      const lineThree = this.i18nService.t("hasItemsVaultNudgeBodyThree");
+      this.hasItemsVaultNudgeBody = `<li>${lineOne}</li><li>${lineTwo}</li><li>${lineThree}</li>`;
path: apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts
user: login: Jingo88
body: Awesome thanks. that all makes sense. I updated it to take an optional subtitle, and passed in ng-content. looks the same now but mac voiceover navigation is better. 
created_at: 2025-05-13T20:23:42Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-12T16:15:11Z
body: ## [Codecov](https://app.codecov.io/gh/bitwarden/clients/pull/14737?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) Report
Attention: Patch coverage is `11.53846%` with `23 lines` in your changes missing coverage. Please review.
> Project coverage is 36.57%. Comparing base [(`5fb46df`)](https://app.codecov.io/gh/bitwarden/clients/commit/5fb46df3415aefced0b52f2db86c873962255448?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) to head [(`fa23341`)](https://app.codecov.io/gh/bitwarden/clients/commit/fa233418ee97412da54077b41252438e98501374?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).
> Report is 7 commits behind head on main.

:white_check_mark: All tests successful. No failed tests found.

| [Files with missing lines](https://app.codecov.io/gh/bitwarden/clients/pull/14737?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) | Patch % | Lines |
|---|---|---|
| [.../custom-nudges-services/has-items-nudge.service.ts](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&filepath=libs%2Fvault%2Fsrc%2Fservices%2Fcustom-nudges-services%2Fhas-items-nudge.service.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-bGlicy92YXVsdC9zcmMvc2VydmljZXMvY3VzdG9tLW51ZGdlcy1zZXJ2aWNlcy9oYXMtaXRlbXMtbnVkZ2Uuc2VydmljZS50cw==) | 15.78% | [16 Missing :warning: ](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |
| [...lt/popup/components/vault-v2/vault-v2.component.ts](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&filepath=apps%2Fbrowser%2Fsrc%2Fvault%2Fpopup%2Fcomponents%2Fvault-v2%2Fvault-v2.component.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-YXBwcy9icm93c2VyL3NyYy92YXVsdC9wb3B1cC9jb21wb25lbnRzL3ZhdWx0LXYyL3ZhdWx0LXYyLmNvbXBvbmVudC50cw==) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |
| [...ustom-nudges-services/empty-vault-nudge.service.ts](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&filepath=libs%2Fvault%2Fsrc%2Fservices%2Fcustom-nudges-services%2Fempty-vault-nudge.service.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-bGlicy92YXVsdC9zcmMvc2VydmljZXMvY3VzdG9tLW51ZGdlcy1zZXJ2aWNlcy9lbXB0eS12YXVsdC1udWRnZS5zZXJ2aWNlLnRz) | 0.00% | [3 Missing :warning: ](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |
| [...lt/src/components/spotlight/spotlight.component.ts](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&filepath=libs%2Fvault%2Fsrc%2Fcomponents%2Fspotlight%2Fspotlight.component.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-bGlicy92YXVsdC9zcmMvY29tcG9uZW50cy9zcG90bGlnaHQvc3BvdGxpZ2h0LmNvbXBvbmVudC50cw==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/bitwarden/clients/pull/14737?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main   #14737   +/-   ##
=======================================
  Coverage   36.56%   36.57%           
=======================================
  Files        3195     3194    -1     
  Lines       92250    92219   -31     
  Branches    16546    16542    -4     
=======================================
- Hits        33735    33725   -10     
+ Misses      56113    56093   -20     
+ Partials     2402     2401    -1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/bitwarden/clients/pull/14737?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-12T16:21:09Z
body: ![Logo](https://cdn.ast.checkmarx.net/integrations/logo/Checkmarx.png)
**Checkmarx One – Scan Summary & Details** – [32b5b062\-46b9\-4e39\-8f4c\-4f6dc7470dbc](https://ast.checkmarx.net/projects/41bd746e-d417-4eda-afeb-01a59d162661/scans?id=32b5b062-46b9-4e39-8f4c-4f6dc7470dbc&branch=PM-21395-vault-nudge-bugs)
### Great job, no security vulnerabilities found in this Pull Request

author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-13T20:25:04Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=bitwarden_clients&pullRequest=14737) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=bitwarden_clients&pullRequest=14737&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=bitwarden_clients&pullRequest=14737&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=bitwarden_clients&pullRequest=14737&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=bitwarden_clients&pullRequest=14737&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=bitwarden_clients&pullRequest=14737&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=bitwarden_clients&pullRequest=14737)


author_association: NONE