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 (