How it works
This page is for the curious — you don’t need any of it to use Shrike, but it explains why the app feels the way it does.
Local-first by construction
Section titled “Local-first by construction”The UI reads exclusively from a local SQLite store with an FTS5 full-text index. The network sync layer writes into that store asynchronously; the interface never reads from the network directly. That’s why scrolling, opening, and searching are instant, and why Shrike keeps working offline — the store is the source of truth the UI renders, and sync just keeps it fresh.
One mutation site
Section titled “One mutation site”Shrike follows the Elm architecture: state → message → update → view. There
is exactly one place where state changes — the update function. The
keymap and the command palette both
emit the same action type, which is why a keystroke and its palette entry behave
identically. Triage actions mutate local state immediately and enqueue a
background sync, with rollback on failure and a ⌘Z undo window.
Clean boundaries
Section titled “Clean boundaries”Two async boundaries keep the app decoupled and replaceable:
MailProvider— the seam to Gmail. The Gmail implementation does OAuth, then IMAP + SMTP overXOAUTH2, with incremental sync into the store.AiEngine— the seam to the on-device model. The embedded llama.cpp backend sits behind it, so the model and even the inference backend are decoupled from the rest of the app — which is exactly where the in-app model picker plugs in.
The calendar follows the same shape, with a CalendarProvider
trait and a Google Calendar adapter that shares the mail OAuth token.
The stack
Section titled “The stack”| Layer | Choice |
|---|---|
| Language | Rust |
| GUI | iced (wgpu → Metal) |
| Local store | SQLite (rusqlite, bundled) + FTS5 |
| Mail transport | IMAP + SMTP over OAuth XOAUTH2 |
| On-device AI | llama.cpp (Metal), GGUF models |
| Platform | macOS, Apple Silicon |