3 Waycast refactor
javif89 edited this page 2026-06-22 21:28:00 +00:00

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 waycast binary runs both the daemon thread and the IPC listener thread.
  • Single instance lock via fs2 + Unix socket IPC for the "show" command. Running waycast again 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-style remember pattern).
  • Three scanners are implemented: ApplicationScanner, FileScanner, ProjectScanner. All run concurrently via tokio::join! + spawn_blocking. File scanner uses the ignore crate with parallel walking.
  • Daemon runs scans on a 20-second interval.
  • App entry directory watching is wired up in main.rs using notify-debouncer-full. Detects changes to .desktop file directories and triggers a rescan via insert_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 use waycast_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) runs waycast-daemon, but that crate is a library, not a binary. Either add a main.rs to waycast-daemon or update the service to run waycast with a --daemon flag.
  • async move bug in watcher thread — In main.rs, the async move block 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_directories function exists but is only called for app dirs. Need to also watch project paths from config and trigger insert_of_kind for projects.
  • CLI commands are bare-bones — Only version is 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.

References