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?;