Context
The initial implementation of waycast worked fine, but the simple and naive design presented some issues:
- Every app, file and project got scanned on app start. While a simple approach, this means that sometimes waycast would take forever to update.
- I had to do a weird workaround for steam specifically: Since the app would just exit after selecting what you wanted to launch, steam wouldn't start sometimes. I just added a one second timeout where the UI would hide but waycast would just "wait".
- Sometimes waycast would take a while to start. I don't know why but I assume it's because the first initial scan would take a bit.
The Refactor
Daemon Architecture
Instead of waycast being a monolithic app and doing the scanning + app launching, I switched to a daemon + UI architecture.
The daemon runs in the background and does the file scanning + app launching. When the daemon
is running and you run the waycast command again, it sends a signal to the daemon to launch
the UI.
Async
The initial version of waycast used no async to keep it simple. Now we use the tokio runtime so we can run scans and other tasks concurrently. This was also necessary to use SQLX for the database.
Database (SQLite)
We now use SQLite as a data store. On every scan, the database gets updated with all the apps, projects, and files we scan. This gives us more flexibility since we now have a data store that we can query, as opposed to just holding everything in memory.
Additionally, if the daemon has a failure when scanning, we can still show data in the UI, even if stale.
The original version of waycast used redb as a key value store for caching. Since we now
have SQLite, we just have a cache table instead. No need for a whole separate dependency
for just story key:value pairs.
Logging
We're now using the tracing library for logging. This is a standard in rust. Since we now have a background process we need better visibility into the daemon's operations.
Current State
The refactor is roughly 80% complete. The core architecture is working end-to-end.
What's done
- Daemon + UI architecture is implemented and works. Single
waycastbinary runs both the daemon thread and the IPC listener thread. - Single instance lock via
fs2+ Unix socket IPC for the "show" command. Runningwaycastagain sends a message to the existing daemon to launch the UI. - Database layer (
waycast-data) is complete: SQLite via SQLX with WAL mode, connection pooling, migrations, repository pattern (LauncherItemRepository+CacheRepository), FTS5 search index with triggers, staging table pattern for safe atomic updates, and a cache table with TTL (Laravel-stylerememberpattern). - Three scanners are implemented:
ApplicationScanner,FileScanner,ProjectScanner. All run concurrently viatokio::join!+spawn_blocking. File scanner uses theignorecrate with parallel walking. - Daemon runs scans on a 20-second interval.
- App entry directory watching is wired up in
main.rsusingnotify-debouncer-full. Detects changes to.desktopfile directories and triggers a rescan viainsert_of_kind. - UI is functional: Iced-based with
iced-layershell, hybrid search (FTS for files, nucleo fuzzy matcher for apps/projects), icon resolution with caching (SVG + raster), keyboard navigation, math expression evaluation in the search bar, item execution for all three types. - Config (
waycast-config) works: TOML config file + env var overrides, dev mode detection, XDG-compliant directory resolution. - Nix flake builds. Home-manager module exists with systemd service definition.
- Desktop notification on daemon startup.
What needs doing
Blockers for production
- Hardcoded database path — Both the daemon (
waycast-daemon/src/lib.rs) and UI (waycast-ui/src/app.rs) use"waycast.db"(relative path). Needs to usewaycast_config::data_dir()to put it on the XDG data path in production. - Systemd service references non-existent binary — The home-manager module
(
modules/home-manager/waycast.nix) runswaycast-daemon, but that crate is a library, not a binary. Either add amain.rstowaycast-daemonor update the service to runwaycastwith a--daemonflag. async movebug in watcher thread — Inmain.rs, theasync moveblock moves the tokio runtime into the closure, so only the first file change event gets processed. Subsequent events will panic.
Feature gaps
- Project directory watching not wired up — The
watch_directoriesfunction exists but is only called for app dirs. Need to also watch project paths from config and triggerinsert_of_kindfor projects. - CLI commands are bare-bones — Only
versionis implemented. Missing:cache clear,diagnose,db reset,rescan,show,status. - Icon resolver needs a guaranteed fallback — The UI code can panic if the vscode fallback icon isn't in the handle map. Need a proper icon resolver with a default that is guaranteed to exist.
Nice-to-haves
- Search request sequencing to prevent out-of-order results when typing fast.
- Crash restart notification (systemd restarts, but user isn't notified).
- Parallel project scanning when multiple dirs are configured.