= 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<ServerMessage>` and `Flow<ConnectionEvent>` 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+)?
