From 973d575b5190bc3f7c933bea75cedae92fe428a3 Mon Sep 17 00:00:00 2001 From: Tristan Michael Date: Mon, 6 Apr 2026 17:17:00 -0700 Subject: [PATCH] feat: add onyx dark theme support Add onyx theme as a new dark theme option. Register it in the dark themes set, add the theme option to the settings dropdown, and define complete color variables and scrollbar styling for the onyx theme. Also improve sync error handling to gracefully skip upload and conflict resolution actions when files are missing, preventing sync failures due to files moved or deleted between action computation and execution. --- apps/tauri/src/app.css | 27 +++++++++++++++++++ .../src/lib/screens/SettingsScreen.svelte | 1 + apps/tauri/src/lib/stores/app.svelte.ts | 2 +- crates/onyx-core/src/sync.rs | 24 +++++++++++++++-- 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/apps/tauri/src/app.css b/apps/tauri/src/app.css index c6f76be..926b3cf 100644 --- a/apps/tauri/src/app.css +++ b/apps/tauri/src/app.css @@ -139,6 +139,33 @@ body { --color-danger: #ff5555; } +[data-theme="onyx"] { + --color-primary: #ffffff; + --color-primary-hover: #e0e0e0; + --color-surface-light: #0a0a0a; + --color-surface-dark: #0a0a0a; + --color-card-light: #161616; + --color-card-dark: #161616; + --color-text-light: #c4a95a; + --color-text-dark: #c4a95a; + --color-text-secondary-light: #917a3e; + --color-text-secondary-dark: #917a3e; + --color-border-light: #2a2418; + --color-border-dark: #2a2418; + --color-danger: #ef4444; +} + +[data-theme="onyx"] * { + scrollbar-color: #917a3e transparent; +} +[data-theme="onyx"] ::-webkit-scrollbar-thumb { + background: #917a3e; +} +[data-theme="onyx"] select option { + background-color: #0a0a0a; + color: #c4a95a; +} + [data-theme="solarized"] { --color-primary: #268bd2; --color-primary-hover: #1e7ac0; diff --git a/apps/tauri/src/lib/screens/SettingsScreen.svelte b/apps/tauri/src/lib/screens/SettingsScreen.svelte index f35bc72..11c3219 100644 --- a/apps/tauri/src/lib/screens/SettingsScreen.svelte +++ b/apps/tauri/src/lib/screens/SettingsScreen.svelte @@ -259,6 +259,7 @@ + diff --git a/apps/tauri/src/lib/stores/app.svelte.ts b/apps/tauri/src/lib/stores/app.svelte.ts index c1f8bec..298c7d7 100644 --- a/apps/tauri/src/lib/stores/app.svelte.ts +++ b/apps/tauri/src/lib/stores/app.svelte.ts @@ -67,7 +67,7 @@ let hasWorkspace = $derived( Object.keys(config.workspaces).length > 0, ); -const DARK_THEMES = new Set(["dark", "nord", "dracula", "solarized"]); +const DARK_THEMES = new Set(["dark", "nord", "dracula", "solarized", "onyx"]); let currentTheme = $derived( config?.current_workspace ? config.workspaces[config.current_workspace]?.theme ?? null diff --git a/crates/onyx-core/src/sync.rs b/crates/onyx-core/src/sync.rs index dc7de5f..7f2c5e5 100644 --- a/crates/onyx-core/src/sync.rs +++ b/crates/onyx-core/src/sync.rs @@ -688,7 +688,18 @@ async fn execute_action( match action { SyncAction::Upload { path } => { let local_path = workspace_path.join(path.replace('/', std::path::MAIN_SEPARATOR_STR)); - let data = std::fs::read(&local_path)?; + let data = match std::fs::read(&local_path) { + Ok(d) => d, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + // File was moved or deleted since the action was computed (e.g. task + // moved between lists). Drop the stale upload and clean up sync state + // so the next cycle can re-evaluate from a clean baseline. + report(&format!(" - Skipping upload for missing file {}", path)); + sync_state.files.remove(path.as_str()); + return Ok(()); + } + Err(e) => return Err(e.into()), + }; let checksum = compute_checksum(&data); if let Some(parent) = path_parent(path) { @@ -707,7 +718,16 @@ async fn execute_action( SyncAction::Conflict { path } => { let local_path = workspace_path.join(path.replace('/', std::path::MAIN_SEPARATOR_STR)); - let local_data = std::fs::read(&local_path)?; + let local_data = match std::fs::read(&local_path) { + Ok(d) => d, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + // Local file gone — treat as remote-wins: download it on next cycle. + report(&format!(" - Skipping conflict for missing local file {}", path)); + sync_state.files.remove(path.as_str()); + return Ok(()); + } + Err(e) => return Err(e.into()), + }; let local_checksum = compute_checksum(&local_data); let remote_data = client.get_file(path).await?;