Two engine tests leaned on elapsed time. "It keeps stamping while the
window is in front" slept 300 ms and wanted more than three stamps: a
worker held up on a loaded machine stamps fewer. "Nothing watched,
nothing stamped" slept 60 ms after sending the empty set and then wanted
no stamp at all, but a worker already past its wait with the old set
stamps that round once more, whenever it gets to run.
The first now waits (at most 10 s) until the stamps arrive. The second
checks the invariant instead: after `set_watched(∅)` at most one round of
the old set (one file) can still come, so the count may grow by one and
no more; a worker that ignored the empty set stamps every 20 ms and still
fails it.
Checked with the worker slowed (100 ms after each wait, 50 ms between the
report and the wake): these tests pass; the previous ones fail at
"it keeps stamping while the window is in front".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings ▸ Import ▸ Reload Footage Changed on Disk used to spawn a stat thread every two seconds
from the UI thread, request a repaint 100 ms later to collect it, and request another repaint every
2 s in between, for every project with items, focused or not: the whole interface redrew on a timer
for footage that hadn't changed.
`footage_reload::Watcher` is one long-lived worker: the interface tells it what to watch (recomputed
when the project revision or the preference changes) and whether the window is in front; it stamps
the files every 2 s while the window is in front, every 10 s while another app has the front, at once
on focus regain and on a new set, and hands the stamps over — calling `ctx.request_repaint()` — only
when one differs from its last look. Nothing watched means the thread sleeps. `apply_stamps` and the
reload semantics are unchanged; `start_scan`/`poll_scan`/`Session::footage_scan` are gone. No new
dependencies: the stat loop is also what footage on a network share relies on, where FSEvents would
not report another machine's writes.
Tests: the watcher wakes the interface for the first stamps and for each change and never for an
unchanged file; behind another app it rests until the window is back; nothing watched is nothing
stamped and a new set is reported whole (and dropping it ends the thread); the host test
`the_watcher_notices_a_footage_file_changed_on_disk` saves a PSD from "another app" and sees it
reloaded once.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`Readbacks::new` spawned a thread that slept 100 ms and checked for expired readbacks forever,
whether or not anything was pending: ten wakeups a second for the life of every desktop session
with a GPU compositor (12–13 idle wakeups/s measured, against 0.4–1.8 for the sibling apps).
The worker now waits on a condition: indefinitely while nothing is pending, otherwise until the
earliest deadline or until what is pending changes (start, complete, cancel, retire poke it).
It owns the state behind an `Arc<Inner>` rather than the `Readbacks`, so dropping the context
closes it instead of leaking the thread. Timeout, exactly-once callbacks, panic isolation and the
browser's timer path are unchanged.
Measured on an M2 Max (release build, window on screen, 60 s samples, A/B interleaved): idle
wakeups 12.15/s → 2.63/s not frontmost, 12.36/s → 2.72/s frontmost.
Tests: an idle worker does not wake; a due deadline wakes it when due; dropping the context stops
it; the existing panic/strand test moves deadlines through a hook that notifies the worker.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
#2410's dependency walk for a soloed 3D model was written before
reads_other_times took the effect count; it asks about all effects (None),
as the other walks do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #2604 a picked numbered still imports as itself unless the import
asks for a sequence, as in After Effects. The compositing and tracking QA
scenarios import a PNG run, so they pass `sequence: true`; docs/agents.md
describes the new default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>