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("