diff --git a/ui/src/components/WorkFolderBrowser.test.tsx b/ui/src/components/WorkFolderBrowser.test.tsx index ed1ed61d54d..9fdeee87151 100644 --- a/ui/src/components/WorkFolderBrowser.test.tsx +++ b/ui/src/components/WorkFolderBrowser.test.tsx @@ -10,13 +10,16 @@ vi.mock("@/context/CompanyContext", () => ({ useCompany: () => ({ selectedCompan const owner = { companyId: "company", scope: "task" as const, ownerId: "task" }; const key = ["work-folders", owner.companyId, owner.scope, owner.ownerId]; -function render(statuses: WorkFolderSyncStatus[], lastOperationAt: string | null, readOnly = false, queryState?: "loading" | "error") { +function render(statuses: WorkFolderSyncStatus[], lastOperationAt: string | null, readOnly = false, queryState?: "loading" | "error", filesState?: "loading" | "error") { const client = new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity } } }); client.setQueryData([...key, "files", false], { files: [], lastOperationAt }); client.setQueryData([...key, "sync"], statuses); if (queryState === "loading") client.removeQueries({ queryKey: [...key, "sync"] }); if (queryState === "error") client.getQueryCache().find({ queryKey: [...key, "sync"] })! .setState({ status: "error", error: new Error("Save service unavailable") }); + if (filesState === "loading") client.removeQueries({ queryKey: [...key, "files", false] }); + if (filesState === "error") client.getQueryCache().find({ queryKey: [...key, "files", false] })! + .setState({ status: "error", error: new Error("File listing unavailable") }); return renderToStaticMarkup(); } const checkpoint: WorkFolderSyncStatus = { runId: "run", state: "saved", active: false, @@ -36,6 +39,18 @@ describe("work folder save feedback", () => { expect(unavailable).toContain("Last agent save"); expect(unavailable).toContain("Save service unavailable"); }); + it.each(["loading", "error"] as const)("preserves known save status while file listing is %s", (filesState) => { + for (const [state, expected] of [["saved", "Saved"], ["saving", "Saving…"], ["failed", "Run save failed"]] as const) { + const html = render([{ ...checkpoint, state }], null, true, undefined, filesState); + expect(html).toContain(`role="status">${expected}`); + expect(html).toContain("Last agent save"); + if (filesState === "error") { + expect(html).toContain("File listing unavailable"); + expect(html).toContain("File list unavailable"); + expect(html).not.toContain("No files yet"); + } + } + }); it("keeps inspection free of controls that mutate the cache or refresh the sandbox", () => { const html = render([checkpoint], null, true); for (const action of ["Upload", "Create folder", "Refresh sandbox", "Delete", "Restore", "Purge"]) expect(html).not.toContain(action); diff --git a/ui/src/components/WorkFolderBrowser.tsx b/ui/src/components/WorkFolderBrowser.tsx index 2040c6143e3..36e0de7c5ba 100644 --- a/ui/src/components/WorkFolderBrowser.tsx +++ b/ui/src/components/WorkFolderBrowser.tsx @@ -77,8 +77,8 @@ export function WorkFolderBrowser({ owner, exampleFiles, readOnly = false, fillH const lastOperation = filesQuery.data?.lastOperationAt; const saveLabel = mutation.isPending ? "Saving…" : mutation.isError ? "Save failed" - : syncQuery.isError || filesQuery.isError ? "Save status unavailable" - : !exampleFiles && (syncQuery.isPending || filesQuery.isPending) ? "Loading save status…" + : syncQuery.isError ? "Save status unavailable" + : !exampleFiles && syncQuery.isPending ? "Loading save status…" : saving ? "Saving…" : failures.length > 0 ? "Run save failed" : !lastSaved && statuses.some((status) => status.active) ? "Waiting for first save" @@ -138,7 +138,7 @@ export function WorkFolderBrowser({ owner, exampleFiles, readOnly = false, fillH return next; }); }} - onSelectFile={setSelectedPath} loading={!exampleFiles && filesQuery.isLoading} empty={{ title: "No files yet", description: readOnly ? "No cached files have been saved for this scope." : "Upload files here, or create them during a sandbox run." }} ariaLabel={`${owner.scope} files`} /> + onSelectFile={setSelectedPath} loading={!exampleFiles && filesQuery.isLoading} empty={filesQuery.isError ? { title: "File list unavailable", description: "The saved contents could not be listed." } : { title: "No files yet", description: readOnly ? "No cached files have been saved for this scope." : "Upload files here, or create them during a sandbox run." }} ariaLabel={`${owner.scope} files`} />
{selected &&
{selected.path} {selected.kind === "file" && !exampleFiles && }