diff --git a/src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts b/src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts index 6ce0830d27d..6d35ee6a17f 100644 --- a/src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts +++ b/src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts @@ -340,6 +340,7 @@ function makeFolderWorkspacePathStatusState(): Record { function setFolderWorkspaceFixtureState( options: { createdFrom?: ProjectGroup['createdFrom'] + experimentalNewWorktreeCardStyle?: boolean nestedGroup?: boolean } = {} ): void { @@ -405,7 +406,9 @@ function setFolderWorkspaceFixtureState( setRenamingWorktreeId: vi.fn(), setShowSleepingWorkspaces: vi.fn(), setSortBy: vi.fn(), - settings: null, + settings: options.experimentalNewWorktreeCardStyle + ? { experimentalNewWorktreeCardStyle: true } + : null, renamingWorktreeId: null, showSleepingWorkspaces: true, sortBy: 'manual', @@ -1040,6 +1043,25 @@ describe('WorktreeList lineage child card renderer', () => { ).toBe(20) }) + it('uses comparable new-card worktree geometry for experimental folder workspace rows', async () => { + setFolderWorkspaceFixtureState({ experimentalNewWorktreeCardStyle: true }) + const markup = await renderWorktreeListMarkup() + const folderWorktreeId = folderWorkspaceKey('folder-workspace-1') + const cardOpeningTag = getCardOpeningTag(markup, folderWorktreeId) + const surfaceOpeningTag = getFolderWorkspaceSurfaceOpeningTag(markup, 'folder-workspace-1') + const cardContentIndent = getDataNumber(cardOpeningTag, 'data-content-indent') + + expect(surfaceOpeningTag).toContain('padding-left:14px') + expect(cardOpeningTag).toContain('data-content-indent="16"') + expect(cardOpeningTag).toContain('data-flush-surface="true"') + expect( + getFlushCardContentStart({ + cardContentIndent, + surfaceInset: getPaddingLeft(surfaceOpeningTag) + }) + ).toBe(30) + }) + it('preserves manual folder workspace indentation outside folder-scanned groups', async () => { setFolderWorkspaceFixtureState({ createdFrom: 'manual' }) const markup = await renderWorktreeListMarkup() diff --git a/src/renderer/src/components/sidebar/WorktreeList.tsx b/src/renderer/src/components/sidebar/WorktreeList.tsx index 3764ee8adff..60be4d41fde 100644 --- a/src/renderer/src/components/sidebar/WorktreeList.tsx +++ b/src/renderer/src/components/sidebar/WorktreeList.tsx @@ -223,8 +223,7 @@ import { LINEAGE_CHILDREN_INLINE_OFFSET, getFolderBackedRepoWorktreeCardContentIndent, getFolderBackedRepoWorktreeCardSurfaceInset, - getFolderWorkspaceCardContentIndent, - getFolderWorkspaceCardSurfaceInset, + getFolderWorkspaceRowGeometry, getLineageChildrenInlineStyle, getLineageNestedRowGeometry, getProjectGroupHeaderPaddingLeft, @@ -4492,28 +4491,13 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp folderWorkspacePathStatus.reason === 'ambiguous-connection') const isFolderBackedWorkspaceChild = groupBy === 'repo' && folderWorkspaceRow.projectGroup.createdFrom === 'folder-scan' - const contentIndent = isFolderBackedWorkspaceChild - ? getFolderWorkspaceCardContentIndent({ - groupDepth: folderWorkspaceRow.groupDepth - }) - : getWorktreeCardContentIndent({ - isGrouped: groupBy !== 'none', - groupDepth: folderWorkspaceRow.groupDepth, - lineageDepth: folderWorkspaceRow.depth - }) - // Why: folder workspace surfaces should step inward with their - // project-group nesting, matching lineage child card surfaces - // instead of spanning from the sidebar edge at every depth. - const surfaceInset = isFolderBackedWorkspaceChild - ? getFolderWorkspaceCardSurfaceInset({ - isGrouped: true, - groupDepth: folderWorkspaceRow.groupDepth - }) - : getWorktreeCardSurfaceInset({ - isGrouped: groupBy !== 'none', - groupDepth: folderWorkspaceRow.groupDepth - }) - const insetContentIndent = Math.max(0, contentIndent - surfaceInset) + const { surfaceInset, cardContentIndent } = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: newCardStyle, + isFolderBackedWorkspaceChild, + isGrouped: groupBy !== 'none', + groupDepth: folderWorkspaceRow.groupDepth, + lineageDepth: folderWorkspaceRow.depth + }) return (
{ it('keeps ungrouped workspaces flush with the list', () => { expect(getWorktreeCardContentIndent({ isGrouped: false, groupDepth: 4, lineageDepth: 0 })).toBe( @@ -76,6 +89,118 @@ describe('worktree list indentation', () => { expect(getFolderWorkspaceCardSurfaceInset({ isGrouped: false, groupDepth: 2 })).toBe(0) }) + it('preserves legacy folder-scanned folder workspace row geometry', () => { + const geometry = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: false, + isFolderBackedWorkspaceChild: true, + isGrouped: true, + groupDepth: 1, + lineageDepth: 0 + }) + + expect(geometry).toEqual({ + surfaceInset: 14, + cardContentIndent: 6 + }) + expect(getFlushCardContentStart(geometry)).toBe(20) + }) + + it('preserves legacy nested folder-scanned folder workspace row geometry', () => { + const geometry = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: false, + isFolderBackedWorkspaceChild: true, + isGrouped: true, + groupDepth: 2, + lineageDepth: 0 + }) + + expect(geometry).toEqual({ + surfaceInset: 24, + cardContentIndent: 6 + }) + expect(getFlushCardContentStart(geometry)).toBe(30) + }) + + it('preserves legacy manual grouped folder workspace row geometry', () => { + const geometry = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: false, + isFolderBackedWorkspaceChild: false, + isGrouped: true, + groupDepth: 1, + lineageDepth: 0 + }) + + expect(geometry).toEqual({ + surfaceInset: 14, + cardContentIndent: 24 + }) + expect(getFlushCardContentStart(geometry)).toBe(38) + }) + + it('uses comparable repo worktree geometry for experimental folder-scanned folder workspaces', () => { + const geometry = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: true, + isFolderBackedWorkspaceChild: true, + isGrouped: true, + groupDepth: 1, + lineageDepth: 0 + }) + + expect(geometry).toEqual({ + surfaceInset: 14, + cardContentIndent: 16 + }) + expect(getFlushCardContentStart(geometry)).toBe(30) + }) + + it('uses comparable repo worktree geometry for experimental nested folder workspaces', () => { + const geometry = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: true, + isFolderBackedWorkspaceChild: true, + isGrouped: true, + groupDepth: 4, + lineageDepth: 3 + }) + + expect(geometry).toEqual({ + surfaceInset: 54, + cardContentIndent: 6 + }) + expect(getFlushCardContentStart(geometry)).toBe(60) + }) + + it('keeps experimental manual grouped folder workspaces on normal worktree geometry', () => { + const geometry = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: true, + isFolderBackedWorkspaceChild: false, + isGrouped: true, + groupDepth: 1, + lineageDepth: 0 + }) + + expect(geometry).toEqual({ + surfaceInset: 14, + cardContentIndent: 24 + }) + expect(getFlushCardContentStart(geometry)).toBe(38) + }) + + it('keeps experimental flat folder workspaces on normal worktree geometry', () => { + const geometry = getFolderWorkspaceRowGeometry({ + experimentalNewWorktreeCardStyle: true, + isFolderBackedWorkspaceChild: false, + isGrouped: false, + groupDepth: 3, + lineageDepth: 0 + }) + + expect(geometry).toEqual({ + surfaceInset: 0, + cardContentIndent: 0 + }) + expect(getFlushCardContentStart(geometry)).toBe(6) + }) + it('caps header indentation separately from workspace content indentation', () => { expect(getProjectGroupHeaderPaddingLeft(100)).toBe(70) }) diff --git a/src/renderer/src/components/sidebar/worktree-list-indentation.ts b/src/renderer/src/components/sidebar/worktree-list-indentation.ts index fc3dc252e49..28c87b56662 100644 --- a/src/renderer/src/components/sidebar/worktree-list-indentation.ts +++ b/src/renderer/src/components/sidebar/worktree-list-indentation.ts @@ -99,6 +99,61 @@ export function getFolderWorkspaceCardSurfaceInset(args: { return Math.min(genericSurfaceInset, Math.max(0, maxSurfaceInset)) } +export function getFolderWorkspaceRowGeometry(args: { + experimentalNewWorktreeCardStyle: boolean + isFolderBackedWorkspaceChild: boolean + isGrouped: boolean + groupDepth: number + lineageDepth: number +}): { + surfaceInset: number + cardContentIndent: number +} { + if (args.experimentalNewWorktreeCardStyle && args.isFolderBackedWorkspaceChild) { + // Why: standalone folder workspace rows do not get a lineage wrapper + // offset, so align them to the comparable folder-backed repo row anchor. + const contentAnchor = getFolderBackedRepoWorktreeCardContentIndent({ + groupDepth: args.groupDepth, + lineageDepth: 0 + }) + const surfaceInset = getFolderBackedRepoWorktreeCardSurfaceInset({ + groupDepth: args.groupDepth, + lineageDepth: 0 + }) + + return { + surfaceInset, + cardContentIndent: Math.max(0, contentAnchor - surfaceInset) + } + } + + const contentIndent = args.isFolderBackedWorkspaceChild + ? getFolderWorkspaceCardContentIndent({ + groupDepth: args.groupDepth + }) + : getWorktreeCardContentIndent({ + isGrouped: args.isGrouped, + groupDepth: args.groupDepth, + lineageDepth: args.lineageDepth + }) + // Why: legacy folder-scanned folder workspaces keep their compact anchor, + // while all other folder rows share the normal worktree row surface path. + const surfaceInset = args.isFolderBackedWorkspaceChild + ? getFolderWorkspaceCardSurfaceInset({ + isGrouped: true, + groupDepth: args.groupDepth + }) + : getWorktreeCardSurfaceInset({ + isGrouped: args.isGrouped, + groupDepth: args.groupDepth + }) + + return { + surfaceInset, + cardContentIndent: Math.max(0, contentIndent - surfaceInset) + } +} + export function getWorktreeCardSurfaceInset(args: { isGrouped: boolean groupDepth: number diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 20488fa7ea9..9c39c9bccff 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -6255,7 +6255,19 @@ "ab20575a8a": "off", "ab3a1f9068": "wsl.exe", "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", - "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed.", + "helper": "Adjust how wheel input feels in scrollback and in mouse-aware terminal apps.", + "reset": "Reset", + "normal": "Normal", + "normalDescription": "Scrollback wheel multiplier.", + "fast": "Fast", + "fastDescription": "Extra multiplier while scrolling with a modifier key.", + "tui": "TUI", + "tuiDescription": "Discrete wheel reports for full-screen terminal apps." + } }, "TerminalSettingsPreview": { "a63953a48a": "Preview {{value0}} theme", @@ -7891,7 +7903,11 @@ "0fe0073f0c": "Default terminal font size for new panes and live updates.", "5930244899": "Font Size", "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", - "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed." + } }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 23d644fa99f..eafb7919e08 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -6218,7 +6218,19 @@ "ab20575a8a": "apagado", "ab3a1f9068": "wsl.exe", "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", - "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed.", + "helper": "Adjust how wheel input feels in scrollback and in mouse-aware terminal apps.", + "reset": "Reset", + "normal": "Normal", + "normalDescription": "Scrollback wheel multiplier.", + "fast": "Fast", + "fastDescription": "Extra multiplier while scrolling with a modifier key.", + "tui": "TUI", + "tuiDescription": "Discrete wheel reports for full-screen terminal apps." + } }, "TerminalSettingsPreview": { "a63953a48a": "Vista previa del tema {{value0}}", @@ -7854,7 +7866,11 @@ "keyword_custom": "custom" }, "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", - "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed." + } }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index dcce2ac18ed..01f3290bdc4 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -6240,7 +6240,19 @@ "ab20575a8a": "オフ", "ab3a1f9068": "wsl.exe", "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", - "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed.", + "helper": "Adjust how wheel input feels in scrollback and in mouse-aware terminal apps.", + "reset": "Reset", + "normal": "Normal", + "normalDescription": "Scrollback wheel multiplier.", + "fast": "Fast", + "fastDescription": "Extra multiplier while scrolling with a modifier key.", + "tui": "TUI", + "tuiDescription": "Discrete wheel reports for full-screen terminal apps." + } }, "TerminalSettingsPreview": { "a63953a48a": "{{value0}} テーマのプレビュー", @@ -7876,7 +7888,11 @@ "0fe0073f0c": "新規ペインとライブ アップデートのデフォルトの terminal フォント サイズ。", "5930244899": "フォントサイズ", "ask_before_closing_running_terminals_title": "Ask Before Closing Running Terminals", - "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent." + "ask_before_closing_running_terminals_description": "Show a confirmation before closing a terminal that has a running command or agent.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed." + } }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 31d7c09eb57..1922d7953c2 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -6203,7 +6203,19 @@ "ab20575a8a": "끄기", "ab3a1f9068": "wsl.exe", "ask_before_closing_running_terminals_title": "실행 중인 Terminals을 닫기 전에 확인", - "ask_before_closing_running_terminals_description": "실행 중인 명령이나 agent가 있는 terminal을 닫기 전에 확인 창을 표시합니다." + "ask_before_closing_running_terminals_description": "실행 중인 명령이나 agent가 있는 terminal을 닫기 전에 확인 창을 표시합니다.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed.", + "helper": "Adjust how wheel input feels in scrollback and in mouse-aware terminal apps.", + "reset": "Reset", + "normal": "Normal", + "normalDescription": "Scrollback wheel multiplier.", + "fast": "Fast", + "fastDescription": "Extra multiplier while scrolling with a modifier key.", + "tui": "TUI", + "tuiDescription": "Discrete wheel reports for full-screen terminal apps." + } }, "TerminalSettingsPreview": { "a63953a48a": "{{value0}} 테마 미리보기", @@ -7839,7 +7851,11 @@ "keyword_custom": "custom" }, "ask_before_closing_running_terminals_title": "실행 중인 Terminals을 닫기 전에 확인", - "ask_before_closing_running_terminals_description": "실행 중인 명령이나 agent가 있는 terminal을 닫기 전에 확인을 표시합니다." + "ask_before_closing_running_terminals_description": "실행 중인 명령이나 agent가 있는 terminal을 닫기 전에 확인을 표시합니다.", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed." + } }, "windows": { "search": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index a2a27399a9d..ff179118724 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -6203,7 +6203,19 @@ "ab20575a8a": "关", "ab3a1f9068": "执行程序", "ask_before_closing_running_terminals_title": "关闭运行中的 Terminal 前询问", - "ask_before_closing_running_terminals_description": "在关闭有运行中命令或 agent 的 terminal 前显示确认。" + "ask_before_closing_running_terminals_description": "在关闭有运行中命令或 agent 的 terminal 前显示确认。", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed.", + "helper": "Adjust how wheel input feels in scrollback and in mouse-aware terminal apps.", + "reset": "Reset", + "normal": "Normal", + "normalDescription": "Scrollback wheel multiplier.", + "fast": "Fast", + "fastDescription": "Extra multiplier while scrolling with a modifier key.", + "tui": "TUI", + "tuiDescription": "Discrete wheel reports for full-screen terminal apps." + } }, "TerminalSettingsPreview": { "a63953a48a": "预览 {{value0}} 主题", @@ -7839,7 +7851,11 @@ "keyword_custom": "自定义" }, "ask_before_closing_running_terminals_title": "关闭运行中的 Terminal 前询问", - "ask_before_closing_running_terminals_description": "在关闭有运行中命令或 agent 的 terminal 前显示确认。" + "ask_before_closing_running_terminals_description": "在关闭有运行中命令或 agent 的 terminal 前显示确认。", + "scrollSpeed": { + "title": "Scroll Speed", + "description": "Tune normal terminal scrollback, fast modifier scrolling, and full-screen TUI wheel speed." + } }, "windows": { "search": {