# Terminal manager view abstraction — TECH ## Context The local TTY terminal manager now has a reusable construction path for terminal frontends. The GUI remains the only frontend in this change, but the manager no longer needs to treat `TerminalView` as part of the object-safe manager contract. The pre-change local manager was tightly coupled to `TerminalView`: - [`app/src/terminal/writeable_pty/terminal_manager_util.rs:23 @ 0b2273da`](https://github.com/warpdotdev/warp/blob/0b2273da3443eacc8d78b748f37962427e968fe5/app/src/terminal/writeable_pty/terminal_manager_util.rs#L23) wired `PtyController` directly to `ViewHandle` and matched `terminal::view::Event` variants for PTY writes, resize, command execution, and native completions. - [`app/src/terminal/local_tty/terminal_manager.rs:139 @ 0b2273da`](https://github.com/warpdotdev/warp/blob/0b2273da3443eacc8d78b748f37962427e968fe5/app/src/terminal/local_tty/terminal_manager.rs#L139) stored `ViewHandle` directly and called GUI-specific lifecycle hooks for shell startup, spawn failures, shell launch-data updates, and Unix terminal-attributes/password-prompt handling. - [`app/src/terminal/terminal_manager.rs:24 @ 0b2273da`](https://github.com/warpdotdev/warp/blob/0b2273da3443eacc8d78b748f37962427e968fe5/app/src/terminal/terminal_manager.rs#L24) exposed `view() -> ViewHandle` from the object-safe `TerminalManager` trait, so callers recovered the GUI view from the boxed manager. The current design splits the terminal manager responsibilities into three layers: 1. `TerminalSurface` and `PtyIntent` define the narrow frontend-to-PTY boundary. 2. `TerminalManager` owns local terminal model/session/controller/event-loop state for any terminal surface. 3. `TerminalManager::create_model` is the generic model-producing construction path. The current GUI pane system calls it with `S = TerminalView` and a GUI surface setup callback. ## Proposed changes ### Terminal surface API `app/src/terminal/writeable_pty/terminal_surface.rs` defines `PtyIntent`, `PtyIntentEvent`, and `TerminalSurface`. `PtyIntent` is the only event vocabulary the generic PTY wiring understands. It is intentionally limited to PTY/session-driving actions: process control, byte writes, resize, command execution, and native shell completions. UI events, pane orchestration, remote-server choice UI, and shared-session protocol events remain on concrete surface event types. `PtyIntentEvent` preserves the `From<&SurfaceEvent> for Option` projection pattern while hiding the higher-ranked bound behind a simple method: ```rust pub(crate) trait PtyIntentEvent { fn pty_intent(&self) -> Option; } impl PtyIntentEvent for T where for<'a> Option: From<&'a T>, { fn pty_intent(&self) -> Option { Option::::from(self) } } ``` `TerminalSurface` is the frontend contract consumed by `TerminalManager`. It requires a surface event type that implements `PtyIntentEvent` and lifecycle hooks for shell startup, PTY spawn failure, launch-data updates, and Unix password-prompt polling. `TerminalView` implements this contract and maps only the existing PTY-driving `terminal::view::Event` variants to `PtyIntent`: - `CtrlD` - `ShutdownPty` - `WriteBytesToPty` - `WriteAgentInputToPty` - `Resize` - `ExecuteCommand` - `RunNativeShellCompletions` Every other `TerminalView` event maps to `None`. ### View-agnostic terminal manager trait `crate::terminal::TerminalManager` no longer exposes `view() -> ViewHandle`. The object-safe trait now describes terminal model/session lifecycle: - `model()` - `on_view_detached(...)` - `as_any()` - `as_any_mut()` Constructors that create a `TerminalView` return the view separately alongside the boxed manager. This keeps the boxed manager view-agnostic while preserving existing pane code that needs the `TerminalView` handle. Current constructors that return `(manager, surface)`: - `local_tty::TerminalManager::create_model`, called by the GUI path as `TerminalManager::::create_model` - `remote_tty::TerminalManager::create_model` - `MockTerminalManager::create_model` - `shared_session::viewer::TerminalManager::new` - `shared_session::viewer::TerminalManager::new_deferred` ### Generic local manager construction `app/src/terminal/local_tty/terminal_manager.rs` defines the local manager as: ```rust pub struct TerminalManager { view: ViewHandle, // model, controllers, event-loop state, and retained handles } ``` `TerminalManager::create_model(...)` is the canonical local terminal construction API. It owns the local terminal session construction order, creates the manager-owned channels/models/controllers internally, calls one surface setup callback that returns `(surface, post_wire)`, wires the surface to the `PtyController` through `wire_up_pty_controller_with_surface`, runs `post_wire`, boxes the manager as a WarpUI model, schedules shell determination, and returns `(manager_model, surface)`. The surface factory callback receives only the components a surface needs: - `TerminalModel` - `Sessions` - `ModelEventDispatcher` - wakeup receiver - inactive PTY reads receiver - terminal colors - current terminal size The manager stores the remaining startup and lifetime components itself, including the event-loop sender/receiver, channel event proxy, `PtyController`, `RemoteServerController`, and shell-starter source. Shell determination later consumes that stored startup state from the manager after it has been registered as a WarpUI model. The surface setup callback can return a deferred `post_wire` closure for surface-specific wiring. The constructor runs that closure after the PTY controller is wired and the manager has been assembled. This keeps ordering-sensitive post-wiring inside the manager-owned construction flow without requiring the generic manager core to import `TerminalView`. For the GUI caller, `TerminalManager::::create_model(...)` now: 1. Resolves GUI-specific restored blocks from explicit restored blocks and conversation restoration. 2. Uses a surface setup callback that creates `CurrentPrompt`, `PromptType`, and `TerminalView` from the manager-created surface components, then returns `(view, post_wire)`. 3. The returned `post_wire` closure appends the GUI restoration separator when needed, wires remote-server choice UI, and wires `TerminalView`-specific session sharing. 4. The generic constructor boxes the manager, schedules shell determination, and returns `(manager_model, terminal_view)`. This keeps the end-to-end local terminal construction protocol in `TerminalManager::create_model` while keeping GUI-specific work in the `TerminalView` surface setup callback. A future TUI surface can call the same function with a different surface setup callback. ### Generic PTY wiring `wire_up_pty_controller_with_surface(...)` replaces the direct `TerminalView` wiring. It subscribes to the surface event stream, calls `event.pty_intent()`, and handles only `PtyIntent` values. The behavior of each intent matches the old `TerminalView` event match: - raw bytes and agent bytes write to the PTY controller - resize resizes the PTY - command execution resolves shell type from `Sessions`, sets workflow state, writes the command, and updates command history when requested - native completions call through to `PtyController::run_native_shell_completions` - PTY disconnection exits the `TerminalModel` `wire_up_remote_server_controller_with_view` remains GUI-specific because the remote-server install/skip choice is rendered as `TerminalView` rich content. ### TerminalView-specific session sharing boundary Local session sharing remains GUI-specific in this change. The reusable lower layers stay unchanged: - `shared_session::sharer::Network` - ordered terminal event flow from `TerminalModel` - existing shared-session handler helpers The local sharer wiring is grouped behind `wire_up_terminal_view_session_sharing(...)`. That helper owns the current `TerminalView` adapter responsibilities: - prompt updates - presence selection - LLM/input-mode/conversation broadcasts - AgentView and active-agent registration - CLI-agent session broadcasts - local sharer `Network` setup - network status UI reactions `shared_session::manager::Manager` remains GUI-shaped and continues storing `TerminalView` handles. The sharing boundary is now easier to locate and refactor later without making this change TUI-sharing-ready. ### Unix password-prompt polling The local manager still owns the Unix `termios` poller, but the surface decides whether polling is useful and how to react. The poller now subscribes to `ModelEventDispatcher` rather than `TerminalView` events: - `ModelEvent::AfterBlockStarted { is_for_in_band_command: false, .. }` records the active block index and starts polling only if `surface.should_poll_for_password_prompt(ctx)` returns true. - `ModelEvent::BlockCompleted(completed)` stops polling and calls `surface.on_polled_block_completed(completed, ctx)`. - `TerminalAttributesPollerEvent::TermiosQueryFinished` checks for ECHO off and ICANON on, calls `surface.on_possible_password_prompt(block_index, ctx)`, and stops polling after the first detected prompt. For `TerminalView`, these hooks preserve existing password notification and SSH upload behavior. ## Testing and validation Run formatting and compile checks: - `./script/format` - `cargo check -p warp` - `cargo clippy -p warp --all-targets --tests -- -D warnings` Run focused tests covering: - terminal manager/view constructor paths - PTY write and command execution events - pane creation paths that now receive the `TerminalView` separately - shared-session start/stop behavior Validation performed during implementation: - `./script/format` - `cargo check -p warp` ## Parallelization Do not parallelize implementation across child agents. The core changes touch the same local manager, object-safe trait, PTY wiring, and pane construction call sites; parallel work would create overlapping edits. Validation can be performed separately after the code compiles. ## Risks and mitigations - **Surface boundary grows too broad.** Keep `PtyIntent` limited to PTY/session-driving actions and leave pane, UI, remote-server choice, and shared-session protocol events on concrete surface event types. - **Constructor churn breaks pane creation.** Return `TerminalView` explicitly from GUI constructors and update pane helpers at the same call sites that previously used `manager.view()`. - **Shared-session behavior regresses.** Keep protocol/model layers unchanged and move the GUI-specific sharer setup as a group rather than rewriting it. - **Password-prompt behavior changes.** Route the same termios detection through explicit `TerminalSurface` hooks and preserve one-notification-per-command behavior. ## Out of scope - No TUI backend or `LaunchMode::Tui`. - No terminal-history rendering, virtual list, or key-passthrough work. - No generic shared-session frontend abstraction. - No blanket object-safe trait implementation for every `TerminalManager`; current detach behavior is still specific to the `TerminalView` manager.