From 73afbb42b77f45e818d4eecf10784fb06e7a4347 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Thu, 30 Jul 2026 13:16:52 -0700 Subject: [PATCH] fix(ui): never detach a pending import request from its progress panel Config edits during an in-flight preview or import previously reset the mutation to idle, hiding the progress panel while the server request kept running and detaching its outcome from the UI. Only settled mutation state is cleared now; a pending request keeps its panel (and the action buttons stay disabled) until it settles. Component tests exercise config edits while a deferred mutation is pending in both lanes. --- ui/src/pages/CompanyImport.test.tsx | 21 +++++++++++++++++++++ ui/src/pages/CompanyImport.tsx | 14 ++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/ui/src/pages/CompanyImport.test.tsx b/ui/src/pages/CompanyImport.test.tsx index 7843b14fe15..9e282bbdf55 100644 --- a/ui/src/pages/CompanyImport.test.tsx +++ b/ui/src/pages/CompanyImport.test.tsx @@ -346,6 +346,12 @@ describe("CompanyImport", () => { expect(container.textContent).toContain("Uploading and analyzing your package"); expect(container.textContent).toContain("Keep this page open."); + // Config edits while the request is in flight must not detach it from + // the UI: the progress panel keeps reporting it until it settles. + await enterGithubUrl("https://github.com/acme/starter-b/tree/main/company"); + + expect(container.textContent).toContain("Uploading and analyzing your package"); + await act(async () => { rejectPreview(new Error("stream disconnected")); }); @@ -379,6 +385,21 @@ describe("CompanyImport", () => { expect(container.textContent).toContain("Uploading and importing"); expect(container.textContent).toContain("Keep this page open."); + // Config edits while the import is in flight must not detach it from + // the UI: the progress panel keeps reporting it until it settles. + const midFlightNameInput = container.querySelector( + 'input[placeholder="Imported Company"]', + ); + expect(midFlightNameInput).toBeTruthy(); + await act(async () => { + const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!; + setter.call(midFlightNameInput!, "Mid Flight"); + midFlightNameInput!.dispatchEvent(new Event("input", { bubbles: true })); + }); + await flushReact(); + + expect(container.textContent).toContain("Uploading and importing"); + await act(async () => { rejectImport(new Error("connection reset")); }); diff --git a/ui/src/pages/CompanyImport.tsx b/ui/src/pages/CompanyImport.tsx index 133c05c3ab5..9556b472ea3 100644 --- a/ui/src/pages/CompanyImport.tsx +++ b/ui/src/pages/CompanyImport.tsx @@ -927,13 +927,15 @@ export function CompanyImport() { }, }); - // Any change to the import configuration supersedes the request the last - // progress/error panel describes, so it resets both mutations. The - // new-company name is typed against a rendered preview, so it resets only - // the panels; structural changes also discard the preview itself. + // Any change to the import configuration supersedes the request a settled + // progress/error panel describes, so it clears settled mutation state. A + // pending request is never detached: its panel keeps reporting it (and the + // action buttons stay disabled) until it settles. The new-company name is + // typed against a rendered preview, so it resets only the panels; + // structural changes also discard the preview itself. function resetMutationState() { - previewMutation.reset(); - importMutation.reset(); + if (!previewMutation.isPending) previewMutation.reset(); + if (!importMutation.isPending) importMutation.reset(); } function resetImportFlowState() {