From 96e0e6b0369defec3985cd51ebf9a3bfbc1c3c72 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Sun, 27 Sep 2026 04:18:09 +0200
Subject: [PATCH] fix: keep the pairing QR on screen while the Security tab
checks the server
---
.../__tests__/link-device-section.test.tsx | 14 ++++++++++++++
components/settings/account-security-settings.tsx | 5 ++++-
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/components/settings/__tests__/link-device-section.test.tsx b/components/settings/__tests__/link-device-section.test.tsx
index 03419c4f5..c093d335c 100644
--- a/components/settings/__tests__/link-device-section.test.tsx
+++ b/components/settings/__tests__/link-device-section.test.tsx
@@ -453,6 +453,20 @@ describe('AccountSecuritySettings link-device visibility', () => {
expect(screen.queryByText('email_client.title')).toBeNull();
});
+ it('mounts the linker only once the server probe has a verdict', () => {
+ // A layout guessed before the verdict remounted the linker afterwards and
+ // threw away a QR that was already showing.
+ useAuthStore.setState({ isAuthenticated: true, authMode: 'oauth', client: null });
+ useAccountSecurityStore.setState({ isStalwart: null, isProbing: false });
+ const { rerender } = render();
+ expect(screen.queryByText('link_device.title')).toBeNull();
+ expect(screen.getByText('detecting')).toBeInTheDocument();
+
+ act(() => useAccountSecurityStore.setState({ isStalwart: false }));
+ rerender();
+ expect(screen.getByText('link_device.title')).toBeInTheDocument();
+ });
+
it('hides the linker in Bulwark Lite', () => {
mocks.lite.value = true;
useAccountSecurityStore.setState({ isStalwart: false, isProbing: false });
diff --git a/components/settings/account-security-settings.tsx b/components/settings/account-security-settings.tsx
index 5734179bb..161bbecb9 100644
--- a/components/settings/account-security-settings.tsx
+++ b/components/settings/account-security-settings.tsx
@@ -1451,7 +1451,10 @@ export function AccountSecuritySettings() {
}
}, [isAuthenticated, client, isStalwart, probe, fetchAll, fetchAuthInfo, isOAuth, fetchPublicKeys, fetchCryptoInfo]);
- if (isProbing) {
+ // Until the probe has a verdict, show the spinner instead of guessing a
+ // layout: switching layouts remounts every section below, which threw away
+ // a pairing QR that was already on screen.
+ if (isProbing || (isStalwart === null && isAuthenticated)) {
return (