Improve experimental worktree cards with compact agent summaries

- Add an optional details row for workspace metadata and indicators
- Roll up descendant activity timestamps to parent cards
This commit is contained in:
Jinjing
2026-11-13 18:25:06 -08:00
parent 6dcb54f71d
commit 006bef5155
17 changed files with 281 additions and 71 deletions
@@ -1,4 +1,5 @@
import React, { useCallback } from 'react'
import { ChevronDown } from 'lucide-react'
import type { DashboardAgentRow } from '@/components/dashboard/useDashboardData'
import { useAgentRowConversationName } from '@/components/dashboard/use-agent-row-conversation-name'
import { translate } from '@/i18n/i18n'
@@ -103,6 +104,46 @@ const SlotAgentLineRow = React.memo(function SlotAgentLineRow({
)
})
/** Expanded agents' header: the label sits in the icon column and the chevron in the slot column. */
export function SlotAgentLinesHeader({
label,
onCollapse
}: {
label: string
onCollapse: () => void
}): React.JSX.Element {
const handleClick = useCallback(
(event: React.MouseEvent) => {
event.stopPropagation()
onCollapse()
},
[onCollapse]
)
return (
<button
type="button"
draggable={false}
className="worktree-agent-row-hover mt-[3px] flex h-[19px] w-full min-w-0 items-center gap-1.5 rounded-sm pr-2 text-left text-2xs leading-none font-medium text-muted-foreground outline-none focus-visible:ring-1 focus-visible:ring-worktree-sidebar-ring"
aria-expanded={true}
aria-label={translate(
'auto.components.sidebar.worktree.card.compact.agents.0c1debfe84',
'Collapse {{value0}}',
{ value0: label }
)}
onClick={handleClick}
onMouseDown={stopPropagation}
onPointerDown={stopPropagation}
>
<span className="min-w-0 flex-1 truncate">{label}</span>
<ChevronDown className="size-3 shrink-0" aria-hidden />
</button>
)
}
function stopPropagation(event: React.SyntheticEvent): void {
event.stopPropagation()
}
/** Experimental 2.0 agent lines: icon, what it's doing or said, and the line's own slot. */
export function WorktreeCardSlotAgentLines({
worktreeId,
@@ -138,7 +179,7 @@ export function WorktreeCardSlotAgentLines({
}
return (
<div
className="mt-px flex flex-col gap-px pl-3.5"
className="mt-px flex flex-col gap-px"
role="group"
aria-label={translate('auto.components.sidebar.WorktreeCardAgents.1b0a156717', 'Agents')}
onDoubleClick={stopBubble}
@@ -7,6 +7,7 @@ import type { Worktree } from '../../../../shared/worktree/types'
import { folderWorkspaceToWorktree } from '../../../../shared/folder-workspaces/folder-workspace-worktree'
import { worktreeCardStyleSettingsUpdate } from '../../../../shared/worktree/card-style'
import WorktreeCard from './workspace-card/surface/WorktreeCard'
import * as agentExpansion from './workspace-card/activity/worktree-card-agents-expansion-state'
const MINUTE = 60_000
const SIDEBAR_WIDTH = 320
@@ -56,6 +57,7 @@ function makeWorktree(seed: WorkspaceSeed, now: number): Worktree {
}
function seedWorkspaces(seeds: readonly WorkspaceSeed[], now: number): void {
agentExpansion.clearWorktreeAgentExpansionStateForTests()
const tabsByWorktree: Record<string, TerminalTab[]> = {}
const agentStatusByPaneKey: Record<string, AgentStatusEntry> = {}
const unreadAgentCompletionPanes: Record<string, 'agent-completion'> = {}
@@ -76,7 +78,9 @@ function seedWorkspaces(seeds: readonly WorkspaceSeed[], now: number): void {
stateHistory: [],
...(agent.toolName ? { toolName: agent.toolName, toolInput: agent.toolInput } : {}),
...(agent.message ? { lastAssistantMessage: agent.message } : {}),
...(agent.failed ? { mainAgent: { state: 'done', outcome: 'failure', stateStartedAt: now } } : {})
...(agent.failed
? { mainAgent: { state: 'done', outcome: 'failure', stateStartedAt: now } }
: {})
}
ptyIdsByTabId[tabId] = [`pty-${tabId}`]
if (agent.unread) {
@@ -309,7 +313,7 @@ function Card({
children,
childCount = 0,
collapsed = false,
hiddenDescendantIds
descendantIds
}: {
worktree: Worktree
nested?: boolean
@@ -317,7 +321,7 @@ function Card({
children?: React.ReactNode
childCount?: number
collapsed?: boolean
hiddenDescendantIds?: readonly string[]
descendantIds?: readonly string[]
}): React.JSX.Element {
return (
<div className="relative" data-worktree-id={worktree.id}>
@@ -330,7 +334,7 @@ function Card({
nativeDragEnabled={false}
lineageChildCount={childCount}
lineageCollapsed={collapsed}
lineageHiddenDescendantIds={hiddenDescendantIds}
lineageDescendantIds={descendantIds}
lineageChildren={children}
onLineageToggle={childCount > 0 ? noop : undefined}
/>
@@ -348,10 +352,8 @@ const nestedWorktrees = worktreesFor(NESTED, now)
FOLDER_WORKSPACE.isUnread = false
FOLDER_WORKSPACE.lastActivityAt = now - 9 * MINUTE
export const exceptions = defineFixture({
width: SIDEBAR_WIDTH,
setup: () => seedWorkspaces(FLAT, now),
render: () => (
function FlatList(): React.JSX.Element {
return (
<SidebarFrame>
{FLAT.map((seed) => (
<Card
@@ -363,6 +365,12 @@ export const exceptions = defineFixture({
<Card worktree={FOLDER_WORKSPACE} />
</SidebarFrame>
)
}
export const exceptions = defineFixture({
width: SIDEBAR_WIDTH,
setup: () => seedWorkspaces(FLAT, now),
render: () => <FlatList />
})
export const nesting = defineFixture({
@@ -370,8 +378,17 @@ export const nesting = defineFixture({
setup: () => seedWorkspaces(NESTED, now),
render: () => (
<SidebarFrame>
<Card worktree={nestedWorktrees.get('wt-parent')!} childCount={1}>
<Card nested worktree={nestedWorktrees.get('wt-child')!} childCount={1}>
<Card
worktree={nestedWorktrees.get('wt-parent')!}
childCount={1}
descendantIds={['wt-child', 'wt-grandchild']}
>
<Card
nested
worktree={nestedWorktrees.get('wt-child')!}
childCount={1}
descendantIds={['wt-grandchild']}
>
<Card nested worktree={nestedWorktrees.get('wt-grandchild')!} />
</Card>
</Card>
@@ -379,8 +396,23 @@ export const nesting = defineFixture({
worktree={nestedWorktrees.get('wt-collapsed-parent')!}
childCount={1}
collapsed
hiddenDescendantIds={['wt-collapsed-child']}
descendantIds={['wt-collapsed-child']}
/>
</SidebarFrame>
)
})
export const agentsExpanded = defineFixture({
width: SIDEBAR_WIDTH,
setup: () => {
seedWorkspaces(FLAT, now)
const none = new Set<string>()
const state = {
expandedLineageParents: none,
expandedIdleChildGroups: none,
compactRootListExpanded: true
}
FLAT.forEach((seed) => agentExpansion.seedWorktreeAgentExpansionStateForTests(seed.id, state))
},
render: () => <FlatList />
})
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { useCallback, useMemo } from 'react'
import { ChevronDown, ChevronRight } from 'lucide-react'
import { useShallow } from 'zustand/react/shallow'
@@ -14,7 +14,7 @@ import { agentRowDisplayDotState as getAgentDotState } from '@/lib/agent-row-dot
import { WorktreeCardIdentityHover } from './worktree-card-identity-hover'
import type { WorktreeCardPresentation } from './workspace-card/surface/worktree-card-presentation'
import { WorktreeCardSecondaryRows } from './workspace-card/surface/worktree-card-secondary-rows'
import { useCollapsedLineageSlot } from './worktree-card-slot-rollup'
import { useCollapsedLineageSlot, useLineageLatestAt } from './worktree-card-slot-rollup'
import {
classifySlotLine,
compareSlotLines,
@@ -26,9 +26,21 @@ import {
WorktreeCardRenameFailedButton
} from './worktree-card-title-actions'
import { WorktreeCardSlot } from './WorktreeCardSlot'
import { WorktreeCardSlotAgentLines, type SlotAgentLine } from './WorktreeCardSlotAgentLines'
import { useWorktreeAgentExpansionState } from './workspace-card/activity/worktree-card-agents-expansion-state'
import {
CompactAgentExpansion,
CompactAgentSummaryButton
} from './workspace-card/activity/worktree-card-compact-agents'
import { dispatchSuppressScrollAdjustment } from './workspace-card/activity/WorktreeCardAgents'
import { translate } from '@/i18n/i18n'
import {
SlotAgentLinesHeader,
WorktreeCardSlotAgentLines,
type SlotAgentLine
} from './WorktreeCardSlotAgentLines'
import { shouldBeginWorktreeRename } from './workspace-card/controller/worktree-card-model'
import { WorktreeTitleInlineRename } from './workspace-card/identity/WorktreeTitleInlineRename'
import { WorktreeCardMetaRow } from './workspace-card/metadata/worktree-card-meta-row'
const NO_DESCENDANTS: readonly string[] = []
@@ -79,42 +91,82 @@ export function WorktreeCardSlotContent({
showDeleteQuickAction,
showLineageChildChip,
lineageCollapsed,
lineageHiddenDescendantIds,
lineageDescendantIds,
onLineageToggle,
lineageChildAriaLabel,
childWorkspaceShortLabel
childWorkspaceShortLabel,
settings
} = card
const lines = useSlotAgentLines(worktree.id)
const now = useNow(30_000)
const hasLiveMarker = useAppStore((s) => workspaceHasLiveMarker(s, worktree.id))
// A flag with no tab or agent marker behind it was set by the user.
const manualUnread = worktree.isUnread && !hasLiveMarker
const descendantIds = lineageDescendantIds ?? NO_DESCENDANTS
const collapsedLineage = useCollapsedLineageSlot(
lineageCollapsed ? (lineageHiddenDescendantIds ?? NO_DESCENDANTS) : NO_DESCENDANTS
lineageCollapsed ? descendantIds : NO_DESCENDANTS
)
const ownLinesShown = showInlineAgentList && lines.length > 0
const lineageLatestAt = useLineageLatestAt(descendantIds)
// Several agents fold into the 1.0 summary row; the title slot speaks for them until expanded.
const { compactRootListExpanded: agentsExpanded, toggleCompactRootList } =
useWorktreeAgentExpansionState(worktree.id)
const toggleAgents = useCallback(() => {
dispatchSuppressScrollAdjustment()
toggleCompactRootList()
}, [toggleCompactRootList])
const showLines = showInlineAgentList && lines.length > 0
const foldsAgents = showLines && lines.length > 1
const ownLinesShown = showLines && (!foldsAgents || agentsExpanded)
const titleSlot = titleRowSlot({
offline: isRuntimeDisconnected,
hiddenLines: ownLinesShown ? [] : lines.map((line) => line.signal),
collapsedLineage,
manualUnread,
ownLinesShown,
lastActivityAt: worktree.lastActivityAt > 0 ? worktree.lastActivityAt : null
lastActivityAt: worktree.lastActivityAt > 0 ? worktree.lastActivityAt : null,
lineageLatestAt
})
const showDetailsRow =
settings?.worktreeCardDetailsRow === true &&
(presentation.hasMetaRow || presentation.detailsAndPorts !== null)
const titleBold = manualUnread || lines.some((line) => line.signal.kind === 'unread')
const agentLines = showLines ? (
<WorktreeCardSlotAgentLines
worktreeId={worktree.id}
lines={lines}
now={now}
offline={isRuntimeDisconnected}
/>
) : null
const agentCountLabel = translate('sidebar.agentSummary.agentCount', '{{count}} agents', {
count: lines.length,
defaultValue_one: '{{count}} agent'
})
const slotRows = (
<>
{ownLinesShown ? (
<WorktreeCardSlotAgentLines
worktreeId={worktree.id}
lines={lines}
now={now}
offline={isRuntimeDisconnected}
/>
) : null}
{foldsAgents ? (
<>
{agentsExpanded ? (
<SlotAgentLinesHeader label={agentCountLabel} onCollapse={toggleAgents} />
) : (
<div className="mt-[3px] min-w-0 pr-2">
<CompactAgentSummaryButton
agents={lines.map((line) => line.agent)}
subjectLabel={agentCountLabel}
expanded={false}
onToggle={toggleAgents}
/>
</div>
)}
<CompactAgentExpansion expanded={agentsExpanded}>{agentLines}</CompactAgentExpansion>
</>
) : (
agentLines
)}
{showLineageChildChip ? (
<div className="mt-[3px] flex min-w-0 pl-3.5">
<div className="mt-[3px] flex min-w-0">
<Button
type="button"
variant="outline"
@@ -144,32 +196,51 @@ export function WorktreeCardSlotContent({
>
<WorktreeCardIdentityHover card={card} presentation={presentation}>
<div
className="group/worktree-card flex h-5 min-w-0 items-center gap-1.5 pr-2"
className="group/worktree-card flex min-w-0 flex-col"
data-worktree-card-hover-trigger=""
>
<WorktreeTitleInlineRename
displayName={visibleCardTitle}
disabled={isDeleting || affiliateListMode}
showUnreadEmphasis={titleBold}
dimReadTitle
className="text-[13px] leading-5"
editingClassName="flex-1"
titleWrapper={presentation.titleWrapper}
onEditingChange={affiliateListMode ? undefined : setTitleRenaming}
onRename={handleRenameTitle}
beginEditing={
!affiliateListMode &&
shouldBeginWorktreeRename(renamingWorktreeId, worktree.id, renameRowKey)
}
onBeginEditingConsumed={
affiliateListMode ? undefined : () => setRenamingWorktreeId(null)
}
/>
<WorktreeCardRenameFailedButton card={card} />
<span className="ml-auto flex shrink-0 items-center gap-1">
{showDeleteQuickAction ? <WorktreeCardDeleteQuickAction card={card} /> : null}
<WorktreeCardSlot signal={titleSlot} now={now} />
</span>
<div className="flex h-5 min-w-0 items-center gap-1.5 pr-2">
<WorktreeTitleInlineRename
displayName={visibleCardTitle}
disabled={isDeleting || affiliateListMode}
showUnreadEmphasis={titleBold}
dimReadTitle
className="text-[13px] leading-5"
editingClassName="flex-1"
titleWrapper={presentation.titleWrapper}
onEditingChange={affiliateListMode ? undefined : setTitleRenaming}
onRename={handleRenameTitle}
beginEditing={
!affiliateListMode &&
shouldBeginWorktreeRename(renamingWorktreeId, worktree.id, renameRowKey)
}
onBeginEditingConsumed={
affiliateListMode ? undefined : () => setRenamingWorktreeId(null)
}
/>
<WorktreeCardRenameFailedButton card={card} />
<span className="ml-auto flex shrink-0 items-center gap-1">
{showDeleteQuickAction ? <WorktreeCardDeleteQuickAction card={card} /> : null}
<WorktreeCardSlot signal={titleSlot} now={now} />
</span>
</div>
{showDetailsRow ? (
<div
className="mt-1 mb-1 flex h-4 min-w-0 items-center gap-1.5 pr-2"
data-worktree-card-details-row=""
>
{presentation.hasMetaRow ? (
<div className="min-w-0 flex-1 pl-2">
<WorktreeCardMetaRow card={card} presentation={presentation} />
</div>
) : null}
{presentation.detailsAndPorts ? (
<div className="ml-auto flex shrink-0 items-center gap-1">
{presentation.detailsAndPorts}
</div>
) : null}
</div>
) : null}
</div>
</WorktreeCardIdentityHover>
<WorktreeCardSecondaryRows card={card} presentation={presentation} slotRows={slotRows} />
@@ -32,6 +32,13 @@ import {
} from './worktree-card-idle-child-agents'
import { recomputeOn } from '@/lib/recompute-on'
export const SUPPRESS_WORKTREE_LIST_SCROLL_ADJUSTMENT_EVENT =
'orca-suppress-worktree-list-scroll-adjustment'
export const dispatchSuppressScrollAdjustment = () => {
window.dispatchEvent(new CustomEvent(SUPPRESS_WORKTREE_LIST_SCROLL_ADJUSTMENT_EVENT))
}
type Props = {
worktreeId: string
agents?: DashboardAgentRowData[]
@@ -37,8 +37,8 @@ export type WorktreeCardProps = {
lineageChildCount?: number
lineageChildWorktreeIds?: readonly string[]
lineageCollapsed?: boolean
/** Workspaces folded under a collapsed parent; the 2.0 card reports their exceptions. */
lineageHiddenDescendantIds?: readonly string[]
/** Workspaces nested under this one; the 2.0 card rolls up their latest time and, when collapsed, their exceptions. */
lineageDescendantIds?: readonly string[]
lineageChildren?: React.ReactNode
lineageChildrenStyle?: React.CSSProperties
onLineageToggle?: (event: React.MouseEvent<HTMLButtonElement>) => void
@@ -11,7 +11,9 @@ import type { WorktreeCardController } from '../controller/use-worktree-card-con
export function renderWorktreeCardIndicators(
card: WorktreeCardController
): React.ReactElement | null {
const { worktree, hasDetails, hasPorts, newCardStyle, showPR, showIssue } = card
const { worktree, hasDetails, hasPorts, showPR, showIssue } = card
// 1.0 draws the PR in its status lane; 2.0 has no lane, so it lists the PR like Classic.
const newCardStyle = card.newCardStyle && card.worktreeCardStyle !== 'experimental-2'
if (!hasDetails && !hasPorts) {
return null
}
@@ -36,7 +36,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
lineageChildCount = 0,
lineageChildWorktreeIds,
lineageCollapsed = false,
lineageHiddenDescendantIds,
lineageDescendantIds,
lineageChildren,
lineageChildrenStyle,
onLineageToggle,
@@ -74,7 +74,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
lineageChildCount,
lineageChildWorktreeIds,
lineageCollapsed,
lineageHiddenDescendantIds,
lineageDescendantIds,
lineageChildren,
lineageChildrenStyle,
onLineageToggle,
@@ -19,6 +19,7 @@ import {
getWorktreeCardDisplayOptions
} from '../../worktree-list/controls/sidebar-workspace-option-items'
import { useTranslate } from '@/i18n/use-translate'
import { getWorktreeCardStyle } from '../../../../../../shared/worktree/card-style'
type CardMenuItemProps = {
preserveWorkspaceBoardOpen: boolean
@@ -68,6 +69,9 @@ export function WorktreeCardDisplayMenuItem({
const hasProjectGroups = useAppStore((s) => s.projectGroups.length > 0)
const toggleProperty = useToggleCardProperty()
const options = useMemo(() => getWorktreeCardDisplayOptions(hasProjectGroups), [hasProjectGroups])
const slotLayout = useAppStore((s) => getWorktreeCardStyle(s.settings) === 'experimental-2')
const detailsRow = useAppStore((s) => s.settings?.worktreeCardDetailsRow === true)
const updateSettings = useAppStore((s) => s.updateSettings)
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger>
@@ -82,6 +86,20 @@ export function WorktreeCardDisplayMenuItem({
className="w-56"
data-workspace-board-preserve-open={boardAttr(preserveWorkspaceBoardOpen)}
>
{slotLayout ? (
<>
<DropdownMenuCheckboxItem
checked={detailsRow}
onCheckedChange={(checked) =>
void updateSettings({ worktreeCardDetailsRow: checked === true })
}
onSelect={(e) => e.preventDefault()}
>
{t('sidebar.cardSlot.detailsRow', 'Details row')}
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
</>
) : null}
{options.map((opt) => (
<DropdownMenuCheckboxItem
key={opt.id}
@@ -73,3 +73,33 @@ export function useCollapsedLineageSlot(descendantIds: readonly string[]): SlotS
)
return useAppStore(useShallow(selector))
}
/** The most recent activity among child workspaces, so a parent never looks older than its children. */
export function selectLineageLatestAt(
state: AppState,
descendantIds: readonly string[]
): number | null {
let at = 0
const worktreeMap = getWorktreeMapFromState(state)
for (const worktreeId of descendantIds) {
at = Math.max(at, worktreeMap.get(worktreeId)?.lastActivityAt ?? 0)
for (const entry of selectLiveAgentStatusEntriesForWorktree(state, worktreeId)) {
at = Math.max(at, entry.stateStartedAt)
}
for (const retained of selectRetainedAgentEntriesForWorktree(state, worktreeId)) {
at = Math.max(at, retained.entry.stateStartedAt)
}
}
return at > 0 ? at : null
}
export function useLineageLatestAt(descendantIds: readonly string[]): number | null {
const descendantKey = descendantIds.join('\n')
return useAppStore(
useCallback(
(state: AppState) =>
selectLineageLatestAt(state, descendantKey ? descendantKey.split('\n') : []),
[descendantKey]
)
)
}
@@ -136,6 +136,7 @@ export function titleRowSlot(args: {
manualUnread: boolean
ownLinesShown: boolean
lastActivityAt: number | null
lineageLatestAt?: number | null
}): SlotSignal {
if (args.offline) {
return { kind: 'offline' }
@@ -150,5 +151,10 @@ export function titleRowSlot(args: {
if (args.ownLinesShown) {
return { kind: 'quiet' }
}
return { kind: 'time', at: latest(args.hiddenLines) ?? args.lastActivityAt }
const ownAt = latest(args.hiddenLines) ?? args.lastActivityAt
const lineageAt = args.lineageLatestAt ?? null
return {
kind: 'time',
at: ownAt === null || (lineageAt !== null && lineageAt > ownAt) ? lineageAt : ownAt
}
}
@@ -140,7 +140,7 @@ export function renderWorktreeItemRow(
lineageChildCount={itemRow.lineageChildCount}
lineageChildWorktreeIds={itemRow.lineageChildWorktreeIds}
lineageCollapsed={itemRow.lineageCollapsed}
lineageHiddenDescendantIds={itemRow.lineageHiddenDescendantIds}
lineageDescendantIds={itemRow.lineageDescendantIds}
onLineageToggle={
itemRow.lineageGroupKey ? ctx.getLineageToggleHandler(itemRow.lineageGroupKey) : undefined
}
+2 -1
View File
@@ -264,7 +264,8 @@
"needYouCount": "{{count}} need you",
"needsYou": "Needs you",
"failed": "Failed",
"lastSeen": "Last seen {{state}}"
"lastSeen": "Last seen {{state}}",
"detailsRow": "Details row"
}
},
"app": {
+2
View File
@@ -2,6 +2,7 @@ import type { AntigravityChatAuthSettings } from './antigravity-chat-auth-config
import type { EmulatorOpeningSettings } from './emulator-opening-preferences'
import type { BrowserExtensionGlobalSettings } from './browser/extensions/browser-extension-settings'
import type { ClaudeAgentViewSettings } from './claude-agent-view'
import type { WorktreeCardDisplaySettings } from './worktree/card-display-settings-types'
import type { BrowserLinkOpeningSettings } from './browser-link-opening-settings'
import type { BrowserPasswordsGlobalSettings } from './browser-passwords-types'
import type { ClaudeAccountSwitchSettings } from './claude-account-switch-settings'
@@ -33,6 +34,7 @@ export type GlobalSettingsBase = AntigravityChatAuthSettings &
NativeChatGlobalSettings &
NativeChatIdleStopSettings &
WorktreeDeletionPreferences &
WorktreeCardDisplaySettings &
ClaudeAccountSwitchSettings &
AgentPermissionGlobalSettings &
ExternalConversationSettings &
-2
View File
@@ -473,8 +473,6 @@ export type GlobalSettings = GlobalSettingsBase & {
experimentalNewWorktreeCardStyle?: boolean
/** Plugin frames as isolated guests (default on); main-only kill switch, see plugin-frame-isolation.ts. */
experimentalPluginIsolatedFrames?: boolean
/** Which worktree card the sidebar draws; `experimentalNewWorktreeCardStyle` stays in step for older clients. */
worktreeCardStyle?: 'classic' | 'experimental-1' | 'experimental-2'
/** Experimental: per-workspace on-demand environment recipes and setup surface. */
experimentalEphemeralVms?: boolean
/** Experimental (Windows): add WSL distros as hosts, each running its own Orca server. */
+2 -2
View File
@@ -65,8 +65,8 @@ export type WorktreeRow = {
lineageChildWorktreeIds?: readonly string[]
lineageGroupKey?: string
lineageCollapsed?: boolean
/** Set only while collapsed: every workspace folded under this one. */
lineageHiddenDescendantIds?: readonly string[]
/** Every workspace nested under this one, collapsed or not. */
lineageDescendantIds?: readonly string[]
hostContextLabel?: string
showProjectName?: boolean
}
@@ -65,7 +65,7 @@ export function buildNewExternalWorktreesInboxRow(
}
}
function hiddenDescendantIds(
function descendantIds(
occurrences: readonly WorktreeOccurrence<Worktree>[],
parent: WorktreeOccurrence<Worktree>
): string[] {
@@ -142,11 +142,7 @@ export function appendWorktreeRows(
? {
lineageGroupKey: getWorktreeLineageGroupKey(worktree),
lineageCollapsed: options.collapsedGroups.has(getWorktreeLineageGroupKey(worktree)),
...(options.collapsedGroups.has(getWorktreeLineageGroupKey(worktree))
? {
lineageHiddenDescendantIds: hiddenDescendantIds(sectionOccurrences, occurrence)
}
: {})
lineageDescendantIds: descendantIds(sectionOccurrences, occurrence)
}
: {})
})
@@ -0,0 +1,6 @@
export type WorktreeCardDisplaySettings = {
/** Which worktree card the sidebar draws; `experimentalNewWorktreeCardStyle` stays in step for older clients. */
worktreeCardStyle?: 'classic' | 'experimental-1' | 'experimental-2'
/** 2.0 card: draw the details row (Card display items) under each name; off by default. */
worktreeCardDetailsRow?: boolean
}