Theorem v1.0.6
Pre-release
Pre-release
Added
- Keyboard shortcuts (global + reader-scoped) —
Ctrl+1–7navigate between pages,Ctrl+,for Settings,Ctrl+Ffor search,Ctrl+Bfor sidebar toggle,?for shortcuts help. Reader shortcuts:Ctrl+Dbookmark,Ctrl+TTOC,Ctrl+Ssettings,Ctrl+Aannotations,F11fullscreen. Shortcuts reference available in Settings → Shortcuts tab. - Auto-advance immersion reading — When TTS finishes reading a page in immersion mode, it automatically turns to the next page and starts speaking. Chains continuously until the book ends or the user stops playback.
- Bookmarks page redesign — Bookmarks now match the AnnotationCard design language with proper borders, MoreVertical dropdown menu, and serif blockquote styling.
- PDF paged navigation mode — Scroll-snap based paged mode for PDFs (engine support maintained, UI toggle deferred).
Changed
- TTS: removed Kokoro/ONNX neural engine (~52 MB binary saved) — Replaced with native OS speech:
android.speech.tts.TextToSpeechon Android,say/NSSpeechSynthesizer on macOS,spd-say/espeak-ngon Linux, PowerShell/SAPI on Windows. Zero Rust dependencies, zero model download. Audio now starts instantly — no model warmup delay. - TTS: real pause/resume — Pause now stops at the estimated word position (158 wpm audiobook rate, self-calibrating). Resume continues from where speech was paused, not the beginning of the section.
- TTS: disabled on web — Web Speech API support was janky. TTS is now Tauri-only (Android + desktop). Settings show "Not available in web browser" on web.
- Sync: event-driven, not timer-based — Sync now uses dirty-tracking across all three layers (JS orchestrator, Rust background loop, sync daemon). Mutation-triggered sync fires within seconds; periodic syncs become no-ops when nothing changed. The Rust 5-minute loop checks a data version counter and only initiates sync when data actually changed.
- Sync: autoSyncEnabled toggle respected — The Rust background sync loop now stops when auto-sync is turned off. Previously it ran regardless of the toggle setting.
- Sync: Android WorkManager data loss fixed — The JNI standalone sync round (WorkManager) now persists incoming peer data to
sync-incoming-cache.jsonbefore its ephemeral runtime exits. On next app boot,get_incoming_sync_dataloads and merges this cache. - Bookmarks page redesigned to match AnnotationCard design language.
Fixed
- RSS duplicate subscriptions —
useRssStore.addFeedandrefreshFeednow normalize feed and article URLs (lowercase + trailing-slash strip) before comparison. - RSS remove feed UX — Replaced dropdown action menu with direct hover-triggered trash button.
- RSS duplicate images — Article card summaries now hide inline
<img>and<figure>elements to prevent duplicate image display. - Shelf search and context menus — ShelfDetail uses shared
MemoizedBookCard,BookInfoModal, and context menu. Bulk "Add to Shelf" fixed. - Android TTS crash —
TextToSpeechconstructor must run on UI thread. All TTS operations now go throughHandler(Looper.getMainLooper()).post(). - TTS reads wrong page —
getVisibleTextForTts()was returning entire section text instead of visible portion. Fixed to usevisibleRange.cloneContents(). - Settings page re-render — Extracted
StorageTabas memo component, removed heavy subscriptions from parent. - Reader remount on navigation — Removed
key={currentRoute}fromRouteErrorBoundarythat forced full remount on every route change.
Performance
- Binary size: ~83 MB → ~28 MB — Removed Kokoro/ONNX TTS stack (52 MB). Added
panic = "abort",strip = "symbols". Removed 12 TTS crates, 3 Rust source files. - Event-driven sync — All three sync layers use dirty-tracking. No more wasted manifest builds + SHA-256 hashing + HTTP round-trips when data unchanged. Mutation-triggered sync debounces to 5 seconds.
- Virtual scrolling (all list pages) — Library, Shelves, Annotations, Bookmarks, Vocabulary now use
@tanstack/react-virtualwith padding-based approach. DOM node count reduced by 85-98% for large collections. Overscan: 3 rows. - Background cover hydration —
coversHydrated=trueset immediately on rehydrate. Covers hydrate incrementally in batches of 48, unblocking the UI. - WebP cover downsampling — Covers resized to 200px, saved as WebP (quality 0.75), decoded asynchronously.
- LRU-capped caches — coverCache=100, thumbnailCache=200, materializedPathCache=500, blobCache=3. Persist storage skips values >500KB.
- PDF zoom rescaling — ImageBitmap cache for GPU-scaled instant zoom feedback. DOM window = canvas window + 4 pages. Search capped at 500 pages.
- Barrel import elimination — All imports now use direct module paths instead of
src/core/index.ts, enabling tree-shaking. - Zustand selector anti-patterns — Individual selectors instead of destructuring in ContextMenu, ReaderBookmarks, and other hot-path components.
- Shared context menu portal — Single global
ContextMenuRootreplaces per-card portals. - Fuse index caching —
WeakMap<Book[], Fuse>caches search index. Debounced search (250ms). - Navigation fix — Removed
key={currentRoute}from RouteErrorBoundary (prevented full remount on route change). - DailyActivity pruning — Capped to 365 days on rehydrate (was unbounded).
- DeletionTombstone GC — Garbage-collected on store rehydrate (was only during sync).