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 <coder@users.noreply.github.com>
This commit is contained in:
Kyle Carberry
2026-02-28 22:05:18 -05:00
committed by GitHub
co-authored by Coder
parent fb6bf3a568
commit a6d2462076
2 changed files with 62 additions and 3 deletions
@@ -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: [
+19 -3
View File
@@ -327,6 +327,7 @@ const ChatTreeNode = memo<ChatTreeNodeProps>(({ chat, isChildNode }) => {
}`;
const isArchivingThisChat = isArchiving && archivingChatId === chat.id;
const isExpanded = normalizedSearch ? true : (expandedById[chatID] ?? false);
const isExecuting = chat.status === "pending" || chat.status === "running";
return (
<div className="flex min-w-0 flex-col">
@@ -340,11 +341,21 @@ const ChatTreeNode = memo<ChatTreeNodeProps>(({ chat, isChildNode }) => {
"before:absolute before:-left-2.5 before:top-[17px] before:h-px before:w-2.5 before:bg-border-default/70",
)}
>
<div className="relative mt-1.5 h-5 w-5 shrink-0">
<div
className={cn(
"group/icon relative mt-1.5 h-5 w-5 shrink-0",
hasChildren && "cursor-pointer",
)}
>
<div
className={cn(
"flex h-5 w-5 items-center justify-center rounded-md",
hasChildren && "[@media(hover:hover)]:group-hover:invisible",
hasChildren &&
!isExecuting &&
"[@media(hover:hover)]:group-hover:invisible",
hasChildren &&
isExecuting &&
"[@media(hover:hover)]:group-hover/icon:invisible",
)}
>
<StatusIcon
@@ -361,7 +372,12 @@ const ChatTreeNode = memo<ChatTreeNodeProps>(({ 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}