Decision Records
ADR-0004: Pre-warmed picker window + global hotkey
Architecture Decision Record
- Status: accepted
- Date: 2026-06-14
- Deciders: project owner
Context
The picker must appear within single-digit milliseconds of the user pressing the hotkey — that responsiveness is a core product promise carried over from the original Windows app (which measured ~4ms fastest / ~13ms average). Creating an Avalonia window on each hotkey press cannot hit that; window realization is tens to hundreds of milliseconds (measured ~170ms cold here).
Decision
- Pre-warm the window. The picker
MainWindowis created once at startup and kept alive. The global hotkey callsShowPicker()which onlyShow()s and focuses the already-realized window. Escape / copy / focus-lossHide()it instead of closing, so it stays warm. - Run as a background app.
ShutdownMode = OnExplicitShutdown; a tray icon provides Show / Quit. Closing the picker never quits the process. - Global hotkey behind an interface.
IGlobalHotkeywith a Windows implementation (RegisterHotKeyon a message-pump thread; default Alt+Shift+V, since Win+V is reserved by the OS). macOS/Linux implementations are a later phase. - Single instance via a named mutex — the running instance owns the tray and hotkey.
- Instrument every show.
PerfLogappends the show-cycle duration toperf.log, so the engineering docs can chart real latency.
Consequences
- Measured (via
bench/run-bench.ps1): cold first show ~170ms (one-time window realization); a warm hidden→shown cycle is ~16ms (≈ one 60Hz frame). The synchronous render is ~1ms — the remainder is the OS show/activate/focus. This is on par with the original WPF app and well below the threshold of perceptible lag. - Hide-on-blur is the default; an env flag (
CLIPWELL_NO_AUTOHIDE) disables it for automated screenshot tests. - Still to do: paste-into-the-previously-focused-app after selection, open-at-cursor positioning, typed-item rendering (favicons/icons), pinned/sensitive filters, settings UI, and macOS/Linux hotkey + tray parity.