From d44c88246c3c22e43bbdf322f4b39d82401d7af4 Mon Sep 17 00:00:00 2001 From: Jumoke Bolanle Date: Fri, 8 May 2026 11:20:40 -0500 Subject: [PATCH 01/10] fix: don't abort entire batch on rename failure (compress.rs) Replace the `?` operator on fs::rename with a match that: - Emits an error event with the failure reason - Removes the temporary file to avoid orphans - Continues to the next job instead of aborting the batch This matches the existing GS failure pattern and ensures that a single file's rename failure doesn't abort all remaining jobs. --- src-tauri/src/compress.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/compress.rs b/src-tauri/src/compress.rs index 502aa8b..279b63b 100644 --- a/src-tauri/src/compress.rs +++ b/src-tauri/src/compress.rs @@ -93,7 +93,17 @@ pub async fn compress_files( match run_gs(&app, args).await { Ok(()) => { - std::fs::rename(&tmp_path, &output_path).map_err(|e| e.to_string())?; + if let Err(e) = std::fs::rename(&tmp_path, &output_path) { + let _ = std::fs::remove_file(&tmp_path); + let _ = app.emit("compress:progress", ProgressEvent { + file: job.path.clone(), + status: "error".into(), + saved_bytes: None, + compressed_size: None, + error_msg: Some(e.to_string()), + }); + continue; + } let compressed_size = std::fs::metadata(&output_path) .map(|m| m.len() as i64) From 0b6d0f3a160948da13d5f1cc05541b125f5fe026 Mon Sep 17 00:00:00 2001 From: Jumoke Bolanle Date: Fri, 8 May 2026 11:37:10 -0500 Subject: [PATCH 02/10] refactor: consolidate formatBytes (notification.ts + DetailPanel.svelte) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename formatSavedBytes → formatBytes in notification.ts - Update buildNotificationBody to call formatBytes - Delete formatSize function from DetailPanel.svelte - Import formatBytes from notification.ts in DetailPanel.svelte - Replace all 3 formatSize call sites with formatBytes - Update tests to use formatBytes instead of formatSavedBytes Uses the Math.round version from notification.ts (more idiomatic than .toFixed(0)) as the canonical implementation. --- src/lib/components/DetailPanel.svelte | 13 ++++--------- src/lib/notification.ts | 4 ++-- src/test/notification.test.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/lib/components/DetailPanel.svelte b/src/lib/components/DetailPanel.svelte index 3b961bb..0eb238e 100644 --- a/src/lib/components/DetailPanel.svelte +++ b/src/lib/components/DetailPanel.svelte @@ -5,17 +5,12 @@ import { queue, type Preset } from "$lib/stores/queueStore"; import { selectedFileId } from "$lib/stores/selectionStore"; import { settings } from "$lib/stores/settingsStore"; + import { formatBytes } from "$lib/notification"; const selectedFile = derived([queue, selectedFileId], ([$q, $id]) => $q.find((e) => e.id === $id) ?? null); $: hasFiles = $queue.length > 0; - function formatSize(bytes: number): string { - if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`; - if (bytes >= 1_000) return `${(bytes / 1_000).toFixed(0)} KB`; - return `${bytes} B`; - } - function savingsPct(original: number, compressed: number): string { return `−${Math.round(((original - compressed) / original) * 100)}%`; } @@ -85,13 +80,13 @@ {/if}
-
Original{formatSize($selectedFile.size)}
+
Original{formatBytes($selectedFile.size)}
{#if $selectedFile.status === "done" && $selectedFile.compressedSize !== undefined}
{savingsPct($selectedFile.size, $selectedFile.compressedSize)}
- {formatSize($selectedFile.size)} → {formatSize($selectedFile.compressedSize)} - · saved {formatSize($selectedFile.size - $selectedFile.compressedSize)} + {formatBytes($selectedFile.size)} → {formatBytes($selectedFile.compressedSize)} + · saved {formatBytes($selectedFile.size - $selectedFile.compressedSize)}
diff --git a/src/lib/notification.ts b/src/lib/notification.ts index deba361..8609df5 100644 --- a/src/lib/notification.ts +++ b/src/lib/notification.ts @@ -1,4 +1,4 @@ -export function formatSavedBytes(bytes: number): string { +export function formatBytes(bytes: number): string { if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`; if (bytes >= 1_000) return `${Math.round(bytes / 1_000)} KB`; return `${bytes} B`; @@ -11,7 +11,7 @@ export function buildNotificationBody( ): string { if (errorCount === 0) { const noun = doneCount === 1 ? "PDF" : "PDFs"; - return `${doneCount} ${noun} compressed — saved ${formatSavedBytes(savedBytes)} total`; + return `${doneCount} ${noun} compressed — saved ${formatBytes(savedBytes)} total`; } const total = doneCount + errorCount; return `${doneCount} of ${total} PDFs compressed — ${errorCount} failed`; diff --git a/src/test/notification.test.ts b/src/test/notification.test.ts index 0042360..d5ba6ce 100644 --- a/src/test/notification.test.ts +++ b/src/test/notification.test.ts @@ -1,21 +1,21 @@ import { describe, it, expect } from "vitest"; -import { buildNotificationBody, formatSavedBytes } from "$lib/notification"; +import { buildNotificationBody, formatBytes } from "$lib/notification"; -describe("formatSavedBytes", () => { +describe("formatBytes", () => { it("formats bytes under 1 KB", () => { - expect(formatSavedBytes(500)).toBe("500 B"); + expect(formatBytes(500)).toBe("500 B"); }); it("formats KB (rounds to nearest KB)", () => { - expect(formatSavedBytes(840_000)).toBe("840 KB"); + expect(formatBytes(840_000)).toBe("840 KB"); }); it("formats MB to one decimal place", () => { - expect(formatSavedBytes(2_400_000)).toBe("2.4 MB"); + expect(formatBytes(2_400_000)).toBe("2.4 MB"); }); it("formats exactly 1 MB", () => { - expect(formatSavedBytes(1_000_000)).toBe("1.0 MB"); + expect(formatBytes(1_000_000)).toBe("1.0 MB"); }); }); From 3e42bc90eff50c08fb702853d53439f3fb6979e7 Mon Sep 17 00:00:00 2001 From: Jumoke Bolanle Date: Fri, 8 May 2026 12:05:16 -0500 Subject: [PATCH 03/10] refactor: remove dead check_path_writable_cmd wrapper (lib.rs) The #[tauri::command] wrapper function check_path_writable_cmd was never called from any frontend TypeScript/Svelte code. Remove the wrapper and its entry in generate_handler! while keeping the underlying pub fn check_path_writable which is used by unit tests. Tests: all 38 tests pass --- src-tauri/src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 6e7a6b9..20a8bde 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -45,11 +45,6 @@ pub fn check_path_writable(path: String) -> bool { } } -#[tauri::command] -fn check_path_writable_cmd(path: String) -> bool { - check_path_writable(path) -} - #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { use crate::compress::compress_files; @@ -89,7 +84,6 @@ pub fn run() { reveal_in_finder, get_file_meta, validate_pdf, - check_path_writable_cmd, set_menu_item_enabled, check_for_update, ]) From 57ca0398766358b0bafef1355c7029d7d47d377b Mon Sep 17 00:00:00 2001 From: Jumoke Bolanle Date: Fri, 8 May 2026 12:11:22 -0500 Subject: [PATCH 04/10] refactor: extract revealInFinder to fileActions.ts --- src/lib/components/DetailPanel.svelte | 6 +----- src/lib/fileActions.ts | 4 ++++ src/routes/+page.svelte | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/components/DetailPanel.svelte b/src/lib/components/DetailPanel.svelte index 0eb238e..e2a6259 100644 --- a/src/lib/components/DetailPanel.svelte +++ b/src/lib/components/DetailPanel.svelte @@ -1,11 +1,11 @@