Skip to content

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:

  1. Polling (state lists)ListWorkers, ListConnections, and GetAllState are called on a configurable interval (default 2s). The interval is set on the App.config.refresh_interval_sec attribute and can be changed at runtime with Ctrl+R to force-refresh.

  2. Streaming (events)Gateway.Subscribe(services, topics, event_types) is opened once at context start and consumed in a background task. Events append to a RichLog widget. 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

  1. virtufin-tui is invoked (from uvx, pip install, or local).
  2. app.main() parses --config and --context flags.
  3. config.load_config() reads contexts.toml and applies env-var overrides.
  4. 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.
  5. Otherwise, the TUI opens the dashboard for the active context.
  6. On q (quit), the TUI closes the gRPC channel, cancels background tasks, and exits 0.