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>
Remove the local copy of jay_diff and its tests/data; switch all four
import sites to `jaydiff.diff.diff_full` / `jaydiff.merge.merge_full`
from the shared Gitea package (http://192.168.22.90:3001/jayfield/jaypy).
Add the package as a VCS dependency in requirements.txt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server (network.py): send every record in diff mode starting from
prev={}, including the first — fixes history drop on new client connect.
Collect (collect.py): remove payload["_diff"] = True from live broadcast.
GUI client (gui_client.py): always call jay_merge_full in diff mode;
drop the _diff key check that gated reconstruction.
CLI client (client.py): fix `args.diff or True` guard that made --diff
irrelevant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Server (collect.py --diff):
- History burst: first record sent full, subsequent as jay_diff_full diffs
- Live broadcast: each snapshot diffed against the previous one
- Diff payloads tagged with "_diff": true
Client (client.py --diff):
- Reconstructs full state via jay_merge_full before printing
GUI (Connector tab "Diff mode" checkbox):
- TcpReader.connect_to() accepts diff_mode; maintains _state dict
- Merges incoming diffs before emitting to Dashboard/Plot
- Checkbox state persisted to plot_settings.json
Both sides default to off; must be enabled consistently on server and client.
utils/__init__.py added so utils.jay_diff is importable as a package.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously load_today() was only called if in-memory records were empty,
which was never true once the first WeConnect poll had completed.
Now disk records are loaded unconditionally at startup and merged with
live in-memory records (dedup by ts) when a client connects.
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>