refactor(sidebar): group worktree-list files by domain

Follow-up to #14465 / #14467. Keep the landed extract and reorganize the
flat worktree-list dump into drag/, headers/, reveal/, rows/, scroll/,
and viewport/. Fold tiny modules into their owners, move leftover
sidebar-root files into the module, and retarget imports and source-path
tests. Layout-only; no behavior change.
This commit is contained in:
Jinjing
2026-08-14 15:54:28 -07:00
parent 3a212584ec
commit 41a8302a0d
119 changed files with 553 additions and 586 deletions
+2 -2
View File
@@ -235,8 +235,8 @@ inline src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx
inline src/renderer/src/components/sidebar/WorktreeContextMenu.tsx
inline src/renderer/src/components/sidebar/WorktreeList.lineage-child-card.test.ts
inline src/renderer/src/components/sidebar/use-workspace-kanban-area-selection.ts
inline src/renderer/src/components/sidebar/worktree-list-groups.test.ts
inline src/renderer/src/components/sidebar/worktree-list-groups.ts
inline src/renderer/src/components/sidebar/worktree-list/rows/groups.test.ts
inline src/renderer/src/components/sidebar/worktree-list/rows/groups.ts
inline src/renderer/src/components/stats/usage-overview-model.ts
inline src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx
inline src/renderer/src/components/status-bar/StatusBar.tsx
+5 -5
View File
@@ -4334,9 +4334,9 @@
"oracle": "Start with Active Server=Local desktop, connect and navigate Windows 2, then reveal a local terminal and assert it succeeds while the persisted preference remains local before and after restart. Repeat transient routing and selected-runtime Add Project operations with exact-ID host decoys and completion reordering. Separately pair the real web client to an isolated headed host, then make its sole still-listed runtime compatibility-blocked and unavailable. Require a null authoritative selection, a disabled host option, and disabled Browse, Clone, and Create actions with no local fallback. Repeat against headless serve. Run the byte-identical selector/action contract on latest main, candidate, and candidate with the eligibility seam disabled.",
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/settings.test.ts src/main/ipc/runtime-environments.test.ts src/renderer/src/store/slices/settings.test.ts src/renderer/src/store/slices/browser.test.ts src/renderer/src/components/settings/browser-session-host-selection.test.ts src/renderer/src/components/settings/RuntimeEnvironmentsPane.test.ts src/renderer/src/components/status-bar/SshStatusSegment.test.ts src/renderer/src/components/sidebar/use-add-repo-host-selection.test.ts src/renderer/src/hooks/useIpcEvents.test.ts src/renderer/src/web/web-preload-api.test.ts --maxWorkers=1",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list-host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts --maxWorkers=1",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list/viewport/host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts --maxWorkers=1",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/use-add-repo-host-selection.test.ts src/renderer/src/components/sidebar/add-repo-browse-authority.test.ts src/renderer/src/components/sidebar/AddRepoDialogStepContent.test.tsx src/renderer/src/components/sidebar/AddRepoStartSteps.test.tsx src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/runtime/runtime-server-directory-browser.test.ts src/renderer/src/store/slices/repos-paired-runtime-add.test.ts --maxWorkers=1",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list-host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts src/renderer/src/runtime/web-runtime-session.test.ts --maxWorkers=1",
"pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list/viewport/host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts src/renderer/src/runtime/web-runtime-session.test.ts --maxWorkers=1",
"pnpm run ensure:electron-runtime && npx playwright test tests/e2e/pr11346-selected-runtime-add.spec.ts --config tests/playwright.config.ts --project electron-headful --workers=1 --reporter=line",
"pnpm run ensure:electron-runtime && npx playwright test tests/e2e/pr11346-selected-runtime-add.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1 --reporter=line",
"SKIP_BUILD=1 ORCA_E2E_WEB_CLIENT=1 ORCA_E2E_FORCE_HEADFUL=1 pnpm exec playwright test tests/e2e/paired-web-add-project-unavailable-host.spec.ts --config tests/playwright.config.ts --project electron-headful --workers=1 --reporter=line",
@@ -4366,7 +4366,7 @@
"src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts",
"src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts",
"src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts",
"src/renderer/src/components/sidebar/worktree-list-host-filtering.test.ts",
"src/renderer/src/components/sidebar/worktree-list/viewport/host-filtering.test.ts",
"src/renderer/src/lib/resolved-worktree-execution-host.test.ts",
"src/renderer/src/lib/worktree-runtime-owner.test.ts",
"src/renderer/src/store/selectors.test.ts",
@@ -4559,7 +4559,7 @@
"date": "2026-07-30",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list-host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts --maxWorkers=1",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list/viewport/host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts --maxWorkers=1",
"result": "passed",
"durationSeconds": 8.77,
"summary": "Nineteen files and 386 tests passed after rebasing onto 94cf2f1422f30fc309cb47c5e864a831d516fa8b, including exact-ID repo/worktree/group/folder host identity in both catalog orders, fail-closed active-repo selection when a runtime row is unavailable, selected runtime and SSH owner capture through final Add/Clone/Create/folder activation, direct-SSH authoritative pruning, reversed catalog responses, reconnect generation fencing, local-after-runtime isolation, and missing-catalog worktree routing."
@@ -4586,7 +4586,7 @@
"date": "2026-07-31",
"runner": "local",
"platform": "macos",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list-host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts src/renderer/src/runtime/web-runtime-session.test.ts --maxWorkers=1",
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/sidebar/AddProjectFromFolderDialog.test.tsx src/renderer/src/components/sidebar/NonGitFolderDialog.test.tsx src/renderer/src/components/sidebar/AddRepoDialog.default-checkout.test.ts src/renderer/src/components/sidebar/AddRepoSteps.default-checkout.test.ts src/renderer/src/components/sidebar/project-added-default-checkout.test.ts src/renderer/src/components/sidebar/useAddRepoLocalFolderFlow.test.ts src/renderer/src/components/sidebar/useAddRepoNestedImportFlow.test.ts src/renderer/src/components/sidebar/useAddRepoServerPathFlow.test.ts src/renderer/src/components/sidebar/useAddRepoCloneFlow.test.ts src/renderer/src/components/sidebar/useCreateRepo.default-checkout.test.ts src/renderer/src/components/sidebar/worktree-list/viewport/host-filtering.test.ts src/renderer/src/lib/resolved-worktree-execution-host.test.ts src/renderer/src/lib/worktree-runtime-owner.test.ts src/renderer/src/store/selectors.test.ts src/renderer/src/store/slices/repos-all-hosts-folder-workspaces.test.ts src/renderer/src/store/slices/repos-project-groups.test.ts src/renderer/src/store/slices/repos-selected-owner-routing.test.ts src/renderer/src/store/slices/selected-host-active-workspace-identity.test.ts src/renderer/src/store/slices/worktrees.test.ts src/renderer/src/runtime/web-runtime-session.test.ts --maxWorkers=1",
"result": "passed",
"durationSeconds": 7.1,
"summary": "Twenty files and 430 tests passed on current main integration, including explicit runtime-owner preservation for terminal and browser session activation."
@@ -1,6 +1,6 @@
import type { Worktree } from './workspace-list-sections'
// Why: matches desktop's PR_GROUP_META naming from worktree-list-groups.ts.
// Why: matches desktop's PR_GROUP_META naming from worktree-list/rows/groups.ts.
// no PR/draft/unknown -> "In Progress", open -> "In Review", merged -> "Done", closed -> "Closed"
export type PRGroupKey = 'done' | 'in-review' | 'in-progress' | 'closed'
@@ -132,7 +132,7 @@ import { getPaletteHostBadge, type PaletteHostBadge } from '@/components/cmd-j/p
import PaletteFilterMenu from '@/components/cmd-j/PaletteFilterMenu'
import PaletteFilterChips from '@/components/cmd-j/PaletteFilterChips'
import { buildPaletteFilterModel } from '@/components/cmd-j/palette-filter-options'
import { getProjectGroupExecutionHostIdForRows } from '@/components/sidebar/worktree-list-host-filtering'
import { getProjectGroupExecutionHostIdForRows } from '@/components/sidebar/worktree-list/viewport/host-filtering'
import {
buildPaletteFilterPredicate,
EMPTY_PALETTE_FILTER,
@@ -9,7 +9,7 @@ import type { Worktree } from '../../../../shared/worktree/types'
import {
getProjectHeaderRevealTarget,
type ProjectGroupingModel
} from '../sidebar/worktree-list-groups'
} from '../sidebar/worktree-list/rows/groups'
import type { SidebarHostOption } from '../sidebar/sidebar-host-options'
import { buildPaletteFilterOptionSearchText } from './palette-filter-option-list'
@@ -12,7 +12,7 @@ import {
getProjectGroupHeaderKey,
getProjectHeaderRevealTarget,
type ProjectGroupingModel
} from '../sidebar/worktree-list-groups'
} from '../sidebar/worktree-list/rows/groups'
export type CmdJProjectGroupResult = {
id: string
@@ -31,6 +31,9 @@ const HOVER_REVEAL_FILES = [
resolve(__dirname, 'sidebar/WorkspaceKanbanStatusLane.tsx'),
resolve(__dirname, 'sidebar/WorktreeCardPorts.tsx'),
resolve(__dirname, 'sidebar/WorktreeList.tsx'),
resolve(__dirname, 'sidebar/worktree-list/headers/HostSectionHeader.tsx'),
resolve(__dirname, 'sidebar/worktree-list/headers/SectionHeader.tsx'),
resolve(__dirname, 'sidebar/worktree-list/rows/item-row.tsx'),
resolve(__dirname, 'status-bar/ResourceUsageStatusSegment.tsx'),
resolve(__dirname, 'status-bar/ports-status-popover-rows.tsx'),
resolve(__dirname, 'tab-bar/TabBarQuickCommandsMenu.tsx')
@@ -5,7 +5,7 @@ import type { AiVaultScope, AiVaultSession } from '../../../../shared/ai-vault-t
import type { AiVaultResumeStartup } from '@/lib/ai-vault-resume-command'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import { getActiveStickyHeaderIndexForScroll } from '../sidebar/worktree-list-virtual-rows'
import { getActiveStickyHeaderIndexForScroll } from '../sidebar/worktree-list/viewport/virtual-rows'
import { VaultGroupHeader } from './AiVaultPanelControls'
import { EmptyState, SessionLoadingState } from './AiVaultSessionListStates'
import { VaultSessionRow } from './AiVaultSessionRow'
@@ -10,7 +10,7 @@ import { folderWorkspaceKey, worktreeWorkspaceKey } from '../../../../shared/wor
import {
LINEAGE_CHILDREN_INLINE_OFFSET,
getLineageChildrenInlineStyle
} from '@/components/sidebar/worktree-list-indentation'
} from '@/components/sidebar/worktree-list/rows/indentation'
type MockStoreState = {
activeWorktreeId: string | null
@@ -2,7 +2,7 @@ import WorktreeCard from '@/components/sidebar/WorktreeCard'
import {
getLineageChildrenInlineStyle,
getLineageNestedRowGeometry
} from '@/components/sidebar/worktree-list-indentation'
} from '@/components/sidebar/worktree-list/rows/indentation'
import { useAppStore } from '@/store'
import { translate } from '@/i18n/i18n'
import type { Worktree } from '../../../../shared/worktree/types'
@@ -3,7 +3,7 @@ import type { Range } from '@tanstack/react-virtual'
import {
getActiveStickyHeaderIndex,
getPreviousStickyHeaderIndex
} from '../sidebar/worktree-list-virtual-rows'
} from '../sidebar/worktree-list/viewport/virtual-rows'
export const VAULT_GROUP_HEADER_ROW_HEIGHT = 32
export const VAULT_SESSION_ROW_HEIGHT = 98
@@ -4,7 +4,7 @@ import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { SidebarGroupByToggle } from './SidebarGroupByToggle'
import type { WorktreeGroupBy } from './worktree-list-groups'
import type { WorktreeGroupBy } from './worktree-list/rows/groups'
const roots: Root[] = []
@@ -1,5 +1,5 @@
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import type { WorktreeGroupBy } from './worktree-list-groups'
import type { WorktreeGroupBy } from './worktree-list/rows/groups'
import { GROUP_BY_OPTIONS } from './sidebar-workspace-option-items'
type SidebarGroupByToggleProps = {
@@ -8,7 +8,7 @@ import type { Repo } from '../../../../shared/repo-types'
import type { WorktreeLineage } from '../../../../shared/worktree/lineage-types'
import type { Worktree } from '../../../../shared/worktree/types'
import { folderWorkspaceKey } from '../../../../shared/workspace-scope'
import { getPinnedWorktreeRevealCollapsedGroupKeys } from './worktree-list/sidebar-row-reveal-ancestors'
import { getPinnedWorktreeRevealCollapsedGroupKeys } from './worktree-list/reveal/ancestors'
import { cloneDefaultWorkspaceStatuses } from '../../../../shared/workspace-statuses'
const mockStore = vi.hoisted(() => ({
@@ -14,7 +14,7 @@ import {
LINEAGE_IMMEDIATE_PARENT_STEP,
LINEAGE_NESTED_ROW_SURFACE_INSET,
WORKTREE_CARD_SURFACE_MARGIN
} from './worktree-list-indentation'
} from './worktree-list/rows/indentation'
globalThis.IS_REACT_ACT_ENVIRONMENT = true
@@ -14,29 +14,29 @@ import {
getSettingsFocusedExecutionHostId
} from '../../../../shared/execution-host'
import { getActiveSidebarWorkspaceId } from '../../../../shared/workspace-scope'
import { getPinnedWorktreeDisplayPolicy } from './worktree-list-groups'
import { selectWorktreeListReviewCacheInputs } from './worktree-list-review-cache-inputs'
import { getPinnedWorktreeDisplayPolicy } from './worktree-list/rows/groups'
import { selectWorktreeListReviewCacheInputs } from './worktree-list/viewport/review-cache-inputs'
import type { VirtualizedScrollAnchor } from '@/hooks/useVirtualizedScrollAnchor'
import { SidebarWorktreeListDialogs } from './worktree-list/SidebarWorktreeListDialogs'
import { SidebarWorktreeListEmptyState } from './worktree-list/SidebarWorktreeListEmptyState'
import { VirtualizedWorktreeViewport } from './worktree-list/VirtualizedWorktreeViewport'
import { markSidebarWorktreeActiveImmediately } from './worktree-list/worktree-option-dom'
import { VirtualizedWorktreeViewport } from './worktree-list/viewport/VirtualizedWorktreeViewport'
import { markSidebarWorktreeActiveImmediately } from './worktree-list/option-dom'
import {
EMPTY_PROJECT_GROUPS,
NOOP_WORKSPACE_BOARD_DRAG_PREVIEW_CALLBACK
} from './worktree-list/virtualized-worktree-viewport-props'
} from './worktree-list/viewport/viewport-props'
import { useAgentSendTargetWorktreeId } from './worktree-list/use-agent-send-target-worktree'
import { useEffectiveCollapsedGroups } from './worktree-list/use-effective-collapsed-groups'
import { useProjectGroupDialogs } from './worktree-list/use-project-group-dialogs'
import { useSidebarExternalWorktreeCards } from './worktree-list/use-sidebar-external-worktree-cards'
import { useSidebarHostVisibleScope } from './worktree-list/use-sidebar-host-visible-scope'
import { useSidebarRevealRequests } from './worktree-list/use-sidebar-reveal-requests'
import { useSidebarRevealRequests } from './worktree-list/reveal/use-requests'
import { useSidebarSectionRows } from './worktree-list/use-sidebar-section-rows'
import { useSidebarWorktreeFilters } from './worktree-list/use-sidebar-worktree-filters'
import { useSidebarWorktreeSelection } from './worktree-list/use-sidebar-worktree-selection'
import { useSidebarWorktreeSortOrder } from './worktree-list/use-sidebar-worktree-sort-order'
import { useVisibleSidebarWorktrees } from './worktree-list/use-visible-sidebar-worktrees'
import { useWorktreeStatusMutations } from './worktree-list/use-worktree-status-mutations'
import { useWorktreeStatusMutations } from './worktree-list/use-status-mutations'
type WorktreeListProps = {
scrollOffsetRef: React.MutableRefObject<number>
@@ -1,6 +1,6 @@
import type { Repo } from '../../../../shared/repo-types'
import type { Worktree } from '../../../../shared/worktree/types'
import type { WorktreeGroupBy } from './worktree-list-groups'
import type { WorktreeGroupBy } from './worktree-list/rows/groups'
export function getEmptyProjectPlaceholderRepoIds(args: {
groupBy: WorktreeGroupBy
@@ -3,7 +3,7 @@ import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Repo } from '../../../../shared/repo-types'
import type { Worktree } from '../../../../shared/worktree/types'
import { PINNED_GROUP_KEY, type Row } from './worktree-list-groups'
import { PINNED_GROUP_KEY, type Row } from './worktree-list/rows/groups'
import { addHostSectionRows, type HostSectionRow } from './host-section-rows'
function repo(id: string, connectionId?: string | null): Repo {
@@ -14,7 +14,7 @@ import type { SshConnectionStatus } from '../../../../shared/ssh-types'
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Repo } from '../../../../shared/repo-types'
import type { Row } from './worktree-list-groups'
import type { Row } from './worktree-list/rows/groups'
export type HostHeaderRow = {
type: 'host-header'
@@ -12,7 +12,7 @@ import {
effectiveExternalWorktreeVisibility,
isLegacyRepoForExternalWorktreeVisibility
} from '../../../../shared/worktree/ownership'
import type { ImportedWorktreesCardCandidate } from './worktree-list-groups'
import type { ImportedWorktreesCardCandidate } from './worktree-list/rows/groups'
export function getHiddenImportedWorktrees(
detected: DetectedWorktreeListResult | undefined
@@ -8,7 +8,7 @@ import type { Repo } from '../../../../shared/repo-types'
import type { DetectedWorktreeListResult, Worktree } from '../../../../shared/worktree/types'
import { getNewExternalWorktreeInboxWorktrees } from '../../../../shared/external-worktree-inbox'
import { isGitRepoKind } from '../../../../shared/repo-kind'
import type { NewExternalWorktreesInboxCandidate } from './worktree-list-groups'
import type { NewExternalWorktreesInboxCandidate } from './worktree-list/rows/groups'
export function buildNewExternalWorktreesInboxCandidates(args: {
repos: readonly Repo[]
@@ -4,14 +4,14 @@ import { describe, expect, it } from 'vitest'
function readWorktreeListSource(): string {
return readFileSync(
fileURLToPath(new URL('./worktree-list/worktree-section-header-row.tsx', import.meta.url)),
fileURLToPath(new URL('./worktree-list/headers/SectionHeader.tsx', import.meta.url)),
'utf8'
)
}
function readHeaderDragSource(): string {
return readFileSync(
fileURLToPath(new URL('./worktree-list/use-worktree-sidebar-header-drag.ts', import.meta.url)),
fileURLToPath(new URL('./worktree-list/drag/use-header-drag.ts', import.meta.url)),
'utf8'
)
}
@@ -8,7 +8,7 @@ import {
getSidebarOrderedProjectGroupHeaderIdsByBucket,
mapSidebarProjectGroupDropIndexToSiblingInsertIndex
} from './project-group-header-drop'
import type { Row } from './worktree-list-groups'
import type { Row } from './worktree-list/rows/groups'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Repo } from '../../../../shared/repo-types'
@@ -2,7 +2,7 @@ import {
computeWorktreeSidebarHeaderDropPreview,
type WorktreeSidebarHeaderDropPreview
} from './worktree-sidebar-header-drop-preview'
import type { Row } from './worktree-list-groups'
import type { Row } from './worktree-list/rows/groups'
import type { ProjectGroup } from '../../../../shared/project-group-types'
export type ProjectGroupHeaderDragBucketKey = string
@@ -11,7 +11,7 @@ import {
mapSidebarProjectHeaderDropIndexToSiblingInsertIndex,
mapSidebarRepoDropIndexToAllRepoInsertAt
} from './project-header-drop'
import type { Row } from './worktree-list-groups'
import type { Row } from './worktree-list/rows/groups'
import type { Repo } from '../../../../shared/repo-types'
describe('getProjectHeaderDragBucketKey', () => {
@@ -3,7 +3,7 @@ import {
computeWorktreeSidebarHeaderDropPreview,
type WorktreeSidebarHeaderDropPreview
} from './worktree-sidebar-header-drop-preview'
import type { Row } from './worktree-list-groups'
import type { Row } from './worktree-list/rows/groups'
import type { Repo } from '../../../../shared/repo-types'
export type ProjectHeaderDragBucketKey = string
@@ -7,18 +7,18 @@ import {
} from '../../../../shared/execution-host'
import { getRepoMapFromState, getWorktreeMapFromState } from '@/store/selectors'
import { getProjectHostSetupProjectionFromState } from '@/store/project-host-setup-selector'
import { buildRows, getPinnedWorktreeDisplayPolicy } from './worktree-list-groups'
import { buildRows, getPinnedWorktreeDisplayPolicy } from './worktree-list/rows/groups'
import { addHostSectionRows } from './host-section-rows'
import { orderHostSectionOptions } from './host-section-order'
import { buildSidebarHostOptions } from './sidebar-host-options'
import { getLogicalRepoOrderRankById } from './project-header-drop'
import { getRenderedWorktreesInSidebarOrder } from './worktree-sidebar-row-preference'
import { selectWorktreeListReviewCacheInputs } from './worktree-list-review-cache-inputs'
import { selectWorktreeListReviewCacheInputs } from './worktree-list/viewport/review-cache-inputs'
import {
filterFolderWorkspacesForVisibleHosts,
filterProjectGroupsForVisibleHosts,
getVisibleSidebarHostIdSet
} from './worktree-list-host-filtering'
} from './worktree-list/viewport/host-filtering'
const EMPTY_REPO_ID_SET: ReadonlySet<string> = Object.freeze(new Set<string>())
const EMPTY_IMPORTED_BY_REPO = Object.freeze(new Map()) as never
@@ -3,7 +3,7 @@ import React from 'react'
import {
getFlushWorktreeCardPaddingLeft,
getNewCardStyleParentContentMarginLeft
} from './worktree-list-indentation'
} from './worktree-list/rows/indentation'
import {
hasWorktreeCardDetails,
WorktreeCardDetailsHover,
@@ -1,5 +1,5 @@
import type { WorktreeDragGroup } from './worktree-manual-order'
import { ALL_GROUP_KEY, PINNED_GROUP_KEY } from './worktree-list-groups'
import { ALL_GROUP_KEY, PINNED_GROUP_KEY } from './worktree-list/rows/groups'
export type WorktreeDragUnitGroup = WorktreeDragGroup & {
units: { worktreeId: string; worktreeIds: string[] }[]
@@ -1,4 +1,4 @@
import { estimateRenderRowSize, type RenderRow } from './worktree-list-virtual-rows'
import { estimateRenderRowSize, type RenderRow } from './worktree-list/viewport/virtual-rows'
function getEstimatedRenderRowStarts(
rows: readonly RenderRow[],
@@ -132,9 +132,7 @@ describe('getCyclableWorktreeIds', () => {
describe('WorktreeList keyboard cycling', () => {
it('cycles over the rendered rows instead of rebuilding a parallel layout', () => {
const source = readFileSync(
fileURLToPath(
new URL('./worktree-list/use-worktree-list-keyboard-navigation.ts', import.meta.url)
),
fileURLToPath(new URL('./worktree-list/viewport/use-keyboard.ts', import.meta.url)),
'utf8'
)
const navigateWorktree = source.slice(
@@ -1,5 +1,5 @@
import type { HostSectionRow } from './host-section-rows'
import type { PinnedWorktreeDisplayPolicy, WorktreeRow } from './worktree-list-groups'
import type { PinnedWorktreeDisplayPolicy, WorktreeRow } from './worktree-list/rows/groups'
import { getPreferredWorktreeRows } from './worktree-sidebar-row-preference'
/** Worktree ids in sidebar order, taken from the rows the sidebar actually
@@ -2,13 +2,13 @@ import type React from 'react'
import type {
WorkspaceStatus,
WorkspaceStatusDefinition
} from '../../../../../shared/worktree/types'
import type { WorktreeDragGroup } from '../worktree-manual-order'
import type { WorktreeDragUnitGroup } from '../worktree-drag-units'
import type { WorktreeSidebarDropPreview } from '../worktree-sidebar-drop-preview'
import type { WorktreeStatusDropAtIndexArgs } from './virtualized-worktree-viewport-props'
import type { WorktreeStatusDropRequest } from './use-worktree-drag-session'
import type { WorktreeSidebarLineageDropTarget } from './worktree-row-drag-state'
} from '../../../../../../shared/worktree/types'
import type { WorktreeDragGroup } from '../../worktree-manual-order'
import type { WorktreeDragUnitGroup } from '../../worktree-drag-units'
import type { WorktreeSidebarDropPreview } from '../../worktree-sidebar-drop-preview'
import type { WorktreeStatusDropAtIndexArgs } from '../viewport/viewport-props'
import type { WorktreeStatusDropRequest } from './use-session'
import type { WorktreeSidebarLineageDropTarget } from './row-state'
// The shared surface every drop path (pointer, native drag, document capture) commits through.
export type WorktreeDropCommitContext = {
@@ -1,6 +1,6 @@
import { ALL_GROUP_KEY, PINNED_GROUP_KEY } from '../worktree-list-groups'
import type { HostSectionRow } from '../host-section-rows'
import type { WorktreeDragGroup } from '../worktree-manual-order'
import { ALL_GROUP_KEY, PINNED_GROUP_KEY } from '../rows/groups'
import type { HostSectionRow } from '../../host-section-rows'
import type { WorktreeDragGroup } from '../../worktree-manual-order'
// A pinned duplicate of a worktree that also renders in its natural group is not its own drag slot.
function getNaturalWorktreeIds(rows: readonly HostSectionRow[]): Set<string> {
@@ -3,21 +3,15 @@ import {
getWorkspaceKanbanSidebarDropTarget,
isWorkspaceKanbanSidebarDropPointInBoard,
resolveWorkspaceKanbanSidebarFullLaneDropIndex
} from '../workspace-kanban-sidebar-drop'
import { resolveWorkspaceKanbanCardDropCommitTarget } from '../workspace-kanban-card-pointer-drag-dom'
import { getFullDropIndexForWorktreeDragUnit } from '../worktree-drag-units'
import { resolveWorktreeSidebarStatusDropCommitTarget } from '../worktree-sidebar-drop-preview'
import {
getPointerDropStatusTarget,
shouldPreferSidebarStatusDropTarget
} from './pointer-drop-status-target'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import type { WorktreeStatusDropAtIndexArgs } from './virtualized-worktree-viewport-props'
import type { WorktreeSidebarStatusDropTarget } from '../worktree-sidebar-drop-preview'
import {
NO_WORKTREE_SIDEBAR_DROP_TARGET,
type WorktreePointerDrag
} from './worktree-row-drag-state'
} from '../../workspace-kanban-sidebar-drop'
import { resolveWorkspaceKanbanCardDropCommitTarget } from '../../workspace-kanban-card-pointer-drag-dom'
import { getFullDropIndexForWorktreeDragUnit } from '../../worktree-drag-units'
import { resolveWorktreeSidebarStatusDropCommitTarget } from '../../worktree-sidebar-drop-preview'
import { getPointerDropStatusTarget, shouldPreferSidebarStatusDropTarget } from './status-target'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import type { WorktreeStatusDropAtIndexArgs } from '../viewport/viewport-props'
import type { WorktreeSidebarStatusDropTarget } from '../../worktree-sidebar-drop-preview'
import { NO_WORKTREE_SIDEBAR_DROP_TARGET, type WorktreePointerDrag } from './row-state'
type PointerDropCommitArgs = {
event: PointerEvent
@@ -1,17 +1,14 @@
import type React from 'react'
import type { WorkspaceStatus } from '../../../../../shared/worktree/types'
import type { WorkspaceStatus } from '../../../../../../shared/worktree/types'
import {
clearWorkspaceKanbanSidebarDropTargetVisual,
hasWorkspaceKanbanSidebarDropBoard,
isWorkspaceKanbanSidebarDropPointInBoard,
updateWorkspaceKanbanSidebarDropTargetVisual
} from '../workspace-kanban-sidebar-drop'
import { updateSidebarDragPreviewPosition } from '../worktree-sidebar-pointer-drag-dom'
import {
getPointerDropStatusTarget,
shouldPreferSidebarStatusDropTarget
} from './pointer-drop-status-target'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
} from '../../workspace-kanban-sidebar-drop'
import { updateSidebarDragPreviewPosition } from '../../worktree-sidebar-pointer-drag-dom'
import { getPointerDropStatusTarget, shouldPreferSidebarStatusDropTarget } from './status-target'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import {
applyWorktreeDropPreview,
clearWorktreeDropPreview,
@@ -20,7 +17,7 @@ import {
type WorktreePointerDrag,
type WorktreeRowDragState,
type WorktreeSidebarLineageDropTarget
} from './worktree-row-drag-state'
} from './row-state'
export type WorktreePointerDragFrameArgs = {
drag: WorktreePointerDrag
@@ -1,10 +1,10 @@
import type { WorkspaceKanbanCardTrackedDropTarget } from '../workspace-kanban-card-pointer-drag-dom'
import type { WorktreeSidebarDragRect } from '../worktree-sidebar-drag-autoscroll'
import type { WorkspaceKanbanCardTrackedDropTarget } from '../../workspace-kanban-card-pointer-drag-dom'
import type { WorktreeSidebarDragRect } from '../../worktree-sidebar-drag-autoscroll'
import type {
WorktreeSidebarDropPreview,
WorktreeSidebarStatusDropTarget,
WorktreeSidebarTrackedStatusDropTarget
} from '../worktree-sidebar-drop-preview'
} from '../../worktree-sidebar-drop-preview'
export type WorktreeRowDragState = {
draggingWorktreeId: string | null
@@ -1,14 +1,11 @@
import type {
WorkspaceStatus,
WorkspaceStatusDefinition
} from '../../../../../shared/worktree/types'
import { getWorkspaceStatusFromGroupKey } from '../workspace-status'
import { getWorktreeLineageDropTargetId } from '../worktree-lineage-drag-drop'
import type { WorktreeSidebarStatusDropTarget } from '../worktree-sidebar-drop-preview'
import {
NO_WORKTREE_SIDEBAR_DROP_TARGET,
type WorktreeSidebarLineageDropTarget
} from './worktree-row-drag-state'
} from '../../../../../../shared/worktree/types'
import { getWorkspaceStatusFromGroupKey } from '../../workspace-status'
import { getWorktreeLineageDropTargetId } from '../../worktree-lineage-drag-drop'
import type { WorktreeSidebarStatusDropTarget } from '../../worktree-sidebar-drop-preview'
import { NO_WORKTREE_SIDEBAR_DROP_TARGET, type WorktreeSidebarLineageDropTarget } from './row-state'
export function getPointerDropStatusTarget(args: {
container: HTMLElement
@@ -1,10 +1,10 @@
import { useEffect } from 'react'
import type React from 'react'
import { getFullDropIndexForWorktreeDragUnit } from '../worktree-drag-units'
import type { WorktreeSidebarDragSession } from '../worktree-sidebar-drag-autoscroll'
import { getPointerDropStatusTarget } from './pointer-drop-status-target'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import { NO_WORKTREE_SIDEBAR_DROP_TARGET } from './worktree-row-drag-state'
import { getFullDropIndexForWorktreeDragUnit } from '../../worktree-drag-units'
import type { WorktreeSidebarDragSession } from '../../worktree-sidebar-drag-autoscroll'
import { getPointerDropStatusTarget } from './status-target'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import { NO_WORKTREE_SIDEBAR_DROP_TARGET } from './row-state'
// Why: a drop that lands outside the sidebar still belongs to the in-flight session, so the
// capture-phase document listeners commit or abandon it before anything else sees the event.
@@ -1,23 +1,23 @@
import { useCallback, useEffect, useMemo } from 'react'
import type React from 'react'
import { useAppStore } from '@/store'
import type { ProjectGroup } from '../../../../../shared/project-group-types'
import type { ProjectOrderBy } from '../../../../../shared/ui-chrome-types'
import type { Repo } from '../../../../../shared/repo-types'
import type { ExecutionHostId } from '../../../../../shared/execution-host'
import type { HostHeaderRow, HostSectionRow } from '../host-section-rows'
import type { Row, WorktreeGroupBy } from '../worktree-list-groups'
import type { RenderRow } from '../worktree-list-virtual-rows'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { ProjectOrderBy } from '../../../../../../shared/ui-chrome-types'
import type { Repo } from '../../../../../../shared/repo-types'
import type { ExecutionHostId } from '../../../../../../shared/execution-host'
import type { HostHeaderRow, HostSectionRow } from '../../host-section-rows'
import type { Row, WorktreeGroupBy } from '../rows/groups'
import type { RenderRow } from '../viewport/virtual-rows'
import {
getProjectGroupHeaderSectionEndByGroupId,
getRepoHeaderSectionEndByRepoId
} from '../worktree-header-section-boundaries'
import { useHostHeaderDrag } from '../host-header-drag'
import { useRepoHeaderDrag } from '../project-header-drag'
import { getSidebarOrderedRepoHeaderIdsByBucket } from '../project-header-drop'
import { useProjectGroupHeaderDrag } from '../project-group-header-drag'
import { getSidebarOrderedProjectGroupHeaderIdsByBucket } from '../project-group-header-drop'
import { USER_SCROLL_MEASUREMENT_ADJUSTMENT_SUPPRESS_MS } from './worktree-sidebar-scroll-tuning'
} from '../../worktree-header-section-boundaries'
import { useHostHeaderDrag } from '../../host-header-drag'
import { useRepoHeaderDrag } from '../../project-header-drag'
import { getSidebarOrderedRepoHeaderIdsByBucket } from '../../project-header-drop'
import { useProjectGroupHeaderDrag } from '../../project-group-header-drag'
import { getSidebarOrderedProjectGroupHeaderIdsByBucket } from '../../project-group-header-drop'
import { USER_SCROLL_MEASUREMENT_ADJUSTMENT_SUPPRESS_MS } from '../scroll/tuning'
export type WorktreeSidebarHeaderDrag = ReturnType<typeof useWorktreeSidebarHeaderDrag>
@@ -2,14 +2,14 @@ import { useCallback, useMemo } from 'react'
import { toast } from 'sonner'
import { useAppStore } from '@/store'
import { translate } from '@/i18n/i18n'
import type { Repo } from '../../../../../shared/repo-types'
import type { Worktree } from '../../../../../shared/worktree/types'
import type { WorktreeLineage } from '../../../../../shared/worktree/lineage-types'
import { isEligibleWorktreeParent } from '../worktree-parent-candidates'
import { getCyclicProjectedWorktreeLineageIds } from '../worktree-lineage-projection'
import { getReorderedWorktreeIdsToUnnest } from '../worktree-lineage-drag-drop'
import type { WorktreeDragGroup } from '../worktree-manual-order'
import type { WorktreeSidebarLineageDropTarget } from './worktree-row-drag-state'
import type { Repo } from '../../../../../../shared/repo-types'
import type { Worktree } from '../../../../../../shared/worktree/types'
import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types'
import { isEligibleWorktreeParent } from '../../worktree-parent-candidates'
import { getCyclicProjectedWorktreeLineageIds } from '../../worktree-lineage-projection'
import { getReorderedWorktreeIdsToUnnest } from '../../worktree-lineage-drag-drop'
import type { WorktreeDragGroup } from '../../worktree-manual-order'
import type { WorktreeSidebarLineageDropTarget } from './row-state'
export type WorktreeLineageDropCommit = ReturnType<typeof useWorktreeLineageDropCommit>
@@ -1,11 +1,11 @@
import { useCallback } from 'react'
import type React from 'react'
import { getWorktreeSidebarDragAutoscroll } from '../worktree-sidebar-drag-autoscroll'
import { getPointerDropStatusTarget } from './pointer-drop-status-target'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import type { WorktreeDragRuntime } from './use-worktree-drag-runtime'
import type { WorktreeDragSession } from './use-worktree-drag-session'
import { applyWorktreeDropPreview, clearWorktreeDropPreview } from './worktree-row-drag-state'
import { getWorktreeSidebarDragAutoscroll } from '../../worktree-sidebar-drag-autoscroll'
import { getPointerDropStatusTarget } from './status-target'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import type { WorktreeDragRuntime } from './use-runtime'
import type { WorktreeDragSession } from './use-session'
import { applyWorktreeDropPreview, clearWorktreeDropPreview } from './row-state'
// The HTML5 drag equivalent of pointer autoscroll: the browser gives no move events while
// hovering the edge, so re-derive the drop preview from the last known point each frame.
@@ -1,20 +1,20 @@
import { useCallback } from 'react'
import type React from 'react'
import { getWorkspaceKanbanSidebarDropTarget } from '../workspace-kanban-sidebar-drop'
import { getFullDropIndexForWorktreeDragUnit } from '../worktree-drag-units'
import { getWorktreeSidebarDragRectsForGroup } from '../worktree-sidebar-drag-autoscroll'
import { getWorktreeSidebarDragGrab } from '../worktree-sidebar-drag-geometry'
import { getPointerDropStatusTarget } from './pointer-drop-status-target'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import type { WorktreeDragRuntime } from './use-worktree-drag-runtime'
import type { WorktreeDragSession } from './use-worktree-drag-session'
import { useWorktreeNativeDragAutoscroll } from './use-worktree-native-drag-autoscroll'
import { getWorkspaceKanbanSidebarDropTarget } from '../../workspace-kanban-sidebar-drop'
import { getFullDropIndexForWorktreeDragUnit } from '../../worktree-drag-units'
import { getWorktreeSidebarDragRectsForGroup } from '../../worktree-sidebar-drag-autoscroll'
import { getWorktreeSidebarDragGrab } from '../../worktree-sidebar-drag-geometry'
import { getPointerDropStatusTarget } from './status-target'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import type { WorktreeDragRuntime } from './use-runtime'
import type { WorktreeDragSession } from './use-session'
import { useWorktreeNativeDragAutoscroll } from './use-native-autoscroll'
import {
applyWorktreeDropPreview,
clearWorktreeDropPreview,
EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS,
NO_WORKTREE_SIDEBAR_DROP_TARGET
} from './worktree-row-drag-state'
} from './row-state'
// The HTML5 drag path used by cards that start a native dragstart (board <-> sidebar transfers).
export function useWorktreeNativeDrag(args: {
@@ -1,9 +1,9 @@
import { useCallback } from 'react'
import type React from 'react'
import { getWorktreeSidebarDragAutoscroll } from '../worktree-sidebar-drag-autoscroll'
import type { WorktreeDragRuntime } from './use-worktree-drag-runtime'
import type { WorktreeDragSession } from './use-worktree-drag-session'
import type { WorktreePointerDrag } from './worktree-row-drag-state'
import { getWorktreeSidebarDragAutoscroll } from '../../worktree-sidebar-drag-autoscroll'
import type { WorktreeDragRuntime } from './use-runtime'
import type { WorktreeDragSession } from './use-session'
import type { WorktreePointerDrag } from './row-state'
// Scrolls the sidebar while a pointer drag hovers near its top or bottom edge, refreshing
// the drag session on every scrolled frame so the insertion line stays on the right row.
@@ -1,26 +1,23 @@
import { useCallback, useEffect } from 'react'
import type React from 'react'
import type { WorkspaceStatus, Worktree } from '../../../../../shared/worktree/types'
import { hasWorkspaceKanbanSidebarDropBoard } from '../workspace-kanban-sidebar-drop'
import type { WorkspaceStatus, Worktree } from '../../../../../../shared/worktree/types'
import { hasWorkspaceKanbanSidebarDropBoard } from '../../workspace-kanban-sidebar-drop'
import {
createSidebarDragPreview,
isSidebarPointerDragBlocked,
setSidebarPointerDragDocumentStyles
} from '../worktree-sidebar-pointer-drag-dom'
import { getWorktreeSidebarDragRectsForGroup } from '../worktree-sidebar-drag-autoscroll'
import { getWorktreeSidebarDragGrab } from '../worktree-sidebar-drag-geometry'
import { NOOP_WORKSPACE_BOARD_DRAG_PREVIEW_CALLBACK } from './virtualized-worktree-viewport-props'
import type { WorktreeStatusDropAtIndexArgs } from './virtualized-worktree-viewport-props'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import type { WorktreeDragRuntime } from './use-worktree-drag-runtime'
import type { WorktreeDragSession } from './use-worktree-drag-session'
import { useWorktreePointerDragAutoscroll } from './use-worktree-pointer-drag-autoscroll'
import { useWorktreePointerDragWindowEvents } from './use-worktree-pointer-drag-window-events'
import { flushWorktreePointerDragFrame } from './worktree-pointer-drag-flush'
import {
EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS,
type WorktreePointerDrag
} from './worktree-row-drag-state'
} from '../../worktree-sidebar-pointer-drag-dom'
import { getWorktreeSidebarDragRectsForGroup } from '../../worktree-sidebar-drag-autoscroll'
import { getWorktreeSidebarDragGrab } from '../../worktree-sidebar-drag-geometry'
import { NOOP_WORKSPACE_BOARD_DRAG_PREVIEW_CALLBACK } from '../viewport/viewport-props'
import type { WorktreeStatusDropAtIndexArgs } from '../viewport/viewport-props'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import type { WorktreeDragRuntime } from './use-runtime'
import type { WorktreeDragSession } from './use-session'
import { useWorktreePointerDragAutoscroll } from './use-pointer-autoscroll'
import { useWorktreePointerDragWindowEvents } from './use-pointer-window-events'
import { flushWorktreePointerDragFrame } from './pointer-flush'
import { EMPTY_WORKTREE_DRAG_PREVIEW_OFFSETS, type WorktreePointerDrag } from './row-state'
export function useWorktreePointerDrag(args: {
ctx: WorktreeDropCommitContext
@@ -1,10 +1,10 @@
import { useEffect } from 'react'
import type { WorktreeDragRuntime } from './use-worktree-drag-runtime'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import type { WorktreeStatusDropAtIndexArgs } from './virtualized-worktree-viewport-props'
import { commitWorktreePointerDrop } from './worktree-pointer-drag-commit'
import { SIDEBAR_POINTER_DRAG_THRESHOLD_PX } from './worktree-sidebar-scroll-tuning'
import type { WorktreePointerDrag } from './worktree-row-drag-state'
import type { WorktreeDragRuntime } from './use-runtime'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import type { WorktreeStatusDropAtIndexArgs } from '../viewport/viewport-props'
import { commitWorktreePointerDrop } from './pointer-commit'
import { SIDEBAR_POINTER_DRAG_THRESHOLD_PX } from '../scroll/tuning'
import type { WorktreePointerDrag } from './row-state'
// Pointer drags escape the row that started them, so move/up/cancel are tracked on the
// window in capture phase.
@@ -1,18 +1,18 @@
import { useCallback, useMemo, useRef, useState } from 'react'
import type React from 'react'
import type { WorkspaceStatus } from '../../../../../shared/worktree/types'
import { clearWorkspaceKanbanSidebarDropTargetVisual } from '../workspace-kanban-sidebar-drop'
import { setSidebarPointerDragDocumentStyles } from '../worktree-sidebar-pointer-drag-dom'
import type { WorkspaceStatus } from '../../../../../../shared/worktree/types'
import { clearWorkspaceKanbanSidebarDropTargetVisual } from '../../workspace-kanban-sidebar-drop'
import { setSidebarPointerDragDocumentStyles } from '../../worktree-sidebar-pointer-drag-dom'
import type {
WorktreeSidebarDragPoint,
WorktreeSidebarDragSession
} from '../worktree-sidebar-drag-autoscroll'
import type { WorktreeSidebarDropAnchor } from '../worktree-sidebar-drag-geometry'
} from '../../worktree-sidebar-drag-autoscroll'
import type { WorktreeSidebarDropAnchor } from '../../worktree-sidebar-drag-geometry'
import {
WORKTREE_ROW_DRAG_INITIAL_STATE,
type WorktreePointerDrag,
type WorktreeRowDragState
} from './worktree-row-drag-state'
} from './row-state'
export type WorktreeDragRuntime = ReturnType<typeof useWorktreeDragRuntime>
@@ -1,29 +1,29 @@
import { useCallback, useMemo, useRef } from 'react'
import type React from 'react'
import type { WorkspaceStatus } from '../../../../../shared/worktree/types'
import type { HostSectionRow } from '../host-section-rows'
import { PINNED_GROUP_KEY } from '../worktree-list-groups'
import { WORKTREE_SIDEBAR_VIRTUAL_ROW_GAP } from '../worktree-list-virtual-rows'
import { getWorkspaceStatusGroupKey } from '../workspace-status'
import { expandDraggedWorktreeIdsForVisibleLineage } from '../worktree-manual-order'
import { getWorktreeDragUnitGroups } from '../worktree-drag-units'
import type { WorkspaceStatus } from '../../../../../../shared/worktree/types'
import type { HostSectionRow } from '../../host-section-rows'
import { PINNED_GROUP_KEY } from '../rows/groups'
import { WORKTREE_SIDEBAR_VIRTUAL_ROW_GAP } from '../viewport/virtual-rows'
import { getWorkspaceStatusGroupKey } from '../../workspace-status'
import { expandDraggedWorktreeIdsForVisibleLineage } from '../../worktree-manual-order'
import { getWorktreeDragUnitGroups } from '../../worktree-drag-units'
import {
getWorktreeSidebarDragRectsForGroup,
refreshWorktreeSidebarDragSession,
type WorktreeSidebarDragRect,
type WorktreeSidebarDragSession
} from '../worktree-sidebar-drag-autoscroll'
} from '../../worktree-sidebar-drag-autoscroll'
import {
shouldReevaluateWorktreeSidebarDropAnchor,
type WorktreeSidebarDragGrab,
type WorktreeSidebarDropAnchor
} from '../worktree-sidebar-drag-geometry'
} from '../../worktree-sidebar-drag-geometry'
import {
computeWorktreeSidebarDropPreview,
type WorktreeSidebarDropPreview
} from '../worktree-sidebar-drop-preview'
import { getWorktreeDragGroups, getWorktreeDragIndexes } from './worktree-drag-groups'
import type { WorktreeItemRow } from './render-row-item-rows'
} from '../../worktree-sidebar-drop-preview'
import { getWorktreeDragGroups, getWorktreeDragIndexes } from './groups'
import type { WorktreeItemRow } from '../rows/renderable-rows'
export type WorktreeStatusDropRequest = {
pointerY: number
@@ -1,13 +1,13 @@
import { useCallback, useMemo } from 'react'
import type React from 'react'
import type { WorkspaceStatus } from '../../../../../shared/worktree/types'
import type { HostSectionRow } from '../host-section-rows'
import { PINNED_GROUP_KEY, type WorktreeGroupBy } from '../worktree-list-groups'
import { hasWorkspaceDragData, readWorkspaceDragDataIds } from '../workspace-status'
import { useWorkspaceStatusDocumentDrop } from '../use-workspace-status-drop'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import type { WorktreeDragSession } from './use-worktree-drag-session'
import type { WorktreeDragRuntime } from './use-worktree-drag-runtime'
import type { WorkspaceStatus } from '../../../../../../shared/worktree/types'
import type { HostSectionRow } from '../../host-section-rows'
import { PINNED_GROUP_KEY, type WorktreeGroupBy } from '../rows/groups'
import { hasWorkspaceDragData, readWorkspaceDragDataIds } from '../../workspace-status'
import { useWorkspaceStatusDocumentDrop } from '../../use-workspace-status-drop'
import type { WorktreeDropCommitContext } from './drop-commit-context'
import type { WorktreeDragSession } from './use-session'
import type { WorktreeDragRuntime } from './use-runtime'
// Drag-and-drop onto a status section header or a status-grouped row, including the
// document-level fallback used when the pointer leaves the sidebar mid-drag.
@@ -9,7 +9,7 @@ import {
import {
isConfirmedStaleFolderPathStatus,
type FolderWorkspacePathStatus
} from '../../../../../shared/folder-workspace-path-status'
} from '../../../../../../shared/folder-workspace-path-status'
export function FolderPathStatusIndicator({
status
@@ -3,8 +3,8 @@ import { AlertTriangle, ChevronDown, Loader2, Server, ServerOff } from 'lucide-r
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import type { HostHeaderRow } from '../host-section-rows'
import { HostSectionHeaderMenu } from '../HostSectionHeaderMenu'
import type { HostHeaderRow } from '../../host-section-rows'
import { HostSectionHeaderMenu } from '../../HostSectionHeaderMenu'
function formatSectionActivityLabel(count: number, label: string): string {
return `${count} ${label}${count === 1 ? '' : 's'}`
@@ -5,24 +5,24 @@ import { cn } from '@/lib/utils'
import type { AppState } from '@/store/types'
import { RepoIconGlyph } from '@/components/repo/repo-icon'
import { RepoForkIndicator } from '@/components/repo/repo-fork-indicator'
import type { FolderWorkspacePathStatus } from '../../../../../shared/folder-workspace-path-status'
import { isConfirmedStaleFolderPathStatus } from '../../../../../shared/folder-workspace-path-status'
import type { ProjectGroup } from '../../../../../shared/project-group-types'
import type { FolderWorkspacePathStatus } from '../../../../../../shared/folder-workspace-path-status'
import { isConfirmedStaleFolderPathStatus } from '../../../../../../shared/folder-workspace-path-status'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type {
WorkspaceStatus,
WorkspaceStatusDefinition
} from '../../../../../shared/worktree/types'
import type { GroupHeaderRow, WorktreeGroupBy } from '../worktree-list-groups'
import { PINNED_GROUP_KEY } from '../worktree-list-groups'
import { getWorkspaceStatusFromGroupKey } from '../workspace-status'
import { getVirtualRowTransform } from '../worktree-list-virtual-rows'
import { resolveProjectGroupHeaderColor } from '../project-header-color'
import { getRepoHeaderCreateState } from '../repo-header-create-state'
import { ProjectHeaderActions } from '../ProjectHeaderActions'
} from '../../../../../../shared/worktree/types'
import type { GroupHeaderRow, WorktreeGroupBy } from '../rows/groups'
import { PINNED_GROUP_KEY } from '../rows/groups'
import { getWorkspaceStatusFromGroupKey } from '../../workspace-status'
import { getVirtualRowTransform } from '../viewport/virtual-rows'
import { resolveProjectGroupHeaderColor } from '../../project-header-color'
import { getRepoHeaderCreateState } from '../../repo-header-create-state'
import { ProjectHeaderActions } from '../../ProjectHeaderActions'
import {
getProjectGroupHeaderPaddingLeft,
WORKTREE_SECTION_HEADER_PADDING_LEFT
} from '../worktree-list-indentation'
} from '../rows/indentation'
import { FolderPathStatusIndicator } from './FolderPathStatusIndicator'
import {
ProjectGroupCreateWorkspaceButton,
@@ -36,9 +36,9 @@ import {
import {
handleRepoHeaderCollapseAffordancePointerDown,
shouldIgnoreRepoHeaderToggle
} from './repo-header-event-guards'
import type { WorktreeSidebarHeaderDrag } from './use-worktree-sidebar-header-drag'
import { getWorktreeOptionId } from './worktree-option-dom'
} from './event-guards'
import type { WorktreeSidebarHeaderDrag } from '../drag/use-header-drag'
import { getWorktreeOptionId } from '../option-dom'
export type SectionHeaderRowContext = {
groupBy: WorktreeGroupBy
@@ -1,5 +1,5 @@
import type React from 'react'
import { isRepoHeaderActionTarget } from '../project-header-drag'
import { isRepoHeaderActionTarget } from '../../project-header-drag'
export function stopRepoHeaderKeyboardToggle(event: React.KeyboardEvent<HTMLElement>): void {
if (event.key === 'Enter' || event.key === ' ') {
@@ -11,14 +11,14 @@ import {
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import { getFolderWorkspacePathStatusDescription } from '@/lib/folder-workspace-path-status'
import type { ProjectGroup } from '../../../../../shared/project-group-types'
import type { FolderWorkspacePathStatus } from '../../../../../shared/folder-workspace-path-status'
import { REPO_HEADER_ACTION_BUTTON_CLASS } from '../repo-header-action-button-class'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { FolderWorkspacePathStatus } from '../../../../../../shared/folder-workspace-path-status'
import { REPO_HEADER_ACTION_BUTTON_CLASS } from '../../repo-header-action-button-class'
import {
handleRepoHeaderActionPointerDown,
stopRepoHeaderKeyboardToggle,
stopRepoHeaderMenuEvent
} from './repo-header-event-guards'
} from './event-guards'
export function ProjectGroupHeaderMenu({
groupId,
@@ -25,21 +25,36 @@ import {
import { cn } from '@/lib/utils'
import { translate } from '@/i18n/i18n'
import { getRepositoryIconSectionId } from '@/components/settings/repository-settings-targets'
import type { ProjectGroup } from '../../../../../shared/project-group-types'
import type { Repo } from '../../../../../shared/repo-types'
import type { WorktreeVisibilityDefaults } from '../../../../../shared/global-settings-types'
import { isGitRepoKind } from '../../../../../shared/repo-kind'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { Repo } from '../../../../../../shared/repo-types'
import type { WorktreeVisibilityDefaults } from '../../../../../../shared/global-settings-types'
import { isGitRepoKind } from '../../../../../../shared/repo-kind'
import {
effectiveExternalWorktreeVisibility,
isLegacyRepoForExternalWorktreeVisibility
} from '../../../../../../shared/worktree/ownership'
import {
REPO_HEADER_ACTION_BUTTON_CLASS,
REPO_HEADER_ACTION_REVEAL_CLASS
} from '../repo-header-action-button-class'
import type { getRepoHeaderCreateState } from '../repo-header-create-state'
} from '../../repo-header-action-button-class'
import type { getRepoHeaderCreateState } from '../../repo-header-create-state'
import {
handleRepoHeaderActionPointerDown,
stopRepoHeaderKeyboardToggle,
stopRepoHeaderMenuEvent
} from './repo-header-event-guards'
import { getWorktreeVisibilityMenuLabel } from './worktree-visibility-menu-label'
} from './event-guards'
function getWorktreeVisibilityMenuLabel(
repo: Repo,
visibilityDefaults?: WorktreeVisibilityDefaults
): string {
const visibility = effectiveExternalWorktreeVisibility(
repo,
isLegacyRepoForExternalWorktreeVisibility(repo),
visibilityDefaults
)
return visibility === 'show' ? 'Hide non-Orca worktrees' : 'Show hidden worktrees'
}
export type RepoHeaderProjectActions = {
getWorktreeVisibilityDefaults: (repo: Repo) => WorktreeVisibilityDefaults | undefined
@@ -1,9 +0,0 @@
import type { Row } from '../worktree-list-groups'
import type { ImportedWorktreeCardActionState } from '../imported-worktrees-card-actions'
export function canKeepImportedWorktreesHidden(
row: Extract<Row, { type: 'imported-worktrees-card' }>,
actionState: ImportedWorktreeCardActionState | undefined
): boolean {
return row.placement === 'repo-group' && actionState?.forceVisible !== true
}
@@ -1,13 +0,0 @@
import { PINNED_GROUP_KEY } from '../worktree-list-groups'
import type { HostSectionRow } from '../host-section-rows'
export type WorktreeItemRow = Extract<HostSectionRow, { type: 'item' }>
export type FolderWorkspaceItemRow = Extract<HostSectionRow, { type: 'folder-workspace' }>
export function isWorktreeItemRow(row: HostSectionRow): row is WorktreeItemRow {
return row.type === 'item'
}
export function isPinnedWorktreeRow(row: WorktreeItemRow): boolean {
return row.sectionKey === PINNED_GROUP_KEY
}
@@ -1,11 +1,11 @@
import type { ProjectGroup } from '../../../../../shared/project-group-types'
import type { Repo } from '../../../../../shared/repo-types'
import type { Worktree } from '../../../../../shared/worktree/types'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { Repo } from '../../../../../../shared/repo-types'
import type { Worktree } from '../../../../../../shared/worktree/types'
import {
getProjectGroupHeaderKey,
PINNED_GROUP_KEY,
type ProjectGroupingModel
} from '../worktree-list-groups'
} from '../rows/groups'
function getProjectIdFromHeaderRowKey(rowKey: string): string | null {
if (!rowKey.startsWith('project:')) {
@@ -1,14 +1,14 @@
import { describe, expect, it } from 'vitest'
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Worktree } from '../../../../shared/worktree/types'
import { folderWorkspaceKey } from '../../../../shared/workspace-scope'
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { Worktree } from '../../../../../../shared/worktree/types'
import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope'
import {
getFolderWorkspaceRevealGroupKeys,
getKnownSidebarWorktreeById,
sidebarWorkspaceStillExists
} from './worktree-list-folder-reveal'
import { getProjectGroupHeaderKey } from './worktree-list-groups'
} from './folder-reveal'
import { getProjectGroupHeaderKey } from '../rows/groups'
function makeFolderWorkspace(overrides: Partial<FolderWorkspace> = {}): FolderWorkspace {
return {
@@ -1,9 +1,9 @@
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Worktree } from '../../../../shared/worktree/types'
import { folderWorkspaceToWorktree } from '../../../../shared/folder-workspace-worktree'
import { parseWorkspaceKey } from '../../../../shared/workspace-scope'
import { getProjectGroupHeaderKey } from './worktree-list-groups'
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { Worktree } from '../../../../../../shared/worktree/types'
import { folderWorkspaceToWorktree } from '../../../../../../shared/folder-workspace-worktree'
import { parseWorkspaceKey } from '../../../../../../shared/workspace-scope'
import { getProjectGroupHeaderKey } from '../rows/groups'
function findFolderWorkspaceByKey(
worktreeId: string,
@@ -1,5 +1,5 @@
import { revealElementInScrollContainer } from '../worktree-sidebar-reveal'
import { getMountedWorktreeOptions, getWorktreeOptionId } from './worktree-option-dom'
import { revealElementInScrollContainer } from '../../worktree-sidebar-reveal'
import { getMountedWorktreeOptions, getWorktreeOptionId } from '../option-dom'
export function revealMountedWorktreeElement(
container: HTMLElement,
@@ -2,24 +2,24 @@ import type React from 'react'
import type { Virtualizer } from '@tanstack/react-virtual'
import type { AppState } from '@/store/types'
import type { PendingSidebarRowReveal, PendingSidebarWorktreeReveal } from '@/store/slices/ui'
import type { FolderWorkspace } from '../../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../../shared/project-group-types'
import type { Repo } from '../../../../../shared/repo-types'
import type { WorkspaceStatusDefinition, Worktree } from '../../../../../shared/worktree/types'
import type { WorktreeLineage } from '../../../../../shared/worktree/lineage-types'
import type { ExecutionHostId } from '../../../../../shared/execution-host'
import { getWorktreeExecutionHostId } from '../../../../../shared/execution-host'
import type { RenderRow } from '../worktree-list-virtual-rows'
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { Repo } from '../../../../../../shared/repo-types'
import type { WorkspaceStatusDefinition, Worktree } from '../../../../../../shared/worktree/types'
import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types'
import type { ExecutionHostId } from '../../../../../../shared/execution-host'
import { getWorktreeExecutionHostId } from '../../../../../../shared/execution-host'
import type { RenderRow } from '../viewport/virtual-rows'
import {
getGroupKeysForWorktree,
getLineageGroupKey,
type PinnedWorktreeDisplayPolicy,
type ProjectGroupingModel,
type WorktreeGroupBy
} from '../worktree-list-groups'
import { getWorktreeLineageAncestors } from '../worktree-lineage-projection'
import { getFolderWorkspaceRevealGroupKeys } from '../worktree-list-folder-reveal'
import { getPinnedWorktreeRevealCollapsedGroupKeys } from './sidebar-row-reveal-ancestors'
} from '../rows/groups'
import { getWorktreeLineageAncestors } from '../../worktree-lineage-projection'
import { getFolderWorkspaceRevealGroupKeys } from './folder-reveal'
import { getPinnedWorktreeRevealCollapsedGroupKeys } from './ancestors'
export const MAX_REVEAL_RETRIES = 8
@@ -2,21 +2,21 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { toast } from 'sonner'
import { useAppStore } from '@/store'
import { translate } from '@/i18n/i18n'
import { getRenderRowOptionId } from './active-descendant-option'
import { getRenderRowOptionId } from '../viewport/active-descendant-option'
import {
findPreferredRenderRowIndexForWorktree,
getRenderRowSidebarKey,
rowKeyMatchesRenderRow
} from './render-row-worktree-lookup'
import { revealMountedSidebarRowElement, revealMountedWorktreeElement } from './mounted-row-reveal'
import { getSidebarRowRevealAncestorKeys } from './sidebar-row-reveal-ancestors'
import { sidebarWorkspaceStillExists } from '../worktree-list-folder-reveal'
import { resolvePendingSidebarReveal } from './worktree-sidebar-scroll-tuning'
} from '../rows/render-row-lookup'
import { revealMountedSidebarRowElement, revealMountedWorktreeElement } from './mounted-row'
import { getSidebarRowRevealAncestorKeys } from './ancestors'
import { sidebarWorkspaceStillExists } from './folder-reveal'
import { resolvePendingSidebarReveal } from '../scroll/tuning'
import {
expandGroupsForWorktreeReveal,
MAX_REVEAL_RETRIES,
type PendingSidebarRevealArgs
} from './pending-sidebar-reveal-inputs'
} from './pending-inputs'
// Drives the store's two pending reveal requests (a worktree card, or any sidebar row)
// from "expand the ancestors" through "scroll the mounted element into view".
@@ -4,10 +4,10 @@ import {
SCROLL_TO_CURRENT_WORKSPACE_REVEAL_REQUEST_EVENT,
type ScrollToCurrentWorkspaceRevealRequestDetail
} from '@/lib/scroll-to-current-workspace-status'
import type { FolderWorkspace } from '../../../../../shared/folder-workspace-types'
import type { Worktree } from '../../../../../shared/worktree/types'
import type { WorktreeGroupBy } from '../worktree-list-groups'
import { getKnownSidebarWorktreeById } from '../worktree-list-folder-reveal'
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
import type { Worktree } from '../../../../../../shared/worktree/types'
import type { WorktreeGroupBy } from '../rows/groups'
import { getKnownSidebarWorktreeById } from './folder-reveal'
// Turns a "show me the current workspace" request into whatever the sidebar must change
// first — grouping mode, active filters — before the viewport can scroll to it.
@@ -1,23 +1,23 @@
import React from 'react'
import type { VirtualItem } from '@tanstack/react-virtual'
import type { AppState } from '@/store/types'
import type { Repo } from '../../../../../shared/repo-types'
import type { Repo } from '../../../../../../shared/repo-types'
import type {
WorkspaceLineage,
WorktreeLineage
} from '../../../../../shared/worktree/lineage-types'
import type { Worktree } from '../../../../../shared/worktree/types'
import type { FolderWorkspacePathStatus } from '../../../../../shared/folder-workspace-path-status'
import { isConfirmedStaleFolderPathStatus } from '../../../../../shared/folder-workspace-path-status'
import { folderWorkspaceToWorktree } from '../../../../../shared/folder-workspace-worktree'
import WorktreeCard from '../WorktreeCard'
import type { WorktreeGroupBy } from '../worktree-list-groups'
import { getVirtualRowTransform } from '../worktree-list-virtual-rows'
import { getFolderWorkspaceRowGeometry } from '../worktree-list-indentation'
import { getFolderWorkspaceCardPrDisplay } from '../folder-workspace-card-pr-display'
import { FolderPathStatusIndicator } from './FolderPathStatusIndicator'
import type { FolderWorkspaceItemRow } from './render-row-item-rows'
import { getWorktreeOptionId } from './worktree-option-dom'
} from '../../../../../../shared/worktree/lineage-types'
import type { Worktree } from '../../../../../../shared/worktree/types'
import type { FolderWorkspacePathStatus } from '../../../../../../shared/folder-workspace-path-status'
import { isConfirmedStaleFolderPathStatus } from '../../../../../../shared/folder-workspace-path-status'
import { folderWorkspaceToWorktree } from '../../../../../../shared/folder-workspace-worktree'
import WorktreeCard from '../../WorktreeCard'
import type { WorktreeGroupBy } from './groups'
import { getVirtualRowTransform } from '../viewport/virtual-rows'
import { getFolderWorkspaceRowGeometry } from './indentation'
import { getFolderWorkspaceCardPrDisplay } from '../../folder-workspace-card-pr-display'
import { FolderPathStatusIndicator } from '../headers/FolderPathStatusIndicator'
import type { FolderWorkspaceItemRow } from './renderable-rows'
import { getWorktreeOptionId } from '../option-dom'
export type FolderWorkspaceRowContext = {
groupBy: WorktreeGroupBy
@@ -2,8 +2,8 @@
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { getExecutionHostLabel } from '../../../../shared/execution-host'
import { projectHostSetupProjectionFromRepos } from '../../../../shared/project-host-setup-projection'
import { getExecutionHostLabel } from '../../../../../../shared/execution-host'
import { projectHostSetupProjectionFromRepos } from '../../../../../../shared/project-host-setup-projection'
import {
ALL_GROUP_META,
buildRows,
@@ -14,18 +14,18 @@ import {
getPRGroupKey,
PINNED_GROUP_KEY,
type PendingCreationRef
} from './worktree-list-groups'
} from './groups'
import {
REPO_HEADER_ACTION_BUTTON_CLASS,
REPO_HEADER_ACTION_REVEAL_CLASS
} from './repo-header-action-button-class'
import { getWorktreeLineageAncestors } from './worktree-lineage-projection'
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Project, ProjectHostSetup } from '../../../../shared/project-types'
import type { Repo } from '../../../../shared/repo-types'
import type { WorktreeLineage } from '../../../../shared/worktree/lineage-types'
import type { DetectedWorktree, Worktree } from '../../../../shared/worktree/types'
} from '../../repo-header-action-button-class'
import { getWorktreeLineageAncestors } from '../../worktree-lineage-projection'
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { Project, ProjectHostSetup } from '../../../../../../shared/project-types'
import type { Repo } from '../../../../../../shared/repo-types'
import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types'
import type { DetectedWorktree, Worktree } from '../../../../../../shared/worktree/types'
const LOCAL_HOST_LABEL = getExecutionHostLabel('local')
@@ -60,12 +60,12 @@ const worktree: Worktree = {
const repoMap = new Map([[repo.id, repo]])
function readWorktreeListSource(): string {
return readFileSync(fileURLToPath(new URL('./WorktreeList.tsx', import.meta.url)), 'utf8')
return readFileSync(fileURLToPath(new URL('../../WorktreeList.tsx', import.meta.url)), 'utf8')
}
function readSectionHeaderRowSource(): string {
return readFileSync(
fileURLToPath(new URL('./worktree-list/worktree-section-header-row.tsx', import.meta.url)),
fileURLToPath(new URL('../headers/SectionHeader.tsx', import.meta.url)),
'utf8'
)
}
@@ -1,36 +1,39 @@
/* eslint-disable max-lines -- Why: sidebar row construction keeps every grouping mode in one pure module so reveal, virtualized rendering, and tests share the same flat row contract. */
import { CircleX, FolderTree, List, Pin } from 'lucide-react'
import type React from 'react'
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { Project, ProjectHostSetup } from '../../../../shared/project-types'
import type { Repo } from '../../../../shared/repo-types'
import type { ProjectOrderBy } from '../../../../shared/ui-chrome-types'
import type { WorktreeLineage } from '../../../../shared/worktree/lineage-types'
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { Project, ProjectHostSetup } from '../../../../../../shared/project-types'
import type { Repo } from '../../../../../../shared/repo-types'
import type { ProjectOrderBy } from '../../../../../../shared/ui-chrome-types'
import type { WorktreeLineage } from '../../../../../../shared/worktree/lineage-types'
import type {
DetectedWorktree,
WorkspaceStatusDefinition,
Worktree
} from '../../../../shared/worktree/types'
import { branchName } from '../../lib/git-utils'
} from '../../../../../../shared/worktree/types'
import { branchName } from '../../../../lib/git-utils'
import {
getWorkspaceStatus,
getWorkspaceStatusFromGroupKey,
getWorkspaceStatusGroupKey,
getWorkspaceStatusVisualMeta
} from './workspace-status'
} from '../../workspace-status'
import {
ConductorDoneIcon,
ConductorProgressIcon,
ConductorReviewIcon
} from './workspace-status-icons'
} from '../../workspace-status-icons'
import {
getEffectiveProjectGroupManualRank,
UNGROUPED_PROJECT_GROUP_KEY
} from '../../../../shared/project-groups'
import { cloneDefaultWorkspaceStatuses } from '../../../../shared/workspace-statuses'
import type { AppState } from '../../store/types'
import { getGitHubPRCacheKey, getLegacyGitHubPRCacheKey } from '../../store/slices/github-cache-key'
} from '../../../../../../shared/project-groups'
import { cloneDefaultWorkspaceStatuses } from '../../../../../../shared/workspace-statuses'
import type { AppState } from '../../../../store/types'
import {
getGitHubPRCacheKey,
getLegacyGitHubPRCacheKey
} from '../../../../store/slices/github-cache-key'
import { getRepoDisplayLabelKey, getRepoDisplayLabelsByPath } from '@/lib/repo-display-labels'
import { translate } from '@/i18n/i18n'
import {
@@ -40,18 +43,18 @@ import {
getWorktreeExecutionHostId,
toSshExecutionHostId,
type ExecutionHostId
} from '../../../../shared/execution-host'
import { parseWslUncPath } from '../../../../shared/wsl-paths'
} from '../../../../../../shared/execution-host'
import { parseWslUncPath } from '../../../../../../shared/wsl-paths'
import {
isWindowsAbsolutePathLike,
normalizeRuntimePathForComparison
} from '../../../../shared/cross-platform-path'
} from '../../../../../../shared/cross-platform-path'
import {
getCyclicProjectedWorktreeLineageIds,
getLineageRenderInfo
} from './worktree-lineage-projection'
} from '../../worktree-lineage-projection'
export { getLineageRenderInfo } from './worktree-lineage-projection'
export { getLineageRenderInfo } from '../../worktree-lineage-projection'
export { branchName }
@@ -1,11 +1,11 @@
import { describe, expect, it } from 'vitest'
import { canKeepImportedWorktreesHidden } from './worktree-list/imported-worktrees-card-visibility'
import { getRenderRowKey } from './worktree-list-virtual-rows'
import { getWorktreeDragGroups, getWorktreeDragIndexes } from './worktree-list/worktree-drag-groups'
import { renderRowContainsWorktree } from './worktree-list/render-row-worktree-lookup'
import type { Repo } from '../../../../shared/repo-types'
import type { Worktree } from '../../../../shared/worktree/types'
import type { Row } from './worktree-list-groups'
import { canKeepImportedWorktreesHidden } from './notice-rows'
import { getRenderRowKey } from '../viewport/virtual-rows'
import { getWorktreeDragGroups, getWorktreeDragIndexes } from '../drag/groups'
import { renderRowContainsWorktree } from './render-row-lookup'
import type { Repo } from '../../../../../../shared/repo-types'
import type { Worktree } from '../../../../../../shared/worktree/types'
import type { Row } from './groups'
const repo: Repo = {
id: 'repo-1',
@@ -20,7 +20,7 @@ import {
getProjectGroupHeaderPaddingLeft,
getWorktreeCardContentIndent,
getWorktreeCardSurfaceInset
} from './worktree-list-indentation'
} from './indentation'
function getFlushCardContentStart(args: {
surfaceInset: number
@@ -1,9 +1,9 @@
import React from 'react'
import { cn } from '@/lib/utils'
import type { AppState } from '@/store/types'
import type { Worktree } from '../../../../../shared/worktree/types'
import WorktreeCard, { type ActiveSurfaceVariant } from '../WorktreeCard'
import { PINNED_GROUP_KEY, type WorktreeGroupBy } from '../worktree-list-groups'
import type { Worktree } from '../../../../../../shared/worktree/types'
import WorktreeCard, { type ActiveSurfaceVariant } from '../../WorktreeCard'
import { PINNED_GROUP_KEY, type WorktreeGroupBy } from './groups'
import {
getFolderBackedRepoWorktreeCardContentIndent,
getFolderBackedRepoWorktreeCardSurfaceInset,
@@ -12,12 +12,12 @@ import {
getWorktreeCardContentIndent,
getWorktreeCardSurfaceInset,
LINEAGE_CHILDREN_INLINE_OFFSET
} from '../worktree-list-indentation'
import type { LineageToggleHandler } from '../worktree-lineage-toggle-handler-cache'
import { stopNestedWorktreeCardBubble } from './repo-header-event-guards'
import type { WorktreeItemRow } from './render-row-item-rows'
import { getWorktreeOptionId } from './worktree-option-dom'
import type { WorktreePointerDrag, WorktreeRowDragState } from './worktree-row-drag-state'
} from './indentation'
import type { LineageToggleHandler } from '../../worktree-lineage-toggle-handler-cache'
import { stopNestedWorktreeCardBubble } from '../headers/event-guards'
import type { WorktreeItemRow } from './renderable-rows'
import { getWorktreeOptionId } from '../option-dom'
import type { WorktreePointerDrag, WorktreeRowDragState } from '../drag/row-state'
export type WorktreeItemRowContext = {
settings: AppState['settings']
@@ -1,13 +1,19 @@
import React from 'react'
import type { VirtualItem } from '@tanstack/react-virtual'
import type { Row } from '../worktree-list-groups'
import { getVirtualRowTransform } from '../worktree-list-virtual-rows'
import { PendingWorktreeRow } from '../PendingWorktreeRow'
import ImportedWorktreesVisibilityLine from '../ImportedWorktreesVisibilityLine'
import NewExternalWorktreesInboxLine from '../NewExternalWorktreesInboxLine'
import type { ImportedWorktreeCardActionState } from '../imported-worktrees-card-actions'
import type { NewExternalWorktreesInboxActionState } from '../new-external-worktrees-inbox-actions'
import { canKeepImportedWorktreesHidden } from './imported-worktrees-card-visibility'
import type { Row } from './groups'
import { getVirtualRowTransform } from '../viewport/virtual-rows'
import { PendingWorktreeRow } from '../../PendingWorktreeRow'
import ImportedWorktreesVisibilityLine from '../../ImportedWorktreesVisibilityLine'
import NewExternalWorktreesInboxLine from '../../NewExternalWorktreesInboxLine'
import type { ImportedWorktreeCardActionState } from '../../imported-worktrees-card-actions'
import type { NewExternalWorktreesInboxActionState } from '../../new-external-worktrees-inbox-actions'
export function canKeepImportedWorktreesHidden(
row: Extract<Row, { type: 'imported-worktrees-card' }>,
actionState: ImportedWorktreeCardActionState | undefined
): boolean {
return row.placement === 'repo-group' && actionState?.forceVisible !== true
}
type NoticeRowFrameProps = {
vItem: VirtualItem
@@ -1,7 +1,7 @@
import { folderWorkspaceKey } from '../../../../../shared/workspace-scope'
import type { RenderRow } from '../worktree-list-virtual-rows'
import type { PinnedWorktreeDisplayPolicy } from '../worktree-list-groups'
import { isPinnedWorktreeRow, type WorktreeItemRow } from './render-row-item-rows'
import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope'
import type { RenderRow } from '../viewport/virtual-rows'
import type { PinnedWorktreeDisplayPolicy } from './groups'
import { isPinnedWorktreeRow, type WorktreeItemRow } from './renderable-rows'
export function getRenderRowSidebarKey(row: RenderRow): string | null {
if (row.type === 'header') {
@@ -1,7 +1,17 @@
import { getLineageGroupKey } from '../worktree-list-groups'
import type { HostSectionRow } from '../host-section-rows'
import type { RenderRow } from '../worktree-list-virtual-rows'
import { isWorktreeItemRow, type WorktreeItemRow } from './render-row-item-rows'
import { getLineageGroupKey, PINNED_GROUP_KEY } from './groups'
import type { HostSectionRow } from '../../host-section-rows'
import type { RenderRow } from '../viewport/virtual-rows'
export type WorktreeItemRow = Extract<HostSectionRow, { type: 'item' }>
export type FolderWorkspaceItemRow = Extract<HostSectionRow, { type: 'folder-workspace' }>
export function isWorktreeItemRow(row: HostSectionRow): row is WorktreeItemRow {
return row.type === 'item'
}
export function isPinnedWorktreeRow(row: WorktreeItemRow): boolean {
return row.sectionKey === PINNED_GROUP_KEY
}
// Collapse a parent and its visible lineage descendants into one virtual row so the card renders them inline.
export function buildRenderableRows(rows: HostSectionRow[]): RenderRow[] {
@@ -1,20 +1,20 @@
import type React from 'react'
import type { AppState } from '@/store/types'
import type { ProjectGroup } from '../../../../../shared/project-group-types'
import type { RenderRow } from '../worktree-list-virtual-rows'
import type { LineageToggleHandler } from '../worktree-lineage-toggle-handler-cache'
import type { SidebarRevealHighlight } from './use-sidebar-reveal-highlight'
import type { WorktreeDragRuntime } from './use-worktree-drag-runtime'
import type { WorktreeDragSession } from './use-worktree-drag-session'
import type { useFolderWorkspacePathStatusRows } from './use-folder-workspace-path-status-rows'
import type { usePrimaryActiveWorktreeRow } from './use-primary-active-worktree-row'
import type { useWorkspaceStatusRowDrag } from './use-workspace-status-row-drag'
import type { useWorktreeNativeDrag } from './use-worktree-native-drag'
import type { WorktreeSidebarHeaderDrag } from './use-worktree-sidebar-header-drag'
import type { WorktreeListVirtualizer } from './use-worktree-list-virtualizer'
import type { VirtualizedWorktreeViewportProps } from './virtualized-worktree-viewport-props'
import type { WorktreeVirtualRowContext } from './worktree-virtual-row-dispatch'
import { getRepoOwnerWorktreeVisibilityDefaults } from '../../../store/worktree-visibility-defaults-by-host'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import type { RenderRow } from '../viewport/virtual-rows'
import type { LineageToggleHandler } from '../../worktree-lineage-toggle-handler-cache'
import type { SidebarRevealHighlight } from '../reveal/use-highlight'
import type { WorktreeDragRuntime } from '../drag/use-runtime'
import type { WorktreeDragSession } from '../drag/use-session'
import type { useFolderWorkspacePathStatusRows } from '../viewport/use-folder-path-statuses'
import type { usePrimaryActiveWorktreeRow } from '../viewport/use-active-row'
import type { useWorkspaceStatusRowDrag } from '../drag/use-status-row-drag'
import type { useWorktreeNativeDrag } from '../drag/use-native-drag'
import type { WorktreeSidebarHeaderDrag } from '../drag/use-header-drag'
import type { WorktreeListVirtualizer } from '../viewport/use-virtualizer'
import type { VirtualizedWorktreeViewportProps } from '../viewport/viewport-props'
import type { WorktreeVirtualRowContext } from './virtual-row-dispatch'
import { getRepoOwnerWorktreeVisibilityDefaults } from '../../../../store/worktree-visibility-defaults-by-host'
type BuildArgs = {
props: VirtualizedWorktreeViewportProps
@@ -4,38 +4,35 @@ import { cn } from '@/lib/utils'
import type {
WorkspaceStatus,
WorkspaceStatusDefinition
} from '../../../../../shared/worktree/types'
import { getWorkspaceStatus } from '../workspace-status'
import type { WorktreeGroupBy } from '../worktree-list-groups'
} from '../../../../../../shared/worktree/types'
import { getWorkspaceStatus } from '../../workspace-status'
import type { WorktreeGroupBy } from './groups'
import {
getVirtualRowTransform,
shouldUseHeaderTopSpacing,
type RenderRow
} from '../worktree-list-virtual-rows'
import type { ImportedWorktreeCardActionState } from '../imported-worktrees-card-actions'
import type { NewExternalWorktreesInboxActionState } from '../new-external-worktrees-inbox-actions'
import type { useHostHeaderDrag } from '../host-header-drag'
import { HostSectionHeader } from './HostSectionHeader'
import {
renderFolderWorkspaceVirtualRow,
type FolderWorkspaceRowContext
} from './folder-workspace-virtual-row'
} from '../viewport/virtual-rows'
import type { ImportedWorktreeCardActionState } from '../../imported-worktrees-card-actions'
import type { NewExternalWorktreesInboxActionState } from '../../new-external-worktrees-inbox-actions'
import type { useHostHeaderDrag } from '../../host-header-drag'
import { HostSectionHeader } from '../headers/HostSectionHeader'
import { renderFolderWorkspaceVirtualRow, type FolderWorkspaceRowContext } from './folder-row'
import {
renderImportedWorktreesVirtualRow,
renderNewExternalWorktreesInboxVirtualRow,
renderPendingCreationVirtualRow
} from './worktree-notice-virtual-rows'
} from './notice-rows'
import {
renderWorktreeItemRow,
renderWorktreeLineageDescendants,
type WorktreeItemRowContext
} from './worktree-list-item-row'
} from './item-row'
import {
renderWorktreeSectionHeaderRow,
type SectionHeaderRowContext
} from './worktree-section-header-row'
import { getWorktreeVirtualRowTransform } from './virtual-row-dom-attributes'
import type { WorktreeRowDragState } from './worktree-row-drag-state'
} from '../headers/SectionHeader'
import { getWorktreeVirtualRowTransform } from '../viewport/virtual-rows'
import type { WorktreeRowDragState } from '../drag/row-state'
export type WorktreeVirtualRowContext = {
renderRows: RenderRow[]
@@ -7,7 +7,7 @@ import {
reduceHardScrollUpOnIdle,
reduceHardScrollUpOnScroll,
reduceHardScrollUpOnWheel
} from './worktree-list-hard-scroll-up'
} from './hard-scroll-up'
const DEEP = {
scrollTop: 1200,
@@ -3,19 +3,19 @@ import {
countRecordKeysByReference,
resolvePendingSidebarReveal,
shouldAdjustWorktreeSidebarMeasuredRowScroll
} from './worktree-list/worktree-sidebar-scroll-tuning'
} from './tuning'
import {
getScrollTopToRevealBounds,
WORKTREE_SIDEBAR_REVEAL_TOP_INSET
} from './worktree-sidebar-reveal'
} from '../../worktree-sidebar-reveal'
import {
extractWorktreeVirtualRowIndexes,
estimateRenderRowSize,
GROUP_HEADER_ROW_HEIGHT,
getActiveStickyHeaderIndexForScroll
} from './worktree-list-virtual-rows'
import type { Repo } from '../../../../shared/repo-types'
import type { Row } from './worktree-list-groups'
} from '../viewport/virtual-rows'
import type { Repo } from '../../../../../../shared/repo-types'
import type { Row } from '../rows/groups'
const repo: Repo = {
id: 'repo-1',
@@ -40,3 +40,8 @@ export function resolvePendingSidebarReveal(args: {
}
return args.targetWorktreeStillExists ? 'keep-pending' : 'clear'
}
export function installWorktreeVisibleRefreshVisibilityListener(onChange: () => void): () => void {
document.addEventListener('visibilitychange', onChange)
return () => document.removeEventListener('visibilitychange', onChange)
}
@@ -1,8 +1,8 @@
// @vitest-environment happy-dom
import { act, renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useWorktreeListScrollToTop } from './use-worktree-list-scroll-to-top'
import { HARD_SCROLL_UP } from './worktree-list-hard-scroll-up'
import { useWorktreeListScrollToTop } from './use-scroll-to-top'
import { HARD_SCROLL_UP } from './hard-scroll-up'
let clockMs = 0
@@ -2,7 +2,7 @@
import { act, cleanup, renderHook } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { useWorktreeListScrollToTop } from './use-worktree-list-scroll-to-top'
import { useWorktreeListScrollToTop } from './use-scroll-to-top'
function createScroller(): HTMLElement {
const element = document.createElement('div')
@@ -7,7 +7,7 @@ import {
reduceHardScrollUpOnScroll,
reduceHardScrollUpOnWheel,
type HardScrollUpDetectorState
} from './worktree-list-hard-scroll-up'
} from './hard-scroll-up'
function readViewport(element: HTMLElement): { scrollTop: number; maxScroll: number } {
const maxScroll = Math.max(0, element.scrollHeight - element.clientHeight)
@@ -1,15 +1,15 @@
import { useCallback, useEffect, useRef } from 'react'
import type React from 'react'
import { SUPPRESS_WORKTREE_LIST_SCROLL_ADJUSTMENT_EVENT } from '../WorktreeCardAgents'
import { SUPPRESS_WORKTREE_LIST_SCROLL_ADJUSTMENT_EVENT } from '../../WorktreeCardAgents'
import {
createPendingRevealScroll,
isRevealScrollSettling,
type PendingRevealScroll
} from '../worktree-sidebar-reveal-scroll-settle'
} from '../../worktree-sidebar-reveal-scroll-settle'
import {
EXPANDING_CARD_MEASUREMENT_ADJUSTMENT_SUPPRESS_MS,
USER_SCROLL_MEASUREMENT_ADJUSTMENT_SUPPRESS_MS
} from './worktree-sidebar-scroll-tuning'
} from './tuning'
export type WorktreeSidebarScrollSuppression = ReturnType<
typeof useWorktreeSidebarScrollSuppression
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { installWorktreeVisibleRefreshVisibilityListener } from './worktree-list/worktree-visible-refresh-listener'
import { installWorktreeVisibleRefreshVisibilityListener } from './tuning'
describe('installWorktreeVisibleRefreshVisibilityListener', () => {
beforeEach(() => {
@@ -1,19 +0,0 @@
export function isEditableTarget(target: EventTarget | null): boolean {
if (!(target instanceof HTMLElement)) {
return false
}
// xterm's hidden input textarea isn't a real text field; treating it as one would block sidebar shortcuts.
if (target.classList.contains('xterm-helper-textarea')) {
return false
}
if (target.isContentEditable) {
return true
}
return (
target.closest('input, textarea, select, [contenteditable=""], [contenteditable="true"]') !==
null
)
}
@@ -1,9 +0,0 @@
import type { AppState } from '@/store/types'
// Why: selectors that opt out of a slice must return one stable reference, or every store tick looks like a change.
export const EMPTY_AGENT_STATUS_BY_PANE_KEY: AppState['agentStatusByPaneKey'] = {}
export const EMPTY_WORKTREE_ID_SET: ReadonlySet<string> = new Set()
export const EMPTY_TABS_BY_WORKTREE: AppState['tabsByWorktree'] = {}
export const EMPTY_TERMINAL_LAYOUTS_BY_TAB_ID: AppState['terminalLayoutsByTabId'] = {}
export const EMPTY_PTY_IDS_BY_TAB_ID: AppState['ptyIdsByTabId'] = {}
export const EMPTY_RUNTIME_PANE_TITLES_BY_TAB_ID: AppState['runtimePaneTitlesByTabId'] = {}
@@ -1,13 +1,14 @@
import { useMemo } from 'react'
import { useAppStore } from '@/store'
import type { AppState } from '@/store/types'
import { deriveRunningAgentSendTargets } from '@/lib/running-agent-targets'
import {
EMPTY_AGENT_STATUS_BY_PANE_KEY,
EMPTY_PTY_IDS_BY_TAB_ID,
EMPTY_RUNTIME_PANE_TITLES_BY_TAB_ID,
EMPTY_TABS_BY_WORKTREE,
EMPTY_TERMINAL_LAYOUTS_BY_TAB_ID
} from './stable-empty-store-slices'
// Why: selectors that opt out of a slice must return one stable reference, or every store tick looks like a change.
const EMPTY_AGENT_STATUS_BY_PANE_KEY: AppState['agentStatusByPaneKey'] = {}
const EMPTY_TABS_BY_WORKTREE: AppState['tabsByWorktree'] = {}
const EMPTY_TERMINAL_LAYOUTS_BY_TAB_ID: AppState['terminalLayoutsByTabId'] = {}
const EMPTY_PTY_IDS_BY_TAB_ID: AppState['ptyIdsByTabId'] = {}
const EMPTY_RUNTIME_PANE_TITLES_BY_TAB_ID: AppState['runtimePaneTitlesByTabId'] = {}
// The agent send picker forces its target workspace visible, but only while a running
// agent there can actually receive the message.
@@ -10,7 +10,7 @@ import {
PINNED_GROUP_KEY,
type ProjectGroupingModel,
type WorktreeGroupBy
} from '../worktree-list-groups'
} from './rows/groups'
import { getWorktreeLineageAncestors } from '../worktree-lineage-projection'
// While the agent send picker targets a workspace, force open every section that hides it.
@@ -9,7 +9,7 @@ import {
filterFolderWorkspacesForVisibleHosts,
filterProjectGroupsForVisibleHosts,
getVisibleSidebarHostIdSet
} from '../worktree-list-host-filtering'
} from './viewport/host-filtering'
// Narrows repos, project groups, and folder workspaces to the hosts (and devices) the
// current host filter admits.
@@ -17,7 +17,7 @@ import {
type ProjectGroupingModel,
type Row,
type WorktreeGroupBy
} from '../worktree-list-groups'
} from './rows/groups'
import { getLogicalRepoOrderRankById } from '../project-header-drop'
import { getEmptyProjectPlaceholderRepoIds } from '../empty-project-placeholder-repos'
import { addHostSectionRows } from '../host-section-rows'
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'reac
import type React from 'react'
import type { Worktree } from '../../../../../shared/worktree/types'
import type { HostSectionRow } from '../host-section-rows'
import type { PinnedWorktreeDisplayPolicy } from '../worktree-list-groups'
import type { PinnedWorktreeDisplayPolicy } from './rows/groups'
import { getRenderedWorktreesInSidebarOrder } from '../worktree-sidebar-row-preference'
import { setVisibleWorktreeIds } from '../visible-worktrees'
import {
@@ -14,7 +14,7 @@ import {
type WorktreeAttention
} from '../smart-attention'
import { useReusedArrayIdentity } from './use-reused-array-identity'
import { SORT_SETTLE_MS } from './worktree-sidebar-scroll-tuning'
import { SORT_SETTLE_MS } from './scroll/tuning'
function trackSmartClassDistribution(attention: ReadonlyMap<string, WorktreeAttention>): void {
let class1 = 0
@@ -15,7 +15,7 @@ import {
} from '../worktree-manual-order'
import { buildWorkspaceKanbanSidebarDropUpdates } from '../workspace-kanban-sidebar-drop'
import type { SortBy } from '../smart-sort'
import type { WorktreeStatusDropAtIndexArgs } from './virtualized-worktree-viewport-props'
import type { WorktreeStatusDropAtIndexArgs } from './viewport/viewport-props'
function buildRankByWorktreeId(
groups: readonly WorktreeDragGroup[],
@@ -17,9 +17,10 @@ import {
} from '../visible-worktree-activity-inputs'
import type { SortBy } from '../smart-sort'
import type { SidebarWorktreeFilters } from './use-sidebar-worktree-filters'
import { EMPTY_WORKTREE_ID_SET } from './stable-empty-store-slices'
import { useReusedArrayIdentity } from './use-reused-array-identity'
const EMPTY_WORKTREE_ID_SET: ReadonlySet<string> = new Set()
// Applies every sidebar filter to the sorted id stream. Flatten/filter/sort goes through the
// shared utility so card order matches Cmd+1–9 numbering.
export function useVisibleSidebarWorktrees(args: {
@@ -1,37 +1,34 @@
import React, { useCallback, useMemo, useRef, useState } from 'react'
import { useAppStore } from '@/store'
import { translate } from '@/i18n/i18n'
import { WorktreeSidebarDropIndicator } from '../WorktreeSidebarDropIndicator'
import { WorktreeListScrollToTopButton } from '../WorktreeListScrollToTopButton'
import { useWorktreeListScrollToTop } from '../use-worktree-list-scroll-to-top'
import { WorktreeSidebarDropIndicator } from '../../WorktreeSidebarDropIndicator'
import { WorktreeListScrollToTopButton } from '../../WorktreeListScrollToTopButton'
import { useWorktreeListScrollToTop } from '../scroll/use-scroll-to-top'
import { getActiveDescendantOptionId } from './active-descendant-option'
import { buildRenderableRows } from './renderable-rows'
import { useFolderWorkspacePathStatusRows } from './use-folder-workspace-path-status-rows'
import { useGroupToggleWithScrollAnchor } from './use-group-toggle-with-scroll-anchor'
import { usePendingSidebarReveal } from './use-pending-sidebar-reveal'
import { usePrimaryActiveWorktreeRow } from './use-primary-active-worktree-row'
import { useSidebarRevealHighlight } from './use-sidebar-reveal-highlight'
import { useVirtualRowMeasurementSync } from './use-virtual-row-measurement-sync'
import { useVisiblePrRefreshReporting } from './use-visible-pr-refresh-reporting'
import { useWorkspaceStatusRowDrag } from './use-workspace-status-row-drag'
import { useWorktreeDragRuntime } from './use-worktree-drag-runtime'
import { useWorktreeDragSession } from './use-worktree-drag-session'
import { useWorktreeDocumentDrop } from './use-worktree-document-drop'
import { useWorktreeLineageDropCommit } from './use-worktree-lineage-drop-commit'
import { useWorktreeListKeyboardNavigation } from './use-worktree-list-keyboard-navigation'
import { useWorktreeListVirtualizer } from './use-worktree-list-virtualizer'
import { useWorktreeNativeDrag } from './use-worktree-native-drag'
import { useWorktreePointerDrag } from './use-worktree-pointer-drag'
import { useWorktreeSidebarHeaderDrag } from './use-worktree-sidebar-header-drag'
import { useWorktreeSidebarScrollSuppression } from './use-worktree-sidebar-scroll-suppression'
import {
EMPTY_PROJECT_GROUPS,
type VirtualizedWorktreeViewportProps
} from './virtualized-worktree-viewport-props'
import { WORKTREE_SIDEBAR_SCROLL_STYLE } from './worktree-sidebar-scroll-tuning'
import type { WorktreeDropCommitContext } from './worktree-drop-commit-context'
import { buildWorktreeVirtualRowContext } from './worktree-virtual-row-context'
import { renderWorktreeVirtualRow } from './worktree-virtual-row-dispatch'
import { buildRenderableRows } from '../rows/renderable-rows'
import { useFolderWorkspacePathStatusRows } from './use-folder-path-statuses'
import { useGroupToggleWithScrollAnchor } from './use-group-toggle'
import { usePendingSidebarReveal } from '../reveal/use-pending'
import { usePrimaryActiveWorktreeRow } from './use-active-row'
import { useSidebarRevealHighlight } from '../reveal/use-highlight'
import { useVirtualRowMeasurementSync } from './use-row-measurement'
import { useVisiblePrRefreshReporting } from './use-visible-review-refresh'
import { useWorkspaceStatusRowDrag } from '../drag/use-status-row-drag'
import { useWorktreeDragRuntime } from '../drag/use-runtime'
import { useWorktreeDragSession } from '../drag/use-session'
import { useWorktreeDocumentDrop } from '../drag/use-document-drop'
import { useWorktreeLineageDropCommit } from '../drag/use-lineage-drop-commit'
import { useWorktreeListKeyboardNavigation } from './use-keyboard'
import { useWorktreeListVirtualizer } from './use-virtualizer'
import { useWorktreeNativeDrag } from '../drag/use-native-drag'
import { useWorktreePointerDrag } from '../drag/use-pointer-drag'
import { useWorktreeSidebarHeaderDrag } from '../drag/use-header-drag'
import { useWorktreeSidebarScrollSuppression } from '../scroll/use-suppression'
import { EMPTY_PROJECT_GROUPS, type VirtualizedWorktreeViewportProps } from './viewport-props'
import { WORKTREE_SIDEBAR_SCROLL_STYLE } from '../scroll/tuning'
import type { WorktreeDropCommitContext } from '../drag/drop-commit-context'
import { buildWorktreeVirtualRowContext } from '../rows/virtual-row-context'
import { renderWorktreeVirtualRow } from '../rows/virtual-row-dispatch'
export const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewport(
props: VirtualizedWorktreeViewportProps
@@ -1,9 +1,9 @@
import { folderWorkspaceKey } from '../../../../../shared/workspace-scope'
import type { RenderRow } from '../worktree-list-virtual-rows'
import type { PinnedWorktreeDisplayPolicy } from '../worktree-list-groups'
import { isPinnedWorktreeRow } from './render-row-item-rows'
import { getRenderRowWorktreeItem, renderRowContainsWorktree } from './render-row-worktree-lookup'
import { getWorktreeOptionId } from './worktree-option-dom'
import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope'
import type { RenderRow } from './virtual-rows'
import type { PinnedWorktreeDisplayPolicy } from '../rows/groups'
import { isPinnedWorktreeRow } from '../rows/renderable-rows'
import { getRenderRowWorktreeItem, renderRowContainsWorktree } from '../rows/render-row-lookup'
import { getWorktreeOptionId } from '../option-dom'
export function getRenderRowOptionId(
row: RenderRow | undefined,
@@ -1,12 +1,12 @@
import { describe, expect, it } from 'vitest'
import type { FolderWorkspace } from '../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../shared/project-group-types'
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
import {
getFolderPathStatusRouteOptionsForRows,
getFolderWorkspaceExecutionHostIdForRows,
getProjectGroupExecutionHostIdForRows,
getRuntimeEnvironmentIdForFolderPathStatusHost
} from './worktree-list-host-filtering'
} from './host-filtering'
function group(overrides: Partial<ProjectGroup> = {}): ProjectGroup {
return {

Some files were not shown because too many files have changed in this diff Show More