mirror of
https://github.com/stablyai/orca.git
synced 2026-10-15 08:03:08 +00:00
Reconcile source control selection before commit
This commit is contained in:
@@ -3,6 +3,7 @@ import type { GitStatusEntry } from '../../../../shared/types'
|
||||
import {
|
||||
getSelectionRangeKeys,
|
||||
reconcileSelectionKeys,
|
||||
reconcileSourceControlSelectionState,
|
||||
type FlatEntry
|
||||
} from './useSourceControlSelection'
|
||||
|
||||
@@ -36,6 +37,40 @@ describe('reconcileSelectionKeys', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('reconcileSourceControlSelectionState', () => {
|
||||
it('keeps selected keys and anchor identity when all keys are still visible', () => {
|
||||
const flatEntries = [
|
||||
makeEntry('unstaged::a.ts', 'unstaged', 'a.ts'),
|
||||
makeEntry('staged::b.ts', 'staged', 'b.ts')
|
||||
]
|
||||
const selectedKeys = new Set(['unstaged::a.ts'])
|
||||
|
||||
const result = reconcileSourceControlSelectionState({
|
||||
selectedKeys,
|
||||
anchorKey: 'unstaged::a.ts',
|
||||
flatEntries
|
||||
})
|
||||
|
||||
expect(result.selectedKeys).toBe(selectedKeys)
|
||||
expect(result.anchorKey).toBe('unstaged::a.ts')
|
||||
})
|
||||
|
||||
it('prunes stale selected keys and clears stale range anchors', () => {
|
||||
const flatEntries = [makeEntry('staged::b.ts', 'staged', 'b.ts')]
|
||||
|
||||
expect(
|
||||
reconcileSourceControlSelectionState({
|
||||
selectedKeys: new Set(['unstaged::a.ts', 'staged::b.ts']),
|
||||
anchorKey: 'unstaged::a.ts',
|
||||
flatEntries
|
||||
})
|
||||
).toEqual({
|
||||
selectedKeys: new Set(['staged::b.ts']),
|
||||
anchorKey: null
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSelectionRangeKeys', () => {
|
||||
it('selects an inclusive range across visible sections', () => {
|
||||
const flatEntries = [
|
||||
|
||||
@@ -28,6 +28,30 @@ export function reconcileSelectionKeys(
|
||||
return nextSelected
|
||||
}
|
||||
|
||||
export function reconcileSourceControlSelectionState(args: {
|
||||
selectedKeys: ReadonlySet<string>
|
||||
anchorKey: string | null
|
||||
flatEntries: FlatEntry[]
|
||||
}): { selectedKeys: ReadonlySet<string>; anchorKey: string | null } {
|
||||
const { anchorKey, flatEntries, selectedKeys } = args
|
||||
const validKeys = new Set(flatEntries.map((e) => e.key))
|
||||
const nextSelected = new Set<string>()
|
||||
let selectedChanged = false
|
||||
|
||||
for (const key of selectedKeys) {
|
||||
if (validKeys.has(key)) {
|
||||
nextSelected.add(key)
|
||||
} else {
|
||||
selectedChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectedKeys: selectedChanged ? nextSelected : selectedKeys,
|
||||
anchorKey: anchorKey && !validKeys.has(anchorKey) ? null : anchorKey
|
||||
}
|
||||
}
|
||||
|
||||
export function getSelectionRangeKeys(
|
||||
flatEntries: FlatEntry[],
|
||||
anchorKey: string | null,
|
||||
@@ -87,20 +111,19 @@ export function useSourceControlSelection({
|
||||
shouldOpenAsSplitRef.current = shouldOpenAsSplit
|
||||
}, [shouldOpenAsSplit])
|
||||
|
||||
// Clear stale selections if entries disappear
|
||||
useEffect(() => {
|
||||
const validKeys = new Set(flatEntries.map((e) => e.key))
|
||||
const nextSelected = reconcileSelectionKeys(selectedKeys, flatEntries)
|
||||
const changed = nextSelected.size !== selectedKeys.size
|
||||
|
||||
if (changed) {
|
||||
setSelectedKeys(nextSelected)
|
||||
}
|
||||
|
||||
if (anchorKey && !validKeys.has(anchorKey)) {
|
||||
setAnchorKey(null)
|
||||
}
|
||||
}, [flatEntries, selectedKeys, anchorKey])
|
||||
const reconciledSelection = reconcileSourceControlSelectionState({
|
||||
selectedKeys,
|
||||
anchorKey,
|
||||
flatEntries
|
||||
})
|
||||
if (reconciledSelection.selectedKeys !== selectedKeys) {
|
||||
// Why: visible source-control rows can disappear after filtering, staging,
|
||||
// or status refresh; prune stale bulk-action keys before children see them.
|
||||
setSelectedKeys(new Set(reconciledSelection.selectedKeys))
|
||||
}
|
||||
if (reconciledSelection.anchorKey !== anchorKey) {
|
||||
setAnchorKey(reconciledSelection.anchorKey)
|
||||
}
|
||||
|
||||
const handleSelect = useCallback((e: React.MouseEvent, key: string, entry: GitStatusEntry) => {
|
||||
if (shouldOpenAsSplitRef.current?.(e)) {
|
||||
|
||||
Reference in New Issue
Block a user