From 52271a96968c6c07bfc76ec39fff2437368c75dd Mon Sep 17 00:00:00 2001 From: Jesse Merhi <79823012+jesse-merhi@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:03:37 +1000 Subject: [PATCH] fix(ui): block chat while QR setup settles --- .../pages/custodian/custodian-page.qr.test.ts | 17 ++++++++- .../custodian/custodian-session-store.ts | 35 +++++++++++-------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/ui/src/pages/custodian/custodian-page.qr.test.ts b/ui/src/pages/custodian/custodian-page.qr.test.ts index e3872ba0c6b9..78c901354d1d 100644 --- a/ui/src/pages/custodian/custodian-page.qr.test.ts +++ b/ui/src/pages/custodian/custodian-page.qr.test.ts @@ -638,7 +638,8 @@ describe("custodian QR wizard step", () => { .mockResolvedValueOnce(pendingResult) .mockResolvedValueOnce(pendingResult) .mockResolvedValueOnce(terminalResult("Signal setup cancelled.")); - const { page } = await mountPage(createContext(request).context); + const { context, emitGatewayEvent } = createContext(request); + const { page } = await mountPage(context, { onboarding: false }); await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(1_000); @@ -647,6 +648,19 @@ describe("custodian QR wizard step", () => { expect(page.textContent).toContain("This QR code expired."); expect(page.store.wizardInputPending).toBe(false); expect(page.store.wizardSettling).toBe(true); + expect(page.store.messages.some((message) => message.step?.qrDataUrl)).toBe(false); + + emitGatewayEvent({ + event: "health", + payload: { channels: { telegram: { configured: true, healthState: "stale-socket" } } }, + }); + await page.updateComplete; + const nudge = page.querySelector(".custodian__nudge-action"); + expect(nudge).not.toBeNull(); + expect(nudge?.disabled).toBe(true); + nudge?.click(); + await expect(page.store.send("check health now")).resolves.toBe("rejected"); + expect(request).toHaveBeenCalledTimes(2); await vi.advanceTimersByTimeAsync(1_000); expect(request.mock.calls.filter((call) => call[1]?.pollStepId === "qr-step")).toHaveLength(2); @@ -661,5 +675,6 @@ describe("custodian QR wizard step", () => { wizardAnswer: { stepId: "qr-step", value: false }, }); await waitForFast(() => expect(page.textContent).toContain("Signal setup cancelled.")); + expect(page.store.messages.some((message) => message.step?.qrDataUrl)).toBe(false); }); }); diff --git a/ui/src/pages/custodian/custodian-session-store.ts b/ui/src/pages/custodian/custodian-session-store.ts index bcad9caa85c9..761947a17069 100644 --- a/ui/src/pages/custodian/custodian-session-store.ts +++ b/ui/src/pages/custodian/custodian-session-store.ts @@ -173,7 +173,7 @@ export class CustodianSessionStore { this.messages, this.dismissedQuestions, this.answeredQuestions, - this.wizardInputPending, + this.wizardInputPending || this.wizardSettling, this.questionReplyUncertain, ); } @@ -209,7 +209,15 @@ export class CustodianSessionStore { // Trim decides emptiness only; sensitive values may carry meaningful whitespace. const message = this.sensitive ? text : text.trim(); const client = this.activeClient; - if (!message.trim() || !client || !this.chatAvailable || this.sending || this.setupRequired) { + if ( + !message.trim() || + !client || + !this.chatAvailable || + this.sending || + this.setupRequired || + this.wizardInputPending || + this.wizardSettling + ) { this.emit(); return "rejected"; } @@ -451,12 +459,7 @@ export class CustodianSessionStore { this.answeredQuestions = retireCustodianQuestions(this.messages, this.answeredQuestions); this.retryParams = null; this.input = ""; - this.wizardValue = undefined; - this.wizardSecretVisible = false; - this.sensitive = false; - this.wizardInputPending = false; - this.wizardSettling = false; - this.questionReplyUncertain = false; + this.resetWizardInputState(); this.error = null; this.setupIssue = null; this.earlierBoundaryAfterId = this.messages.at(-1)?.id ?? null; @@ -616,6 +619,15 @@ export class CustodianSessionStore { this.emit(); } + private resetWizardInputState(): void { + this.wizardValue = undefined; + this.wizardSecretVisible = false; + this.sensitive = false; + this.wizardInputPending = false; + this.wizardSettling = false; + this.questionReplyUncertain = false; + } + private clearConversation(): void { this.qrScheduler.clear(); this.messages = []; @@ -625,12 +637,7 @@ export class CustodianSessionStore { this.error = null; this.setupIssue = null; this.input = ""; - this.wizardValue = undefined; - this.wizardSecretVisible = false; - this.sensitive = false; - this.wizardInputPending = false; - this.wizardSettling = false; - this.questionReplyUncertain = false; + this.resetWizardInputState(); this.earlierBoundaryAfterId = null; }