Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
# APP-3781: Move Plugin Installation Instructions into a Split Pane
Linear: [APP-3781](https://linear.app/warpdotdev/issue/APP-3781/move-plugin-installation-settings-into-a-dedicated-pane)
## Summary
Replace the blocking modal that shows CLI agent plugin install/update instructions with a split terminal pane containing a specialized zero state block. The pane is a real terminal session, so the user can read the instructions and run the commands without switching context.
## Problem
The current plugin install/update instructions are shown in a modal overlay that blocks the entire screen. The user must:
1. Read a step in the modal
2. Copy the command
3. Close the modal
4. Paste the command into the terminal
5. Re-open the modal to see the next step
This is particularly frustrating because the instructions involve multiple sequential commands that the user needs to run in their terminal.
## Goals
- Show plugin install/update instructions in a side-by-side terminal pane instead of a blocking modal.
- Let the user run the instruction commands directly in the new pane without context switching.
- Preserve the existing "copy" affordance for each command step.
- Apply to both install and update instruction flows.
## Non-goals
- Changing the auto-install/auto-update behavior (the one-click install/update chip continues to work as before).
- Modifying the instruction content itself (titles, subtitles, step descriptions, commands remain unchanged).
- Adding "run in terminal" buttons that auto-execute commands — the user pastes/types them manually.
## User Experience
### Entry point
The entry points remain the same buttons in the CLI agent toolbar:
- The install chip (when in the instructions state) → opens install instructions pane.
- The update chip (when in the instructions state) → opens update instructions pane.
### What happens on click
1. A new terminal pane opens as a split in the current tab (using smart split direction, like env var panes do). This is always a terminal pane, not an agent pane, even if the user's default mode for new sessions is Agent Mode.
2. A specialized zero state block is rendered at the top of the new terminal's block list, displaying the plugin instructions.
3. The terminal session in the new pane is fully functional — the user can type and run commands.
### Instructions block content
The instructions block renders the same information as the current modal:
- **Title** (e.g., "Install Warp Plugin for Claude Code")
- **Subtitle** (e.g., "Ensure that jq is installed on your machine. Then, run these commands inside your Claude Code session.")
- **Numbered steps**, each with:
- A step number in a circular badge
- A text description of the step
- A code block with the command and a copy-to-clipboard button
The visual style should match the existing terminal zero state block pattern: bordered container with terminal-consistent styling. Step rendering reuses the same code block rendering pattern from the modal (the `render_code_block_plain` helper).
### Dismissing the instructions block
The instructions block has a close button (X) in the top-right corner. Clicking it hides the block. The instructions block persists across commands — it does not auto-dismiss when the user runs a command, since the user may be following the multi-step instructions in that pane.
### Closing the pane
The user can close the instructions pane like any other split pane (via the pane close button, keyboard shortcut, etc.). No special cleanup is needed.
### Modal removal
The modal (`PluginInstallModal`) is removed entirely. All references to `is_plugin_install_modal_open` in workspace state are cleaned up.
## Edge Cases
1. **Single-pane tab**: If the tab has only one pane, the split creates a second pane. The instructions block appears in the new (right/bottom) pane.
2. **Already-split tab**: The new instructions pane is added as a sibling of the focused pane in the smart split direction, consistent with how env var panes split.
3. **Multiple instruction requests**: Clicking the instructions button again always opens a new split pane (no deduplication).
4. **Pane closed, re-requested**: If the user closes the instructions pane and clicks the button again, a new instructions pane is created from scratch.
## Success Criteria
1. Clicking the info (ⓘ) button next to the install/update chip opens a split terminal pane, not a modal overlay.
2. The new pane shows a zero state block with the full plugin instructions (title, subtitle, numbered steps with copy-able commands).
3. The user can type and run commands in the new pane while the instructions block is visible.
4. The instructions block persists until the user clicks the close (X) button on it.
5. The copy button on each step copies the command to the clipboard and shows a "Copied to clipboard" toast.
6. The modal overlay (`PluginInstallModal`) is fully removed from the codebase.
7. Both install and update instruction flows use the new split pane behavior.
## Validation
- **Manual test**: Click the install info button → verify a split pane appears with instructions. Copy a command → verify clipboard. Run a command in the pane → verify the instructions block remains visible. Click the close (X) button on the block → verify it disappears. Close the pane → verify clean closure.
- **Both flows**: Verify both install and update info buttons open the pane with the correct instructions.
- **Compile check**: Verify no remaining references to the removed modal types.
## Open Questions
(None outstanding.)
+148
View File
@@ -0,0 +1,148 @@
# APP-3781: Tech Spec — Plugin Instructions in a Split Pane
Linear: [APP-3781](https://linear.app/warpdotdev/issue/APP-3781/move-plugin-installation-settings-into-a-dedicated-pane)
## Problem
The plugin install/update instructions are rendered as a modal (`PluginInstallModal`) that overlays the entire workspace. This change replaces the modal with a split terminal pane containing a rich-content instructions block with a manual close button.
## Relevant Code
**Current modal (to be deleted):**
- `app/src/workspace/view/plugin_install_modal.rs` — the modal view, step rendering, copy-to-clipboard, `render_step_number`
- `app/src/workspace/view.rs:851``plugin_install_modal` field on `Workspace`
- `app/src/workspace/view.rs:14027-14064``handle_plugin_install_modal_event` and `open_plugin_install_modal`
- `app/src/workspace/view.rs:19587-19590` — modal render in workspace overlay stack
- `app/src/workspace/util.rs:119,156,197``is_plugin_install_modal_open` in `WorkspaceState`
**Event chain (to be renamed):**
- `app/src/ai/blocklist/agent_view/agent_input_footer/mod.rs:1788,1959``ShowPluginInstallModal` / `ShowPluginInstructionsModal` actions and events
- `app/src/terminal/input.rs:1047``InputEvent::ShowPluginInstructionsModal`
- `app/src/terminal/view.rs:1892``terminal::view::Event::ShowPluginInstructionsModal`
- `app/src/pane_group/pane/terminal_pane.rs:664-668` — forwards to `pane_group::Event`
- `app/src/pane_group/mod.rs:695``pane_group::Event::ShowPluginInstructionsModal`
- `app/src/workspace/view.rs:11231` — workspace handler
**Plugin instructions data:**
- `app/src/terminal/cli_agent_sessions/plugin_manager/mod.rs``PluginInstructions`, `PluginInstructionStep`, `PluginModalKind`
- `app/src/terminal/cli_agent_sessions/plugin_manager/claude.rs:126-170` — static `INSTALL_INSTRUCTIONS` and `UPDATE_INSTRUCTIONS`
**Patterns to follow:**
- `app/src/terminal/view/zero_state_block.rs``TerminalViewZeroStateBlock` (container styling, positioned close/dismiss button via `Stack::with_positioned_child`)
- `app/src/terminal/view/rich_content.rs``RichContent`, `RichContentMetadata`, `TerminalView::insert_rich_content`
- `app/src/terminal/model/rich_content.rs``RichContentType` enum
- `app/src/ai/blocklist/code_block/mod.rs``render_code_block_plain`, `CodeBlockOptions`, `CodeSnippetButtonHandles` (for code block rendering)
- `app/src/pane_group/mod.rs:3750-3766``PaneGroup::add_terminal_pane` returns `TerminalPaneId`
- `app/src/pane_group/mod.rs:6078-6085``PaneGroup::terminal_view_from_pane_id` returns `ViewHandle<TerminalView>`
- `app/src/pane_group/mod.rs:1057-1072``PaneGroup::smart_split_direction`
- `app/src/workspace/view.rs:569``WORKFLOW_AND_ENV_VAR_SPLIT_RATIO`
## Current State
Event chain: info (ⓘ) button click → `AgentInputFooterAction::ShowPluginInstallModal``AgentInputFooterEvent::ShowPluginInstructionsModal(agent, kind)``InputEvent``terminal::view::Event``TerminalPane``pane_group::Event``Workspace::open_plugin_install_modal`.
The workspace resolves `PluginInstructions` from `plugin_manager_for(agent)`, sets them on the modal view, sets `is_plugin_install_modal_open = true`, and focuses the modal. The modal renders as a centered overlay with dimmed background.
## Proposed Changes
### 1. New file: `app/src/terminal/view/plugin_instructions_block.rs`
A `PluginInstructionsBlock` view that renders plugin instructions as terminal rich content with a close button.
**Struct:**
```rust
struct PluginInstructionsBlock {
instructions: &'static PluginInstructions,
close_button_mouse_state: MouseStateHandle,
step_code_handles: Vec<CodeSnippetButtonHandles>,
should_hide: bool,
}
```
**Rendering:** Uses a `Stack` with:
- Main child: a bordered `Container` (matching `TerminalViewZeroStateBlock` style — horizontal terminal padding, vertical padding, top/bottom border) containing a `Flex::column` with title, subtitle, and numbered step rows
- Positioned child: a close button (using `appearance.ui_builder().close_button()`) in the top-right corner via `OffsetPositioning::offset_from_parent`
Each step row reuses the `render_step_number` badge (moved from the modal into this file as a private `fn`, since no other caller exists) and `render_code_block_plain` for the command code block with copy button.
**Actions and events:**
- `PluginInstructionsBlockAction::Close` — sets `should_hide = true`, emits a close event to the owning `TerminalView` so the rich-content item is removed from the blocklist sumtree, and calls `ctx.notify()`
- `PluginInstructionsBlockAction::CopyCommand(usize)` — copies to clipboard via `ctx.clipboard().write()`, shows toast via `ToastStack::handle(ctx)` singleton (same pattern as the modal, `plugin_install_modal.rs:248-257`)
- `Entity::Event = PluginInstructionsBlockEvent``Close` bubbles to `TerminalView` for rich-content cleanup; toast and clipboard are still handled directly in the block
When `should_hide` is true, `render` returns `Empty::new().finish()`.
### 2. New `RichContentType` and `RichContentMetadata` variants
In `app/src/terminal/model/rich_content.rs`, add `PluginInstructionsBlock` to the `RichContentType` enum.
In `app/src/terminal/view/rich_content.rs`, add `PluginInstructionsBlock` to the `RichContentMetadata` enum.
### 3. Rename event variants
Rename across the entire chain to reflect pane-based behavior:
- `AgentInputFooterAction::ShowPluginInstallModal``OpenPluginInstallInstructionsPane`
- `AgentInputFooterAction::ShowPluginInstructionsModal``OpenPluginUpdateInstructionsPane`
- `AgentInputFooterEvent::ShowPluginInstructionsModal``OpenPluginInstructionsPane` (carries `PluginModalKind`)
- `InputEvent::ShowPluginInstructionsModal``OpenPluginInstructionsPane` (carries `PluginModalKind`)
- `terminal::view::Event::ShowPluginInstructionsModal``OpenPluginInstructionsPane` (carries `PluginModalKind`)
- `pane_group::Event::ShowPluginInstructionsModal``OpenPluginInstructionsPane` (carries `PluginModalKind`)
### 4. Workspace: replace modal with split pane creation
Replace `Workspace::open_plugin_install_modal` with `open_plugin_instructions_pane`. The method:
1. Resolves `PluginInstructions` from `plugin_manager_for(agent)` (same as before)
2. Creates a new terminal pane via `PaneGroup::add_terminal_pane_ignoring_default_session_mode(direction, None, ctx)` so the pane stays in terminal mode even if the user's default mode for new sessions is Agent Mode. Split panes do not show the homepage zero-state, so no `hide_homepage` option is needed.
3. Gets the `ViewHandle<TerminalView>` via `PaneGroup::terminal_view_from_pane_id(pane_id, ctx)`
4. Inside `terminal_view.update()`, creates a `PluginInstructionsBlock` view and calls `view.insert_rich_content(...)` to add it
This keeps `TerminalView` fully decoupled from plugin concepts — the workspace owns the orchestration, and the block is just another rich content view.
### 5. Delete modal code
Remove:
- `app/src/workspace/view/plugin_install_modal.rs` (entire file)
- `mod plugin_install_modal` declaration (`view.rs:13`)
- `use crate::workspace::view::plugin_install_modal::{PluginInstallModal, PluginInstallModalEvent}` import (`view.rs:129`)
- `plugin_install_modal` field from `Workspace` struct (`view.rs:851`)
- `plugin_install_modal` field initialization in `Workspace::new` (`view.rs:2393-2394`)
- `is_plugin_install_modal_open` from `WorkspaceState` and all references in `is_any_non_palette_modal_open`, `close_all_modals` (`util.rs:119,156,197`)
- `handle_plugin_install_modal_event` (`view.rs:14027-14040`)
- Modal construction and subscription in `Workspace::new` (`view.rs:1896-1901`)
- Modal render in overlay stack (`view.rs:19587-19590`)
- `view::plugin_install_modal::init(app)` call (`workspace/mod.rs:87`)
## End-to-End Flow
1. User clicks info (ⓘ) button on install/update chip
2. `AgentInputFooterAction::OpenPluginInstallInstructionsPane` (or update variant) dispatched
3. Event bubbles: `AgentInputFooter``Input``TerminalView``TerminalPane``PaneGroup``Workspace`
4. `Workspace::open_plugin_instructions_pane(agent, kind, ctx)` called
5. Workspace resolves `PluginInstructions` from `CliAgentPluginManager`
6. Inside `active_tab_pane_group().update()`:
- Creates terminal pane with `add_terminal_pane_ignoring_default_session_mode(Direction::Right, None, ctx)``TerminalPaneId`
- Gets `ViewHandle<TerminalView>` via `terminal_view_from_pane_id`
- Inside `terminal_view.update()`: creates `PluginInstructionsBlock`, calls `insert_rich_content`
7. New pane renders with instructions block at top; user types/runs commands below it
8. User clicks close (X) button → block hides, the corresponding rich-content item is removed from the blocklist sumtree, and the terminal pane remains functional
## Risks and Mitigations
**Block insertion timing.** `insert_rich_content` appends to the block list model and works before session bootstrap. The block will be visible while the session bootstraps (sub-second). No special handling needed.
**Toast access.** The block accesses `ToastStack::handle(ctx)` directly (it's a singleton), same pattern as the modal. No event bubbling required for toasts.
**`PluginInstructions` visibility.** `PluginInstructions` and `PluginInstructionStep` are currently `pub(crate)` in `plugin_manager/mod.rs`. The new block file is within the same crate, so no visibility changes needed.
## Testing and Validation
- `cargo check` — no remaining references to deleted modal types
- `cargo fmt` and `cargo clippy` per presubmit
- Manual test: click install info button → split pane with instructions. Copy command → clipboard + toast. Run commands → instructions block persists. Click close (X) → block hides. Close pane → clean.
- Both flows: verify install and update info buttons show correct instructions.
## Follow-ups
- The two `AgentInputFooterAction` variants (`OpenPluginInstallInstructionsPane` for install, `OpenPluginUpdateInstructionsPane` for update) could be collapsed into a single variant carrying `PluginModalKind`. Left as-is for minimal diff, can unify later.