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:
b55a21a31e
resource: repo://src/main.rs
generated: { by: "claude-code", at: "2026-10-19T18:58:46.219Z" }
---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, so App needs no locking.
main (src/main.rs:28-51) runs on the multi-threaded tokio runtime
(#[tokio::main]) and:
Config::load and creates the data directory
(ensure_data_dir). See Configuration and data.history_db_path().tokio::sync::mpsc channels: one for AppEvent
and one for WaEvent.whatsapp::start(session_db_path, wa_tx).App from AppDeps { config, history, wa, events }.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.
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
src/main.rs:57-65): a tokio task that wraps each
WaEvent in AppEvent::Wa and stops when either channel side closes.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.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.App (AI completions, image download,
sends) hold a clone of the sender and report with AiReply / Sent.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, 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.