Align experimental worktree cards with status lane pullback (#6158)

- Apply extra left-padding pullback to experimental cards when the status lane is shown, keeping title text aligned to tree steps.
- Use negative left margin on shallow rows when the minimum content inset clamps the padding adjustment.
- Resolve experimental card style setting dynamically in the folder workspace panel instead of hardcoding it.
This commit is contained in:
Jinjing 2026-06-23 01:14:16 -07:00 committed by GitHub
parent b43b4356db
commit 9607b30fde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 67 additions and 7 deletions

View File

@ -13,6 +13,9 @@ import {
type MockStoreState = {
activeWorktreeId: string | null
activeWorkspaceKey: string | null
settings?: {
experimentalNewWorktreeCardStyle?: boolean
}
folderWorkspaces: {
id: string
name: string
@ -28,6 +31,9 @@ const testState = vi.hoisted(() => ({
store: {
activeWorktreeId: null,
activeWorkspaceKey: null,
settings: {
experimentalNewWorktreeCardStyle: true
},
folderWorkspaces: [],
workspaceLineageByChildKey: {},
worktreeLineageById: {},

View File

@ -16,6 +16,8 @@ function stopNestedWorktreeCardBubble(event: React.SyntheticEvent<HTMLElement>):
export default function FolderWorkspaceWorktreesPanel(): React.JSX.Element {
const activeWorktreeId = useAppStore((s) => s.activeWorktreeId)
const activeWorkspaceKey = useAppStore((s) => s.activeWorkspaceKey)
const experimentalNewWorktreeCardStyle =
useAppStore((s) => s.settings?.experimentalNewWorktreeCardStyle) === true
const folderWorkspaces = useAppStore((s) => s.folderWorkspaces)
const workspaceLineageByChildKey = useAppStore((s) => s.workspaceLineageByChildKey)
const worktreeLineageById = useAppStore((s) => s.worktreeLineageById)
@ -58,9 +60,7 @@ export default function FolderWorkspaceWorktreesPanel(): React.JSX.Element {
const safeLineageChildren = lineageChildren.filter((child) => !nextAncestorIds.has(child.id))
const hasSafeLineageChildren = safeLineageChildren.length > 0
const lineageGeometry = getLineageNestedRowGeometry({
// Why: folder-workspace lineage always uses the in-card child geometry,
// regardless of the main sidebar experimental-card toggle.
experimentalNewWorktreeCardStyle: true,
experimentalNewWorktreeCardStyle,
inheritedCardContentIndent: 0,
lineageDepth: ancestorIds.size
})

View File

@ -63,7 +63,10 @@ import {
} from './workspace-delete-quick-action'
import { DetachedHeadBadge } from '@/components/DetachedHeadBadge'
import { getWorktreeGitIdentityDisplay } from '@/lib/worktree-git-identity-display'
import { getFlushWorktreeCardPaddingLeft } from './worktree-list-indentation'
import {
getFlushWorktreeCardPaddingLeft,
getNewCardStyleParentContentMarginLeft
} from './worktree-list-indentation'
import { translate } from '@/i18n/i18n'
import { recordRendererCrashBreadcrumb } from '@/lib/crash-diagnostics'
import { folderWorkspaceKey, parseWorkspaceKey } from '../../../../shared/workspace-scope'
@ -1155,11 +1158,16 @@ const WorktreeCard = React.memo(function WorktreeCard({
: undefined
// Why: sidebar rows need a small surface inset, while their content remains
// aligned with the pre-inset layout and the repo header hierarchy.
const applyNewCardStyleStatusLaneOffset = newCardStyle && showCombinedStatusSlot
const cardPaddingLeft = flushSurface
? getFlushWorktreeCardPaddingLeft(contentIndent)
? getFlushWorktreeCardPaddingLeft(contentIndent, applyNewCardStyleStatusLaneOffset)
: contentIndent > 0
? `calc(0.125rem + ${contentIndent}px)`
: null
const parentContentMarginLeft =
flushSurface && applyNewCardStyleStatusLaneOffset
? getNewCardStyleParentContentMarginLeft(contentIndent)
: 0
const cardStyle = cardPaddingLeft ? { paddingLeft: cardPaddingLeft } : undefined
const detailsAndPortsContent =
hasDetails || hasPorts ? (
@ -1223,6 +1231,9 @@ const WorktreeCard = React.memo(function WorktreeCard({
'flex w-full min-w-0 gap-0.5 pl-0',
titleOnlyCard ? 'items-center' : 'items-start'
)}
style={
parentContentMarginLeft < 0 ? { marginLeft: `${parentContentMarginLeft}px` } : undefined
}
data-worktree-card-parent-content=""
>
{showCombinedStatusSlot ? (

View File

@ -1,6 +1,8 @@
import { describe, expect, it } from 'vitest'
import {
FLUSH_CARD_CONTENT_PULLBACK,
FLUSH_CARD_MIN_CONTENT_INSET,
NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK,
LINEAGE_CHILDREN_INLINE_OFFSET,
LINEAGE_IMMEDIATE_PARENT_STEP,
LINEAGE_NESTED_ROW_SURFACE_INSET,
@ -97,8 +99,15 @@ describe('worktree list indentation', () => {
expect(getFlushWorktreeCardPaddingLeft(20)).toBe('max(2px, calc(20px - 4px))')
})
it('pulls experimental flush cards back further for the fixed status lane', () => {
expect(getFlushWorktreeCardPaddingLeft(20, true)).toBe(
`max(2px, calc(20px - ${FLUSH_CARD_CONTENT_PULLBACK + NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK}px))`
)
})
it('keeps flush card content off the sidebar edge without indentation', () => {
expect(getFlushWorktreeCardPaddingLeft(0)).toBe('2px')
expect(getFlushWorktreeCardPaddingLeft(0, true)).toBe('2px')
})
it('derives the lineage parent-child step from the pre-refactor grouped-card anchor', () => {

View File

@ -6,6 +6,9 @@ const PROJECT_WORKTREE_CARD_EXTRA_INDENT = 2
// raw tree indent to sit under the group header. A smaller pullback nudges
// content rightward for clearer nesting; this is the knob to tune that gap.
export const FLUSH_CARD_CONTENT_PULLBACK = 4
// Why: experimental cards reserve a fixed status lane inside the padded
// content box; pull the box back so title/meta text stay on the tree step.
export const NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK = 6
// Why: even at zero indent a flush card keeps this minimal left inset so its
// surface never sits hard against the sidebar edge.
export const FLUSH_CARD_MIN_CONTENT_INSET = 2
@ -106,12 +109,43 @@ export function getWorktreeCardSurfaceInset(args: {
return args.isGrouped ? clampDepth(args.groupDepth) * GROUPED_WORKTREE_CARD_SURFACE_INDENT : 0
}
export function getFlushWorktreeCardPaddingLeft(contentIndent: number): string {
export function getFlushWorktreeCardPaddingLeft(
contentIndent: number,
applyNewCardStyleStatusLaneOffset = false
): string {
const pullback =
FLUSH_CARD_CONTENT_PULLBACK +
(applyNewCardStyleStatusLaneOffset ? NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK : 0)
return contentIndent > 0
? `max(${FLUSH_CARD_MIN_CONTENT_INSET}px, calc(${contentIndent}px - ${FLUSH_CARD_CONTENT_PULLBACK}px))`
? `max(${FLUSH_CARD_MIN_CONTENT_INSET}px, calc(${contentIndent}px - ${pullback}px))`
: `${FLUSH_CARD_MIN_CONTENT_INSET}px`
}
export function getNewCardStyleParentContentMarginLeft(contentIndent: number): number {
if (contentIndent <= 0) {
return 0
}
const legacyInnerPadding = Math.max(
FLUSH_CARD_MIN_CONTENT_INSET,
contentIndent - FLUSH_CARD_CONTENT_PULLBACK
)
const newInnerPadding = Math.max(
FLUSH_CARD_MIN_CONTENT_INSET,
contentIndent - FLUSH_CARD_CONTENT_PULLBACK - NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK
)
const paddingShift = legacyInnerPadding - newInnerPadding
const remainingShift = NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK - paddingShift
if (remainingShift <= 0) {
return 0
}
// Why: shallow rows hit the flush-card padding floor; finish the status-lane
// offset with margin, but never pull content past the card's inner edge.
const rawMargin = -remainingShift
return Math.max(-newInnerPadding, rawMargin)
}
export function getLineageNestedRowGeometry(args: {
experimentalNewWorktreeCardStyle: boolean
inheritedCardContentIndent: number