fix(sidebar): indent worktree rows under their project header (#2841)
* fix(sidebar): indent worktree rows under their project header Top-level worktree rows had no left padding, so their status dots sat flush with the project folder icon. Indent each worktree group by one LINEAGE_INDENT step so the dots nest under the folder icon, giving clear parent-to-child visual hierarchy. Nested/lineage cards inherit the base inset and keep their relative indentation. * Avoid ungrouped workspace indentation * review: cover grouped sidebar indentation - reuse the WorktreeList render helper in the lineage test - add the grouped project assertion that fails if the indent is reverted - mock DropdownMenuSeparator for grouped project rendering --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
This commit is contained in:
parent
6eb475afaa
commit
64af9a01c1
|
|
@ -100,6 +100,7 @@ vi.mock('@/components/ui/dropdown-menu', () => ({
|
|||
React.createElement(React.Fragment, null, children),
|
||||
DropdownMenuItem: ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement('div', null, children),
|
||||
DropdownMenuSeparator: () => React.createElement('hr'),
|
||||
DropdownMenuTrigger: ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(React.Fragment, null, children)
|
||||
}))
|
||||
|
|
@ -155,7 +156,7 @@ function makeLineage(worktree: Worktree, parent: Worktree): WorktreeLineage {
|
|||
}
|
||||
}
|
||||
|
||||
function setLineageFixtureState(): void {
|
||||
function setLineageFixtureState(groupBy: 'none' | 'repo' = 'none'): void {
|
||||
const repo = makeRepo()
|
||||
const parent = makeWorktree({
|
||||
id: 'parent',
|
||||
|
|
@ -188,7 +189,7 @@ function setLineageFixtureState(): void {
|
|||
clearPendingRevealWorktreeId: vi.fn(),
|
||||
collapsedGroups: new Set<string>(),
|
||||
filterRepoIds: [],
|
||||
groupBy: 'none',
|
||||
groupBy,
|
||||
hideDefaultBranchWorkspace: false,
|
||||
issueCache: {},
|
||||
migrationUnsupportedByPtyId: {},
|
||||
|
|
@ -229,17 +230,21 @@ function setLineageFixtureState(): void {
|
|||
}
|
||||
}
|
||||
|
||||
async function renderWorktreeListMarkup(): Promise<string> {
|
||||
const { default: WorktreeList } = await import('./WorktreeList')
|
||||
|
||||
return renderToStaticMarkup(
|
||||
React.createElement(WorktreeList, {
|
||||
scrollOffsetRef: { current: 0 },
|
||||
scrollAnchorRef: { current: null }
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
describe('WorktreeList lineage child card renderer', () => {
|
||||
it('renders nested inline agent rows before the nested child-count toggle', async () => {
|
||||
setLineageFixtureState()
|
||||
const { default: WorktreeList } = await import('./WorktreeList')
|
||||
|
||||
const markup = renderToStaticMarkup(
|
||||
React.createElement(WorktreeList, {
|
||||
scrollOffsetRef: { current: 0 },
|
||||
scrollAnchorRef: { current: null }
|
||||
})
|
||||
)
|
||||
const markup = await renderWorktreeListMarkup()
|
||||
|
||||
const childStart = markup.indexOf('lineage child with agent')
|
||||
const agentRowIndex = markup.indexOf('Review fixture prompt', childStart)
|
||||
|
|
@ -250,4 +255,23 @@ describe('WorktreeList lineage child card renderer', () => {
|
|||
expect(childToggleIndex).toBeGreaterThan(childStart)
|
||||
expect(agentRowIndex).toBeLessThan(childToggleIndex)
|
||||
})
|
||||
|
||||
it('does not add group indentation when grouping is disabled', async () => {
|
||||
setLineageFixtureState('none')
|
||||
const markup = await renderWorktreeListMarkup()
|
||||
|
||||
const parentRow = markup.match(/<div[^>]*id="worktree-list-option-parent"[^>]*>/)?.[0] ?? ''
|
||||
|
||||
expect(parentRow).toContain('id="worktree-list-option-parent"')
|
||||
expect(parentRow).not.toContain('padding-left')
|
||||
})
|
||||
|
||||
it('adds one group indentation step when grouped by project', async () => {
|
||||
setLineageFixtureState('repo')
|
||||
const markup = await renderWorktreeListMarkup()
|
||||
|
||||
const parentRow = markup.match(/<div[^>]*id="worktree-list-option-parent"[^>]*>/)?.[0] ?? ''
|
||||
|
||||
expect(parentRow).toContain('style="padding-left:18px"')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -274,6 +274,9 @@ function getWorktreeVisibilityMenuLabel(repo: Repo): string {
|
|||
}
|
||||
|
||||
const LINEAGE_INDENT = 18
|
||||
// Why: top-level worktrees are children of their project header; indent the
|
||||
// group one step so the status dots nest under the folder icon for hierarchy.
|
||||
const WORKTREE_GROUP_INDENT = 18
|
||||
const SIDEBAR_POINTER_DRAG_THRESHOLD_PX = 4
|
||||
|
||||
type VirtualizedWorktreeViewportProps = {
|
||||
|
|
@ -2230,6 +2233,10 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
|||
// Why: child cards render inside the parent card body, so their
|
||||
// first nested level starts flush with that inset.
|
||||
const paddingDepth = nested ? Math.max(0, itemRow.depth - 1) : itemRow.depth
|
||||
// Why: ungrouped mode keeps workspace cards flush; grouped modes
|
||||
// indent top-level cards under their visible section header.
|
||||
const basePadding = !nested && groupBy !== 'none' ? WORKTREE_GROUP_INDENT : 0
|
||||
const paddingLeft = basePadding + paddingDepth * LINEAGE_INDENT
|
||||
const worktreeDragGroupKey = groupKeyByWorktreeId.get(itemRow.worktree.id)
|
||||
const worktreeDragGroupIndex = groupIndexByWorktreeId.get(itemRow.worktree.id)
|
||||
return (
|
||||
|
|
@ -2262,7 +2269,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
|||
nested ? undefined : handleWorktreeRowPointerDown(event, itemRow.worktree.id)
|
||||
}
|
||||
style={{
|
||||
paddingLeft: paddingDepth > 0 ? `${paddingDepth * LINEAGE_INDENT}px` : undefined
|
||||
paddingLeft: paddingLeft > 0 ? `${paddingLeft}px` : undefined
|
||||
}}
|
||||
>
|
||||
<WorktreeCard
|
||||
|
|
|
|||
Loading…
Reference in New Issue