From ebe8c8a5b41e6d5198c6602965ba4d09824153ee Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Mon, 2 Mar 2026 09:20:46 -0500 Subject: [PATCH] fix(site): scope chevron to icon hover when any child is running (#22456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #22452. The previous fix only checked the chat's own status, so a root chat in `waiting` status with actively running sub-agents still showed the expand/collapse chevron on full-row hover. ## Problem A root chat that's idle (`waiting`/`completed`) but has running sub-agents would still swap its status icon for the `>` chevron on row hover. The fix in #22452 only gated on `chat.status` being `pending`/`running`, which doesn't cover the parent when sub-agents are the ones executing. ## Fix `isExecuting` now also checks whether **any direct child** is `pending`/`running`: ```ts const isExecuting = chat.status === "pending" || chat.status === "running" || (hasChildren && childIDs.some((id) => { const c = chatById.get(id); return c?.status === "pending" || c?.status === "running"; })); ``` When `isExecuting` is true, the chevron only appears on hover of the icon area itself (`group-hover/icon`), not the entire row. ## New story Added `IdleParentWithRunningChild` — verifies a `waiting` parent with a `running` child uses icon-only hover scope for the toggle. Co-authored-by: Coder --- .../AgentsPage/AgentsSidebar.stories.tsx | 40 +++++++++++++++++++ site/src/pages/AgentsPage/AgentsSidebar.tsx | 9 ++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx b/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx index a2efccfea7..54184d7e83 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx @@ -240,6 +240,46 @@ export const RunningChatPreservesSpinner: Story = { }, }; +// When a root chat is idle but has a running child, the chevron +// should still be scoped to the icon area hover, not the full row. +export const IdleParentWithRunningChild: Story = { + args: { + chats: [ + buildChat({ + id: "idle-parent", + title: "Idle parent agent", + status: "waiting", + }), + buildChat({ + id: "running-child", + title: "Running sub-agent", + status: "running", + parent_chat_id: "idle-parent", + root_chat_id: "idle-parent", + }), + ], + }, + parameters: { + reactRouter: reactRouterParameters({ + location: { + path: "/agents/running-child", + pathParams: { agentId: "running-child" }, + }, + routing: agentsRouting, + }), + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + // The parent's toggle should use icon-only hover scope because + // its child is actively running. + const toggle = canvas.getByTestId("agents-tree-toggle-idle-parent"); + await expect(toggle).toBeInTheDocument(); + await expect(toggle.className).toMatch(/\binvisible\b/); + await expect(toggle.className).toContain("group-hover/icon:visible"); + }, +}; + export const ActiveChatAncestryExpanded: Story = { args: { chats: [ diff --git a/site/src/pages/AgentsPage/AgentsSidebar.tsx b/site/src/pages/AgentsPage/AgentsSidebar.tsx index 0969789c60..965337b0f5 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.tsx @@ -327,7 +327,14 @@ const ChatTreeNode = memo(({ chat, isChildNode }) => { }`; const isArchivingThisChat = isArchiving && archivingChatId === chat.id; const isExpanded = normalizedSearch ? true : (expandedById[chatID] ?? false); - const isExecuting = chat.status === "pending" || chat.status === "running"; + const isExecuting = + chat.status === "pending" || + chat.status === "running" || + (hasChildren && + childIDs.some((id) => { + const c = chatById.get(id); + return c?.status === "pending" || c?.status === "running"; + })); return (