Skip to content

Theorem v1.5.8

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Sep 18:56
· 1 commit to main since this release

⚠️ Breaking Changes & Migration Notice

Important

Database, Highlight Migration & Breaking Changes:

  • Self-Healing Highlights & CFI Re-Anchoring: Older annotations created on prior builds or shifted EPUB spine formats (such as outdated DOM [w_...] assertions) will now automatically be re-anchored on access via high-speed in-document text scanning, smoothly navigating to the target passage and writing healed canonical CFIs back to local SQLite storage.
  • Hydration & Tombstone Integrity: Fixed an asynchronous startup race condition that previously caused book annotations to be quarantined as orphaned before book metadata finished hydrating. Sync merge now strictly enforces local and remote deletion tombstones, preventing purged books and annotations from resurrecting.
  • Virtualizer Layout Cache Invalidation: Grid, List, and Compact view mode caches are now decoupled; previous display issues with overlapping book cards or clipped titles on view switch are automatically cleared.
  • Desktop Dev Process: Development window closing has been separated from release behavior; pnpm dev:tauri now terminates cleanly on window close without hanging background watch processes.

Reading System & Highlights

  • Resilient highlight re-anchoring & self-healing — Highlights pointing to stale, modified, or migrated EPUB locations (such as outdated word-level [w_...] assertions or shifted spine chapters) now automatically fall back to microsecond DOM text search across the document, smoothly scroll to the exact passage, draw the highlight, and self-heal by writing the clean, valid CFI back to the library store and SQLite database.
  • In-reader annotation navigation — Unified annotation clicks directly with native location routing and forced overlayer redraws, eliminating stale text-range selection jump failures.
  • Deleted highlights persistence — Fixed native sync merge re-inserting annotations and books from stale doc entries after applying tombstones; local tombstones are now transmitted and respected across all merge operations.
  • Highlights hydration race condition — Prevented startup from treating annotations as orphaned when annotations loaded before book records, ensuring zero highlight loss on startup.

Library & UI/UX

  • Library & Shelves view mode switching — Fixed virtualizer row height cache invalidation when toggling between Grid, List, and Compact views, eliminating card overlaps, clipped titles, and broken spacing. Aligned Shelves grid column layout with the virtualizer column detection to prevent accidental row item wrapping.
  • Workbench & Bookmarks card redesign — Cards adaptively fit content with uniform gaps and text-measured heights; long passages collapse with "Show more", menus close on outside click or Escape, article highlights are listed properly, and Bookmarks shares the redesigned toolbar and layout.
  • Desktop dev watcher — Scoped Tauri window close prevention strictly to release builds, ensuring pnpm dev:tauri closes and restarts cleanly without hanging background processes.

