If extract_all() returns None (Dashboard init failure), appending it to
store["records"] would cause _accept_loop to crash with AttributeError on
the next client connection. Since only OSError was caught, the thread
died permanently — no further clients could join push_clients, so live
broadcasts reached nobody.
- collect.py: skip None snapshots instead of appending them
- network.py: filter None records in _accept_loop; catch all exceptions
during history send (not just OSError) so the thread survives
- storage_helpers.py: filter None records when loading from disk
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace load_today_records with load_last_24h_records which computes a
rolling 24 h cutoff, globs log files for both the current and previous
calendar day, and filters records by timestamp — so clients always
receive a full 24 h window regardless of when midnight falls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
storage_helpers: load_today_records() scans logs/ for all files
matching today's UTC date and VIN, merges and sorts their records.
network: _accept_loop receives an optional load_today callable;
if store_ref["current"] has no records it calls load_today() to
replay the day's history from disk before adding the client.
collect: passes a load_today lambda (None when -o is used).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
data_model, log_config, network, storage_helpers, we_connect are now
under server/. collect.py stays in the project root and imports via
the server package.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>