Skip to content

v2.3.1 — Robustness & Shutdown Fixes

Choose a tag to compare

@haxllo haxllo released this 03 Jul 22:50
· 956 commits to master since this release

Resolves all 20 robustness audit issues plus 3 critical shutdown bugs — tray Quit now exits cleanly, arrow key navigation is ~20x faster, and startup code is hardened against poisoned locks.

Changelog (30 commits since v2.3.0)

Bug Fixes

  • Shutdown hang: fixed 3-part root cause preventing process exit after tray Quit — COM STA deadlock resolved by switching to MTA, FileWatcherHandle::drop made non-blocking with mem::forget, std::process::exit(0) added as safety net
  • COM init per invocation: moved CoInitializeEx to thread_local! for the persistent prefetch thread
  • Startup unwrap hardened: replaced 5 bare .unwrap() calls in runtime_loop.rs with unwrap_or_else for poisoned lock recovery
  • Escape handler duplicate: fixed pre-existing bug where search_session.clear(), search_worker.clear_session(), and try_recv drain were duplicated with inconsistent indentation
  • Indexer thread panic: indexer panic now handled gracefully instead of crashing main thread via .expect() on poisoned result slot
  • Stale pruner shutdown: added AtomicBool stop signal so the pruner thread exits cleanly on shutdown
  • File watcher shutdown: stop_file_watchers() called before worker thread join to prevent deadlock
  • Runtime worker panic guard: wrapped worker.run() in catch_unwind to prevent silent UI freeze
  • Blocking search reads: replaced blocking cached_items.read() with try_read() in search fallback paths
  • Icon prefetch thread accumulation: replaced per-call thread spawning with single persistent nex-icon-prefetch thread
  • last_touch unbounded growth: added clean_orphaned_touches() to prevent HashMap from growing beyond LRU capacity

Performance

  • Arrow key navigation: UiCommand::SelectChanged sends only {"selected": idx} (~20 bytes) instead of full state snapshot (~134KB); arrow keys now ~1ms instead of ~20ms
  • Icon delivery: dual PostWebMessageAsJson — lightweight state JSON first, icon data JSON second; state lock hold reduced from ~5ms to ~0.1ms
  • Lock contention: push_state() clones ShimState first, drops lock, then serializes outside lock
  • CSS compositor: removed redundant backdrop-filter: saturate(140%) that doubled compositor work
  • Row animation: moved @keyframes row-in to #body.initial-render .row to prevent 20 concurrent animations per keystroke
  • JS debounce: reduced from 80ms to 40ms for faster first keystroke response
  • Painted IPC: post("painted") only sent after show_pending = true, eliminating unnecessary IPC during typing
  • Personalization cache: 5-second TTL cache for SQLite personalization queries, eliminating ~1-2ms per search
  • Tantivy incremental sync: writer lock held only during fast write phase (~1ms) instead of full scan (~100ms)

Architecture

  • Persistent prefetch thread: single nex-icon-prefetch thread with shared work slot replaces per-call thread spawning
  • COM MTA: icon prefetch uses COINIT_MULTITHREADED instead of COINIT_APARTMENTTHREADED to prevent ExitProcess deadlock
  • catch_unwind on search worker: prevents search panic from killing the worker thread

Documentation

  • New docs/plans/robustness-audit.md with all 20 issues tracked and resolved
  • Updated docs/plans/audit-and-roadmap.md for WebView2 overlay
  • Updated AGENTS.md overlay architecture section
  • Updated docs/v2-verified-gaps.md with WebView2 status

Commit Log

  • 9a1b2d0 chore: commit remaining unstaged changes
  • d80c20a fix(robustness): harden startup unwrap calls, fix Escape handler duplicate
  • cdbffda fix(com): move COM init to thread_local for prefetch thread
  • 216b304 fix(shutdown): use mem::forget to truly prevent DirectoryWatcher::drop
  • 40a2baa fix(shutdown): resolve process hang on tray Quit (3-part fix)
  • 737bca9 fix(exit): force ExitProcess to terminate lingering background threads
  • bf3d4cb fix(shutdown): prevent process from lingering after tray Quit
  • 9dd6bbd fix(shutdown): prevent deadlock when stopping file watchers on quit
  • 0378740 fix(runtime): wrap worker thread in catch_unwind to prevent silent UI freeze
  • 09084f1 fix(pruner): add shutdown signal to stale pruner thread
  • 4c3f5da perf(js): reduce debounce from 80ms to 40ms
  • 403baa3 perf(search): add TTL cache for personalization boosts
  • 93bf193 fix(icons): clean orphaned last_touch entries after LRU eviction
  • b47033c fix(overlay): Show handler now calls push_state with show_pending=true
  • ba6563f fix(overlay): replace per-call prefetch threads with single persistent worker
  • 0fc2839 perf(overlay): only send post('painted') on first render after show
  • c8478df perf(css): only apply row-in animation on initial render
  • b50df8e perf(css): remove backdrop-filter saturate that doubles compositor work
  • 55f410b perf(tantivy): move full scan outside writer lock in incremental_sync_items
  • c4544b4 fix: replace unwrap() with unwrap_or_else for poison recovery
  • 63da456 fix(search): replace blocking cached_items.read() with try_read() in search paths
  • e816099 fix(overlay): handle indexer thread panic gracefully in indexing_progress.rs
  • 082fee9 fix(overlay): move all icon prefetch to background thread
  • 406257e feat(overlay): dual PostWebMessageAsJson icon delivery
  • 4402fd2 revert: icon protocol URLs back to inline base64 data URIs
  • c6e3029 fix: icon URL encoding round-trip breaks cache lookup
  • 6d4bee9 perf: serve icons via nexasset:// protocol instead of inline base64
  • 06a537f perf: lightweight selection update for arrow key navigation
  • 29f2061 code: remove stale FTS5 references from comments
  • 93de515 docs: generate project documentation

Binary