runtime-and-event-loop.md 3.6 KB


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-b55a21a31e 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, 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.
  2. Opens the SQLite history store at history_db_path().
  3. Creates two unbounded tokio::sync::mpsc channels: one for AppEvent and one for WaEvent.
  4. Starts the WhatsApp client 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, 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.