event.stopPropagation()}
onKeyDown={stopRepoHeaderKeyboardToggle}
onPointerDown={handleRepoHeaderActionPointerDown}
@@ -3035,7 +2958,11 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
}
}}
>
- {translate("auto.components.sidebar.WorktreeList.4d7b73658c", "Rename group")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.4d7b73658c',
+ 'Rename group'
+ )}
+
{
@@ -3044,12 +2971,16 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
}
}}
>
- {translate("auto.components.sidebar.WorktreeList.902115cdbe", "Delete group")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.902115cdbe',
+ 'Delete group'
+ )}
+
) : null}
- {row.repo && groupBy === "repo" ? (
+ {row.repo && groupBy === 'repo' ? (
@@ -3060,7 +2991,11 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
size="icon-xs"
data-repo-header-action=""
className="size-5 shrink-0 rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-accent/70 hover:text-foreground focus:opacity-100 group-hover:opacity-100 data-[state=open]:opacity-100"
- aria-label={translate("auto.components.sidebar.WorktreeList.609633a9e6", "Project actions for {{value0}}", { value0: row.label })}
+ aria-label={translate(
+ 'auto.components.sidebar.WorktreeList.609633a9e6',
+ 'Project actions for {{value0}}',
+ { value0: row.label }
+ )}
onClick={(event) => event.stopPropagation()}
onKeyDown={stopRepoHeaderKeyboardToggle}
onPointerDown={handleRepoHeaderActionPointerDown}
@@ -3070,7 +3005,11 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
- {translate("auto.components.sidebar.WorktreeList.2ef41bf9a7", "Project actions")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.2ef41bf9a7',
+ 'Project actions'
+ )}
+
- {translate("auto.components.sidebar.WorktreeList.2cdffbc728", "Project Settings")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.2cdffbc728',
+ 'Project Settings'
+ )}
+
{
if (row.repo) {
@@ -3106,7 +3049,11 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
}}
>
- {translate("auto.components.sidebar.WorktreeList.e82d3589a1", "Change Project Icon")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.e82d3589a1',
+ 'Change Project Icon'
+ )}
+
{row.repo && isGitRepoKind(row.repo) ? (
{
@@ -3127,12 +3074,20 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
}}
>
- {translate("auto.components.sidebar.WorktreeList.cbfd565f83", "New group from project")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.cbfd565f83',
+ 'New group from project'
+ )}
+
{projectGroups.length > 0 ? (
- {translate("auto.components.sidebar.WorktreeList.4a08fb55f2", "Move to group")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.4a08fb55f2',
+ 'Move to group'
+ )}
+
{projectGroups.map((group) => (
- {translate("auto.components.sidebar.WorktreeList.64e55f7f01", "Remove from group")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.64e55f7f01',
+ 'Remove from group'
+ )}
+
) : null}
- {translate("auto.components.sidebar.WorktreeList.c83968f87f", "Remove Project")}
+ {translate(
+ 'auto.components.sidebar.WorktreeList.c83968f87f',
+ 'Remove Project'
+ )}
+
) : null}
- {row.repo && groupBy === "repo" ? (
+ {row.repo && groupBy === 'repo' ? (
{createState?.disabled ? (
@@ -3208,7 +3171,12 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
data-repo-header-action=""
className="size-5 shrink-0 rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-accent/70 hover:text-foreground focus:opacity-100 group-hover:opacity-100"
aria-label={
- createState?.ariaLabel ?? translate("auto.components.sidebar.WorktreeList.bb85cd86ba", "Create workspace for {{value0}}", { value0: row.label })
+ createState?.ariaLabel ??
+ translate(
+ 'auto.components.sidebar.WorktreeList.bb85cd86ba',
+ 'Create workspace for {{value0}}',
+ { value0: row.label }
+ )
}
onKeyDown={stopRepoHeaderKeyboardToggle}
onClick={(event) => {
@@ -3224,7 +3192,12 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
)}
- {createState?.tooltip ?? translate("auto.components.sidebar.WorktreeList.bb85cd86ba", "Create workspace for {{value0}}", { value0: row.label })}
+ {createState?.tooltip ??
+ translate(
+ 'auto.components.sidebar.WorktreeList.bb85cd86ba',
+ 'Create workspace for {{value0}}',
+ { value0: row.label }
+ )}
) : null}
@@ -3240,9 +3213,17 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
forceActiveSurface = false
) => {
const lineageToggleGroupKey = itemRow.lineageGroupKey
- // Why: child cards render inside the parent card body, so their
- // first nested level starts flush with that inset.
+ // Why: child card rows own lineage depth, while WorktreeCard
+ // still owns the project/group inset inside each card surface.
const paddingDepth = nested ? Math.max(0, itemRow.depth - 1) : itemRow.depth
+ const nestedCardPaddingLeft = nested
+ ? Math.max(0, itemRow.depth) * NESTED_LINEAGE_CARD_INDENT
+ : 0
+ const inheritedCardContentIndent = getWorktreeCardContentIndent({
+ isGrouped: groupBy !== 'none',
+ groupDepth: itemRow.groupDepth,
+ lineageDepth: 0
+ })
// Why: grouped rows inherit their project/group header depth,
// while the card surface still spans the full hit/background row.
const paddingLeft = getWorktreeCardContentIndent({
@@ -3250,6 +3231,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
groupDepth: itemRow.groupDepth,
lineageDepth: paddingDepth
})
+ const cardContentIndent = nested ? inheritedCardContentIndent : paddingLeft
const worktreeDragGroupKey = groupKeyByWorktreeId.get(itemRow.worktree.id)
const worktreeDragGroupIndex = groupIndexByWorktreeId.get(itemRow.worktree.id)
const revealHighlightTone =
@@ -3286,7 +3268,8 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
nested ? undefined : handleWorktreeRowPointerDown(event, itemRow.worktree.id)
}
style={{
- paddingLeft: nested && paddingLeft > 0 ? `${paddingLeft}px` : undefined
+ paddingLeft:
+ nested && nestedCardPaddingLeft > 0 ? `${nestedCardPaddingLeft}px` : undefined
}}
>
{
- const isActive = activeWorktreeId === child.worktree.id
- const isDeleting = deleteStateByWorktreeId[child.worktree.id]?.isDeleting ?? false
- const revealHighlightTone =
- agentSendTargetWorktreeId === child.worktree.id ? 'ai' : 'default'
- const showStatus = cardProps.includes('status')
- const showUnreadQuickAction = cardProps.includes('unread')
- const unreadTooltip = child.worktree.isUnread ? 'Mark read' : 'Mark unread'
- const stopQuickActionPointerPropagation = (
- event: React.PointerEvent
- ) => {
- event.stopPropagation()
- }
- const handleToggleUnreadQuick = (event: React.MouseEvent) => {
- event.preventDefault()
- event.stopPropagation()
- onToggleWorktreeUnread(child.worktree)
- }
- const handleClick = (event: React.MouseEvent) => {
- event.preventDefault()
- event.stopPropagation()
- if (isDeleting) {
- return
+ const renderLineageDescendants = (
+ parent: WorktreeItemRow,
+ descendants: readonly WorktreeItemRow[]
+ ): React.ReactNode | undefined => {
+ const childNodes: React.ReactNode[] = []
+ let cursor = 0
+ while (cursor < descendants.length) {
+ const child = descendants[cursor]
+ if (!child || child.depth !== parent.depth + 1) {
+ cursor++
+ continue
}
- const selectionOnly = onSelectionGesture(event, child.worktree.id)
- if (selectionOnly) {
- return
- }
- onImmediateWorktreeActivate(child.worktree.id)
- activateWorktreeFromSidebar(child.worktree.id)
- if (child.repo?.connectionId) {
- const sshStatus =
- useAppStore.getState().sshConnectionStates.get(child.repo.connectionId)
- ?.status ?? 'disconnected'
- if (sshStatus !== 'connected') {
- setLineageReconnectWorktreeId(child.worktree.id)
- }
+
+ let nextSiblingIndex = cursor + 1
+ while (
+ nextSiblingIndex < descendants.length &&
+ descendants[nextSiblingIndex]!.depth > child.depth
+ ) {
+ nextSiblingIndex++
}
+
+ const childLineageChildren = renderLineageDescendants(
+ child,
+ descendants.slice(cursor + 1, nextSiblingIndex)
+ )
+ childNodes.push(renderWorktreeRow(child, true, childLineageChildren))
+ cursor = nextSiblingIndex
}
- const lineageToggleGroupKey = child.lineageGroupKey
- const childCardIndent = Math.max(0, child.depth) * LINEAGE_INDENT
- const childContentIndent = Math.max(0, child.depth - 1) * LINEAGE_INDENT
- return (
- 0 ? { paddingLeft: `${childCardIndent}px` } : undefined}
- >
-
onContextMenuSelect(event, child.worktree)}
- >
- event.stopPropagation()}
- >
- {isDeleting && (
-
-
-
- {translate("auto.components.sidebar.WorktreeList.5fc9d1891b", "Deleting…")}
-
- )}
-
0
- ? { paddingLeft: `calc(0.5rem + ${childContentIndent}px)` }
- : undefined
- }
- >
-
-
-
-
-
- updateWorktreeMeta(child.worktree.id, { displayName })
- }
- beginEditing={renamingWorktreeId === child.worktree.id}
- onBeginEditingConsumed={() => setRenamingWorktreeId(null)}
- />
-
- {child.repo && groupBy !== "repo" ? (
-
-
-
- {child.repo.displayName}
-
-
- ) : null}
-
- {branchDisplayName(child.worktree.branch)}
-
-
- {child.worktree.linkedIssue || child.worktree.comment ? (
-
- {child.worktree.linkedIssue ? (
-
- #{child.worktree.linkedIssue}
-
- ) : null}
- {child.worktree.linkedIssue && child.worktree.comment ? ' ' : null}
- {child.worktree.comment}
-
- ) : null}
- {showInlineAgentCards ? (
- // Why: nested lineage children use this lightweight
- // renderer instead of WorktreeCard, so their inline
- // agent rows must be mounted here explicitly.
-
- ) : null}
- {child.lineageChildCount > 0 && lineageToggleGroupKey ? (
-
-
-
-
-
-
- {child.lineageCollapsed
- ? translate("auto.components.sidebar.WorktreeList.84a2238242", "Show child workspaces")
- : translate("auto.components.sidebar.WorktreeList.ebc5c7dcef", "Hide child workspaces")}
-
-
-
- ) : null}
-
-
-
-
-
- )
+
+ return childNodes.length > 0 ? childNodes : undefined
}
if (row.type === 'lineage-group') {
@@ -3554,9 +3374,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
? renderWorktreeRow(
parent,
false,
- children.length > 0
- ? children.map((child) => renderLineageChildCard(child))
- : undefined,
+ renderLineageDescendants(parent, children),
childIsActive
)
: null}
@@ -4456,13 +4274,6 @@ const WorktreeList = React.memo(function WorktreeList({
[updateWorktreeMeta, worktreeMap, workspaceStatuses]
)
- const toggleWorktreeUnread = useCallback(
- (worktree: Worktree) => {
- void updateWorktreeMeta(worktree.id, { isUnread: !worktree.isUnread })
- },
- [updateWorktreeMeta]
- )
-
const moveWorktreesToStatus = useCallback(
(worktreeIds: readonly string[], status: WorkspaceStatus) => {
const updates = new Map()
@@ -4711,14 +4522,17 @@ const WorktreeList = React.memo(function WorktreeList({
>
- {translate("auto.components.sidebar.WorktreeList.b7acbf038b", "No workspaces found")}
+
+ {translate('auto.components.sidebar.WorktreeList.b7acbf038b', 'No workspaces found')}
+
{hasFilters && (
+ {translate('auto.components.sidebar.WorktreeList.370c6a55dd', 'Clear Filters')}
+
)}
@@ -4731,12 +4545,20 @@ const WorktreeList = React.memo(function WorktreeList({