type: architecture title: Terminal UI rendering description: How src/ui.rs draws the connecting, QR pairing and main screens with ratatui, including the filtered list with global autoreply marker, message formatting, wrapping, the input box and the footer with key help and build version. tags: [ui, ratatui, tui, qr-code, rendering] sources:
6bddea306b
resource: repo://src/ui.rs
generated: { by: "claude-code", at: "2026-10-21T21:49:12.291Z" }
verified:src/ui.rs is a pure view: render(frame, &App) reads the
application state and fills a ratatui frame. It never mutates
App; the event loop calls it once per event.
All user-facing strings are Italian constants at the top of the file
(src/ui.rs:21-99).
render (src/ui.rs:107-114) dispatches on app.screen:
| Screen | Function | Content |
|---|---|---|
Connecting |
render_connecting |
"Connessione a WhatsApp in corso..." plus the status line, centered |
Pairing |
render_pairing |
Title, QR code, pairing steps ("WhatsApp > Impostazioni > Dispositivi collegati > Collega un dispositivo"), status |
Main |
render_main |
List pane, messages pane, input pane, footer |
render_centered (src/ui.rs:152-165) centers a block of lines vertically
and horizontally.
The QR payload comes from the WhatsApp client
(WaEvent::Qr). qr_to_text (src/ui.rs:192-201) encodes it with the
qrcode crate using Dense1x2 half-block characters and a quiet zone;
colors are inverted (dark modules drawn as empty cells) so the code scans on a
dark terminal background.
qr_block_lines (src/ui.rs:171-185) replaces the QR with a single line when:
PAIRING_TEXT_ROWS = 5, or
columns): "Terminale troppo piccolo per il QR: ingrandisci la finestra".render_main (src/ui.rs:207-222):
+---------------------------------+--------------------------------------+
| Chat [F2: Contatti] ●AI tutte | <name> ── AI: ON |
| Cerca: mar | 10:02 Alice: ciao |
| > Mario ●AI | 10:03 Tu: ciao! |
| Marta | 10:03 [AI] Tu: ... |
| (30%) +--------------------------------------+
| | Messaggio |
| | > typed text_ |
+---------------------------------+--------------------------------------+
<status> Tab:focus Enter:apri/invia F2:chat/contatti Canc:elimina F3:AI chat F4:AI tutte F10:esci v0.1.0 (e147fe6)
(The list title is a single line: base title, then the global marker, then the search text.)
src/ui.rs:242-261): the filtered chats or
contacts (App::visible_chats / visible_contacts) depending on
list_mode. The title (list_title, src/ui.rs:268-277) is
"Chat [F2: Contatti]" / "Contatti [F2: Chat]", followed — in chat mode
only — by ●AI tutte when the global autoreply is on, and by
Cerca: <filter> when a filter is typed. The selection is reversed with a
"> " marker and clamped by selected_index. Chats whose per-chat
autoreply is enabled show a green ●AI marker (chat_item,
src/ui.rs:291-298).src/ui.rs:312-348): title is
<name> ── AI: ON|OFF or " Nessuna chat aperta "; AI: ON is shown when
the open chat's flag or the global autoreply is on. All messages are
wrapped to the inner width and only the last rows that fit are kept, so the
newest messages are always visible (no scrolling).src/ui.rs:494-506): shows "> " + input; when
the text is wider than the box only the tail is shown (tail_fitting), and
the terminal cursor is placed after it while the input has focus.src/ui.rs:530-573): split into a left area with the
bold status text followed by the key help
Tab:focus Enter:apri/invia F2:chat/contatti Canc:elimina F3:AI chat F4:AI tutte F10:esci,
and a right area, exactly as wide as needed, with the dimmed build version
v<version> (<git hash>) (version_label, from BUILD_VERSION, see
Runtime and event loop). The help is dimmed,
except F4:AI tutte, which is drawn green while the global autoreply is on
(key_help_spans).The focused pane (list or input) has a yellow border (pane_block,
src/ui.rs:228-236).
format_message_line (src/ui.rs:379-399) renders a
stored message as HH:MM <sender>: <text> in
the local time zone; out-of-range timestamps show --:--
(format_clock_time, src/ui.rs:401-410).
sender_label (src/ui.rs:416-433) picks:
Tu for own messages, [AI] Tu when the message was generated by the
AI autoreply;@).Colors (message_style, src/ui.rs:367-373): AI messages cyan, own messages
green, incoming messages default.
Widths are measured in terminal cells through ratatui's Line::width, so
wide characters (CJK, emoji) count as two cells (src/ui.rs:443-455).
wrap_text (src/ui.rs:461-488) splits on \n, wraps each paragraph by
characters (not words), drops control characters and always emits at least
one row per paragraph.
Unit tests in src/ui.rs:575 onward cover the footer key help (F4
highlighted only when the global autoreply is on), the version label, sender
labels and fallbacks, clock
formatting in a fixed time zone, QR text generation and its error/missing
cases, wrapping, keeping the latest visible lines, tail_fitting, the
messages title, the list title (global marker and filter) and selection
clamping. See Unit tests.