fix(connectivity): drive reachability from real repository traffic
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 3m32s
Traceability Validation / Check Requirement Traces (push) Successful in 20s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 18m27s
Build & Release / Run Tests (push) Successful in 3m36s
Build & Release / Build Linux (push) Successful in 15m43s
Build & Release / Build Android (push) Successful in 18m40s
Build & Release / Create Release (push) Failing after 22s
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 3m32s
Traceability Validation / Check Requirement Traces (push) Successful in 20s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 18m27s
Build & Release / Run Tests (push) Successful in 3m36s
Build & Release / Build Linux (push) Successful in 15m43s
Build & Release / Build Android (push) Successful in 18m40s
Build & Release / Create Release (push) Failing after 22s
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>
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
// Connectivity state store for offline support
|
||||
//
|
||||
// Simplified wrapper over Rust connectivity monitor.
|
||||
// The Rust backend handles all polling, reachability checks, and adaptive intervals.
|
||||
// Pure reflection of the Rust ConnectivityMonitor. Reachability is the single
|
||||
// source of truth in Rust, derived from real repository traffic (success/
|
||||
// RepoError), with a time-window debounce before going offline and instant
|
||||
// recovery. This store only listens for `connectivity:changed` events and
|
||||
// mirrors status; it does not decide reachability itself. navigator.onLine is
|
||||
// advisory and triggers a recheck rather than forcing offline.
|
||||
// See docs/architecture/07-connectivity.md.
|
||||
// TRACES: UR-002 | DR-013
|
||||
|
||||
import { writable, derived } from "svelte/store";
|
||||
@@ -61,22 +66,27 @@ function createConnectivityStore() {
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to browser online/offline events and update state
|
||||
// Listen to browser online/offline events. These are ADVISORY only — the
|
||||
// Rust ConnectivityMonitor (fed by real repository traffic) is the source of
|
||||
// truth for server reachability. navigator.onLine can be wrong (e.g. a
|
||||
// LAN-only server is still reachable while the browser reports "offline"),
|
||||
// so we use these events to trigger an immediate recheck rather than forcing
|
||||
// the offline state. See docs/architecture/07-connectivity.md.
|
||||
window.addEventListener("online", () => {
|
||||
update((s) => ({ ...s, isOnline: true }));
|
||||
// Device regained network — ask the backend to re-verify the server now.
|
||||
checkServerReachable().catch((err) => {
|
||||
console.debug("[ConnectivityStore] Recheck after 'online' failed:", err);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("offline", () => {
|
||||
update((s) => ({
|
||||
...s,
|
||||
isOnline: false,
|
||||
isServerReachable: false,
|
||||
connectionError: "Device is offline",
|
||||
}));
|
||||
|
||||
if (eventHandlers.onConnectivityChange) {
|
||||
eventHandlers.onConnectivityChange(false);
|
||||
}
|
||||
// Reflect the browser's view of the device link, but let the backend
|
||||
// decide whether the server is actually reachable.
|
||||
update((s) => ({ ...s, isOnline: false }));
|
||||
checkServerReachable().catch((err) => {
|
||||
console.debug("[ConnectivityStore] Recheck after 'offline' failed:", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -199,43 +209,11 @@ function createConnectivityStore() {
|
||||
return checkServerReachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark server as reachable (e.g., after successful API call)
|
||||
*/
|
||||
async function markReachable(): Promise<void> {
|
||||
try {
|
||||
await commands.connectivityMarkReachable();
|
||||
|
||||
// Update local state
|
||||
update((s) => ({
|
||||
...s,
|
||||
isServerReachable: true,
|
||||
lastChecked: new Date(),
|
||||
connectionError: null,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("[ConnectivityStore] Failed to mark reachable:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark server as unreachable (e.g., after failed API call)
|
||||
*/
|
||||
async function markUnreachable(error?: string): Promise<void> {
|
||||
try {
|
||||
await commands.connectivityMarkUnreachable(error ?? null);
|
||||
|
||||
// Update local state
|
||||
update((s) => ({
|
||||
...s,
|
||||
isServerReachable: false,
|
||||
lastChecked: new Date(),
|
||||
connectionError: error || "Server unreachable",
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error("[ConnectivityStore] Failed to mark unreachable:", err);
|
||||
}
|
||||
}
|
||||
// Reachability is now driven by the Rust backend from real repository
|
||||
// traffic (see docs/architecture/07-connectivity.md). The frontend no longer
|
||||
// mutates reachability itself, so the former markReachable/markUnreachable
|
||||
// helpers have been removed. The underlying Rust commands remain available
|
||||
// for deliberate signals if ever needed.
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
@@ -244,8 +222,6 @@ function createConnectivityStore() {
|
||||
setServerUrl,
|
||||
forceCheck,
|
||||
checkServerReachable,
|
||||
markReachable,
|
||||
markUnreachable,
|
||||
isNetworkError,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user