perf(sidebar): share one natural-worktree-id scan and drop a redundant row-key join

The natural-worktree-id Set was spelled out four times — twice in the drag
groups module, once in the drag session hook, once in the drag units module —
each as rows.flatMap(row => cond ? [id] : []), which allocates a throwaway array
per row. All four memoize on the same rows, so they recompute together. Share
one loop-based helper.

use-row-measurement also built a joined string of every row key purely as an
effect dependency, alongside a Set memoized on the same input. A fresh Set
always changes identity, so the string could never fire the effect on its own.
This commit is contained in:
Neil
2026-09-04 04:45:09 -07:00
parent 637dc30a32
commit 794e6dcf3e
6 changed files with 75 additions and 35 deletions
@@ -0,0 +1,45 @@
import { describe, expect, it } from 'vitest'
import { PINNED_GROUP_KEY } from './worktree-list/grouping/group-keys'
import { getNaturalWorktreeIds } from './natural-worktree-ids'
const item = (id: string, sectionKey: string) => ({
type: 'item' as const,
sectionKey,
worktree: { id }
})
describe('getNaturalWorktreeIds', () => {
it('collects item rows outside the pinned section', () => {
expect([...getNaturalWorktreeIds([item('a', 'group-1'), item('b', 'group-2')])]).toEqual([
'a',
'b'
])
})
it('excludes a pinned duplicate that also renders in its natural group', () => {
const rows = [item('a', PINNED_GROUP_KEY), item('a', 'group-1'), item('b', PINNED_GROUP_KEY)]
const ids = getNaturalWorktreeIds(rows)
expect(ids.has('a')).toBe(true)
expect(ids.has('b')).toBe(false)
})
it('ignores every non-item row type', () => {
const rows = [
{ type: 'header', key: 'k' },
{ type: 'host-header' },
{ type: 'imported-worktrees-card' },
{ type: 'new-external-worktrees-inbox' },
{ type: 'pending-creation' },
{ type: 'folder-workspace' },
item('a', 'group-1')
]
expect([...getNaturalWorktreeIds(rows)]).toEqual(['a'])
})
it('returns an empty set for no rows', () => {
expect(getNaturalWorktreeIds([]).size).toBe(0)
})
})
@@ -0,0 +1,24 @@
import { PINNED_GROUP_KEY } from './worktree-list/grouping/group-keys'
/** The row shape both drag models share: only `item` rows carry a worktree and a section. */
type NaturalWorktreeIdRow = { type: string } & Partial<{
sectionKey: string
worktree: { id: string }
}>
/**
* Ids of worktrees rendered in their own group.
*
* Why: a pinned duplicate of a worktree that also renders in its natural group is not its own drag
* slot. Shared by every drag model so the rule lives in one place, and built with a loop rather
* than `flatMap` — that allocated a throwaway array per row, four times per row-model rebuild.
*/
export function getNaturalWorktreeIds(rows: readonly NaturalWorktreeIdRow[]): Set<string> {
const ids = new Set<string>()
for (const row of rows) {
if (row.type === 'item' && row.sectionKey !== PINNED_GROUP_KEY && row.worktree) {
ids.add(row.worktree.id)
}
}
return ids
}
@@ -1,5 +1,6 @@
import type { WorktreeDragGroup } from './worktree-manual-order'
import { ALL_GROUP_KEY, PINNED_GROUP_KEY } from './worktree-list/grouping/group-keys'
import { getNaturalWorktreeIds } from './natural-worktree-ids'
export type WorktreeDragUnitGroup = WorktreeDragGroup & {
units: { worktreeId: string; worktreeIds: string[] }[]
@@ -19,11 +20,7 @@ export function getWorktreeDragUnitGroups(
): WorktreeDragUnitGroup[] {
const groups: WorktreeDragUnitGroup[] = []
let current: { key: string; units: WorktreeDragUnitGroup['units'] } | null = null
const naturalWorktreeIds = new Set(
rows.flatMap((row) =>
row.type === 'item' && row.sectionKey !== PINNED_GROUP_KEY ? [row.worktree.id] : []
)
)
const naturalWorktreeIds = getNaturalWorktreeIds(rows)
for (const row of rows) {
if (row.type === 'header') {
@@ -1,16 +1,8 @@
import { ALL_GROUP_KEY, PINNED_GROUP_KEY } from '../grouping/group-keys'
import { getNaturalWorktreeIds } from '../../natural-worktree-ids'
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> {
return new Set(
rows.flatMap((row) =>
row.type === 'item' && row.sectionKey !== PINNED_GROUP_KEY ? [row.worktree.id] : []
)
)
}
export function getWorktreeDragGroups(rows: HostSectionRow[]): WorktreeDragGroup[] {
const groups: WorktreeDragGroup[] = []
let current: { key: string; ids: string[] } | null = null
@@ -24,6 +24,7 @@ import {
} from '../../worktree-sidebar-drop-preview'
import { getWorktreeDragGroups, getWorktreeDragIndexes } from './groups'
import type { WorktreeItemRow } from '../listing/renderable-rows'
import { getNaturalWorktreeIds } from '../../natural-worktree-ids'
export type WorktreeStatusDropRequest = {
pointerY: number
@@ -48,15 +49,7 @@ export function useWorktreeDragSession(args: {
const worktreeDragGroups = useMemo(() => getWorktreeDragGroups(rows), [rows])
const worktreeDragUnitGroups = useMemo(() => getWorktreeDragUnitGroups(rows), [rows])
const naturalDragWorktreeIds = useMemo(
() =>
new Set(
rows.flatMap((row) =>
row.type === 'item' && row.sectionKey !== PINNED_GROUP_KEY ? [row.worktree.id] : []
)
),
[rows]
)
const naturalDragWorktreeIds = useMemo(() => getNaturalWorktreeIds(rows), [rows])
const worktreeLineageDragRows = useMemo(
() =>
rows
@@ -51,10 +51,6 @@ export function useVirtualRowMeasurementSync(args: {
const { virtualizer, isCurrentVirtualRowElement } = virtualization
const prCacheLen = useAppStore((s) => countRecordKeysByReference(s.prCache))
const issueCacheLen = useAppStore((s) => countRecordKeysByReference(s.issueCache))
const renderRowKeySignature = useMemo(
() => renderRows.map(getRenderRowKey).join('\n'),
[renderRows]
)
const activeRenderRowKeys = useMemo(() => new Set(renderRows.map(getRenderRowKey)), [renderRows])
const lineageRowRekeys = useMemo(() => buildLineageRowRekeyMap(renderRows), [renderRows])
const totalSize = virtualizer.getTotalSize()
@@ -100,14 +96,7 @@ export function useVirtualRowMeasurementSync(args: {
measureMountedRows()
const frameId = window.requestAnimationFrame(measureMountedRows)
return () => window.cancelAnimationFrame(frameId)
}, [
activeRenderRowKeys,
prCacheLen,
issueCacheLen,
measureMountedRows,
renderRowKeySignature,
virtualizer
])
}, [activeRenderRowKeys, prCacheLen, issueCacheLen, measureMountedRows, virtualizer])
useVirtualizedScrollAnchor({
anchorRef: scrollAnchorRef,