Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions client-tauri/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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());
}
}
Expand Down
14 changes: 11 additions & 3 deletions client-tauri/src/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {});
}, []);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-21
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
<!-- None -->

### 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.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Loading