[0.3.5] - Unreleased
Changed
- Fast vector index (HNSW): Migrated from
instant-distancetohnsw_rs(stable-Rust HNSW). The ANN graph is keyed byVectorIndexslot; stale chunk IDs are tombstoned on message re-embed, and online inserts keep the graph current without full rebuilds. Graph persistence writeshnswlib_rs_v1.hnsw.data/hnswlib_rs_v1.hnsw.graphand a tinyhnswlib_rs_v1_metasidecar to the cache directory; on startup the graph is loaded from disk in seconds rather than rebuilt from scratch. The oldhnsw_cache.binformat is no longer read. Background priority still duty-cycle throttles bulk catch-up inserts.
Fixed
- Reminders cleanup slow query with plugin disabled: After each sync,
clear_account_remindersran aSELECT EXISTSwith anORacrossreminder_kindandreminder_dismissed_at, which forced a full scan ofconversation_stats(~1 s per account) even when Reminders was off and no rows carried reminder state. The existence check now uses aUNIONso SQLite can walk the existing partial indexes on each branch separately.
[0.3.4] - 2026-06-04
Added
- Sidebar unread badges and toggles (Settings → General, all on by default):
- Show unread counts next to accounts: per-account totals and the Unified Mailbox total (hidden when the count is zero).
- Show unread counts next to custom folders: unread totals on user-created folders (combined by folder name in unified mode; hidden when zero).
- Show unread count next to the selected default folder: unread total on the active Default Folders row only (Conversations, Inbox, Sent, Starred, Archive, Trash, Spam, Drafts, etc.; role totals combined in unified mode; hidden when zero).
- Backend commands
custom_folder_unread_counts,default_folder_unread_counts,conversations_unread_counts, andstarred_unread_countsfeed the sidebar; counts refresh after sync and read/unread actions.
Fixed
-
Sidebar unread badges not updating live: Unread numbers could stay stale until a folder section was collapsed and expanded. Badge rows now re-render when counts change (reactive snapshot and
{#each}keys tied to unread totals). -
Default-folder unread badge: Active-state detection now follows
listSource, so the selected-folder badge works for Conversations and role folders (including Archive), not only when a non-reactive local role variable happened to match. -
Instant unread badge feedback: Mark read/unread, new mail, and conversation mutations apply optimistic sidebar deltas (0 to 1 and back) before the database refresh; custom-folder updates no longer wait on a 1s throttle for user actions.
-
Per-account conversation loading: Opening a thread from a specific account could feel much slower than in the unified mailbox because SQLite chose the wrong indexes. Thread fetch (
get_conversation_messages) now usesINDEXED BY idx_messages_conv_listingso the planner does not scanidx_messages_conv_dateacross the whole mailbox to satisfyORDER BY date. Conversation summary refresh for a single account now usesINDEXED BY idx_messages_account_conv_listing(account-first covering index from migration 0023) instead of the conversation-first index, avoiding a heap-fetch ofaccount_idon every row.
[0.3.3] - 2026-06-04
Added
-
Unarchive in Archive: When viewing the Archive folder, right-click a message for Unarchive in the context menu, use the bulk action bar Unarchive button for multi-select, or press
eto move selected mail back to Inbox. Gmail accounts re-apply theINBOXlabel; IMAP accounts move to the Inbox folder. Uses the existingbulk_move_messagespath (same as “Not spam”). -
One-click unsubscribe: Messages with
List-Unsubscribeheaders show an Unsubscribe action in the reader toolbar and message context menu. Synapse tries RFC 8058 one-click POST (withList-Unsubscribe: One-Click), falls back to HTTPS GET when POST is not allowed, then sends amailto:unsubscribe if present. If automation fails, the list URL opens in your default browser so you can finish there. The button appears for bulk/list mail detected at sync (has_list_headers); PEC messages are excluded. -
Embedding processing priority (Settings → AI): choose Background (default) or Maximum performance. Background duty-cycles ONNX embedding work and fast vector index (HNSW) builds to about 10% average GPU/CPU utilization (sleep
9×work duration after each sub-batch or HNSW build phase) so long backfills, ANN graph construction, and incremental indexing stay responsive. Maximum performance removes throttling so indexing finishes as fast as possible. The choice is stored inlocalStorageand pushed to the Rust backend viaset_embedding_priorityon startup and when changed.
Changed
-
On-device LLM runtime (
llama-cpp-4): Replacedllama-cpp-20.1.146 withllama-cpp-40.3.0 (May 2026 llama.cpp, Vulkan + OpenMP only;mtmdanddynamic-linkdefault features disabled). The bundled engine was too old for bartowski Qwen3.5-4B GGUFs (qwen35hybrid SSM/DeltaNet); the new runtime matches current converter expectations. Inference code inai/inference.rswas adapted: chat template viaget_chat_template/apply_chat_template(no OpenAI-compat Jinja kwargs in -4), empty thinking-block suffix for Qwen3.5 whenenable_thinkingis off,token_to_bytesdecoding, and panic-safe GBNF grammar init. -
Mail list IPC payload: List and pagination commands (
list_messages, unified folder/role/starred queries) now use a slim SQL select instead of full message bodies.body_htmlis omitted (NULLon the wire);body_textis truncated to 500 characters, enough for the list preview. Single-message fetch (get_message) and the final search top-50 still return full bodies for the reader. -
Sender avatar computation: Gravatar/Libravatar URLs, identity parsing, and fallback colors/aurora meshes are memoized per email for the app session, so scrolling or re-rendering the mail list does not repeat MD5 and gradient work for the same sender.
-
Search candidate RAM: Hybrid, FTS, and semantic search now load slim candidates (
body_htmlomitted,body_texttruncated to 4000 characters) for up to ~200 rows per leg, then hydrate full bodies in one query for the final top 50. Cuts peak search RAM from hundreds of MB (full HTML across hundreds of candidates) to a few MB plus a single 50-row fetch. -
Thread body lazy loading: Opening a conversation loads slim message rows (no
body_html,body_texttruncated to 500 characters for collapsed previews). Expanding a message fetches the full body viaget_messageonce per message, so long threads no longer put every body in the JS heap upfront. -
IMAP post-sync concurrency: After each sync batch, attachment indexing and chunk embedding tasks share bounded semaphores (4 permits each) instead of spawning unbounded work. Raw message bytes are passed as
Arc<Vec<u8>>so attachment tasks clone a reference, not a full copy of the RFC822 payload. -
Embedding backfill ID pagination: Auto-backfill, manual backfill, and rebuild-search-index no longer load every pending message ID in one SQLite query (which could return 400k+ rows and trip sqlx slow-statement warnings). They use
count_pendingfor the progress total and cursor-paginatedfetch_pending_ids_pageso each page stays bounded to the backfill chunk size. -
Chunk vector cache incremental load: When the on-disk vector cache is behind
vec_message_chunksbut still valid, startup merges only new chunk rows (WHERE id > max_cached_id) instead of always scanning all embedding BLOBs in 10k-row pages. Matches the existing incremental path for legacyvec_messages. -
Embedding batch write throughput: Phase 2 of the chunk batch pipeline now deletes existing rows with a single
DELETE … WHERE message_id IN (…)per 999 IDs instead of one statement per message. Phase 4 wraps all chunk INSERTs andUPDATE has_embedding = 1statements in a single transaction, reducing WAL flushes from ~2 000 per 500-message batch to one. In-memory vector cache updates and event emissions are deferred to after the commit so they only reflect durable data. -
Chunk overlap reduction: Default chunk overlap reduced from 50 words to 25 words (step 200 → 225). Generates ~10% fewer chunks per message, directly reducing ONNX inference work during backfill with negligible recall impact.
-
HNSW build memory: HNSW graph construction documents and tightens the transient vector copy lifecycle (
points/valuesbuilt with exact capacity, read lock released beforeBuilder::build, vectors moved in and freed on return). Steady-state vector-index deduplication (mmap, quantization) remains deferred. -
Frontend dependency upgrades: Tailwind CSS 3 → 4 with the official upgrade path (
@tailwindcss/postcss, CSS-first theme inapp.css, removedtailwind.config.tsandautoprefixer). SvelteKit, Svelte, TypeScript, Vite, Vitest, and@sveltejs/vite-plugin-svelte(v6, kept on Vite 6) bumped to current releases. TipTap compose packages updated to 3.23.x. Tauri JavaScript packages (@tauri-apps/api, CLI, plugins) aligned on the 2.11 line. -
Icons and UI libraries:
lucide-sveltereplaced by@lucide/svelte(package rename); imports updated across the app. Removed unusedbits-ui. -
Rust and Tauri dependency upgrades:
tauripinned to 2.11 to match@tauri-apps/api. Major crate bumps:sqlx0.8 → 0.9 (dynamic SQL audited withsqlx::AssertSqlSafe),oauth24 → 5 (OAuth client builder updated inauth),thiserror1 → 2. Lockfiles (package-lock.json,Cargo.lock) regenerated.
Fixed
-
Qwen3.5 model load failure:
llama_model_load: missing tensor 'blk.32.ssm_conv1d.weight'when loading a complete GGUF was an engine/GGUF version mismatch, not a truncated download. Resolved by thellama-cpp-4migration above. -
Model download on Windows: Replacing an in-use GGUF during download could fail with “Access is denied” (os error 5) because llama.cpp memory-maps the file.
load_modelnow optionally deletes corrupt files before releasingLOAD_LOCK;download_modelwaits on that lock and retries the.tmprename with backoff. Truncated downloads are rejected by size check; failed loads remove the file and emitmodel://corruptfor Settings. -
Model download UI: Progress is restored when reopening Settings if a download is still running (
is_model_downloading). A second Download click no longer starts parallel downloads (IS_DOWNLOADINGguard andalready_runningprogress events). -
Onboarding window drag: The Welcome screen could not move the frameless main window. Added a top drag strip with
data-tauri-drag-regionand the samestartDragging()mousedown handler used in Settings and the EML viewer. -
docs/search.mdresource budget: Corrected the vector-cache row; the app uses a 256-entry query-embedding cache (clear on overflow), not a 256 MB LRU vector cache. -
src-tauri/Cargo.tomlcorruption: Section-separator comment lines had been damaged by repeated mis-encoding (mojibake), inflating the file to about 4.9 MB. Restored clean ASCII comments; Cargo reads a normal-sized manifest again. -
Reminders plugin scan storm: Enabling Reminders (Settings → Plugins) or saving its settings could start multiple overlapping full-account scans, each running a separate ~1 s SQL query per conversation (
LIMIT 2on message bodies). Disabling the plugin only cleared flags in a new task; earlier scans kept running and spammed slow-query logs. Scans now go through a per-account coordinator (one active scan, generation bump aborts superseded work, loop stops when disabled). Message data is loaded in chunks of 150 conversations via a window-function query with an 8 KBbody_textprefix and mailboxINfiltering instead of per-thread JOINs. -
Reminders smarter filtering: At sync, Gmail and IMAP now persist bulk-mail header signals (
Auto-Submitted,Precedence,List-*, noreplyReply-To,Feedback-ID) on each message. The scanner uses those flags before body heuristics, matches a shipped offline ESP/transactional domain list, expanded subject and HTML unsubscribe patterns, and skips newsletter-only threads where you never sent a reply. -
synapse://deep links on Windows: Pasting or opening URLs such assynapse://email/503804in a browser did nothing because Windows had no handler registered (browsers treated the string as a search query). The NSIS installer now writes thesynapseURL protocol underHKCR, release builds re-register only when the installed executable is not already the handler, andtauri-plugin-single-instanceforwards links to the running app instead of spawning a second instance. Callingregister_all()on every Windows startup was removed: it overwrote the installer handler with a dev binary that loadshttp://localhost:1420, so cold-start links from Edge failed when Synapse was closed and Vite was not running. -
Search bar clear button: Clicking the X to clear the query no longer leaves the search field focused; keyboard focus moves away so the bar returns to its unfocused appearance.
[0.3.2] - 2026-05-28
Added
-
Collapse previous messages in email chains: Long reply threads in the message body can be collapsed into a single Previous message or Previous messages (N) disclosure. The control uses native HTML
<details>inside the sandboxed email iframe (no scripts), so it works with the existing security model. Nested quotes from Gmail, Apple Mail, Outlook, Mailbird, and similar clients are flattened into one block. A General setting controls whether quoted chains start collapsed or expanded (default: collapsed). The iframe resizes when you expand or collapse the block. -
Save all attachments: When a message has more than one attachment, the attachment right-click menu includes Save All Attachments, which opens a folder picker and copies every downloaded attachment into the chosen folder in one step. Duplicate filenames get a numeric suffix before the extension (for example
photo_2.jpg).
Changed
- Full-screen views hide the sidebar: Settings, Address Book, and Outbox no longer show the accounts and folders sidebar; those panels use the full window width. The sidebar stays mounted but hidden so account avatars do not flash when you return to mail.
Fixed
-
Search when leaving Settings: Returning from Settings to the mail view could throw
Cannot access 'debounceTimer' before initializationwhen a search was still active.SearchBarnow declares debounce state before store subscriptions so remounting the search bar is safe. -
Compose quoted email selection: Previous messages in a reply or forward are shown in a sandboxed iframe inside the compose window. That iframe had
pointer-events: noneanduser-select: none, so you could not select or copy text from the quoted chain. The iframe is now interactive: you can select and copy quoted text, and http/https links open in the system browser. The surrounding compose layout and CSS are unchanged; deleting the whole quoted block still works from the attribution line or with Backspace from the line above. -
Gmail custom folder sync: Custom Gmail labels (user-created folders) could stop updating after the initial sync: counts and latest dates in Synapse fell behind Gmail because incremental sync only routed new mail to system folders (Inbox, Sent, Drafts, Trash, Spam) or Archive and ignored custom label ids. Label add/remove history for custom folders was also ignored. Migration
0062_gmail_label_id_on_mailboxes.sqlstores each mailbox’s Gmail label id; incremental routing and history handling now move messages into and out of custom folders. A one-time reconcile on the first sync after upgrade reassigns messages that were misrouted to Archive so existing folders catch up with Gmail.
[0.3.1] - 2026-05-27
Added
- Mail list wide layout: Added a “Wide Layout” option for the mail list in Split Bottom mode. When enabled, the sender and subject are displayed on a single line to better utilize horizontal space. When combined with “Hide message preview”, the list enters a Compact Mode where the row padding and avatar size are reduced, allowing significantly more rows to fit in the same vertical space. The setting is automatically disabled in Three Column mode to preserve legibility.
- Durable outbox (SMTP and Gmail): Every send is written to the outbox
before any network call, with
draft_jsonand opportunisticraw_mimestored for retries. Failed sends move throughqueued/failedwith exponential backoff; after five attempts they escalate toneeds_attention. Migration0061_outbox_durability.sqladds the new columns and index. IMAP idle and Gmail poll call a unifiedretry_outboxloop; MIME can be rebuilt fromdraft_jsonwhen needed. - Outbox UI: Sidebar entry and Outbox view list stuck messages with
Retry now, Edit & resend, Discard, and expandable error details.
A bottom banner in the main content area reports pending or needs-attention
counts and links to the Outbox view (
outbox://pending-countandoutbox://needs-attention-countevents). - Compose guards on send failure: After a failed send, the compose window flushes the draft session and prompts before close so unsent content is not lost.
- Outbox tests: Rust unit tests in
outbox/mod.rscover enqueue, retry selection, backoff, escalation, counts, force retry, discard, list/get, MIME reconstruction, and legacy row handling. - Conversation archive tests: Rust integration tests in
conversations.rscover thread view and listing when messages are in inbox, sent, archive, or mixed roles (fully archived threads hidden from the list; mixed threads show only non-archived messages).
Changed
-
Reply with AI: Opens in reply-all mode when the message has multiple recipients (the same condition that shows the Reply all button). Previously it always opened a single-recipient reply.
-
Outbox banner placement: The delivery-status strip is no longer a full-width fixed overlay; it sits at the bottom of the main content column so it does not cover sidebar items such as Settings.
-
Outbox discard confirmation: Discard in the Outbox view uses the app
ConfirmDialog(destructive style) instead of the browserconfirm()dialog. -
Conversations view and archived mail: Threads in the Conversations view now include only inbox and sent messages. Archived messages in the same conversation are omitted from the thread and from list summaries (message count, snippet). Conversations whose messages are all archived stay hidden from the list (
non_archive_ct = 0), unchanged from before.
Fixed
-
Stuck embedding backfill: Fixed an infinite loop in the embedding backfill process where messages (especially those with mostly whitespace bodies) would remain in the “pending” state despite multiple attempts. The
has_embeddingflag is now the single source of truth for processed messages, ensuring ineligible or failed messages are correctly excluded from future counts. Also resolved a bug in chunk ID retrieval duringINSERT OR IGNOREconflicts. -
Search mode toggle: Changing the search mode (FTS / AI / Hybrid) in the search bar now correctly re-triggers the search when results are already visible. Previously, the toggle only updated the UI state without refreshing the results.
-
Outbox rows without
draft_json: Messages queued before migration 0061 could not be retried or opened for edit becauseraw_mimeanddraft_jsonwere missing. Retry now and Edit & resend reconstruct aDraftMessagefrom the stored address and body columns, back-filldraft_json, and proceed (attachments on those rows cannot be recovered). -
Outbox force retry: Retry now resets
attemptsand clears backoff soneeds_attentionrows get a full retry cycle;queuedrows are always eligible for the retry loop regardless of prior attempt count. -
Compose insert link: After applying a link to selected text, the caret now moves to the end of the linked text (outside the link mark) instead of leaving the selection highlighted. Typing after Apply no longer extends the link or inserts an extra space inside it.
-
Search bar clear-all button: With many filter chips or a narrow mail list column, the X to clear the entire query sat inside the horizontally scrollable chip row and could only be reached by scrolling. The clear button now sits outside the scrollable region so it stays visible at the trailing edge of the search field.
-
Gmail thread conversation lookup: Resolving an existing conversation by Gmail native
threadIdcould take many seconds on large mailboxes because the index only coveredgmail_native_thread_id, so SQLite scanned every account sharing that thread before applyingaccount_id. Migration0058_gmail_native_thread_id_account_index.sqladds a partial composite index on(account_id, gmail_native_thread_id)whereconversation_id IS NOT NULL. Migration0060_gmail_thread_account_covering_index.sqlextends that index withdate,id, andconversation_idsoORDER BY date ASC, id ASC LIMIT 1is satisfied from the index alone (no heap sort or table lookup).
[0.3.0] - 2026-05-15
Added
- Deferred sending with undo: After clicking Send, Synapse waits a configurable grace period (default 5 s) before making the actual send call. A toast with an Undo button appears during the wait; pressing Undo or Ctrl+Z cancels the send and reopens the compose window with the draft intact. The delay can be changed (Off / 5 s / 10 s / 20 s / 30 s) in Settings → General → Undo send delay. Matches Gmail’s “Undo Send” UX. Setting the delay to Off restores immediate send behaviour.
- Archive and trash undo: Archiving or moving messages or conversations to Trash (keyboard shortcuts, bulk bar, and context menu) hides them in the list immediately, shows a success toast with an Undo action, and runs the remote mutation after a short grace period. Undo or Ctrl+Z cancels the pending action and restores the rows; permanent deletes from Trash are unchanged (immediate, no undo).
- Pending send banner (IMAP / SMTP outbox): When a send is stored in the
outbox with status
failed(for example after going offline), the main window shows a persistent bottom banner with the count of messages waiting to send and that they will retry automatically when connected. The count is loaded on startup and kept in sync via theoutbox://pending-countevent after each sync cycle and when a send attempt fails. - Automated tests for offline resilience: Unit tests for the operation queue
(enqueue, drain, max attempts), SMTP outbox retry,
network_error_to_string, and Vitest coverage fortoastnetwork-aware helpers. - Reader link hover tooltip: Hovering a link in the HTML message body (and
in the PEC envelope iframe when shown) displays a floating tooltip with the
full
hrefstring. The tooltip is drawn in the outer window so it is not clipped by the email iframe.
Fixed
- Keyboard shortcuts with Ctrl+letter:
normalizeKeyCombonow uppercases the base key when Ctrl, Alt, or Meta is held so the produced combo matches preset maps (e.g.Ctrl+Zfor undo, Mailbird’sCtrl+N/Ctrl+R, etc.). Browsers reportevent.keyas lowercase for these chords, which previously prevented the reverse lookup from firing. - Gmail send while offline: Offline send failures could surface as a raw
error sending request for url (https://gmail.googleapis.com/...)string. Transport-style reqwest failures (including send-phase errors whereis_connect()is false) are now mapped to the sameNETWORK_UNAVAILABLEpath as other commands, and the compose window shows the offline or server-unreachable message instead of the URL. - Gmail conversation threading: Synapse grouped conversations only from
RFC headers (
References,In-Reply-To,Message-ID), so threads that Gmail kept together (e.g. weak or missing reply headers) could appear as several separate conversations. Assignment now falls back to Gmail’s nativethreadIdwhen headers do not link a message to an existing conversation, and migration0056_rethread_gmail_split_conversations.sqlclearsconversation_idfor messages in split native threads so the next startup backfill reunifies them. - Mail list multi-select shortcuts: Read/unread, archive, and delete keyboard shortcuts only affected the focused row. They now apply to every selected conversation or message, consistent with the bulk action bar.
- Mail list: J/K navigation and focus ring: Clicking a row moved browser
focus to that element, but J / K only updated the selected row in state,
so the previously clicked row could keep the
:focus-visiblering (two blue lines) while another row showed the selected styling. After each nextItem / prevItem navigation, the handler now focuses the newly selected.mail-row(withpreventScrollso scrolling stays unchanged). - Message hybrid search (short queries): For three or fewer plain tokens, the semantic leg could behave like OR and surface messages that matched only part of the query. Semantic candidates are now post-filtered so every token appears in the subject or body before reciprocal-rank fusion.
- Conversation view and custom folders: Threads whose messages live only in
user-created folders (
roleunset) were still listed because stats and scope treated those mailboxes like inbox/sent. Listing, search, and hydration now align with standard folders only (inbox, sent, archive); migration0057_exclude_custom_folder_conversations.sqlrecomputesconversation_statsand replaces the triggers sonon_archive_ctcounts only inbox and sent. - Gmail bulk move to a custom folder: Moving to a user label called
modify_messagewith an emptyaddLabelIdslist while still removing the source label (e.g.INBOX), so messages left the inbox in Gmail but never received the new label. Custom targets now resolve the API label id by display name, and the “skip remote update” guard no longer fires when the target is a custom folder but there is nothing to remove from the current label set. - Unified mode: custom folders: The sidebar shows one row per custom
folder name, but opening it loaded only the first account’s mailbox id, so
messages in a same-named folder on another account (e.g. after a cross-account
move) never appeared in the mail list. Unified selection now lists messages
from every mailbox with that name via
list_unified_messages_by_folder_name, with matching pagination, refresh, soft-reload, and live-sync handling. - Local LLM model download (Windows): Downloading the optional Qwen GGUF could fail with Cannot create model file and Windows error 1224 (file has a user-mapped section open) when a previous or partial file was already loaded at startup. The downloader now skips work when the model is already loaded, unloads before overwriting so llama.cpp releases its memory map, streams into a temporary file, and renames it into place so interrupted downloads do not leave a truncated GGUF on the final path.
Changed
- Theme: “Default” renamed to “Simple”: The first light theme is now
labelled Simple (id
simple) to reflect that it is no longer the application default. Existing preference values pointing todefaultshould be migrated tosimple. - Theme: Aurora Sky: Hues shifted from the cyan/teal range (158–198) to the blue range (190–215) and lightness reduced by ~4–8 points throughout, giving the sidebar, accents, and aurora mesh a noticeably deeper, bluer look.
- Mail list multi-select: After a bulk action finishes, the row selection is cleared and multi-select mode turns off. This applies to toolbar actions (including read/unread toggle and export, which previously left selection active) and to the matching mail shortcuts when several rows are selected.
- Conversation FTS ordering and cap: The bounded FTS subquery now selects
candidates ordered by BM25 (
rank), and the outer query keeps that order so results are relevance-ranked instead of docid (insertion) order. The candidate cap is lowered from 10 000 to 2 000 to reduce sort work on broad queries while still yielding enough rows after mailbox filters. - Conversation thread-level rerank: Rank-decayed thread scoring now includes at most three matching messages per thread, so very large threads with many weak body hits no longer outscore smaller threads with a stronger best individual match.
- Gmail attachment errors offline: When Gmail attachment download fails
because the network is unreachable (connection, timeout, or send-phase
error sending requestfailures), the app shows a short offline / connectivity toast instead of a rawreqwestURL error. Other failures still surface the detailed error string. - Hybrid search reranking: Subject signals are tiered (contiguous phrase, all tokens in any order, partial) with stronger boosts for an exact phrase in the subject line, including when passing dense query text into exact-match feature computation so phrase matches can outrank recency-weighted partial hits.
[0.2.9] - 2026-05-13
Fixed
- Search index health (Settings):
COUNT(*)on FTS5 virtual tables scanned the full inverted index (tens of seconds on large mailboxes). Diagnostics now count rows via the FTS5_docsizeshadow tables instead, which is effectively instant while still reflecting indexed documents. - Search index health (Settings): The diagnostics card could stay on
“Loading diagnostics…” when Settings → AI opened while the embedding model was
still initialising and the SQLite pool was busy with mail sync. The UI now
defers the
get_search_diagnosticsinvoke until embeddings reportready(same guard as the pending-count refresh), the main DB pool uses an explicit connection acquire timeout so a starved pool fails fast instead of hanging forever, and the pending-embeddings diagnostic count usesTRIM(body_text)so it can use the existing partial index. - Conversation hybrid search latency: Natural-language queries could spend
15+ seconds in the FTS leg because common function words inflated posting-list
intersections and the planner could not cap FTS iteration before applying
account and mailbox filters. Plain Italian and English stop words are now
omitted from the compiled FTS
MATCH(but still included in dense text for the semantic leg), and conversation FTS uses a bounded inner subquery so the index stops after a fixed candidate cap before joining tomessages. - Mark conversation unread: Mark-unread from the conversation list (e.g. Ctrl+U with the Mailbird shortcut preset) previously flagged every message in the thread as unread. It now marks only the latest message per conversation, so the thread shows a single unread instead of inflating counts across the whole conversation.
- Gmail attachments (e.g. PDFs with Content-ID):
has_attachmentstreated every MIME part with aContent-IDas an inline-only asset, so real attachments such as PDFs that still carried a Content-ID never surfaced in the reader or paperclip indicator. Detection now ignores only CID parts that are actuallyimage/*; migration 0055 back-fillshas_attachmentsfor affected rows, and a successful attachment download corrects the flag when the indexer had skipped the part.
[0.2.8] - 2026-05-11
Added
- Low-memory mode for embeddings: On machines with 16 GB of RAM or less,
Synapse now automatically detects available system memory at startup and
activates a conservative embedding pipeline:
- ONNX Runtime batch size reduced from 256 → 64, cutting peak ORT arena allocation during backfill by ~4×.
- Body-fetch chunk size reduced from 500 → 100 messages per SQLite batch.
tokio::task::yield_now()between every sub-batch keeps the UI thread responsive; an additional 20 ms sleep per chunk gives the OS room to reclaim freed pages.- Message body
Strings are freed immediately after each embed call (HashMap::remove) instead of being held until end-of-batch. - HNSW graph build no longer clones the full
VectorIndex.databuffer (~900 MB);Vec<E5Point>is built directly from a single short-lived read guard. - HNSW serialisation streams through a
BufWriter→ atomic rename instead of materialising the whole graph as aVec<u8>in RAM (~1 GB peak). vector_cache.binreads stream through aBufReader(halving the ~1.8 GB transient peak); writes stream through aBufWriter→ atomic rename (eliminating the ~900 MBencode_cachebuffer).- Settings → AI shows an amber “Low-memory mode active (system has
≤ 16 GB RAM)” badge. The badge appears correctly even when Settings is
opened after the embedding model has already finished loading, via a new
is_low_memory_modebackend command. - Override with
SYNAPSE_FORCE_LOW_MEMORY=1insrc-tauri/.envto test the low-memory paths on any machine.
[0.2.7] - 2026-05-11
Added
- Mail search (DSL + hybrid retrieval): Gmail-style query operators
(
from:,to:,subject:,before:/after:,older_than:/newer_than:,is:unread,has:attachment,in:,account:, phrase quotes, exclusions,OR, and more) compiled to SQLite FTS5 and metadata filters, with optional on-device semantic search fused via weighted RRF, intent classification, thread-level aggregation, recency shaping, and a lightweight feature reranker. - Search indexes and migrations: rebuilt FTS5 with extra columns and prefix
indexing, trigram fallback (
messages_fts_tri), vector chunks for embeddings, attachments FTS, saved searches, personalization signal storage, and B-tree indexes to speed address filters and contact typeahead. - Automated search tests: integration tests with deterministic fixtures and
an optional YAML-driven quality eval harness (
search_eval), plus a release gate that runsnpm run testbefore production builds inpublish-release.ps1. - Documentation:
docs/search.mddescribing behavior and operators. - Windows Start Menu shortcut: the installer now asks whether to add
Synapse to the Windows Start Menu (default: yes). Silent installs (
/S) automatically create the shortcut. The shortcut is removed cleanly on uninstall regardless of the choice made at install time. - Settings → AI: The embeddings section shows which compute device is in
use for vector embeddings (NPU, GPU, or CPU), plus the adapter
name when DirectML is active. The backend reports this on
embeddings://init-statusand viaget_embeddings_ep_infoso the label stays correct if Settings opens after the model is already loaded. - Adaptive GPU preference: On machines without a battery (desktops),
the DirectML adapter list now uses
DXGI_GPU_PREFERENCE_HIGH_PERFORMANCEso the discrete GPU is preferred over the integrated one, giving faster backfill throughput. On laptops and tablets (battery present) the previousDXGI_GPU_PREFERENCE_MINIMUM_POWERordering is preserved to save battery. NPUs remain first in both cases.
Changed
- Account avatars: When an account has a sidebar label and no custom uploaded avatar, the initials fallback (shown if Gravatar, Libravatar, and brand logo do not resolve) is derived from that label instead of the display name or email local part. Tooltip and accessible name for the avatar prefer the label as well.
- Search UI: richer search bar (chips, filters, improved typing behavior), search-mode integration in list/thread views, and a compact loading spinner instead of wide “Searching…” text.
- AI / infra: embedding pipeline and chunking updates; attachment indexing helpers; reminder and vault-related robustness aligned with new fields and SQLCipher open ordering.
- Split-bottom layout: The horizontal resize strip between the mail list and reader pane uses a subtle border-colored fill at rest so the divider is easier to see; hover feedback while resizing is unchanged.
Fixed
- Settings + sidebar: Clicking a folder (including Conversations and reminder folders) while Settings is open now returns to the mail view and selects that folder. Sidebar row buttons no longer show a clipped focus ring (light blue above/below the row) after that transition.
- Search correctness: stricter application of structural filters (e.g. unread) across folder and conversation search paths; trigram fallback no longer reintroduces rows excluded by FTS negation.
- Intent and ranking: person vs filter-only classification order and recency weights tuned so newer results can surface appropriately where intended.
- Misc: calendar invite UTC formatting for tests; streaming thinking-strip
test alignment; Italian bulk-email marker coverage; secrets store
Debugand key validation ordering for clearer wrong-passphrase errors. - Mail list: Conversation row context menu vertical position now accounts for reminder-related items (dismiss, ignore rules) before opening, so the menu stays above the Windows taskbar when right-clicking near the bottom of the screen.
[0.2.6] - 2026-05-08
Added
.emlattachment viewer: Right-click an.emlattachment and choose Open in Synapse to open it in a dedicated viewer window so the main inbox stays unchanged.
Fixed
- Gmail reply threading: Replies sent via the Gmail API now include the
stored native
threadIdso messages stay in the correct conversation instead of starting a new thread.
[0.2.5] - 2026-05-06
Changed
- Forward in folder views: Forward is no longer limited to the unified conversation layout; it is available in ordinary mail folders as well. It remains hidden in Drafts, where forwarding is not meaningful.
Fixed
-
Local AI semantic search on DirectML: ONNX Runtime only allows one DirectML execution provider per process; registering multiple adapters (NPU plus GPU) could abort embedding initialization with “Provider DmlExecutionProvider has already been registered”. The embedding session now registers a single best adapter (NPU preferred over GPU, then CPU fallback and retry if the slot is already taken).
-
Application exit: fixed a bug where using the window close control did not fully shut down Synapse.
-
FastEmbed cache directory: running the portable
.exefrom the Desktop (or another ad-hoc working directory) could create a.fastembed_cachefolder beside the executable. The cache is now written to the proper application data location. -
Permanent delete from Trash: deleting a message that is already in Trash now completes successfully.
-
Message body and context menus: right-click menus no longer stay open when you click inside the email body; the menu closes as expected.
[0.2.4] - 2026-05-06
Added
- Skip trash confirmation: new toggle in Settings → General to bypass the “Move to Trash?” confirmation popup when deleting emails or conversations. The permanent-delete confirmation (shown when deleting items already in Trash) is always preserved regardless of this setting.
Fixed
-
Search mode buttons in split-bottom layout: the FTS / AI / Hybrid toggle buttons were sliding under the fixed window-control overlay in split-bottom mode (where the search bar spans the full window width). Added right-side clearance padding so the buttons are always fully visible and clickable.
-
Delete confirmation dialog: replaced the native browser
confirm()popup (which displayed an ugly “Localhost says” header in the WebView) with a custom in-app dialog that matches the application’s visual style. Trash-move dialogs use the primary button color; permanent-delete dialogs use a destructive red button to signal the action is irreversible. -
Message list relative dates: the mail list showed only the time of day for messages received on the previous calendar day when fewer than 24 hours had elapsed (e.g. 18:00 yesterday vs. noon today). It now compares calendar midnights so Yesterday and weekday labels match the full date shown in the reader.
-
Windows “Open with” for
.emlfiles: choosing Synapse from the context menu launched the app but did not open the standalone EML viewer. Windows passes paths likeC:\…\file.eml; the URL parser treated the drive letter as a bogus scheme and the path was skipped. Paths with a drive letter are now recognised before URL parsing (cold start and when Synapse is already running).
[0.2.0] - 2026-05-05
Fixed
- Production OAuth flow: Google (and Microsoft) OAuth credentials are now
baked into the release binary at compile time instead of being read from a
.envfile at runtime. On a freshly installed machine no.envfile exists, so previously the app fell back to the placeholder value and blocked the user with “OAuth client ID not configured” the first time they tried to add a Google account. - Onboarding → Accounts tab: The “Add Your First Account” button on the welcome screen now opens Settings on the Accounts tab directly instead of landing on the General tab.
- Onboarding button draggability: The “Add Your First Account” button was accidentally treated as a drag handle for the frameless window; it now responds to clicks reliably.
- Auto-update manifest parsing: The
latest.jsonrelease manifest was written with a UTF-8 BOM by PowerShell 5.x, which causedserde_jsoninside the Tauri updater to reject it with “Could not fetch a valid release JSON from the remote”. All files produced by the publish script now use UTF-8 without BOM consistently.
[0.1.0] - 2026-05-04
Added
Email & Accounts
- Google (Gmail REST API + IMAP IDLE) and generic IMAP/SMTP account support
- PEC (Italian certified email) support with auto-configuration for the most popular Italian PEC providers
- Multi-account unified inbox with per-account unread badges
- Conversation (thread) view with expand/collapse and newest-first option
- Real-time push sync via IMAP IDLE; offline-first SQLite cache
- Two-way read/unread status sync
- Mark as spam / not spam, star emails
- Bulk actions via multi-select
- Move emails between folders
- Forward emails including attachments
- EML file viewer with system file-association registration
Compose
- Multi-window compose with rich text formatting
- Inline image paste and drag-and-drop
- Attachment drag-and-drop with context menu
- Smart recipient autocomplete with address-book integration
- Collapsible CC / BCC fields
- Spellcheck with multi-language dictionary support (including Italian)
Search
- Hybrid full-text + semantic search powered by on-device vector embeddings
- Multilingual embedding support
AI (on-device, fully private)
- Local LLM integration (Qwen, GPU-accelerated via Vulkan: no cloud required)
- AI reply suggestions with grammar-constrained structured output
- Stop button and configurable writing-language support
- Asana plugin: create Asana tasks directly from an email thread
- ClickUp plugin: create ClickUp tasks directly from an email thread
- Calendar plugin: accept, tentatively accept, or decline ICS calendar invites via Google Calendar API, directly from the email banner
- Reminders plugin: surfaces follow-up reminders for emails that need a reply or action, with per-thread dismiss support
UI / UX
- Custom frameless window with native-feel drag region and window controls
- Resizable multi-column layout with persisted column widths
- Collapsible and resizable sidebar with per-account folder visibility toggle
- Full theming system: Light, Dark, Aurora Violet
- Aurora gradient mesh avatars; branded provider logos as fallback
- Ctrl + Scroll wheel zoom in the mail reader
- Scroll-to-top button in mail list
- Unread-on-top ordering (configurable)
- Mark as read on open (configurable)
- Right-click context menus throughout (mail list, conversations, attachments, address fields, address book)
- Keyboard shortcut system with configurable presets
Infrastructure
- Encrypted credential vault (SQLCipher): all OAuth tokens and passwords are stored encrypted at rest; vault is unlocked with a master password on launch
- Auto-updater with silent background checks and passive install on Windows
- Address book with contact management and right-click actions