+
@@ -138,18 +251,15 @@ export const Inline: Story = {
`,
}),
- args: {
- linkType: "primary",
- },
};
-export const Disabled: Story = {
+export const Inactive: Story = {
render: (args) => ({
props: args,
template: /*html*/ `
Primary
Secondary
-
+
Contrast
`,
diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts
index 23c95cafb8a..e54064f0c9d 100644
--- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts
+++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-form.component.ts
@@ -73,7 +73,6 @@ import { KitchenSinkSharedModule } from "../kitchen-sink-shared.module";
A random password
Bitwarden Kitchen Sink
- This is a link
+ This is a link
and this is a link button popover trigger:
Date: Wed, 21 Jan 2026 09:22:58 -0800
Subject: [PATCH 6/8] Add ssh-agent-v2 feature flag (#18347)
---
libs/common/src/enums/feature-flag.enum.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index 0f834abbe2a..9f6beb5f81e 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -23,6 +23,7 @@ export enum FeatureFlag {
MacOsNativeCredentialSync = "macos-native-credential-sync",
WindowsDesktopAutotype = "windows-desktop-autotype",
WindowsDesktopAutotypeGA = "windows-desktop-autotype-ga",
+ SSHAgentV2 = "ssh-agent-v2",
/* Billing */
TrialPaymentOptional = "PM-8163-trial-payment",
@@ -107,6 +108,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.MacOsNativeCredentialSync]: FALSE,
[FeatureFlag.WindowsDesktopAutotype]: FALSE,
[FeatureFlag.WindowsDesktopAutotypeGA]: FALSE,
+ [FeatureFlag.SSHAgentV2]: FALSE,
/* Tools */
[FeatureFlag.UseSdkPasswordGenerators]: FALSE,
From 5a4ac17e03c50d059348b03c5ead8ab0309bf8d4 Mon Sep 17 00:00:00 2001
From: Brad <44413459+lastbestdev@users.noreply.github.com>
Date: Wed, 21 Jan 2026 09:25:53 -0800
Subject: [PATCH 7/8] [PM-30250] Prevent over scrolling on Inactive 2FA and
Unsecure Website reports (#18399)
* use bit-table-scroll on inactive 2FA and unsecured website reports
* fix: back to reports button renders on report page load
---
.../inactive-two-factor-report.component.html | 140 +++++++++---------
.../unsecured-websites-report.component.html | 111 +++++++-------
.../reports/reports-layout.component.html | 8 +-
.../dirt/reports/reports-layout.component.ts | 24 +--
4 files changed, 137 insertions(+), 146 deletions(-)
diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html
index 05758a854c2..9a99a55b77b 100644
--- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html
+++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.html
@@ -31,81 +31,75 @@
-
+
-
-
- {{ "name" | i18n }}
- {{ "owner" | i18n }}
-
-
+
+ {{ "name" | i18n }}
+ {{ "owner" | i18n }}
+
-
-
-
-
-
-
-
-
- {{ r.name }}
-
-
- {{ r.name }}
-
-
-
- {{ "shared" | i18n }}
-
-
-
- {{ "attachments" | i18n }}
-
-
- {{ r.subTitle }}
-
-
-
-
-
-
-
- {{ "instructions" | i18n }}
-
-
-
-
+
+
+
+
+
+
+ {{ row.name }}
+
+
+ {{ row.name }}
+
+
+
+ {{ "shared" | i18n }}
+
+
+
+ {{ "attachments" | i18n }}
+
+
+ {{ row.subTitle }}
+
+
+
+
+
+
+
+ {{ "instructions" | i18n }}
+
+
+
diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html
index 5dd11b59999..cc7537333ad 100644
--- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html
+++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.html
@@ -32,68 +32,63 @@
-
+
-
-
- {{ "name" | i18n }}
- {{ "owner" | i18n }}
-
-
+
+ {{ "name" | i18n }}
+ {{ "owner" | i18n }}
-
-
-
-
-
-
-
- {{ r.name }}
-
-
- {{ r.name }}
-
-
-
- {{ "shared" | i18n }}
-
-
-
- {{ "attachments" | i18n }}
-
-
- {{ r.subTitle }}
-
-
-
+
+
+
+
+
+ {{ row.name }}
-
-
-
+
+
+ {{ row.name }}
+
+
+
+ {{ "shared" | i18n }}
+
+
+
+ {{ "attachments" | i18n }}
+
+
+ {{ row.subTitle }}
+
+
+
+
+
-
+
diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.html b/apps/web/src/app/dirt/reports/reports-layout.component.html
index a27556a7aa9..0cb5d304a34 100644
--- a/apps/web/src/app/dirt/reports/reports-layout.component.html
+++ b/apps/web/src/app/dirt/reports/reports-layout.component.html
@@ -2,8 +2,10 @@
diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.ts b/apps/web/src/app/dirt/reports/reports-layout.component.ts
index c2fbf858590..a6d84ccb037 100644
--- a/apps/web/src/app/dirt/reports/reports-layout.component.ts
+++ b/apps/web/src/app/dirt/reports/reports-layout.component.ts
@@ -1,6 +1,6 @@
-import { Component, OnDestroy } from "@angular/core";
+import { Component } from "@angular/core";
+import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { NavigationEnd, Router } from "@angular/router";
-import { Subscription } from "rxjs";
import { filter } from "rxjs/operators";
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
@@ -10,20 +10,20 @@ import { filter } from "rxjs/operators";
templateUrl: "reports-layout.component.html",
standalone: false,
})
-export class ReportsLayoutComponent implements OnDestroy {
+export class ReportsLayoutComponent {
homepage = true;
- subscription: Subscription;
constructor(router: Router) {
- this.subscription = router.events
- .pipe(filter((event) => event instanceof NavigationEnd))
- // eslint-disable-next-line rxjs-angular/prefer-takeuntil
+ const reportsHomeRoute = "/reports";
+
+ this.homepage = router.url === reportsHomeRoute;
+ router.events
+ .pipe(
+ takeUntilDestroyed(),
+ filter((event) => event instanceof NavigationEnd),
+ )
.subscribe((event) => {
- this.homepage = (event as NavigationEnd).url == "/reports";
+ this.homepage = (event as NavigationEnd).url == reportsHomeRoute;
});
}
-
- ngOnDestroy(): void {
- this.subscription?.unsubscribe();
- }
}
From d4b85589562197087d56d6018934868aaa387460 Mon Sep 17 00:00:00 2001
From: Jason Ng
Date: Wed, 21 Jan 2026 12:30:31 -0500
Subject: [PATCH 8/8] [PM-30748] update archived restored toast (#18367)
---
apps/browser/src/_locales/en/messages.json | 3 +++
.../trash-list-items-container.component.ts | 9 ++++++++-
apps/desktop/src/locales/en/messages.json | 3 +++
.../vault/app/vault/item-footer.component.ts | 9 ++++++++-
.../vault/individual-vault/vault.component.ts | 18 ++++++++++++++++--
apps/web/src/locales/en/messages.json | 6 ++++++
6 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json
index 90cc4a5c338..68149a9781e 100644
--- a/apps/browser/src/_locales/en/messages.json
+++ b/apps/browser/src/_locales/en/messages.json
@@ -2473,6 +2473,9 @@
"permanentlyDeletedItem": {
"message": "Item permanently deleted"
},
+ "archivedItemRestored": {
+ "message": "Archived item restored"
+ },
"restoreItem": {
"message": "Restore item"
},
diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts
index bad6011b2d8..edebdab062f 100644
--- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts
+++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts
@@ -115,15 +115,22 @@ export class TrashListItemsContainerComponent {
}
async restore(cipher: PopupCipherViewLike) {
+ let toastMessage;
try {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
await this.cipherService.restoreWithServer(cipher.id as string, activeUserId);
+ if (cipher.archivedDate) {
+ toastMessage = this.i18nService.t("archivedItemRestored");
+ } else {
+ toastMessage = this.i18nService.t("restoredItem");
+ }
+
await this.router.navigate(["/trash"]);
this.toastService.showToast({
variant: "success",
title: null,
- message: this.i18nService.t("restoredItem"),
+ message: toastMessage,
});
} catch (e) {
this.logService.error(e);
diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json
index f9947e16692..0ce98b8c62b 100644
--- a/apps/desktop/src/locales/en/messages.json
+++ b/apps/desktop/src/locales/en/messages.json
@@ -2092,6 +2092,9 @@
"permanentlyDeletedItem": {
"message": "Item permanently deleted"
},
+ "archivedItemRestored": {
+ "message": "Archived item restored"
+ },
"restoredItem": {
"message": "Item restored"
},
diff --git a/apps/desktop/src/vault/app/vault/item-footer.component.ts b/apps/desktop/src/vault/app/vault/item-footer.component.ts
index c80e4e59ae4..3f22a08d00e 100644
--- a/apps/desktop/src/vault/app/vault/item-footer.component.ts
+++ b/apps/desktop/src/vault/app/vault/item-footer.component.ts
@@ -173,16 +173,23 @@ export class ItemFooterComponent implements OnInit, OnChanges {
}
async restore(): Promise {
+ let toastMessage;
if (!this.cipher.isDeleted) {
return false;
}
+ if (this.cipher.isArchived) {
+ toastMessage = this.i18nService.t("archivedItemRestored");
+ } else {
+ toastMessage = this.i18nService.t("restoredItem");
+ }
+
try {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
await this.restoreCipher(activeUserId);
this.toastService.showToast({
variant: "success",
- message: this.i18nService.t("restoredItem"),
+ message: toastMessage,
});
this.onRestore.emit(this.cipher);
} catch (e) {
diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts
index 527df0b5370..5ca3a11d5ab 100644
--- a/apps/web/src/app/vault/individual-vault/vault.component.ts
+++ b/apps/web/src/app/vault/individual-vault/vault.component.ts
@@ -1271,6 +1271,7 @@ export class VaultComponent implements OnInit, OnDestr
}
restore = async (c: C): Promise => {
+ let toastMessage;
if (!CipherViewLikeUtils.isDeleted(c)) {
return;
}
@@ -1284,13 +1285,19 @@ export class VaultComponent implements OnInit, OnDestr
return;
}
+ if (CipherViewLikeUtils.isArchived(c)) {
+ toastMessage = this.i18nService.t("archivedItemRestored");
+ } else {
+ toastMessage = this.i18nService.t("restoredItem");
+ }
+
try {
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
await this.cipherService.restoreWithServer(uuidAsString(c.id), activeUserId);
this.toastService.showToast({
variant: "success",
title: null,
- message: this.i18nService.t("restoredItem"),
+ message: toastMessage,
});
this.refresh();
} catch (e) {
@@ -1299,11 +1306,18 @@ export class VaultComponent implements OnInit, OnDestr
};
async bulkRestore(ciphers: C[]) {
+ let toastMessage;
if (ciphers.some((c) => !c.edit)) {
this.showMissingPermissionsError();
return;
}
+ if (ciphers.some((c) => !CipherViewLikeUtils.isArchived(c))) {
+ toastMessage = this.i18nService.t("restoredItems");
+ } else {
+ toastMessage = this.i18nService.t("archivedItemsRestored");
+ }
+
if (!(await this.repromptCipher(ciphers))) {
return;
}
@@ -1323,7 +1337,7 @@ export class VaultComponent implements OnInit, OnDestr
this.toastService.showToast({
variant: "success",
title: null,
- message: this.i18nService.t("restoredItems"),
+ message: toastMessage,
});
this.refresh();
}
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index b438920a99f..bf7c1a4c908 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -5418,6 +5418,12 @@
"restoreSelected": {
"message": "Restore selected"
},
+ "archivedItemRestored": {
+ "message": "Archived item restored"
+ },
+ "archivedItemsRestored": {
+ "message": "Archived items restored"
+ },
"restoredItem": {
"message": "Item restored"
},