mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
perf(sidebar): stable per-group onLineageToggle identity so lineage-parent WorktreeCards memo-bail
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -24,6 +24,7 @@ import {
|
||||
Trash2
|
||||
} from 'lucide-react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { createLineageToggleHandlerCache } from './worktree-lineage-toggle-handler-cache'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import type { AppState } from '@/store/types'
|
||||
import {
|
||||
@@ -2449,6 +2450,12 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
||||
},
|
||||
[recordCurrentScrollAnchor, toggleGroup]
|
||||
)
|
||||
// Why: memo'd WorktreeCard needs a per-group-key stable onLineageToggle
|
||||
// identity to bail out of re-renders; see worktree-lineage-toggle-handler-cache.
|
||||
const getLineageToggleHandler = useMemo(
|
||||
() => createLineageToggleHandlerCache(toggleGroupWithScrollAnchor),
|
||||
[toggleGroupWithScrollAnchor]
|
||||
)
|
||||
|
||||
const navigateWorktree = useCallback(
|
||||
(direction: 'up' | 'down') => {
|
||||
@@ -4856,11 +4863,7 @@ const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktreeViewp
|
||||
lineageChildrenStyle={lineageChildrenStyle}
|
||||
onLineageToggle={
|
||||
lineageToggleGroupKey
|
||||
? (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
toggleGroupWithScrollAnchor(lineageToggleGroupKey)
|
||||
}
|
||||
? getLineageToggleHandler(lineageToggleGroupKey)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type React from 'react'
|
||||
import { createLineageToggleHandlerCache } from './worktree-lineage-toggle-handler-cache'
|
||||
|
||||
const makeEvent = (): React.MouseEvent<HTMLButtonElement> =>
|
||||
({
|
||||
preventDefault: vi.fn(),
|
||||
stopPropagation: vi.fn()
|
||||
}) as unknown as React.MouseEvent<HTMLButtonElement>
|
||||
|
||||
describe('createLineageToggleHandlerCache', () => {
|
||||
it('returns a referentially stable handler for the same group key across calls', () => {
|
||||
const getHandler = createLineageToggleHandlerCache(vi.fn())
|
||||
|
||||
const first = getHandler('lineage:alpha')
|
||||
const second = getHandler('lineage:alpha')
|
||||
|
||||
// Why: stable identity is what lets React.memo'd WorktreeCard bail out.
|
||||
expect(second).toBe(first)
|
||||
})
|
||||
|
||||
it('returns distinct handlers for distinct group keys', () => {
|
||||
const getHandler = createLineageToggleHandlerCache(vi.fn())
|
||||
|
||||
expect(getHandler('lineage:alpha')).not.toBe(getHandler('lineage:beta'))
|
||||
})
|
||||
|
||||
it('prevents default, stops propagation, and toggles the bound group key', () => {
|
||||
const toggleGroup = vi.fn()
|
||||
const getHandler = createLineageToggleHandlerCache(toggleGroup)
|
||||
const event = makeEvent()
|
||||
|
||||
getHandler('lineage:alpha')(event)
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalledTimes(1)
|
||||
expect(event.stopPropagation).toHaveBeenCalledTimes(1)
|
||||
expect(toggleGroup).toHaveBeenCalledExactlyOnceWith('lineage:alpha')
|
||||
})
|
||||
|
||||
it('keeps each cached handler bound to its own group key', () => {
|
||||
const toggleGroup = vi.fn()
|
||||
const getHandler = createLineageToggleHandlerCache(toggleGroup)
|
||||
|
||||
getHandler('lineage:beta')(makeEvent())
|
||||
|
||||
expect(toggleGroup).toHaveBeenCalledExactlyOnceWith('lineage:beta')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import type React from 'react'
|
||||
|
||||
export type LineageToggleHandler = (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||
|
||||
// Why: WorktreeCard is React.memo'd; an inline arrow per row would give
|
||||
// onLineageToggle a fresh identity every render and defeat the memo bail-out
|
||||
// for every lineage-parent card on each sort/status epoch bump. Cache one
|
||||
// handler per group key (bounded by lineage-group count) so identity is stable.
|
||||
export const createLineageToggleHandlerCache = (
|
||||
toggleGroup: (groupKey: string) => void
|
||||
): ((groupKey: string) => LineageToggleHandler) => {
|
||||
const handlersByGroupKey = new Map<string, LineageToggleHandler>()
|
||||
return (groupKey: string) => {
|
||||
const cached = handlersByGroupKey.get(groupKey)
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
const handler: LineageToggleHandler = (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
toggleGroup(groupKey)
|
||||
}
|
||||
handlersByGroupKey.set(groupKey, handler)
|
||||
return handler
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user