Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -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.)
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user