Web & Performance Parity

  • Edge Immutable Caching (W1) — Configured public/_headers specifying max-age=31536000, immutable for /assets/*, *.wasm, /wasm/*, /pdfjs/wasm/*, and /fonts/*, with max-age=0, must-revalidate for /index.html (0ms repeat load from browser disk/memory cache).
  • Active Client WASM Hookup (W2) — Connected theorem-core.wasm into the web client so browser-mode searches use SIMD wasm_fuzzy_rank (nucleo-matcher) and wasm_markdown_to_html (pulldown-cmark) instead of JS regex fallbacks.
  • Web Worker Task Offloading (W3) — Offloaded WASM fuzzy ranking, Markdown rendering, and background EPUB unzipping to a dedicated Web Worker (core-worker.ts) using transferable ArrayBuffers, keeping the main UI thread at 120fps during inflation and search.
  • Origin Private File System (OPFS) (W4) — Streamed large book binaries directly into OPFS (navigator.storage.getDirectory()), avoiding IndexedDB clone overhead with zero-copy reads and transparent IDB fallback.

Reading System

  • PDF links and Theorem Lens — Citations, footnotes, TOC links and URLs in PDFs are clickable. Hovering an internal link shows a borderless preview of the destination rendered from the page; clicking it jumps to the exact spot, even on pages not loaded yet. Back/forward history (Alt+←/→, mouse buttons 4/5, Android back).
  • PDF smoothness — Wheel and pinch zoom preview with a transform and re-render once, anchored under the cursor; the initial fit no longer opens a third of a page down. Rendered pages live in a bounded LRU (6 pages / 24 MP on desktop, 3 / 8 MP on Android). Canvas and text layer render separately, so text selection survives scrolling and no longer blinks. On WebKitGTK the time blocked during a zoom dropped from 1483 ms to 136 ms.
  • PDF features — Page thumbnails (Navigation → Pages, rendered only when the viewer is idle), Present mode (F5: full screen, one page at a time), Print (Ctrl+P, desktop), embedded attachments listed in Book Info with Save, Define/Copy for selected text (same dictionary as EPUB), logical page labels (xii (12), go to A-3), match-case / whole-word search with F3 cycling, and document properties (producer, PDF version, page size, correctly parsed creation dates). Pages are always shown as the file draws them: the old dark/sepia filters, which also recoloured photos, are gone. The tool palette and View panel use the reader's square style. JPX, JBIG2 and CCITT images now decode; the pdf.js cmaps, fonts and wasm decoders ship at the right paths (they were missing from builds), with a post-build check.
  • EPUB — Selections highlight glyphs only, like PDF, instead of flooding margins. Only the visible chapter's highlights are drawn (one call instead of about 1,000 on open). Books are read by byte range, and every chapter and image is decompressed in Rust (epub_read_entry) instead of zip.js on the UI thread.
  • Fast scrolling — A fling or scrollbar drag renders entering pages at low resolution first (instead of leaving them white), then sharp when scrolling stops.
  • No UI-thread blocking from native calls — 64 Tauri commands (SQLite, PDF/EPUB reads, file reads, RSS and article fetches, metadata parsing) used to run on the window's main thread; they now run on a background pool. SQLite calls keep their order through a FIFO queue.
  • theorem-core WASM & Native Crate (R9) — Extracted pure computational algorithms (pulldown-cmark Markdown rendering, nucleo-matcher SIMD fuzzy matching, deterministic speech text normalization, and PKM note vault formatting) into an independent workspace crate theorem-core supporting both native compilation and wasm32.

Fixed

  • In-book search — Finds the literal text, every occurrence on every page, instead of "fuzzy" results made of scattered letters; PDF search uses the text pdf.js decodes. EPUB match positions stay correct around characters whose lowercase has a different length (e.g. "İ").
  • Library search — No more unrelated titles matched by scattered letters (only compact matches or word-start acronyms like "lotr"). Shelves use the same native search as the Library, results no longer flash, a failed search falls back instead of showing nothing, and deleted or renamed books no longer linger in the search index.
  • Rotated PDF pages — Pages a PDF marks as rotated (common in scans) were drawn sideways.
  • No lost data on quit — Pending writes (library, progress, reading time) are flushed to SQLite before the window closes, the app quits from the tray, or it goes to the background.
  • Sync after page turns — Reading progress syncs at most every 30 s instead of starting a full sync round two seconds after each page turn (a source of periodic stutters).
  • Statistics in local time — Reading days, streaks, goals and the daily reminder used UTC dates.
  • Reading speed — Measured over the whole time on a page instead of counting each page twice.
  • Vocabulary and annotation storage — Replaced the dual-write to JSON blobs with SQLite as the single source of truth on native platforms. State serialization drops full arrays from kv_store, eliminating JSON stringification overhead and preventing resurrected items during rehydration.
  • Sync merge in Rust — sqlite_merge_sync_entries directly applies incoming gossip updates and deletion tombstones for books, annotations, vocabulary, and RSS articles within an atomic SQLite transaction, returning granular diff reports of changed entity IDs so in-memory stores update incrementally without full-collection re-stringifications or IPC roundtrips.
  • Relational library store — Decoupled book metadata persistence from giant JSON blobs in kv_store on native platforms into individual book_metadata rows in SQLite. partialize excludes books from Zustand persistence payload, and updateProgress saves reading state directly via sqlite_update_book_progress without re-serializing the library or triggering FTS re-indexing on every page turn.
  • Vocabulary sync — Merges write only changed terms and propagate deletions.
  • RSS — Favorites are never aged out; other articles are kept for 30 days, newest 500. Feeds refresh four at a time. All Markdown is rendered by pulldown-cmark in Rust, with raw HTML and javascript: links neutralised.
  • Vault export — Only changed notes are rewritten; notes for removed books are deleted unless you edited them. Obsidian, Logseq, Minimalist, and Custom (Knap) presets with an integrated live template editor, syntax validation, and wikilink/callout filters; the native and fallback exporters write byte-identical notes and file names (shared golden tests).
  • Covers — Stored as raw image bytes (a third smaller than base64; converted on first start) and served from SQLite via theorem-cover:// without decoding, instead of loading every cover into memory at startup; synced as their own entries.
  • RSS sync — Each article syncs as its own entry, so changing one article no longer re-sends every article.
  • Audiobook encoder — libopus is now linked statically; release builds had been loading the system libopus.so.0.
  • Android build — Ported to jni 0.22 after the Dependabot bump.

Dependencies

  • zip 4 → 8, bzip2 0.4 → 0.6, jni 0.21 → 0.22, iroh-mdns-address-lookup; removed @mozilla/readability, markdown-it, @zip.js/zip.js (dev) and foliate's vendored fflate copy (npm fflate is used); one libopus build instead of two. CI now runs clippy for Android.

Performance

  • Fuzzy Search: Eliminated N×keys RegExp Allocations — In src/core/lib/search/fuzzy.ts, the word-boundary RegExp was previously constructed inside scoreFieldMatch for every book × every field on every keystroke. For a 1000-book library with 4 fields, that was 4000+ RegExp objects per keystroke. Introduced scoreFieldMatchFast which accepts a pre-compiled RegExp from the caller; rankByFuzzyQuery now compiles it once per query and passes it into the inner scorer, reducing per-search RegExp allocations to exactly 1.
  • Library Sort: Schwartzian Transform Eliminates Date Allocations — In src/features/library/filtering.ts, new Date() was called inside the sort comparator (O(n log n) calls = ~20,000 Date allocations per sort on a 1000-book library). Replaced with a Schwartzian transform: timestamps are pre-computed once before sorting into a decorated array, the sort operates on raw numbers, then the books are extracted back out. Zero transient Date allocations in the comparator.
  • Book Model Cache: Cleanup on Eviction — In src/features/reader/engines/foliate-engine.ts, the 2-entry LRU book model cache silently dropped evicted entries without calling any cleanup, leaving EPUB ZIP decompressor state and inflated chapter buffers alive (50–100MB leak per eviction). Cache now stores {book, cleanup} pairs and calls cleanup() → book.destroy?.() before evicting the oldest entry. Cache Map type narrowed from Map<string, any> to Map<string, {book: unknown; cleanup: () => void}>.
  • Reader.tsx: Remove Redundant useMemo Wrapping useShallow — useShallow already guarantees stable reference equality when values are shallowly equal. The additional useMemo wrapping its result added one extra object allocation and comparison per render. Removed; settings is now the direct useShallow result.
  • Annotations & Bookmarks: Eliminate Whole-Books Array Subscription — AnnotationsPage and BookmarksPage subscribed to the entire books array (useLibraryStore(s => s.books)), causing a re-render on every progress tick for any book in the library. Replaced with getBook (O(1) selector); each page now derives its title/book lookup only from the bookIds present in its own annotations/bookmarks via useMemo.
  • Vite Bundle: Vendor Chunk Splits for @tanstack, sonner, @radix-ui — These stable dependencies previously landed in the main app chunk, busting the browser cache on every app code change. Added tanstack and ui-vendors manual chunks to vite.config.ts so they get independent, long-lived cache entries.

Fixed

  • Library Multi-Select Repaired; Shelf Selection Mode Added (#109) — Selection checkboxes and selected rings now render in grid, list, and compact views; keyboard Enter/Space toggles in selecting mode; toolbar toggle exposes data-action="toggle-select-mode" so Ctrl+A works; selection lookups use a Set. New shelf-detail selection mode: Select All, Shift+click range select, and a shelf-aware bar (Remove from Shelf, Add to Shelf, Mark Read/Unread, Delete with confirm). New single-set batch store actions (addBooksToCollection, removeBooksFromCollection, markBooksCompleted, markBooksUnread, removeBooks) so batch confirms dismiss instantly.
  • Route Keep-Alive with Mount-on-First-Visit (#103) — Non-reader routes stay mounted behind hidden toggles with per-route mount-on-first-visit, so back-navigation is an instant class toggle preserving scroll position, filters, and virtualizer caches. The reader stays exclusive so engines unmount on exit.
  • Reader Open-Path Navigation Retry (#105) — Initial goTo runs with a 15s budget plus one retry instead of a single 6s timeout that false-positived under spine/CSS load.
  • RSS Renders All Entity Shapes (#107) — Article body prefers fullContent; the sanitizer iteratively decodes named, decimal, hex, and double-encoded entities (including mixed genuine+escaped payloads) before sanitize, with markdown detection running on decoded text. Rust article_epub detects namespaced/attributed markup (<p xmlns>) instead of requiring exact <p>/<div>, so reader EPUB conversion no longer escapes real markup into visible tags.
  • Updater Beta Channel (#106) — Pre-release builds fall back to a GitHub Releases prerelease lookup with semver comparison; new "Beta Available → View Beta Release" UI. Stable path unchanged.
  • IPC Access for Reader Windows (#102) — default.json capability now covers reader_* windows (previously only main, so every invoke in second windows was ACL-denied). Frontend memory trim routes through trim_memory with a 5s throttle.
  • Device-Local Vault Path Kept on Sync — mergeSettings preserves existing.vault like deviceSync, so a peer's empty path no longer clobbers this device's export folder.

Performance

  • Sync Bridge Persistent Identity Indexes — The docs subscriber rebuilt Maps/Sets and re-serialized every entity on each notification. Now a single pass with O(1) referential-identity fast paths; only changed entities stringify. Exact deletion semantics preserved.
  • Live Gossip Batching — Annotation/collection keys coalesce over 200ms into one merge + one setState; tombstone re-merges trail 500ms into an idle callback; merged-book lookup uses an index Map instead of per-item .find.
  • Persist Pipeline Off the set() Hot Path — New deferred JSON storage adapter coalesces bursts and stringifies in an idle callback, with memoized partialize skipping rebuilds when persisted slices are unchanged. Rehydrate, migrations, and hide/unload flushes preserved.
  • Absolute Virtual Rows Everywhere — Library, Shelves, Bookmarks, and Annotations use absolute translateY rows with no per-row measuring.
  • Shared Solid Scrollbar — One .scrollbar-solid utility (opaque thumb, reserved gutter) on all list surfaces.
  • Article Highlight Single Pass — One text-index walk plus binary-search lookups replaces a full tree walk per highlight.
  • Discover Indexed Title Checks — Shared WeakMap-cached title Set replaces per-card books scans.
  • Reader Navbar Composites Opaque — Dropped backdrop-blur-xl over its solid surface.

Download & install

Platform Download
Windows (x64) Theorem_1.5.8_x64-setup.exe
macOS (Apple Silicon) Theorem_1.5.8_aarch64.dmg
macOS (Intel) Theorem_1.5.8_x64.dmg
Linux (Debian/Ubuntu) Theorem_1.5.8_amd64.deb
Linux (any) Theorem_1.5.8_amd64.AppImage
Android (arm64, most phones) app-arm64-release.apk
Android (32-bit arm) app-arm-release.apk
Android (x86_64 emulator) app-x86_64-release.apk
Android (x86 emulator) app-x86-release.apk

Linux one-liner: curl -fsSL https://raw.githubusercontent.com/Fundaments-Work/Theorem/main/scripts/install-linux.sh | bash

Desktop builds auto-update in place (Settings → About → Check for Updates). Android updates are manual unless installed via F-Droid.