mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 08:03:09 +00:00
fix(ssh): rebind file watcher when connection id hydrates
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -16,10 +16,29 @@ vi.mock('@/components/editor/editor-autosave', () => ({
|
||||
|
||||
import {
|
||||
createExternalWatchEventHandler,
|
||||
getOverflowExternalReloadTargets
|
||||
getOverflowExternalReloadTargets,
|
||||
getWatchedTargetKey
|
||||
} from './useEditorExternalWatch'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
describe('getWatchedTargetKey', () => {
|
||||
it('changes when a worktree gains an SSH connection id', () => {
|
||||
expect(
|
||||
getWatchedTargetKey({
|
||||
worktreeId: 'wt-1',
|
||||
worktreePath: '/repo',
|
||||
connectionId: undefined
|
||||
})
|
||||
).not.toBe(
|
||||
getWatchedTargetKey({
|
||||
worktreeId: 'wt-1',
|
||||
worktreePath: '/repo',
|
||||
connectionId: 'conn-1'
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getOverflowExternalReloadTargets', () => {
|
||||
const setExternalMutation = vi.fn()
|
||||
|
||||
|
||||
@@ -66,6 +66,13 @@ type ExternalWatchNotification = {
|
||||
relativePath: string
|
||||
}
|
||||
|
||||
export function getWatchedTargetKey(target: WatchedTarget): string {
|
||||
// Why: SSH worktrees can exist in the store before their remote filesystem
|
||||
// provider is ready. Include connectionId so a local/unknown placeholder
|
||||
// watch is replaced by the real SSH watch when the repo metadata hydrates.
|
||||
return `${target.worktreeId}::${target.worktreePath}::${target.connectionId ?? 'local'}`
|
||||
}
|
||||
|
||||
// Why: macOS atomic writes (Claude Code Edit, vim :w, VSCode save) deliver a
|
||||
// delete event immediately followed by a create event for the same path. When
|
||||
// those two land in separate fs:changed payloads a few ms apart, the tab
|
||||
@@ -123,12 +130,13 @@ export function useEditorExternalWatch(): void {
|
||||
if (!wt) {
|
||||
continue
|
||||
}
|
||||
nextTargets.push({
|
||||
const target = {
|
||||
worktreeId: id,
|
||||
worktreePath: wt.path,
|
||||
connectionId: getConnectionId(id) ?? undefined
|
||||
})
|
||||
parts.push(`${id}::${wt.path}`)
|
||||
}
|
||||
nextTargets.push(target)
|
||||
parts.push(getWatchedTargetKey(target))
|
||||
}
|
||||
return { targets: nextTargets, targetsKey: parts.join('|') }
|
||||
}, [openFiles, worktreesByRepo, activeWorktreeId])
|
||||
@@ -144,10 +152,10 @@ export function useEditorExternalWatch(): void {
|
||||
useEffect(() => {
|
||||
const nextTargets = latestTargetsRef.current
|
||||
const prev = targetsRef.current
|
||||
const prevIds = new Set(prev.map((t) => t.worktreeId))
|
||||
const nextIds = new Set(nextTargets.map((t) => t.worktreeId))
|
||||
const removed = prev.filter((t) => !nextIds.has(t.worktreeId))
|
||||
const added = nextTargets.filter((t) => !prevIds.has(t.worktreeId))
|
||||
const prevKeys = new Set(prev.map(getWatchedTargetKey))
|
||||
const nextKeys = new Set(nextTargets.map(getWatchedTargetKey))
|
||||
const removed = prev.filter((t) => !nextKeys.has(getWatchedTargetKey(t)))
|
||||
const added = nextTargets.filter((t) => !prevKeys.has(getWatchedTargetKey(t)))
|
||||
|
||||
for (const target of removed) {
|
||||
void window.api.fs.unwatchWorktree({
|
||||
|
||||
Reference in New Issue
Block a user