From f5295b5980694b7bc512cbd4efda558807bd8fe7 Mon Sep 17 00:00:00 2001 From: Tristan Michael Date: Mon, 6 Apr 2026 09:10:36 -0700 Subject: [PATCH 1/2] fix: reset tasks and remount screen on workspace change Reset the tasks array when switching workspaces to prevent stale task data from persisting. Wrap TasksScreen in a keyed block to force remounting when the current workspace changes, ensuring a clean state for each workspace. Configure line endings to use LF across all files for consistency. --- .gitattributes | 1 + apps/tauri/src/App.svelte | 4 +++- apps/tauri/src/lib/screens/TasksScreen.svelte | 1 + apps/tauri/src/lib/stores/app.svelte.ts | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/apps/tauri/src/App.svelte b/apps/tauri/src/App.svelte index 6ce797f..5e29705 100644 --- a/apps/tauri/src/App.svelte +++ b/apps/tauri/src/App.svelte @@ -60,7 +60,9 @@ {:else if app.screen === "setup"} {:else} - + {#key app.config?.current_workspace} + + {/key} {/if} diff --git a/apps/tauri/src/lib/screens/TasksScreen.svelte b/apps/tauri/src/lib/screens/TasksScreen.svelte index bc84859..f05d2b1 100644 --- a/apps/tauri/src/lib/screens/TasksScreen.svelte +++ b/apps/tauri/src/lib/screens/TasksScreen.svelte @@ -25,6 +25,7 @@ } }); + function openTask(task: Task) { taskStack = [task.id]; } diff --git a/apps/tauri/src/lib/stores/app.svelte.ts b/apps/tauri/src/lib/stores/app.svelte.ts index 0bcdc81..c3859cb 100644 --- a/apps/tauri/src/lib/stores/app.svelte.ts +++ b/apps/tauri/src/lib/stores/app.svelte.ts @@ -133,6 +133,7 @@ async function switchWorkspace(id: string) { await invoke("set_current_workspace", { id }); config = await invoke("get_config"); activeListId = null; + tasks = []; await loadLists(); const ws = config?.workspaces[id]; if (ws) invoke("watch_workspace", { path: ws.path }).catch((e) => console.warn("File watcher failed:", e)); From 50800f0c2d093020e378d151104fecf9223a8e12 Mon Sep 17 00:00:00 2001 From: Tristan Michael Date: Mon, 6 Apr 2026 09:37:06 -0700 Subject: [PATCH 2/2] feat: add separate sync interval for unfocused state Introduce a new sync interval configuration for when the app is in the background, allowing users to set a longer sync period to reduce resource usage. Replace the fixed focus threshold with dynamic interval switching based on app focus state. Add syncIntervalUnfocused setting to the UI with a new background sync interval selector. Refactor sync interval management into a restartSyncInterval function to handle both focused and unfocused intervals consistently. --- apps/tauri/src-tauri/src/lib.rs | 16 ++++++ .../src/lib/screens/SettingsScreen.svelte | 20 +++++++- apps/tauri/src/lib/stores/app.svelte.ts | 51 +++++++++++++++---- apps/tauri/src/lib/types.ts | 1 + crates/onyx-core/src/config.rs | 4 +- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/apps/tauri/src-tauri/src/lib.rs b/apps/tauri/src-tauri/src/lib.rs index 314a8d6..aa0922f 100644 --- a/apps/tauri/src-tauri/src/lib.rs +++ b/apps/tauri/src-tauri/src/lib.rs @@ -556,6 +556,19 @@ fn set_sync_interval( s.save_config() } +#[tauri::command] +fn set_sync_interval_unfocused( + workspace_id: String, + interval_secs: Option, + state: State<'_, Mutex>, +) -> Result<(), String> { + let mut s = lock_state(&state)?; + if let Some(ws) = s.config.workspaces.get_mut(&workspace_id) { + ws.sync_interval_unfocused_secs = interval_secs; + } + s.save_config() +} + /// A remote folder entry returned to the frontend. #[derive(Debug, Serialize, Deserialize)] struct RemoteFolderEntry { @@ -792,6 +805,8 @@ async fn sync_workspace( { let mut s = lock_state(&state)?; + // Suppress file watcher events from sync-written files (500ms debounce + margin) + mute_watcher(&mut s); if let Some(ws) = s.config.workspaces.get_mut(&workspace_id) { ws.last_sync = Some(Utc::now()); } @@ -914,6 +929,7 @@ pub fn run() { set_webdav_config, set_workspace_theme, set_sync_interval, + set_sync_interval_unfocused, add_webdav_workspace, list_remote_folder, inspect_remote_workspace, diff --git a/apps/tauri/src/lib/screens/SettingsScreen.svelte b/apps/tauri/src/lib/screens/SettingsScreen.svelte index a56cc53..f35bc72 100644 --- a/apps/tauri/src/lib/screens/SettingsScreen.svelte +++ b/apps/tauri/src/lib/screens/SettingsScreen.svelte @@ -205,7 +205,7 @@
- +
+ +
+ + +
{/if} diff --git a/apps/tauri/src/lib/stores/app.svelte.ts b/apps/tauri/src/lib/stores/app.svelte.ts index c3859cb..c1f8bec 100644 --- a/apps/tauri/src/lib/stores/app.svelte.ts +++ b/apps/tauri/src/lib/stores/app.svelte.ts @@ -35,8 +35,9 @@ let _syncInterval: ReturnType | null = null; let _syncDebounce: ReturnType | null = null; let _focusUnlisten: (() => void) | null = null; const DEFAULT_SYNC_INTERVAL_SECS = 60; +const DEFAULT_SYNC_INTERVAL_UNFOCUSED_SECS = 600; const SYNC_DEBOUNCE_MS = 5_000; -const SYNC_FOCUS_THRESHOLD_MS = 30_000; +let _appFocused = true; // ── Derived ────────────────────────────────────────────────────────── @@ -85,6 +86,11 @@ let syncIntervalSecs = $derived( ? config.workspaces[config.current_workspace]?.sync_interval_secs ?? DEFAULT_SYNC_INTERVAL_SECS : DEFAULT_SYNC_INTERVAL_SECS, ); +let syncIntervalUnfocusedSecs = $derived( + config?.current_workspace + ? config.workspaces[config.current_workspace]?.sync_interval_unfocused_secs ?? DEFAULT_SYNC_INTERVAL_UNFOCUSED_SECS + : DEFAULT_SYNC_INTERVAL_UNFOCUSED_SECS, +); // ── Actions ────────────────────────────────────────────────────────── @@ -364,21 +370,26 @@ function debouncedSync() { _syncDebounce = setTimeout(() => { _syncDebounce = null; triggerSync(); }, SYNC_DEBOUNCE_MS); } +function restartSyncInterval() { + if (_syncInterval) clearInterval(_syncInterval); + var secs = _appFocused ? syncIntervalSecs : syncIntervalUnfocusedSecs; + _syncInterval = setInterval(triggerSync, secs * 1000); +} + function startAutoSync() { stopAutoSync(); + _appFocused = true; triggerSync(); - _syncInterval = setInterval(triggerSync, syncIntervalSecs * 1000); - // Store the promise-returned unlisten function, ensuring we clean up any - // previous listener before assigning a new one. + restartSyncInterval(); getCurrentWindow().onFocusChanged(({ payload: focused }) => { - if (focused && Date.now() - lastSyncTime > SYNC_FOCUS_THRESHOLD_MS) triggerSync(); + // Sync on re-focus if stale beyond the focused interval + if (focused && !_appFocused && Date.now() - lastSyncTime > syncIntervalSecs * 1000) + triggerSync(); + _appFocused = focused; + restartSyncInterval(); }).then((unlisten) => { - // If stopAutoSync was called while the promise was pending, immediately clean up - if (!_syncInterval) { - unlisten(); - } else { - _focusUnlisten = unlisten; - } + if (!_syncInterval) unlisten(); + else _focusUnlisten = unlisten; }).catch((e) => { console.warn("Failed to set up focus listener:", e); }); @@ -404,6 +415,20 @@ async function setSyncInterval(secs: number | null) { } } +async function setSyncIntervalUnfocused(secs: number | null) { + if (!config?.current_workspace) return; + try { + await invoke("set_sync_interval_unfocused", { + workspaceId: config.current_workspace, + intervalSecs: secs, + }); + config = await invoke("get_config"); + if (isWebdav) startAutoSync(); + } catch (e) { + error = String(e); + } +} + async function setTheme(theme: string | null) { if (!config?.current_workspace) return; try { @@ -518,6 +543,9 @@ export const app = { get syncIntervalSecs() { return syncIntervalSecs; }, + get syncIntervalUnfocusedSecs() { + return syncIntervalUnfocusedSecs; + }, get lastSyncResult() { return lastSyncResult; }, @@ -553,6 +581,7 @@ export const app = { startAutoSync, stopAutoSync, setSyncInterval, + setSyncIntervalUnfocused, setTheme, addWebdavWorkspace, forgetMissingWorkspace, diff --git a/apps/tauri/src/lib/types.ts b/apps/tauri/src/lib/types.ts index ca603fe..36d07c8 100644 --- a/apps/tauri/src/lib/types.ts +++ b/apps/tauri/src/lib/types.ts @@ -29,6 +29,7 @@ export interface WorkspaceConfig { last_sync: string | null; theme: string | null; sync_interval_secs: number | null; + sync_interval_unfocused_secs: number | null; } export interface AppConfig { diff --git a/crates/onyx-core/src/config.rs b/crates/onyx-core/src/config.rs index 4ed9528..d7807be 100644 --- a/crates/onyx-core/src/config.rs +++ b/crates/onyx-core/src/config.rs @@ -33,11 +33,13 @@ pub struct WorkspaceConfig { pub theme: Option, #[serde(skip_serializing_if = "Option::is_none", default)] pub sync_interval_secs: Option, + #[serde(skip_serializing_if = "Option::is_none", default)] + pub sync_interval_unfocused_secs: Option, } impl WorkspaceConfig { pub fn new(name: String, path: PathBuf) -> Self { - Self { name, path, mode: WorkspaceMode::Local, webdav_url: None, webdav_path: None, last_sync: None, theme: None, sync_interval_secs: None } + Self { name, path, mode: WorkspaceMode::Local, webdav_url: None, webdav_path: None, last_sync: None, theme: None, sync_interval_secs: None, sync_interval_unfocused_secs: None } } }