- A transport exception in the poll loop killed the thread silently, leaving the
GUI on a frozen 'Connected' dashboard and blocking run_oneoff callers for the
full timeout. _loop now catches it -> stops, fails pending one-offs with the
real error, and calls an on_error callback. Controller wires on_error to flag
the connection dead; the GUI detects it in _tick and tears down with a
'Connection lost' dialog.
- A run_oneoff that timed out left its job queued, so it executed LATER on the
shared link -- a ghost/duplicate vehicle command. Jobs now carry
cancelled/started flags under a lock; on timeout a not-yet-started job is
cancelled (skipped by _drain_oneoffs), and a started one reports 'still
running -- do NOT retry'. stop() also frees stranded submitters.
- tests/test_scheduler.py: cancel-on-timeout, freed-on-death, loop-survives.
Closes#8
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yT89n4zR4qbrySoSiEyZs