--- type: architecture title: Runtime and event loop description: Process startup in src/main.rs, the tokio runtime, the two event channels, the input thread, the WhatsApp forwarder and the draw/recv TUI loop. tags: [runtime, tokio, event-loop, ratatui, crossterm] verified: - by: openwiki/0.7.2 at: 2026-10-19T18:58:46.219Z sources: - id: openwiki-source-b55a21a31ede1b56cd31a6a6 resource: repo://src/main.rs generated: { by: "claude-code", at: "2026-10-19T18:58:46.219Z" } --- # Runtime and event loop The binary (`wh`) is a single-process terminal app. `src/main.rs` wires the modules together and runs one serialised event loop: every state change goes through [`App::handle_event`](app-state.md), so `App` needs no locking. ## Startup sequence `main` (`src/main.rs:28-51`) runs on the multi-threaded tokio runtime (`#[tokio::main]`) and: 1. Loads the configuration with `Config::load` and creates the data directory (`ensure_data_dir`). See [Configuration and data](../operations/configuration-and-data.md). 2. Opens the SQLite [history store](../concepts/history-store.md) at `history_db_path()`. 3. Creates two unbounded `tokio::sync::mpsc` channels: one for `AppEvent` and one for `WaEvent`. 4. Starts the [WhatsApp client](../integrations/whatsapp-client.md) with `whatsapp::start(session_db_path, wa_tx)`. 5. Spawns the WhatsApp forwarder. 6. Builds `App` from `AppDeps { config, history, wa, events }`. 7. Spawns the input thread. 8. Runs the TUI until quit. Any failure before the TUI starts aborts with an `anyhow` context such as `cannot load configuration`, `cannot open history database` or `cannot start WhatsApp client`. ## Event sources ``` crossterm (blocking) whatsapp-rust bot tokio tasks in App "wh-input" thread Translator task (AI, vision, send) | | | | AppEvent::Key/Redraw | WaEvent | AiReply / Sent v v | app_tx <--------- WhatsApp forwarder (Wa(..)) | ^ | +-----------------------------------------------------+ | app_rx --> event_loop: draw ui::render -> recv -> app.handle_event ``` - **WhatsApp forwarder** (`src/main.rs:57-65`): a tokio task that wraps each `WaEvent` in `AppEvent::Wa` and stops when either channel side closes. - **Input thread** (`src/main.rs:71-102`): a named OS thread (`wh-input`) because `crossterm::event::read` blocks. On a read error it sends `WaEvent::Error("terminal input error: …")` and stops instead of spinning. - **Event mapping** (`src/main.rs:109-115`): only `KeyEventKind::Press` keys become `AppEvent::Key` (Windows also emits release and repeat events); `Resize` becomes `AppEvent::Redraw`; everything else is dropped. - **Background tasks** spawned by `App` (AI completions, image download, sends) hold a clone of the sender and report with `AiReply` / `Sent`. ## TUI loop `run_tui` (`src/main.rs:121-126`) calls `ratatui::init`, runs the loop and always calls `ratatui::restore`, so raw mode and the alternate screen are left even on errors. `event_loop` (`src/main.rs:132-147`) repeats while `!app.should_quit`: draw a frame with [`ui::render`](terminal-ui.md), await the next event, and pass it to `app.handle_event`. The loop also ends cleanly when the event channel closes. Draw errors and fatal `handle_event` errors end the process. Because rendering happens only after an event, the screen is redrawn on key presses, resizes, WhatsApp events and background task results — there is no timer tick.