Architecture¶
Data flow¶
┌─────────────────────┐ ┌─────────────────────┐
│ virtufin-tui │ │ virtufin-api │
│ (your laptop) │ ◄── gRPC :5002 ──► (cluster) │
│ │ │ │
│ ┌───────────────┐ │ │ - Gateway.* RPCs │
│ │ Textual App │ │ │ - State.* RPCs │
│ └──────┬────────┘ │ │ - Pubsub.* RPCs │
│ │ │ │ │
│ ┌──────▼────────┐ │ │ (talks to Dapr │
│ │ virtufin.api │ │ │ pubsub + state │
│ │ Python client │ │ │ internally) │
│ └──────┬────────┘ │ │ │
│ │ │ │ │
│ ┌──────▼────────┐ │ └─────────────────────┘
│ │ Polling + │ │
│ │ Subscribe │ │
│ │ background │ │
│ │ workers │ │
│ └───────────────┘ │
└─────────────────────┘
Components¶
1. Textual App (src/virtufin_tui/app.py)¶
The root App subclass. Holds a single TuiConfig and binds the global
keybindings. The default compose() produces a placeholder body; subsequent tasks
add the 3-panel dashboard, detail popovers, and the event stream widget.
2. Config loader (src/virtufin_tui/config.py)¶
Reads contexts.toml from the OS-appropriate user config path
(platformdirs.user_config_dir). Applies VIRTUFIN_TUI_API_HOST /
VIRTUFIN_TUI_API_PORT / VIRTUFIN_TUI_CONTEXT env-var overrides. Returns a
TuiConfig with the list of contexts and the active one.
3. Client wrapper (src/virtufin_tui/client.py — planned)¶
Thin async wrapper around virtufin.api.client.ApiClient that exposes the
operations the TUI needs as plain async methods (no Textual dependencies). This
is what the screens call. Lets the screens be tested with stub clients.
4. Screens (src/virtufin_tui/screens/*.py)¶
Each screen is a Textual.Screen subclass. The dashboard is the default; the
contexts picker is pushed when the user presses c. Detail popovers are
pushed modally.
5. Widgets (src/virtufin_tui/widgets/*.py)¶
DataTable-backed widgets for the workers and connections panels, and a
RichLog-backed widget for the event stream. All widgets are pure
presentational; they take their data from a DataSource injected by the parent
screen.
Real-time updates¶
The TUI uses two real-time paths:
-
Polling (state lists) —
ListWorkers,ListConnections, andGetAllStateare called on a configurable interval (default 2s). The interval is set on theApp.config.refresh_interval_secattribute and can be changed at runtime withCtrl+Rto force-refresh. -
Streaming (events) —
Gateway.Subscribe(services, topics, event_types)is opened once at context start and consumed in a background task. Events append to aRichLogwidget. On context switch the old subscription is cancelled and a new one is opened.
Cancellation / reconnect¶
gRPC channels are wrapped in a grpc.aio.insecure_channel (or
secure_channel if tls: true). On disconnect, the TUI retries with
exponential backoff (1s, 2s, 4s, 8s, 16s, 30s cap) and shows a
"reconnecting..." status. In-flight poll tasks are cancelled on reconnect.
Lifecycle¶
virtufin-tuiis invoked (fromuvx,pip install, or local).app.main()parses--configand--contextflags.config.load_config()readscontexts.tomland applies env-var overrides.- If no contexts configured, the TUI opens the contexts picker and prompts the user to add one. On save, the TUI restarts with the new active context.
- Otherwise, the TUI opens the dashboard for the active context.
- On
q(quit), the TUI closes the gRPC channel, cancels background tasks, and exits 0.