= app-tui == purpose Terminal UI built on the Tamboulib TUI framework that provides a real-time interactive dashboard for monitoring and controlling Correx sessions. == responsibilities * Display a live session list with status, workflow name, and background-update badges * Show workflow definitions available on the server for starting new sessions * Stream session lifecycle events (stage, tool, inference) in a scrollable event strip * Display tool diffs when tool executions complete with file modifications * Surface approval requests with tool name, tier, risk summary, and command preview * Accept approval decisions (approve / reject) and steering notes via keyboard input * Support session filtering by workflow name, input history navigation, and keyboard-driven UI modes == non-responsibilities * Does not contain any domain orchestration or event-sourcing logic * Does not persist any state — all state is in-memory and rebuilt on reconnect * Does not authenticate or encrypt connections * Does not support mouse interaction == key types === TuiState * **kind**: data class * **purpose**: Root application state. Contains `connection`, `sessions`, `input`, `provider` sub-states plus display metadata (cursor position, input buffer, history, snapshot phase flags, per-session cursors). === SessionsState * **kind**: data class * **purpose**: Holds the list of `SessionSummary` objects, filter text, session/workflow selection, and background update counter. === DisplayState * **kind**: enum * **purpose**: Tri-state enum (`IDLE`, `IN_SESSION`, `APPROVAL`) that drives which layout the TUI renders. === SessionSummary * **kind**: data class * **purpose**: Projected view of a session for display — ID, status, workflow, stage, tools, recent events, pending approval. === TuiWsClient * **kind**: class * **purpose**: Ktor WebSocket client that maintains a persistent global stream (`/stream`). Exposes cold `Flow` and `Flow` backed by `Channel.UNLIMITED`. Implements exponential backoff reconnection. === Action (sealed interface) * **kind**: sealed interface * **purpose**: Union of all user input and system events (navigation, input editing, approval, server messages, connection events). === KeyEvent (sealed class) * **kind**: sealed class * **purpose**: Normalized key events independent of the TUI backend (TambouiKeyEvent mapping). === RootReducer * **kind**: object * **purpose**: Top-level reducer that coordinates `SnapshotPhaseReducer` + four sub-reducers (`InputReducer`, `SessionsReducer`, `ConnectionReducer`, `ProviderReducer`). Handles cross-reducer state weaving (input history, approval dismissal, auto-enter on session start). === SnapshotPhaseReducer * **kind**: object * **purpose**: Manages the snapshot-to-live transition. Buffers event-bearing messages during snapshot replay; drains buffered events atomically when `SnapshotComplete` arrives; deduplicates live events by `sessionSequence`. === Effect (sealed interface) * **kind**: sealed interface * **purpose**: Side-effect descriptors produced by reducers — either `SendWs(ClientMessage)` or `Quit`. === EffectDispatcher * **kind**: class * **purpose**: Executes effects sequentially. Dispatches WS messages via `TuiWsClient.send()` and `Quit` via a callback that exits the TUI runner. == event flow *inbound:* `ServerMessage` frames from the global WebSocket stream, decoded by `TuiWsClient` and wrapped in `Action.ServerEventReceived`. Also connection lifecycle events (`Connected`, `Disconnected`, `RetryScheduled`). *outbound:* `ClientMessage` frames sent via `Effect.SendWs` — `StartSession`, `CancelSession`, `ApprovalResponse`. == integration points * `:apps:server` — imports `ServerMessage`, `ClientMessage`, `ApprovalDecision`, `PauseReason`, `WorkflowDto`, `ProviderHealthDto` * `:core:events` — imports `ApprovalRequestId`, `SessionId` * `:core:approvals` — imports `Tier` Tamboulib framework: `tamboui-tui`, `tamboui-widgets`, `tamboui-core`, `tamboui-jline3-backend`. == invariants * `ws.messages` and `ws.connection` are single-consumer cold flows — adding a second collector drops messages from one * Effects are dispatched sequentially (dispatchAll) — order matches `RootReducer` concatenation * `Effect.Quit` is always appended last by `RootReducer.reduce` * Snapshot phase is entered initially and on every `Disconnected`; buffered events are drained atomically at `SnapshotComplete` * Live event deduplication uses per-session `sessionSequence` cursor; gaps are warned and applied anyway * The TUI creates one WS connection (`/stream`) — per-session streams are not used == PlantUML diagram [plantuml, app-tui, "png"] ---- include::../../diagrams/app-tui.puml[] ---- == known issues * `StageToolManifest` arriving before `SessionStarted` is silently dropped (TODO RF-3) — the session won't exist in the list yet * Chat submission in `IN_SESSION` is blocked (returns empty effects) — router integration is deferred to Epic 14 * `InputHistory` limit is hardcoded to 50 entries per session * Event strip shows at most 5 events for the selected session * Session list is capped at 7 visible entries; overflow is shown as a count but not scrollable == open questions * Should the TUI support per-session WebSocket streams instead of relying solely on the global stream? * Should approval decisions support freeform steering note input that can exceed one line? * How should workflow picker handle very large numbers of available workflows (10+)?