From f445533cfde48ff62a761c2b0aef146c563c107e Mon Sep 17 00:00:00 2001
From: gaoming <2168502171@qq.com>
Date: Wed, 12 Aug 2026 17:48:24 +0800
Subject: [PATCH] fix: reset run-scoped UI state on selection changes
---
web/src/App.test.tsx | 106 ++++++++++++++++++++++++++++++++++++++++---
web/src/App.tsx | 6 ++-
2 files changed, 104 insertions(+), 8 deletions(-)
diff --git a/web/src/App.test.tsx b/web/src/App.test.tsx
index 95723fb4..134579e2 100644
--- a/web/src/App.test.tsx
+++ b/web/src/App.test.tsx
@@ -1,8 +1,12 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { fireEvent, render, screen } from "@testing-library/react";
+import { act, fireEvent, render, screen } from "@testing-library/react";
+import { useState } from "react";
import App from "./App";
import { useConnectionStore } from "./state/connection";
+const submitRunDraft = vi.hoisted(() => vi.fn());
+const submitSessionDraft = vi.hoisted(() => vi.fn());
+
vi.mock("./lib/locale", () => ({
useLocale: () => ({ locale: "zh-CN", setLocale: () => undefined,
t: (chinese: string) => chinese }),
@@ -10,12 +14,47 @@ vi.mock("./lib/locale", () => ({
vi.mock("./components/resource-sidebar", () => ({ ResourceSidebar: () => null }));
vi.mock("./components/run-workspace", () => ({
- RunWorkspace: ({ client }: { client: { hasVerificationEvidence: boolean } }) =>
-
- {String(client.hasVerificationEvidence)}
-
,
+ RunWorkspace: ({ client, runID }: {
+ client: { hasVerificationEvidence: boolean };
+ runID: string;
+ }) => {
+ const [draft, setDraft] = useState("");
+ const [operationState, setOperationState] = useState("idle");
+ return
+
+ {String(client.hasVerificationEvidence)}
+
+
{runID}
+
{operationState}
+
;
+ },
+}));
+vi.mock("./components/session-workspace", () => ({
+ SessionWorkspace: ({ sessionID }: { sessionID: string }) => {
+ const [draft, setDraft] = useState("");
+ const [operationState, setOperationState] = useState("idle");
+ return
+
{sessionID}
+
{operationState}
+
;
+ },
}));
-vi.mock("./components/session-workspace", () => ({ SessionWorkspace: () => null }));
vi.mock("./components/desktop-skill-preview", () => ({
DesktopSkillPreviewDialog: () => null,
}));
@@ -26,6 +65,8 @@ vi.mock("./components/run-creation-dialog", () => ({ RunCreationDialog: () => nu
describe("App capability wiring", () => {
beforeEach(() => {
+ submitRunDraft.mockClear();
+ submitSessionDraft.mockClear();
useConnectionStore.getState().disconnect();
useConnectionStore.getState().connect("read-token", {
status: "ok",
@@ -55,4 +96,57 @@ describe("App capability wiring", () => {
expect(document.querySelector(".prayu-shell.settings-mode")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "常规" })).toHaveClass("active");
});
+
+ it("remounts Run-scoped drafts and operation state when the selected Run changes", () => {
+ vi.stubGlobal("fetch", vi.fn(() => new Promise(() => undefined)));
+ render();
+
+ fireEvent.change(screen.getByLabelText("Run-scoped draft"), {
+ target: { value: "private instructions for run A" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Mark operation uncertain" }));
+ expect(screen.getByTestId("run-operation-state")).toHaveTextContent("uncertain");
+
+ act(() => useConnectionStore.getState().selectRun("run-b"));
+
+ expect(screen.getByTestId("run-identity")).toHaveTextContent("run-b");
+ expect(screen.getByLabelText("Run-scoped draft")).toHaveValue("");
+ expect(screen.getByTestId("run-operation-state")).toHaveTextContent("idle");
+ expect(screen.getByRole("button", { name: "Submit run draft" })).toBeDisabled();
+ expect(submitRunDraft).not.toHaveBeenCalled();
+
+ fireEvent.change(screen.getByLabelText("Run-scoped draft"), {
+ target: { value: "instructions for run B" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Submit run draft" }));
+ expect(submitRunDraft).toHaveBeenCalledOnce();
+ expect(submitRunDraft).toHaveBeenCalledWith("run-b", "instructions for run B");
+ });
+
+ it("remounts Session-scoped drafts and operation state when the selected Session changes", () => {
+ vi.stubGlobal("fetch", vi.fn(() => new Promise(() => undefined)));
+ useConnectionStore.getState().selectSession("session-a");
+ render();
+
+ fireEvent.change(screen.getByLabelText("Session-scoped draft"), {
+ target: { value: "private instructions for session A" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Mark Session operation uncertain" }));
+ expect(screen.getByTestId("session-operation-state")).toHaveTextContent("uncertain");
+
+ act(() => useConnectionStore.getState().selectSession("session-b"));
+
+ expect(screen.getByTestId("session-identity")).toHaveTextContent("session-b");
+ expect(screen.getByLabelText("Session-scoped draft")).toHaveValue("");
+ expect(screen.getByTestId("session-operation-state")).toHaveTextContent("idle");
+ expect(screen.getByRole("button", { name: "Submit Session draft" })).toBeDisabled();
+ expect(submitSessionDraft).not.toHaveBeenCalled();
+
+ fireEvent.change(screen.getByLabelText("Session-scoped draft"), {
+ target: { value: "instructions for session B" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Submit Session draft" }));
+ expect(submitSessionDraft).toHaveBeenCalledOnce();
+ expect(submitSessionDraft).toHaveBeenCalledWith("session-b", "instructions for session B");
+ });
});
diff --git a/web/src/App.tsx b/web/src/App.tsx
index 081f5218..f9e945b1 100644
--- a/web/src/App.tsx
+++ b/web/src/App.tsx
@@ -294,9 +294,11 @@ function ConnectedWorkbench({ token, controlToken, runControlEnabled, runCreatio
onOpenPlugins={desktop ? () => setSkillPreviewOpen(true) : undefined} />
: selectedResourceID
? resourceKind === "run"
- ? setSkillPreviewOpen(true) : undefined}
+ ? setSkillPreviewOpen(true) : undefined}
runID={selectedRunID} />
- : setSkillPreviewOpen(true) : undefined}
+ : setSkillPreviewOpen(true) : undefined}
sessionID={selectedSessionID} />
: