From 66aae232c5442c92c0b1a1cbb9238632e33ef5f8 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 6 Jul 2026 13:43:23 -0500 Subject: [PATCH 01/43] fix(calendar): use shared errorMessage() for PST import errors Tauri commands reject with a {kind, message} object, not a native Error, so `e instanceof Error ? e.message : String(e)` always fell through to String(e) -> "[object Object]", hiding the actual readpst failure reason. --- src/lib/stores/calendar.svelte.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/stores/calendar.svelte.ts b/src/lib/stores/calendar.svelte.ts index b4d67f7..2b95bfd 100644 --- a/src/lib/stores/calendar.svelte.ts +++ b/src/lib/stores/calendar.svelte.ts @@ -1,7 +1,7 @@ // Imported calendar events (Phase 6, FR-CAL-*). Svelte 5 runes store, same // shape as settings.svelte.ts/meetings.svelte.ts. -import { api, events, type CalendarEvent } from "../api"; +import { api, errorMessage, events, type CalendarEvent } from "../api"; class CalendarStore { events = $state([]); @@ -30,7 +30,7 @@ class CalendarStore { await api.importPst(path, password); await this.load(); } catch (e) { - this.importError = e instanceof Error ? e.message : String(e); + this.importError = errorMessage(e); } finally { this.importing = false; this.importProgress = null; -- 2.34.1 From 2ca41514440afd587d0510997b5fdf5755ba9608 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 6 Jul 2026 13:56:44 -0500 Subject: [PATCH 02/43] fix(calendar): fall back to stdout when readpst's stderr is empty readpst writes some failure messages to stdout rather than stderr, so a failing run with empty stderr surfaced as a blank "parse failed: " error with no diagnostic content. --- src-tauri/src/calendar/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/calendar/mod.rs b/src-tauri/src/calendar/mod.rs index e99450f..d6711e2 100644 --- a/src-tauri/src/calendar/mod.rs +++ b/src-tauri/src/calendar/mod.rs @@ -79,9 +79,15 @@ fn run_readpst(pst_path: &str, out_dir: &Path) -> Result<(), CalError> { _ => CalError::Open(e.to_string()), })?; if !output.status.success() { - return Err(CalError::Parse( - String::from_utf8_lossy(&output.stderr).trim().to_string(), - )); + // readpst writes some errors to stdout rather than stderr; show + // whichever stream actually has text, stderr first. + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + let message = if stderr.is_empty() { + String::from_utf8_lossy(&output.stdout).trim().to_string() + } else { + stderr + }; + return Err(CalError::Parse(message)); } Ok(()) } -- 2.34.1 From 8f98332cdda7493702580711bbb5bcb5ab242010 Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Mon, 6 Jul 2026 14:19:14 -0500 Subject: [PATCH 03/43] feat(calendar): filter imported events by title and date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client-side filter over the already-loaded events list (T6.3, FR-CAL-2) — 1700+ events from a real mailbox is unwieldy to browse unfiltered. --- src/lib/views/Settings.svelte | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/lib/views/Settings.svelte b/src/lib/views/Settings.svelte index 3b71e3d..0246b03 100644 --- a/src/lib/views/Settings.svelte +++ b/src/lib/views/Settings.svelte @@ -173,6 +173,26 @@ // ---- Calendar / .pst import (T6.1/T6.2/T6.3, FR-CAL-1/2) ---- let pstPath = $state(""); let pstPassword = $state(""); + let eventTitleFilter = $state(""); + let eventDateFilter = $state(""); + + function eventLocalYmd(unixSecs: number | null): string { + if (!unixSecs) return ""; + const d = new Date(unixSecs * 1000); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; + } + + let filteredEvents = $derived( + calendar.events.filter((ev) => { + if (eventDateFilter && eventLocalYmd(ev.starts_at) !== eventDateFilter) return false; + if ( + eventTitleFilter && + !(ev.subject ?? "").toLowerCase().includes(eventTitleFilter.toLowerCase()) + ) + return false; + return true; + }), + ); async function pickPstFile() { const path = await open({ filters: [{ name: "Outlook data file", extensions: ["pst"] }] }); @@ -708,8 +728,21 @@ {#if calendar.events.length === 0}

No events imported yet.

{:else} +
+ + +
+

+ {filteredEvents.length} of {calendar.events.length} events +