From a6d24620769e2e44f34b3dfdb7c3cf76b1d569f0 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Sat, 28 Feb 2026 22:05:18 -0500 Subject: [PATCH] fix(site): preserve running spinner on agents sidebar row hover (#22452) When hovering over a running/pending chat in the agents sidebar, the spinning status icon was being replaced by the expand/collapse chevron button. This was disorienting because the spinner conveys important "in progress" state. ## Changes **`AgentsSidebar.tsx`**: - Added `group/icon` scoped hover group to the icon container div - When a chat is executing (`pending`/`running`), the chevron toggle only appears on hover of the icon area itself, not the entire row - Non-executing chats retain the original whole-row hover behavior (no UX change) **`AgentsSidebar.stories.tsx`**: - Added `RunningChatPreservesSpinner` story verifying the spinner is present and the toggle button starts invisible for running chats with children Co-authored-by: Coder --- .../AgentsPage/AgentsSidebar.stories.tsx | 43 +++++++++++++++++++ site/src/pages/AgentsPage/AgentsSidebar.tsx | 22 ++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx b/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx index 275e64c30b..a2efccfea7 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx @@ -197,6 +197,49 @@ export const ExpandCollapse: Story = { }, }; +export const RunningChatPreservesSpinner: Story = { + args: { + chats: [ + buildChat({ + id: "root-running", + title: "Running root agent", + status: "running", + }), + buildChat({ + id: "child-of-running", + title: "Child of running", + parent_chat_id: "root-running", + root_chat_id: "root-running", + }), + ], + }, + parameters: { + reactRouter: reactRouterParameters({ + location: { + path: "/agents/child-of-running", + pathParams: { agentId: "child-of-running" }, + }, + routing: agentsRouting, + }), + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + // The root chat is running and has children, so a spinning + // Loader2Icon should be rendered inside the icon wrapper. + const node = canvas.getByTestId("agents-tree-node-root-running"); + const spinner = node.querySelector(".animate-spin"); + await expect(spinner).toBeInTheDocument(); + + // The toggle button should exist (the node has children) but + // must be invisible by default — it only appears on hover of + // the icon area itself, not the whole row. + const toggle = canvas.getByTestId("agents-tree-toggle-root-running"); + await expect(toggle).toBeInTheDocument(); + await expect(toggle.className).toMatch(/\binvisible\b/); + }, +}; + export const ActiveChatAncestryExpanded: Story = { args: { chats: [ diff --git a/site/src/pages/AgentsPage/AgentsSidebar.tsx b/site/src/pages/AgentsPage/AgentsSidebar.tsx index e5ed8e44b6..0969789c60 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.tsx @@ -327,6 +327,7 @@ 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"; return (
@@ -340,11 +341,21 @@ const ChatTreeNode = memo(({ chat, isChildNode }) => { "before:absolute before:-left-2.5 before:top-[17px] before:h-px before:w-2.5 before:bg-border-default/70", )} > -
+
(({ chat, isChildNode }) => { variant="subtle" size="icon" onClick={() => toggleExpanded(chatID)} - className="absolute inset-0 invisible flex h-5 w-5 min-w-0 items-center justify-center rounded-md p-0 text-content-secondary/60 hover:text-content-primary [@media(hover:hover)]:group-hover:visible [&>svg]:size-3.5" + className={cn( + "absolute inset-0 invisible flex h-5 w-5 min-w-0 items-center justify-center rounded-md p-0 text-content-secondary/60 hover:text-content-primary [&>svg]:size-3.5", + isExecuting + ? "[@media(hover:hover)]:group-hover/icon:visible" + : "[@media(hover:hover)]:group-hover:visible", + )} data-testid={`agents-tree-toggle-${chat.id}`} aria-label={isExpanded ? "Collapse" : "Expand"} aria-expanded={isExpanded}