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}