From a264dee030826221bf8ec22bcebb017ddd748f23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?=
Date: Wed, 21 Oct 2026 15:06:47 +0200
Subject: [PATCH] fix(settings): keep the end-access button while the grant
list loads
The button for a grant that sign-out-everywhere left standing rendered
only once the grant list (which carries the audit window its
confirmation names) had loaded, so while it loaded, and for good when
the read failed, the row explained the access but offered no way to end
it. The button now shows disabled until the list arrives, and a failed
read draws the standard error row with a retry.
---
.../__tests__/kept-grants-list.test.tsx | 70 +++++++
.../settings/security-sessions-card.tsx | 178 +++++++++++-------
2 files changed, 184 insertions(+), 64 deletions(-)
create mode 100644 src/components/settings/__tests__/kept-grants-list.test.tsx
diff --git a/src/components/settings/__tests__/kept-grants-list.test.tsx b/src/components/settings/__tests__/kept-grants-list.test.tsx
new file mode 100644
index 000000000..92adedb26
--- /dev/null
+++ b/src/components/settings/__tests__/kept-grants-list.test.tsx
@@ -0,0 +1,70 @@
+/**
+ * The "end access" button of a grant sign-out-everywhere left standing.
+ *
+ * Its confirmation names the audit window, which rides the grant list. The
+ * row used to render the button only once that list had loaded, so while it
+ * loaded, and for good when the read failed, the row offered no way to end
+ * the access it had just explained. Now the button is there, disabled, until
+ * the list arrives, and a failed read draws the standard error row.
+ */
+import { describe, expect, it } from "vitest";
+import { renderToStaticMarkup } from "react-dom/server";
+
+import { I18nProvider } from "@/lib/i18n/context";
+import { KeptGrantsList, type KeptGrant } from "../security-sessions-card";
+
+const GRANTS: KeptGrant[] = [
+ {
+ id: "g1",
+ account: { id: "a1", username: "carer", displayName: null },
+ access: "WRITE",
+ },
+];
+
+function render(grantList: Parameters[0]["grantList"]) {
+ return renderToStaticMarkup(
+
+ {}, isPending: false }}
+ onRevoked={() => {}}
+ onRevokeFailed={() => {}}
+ />
+ ,
+ );
+}
+
+function revokeButton(html: string): string {
+ const at = html.indexOf('data-slot="kept-grant-revoke"');
+ expect(at).toBeGreaterThan(-1);
+ return html.slice(html.lastIndexOf("