// project · davidcast
A keyboard-first launcher for macOS. ⌥ Space anywhere, type, hit Enter. Apps, snippets, quicklinks, files, running Claude CLI agents, Vite dev servers, Docker containers, clipboard history, inline math — one fuzzy search across all of them, store on disk as plain JSON, ranked by what you actually use. ⌃⌥ I launches iTerm directly because the analytics said you wanted that.
Note: this page is an AI-generated summary of the project. The product, the code, and the data behind it are real — the prose isn't hand-written.
// why
Love Raycast, just wanted a simpler version with more customization — and an excuse to play with Tauri. Raycast's data lives in an encrypted SQLite database you can't script, diff, or sync across tools. davidcast keeps everything as readable JSON from day one, and the data model is sync-shaped (UUIDv7, updated_at, tombstones, rev) so the cloud story can be bolted on without a migration. Vibed it in a weekend.
// look


// theming
Themes aren't a colour-swap — they're full visual identities. Each one ships colour tokens, font family, background gradient, badge pills and keyboard-hint typography, all wired through CSS custom properties on the document root. Pick themes.switch and arrow through them — every row live-previews on hover so you can scrub the look without committing. A few themes ship bespoke chrome on top of the tokens — LCARS gets a chunky orange "1701-D" endcap; Pokémon ships its own bitmap dialog box.

Custom themes are drop-in JSON. Anything you save to ~/Library/Application Support/davidcast/themes/*.json shows up in the picker on next launch — same shape as the built-ins:
{
"id": "midnight",
"name": "Midnight",
"tokens": {
"bg": "#0F0E2E",
"bg-solid": "#0F0E2E",
"fg": "#E8E8EA",
"fg-dim": "#9A98C8",
"border": "#2A2848",
"accent": "#818CF8",
"font-family": "'JetBrains Mono', monospace",
"font-family-mono": "'JetBrains Mono', monospace"
}
}// plugins
Each kind of palette entry is its own Rust module that decides what to surface and what happens on ↵. They all feed the same fuzzy index and the same recents ranking, so "chrome", "ghs", ":img screenshot", "logs nginx" all work without thinking about which plugin owns the answer.
// smart ranking
The list adapts to what you do. With an empty query you see your most-used items first (recents map, capped at 24, in localStorage), then by kind priority — apps before your own items, before the live system plugins. As soon as you start typing, Fuse.js gets a -0.4 bonus for any item whose name or keyword starts with what you typed, and a softer -0.18 bonus for items in your recents — so typing i lands on iTerm, not on a fuzzy mid-string match.
Every action you take is appended (locally, no network) to analytics.jsonl with a session id, dwell time, query at execute, result count, and outcome — so the ranker can keep getting smarter, and so you can grep your own usage when you want to. The log is also load-bearing for product decisions: v0.2.6 added a dedicated ⌃⌥ I iTerm hotkey because iTerm was 40 % of every execute, and the inline calculator landed because no_results kept logging things like "4x4 =" and "20% out of 100". Same release also fixed a search bug emoji-prefixed commands introduced — "🎉 Throw Confetti" gets a normalized search alias so typing confetti actually finds it.
// the data layer
Everything you create is a JSON file you can cat, diff, version, sync. Atomic writes (write-temp-then-rename), tombstones instead of hard deletes, monotonic rev per item — the same shape a sync engine would want, sitting on disk waiting.
~/Library/Application Support/davidcast/
config.json # workspaces + active id + plugin toggles + theme
workspaces/
<workspace-id>/
snippets.json # [{ id, name, text, keyword, rev, updated_at, deleted }]
quicklinks.json # [{ id, name, url, open_in, rev, updated_at, deleted }]
sync_state.json # phase 2
themes/
*.json # any extra themes you drop in (built-ins are baked in)
analytics.jsonl # append-only event log, one JSON per lineWorkspace membership is implicit in the file path — items don't carry a workspace field, so moving an item between spaces is a file move. The Cloudflare sync target slots in over the same fields when phase 2 ships.
analytics.jsonl is the same shape: {ts, session_id, kind, data}. Three event kinds today — open, execute (with kind, name, success, duration, query, result count, dwell), and no_results (debounced). The new Show Analytics command rolls the file up into top queries, top items, kind breakdown, daily-opens sparkline, success rate, and average dwell — entirely client-side, never uploaded.
// stack
Single Tauri app, two webviews — the palette and the preferences window — share one bundle and one Rust core. Activation policy is Accessory: menu-bar only, no dock icon, hides on blur so the hotkey feels instant.
// status
// work together
Like what you see? I build products like this end-to-end — idea to shipped, front-end to infra. Hiring, have an idea you want made, or just want to poke at what I built here? Drop me a line — I reply.