1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! P5-4 (COMPOSABLE-HARNESS-DESIGN.md §2 module 30 `tui`; §1.9 recorded
//! deviation; §2.1 `tools.question`/`permissions.approvals`(ask-UI) →
//! `tui`|`server`): the full-screen interactive TUI, AND — because §2.1
//! names it as the interactive surface three EARLIER phases explicitly
//! deferred here — the home for the three handlers that close those
//! deferred chains:
//!
//! 1. **P5-1's approval ask-UI** — [`handlers::TuiApprovalHandler`]
//! implements `crate::permissions::PermissionsApprovalHandler`.
//! 2. **P5-2's elicitation prompt** (+ OAuth device-code display) —
//! [`handlers::TuiElicitationHandler`] implements
//! `crate::mcp::McpElicitationHandler`;
//! [`bridge::PendingOAuthDisplay`] carries the device-code modal push.
//! 3. **P5-3's parent-answerable child approvals** —
//! [`handlers::TuiChildApprovalHandler`], installed via
//! `crate::agent::Agent::set_child_approval_handler_factory` (P5-4's own
//! new seam — see that method's doc comment for why this can only ever
//! grant what the rule engine already routed to a prompt, never
//! escalate past a `Deny`).
//!
//! ## Two layers (why this crate only has ONE of them)
//!
//! A real terminal can't be driven headlessly in a unit test — so this
//! module is split in two, and only the TESTABLE half lives here:
//!
//! - **The view-model core** ([`state`], [`key`], [`keymap`], [`theme`],
//! [`history`], [`bridge`], [`handlers`]) — a pure `handle_key(KeyEvent)
//! -> Vec<Action>` / `apply(Action)` state machine plus the three
//! interactive handlers above. Zero terminal-library dependency (no
//! `ratatui`/`crossterm` in `crates/harness`'s `Cargo.toml` at all — see
//! [`key::KeyEvent`]'s doc comment) — every interaction in this crate is
//! unit-testable by constructing a [`key::KeyEvent`] by hand.
//! - **The render + event-loop layer** lives in `crates/cli/src/tui/` (a
//! binary-crate concern: `ratatui` + `crossterm`, an alternate-screen
//! terminal, real keyboard/paste input). It translates real
//! `crossterm::event::KeyEvent`s into [`key::KeyEvent`], drives
//! [`state::TuiState`], and renders the result — see that module's own
//! doc comment for the render loop and its `TestBackend` smoke test.
//!
//! ## Activation
//!
//! `Config::tui_enabled` (`capabilities.tui.enabled`, default `false`) is
//! the master gate — `crates/cli`'s `chat()` runs the pre-P5-4 rustyline
//! REPL loop byte-for-byte when it's off, or when stdin/stdout/stderr
//! aren't all a real tty (`--print`, a piped/non-interactive invocation,
//! or any headless test harness) — see [`should_activate`].
//!
//! ## Shippable-complete vs honestly-staged
//!
//! - **Shippable-complete:** the view-model core (composer editing,
//! scrollback, streaming accumulation, the three interactive-handler
//! modals, theme toggle, configurable global keybindings, cross-session
//! Ctrl+R prompt-history search, BASIC vim emulation), the render/
//! event-loop layer, image-paste passthrough — a placeholder token +
//! [`state::TuiState::pending_images`], drained by `crates/cli`'s
//! render loop via [`state::TuiState::take_pending_images`] and routed
//! into the turn as real multimodal content (`Agent::send_with_images`)
//! — external-`$EDITOR` invocation.
//! - **Honestly staged, not half-built:**
//! - FULL vim emulation (registers, visual mode, `.`-repeat, counts,
//! `dd`/`yy`/`p` as real two-keystroke commands rather than the
//! single-keystroke approximation
//! [`state::TuiState::handle_key`]'s vim-normal-mode match
//! implements) — see [`state::VimMode`]'s doc comment.
//! - OAuth device-code display (`bridge::TuiBridge::oauth_sender` /
//! [`bridge::PendingOAuthDisplay`] / `Action::ShowOAuthModal`): the
//! plumbing is complete and tested end-to-end (push → modal → render
//! → dismiss), but `crates/cli` has no call site that ever SENDS into
//! it — `attach_mcp`'s OAuth handling (`resolve_oauth_header`) only
//! ever consults an ALREADY-stored, possibly-refreshed token; a fresh
//! device-code flow (`mcp_oauth::run_device_flow`) only ever runs via
//! the separate `supercode mcp login <name>` subcommand, which never
//! activates the TUI. So a TUI session currently never has a moment
//! where a device code needs displaying at all — wiring one in would
//! mean teaching `attach_mcp` to trigger a live, session-startup-
//! blocking device-code flow when no cached token exists, a real
//! interactive-flow design decision (timeout? cancel-and-continue
//! without the server? non-interactive callers?) out of scope for
//! this module's own render-loop-and-handlers job. Kept (not removed)
//! as the ready seam for whichever future unit makes that call —
//! zero cost while unused, the same "installing a handler alone
//! changes nothing" posture every other optional seam in this crate
//! has. Cited as a follow-up for this module, not a broken partial
//! implementation — same posture as the vim item above.
pub use ;
pub use ;
pub use PromptHistory;
pub use ;
pub use ;
pub use ;
pub use Theme;
/// Whether the TUI should activate for this process: `config.tui_enabled`
/// AND stdin/stdout/stderr are all a real terminal. `crates/cli`'s `chat()`
/// is the sole call site that matters for the "default-off / non-tty /
/// `--print` = byte-identical REPL" contract (P5-4's build brief) — a
/// piped/non-interactive invocation (virtually every CI/test run,
/// regardless of whether a parity preset sets `capabilities.tui.enabled =
/// true`) always falls through to the pre-P5-4 REPL because THIS check
/// fails, not because the config bit is off.
///
/// Takes the three tty booleans as explicit parameters (rather than
/// calling `std::io::IsTerminal` itself) so this stays testable without a
/// real terminal — `crates/cli` passes
/// `io::stdin().is_terminal()`/`io::stdout().is_terminal()`/
/// `io::stderr().is_terminal()` at the real call site.