diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index d526fa1f5f2..557e9cda20c 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -1013,10 +1013,8 @@ animation: compact-agent-expansion-reveal 180ms cubic-bezier(0.16, 1, 0.3, 1) both; } -/* Why: the expanded summary becomes one enclosed panel. Keep the box model - stable across states (padding always reserved, border drawn as a non-layout - box-shadow) and fade only the chrome, so toggling doesn't snap the header and - rows while the content height animates. */ +/* Why: the collapsed summary still uses pill spacing, while expanded compact + agents read as a quiet tree inside the existing worktree card. */ .compact-agent-summary-panel { padding: 2px; border-radius: var(--radius-sm, 0.25rem); @@ -1028,8 +1026,61 @@ } .compact-agent-summary-panel-expanded { - background: color-mix(in srgb, var(--sidebar-accent) 20%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sidebar-border) 70%, transparent); + background: transparent; + box-shadow: inset 0 0 0 1px transparent; +} + +[data-compact-agent-list='true'] { + margin-inline-start: -0.5rem; + width: calc(100% + 0.5rem); +} + +/* Why: compact expanded agents live inside an already-framed worktree card. + Keep the tree legible with indentation and interaction states instead of + stacking row cards and shaded panels. */ +[data-compact-agent-list='true'] .compact-agent-row.worktree-agent-lineage-parent-row, +[data-compact-agent-list='true'] .compact-agent-row.worktree-agent-lineage-child-row { + background: transparent; + box-shadow: none; +} + +[data-compact-agent-list='true'] .compact-agent-row.worktree-agent-lineage-child-row::before { + content: none; +} + +[data-compact-agent-list='true'] + .compact-agent-row.worktree-agent-lineage-parent-row.worktree-agent-row-hover:hover, +[data-compact-agent-list='true'] + .compact-agent-row.worktree-agent-lineage-child-row.worktree-agent-row-hover:hover { + background: color-mix(in srgb, var(--sidebar-foreground) 1.25%, transparent); +} + +.dark + [data-compact-agent-list='true'] + .compact-agent-row.worktree-agent-lineage-parent-row.worktree-agent-row-hover:hover, +.dark + [data-compact-agent-list='true'] + .compact-agent-row.worktree-agent-lineage-child-row.worktree-agent-row-hover:hover { + background: color-mix(in srgb, var(--accent) 18%, transparent); +} + +[data-compact-agent-list='true'] + .compact-agent-row.worktree-agent-row-hover[data-focused-agent-pane='true'] { + background: color-mix(in srgb, var(--sidebar-foreground) 12%, var(--sidebar-accent)); +} + +.dark + [data-compact-agent-list='true'] + .compact-agent-row.worktree-agent-row-hover[data-focused-agent-pane='true'] { + background: color-mix(in srgb, var(--accent) 70%, transparent); +} + +[data-compact-agent-list='true'] .worktree-agent-lineage-children { + border-left-color: color-mix(in srgb, var(--sidebar-foreground) 16%, transparent); +} + +.dark [data-compact-agent-list='true'] .worktree-agent-lineage-children { + border-left-color: color-mix(in srgb, var(--accent) 20%, transparent); } @media (prefers-reduced-motion: reduce) { diff --git a/src/renderer/src/components/sidebar/WorktreeCard.tsx b/src/renderer/src/components/sidebar/WorktreeCard.tsx index 7399de94c80..5235e5a3bac 100644 --- a/src/renderer/src/components/sidebar/WorktreeCard.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCard.tsx @@ -662,6 +662,7 @@ const WorktreeCard = React.memo(function WorktreeCard({ : hasDetailedMetaRowContent const showHeaderActions = showTitleRowUnread || showTitleRowPrimary || showDeleteQuickAction const showBranchIdentityHover = compactCards && showBranch + const showInlineAgentList = showDetailedCardProperties && cardProps.includes('inline-agents') // Why: sidebar rows need a small surface inset, while their content remains // aligned with the pre-inset layout and the repo header hierarchy. const cardStyle = flushSurface @@ -808,7 +809,9 @@ const WorktreeCard = React.memo(function WorktreeCard({
{/* Header row: Title */} @@ -1054,7 +1057,7 @@ const WorktreeCard = React.memo(function WorktreeCard({ naturally when agents appear/disappear. When agents directly follow the title, counterbalance the card stack gap so both rows read as one compact header group. */} - {showDetailedCardProperties && cardProps.includes('inline-agents') && ( + {showInlineAgentList && ( ({ onSendTargetClick, childAgentCount, childAgentsExpanded, - onToggleChildAgents + onToggleChildAgents, + reserveDisclosureGutter }: { agent: { paneKey: string } isFocusedPane?: boolean @@ -107,6 +108,7 @@ vi.mock('@/components/dashboard/DashboardAgentRow', () => ({ childAgentCount?: number childAgentsExpanded?: boolean onToggleChildAgents?: () => void + reserveDisclosureGutter?: boolean }) => (
({ data-disabled-reason={sendTargetDisabledReason} data-has-send-handler={typeof onSendTargetClick === 'function' ? 'true' : 'false'} data-pane-key={agent.paneKey} + data-reserve-disclosure-gutter={reserveDisclosureGutter ? 'true' : 'false'} > {agent.paneKey} {typeof childAgentCount === 'number' && childAgentCount > 0 ? ( @@ -222,6 +225,7 @@ describe('WorktreeCardAgents', () => { expect(markup).toContain('role="tree"') expect(markup).toContain('data-pane-key="tab-parent:1"') expect(markup).toContain('data-pane-key="tab-child:1"') + expect(markup).toContain('data-pane-key="tab-child:1" data-reserve-disclosure-gutter="false"') expect(markup).toContain('aria-label="Hide 1 child agent"') expect(markup).toContain('aria-expanded="true"') }) @@ -362,6 +366,7 @@ describe('WorktreeCardAgents', () => { const markup = renderToStaticMarkup() + expect(markup).toContain('compact-agent-row') expect(markup).toContain('group/compact-agent-row') expect(markup).toContain(' { expect(markup).toContain('aria-expanded="true"') expect(markup).toContain('Collapse 5 agents') + expect(markup).toContain('compact-agent-summary-button-expanded') expect(markup).toContain('>5 agents<') expect(markup).not.toContain('>+2<') expect(markup).not.toContain('Expand All 5 agents working') @@ -568,12 +574,14 @@ describe('WorktreeCardAgents', () => { const markup = renderToStaticMarkup() + expect(markup).toContain('data-compact-agent-list="true"') expect(markup).toContain('role="tree"') - expect(markup).toContain('3 parents: 1 waiting, 1 working, 1 done') + expect(markup).toContain('3 agents: 1 waiting, 1 working, 1 done') expect(markup).not.toContain('title="Gemini waiting"') expect(markup).not.toContain('title="Codex working"') expect(markup).not.toContain('title="Codex done"') expect(markup).not.toContain('Parent A') expect(markup).not.toContain('Child A') + expect(markup).not.toContain('compact-agent-row') }) }) diff --git a/src/renderer/src/components/sidebar/WorktreeCardAgents.tsx b/src/renderer/src/components/sidebar/WorktreeCardAgents.tsx index fcce9b8711b..a97b1637c66 100644 --- a/src/renderer/src/components/sidebar/WorktreeCardAgents.tsx +++ b/src/renderer/src/components/sidebar/WorktreeCardAgents.tsx @@ -253,11 +253,10 @@ const WorktreeCardAgentsBody = React.memo(function WorktreeCardAgentsBody({ e.stopPropagation() }, []) - // Why: when any root row has a disclosure chevron, leaf siblings reserve a - // matching leading spacer so the state-dot column stays aligned across the - // card. Without this, parent rows shift right by the chevron's width while - // leaf rows hug the gutter — visible misalignment when the user sweeps the - // leading column. + // Why: when any root row has a disclosure chevron, root leaf siblings reserve + // a matching leading spacer so the state-dot column stays aligned across the + // card. Descendants already have the child rail indent, so adding this spacer + // there double-indents child agents. const anyRootHasChildren = rootAgents.some( (agent) => (childrenByParentPaneKey.get(agent.paneKey) ?? []).length > 0 ) @@ -273,6 +272,7 @@ const WorktreeCardAgentsBody = React.memo(function WorktreeCardAgentsBody({ } const childAgents = childrenByParentPaneKey.get(agent.paneKey) ?? [] const hasChildAgents = childAgents.length > 0 + const isRootAgent = ancestorPaneKeys.size === 0 // Why: spawned child agents are actionable work, so they should be visible // as soon as the parent appears; the disclosure remains available to fold noise. const expanded = !collapsedLineageParents.has(agent.paneKey) @@ -315,7 +315,7 @@ const WorktreeCardAgentsBody = React.memo(function WorktreeCardAgentsBody({ } // Why: keep leaf rows aligned with parent rows in the same card — // see anyRootHasChildren above. - reserveDisclosureGutter={anyRootHasChildren && !hasChildAgents} + reserveDisclosureGutter={isRootAgent && anyRootHasChildren && !hasChildAgents} isFocusedPane={agent.paneKey === focusedAgentPaneKey} sendTargetStatus={sendTarget?.status} sendTargetDisabledReason={sendTarget?.disabledReason} @@ -346,6 +346,7 @@ const WorktreeCardAgentsBody = React.memo(function WorktreeCardAgentsBody({ } const childAgents = childrenByParentPaneKey.get(agent.paneKey) ?? [] const hasChildAgents = childAgents.length > 0 + const isRootAgent = ancestorPaneKeys.size === 0 const expanded = !collapsedLineageParents.has(agent.paneKey) const descendantAncestorPaneKeys = new Set(ancestorPaneKeys) descendantAncestorPaneKeys.add(agent.paneKey) @@ -360,7 +361,7 @@ const WorktreeCardAgentsBody = React.memo(function WorktreeCardAgentsBody({ onToggleChildAgents={ hasChildAgents ? () => toggleLineageParent(agent.paneKey) : undefined } - reserveDisclosureGutter={anyRootHasChildren && !hasChildAgents} + reserveDisclosureGutter={isRootAgent && anyRootHasChildren && !hasChildAgents} isFocusedPane={agent.paneKey === focusedAgentPaneKey} /> {hasChildAgents ? ( @@ -381,9 +382,7 @@ const WorktreeCardAgentsBody = React.memo(function WorktreeCardAgentsBody({ // Why: compact worktree cards keep multiple active agents to a single // predictable status line, even when there are only two agents. const shouldUseSummaryRow = summaryAgents.length > 1 - const subjectLabel = hasLineage - ? `${rootAgents.length} ${rootAgents.length === 1 ? 'parent' : 'parents'}` - : `${agents.length} agents` + const subjectLabel = `${hasLineage ? rootAgents.length : agents.length} agents` return (
{shouldUseSummaryRow ? ( - // Why: when expanded, the header and its agent rows read as a single - // enclosed panel rather than a standalone pill with rows floating - // below it — the border/fill wraps the whole group. + // Why: the worktree card is already the surface. Expanded compact + // agents stay a quiet tree; only the collapsed summary reads as a pill.