From 24316e41f7f54ac161cd2e3223eaa7c764de79e1 Mon Sep 17 00:00:00 2001 From: George Pickett Date: Tue, 24 Feb 2026 12:18:39 -0800 Subject: [PATCH] Replace Idle filter with Approvals and close model tooltip on select --- .../agents/components/AgentChatPanel.tsx | 1 + .../agents/components/FleetSidebar.tsx | 2 +- src/features/agents/state/store.tsx | 6 ++--- src/lib/studio/settings.ts | 5 +++-- tests/unit/agentChatPanel-controls.test.ts | 9 ++++++-- tests/unit/agentStore.test.ts | 6 ++--- tests/unit/fleetSidebar-create.test.ts | 16 ++++++++++++++ tests/unit/studioBootstrapOperation.test.ts | 4 ++-- tests/unit/studioBootstrapWorkflow.test.ts | 4 ++-- tests/unit/studioSettings.test.ts | 22 +++++++++++++++++-- tests/unit/studioSettingsCoordinator.test.ts | 2 +- 11 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/features/agents/components/AgentChatPanel.tsx b/src/features/agents/components/AgentChatPanel.tsx index 19b95cd..1f80553 100644 --- a/src/features/agents/components/AgentChatPanel.tsx +++ b/src/features/agents/components/AgentChatPanel.tsx @@ -963,6 +963,7 @@ const AgentChatComposer = memo(function AgentChatComposer({ onChange={(event) => { const nextValue = event.target.value.trim(); onModelChange(nextValue ? nextValue : null); + event.currentTarget.blur(); }} > {modelOptions.length === 0 ? ( diff --git a/src/features/agents/components/FleetSidebar.tsx b/src/features/agents/components/FleetSidebar.tsx index 5ca5998..ff3f48b 100644 --- a/src/features/agents/components/FleetSidebar.tsx +++ b/src/features/agents/components/FleetSidebar.tsx @@ -22,7 +22,7 @@ type FleetSidebarProps = { const FILTER_OPTIONS: Array<{ value: FocusFilter; label: string; testId: string }> = [ { value: "all", label: "All", testId: "fleet-filter-all" }, { value: "running", label: "Running", testId: "fleet-filter-running" }, - { value: "idle", label: "Idle", testId: "fleet-filter-idle" }, + { value: "approvals", label: "Approvals", testId: "fleet-filter-approvals" }, ]; export const FleetSidebar = ({ diff --git a/src/features/agents/state/store.tsx b/src/features/agents/state/store.tsx index cacdc1d..33479fe 100644 --- a/src/features/agents/state/store.tsx +++ b/src/features/agents/state/store.tsx @@ -20,7 +20,7 @@ import { } from "@/features/agents/state/transcript"; export type AgentStatus = "idle" | "running" | "error"; -export type FocusFilter = "all" | "running" | "idle"; +export type FocusFilter = "all" | "running" | "approvals"; export type AgentStoreSeed = { agentId: string; @@ -526,8 +526,8 @@ export const getFilteredAgents = (state: AgentStoreState, filter: FocusFilter): return sortAgents(state.agents, true); case "running": return sortAgents(state.agents.filter((agent) => agent.status === "running"), false); - case "idle": - return sortAgents(state.agents.filter((agent) => agent.status === "idle"), false); + case "approvals": + return sortAgents(state.agents.filter((agent) => agent.awaitingUserInput), false); default: { const _exhaustive: never = filter; void _exhaustive; diff --git a/src/lib/studio/settings.ts b/src/lib/studio/settings.ts index e4756b7..7e52f5f 100644 --- a/src/lib/studio/settings.ts +++ b/src/lib/studio/settings.ts @@ -3,7 +3,7 @@ export type StudioGatewaySettings = { token: string; }; -export type FocusFilter = "all" | "running" | "idle"; +export type FocusFilter = "all" | "running" | "approvals"; export type StudioViewMode = "focused"; export type StudioFocusedPreference = { @@ -66,10 +66,11 @@ const normalizeFocusFilter = ( ): FocusFilter => { const filter = coerceString(value); if (filter === "needs-attention") return "all"; + if (filter === "idle") return "approvals"; if ( filter === "all" || filter === "running" || - filter === "idle" + filter === "approvals" ) { return filter; } diff --git a/tests/unit/agentChatPanel-controls.test.ts b/tests/unit/agentChatPanel-controls.test.ts index e24d735..06814d2 100644 --- a/tests/unit/agentChatPanel-controls.test.ts +++ b/tests/unit/agentChatPanel-controls.test.ts @@ -222,7 +222,7 @@ describe("AgentChatPanel controls", () => { expect(runningBadge).toBeNull(); }); - it("invokes_on_model_change_when_model_select_changes", () => { + it("invokes_on_model_change_when_model_select_changes_and_blurs_select", () => { const onModelChange = vi.fn(); render( createElement(AgentChatPanel, { @@ -242,10 +242,15 @@ describe("AgentChatPanel controls", () => { }) ); - fireEvent.change(screen.getByLabelText("Model"), { + const modelSelect = screen.getByLabelText("Model") as HTMLSelectElement; + modelSelect.focus(); + expect(modelSelect).toHaveFocus(); + + fireEvent.change(modelSelect, { target: { value: "openai/gpt-5-mini" }, }); expect(onModelChange).toHaveBeenCalledWith("openai/gpt-5-mini"); + expect(modelSelect).not.toHaveFocus(); }); it("invokes_on_thinking_change_when_thinking_select_changes", () => { diff --git a/tests/unit/agentStore.test.ts b/tests/unit/agentStore.test.ts index dda3e87..c3289fb 100644 --- a/tests/unit/agentStore.test.ts +++ b/tests/unit/agentStore.test.ts @@ -261,7 +261,7 @@ describe("agent store", () => { expect(cleared?.hasUnseenActivity).toBe(false); }); - it("filters_agents_by_status", () => { + it("filters_agents_by_status_and_approvals", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", @@ -286,7 +286,7 @@ describe("agent store", () => { state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", - patch: { status: "idle" }, + patch: { status: "idle", awaitingUserInput: true }, }); state = agentStoreReducer(state, { type: "updateAgent", @@ -307,7 +307,7 @@ describe("agent store", () => { expect(getFilteredAgents(state, "running").map((agent) => agent.agentId)).toEqual([ "agent-2", ]); - expect(getFilteredAgents(state, "idle").map((agent) => agent.agentId)).toEqual([ + expect(getFilteredAgents(state, "approvals").map((agent) => agent.agentId)).toEqual([ "agent-1", ]); }); diff --git a/tests/unit/fleetSidebar-create.test.ts b/tests/unit/fleetSidebar-create.test.ts index bbd502d..4dd13bb 100644 --- a/tests/unit/fleetSidebar-create.test.ts +++ b/tests/unit/fleetSidebar-create.test.ts @@ -93,6 +93,22 @@ describe("FleetSidebar new agent action", () => { expect(screen.getByTestId("fleet-new-agent-button")).toBeDisabled(); }); + it("shows approvals tab instead of idle tab", () => { + render( + createElement(FleetSidebar, { + agents: [createAgent()], + selectedAgentId: "agent-1", + filter: "all", + onFilterChange: vi.fn(), + onSelectAgent: vi.fn(), + onCreateAgent: vi.fn(), + }) + ); + + expect(screen.getByTestId("fleet-filter-approvals")).toBeInTheDocument(); + expect(screen.queryByTestId("fleet-filter-idle")).toBeNull(); + }); + it("shows needs approval badge for awaiting agents", () => { render( createElement(FleetSidebar, { diff --git a/tests/unit/studioBootstrapOperation.test.ts b/tests/unit/studioBootstrapOperation.test.ts index 7c42340..d75d974 100644 --- a/tests/unit/studioBootstrapOperation.test.ts +++ b/tests/unit/studioBootstrapOperation.test.ts @@ -238,7 +238,7 @@ describe("studioBootstrapOperation", () => { commands: [ { kind: "set-focused-preferences-loaded", value: false }, { kind: "set-preferred-selected-agent-id", agentId: "agent-1" }, - { kind: "set-focus-filter", filter: "idle" }, + { kind: "set-focus-filter", filter: "approvals" }, { kind: "log-error", message: "failed", error: new Error("boom") }, ], setFocusedPreferencesLoaded, @@ -249,7 +249,7 @@ describe("studioBootstrapOperation", () => { expect(setFocusedPreferencesLoaded).toHaveBeenCalledWith(false); expect(setPreferredSelectedAgentId).toHaveBeenCalledWith("agent-1"); - expect(setFocusFilter).toHaveBeenCalledWith("idle"); + expect(setFocusFilter).toHaveBeenCalledWith("approvals"); expect(logError).toHaveBeenCalledTimes(1); }); diff --git a/tests/unit/studioBootstrapWorkflow.test.ts b/tests/unit/studioBootstrapWorkflow.test.ts index 89f5325..6da1207 100644 --- a/tests/unit/studioBootstrapWorkflow.test.ts +++ b/tests/unit/studioBootstrapWorkflow.test.ts @@ -150,7 +150,7 @@ describe("studioBootstrapWorkflow", () => { "https://gateway.test": { mode: "focused", selectedAgentId: "agent-3", - filter: "idle", + filter: "approvals", }, }, avatars: {}, @@ -164,7 +164,7 @@ describe("studioBootstrapWorkflow", () => { }) ).toEqual({ preferredSelectedAgentId: "agent-3", - focusFilter: "idle", + focusFilter: "approvals", }); expect( diff --git a/tests/unit/studioSettings.test.ts b/tests/unit/studioSettings.test.ts index 5eb17ae..a49c5f3 100644 --- a/tests/unit/studioSettings.test.ts +++ b/tests/unit/studioSettings.test.ts @@ -59,6 +59,24 @@ describe("studio settings normalization", () => { }); }); + it("normalizes_legacy_idle_filter_to_approvals", () => { + const normalized = normalizeStudioSettings({ + focused: { + "ws://localhost:18789": { + mode: "focused", + selectedAgentId: "agent-1", + filter: "idle", + }, + }, + }); + + expect(normalized.focused["ws://localhost:18789"]).toEqual({ + mode: "focused", + selectedAgentId: "agent-1", + filter: "approvals", + }); + }); + it("merges_dual_mode_preferences", () => { const current = normalizeStudioSettings({ focused: { @@ -73,7 +91,7 @@ describe("studio settings normalization", () => { const merged = mergeStudioSettings(current, { focused: { "ws://localhost:18789": { - filter: "idle", + filter: "approvals", }, }, }); @@ -81,7 +99,7 @@ describe("studio settings normalization", () => { expect(merged.focused["ws://localhost:18789"]).toEqual({ mode: "focused", selectedAgentId: "main", - filter: "idle", + filter: "approvals", }); }); diff --git a/tests/unit/studioSettingsCoordinator.test.ts b/tests/unit/studioSettingsCoordinator.test.ts index 4c4cebc..874103d 100644 --- a/tests/unit/studioSettingsCoordinator.test.ts +++ b/tests/unit/studioSettingsCoordinator.test.ts @@ -77,7 +77,7 @@ describe("StudioSettingsCoordinator", () => { focused: { "ws://localhost:18789": { mode: "focused", - filter: "idle", + filter: "approvals", selectedAgentId: null, }, },