From b6342d4afd3d1d40cf5a069fa496b51901ba9306 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Mon, 8 Jun 2026 21:57:59 +0000 Subject: [PATCH 1/2] test(canvas): gating test for in-flight turn status preservation on hydrate (issue #2391) --- canvas/src/store/__tests__/canvas.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/canvas/src/store/__tests__/canvas.test.ts b/canvas/src/store/__tests__/canvas.test.ts index e3410b147..d7bc343e5 100644 --- a/canvas/src/store/__tests__/canvas.test.ts +++ b/canvas/src/store/__tests__/canvas.test.ts @@ -162,6 +162,27 @@ describe("hydrate", () => { useCanvasStore.getState().hydrate([ws]); expect(useCanvasStore.getState().nodes[0].data.currentTask).toBe(""); }); + + it("preserves in-flight turn status after refresh (issue #2391)", () => { + // Simulates a page refresh: the canvas re-hydrates from GET /workspaces + // while the agent has an active in-flight turn. The store must reflect + // "working" immediately — no dependence on a subsequent TASK_UPDATED + // socket event. This prevents the "stuck idle" UX after reload. + const ws = makeWS({ + id: "ws-1", + status: "online", + current_task: "Analyzing data", + active_tasks: 2, + }); + useCanvasStore.getState().hydrate([ws]); + const node = useCanvasStore.getState().nodes[0]; + expect(node.data.currentTask).toBe("Analyzing data"); + expect(node.data.activeTasks).toBe(2); + expect(node.data.status).toBe("online"); + // Defensive: the node must be considered "working" for any UI that + // gates on currentTask (e.g. ChatTab thinking indicator). + expect(!!node.data.currentTask).toBe(true); + }); }); describe("summarizeWorkspaceCapabilities", () => { -- 2.52.0 From bf1f1750fa049ee322d2d72fe6739146f2b851a7 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Mon, 8 Jun 2026 23:54:14 +0000 Subject: [PATCH 2/2] test(canvas): deflake DisplayTab noVNC constructor assertion\n\nAdds an explicit waitFor before asserting mockRFBConstructor arguments.\nThe noVNC client is loaded via dynamic import inside a useEffect, so in\nCI the assertion could race ahead of the async init and fail with\n'Number of calls: 0'. This is the flake blocking CI/all-required on #2426.\n\nCo-Authored-By: Claude Opus 4.8 --- canvas/src/components/tabs/__tests__/DisplayTab.test.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx b/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx index 918efac12..a452a976e 100644 --- a/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx +++ b/canvas/src/components/tabs/__tests__/DisplayTab.test.tsx @@ -162,6 +162,11 @@ describe("DisplayTab", () => { controller: "user", ttl_seconds: 300, }); + // Defensive: the noVNC constructor is async (dynamic import), so wait + // for it to be called before asserting arguments (prevents flake in CI). + await waitFor(() => { + expect(mockRFBConstructor).toHaveBeenCalled(); + }); expect(mockRFBConstructor).toHaveBeenCalledWith( expect.any(HTMLElement), expect.stringContaining("/workspaces/ws-display/display/session/websockify"), -- 2.52.0