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.
This commit is contained in:
@@ -2109,22 +2109,8 @@ describe("App", () => {
|
||||
await screen.findByRole("heading", {
|
||||
name: "今天想一起完成什么?",
|
||||
});
|
||||
const runtimeStatus =
|
||||
document.querySelector<HTMLElement>(".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(<App />);
|
||||
const connectingStatus =
|
||||
document.querySelector<HTMLElement>(".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 () => {
|
||||
|
||||
+20
-24
@@ -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 {
|
||||
</>
|
||||
)}
|
||||
<div className="topbar__actions">
|
||||
<span
|
||||
aria-describedby={runtimeDetailId}
|
||||
className={`runtime-status runtime-status--${runtimeState}`}
|
||||
title={runtimeDetail}
|
||||
>
|
||||
<span className="runtime-status__dot" />
|
||||
<span className="runtime-status__label">
|
||||
{runtime?.label ?? t("runtime.detecting")}
|
||||
</span>
|
||||
{runtime?.capability === "image-generation" && (
|
||||
<span className="runtime-capability-badge">
|
||||
{t("runtime.imageGeneration")}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="sr-only" id={runtimeDetailId}>
|
||||
{runtimeDetail}
|
||||
</span>
|
||||
<button
|
||||
aria-controls="assistant-sidebar"
|
||||
aria-expanded={assistantSidebarOpen}
|
||||
@@ -10829,6 +10822,9 @@ function App(): React.JSX.Element {
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
<span className="sr-only" id={runtimeDetailId}>
|
||||
{runtimeDetail}
|
||||
</span>
|
||||
<button
|
||||
aria-describedby={
|
||||
runtimeState !== "ready"
|
||||
|
||||
@@ -84,18 +84,6 @@ describe('WorkspacePrimitives', () => {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it('uses semantic colors for compact Runtime status dots', () => {
|
||||
expect(stylesheet).toMatch(
|
||||
/\.runtime-status--ready \.runtime-status__dot\s*\{[^}]*background:\s*var\(--success\);/u
|
||||
)
|
||||
expect(stylesheet).toMatch(
|
||||
/\.runtime-status--connecting \.runtime-status__dot\s*\{[^}]*background:\s*var\(--warning\);/u
|
||||
)
|
||||
expect(stylesheet).toMatch(
|
||||
/\.runtime-status--unavailable \.runtime-status__dot\s*\{[^}]*background:\s*var\(--text-muted\);/u
|
||||
)
|
||||
})
|
||||
|
||||
it('wraps long model names, prompts, and errors inside shared message status cards', () => {
|
||||
expect(stylesheet).toMatch(
|
||||
/\.message-retrieval-status\s*\{[^}]*overflow-wrap:\s*anywhere;/u
|
||||
|
||||
@@ -5419,51 +5419,6 @@ button > svg {
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.runtime-status {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
max-width: clamp(108px, 18vw, 220px);
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-default);
|
||||
border-radius: 8px;
|
||||
margin-right: 8px;
|
||||
background: var(--surface-raised);
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--font-caption);
|
||||
font-weight: 600;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.runtime-status__dot {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.runtime-status__label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.runtime-status__dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
.runtime-status--ready .runtime-status__dot {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.runtime-status--connecting .runtime-status__dot {
|
||||
background: var(--warning);
|
||||
}
|
||||
|
||||
.runtime-status--unavailable .runtime-status__dot {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
.chat-scroll-region {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
@@ -16234,7 +16189,6 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
.user-card,
|
||||
.icon-button,
|
||||
.assistant-sidebar__row,
|
||||
.runtime-status,
|
||||
.welcome__description,
|
||||
.tool-activity,
|
||||
.context-chip,
|
||||
@@ -16822,7 +16776,6 @@ details.settings-section > :not(summary) + :not(summary) {
|
||||
|
||||
:root :is(
|
||||
.task-status-dot,
|
||||
.runtime-status__dot,
|
||||
.heartbeat-center__pulse-dot,
|
||||
.heartbeat-center__timeline-dot
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user