Repository navigation
v2.3.1 — Robustness & Shutdown Fixes
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::dropmade non-blocking withmem::forget,std::process::exit(0)added as safety net - COM init per invocation: moved
CoInitializeExtothread_local!for the persistent prefetch thread - Startup unwrap hardened: replaced 5 bare
.unwrap()calls inruntime_loop.rswithunwrap_or_elsefor poisoned lock recovery - Escape handler duplicate: fixed pre-existing bug where
search_session.clear(),search_worker.clear_session(), andtry_recvdrain 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
AtomicBoolstop 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()incatch_unwindto prevent silent UI freeze - Blocking search reads: replaced blocking
cached_items.read()withtry_read()in search fallback paths - Icon prefetch thread accumulation: replaced per-call thread spawning with single persistent
nex-icon-prefetchthread - last_touch unbounded growth: added
clean_orphaned_touches()to preventHashMapfrom growing beyond LRU capacity
Performance
- Arrow key navigation:
UiCommand::SelectChangedsends 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()clonesShimStatefirst, drops lock, then serializes outside lock - CSS compositor: removed redundant
backdrop-filter: saturate(140%)that doubled compositor work - Row animation: moved
@keyframes row-into#body.initial-render .rowto prevent 20 concurrent animations per keystroke - JS debounce: reduced from 80ms to 40ms for faster first keystroke response
- Painted IPC:
post("painted")only sent aftershow_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-prefetchthread with shared work slot replaces per-call thread spawning - COM MTA: icon prefetch uses
COINIT_MULTITHREADEDinstead ofCOINIT_APARTMENTTHREADEDto preventExitProcessdeadlock - catch_unwind on search worker: prevents search panic from killing the worker thread
Documentation
- New
docs/plans/robustness-audit.mdwith all 20 issues tracked and resolved - Updated
docs/plans/audit-and-roadmap.mdfor WebView2 overlay - Updated
AGENTS.mdoverlay architecture section - Updated
docs/v2-verified-gaps.mdwith WebView2 status
Commit Log
9a1b2d0chore: commit remaining unstaged changesd80c20afix(robustness): harden startup unwrap calls, fix Escape handler duplicatecdbffdafix(com): move COM init to thread_local for prefetch thread216b304fix(shutdown): use mem::forget to truly prevent DirectoryWatcher::drop40a2baafix(shutdown): resolve process hang on tray Quit (3-part fix)737bca9fix(exit): force ExitProcess to terminate lingering background threadsbf3d4cbfix(shutdown): prevent process from lingering after tray Quit9dd6bbdfix(shutdown): prevent deadlock when stopping file watchers on quit0378740fix(runtime): wrap worker thread in catch_unwind to prevent silent UI freeze09084f1fix(pruner): add shutdown signal to stale pruner thread4c3f5daperf(js): reduce debounce from 80ms to 40ms403baa3perf(search): add TTL cache for personalization boosts93bf193fix(icons): clean orphaned last_touch entries after LRU evictionb47033cfix(overlay): Show handler now calls push_state with show_pending=trueba6563ffix(overlay): replace per-call prefetch threads with single persistent worker0fc2839perf(overlay): only send post('painted') on first render after showc8478dfperf(css): only apply row-in animation on initial renderb50df8eperf(css): remove backdrop-filter saturate that doubles compositor work55f410bperf(tantivy): move full scan outside writer lock in incremental_sync_itemsc4544b4fix: replace unwrap() with unwrap_or_else for poison recovery63da456fix(search): replace blocking cached_items.read() with try_read() in search pathse816099fix(overlay): handle indexer thread panic gracefully in indexing_progress.rs082fee9fix(overlay): move all icon prefetch to background thread406257efeat(overlay): dual PostWebMessageAsJson icon delivery4402fd2revert: icon protocol URLs back to inline base64 data URIsc6e3029fix: icon URL encoding round-trip breaks cache lookup6d4bee9perf: serve icons via nexasset:// protocol instead of inline base6406a537fperf: lightweight selection update for arrow key navigation29f2061code: remove stale FTS5 references from comments93de515docs: generate project documentation