The offline/online switch was janky because two independent systems decided
"online" and never communicated:
- ConnectivityMonitor owned is_server_reachable (drove the UI banner) but
learned reachability only from a standalone /System/Info/Public ping loop
and from auth/login calls.
- HybridRepository served all real data by racing cache-vs-server but never
read or wrote reachability.
So the banner reflected a side-channel poller, not the system the user actually
experienced: a successful ping could read "online" while authenticated data
calls 401'd or timed out, and three different timeout regimes (5s ping / 30s
data / 100ms cache race) flapped against each other.
Unify into a single source of truth:
- Extract a cheap, cloneable ConnectivityReporter that owns all reachability
transitions and event emission.
- OnlineRepository reports the outcome of every server request to the reporter,
classified via RepoError: Ok/Authentication/NotFound/Server => reachable
(the server answered), Network => offline candidate, Database/Offline =>
ignored (not a server signal).
- Time-window debounce (OFFLINE_CONFIRM_WINDOW = 5s): flip offline only after
sustained network failure; recover instantly on the first success.
- Demote the ping loop to an offline-only recovery probe (no online polling;
real traffic is the signal when online).
- Frontend: navigator.onLine is now advisory (triggers a recheck instead of
forcing offline); removed the dead markReachable/markUnreachable store methods.
Docs updated (README, 07-connectivity, 03-data-flow, 02-svelte-frontend) to
describe the new model and fix pre-existing drift (HTTP client is 30s timeout +
5s ping, not the documented 10s/base_url).
Tests: 12 connectivity tests (debounce, instant recovery, RepoError
classification through report_outcome). Full suite: 398 Rust + 384 frontend
passing, svelte-check clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the remaining ~155 untyped invoke() calls across stores, services,
components, and routes with the generated commands.* wrappers from
$lib/api/bindings, so every IPC call is compile-time-checked against the
command signatures.
- Register repository_get_subtitle_url and repository_get_video_download_url
in specta_builder() and the invoke_handler; regenerate bindings.ts.
- Source duplicated wire types (AutoplaySettings, CacheConfig, Session,
ConnectivityStatus, audio/video settings, etc.) from bindings.
- Fix two bugs surfaced by the typed wrappers:
- VideoDownloadButton passed an un-awaited Promise as the stream URL.
- setAutoplaySettings omitted the required userId argument.
- Update unit tests asserting the old invoke(name, args) shape.
- Remove the five param-naming guard tests; the compiler and codegen now
enforce what they checked.
svelte-check: 0 errors. vitest: green. cargo test --lib: green.