- IntakeSession.observe_frame now catches ValueError from crop_to_book
(empty-platform capture), cleans up any orphan wide-image file/dir,
resets the strategy, and does not advance session state. The error
is surfaced via a new last_error field.
- /api/observe adds an "error" field to the response when a capture
fires but fails, instead of an unhandled 500.
- observe_frame only reports fired=True (and only runs capture logic)
when state is WAITING_FRONT or WAITING_BACK, so a completed book
left on the platform can no longer produce a no-op "fired" capture
from an auto-strategy re-triggering.
- observe_frame now holds a per-session threading.Lock for its whole
body, since the frontend polls /api/observe every ~500ms without
chaining requests and Catalogue.update_row() does a non-atomic
read-all-then-rewrite-whole-file rewrite of catalogue.csv.
Adds tests for empty-platform front/back captures and the
BOOK_COMPLETE no-op-fire case, plus an API-level empty-platform test.