From f8cd34533db037d69a8f845a2aa0927e2be4263a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Brandst=C3=A4tter?= Date: Wed, 21 Oct 2026 09:27:46 +0200 Subject: [PATCH] fix(client): report telemetry ping on background sync and synchronize syncing status in UI --- client-tauri/src-tauri/src/lib.rs | 5 +++ client-tauri/src/Settings.tsx | 14 +++++++-- .../.openspec.yaml | 2 ++ .../design.md | 31 +++++++++++++++++++ .../proposal.md | 22 +++++++++++++ .../specs/desktop-client/spec.md | 23 ++++++++++++++ .../tasks.md | 9 ++++++ 7 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 openspec/changes/fix-client-background-sync-ping-and-status-sync/.openspec.yaml create mode 100644 openspec/changes/fix-client-background-sync-ping-and-status-sync/design.md create mode 100644 openspec/changes/fix-client-background-sync-ping-and-status-sync/proposal.md create mode 100644 openspec/changes/fix-client-background-sync-ping-and-status-sync/specs/desktop-client/spec.md create mode 100644 openspec/changes/fix-client-background-sync-ping-and-status-sync/tasks.md diff --git a/client-tauri/src-tauri/src/lib.rs b/client-tauri/src-tauri/src/lib.rs index ab8cc7c..3234c1e 100644 --- a/client-tauri/src-tauri/src/lib.rs +++ b/client-tauri/src-tauri/src/lib.rs @@ -710,6 +710,8 @@ fn spawn_background_sync_loop(app: tauri::AppHandle) { client.set_refresh_token(Some(refresh_token)); } let result = sync::run_sync(&mut client, &root); + let sync_ok = result.is_ok(); + let last_sync_at = chrono::Utc::now().to_rfc3339(); match &result { Ok((bytes_downloaded, bytes_uploaded, warning)) => { if let Some(msg) = warning { @@ -731,6 +733,9 @@ fn spawn_background_sync_loop(app: tauri::AppHandle) { update_tray_status(&app_handle, "error", Some(e)); } } + if let Err(e) = client.client_ping(Some(sync_ok), Some(last_sync_at)) { + log::warn!("client_ping failed: {}", e); + } let _ = app_handle.emit("sync-status", sync::get_sync_status_payload()); } } diff --git a/client-tauri/src/Settings.tsx b/client-tauri/src/Settings.tsx index eee5aa7..1b7b4a2 100644 --- a/client-tauri/src/Settings.tsx +++ b/client-tauri/src/Settings.tsx @@ -127,8 +127,12 @@ export default function Settings({ email, onLogout }: SettingsProps) { useEffect(() => { invoke<{ status: string; message?: string | null }>("get_sync_status") .then((s) => { - if (s.status === "error" && s.message) setSyncError(s.message); - if (s.status === "warning" && s.message) setSyncWarning(s.message); + if (s.status === "syncing") { + setSyncing(true); + } else { + if (s.status === "error" && s.message) setSyncError(s.message); + if (s.status === "warning" && s.message) setSyncWarning(s.message); + } }) .catch(() => {}); }, []); @@ -140,7 +144,11 @@ export default function Settings({ email, onLogout }: SettingsProps) { useEffect(() => { const unlistenPromise = listen<{ status: string; message?: string | null }>("sync-status", (event) => { const { status, message } = event.payload; - if (status === "synced" || status === "error" || status === "warning") { + if (status === "syncing") { + setSyncing(true); + setSyncError(null); + setSyncWarning(null); + } else if (status === "synced" || status === "error" || status === "warning") { setSyncing(false); setSyncProgress(null); if (status === "error" && message) setSyncError(message); diff --git a/openspec/changes/fix-client-background-sync-ping-and-status-sync/.openspec.yaml b/openspec/changes/fix-client-background-sync-ping-and-status-sync/.openspec.yaml new file mode 100644 index 0000000..dc36fb1 --- /dev/null +++ b/openspec/changes/fix-client-background-sync-ping-and-status-sync/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-21 diff --git a/openspec/changes/fix-client-background-sync-ping-and-status-sync/design.md b/openspec/changes/fix-client-background-sync-ping-and-status-sync/design.md new file mode 100644 index 0000000..d6a1439 --- /dev/null +++ b/openspec/changes/fix-client-background-sync-ping-and-status-sync/design.md @@ -0,0 +1,31 @@ +## Context + +See proposal.md for motivation and problem background. + +Currently, `run_sync` in `client-tauri/src-tauri/src/lib.rs` executes `client.client_ping(Some(sync_ok), Some(last_sync_at))` after running sync, but `spawn_background_sync_loop` in the same file omits this call entirely. Additionally, `Settings.tsx` only handles terminal states (`"synced"`, `"error"`, `"warning"`) in its `sync-status` listener and initial mount fetch, ignoring `"syncing"` events. + +## Goals / Non-Goals + +**Goals:** +- Ensure the background sync loop reports client version and outcome via `client.client_ping(...)` matching `run_sync`. +- Ensure `Settings.tsx` transitions to `syncing: true` whenever `"syncing"` status is received or queried, clearing previous error/warning banners and engaging progress polling. + +**Non-Goals:** +- Modify backend telemetry models or ping endpoints. +- Alter sync concurrency locking or interval scheduling. + +## Decisions + +### 1. Call `client_ping` with best-effort error handling in `spawn_background_sync_loop` +- **Choice**: Call `if let Err(e) = client.client_ping(Some(sync_ok), Some(last_sync_at)) { log::warn!("client_ping failed: {}", e); }` before emitting the final `sync-status` payload. +- **Rationale**: Matches the implementation in `run_sync` exactly and prevents any ping failure from affecting local sync state or disrupting the background sync thread. +- **Alternatives considered**: Spawning a separate detached thread for `client_ping`. Unnecessary because `client_ping` is a fast HTTP call with standard timeout, and `spawn_background_sync_loop` already runs in its own background worker thread. + +### 2. Handle `"syncing"` status in `Settings.tsx` +- **Choice**: In `Settings.tsx`, check `if (status === "syncing") { setSyncing(true); setSyncError(null); setSyncWarning(null); }` in both the `sync-status` event listener and the initial `get_sync_status` call. +- **Rationale**: Keeps the UI responsive to background sync starts and ensures that opening the Settings window while sync is running accurately reflects the ongoing operation. + +## Risks / Trade-offs + +- [Telemetry Ping Failure] → Network errors during ping are caught and logged at `log::warn` without failing sync or breaking the daemon loop. +- [UI State Thrashing] → State updates only fire when events are received or on initial window mount, matching existing event handling patterns. diff --git a/openspec/changes/fix-client-background-sync-ping-and-status-sync/proposal.md b/openspec/changes/fix-client-background-sync-ping-and-status-sync/proposal.md new file mode 100644 index 0000000..99278f0 --- /dev/null +++ b/openspec/changes/fix-client-background-sync-ping-and-status-sync/proposal.md @@ -0,0 +1,22 @@ +## Why + +The desktop client background sync loop (`spawn_background_sync_loop`) runs periodic synchronization in the background but omits reporting telemetry ping to `POST /api/clients/ping` upon completion. Consequently, the server's operator diagnostics (`GET /api/admin/clients`) remain stale during normal unattended operation. In addition, when background synchronization runs or when the user opens the Settings window while synchronization is active, the desktop Settings UI ignores the `"syncing"` status event and query state, causing the UI to show an idle state and allowing users to trigger concurrent manual sync attempts that fail with a confusing warning. + +## What Changes + +- Update `spawn_background_sync_loop` in `client-tauri/src-tauri/src/lib.rs` to invoke `client.client_ping(Some(sync_ok), Some(last_sync_at))` upon completion of each background sync pass, aligning background sync with manual `run_sync` telemetry reporting. +- Update `client-tauri/src/Settings.tsx` to handle the `"syncing"` status in both the `sync-status` event listener and the initial `get_sync_status` call on mount. When syncing is active, `syncing` is set to `true`, clearing previous errors or warnings, disabling duplicate sync triggers, and activating progress polling. + +## Capabilities + +### New Capabilities + + +### Modified Capabilities +- `desktop-client`: Background sync telemetry reporting to server and UI synchronization state reflecting active sync operations. + +## Impact + +- `client-tauri/src-tauri/src/lib.rs`: Background sync loop records telemetry via `client_ping`. +- `client-tauri/src/Settings.tsx`: Settings window correctly tracks syncing state and progress across background and manual runs. +- Server telemetry / operator diagnostics: Correctly reflects client activity for unattended desktop clients without requiring manual sync triggers. diff --git a/openspec/changes/fix-client-background-sync-ping-and-status-sync/specs/desktop-client/spec.md b/openspec/changes/fix-client-background-sync-ping-and-status-sync/specs/desktop-client/spec.md new file mode 100644 index 0000000..3c2fd2b --- /dev/null +++ b/openspec/changes/fix-client-background-sync-ping-and-status-sync/specs/desktop-client/spec.md @@ -0,0 +1,23 @@ +## ADDED Requirements + +### Requirement: Background Sync Telemetry Reporting +The desktop client SHALL report client version and synchronization status via the client ping endpoint after completing each background synchronization cycle. + +#### Scenario: Background sync completes successfully +- **WHEN** the background synchronization loop completes a sync pass without errors +- **THEN** the client sends a telemetry ping to `/api/clients/ping` with `last_sync_ok: true` and the ISO timestamp of the sync completion + +#### Scenario: Background sync encounters an error +- **WHEN** the background synchronization loop encounters an error during a sync pass +- **THEN** the client sends a telemetry ping to `/api/clients/ping` with `last_sync_ok: false` and the ISO timestamp before or alongside updating local error status + +### Requirement: User Interface Active Synchronization State +The desktop client settings user interface SHALL synchronize its active state with background synchronization events and current execution status. + +#### Scenario: Background synchronization begins +- **WHEN** the synchronization engine emits a `sync-status` event with `{"status": "syncing"}` +- **THEN** the settings user interface transitions to the syncing state, disables redundant manual sync triggers, and begins polling progress + +#### Scenario: Settings window opened during active synchronization +- **WHEN** the user opens the settings window while synchronization is in progress +- **THEN** the settings window detects the active status and transitions immediately to the syncing state with progress polling active diff --git a/openspec/changes/fix-client-background-sync-ping-and-status-sync/tasks.md b/openspec/changes/fix-client-background-sync-ping-and-status-sync/tasks.md new file mode 100644 index 0000000..60d2103 --- /dev/null +++ b/openspec/changes/fix-client-background-sync-ping-and-status-sync/tasks.md @@ -0,0 +1,9 @@ +## 1. Background Sync Telemetry Reporting + +- [x] 1.1 In `client-tauri/src-tauri/src/lib.rs`, capture sync outcome and invoke `client.client_ping(Some(sync_ok), Some(last_sync_at))` in `spawn_background_sync_loop`, verifying compilation with `cargo check` +- [x] 1.2 Run `cargo test` in `client-tauri/src-tauri` and verify all tests pass + +## 2. Desktop Settings UI Active Sync Synchronization + +- [x] 2.1 In `client-tauri/src/Settings.tsx`, handle `status === "syncing"` in the `sync-status` event listener and in initial `get_sync_status` invoke to set `syncing: true` and clear previous error and warning banners +- [x] 2.2 Run `npm run build` in `client-tauri` and verify build succeeds without TypeScript or Vite errors