From b9c03cea6552879b680410be12076257a3129bcc Mon Sep 17 00:00:00 2001 From: Dotta Date: Thu, 22 Oct 2026 15:56:02 -0700 Subject: [PATCH] fix(chat): preserve request selection during dismissal save --- ui/src/components/TaskChatThread.test.tsx | 17 +++++++++++++++++ ui/src/components/TaskChatThread.tsx | 12 +++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/ui/src/components/TaskChatThread.test.tsx b/ui/src/components/TaskChatThread.test.tsx index 85fae2d119f..f7a0e7c7bd8 100644 --- a/ui/src/components/TaskChatThread.test.tsx +++ b/ui/src/components/TaskChatThread.test.tsx @@ -3403,6 +3403,23 @@ describe.each([true, false])("Unanswered question history (conversationMode=%s)" expect(container.querySelector('[role="alert"]')?.textContent).toContain("Dismissal could not be saved"); }); + it("keeps the newly opened request visible when an earlier dismissal finishes saving", async () => { + const { genericPendingRequestConfirmationInteraction } = await import("@/fixtures/issueThreadInteractionFixtures"); + const first = { ...genericPendingRequestConfirmationInteraction, id: "first-review", title: "First review" }; + const second = { ...first, id: "second-review", title: "Second review" }; + let finishDismiss!: () => void; + const onDismissInteraction = vi.fn(() => new Promise(resolve => { finishDismiss = resolve; })); + render(); + await act(async () => container.querySelector('[aria-label="Open request: First review"]')!.click()); + await dismiss(); + expect(onDismissInteraction).toHaveBeenCalledWith(first); + await act(async () => container.querySelector('[aria-label="Open request: Second review"]')!.click()); + expect(takeover()?.textContent).toContain("Second review"); + await act(async () => finishDismiss()); + expect(takeover()?.textContent).toContain("Second review"); + expect(takeover()?.textContent).not.toContain("First review"); + }); + it("opens the new question but can reopen the exact historical native question", async () => { const nativeOld = { ...old, sourceRunId: "old-run", payload: { ...old.payload, runtimeRequestId: "old-request" } } as IssueThreadInteraction; render(); diff --git a/ui/src/components/TaskChatThread.tsx b/ui/src/components/TaskChatThread.tsx index 7ed00aadf19..b4a6ac857ed 100644 --- a/ui/src/components/TaskChatThread.tsx +++ b/ui/src/components/TaskChatThread.tsx @@ -2591,6 +2591,10 @@ export function TaskChatThread(props: TaskChatThreadProps) { pendingComposerInputs.find((input) => input.key === selectedPendingKey) ?? currentPendingInputs[0] ?? null; + const selectedPendingInputKeyRef = useRef(null); + useLayoutEffect(() => { + selectedPendingInputKeyRef.current = selectedPendingInput?.key ?? null; + }, [selectedPendingInput?.key]); const interactionDraftKey = selectedPendingInput ? `paperclip:task-input:${issueId ?? "unknown"}:${selectedPendingInput.key}` : undefined; @@ -2870,9 +2874,11 @@ export function TaskChatThread(props: TaskChatThreadProps) { } else { dismissInput(selectedPendingInput.key); } - explicitlyOpenedInputRef.current = null; - setSelectedPendingKey(null); - setTakeoverMode("normal"); + if (selectedPendingInputKeyRef.current === selectedPendingInput.key) { + explicitlyOpenedInputRef.current = null; + setSelectedPendingKey(null); + setTakeoverMode("normal"); + } } catch (error) { setDismissError(error instanceof Error ? error.message : "Unable to dismiss this request. Try again."); }