architecture.md
Language Markdown5.4 KB96 lines
# Architecture```+------------------------------ Cortex.app ---------------------------------+| || WebView (React) Rust (Tauri shell, src-tauri) || +-----------------------+ invoke +------------------------------+ || | main window | ------------> | commands.rs | || | sidebar, chat, | | thin wrappers | || | composer, picker, | <-- Channel - | (stream events) | || | settings, usage | | windows.rs | || +-----------------------+ | vibrancy, quick window, | || | quick window | | tray, global shortcut | || +-----------------------+ +--------------+---------------+ || no network, no keys, no paths | || +---------------v---------------+ || | cortex-core | || | provider: SSE, client, | || | errors, cancellation -----> DeepInfra or a| | chat: one turn end to end | | local server| | pricing, cost, estimate | || | storage: SQLite + FTS5 | || | attachments, export, secrets ----> OS keychain| +-------------------------------+ |+---------------------------------------------------------------------------+```## The split- **`crates/cortex-core`** holds all the logic and has no Tauri dependency, soit builds and tests in seconds on any machine. The whole of a chat turn(history into a request, streaming, stopping, usage, cost, saving) is onefunction, `chat::run_turn`, tested against the stand-in server.- **`src-tauri`** is the shell: commands that call into the core, the twowindows, the tray and the shortcut.- **`src/`** is the interface. It talks to Rust only through the `IpcClient`interface (`src/ipc/client.ts`), which has two implementations: the real one(`tauri.ts`) and an in-memory one with sample data (`mock/`), which thebrowser tests and the screenshots use.- **`crates/standin`** is a small OpenAI-compatible server whose behavior ispicked by model id (echo, slow, no usage, reasoning, vision, errors). Everyprovider path is tested against it, and so is the real app end to end.## Rules the code keeps- **The webview never sees a key or the network.** Provider calls happen inRust. The CSP allows no outside connections. Keys are write-only from theinterface: it can set, clear and ask whether one exists.- **The webview never supplies a file path.** File pickers are opened fromRust; pasted and dropped files arrive as bytes.- **Each window gets only the commands it needs.** `build.rs` declares everycommand, so each one needs a permission; the quick window's capabilitygrants 14 of them (`src-tauri/capabilities/`).- **No model call behind your back.** Chats are titled from their first line,not by asking a model. Failed requests are not retried automatically,because a retry is billed.- **Types cross the boundary once.** Rust structs generate their TypeScripttwins (`src/ipc/gen`, via ts-rs) when the tests run; CI fails if they arestale.## Streaming`provider/sse.rs` is a byte-level Server-Sent Events parser: a network chunkcan split a line, a `\r\n` pair or a UTF-8 character anywhere, and a propertytest feeds it every way of cutting the same stream. `provider/openai.rs` readseach chunk tolerantly (DeepInfra, Ollama and llama.cpp each differ a little)and keeps the last usage block seen, whether it arrives on the final contentchunk or in a chunk of its own. Deltas are batched about every 30 ms on the wayto the interface, which paints them once per frame.Stopping cancels a token held by the stream: the read loop drops the HTTPconnection at once (the stand-in test checks the server sees it go), and thepartial reply is saved as `cancelled`, with estimated tokens.## StorageOne SQLite file in the app's data folder. Tables for providers, conversations,messages (with tokens, prices and cost on each row), attachments, presets,settings and the model price list. FTS5 indexes messages and titles, kept instep by triggers; search input is quoted word by word, so nothing typed canbreak the query. A reply left `streaming` by a crash is marked interrupted atthe next start. Images are stored once per content hash, downscaled first to1568 px on the long edge, because vision models bill by size.## Windows- **Main:** created hidden and shown when the page has painted (no whiteflash). Closing it hides it; Quit quits. On macOS it has a transparentoverlay title bar over `Sidebar` vibrancy.- **Quick prompt:** a separate small page, created hidden at startup so itopens at once. A global shortcut (Cmd+Shift+Space by default) shows it on thescreen under the pointer; it hides on Esc or when it loses focus.- **Tray:** new chat, quick prompt, this month's spend, settings, quit.On Linux the window is opaque and the page paints its own soft backdrop behindthe glass. Windows is not built or tested.