From df89d9680368d84dbab24a235defaf8be0d876cc Mon Sep 17 00:00:00 2001 From: mesalogo Date: Sat, 19 Sep 2026 20:24:00 +0800 Subject: [PATCH] fix: remove redundant topbar runtime status Keep model selection and switching feedback in the composer. Report automatic runtime status failures through notifications and move the send button accessible description into the composer. Release note: Remove the duplicate top-right model/runtime badge while retaining switching feedback and error notifications. --- UI-DESIGN.md | 1 + src/renderer/src/App.test.tsx | 42 ++++++++--------- src/renderer/src/App.tsx | 44 ++++++++--------- src/renderer/src/WorkspacePrimitives.test.tsx | 12 ----- src/renderer/src/styles.css | 47 ------------------- 5 files changed, 40 insertions(+), 106 deletions(-) diff --git a/UI-DESIGN.md b/UI-DESIGN.md index d4ef9e5a..f3769edd 100644 --- a/UI-DESIGN.md +++ b/UI-DESIGN.md @@ -35,6 +35,7 @@ - 加载、空内容、无结果、失败、只读和权限不足是不同状态,必须分别呈现。 - 可撤销操作优先提供撤销,不可撤销操作必须在执行前说明影响范围。 - 不依赖颜色单独表达状态或风险。 +- 当前模型或 Runtime 及切换进度集中显示在会话输入区,顶栏不重复显示可用性标识。切换失败、自动状态检查失败或返回不可用时,通过应用通知说明原因;发送按钮保留关联的无障碍状态说明。 ### 2.5 默认支持浅色与深色主题 diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx index a72827fb..04516f07 100644 --- a/src/renderer/src/App.test.tsx +++ b/src/renderer/src/App.test.tsx @@ -2109,22 +2109,8 @@ describe("App", () => { await screen.findByRole("heading", { name: "今天想一起完成什么?", }); - const runtimeStatus = - document.querySelector(".runtime-status"); - expect(runtimeStatus).not.toBeNull(); - expect(within(runtimeStatus!).queryByText("就绪")).not.toBeInTheDocument(); - expect(runtimeStatus).toHaveClass("runtime-status--ready"); - expect( - runtimeStatus?.querySelector(".runtime-status__dot"), - ).toBeInTheDocument(); - expect(runtimeStatus).toHaveAttribute( - "aria-describedby", - "topbar-runtime-detail", - ); - expect(screen.getByText("Ready", { selector: ".sr-only" })).toHaveAttribute( - "id", - "topbar-runtime-detail", - ); + expect(document.querySelector(".runtime-status")).not.toBeInTheDocument(); + expect(document.getElementById("topbar-runtime-detail")).toBeNull(); expect( screen.getByText( /你好,我是 GoodBuddy。你可以直接向我提问、添加本地文件/u, @@ -2137,14 +2123,24 @@ describe("App", () => { ).not.toBeInTheDocument(); }); - it("uses a compact color indicator while connecting to a Runtime", () => { + it("notifies when automatic Runtime status checks fail", async () => { + vi.mocked(api.conversations.list).mockResolvedValue([{ + id: "00000000-0000-4000-8000-000000000951", + projectId, + title: "Runtime check", + updatedAt: Date.now(), + messages: [], + runtimeSelection: { provider: "opencode" }, + }]); + vi.mocked(api.agent.getStatus).mockImplementation(async (selection) => { + if (selection) throw new Error("Runtime status check failed"); + return { id: "model", label: "sonnet-5", available: true, supportsToolExecution: true, detail: "Ready" }; + }); render(); - const connectingStatus = - document.querySelector(".runtime-status"); - expect(connectingStatus).toHaveClass("runtime-status--connecting"); - expect( - within(connectingStatus!).queryByText("连接中"), - ).not.toBeInTheDocument(); + expect(await screen.findByRole("alert")).toHaveTextContent("Runtime status check failed"); + const sendButton = screen.getByRole("button", { name: "发送" }); + expect(sendButton).toBeDisabled(); + expect(sendButton).toHaveAccessibleDescription("Runtime status check failed"); }); it("loads customized branding into the primary sidebar", async () => { diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 377b4949..2560c69a 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -3952,6 +3952,16 @@ function App(): React.JSX.Element { } setRuntime(status); setRuntimeStatusKey(activeRuntimeSelectionKey); + if (!status.available) { + notify({ + tone: "error", + message: tRef.current("runtime.selectionUnavailable", { + label: status.label, + detail: status.detail, + }), + dedupeKey: "runtime-status", + }); + } if (!status.available && !runtimeSetupPromptedRef.current) { runtimeSetupPromptedRef.current = true; setView("settings"); @@ -3961,19 +3971,20 @@ function App(): React.JSX.Element { if (runtimeStatusRequestRef.current !== requestId) { return; } + const detail = reason instanceof Error + ? reason.message + : tRef.current("runtime.errors.readStatus"); setRuntime({ id: "setup", label: tRef.current("runtime.unavailable"), available: false, supportsToolExecution: false, - detail: - reason instanceof Error - ? reason.message - : tRef.current("runtime.errors.readStatus"), + detail, }); setRuntimeStatusKey(activeRuntimeSelectionKey); + notify({ tone: "error", message: detail, dedupeKey: "runtime-status" }); }); - }, [activeRuntimeSelectionKey, runtimeSettings, setView]); + }, [activeRuntimeSelectionKey, runtimeSettings, setView, notify]); const startNewConversation = useCallback( (projectId?: string, preview?: { ready: (conversation: Conversation) => void }): boolean => { @@ -8852,7 +8863,7 @@ function App(): React.JSX.Element { : runtime.available ? "ready" : "unavailable"; - const runtimeDetailId = "topbar-runtime-detail"; + const runtimeDetailId = "composer-runtime-detail"; const runtimeDetail = runtimeState === "connecting" ? t("runtime.connecting") @@ -9735,24 +9746,6 @@ function App(): React.JSX.Element { )}
- - - - {runtime?.label ?? t("runtime.detecting")} - - {runtime?.capability === "image-generation" && ( - - {t("runtime.imageGeneration")} - - )} - - - {runtimeDetail} - )} + + {runtimeDetail} +