From 37cc66e9d68c485cb361b4743929d27ecfaee7e5 Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Thu, 30 Jul 2026 13:07:56 -0700 Subject: [PATCH] fix(ui): reset stale mutation state when the import configuration changes After a failed preview or import, editing the package URL, source mode, target, or collision strategy cleared the preview but left the mutation error state behind, so the persistent error panel kept describing a superseded request (and a stale import error could resurface under a fresh preview). Route all configuration changes through a shared reset that also clears both mutations, and cover the reset in the component tests. --- ui/src/pages/CompanyImport.test.tsx | 17 +++++++++++++++-- ui/src/pages/CompanyImport.tsx | 19 ++++++++++++++----- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/ui/src/pages/CompanyImport.test.tsx b/ui/src/pages/CompanyImport.test.tsx index 08d01b5c10c..b1baabe5296 100644 --- a/ui/src/pages/CompanyImport.test.tsx +++ b/ui/src/pages/CompanyImport.test.tsx @@ -206,14 +206,14 @@ describe("CompanyImport", () => { await flushReact(); } - async function enterGithubUrl() { + async function enterGithubUrl(url = "https://github.com/acme/starter/tree/main/company") { const urlInput = container.querySelector( 'input[placeholder="https://github.com/owner/repo/tree/main/company"]', ); expect(urlInput).toBeTruthy(); await act(async () => { const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!; - setter.call(urlInput!, "https://github.com/acme/starter/tree/main/company"); + setter.call(urlInput!, url); urlInput!.dispatchEvent(new Event("input", { bubbles: true })); }); await flushReact(); @@ -355,6 +355,11 @@ describe("CompanyImport", () => { expect(container.textContent).toContain("Preview failed: stream disconnected"); expect(container.textContent).toContain("Retry, or use the CLI folder import"); expect(mockPushToast).toHaveBeenCalledWith(expect.objectContaining({ tone: "error" })); + + // Changing the package supersedes the failed request: the error panel resets. + await enterGithubUrl("https://github.com/acme/other-starter/tree/main/company"); + + expect(container.textContent).not.toContain("Preview failed:"); }); it("shows a progress panel while the import runs and a durable error panel when it fails", async () => { @@ -383,5 +388,13 @@ describe("CompanyImport", () => { expect(container.textContent).toContain("Import failed: connection reset"); expect(container.textContent).toContain("check the target company before retrying."); expect(mockPushToast).toHaveBeenCalledWith(expect.objectContaining({ tone: "error" })); + + // Changing the package supersedes the failed import: previewing a fresh + // package must not resurface the stale import error panel. + await enterGithubUrl("https://github.com/acme/other-starter/tree/main/company"); + await clickButton((text) => text === "Preview import"); + + expect(findButton((text) => text.startsWith("Import 3 file"))).toBeTruthy(); + expect(container.textContent).not.toContain("Import failed:"); }); }); diff --git a/ui/src/pages/CompanyImport.tsx b/ui/src/pages/CompanyImport.tsx index 352fa6d46b2..bb2f9d9c5d3 100644 --- a/ui/src/pages/CompanyImport.tsx +++ b/ui/src/pages/CompanyImport.tsx @@ -927,13 +927,22 @@ export function CompanyImport() { }, }); + // Any change to the package or import configuration supersedes the last + // preview and any settled mutation state, so the persistent progress/error + // panels never keep describing a request that no longer applies. + function resetImportFlowState() { + setImportPreview(null); + previewMutation.reset(); + importMutation.reset(); + } + async function handleChooseLocalPackage(e: ChangeEvent) { const fileList = e.target.files; if (!fileList || fileList.length === 0) return; try { const pkg = await readLocalPackageZip(fileList[0]!); setLocalPackage(pkg); - setImportPreview(null); + resetImportFlowState(); } catch (err) { pushToast({ tone: "error", @@ -1311,7 +1320,7 @@ export function CompanyImport() { )} onClick={() => { setSourceMode(key); - setImportPreview(null); + resetImportFlowState(); }} >
@@ -1379,7 +1388,7 @@ export function CompanyImport() { placeholder="https://github.com/owner/repo/tree/main/company" onChange={(e) => { setImportUrl(e.target.value); - setImportPreview(null); + resetImportFlowState(); }} /> @@ -1391,7 +1400,7 @@ export function CompanyImport() { value={targetMode} onChange={(e) => { setTargetMode(e.target.value as "existing" | "new"); - setImportPreview(null); + resetImportFlowState(); }} > @@ -1425,7 +1434,7 @@ export function CompanyImport() { value={collisionStrategy} onChange={(e) => { setCollisionStrategy(e.target.value as CompanyPortabilityCollisionStrategy); - setImportPreview(null); + resetImportFlowState(); }} >