Files
orca/src/shared/filesystem-watch-batch-window.test.ts
T
Neil 5f126af9f8 fix(watcher): flush file changes on a steady cadence instead of waiting for quiet (#30270)
* perf(watcher): flush file changes on a steady cadence instead of waiting for quiet

Watcher batches used a 150 ms trailing debounce capped at 500 ms, so a worktree
with agents writing continuously always waited the full cap. The git status
nudge had no cap and could starve until the 60 s safety poll. Batches now flush
25 ms after the first change following a quiet period, then at most every 100 ms;
new events join an armed flush instead of pushing it back.

* fix(watcher): keep atomic saves in one batch and survive clock jumps

Raise the first-flush delay to 75 ms so the native watcher's split burst lands
in one batch, cap the flush delay at one interval so a backwards clock jump
can't stall delivery, and widen the editor delete debounce to cover a split
across one flush interval.

* test: wait past the widened editor delete debounce

* fix(watcher): keep markdown list refreshing under sustained writes and widen delete grace

Markdown document refresh used a resetting 125 ms debounce that never fired
once watch batches arrive every 100 ms. Delete tombstones now wait two batch
intervals so slow stat calls (WSL, AV scans) can't split a save into a flash.

* fix(watcher): bound watcher-driven reads and scans under sustained writes

One watcher-triggered editor read and one markdown scan per target in flight,
with a single follow-up; markdown scans honour the latest change time; the
headless Windows watcher no longer resets its refresh timer on every event.

* fix(watcher): make the faster remote cadence opt-in and harden watcher-driven reloads

files.watch takes an optional batchCadence; hosts keep legacy trailing batching
unless the client opts in. Coalesced editor follow-ups re-check the tab and
replace a hung read only once; Markdown mounts skip pre-change scans; the
Windows fallback refreshes the whole tree at most every 500 ms.

* fix(watcher): stop restarting the remaining watch-driven timers

Combined-diff section reloads and per-file editor reload dispatch no longer
push an armed timer back; a queued editor follow-up for a tab that left the
screen marks it stale instead of dropping the change.

* fix(watcher): let combined-diff and worktree notifications land under sustained writes

A combined-diff section discards an invalidated in-flight load at most once in a
row; worktree base-directory notifications no longer restart their timer.

* fix(watcher): never overwrite an edited diff section and unstick hung reloads

A combined-diff load never commits over a section with unsaved edits; a reload
queued behind a hung read starts after the stale limit without needing another
change event.

* fix(watcher): recover from hung markdown scans and keep evidence for edited tabs

A Markdown scan that never settles is abandoned after the join window so later
changes scan again; a queued editor follow-up for a tab edited meanwhile marks
it changed-on-disk and stale instead of dropping the change.

* chore(watcher): use a numeric markdown scan id

* fix(watcher): don't let later events join a stale echo-verification read

Payloads from the same burst still share the in-flight read; a payload from a
later task gets exactly one fresh read issued after the current one settles.
2026-10-18 23:44:18 -07:00

35 lines
1.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
WATCH_BATCH_INTERVAL_MS,
WATCH_BATCH_LEADING_MS,
watchBatchFlushDelayMs
} from './filesystem-watch-batch-window'
describe('watchBatchFlushDelayMs', () => {
it('uses the leading delay before the first flush', () => {
expect(watchBatchFlushDelayMs(1_000_000, 0)).toBe(WATCH_BATCH_LEADING_MS)
})
it('uses the leading delay once a full interval has passed', () => {
expect(watchBatchFlushDelayMs(1_000 + WATCH_BATCH_INTERVAL_MS, 1_000)).toBe(
WATCH_BATCH_LEADING_MS
)
})
it('waits out the rest of the interval after a recent flush', () => {
expect(watchBatchFlushDelayMs(1_010, 1_000)).toBe(WATCH_BATCH_INTERVAL_MS - 10)
})
it('never returns less than the leading delay', () => {
expect(watchBatchFlushDelayMs(1_090, 1_000)).toBe(WATCH_BATCH_LEADING_MS)
})
it('never waits longer than one interval when the clock moves backwards', () => {
expect(watchBatchFlushDelayMs(1_000, 3_600_000)).toBe(WATCH_BATCH_INTERVAL_MS)
})
it('honours custom windows', () => {
expect(watchBatchFlushDelayMs(1_000, 950, 5, 200)).toBe(150)
})
})