From 5b0cccc0fa31c8c24331e409933d062e519f59eb Mon Sep 17 00:00:00 2001 From: Brad <44413459+lastbestdev@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:05:17 -0800 Subject: [PATCH 1/4] [PM-29952] Fix: Access Intelligence password change tasks progress bar (#18488) * check tasks completed after report generation * fix type safety --- .../password-change-metric.component.ts | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts index 60b53f7405d..c1a00731100 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/activity-cards/password-change-metric.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, DestroyRef, - Injector, OnInit, Signal, computed, @@ -11,7 +10,7 @@ import { input, signal, } from "@angular/core"; -import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop"; +import { toSignal } from "@angular/core/rxjs-interop"; import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -59,6 +58,9 @@ export class PasswordChangeMetricComponent implements OnInit { private readonly _tasks: Signal = signal([]); private readonly _atRiskCipherIds: Signal = signal([]); private readonly _hasCriticalApplications: Signal = signal(false); + private readonly _reportGeneratedAt: Signal = signal( + undefined, + ); // Computed properties readonly tasksCount = computed(() => this._tasks().length); @@ -80,8 +82,24 @@ export class PasswordChangeMetricComponent implements OnInit { } const inProgressTasks = tasks.filter((task) => task.status === SecurityTaskStatus.Pending); - const assignedIdSet = new Set(inProgressTasks.map((task) => task.cipherId)); - const unassignedIds = atRiskIds.filter((id) => !assignedIdSet.has(id)); + const inProgressTaskIds = new Set(inProgressTasks.map((task) => task.cipherId)); + + const reportGeneratedAt = this._reportGeneratedAt(); + const completedTasksAfterReportGeneration = reportGeneratedAt + ? tasks.filter( + (task) => + task.status === SecurityTaskStatus.Completed && + new Date(task.revisionDate) >= reportGeneratedAt, + ) + : []; + const completedTaskIds = new Set( + completedTasksAfterReportGeneration.map((task) => task.cipherId), + ); + + // find cipher ids from last report that do not have a corresponding in progress task (awaiting password reset) OR completed task + const unassignedIds = atRiskIds.filter( + (id) => !inProgressTaskIds.has(id) && !completedTaskIds.has(id), + ); return unassignedIds.length; }); @@ -109,36 +127,26 @@ export class PasswordChangeMetricComponent implements OnInit { constructor( private allActivitiesService: AllActivitiesService, private i18nService: I18nService, - private injector: Injector, private riskInsightsDataService: RiskInsightsDataService, protected securityTasksService: AccessIntelligenceSecurityTasksService, private toastService: ToastService, ) { - // Setup the _tasks signal by manually passing in the injector - this._tasks = toSignal(this.securityTasksService.tasks$, { - initialValue: [], - injector: this.injector, - }); - // Setup the _atRiskCipherIds signal by manually passing in the injector + this._tasks = toSignal(this.securityTasksService.tasks$, { initialValue: [] }); this._atRiskCipherIds = toSignal( this.riskInsightsDataService.criticalApplicationAtRiskCipherIds$, - { - initialValue: [], - injector: this.injector, - }, + { initialValue: [] }, ); - this._hasCriticalApplications = toSignal( this.riskInsightsDataService.criticalReportResults$.pipe( - takeUntilDestroyed(this.destroyRef), map((report) => { return report != null && (report.reportData?.length ?? 0) > 0; }), ), - { - initialValue: false, - injector: this.injector, - }, + { initialValue: false }, + ); + this._reportGeneratedAt = toSignal( + this.riskInsightsDataService.enrichedReportData$.pipe(map((report) => report?.creationDate)), + { initialValue: undefined }, ); effect(() => { From 2109092a9443d178e3c1be2a74819b85a390c0d4 Mon Sep 17 00:00:00 2001 From: Brad <44413459+lastbestdev@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:20:17 -0800 Subject: [PATCH 2/4] [PM-31354] Fix Reports page loading (#18631) * fix reports page loading * update to signals, leave OnPush detection strategy --- .../dirt/reports/pages/reports-home.component.html | 2 +- .../app/dirt/reports/pages/reports-home.component.ts | 8 ++++---- .../shared/report-list/report-list.component.html | 2 +- .../shared/report-list/report-list.component.ts | 11 +++-------- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/web/src/app/dirt/reports/pages/reports-home.component.html b/apps/web/src/app/dirt/reports/pages/reports-home.component.html index 9101933bc40..ee3caae4212 100644 --- a/apps/web/src/app/dirt/reports/pages/reports-home.component.html +++ b/apps/web/src/app/dirt/reports/pages/reports-home.component.html @@ -3,5 +3,5 @@

{{ "reportsDesc" | i18n }}

- +
diff --git a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts index 25cf663ba7e..5dd7f1d3ec0 100644 --- a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { ChangeDetectionStrategy, Component, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, Component, OnInit, signal } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -16,7 +16,7 @@ import { ReportEntry, ReportVariant } from "../shared"; standalone: false, }) export class ReportsHomeComponent implements OnInit { - reports: ReportEntry[]; + readonly reports = signal([]); constructor( private billingAccountProfileStateService: BillingAccountProfileStateService, @@ -32,7 +32,7 @@ export class ReportsHomeComponent implements OnInit { ? ReportVariant.Enabled : ReportVariant.RequiresPremium; - this.reports = [ + this.reports.set([ { ...reports[ReportType.ExposedPasswords], variant: reportRequiresPremium, @@ -57,6 +57,6 @@ export class ReportsHomeComponent implements OnInit { ...reports[ReportType.DataBreach], variant: ReportVariant.Enabled, }, - ]; + ]); } } diff --git a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.html b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.html index bba57882027..4726eb5c42f 100644 --- a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.html +++ b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.html @@ -1,7 +1,7 @@
- @for (report of reports; track report) { + @for (report of reports(); track report) {
([]); } From 1db00097d21d66ff03015db12eea0cd33e84d387 Mon Sep 17 00:00:00 2001 From: Leslie Xiong Date: Thu, 29 Jan 2026 04:51:46 -0500 Subject: [PATCH 3/4] fixed nested folders missing 'onEditFolder' (#18629) --- .../vault-filter/filters/folder-filter.component.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.html b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.html index f063167c48f..0a0193302a0 100644 --- a/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.html +++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/filters/folder-filter.component.html @@ -22,7 +22,11 @@ > } @for (childFolder of folder().children; track childFolder.node.id) { - + } } @else { From 6d1693050c252966fbc04e0535e036d358d0e908 Mon Sep 17 00:00:00 2001 From: Colton Hurst Date: Thu, 29 Jan 2026 08:39:45 -0500 Subject: [PATCH 4/4] Autofill Provider Readme Update (#18624) * Update the autofill provider readme * Update casing based on pr suggestion --- apps/desktop/desktop_native/autofill_provider/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/desktop/desktop_native/autofill_provider/README.md b/apps/desktop/desktop_native/autofill_provider/README.md index 86c49356161..7f1312a0700 100644 --- a/apps/desktop/desktop_native/autofill_provider/README.md +++ b/apps/desktop/desktop_native/autofill_provider/README.md @@ -2,11 +2,14 @@ A library for native autofill providers to interact with a host Bitwarden desktop app. +In this desktop context, "native autofill providers" are operating system frameworks or APIs (like the macOS [autofill framework](https://developer.apple.com/documentation/Security/password-autofill)) that allow Bitwarden to provide provide user credentials for things like autofill, passkey operations, etc. + # Explainer: Mac OS Native Passkey Provider This document describes the changes introduced in https://github.com/bitwarden/clients/pull/13963, where we introduce the MacOS Native Passkey Provider. It gives the high level explanation of the architecture and some of the quirks and additional good to know context. ## The high level + MacOS has native APIs (similar to iOS) to allow Credential Managers to provide credentials to the MacOS autofill system (in the PR referenced above, we only provide passkeys). We’ve written a Swift-based native autofill-extension. It’s bundled in the app-bundle in PlugIns, similar to the safari-extension. @@ -16,7 +19,7 @@ This swift extension currently communicates with our Electron app through IPC ba Footnotes: * We're not using the IPC framework as the implementation pre-dates the IPC framework. -* Alternatives like XPC or CFMessagePort may have better support for when the app is sandboxed. +* Alternatives like XPC or CFMessagePort may have better support for when the app is sandboxed. Electron receives the messages and passes it to Angular (through the electron-renderer event system). @@ -26,7 +29,7 @@ Our existing fido2 services in the renderer respond to events, displaying UI as We utilize the same FIDO2 implementation and interface that is already present for our browser authentication. It was designed by @coroiu with multiple ‘ui environments' in mind. -Therefore, a lot of the plumbing is implemented in /autofill/services/desktop-fido2-user-interface.service.ts, which implements the interface that our fido2 authenticator/client expects to drive UI related behaviors. +Therefore, a lot of the plumbing is implemented in /autofill/services/desktop-fido2-user-interface.service.ts, which implements the interface that our fido2 authenticator/client expects to drive UI related behaviors. We’ve also implemented a couple FIDO2 UI components to handle registration/sign in flows, but also improved the “modal mode” of the desktop app.