Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# Product Spec: Plugin Installation Fallback Modal
|
||||
|
||||
## Problem
|
||||
|
||||
When the Warp notification plugin can't be auto-installed (SSH session, or a previous install attempt failed), the user currently has no way to learn how to install it manually. We need a modal that shows step-by-step manual installation instructions.
|
||||
|
||||
## Current Behavior
|
||||
|
||||
- A green "Install Warp plugin" chip appears in the CLI agent footer when the plugin isn't installed (`agent_input_footer/mod.rs:611-633`)
|
||||
- Clicking it runs auto-install via `claude plugin` CLI commands (`plugin_manager/claude.rs:29-37`)
|
||||
- On failure: an error toast appears with a link to logs
|
||||
- On SSH: the chip visibility has a bug (see below)
|
||||
|
||||
## Chip Visibility Fix (Remote Sessions)
|
||||
|
||||
`should_show_install_plugin_button` hides the chip when `manager.is_installed()` returns true. But `is_installed()` reads the **local** filesystem (`~/.claude/plugins/installed_plugins.json`), not the remote machine's. In any remote session (warpified SSH, legacy SSH, Docker via SSH) where Claude Code runs on the remote, this check is wrong:
|
||||
|
||||
- Plugin installed locally but not on remote → chip hidden, user stuck with no instructions
|
||||
|
||||
Fix: the `CLIAgentSession` tracks an `is_remote` flag (set at session creation from `active_session_is_local()`). When `is_remote` is true, skip the `is_installed()` check and rely solely on whether the session has an active listener. If no listener → show the chip.
|
||||
|
||||
## Two Chip Modes
|
||||
|
||||
The chip has two modes depending on context:
|
||||
|
||||
### Mode 1: Auto-Install (current behavior)
|
||||
|
||||
**When:** local session, no prior install failure for this session.
|
||||
|
||||
- Chip label: "Install Warp plugin"
|
||||
- Chip tooltip: "Install the Warp plugin to enable rich agent notifications within Warp"
|
||||
- On click: runs auto-install (existing `handle_install_plugin` flow)
|
||||
- On success: chip disappears (listener registers)
|
||||
- On failure: transitions to Mode 2 for the rest of the session
|
||||
|
||||
### Mode 2: Manual Instructions Modal
|
||||
|
||||
**When:** SSH session, OR auto-install previously failed in this session.
|
||||
|
||||
- Chip label: "Plugin install instructions"
|
||||
- Chip tooltip: "View instructions to install the Warp plugin"
|
||||
- Chip icon: `Icon::Info` (instead of `Icon::Download`)
|
||||
- On click: opens a modal with manual installation steps
|
||||
|
||||
## Modal Design
|
||||
|
||||
### Layout
|
||||
|
||||
Custom modal view following the `CodexModal` pattern (centered overlay, semi-transparent backdrop, Escape to close, click-outside to dismiss via `Dismiss` element).
|
||||
|
||||
- Title from `PluginInstallInstructions.title` (e.g. "Install Warp Plugin for Claude Code")
|
||||
- Subtitle from `PluginInstallInstructions.subtitle`
|
||||
- Numbered steps, each with:
|
||||
- A short description of what the step does
|
||||
- A monospace code block rendered via `render_code_block_plain` with a copy button
|
||||
- Close button (X) in the top-right corner
|
||||
- Copying a command shows a "Copied to clipboard" ephemeral toast
|
||||
|
||||
### Content for Claude Code
|
||||
|
||||
These are in-session slash commands (the user is already running Claude Code).
|
||||
|
||||
Step 1: "Add the Warp plugin marketplace repository"
|
||||
```
|
||||
/plugins marketplace add warpdotdev/claude-code-warp
|
||||
```
|
||||
|
||||
Step 2: "Install the Warp plugin"
|
||||
```
|
||||
/plugins install warp@claude-code-warp
|
||||
```
|
||||
|
||||
Step 3: "Reload plugins to activate"
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
Subtitle: "Ensure that jq is installed on your machine. Then, run these commands inside your Claude Code session."
|
||||
|
||||
Auto-install success toast: "Warp plugin installed. Please run /reload-plugins to activate."
|
||||
|
||||
### Extensibility
|
||||
|
||||
Each agent provides its own modal view. Common rendering helpers (backdrop, title bar, step layout, code blocks) are shared. Adding a new agent's modal means:
|
||||
|
||||
- Implementing a new view that uses the shared helpers
|
||||
- Returning the appropriate view from a factory function keyed on `CLIAgent`
|
||||
|
||||
## State Tracking
|
||||
|
||||
### `plugin_install_failed` on `AgentInputFooter`
|
||||
|
||||
A per-session boolean (scoped to the `AgentInputFooter` instance) that tracks whether auto-install has failed. Set to `true` in the `handle_install_plugin` error callback. This determines whether the chip is in Mode 1 or Mode 2 for local sessions.
|
||||
|
||||
Reset to `false` if the plugin activates (listener connects — which already hides the chip entirely).
|
||||
|
||||
### No persistence across sessions
|
||||
|
||||
Failure state is not persisted. A new terminal session starts fresh in Mode 1 (auto-install).
|
||||
|
||||
## Behavior Summary
|
||||
|
||||
- Plugin active (listener present) → chip hidden
|
||||
- Local, plugin installed on disk → chip hidden
|
||||
- Local, plugin not installed, no prior failure → chip shown, Mode 1 (auto-install)
|
||||
- Local, plugin not installed, prior failure → chip shown, Mode 2 (modal)
|
||||
- Remote (any SSH), no listener → chip shown, Mode 2 (modal)
|
||||
- Remote (any SSH), listener present → chip hidden
|
||||
- Agent has no plugin support → chip hidden
|
||||
- Install in progress → chip hidden
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **User installs plugin manually mid-session (without using the chip):** The listener will connect on next `SessionStart` event, chip disappears automatically.
|
||||
- **User clicks chip in Mode 2 then installs manually:** Modal stays open until dismissed. Chip disappears on next render once listener is present.
|
||||
- **Multiple terminal tabs with same agent:** Each tab has its own `AgentInputFooter` with independent failure tracking. This is correct — one tab's failure shouldn't affect another.
|
||||
- **Warpified SSH (tmux wrapper):** Even though the local filesystem is accessible via tmux, the agent runs on the remote machine. The `is_remote` flag is set for all SSH sessions (warpified or legacy), so Mode 2 applies to all remote sessions.
|
||||
@@ -0,0 +1,117 @@
|
||||
# Tech Spec: Plugin Installation Fallback Modal
|
||||
|
||||
See `specs/APP-3619/PRODUCT.md` for the full product spec.
|
||||
|
||||
## 1. Install Instructions Data Model
|
||||
|
||||
`plugin_manager/mod.rs`
|
||||
|
||||
Add `PluginInstallStep` and `PluginInstallInstructions` structs. Replace the existing `post_install_hint()` trait method with `install_instructions() -> &'static PluginInstallInstructions` that returns all modal content for this agent.
|
||||
|
||||
```rust
|
||||
pub(crate) struct PluginInstallStep {
|
||||
pub description: &'static str,
|
||||
pub command: &'static str,
|
||||
}
|
||||
|
||||
pub(crate) struct PluginInstallInstructions {
|
||||
pub title: &'static str,
|
||||
pub subtitle: &'static str,
|
||||
pub steps: &'static [PluginInstallStep],
|
||||
pub success_toast: &'static str,
|
||||
}
|
||||
```
|
||||
|
||||
Each implementation uses `LazyLock` so the data is built once. Claude Code's steps are the in-session slash commands (`/plugins marketplace add ...`, `/plugins install ...`, `/reload-plugins`) since the user is already in a running session.
|
||||
|
||||
The existing `post_install_hint` is removed. The auto-install success toast reads from `install_instructions().success_toast`.
|
||||
|
||||
## 2. Modal View
|
||||
|
||||
New file: `workspace/view/plugin_install_modal.rs`, following the `CodexModal` pattern (standalone view, centered overlay, backdrop, Escape to close).
|
||||
|
||||
### View struct
|
||||
|
||||
```rust
|
||||
pub struct PluginInstallModal {
|
||||
agent: Option<CLIAgent>,
|
||||
close_button_mouse_state: MouseStateHandle,
|
||||
step_code_handles: Vec<CodeSnippetButtonHandles>,
|
||||
}
|
||||
```
|
||||
|
||||
`agent` is `Option` so the view can be constructed once in workspace init and updated via `set_agent()` before each open. All `MouseStateHandle`s are stored in the struct (not created inline during render). Copying a command shows a "Copied to clipboard" ephemeral toast via `ToastStack`. The modal uses `Dismiss` to close on outside click.
|
||||
|
||||
### Code block rendering
|
||||
|
||||
Reuse `render_code_block_plain` (`ai/blocklist/code_block.rs:117`) with `CodeBlockOptions` — the same component already used by `cloud_setup_guide_view.rs:393` for its step-by-step CLI guide. Each step gets `on_copy` + a `CodeSnippetButtonHandles`.
|
||||
|
||||
### Per-agent extensibility
|
||||
|
||||
`render()` dispatches to per-agent render functions that compose shared helpers (modal shell, numbered step layout, code blocks). Adding a new agent means adding a render function + `PluginInstallInstructions` implementation.
|
||||
|
||||
### Actions & Events
|
||||
|
||||
```rust
|
||||
enum PluginInstallModalAction { Close, CopyCommand(usize) }
|
||||
enum PluginInstallModalEvent { Close }
|
||||
```
|
||||
|
||||
## 3. Workspace Wiring
|
||||
|
||||
Follows the standard modal pattern (like `CodexModal` at `workspace/view.rs:817,1573,12878,18110`):
|
||||
|
||||
- `is_plugin_install_modal_open` on `WorkspaceState` (add to `is_any_non_palette_modal_open` and `close_all_modals` in `workspace/util.rs`)
|
||||
- `plugin_install_modal: ViewHandle<PluginInstallModal>` on `Workspace`
|
||||
- `open_plugin_install_modal(agent)` sets the agent, flips the bool, focuses the modal
|
||||
- Rendered conditionally in the workspace render method's modal stack
|
||||
|
||||
## 4. Event Plumbing: Footer → Workspace
|
||||
|
||||
New `ShowPluginInstallModal(CLIAgent)` variant propagated through the standard event chain:
|
||||
|
||||
`AgentInputFooterEvent` (`mod.rs:1325`) → `Input::Event` (`input.rs:1039`) → `TerminalView::Event` → `pane_group::Event` → Workspace handler
|
||||
|
||||
Follows the existing `OpenAutoReloadModal` pattern (`view.rs:18411`, `workspace/view.rs:10182`).
|
||||
|
||||
## 5. Remote Session Detection
|
||||
|
||||
`CLIAgentSession` has an `is_remote: bool` field set at session creation from `TerminalView::active_session_is_local()`. This uses `SessionType::WarpifiedRemote` and `IsLegacySSHSession` — the same logic as the SSH host chip (`context_chips/builtins.rs:76`).
|
||||
|
||||
This avoids relying on `terminal_model.is_ssh_block()` (which only tracks the pre-warpification login phase) or `is_warpified_ssh()` (which misses legacy SSH). The `is_remote` flag is threaded through `set_session` and `register_listener` at all call sites in `terminal/view.rs`.
|
||||
|
||||
## 6. Two-Mode Chip
|
||||
|
||||
`agent_input_footer/mod.rs` — the install chip has two modes based on whether auto-install is viable.
|
||||
|
||||
### Mode selection
|
||||
|
||||
`should_use_manual_install_mode(&self, app)` returns `true` when:
|
||||
|
||||
- `plugin_install_failed` is set (auto-install already failed this session), OR
|
||||
- `session.is_remote` is true (checked via `CLIAgentSessionsModel`)
|
||||
|
||||
### Remote visibility fix
|
||||
|
||||
`should_show_install_plugin_button` checks `session.is_remote` — when true, skips the local `is_installed()` filesystem check and relies solely on listener presence.
|
||||
|
||||
### Two buttons
|
||||
|
||||
`ActionButton` doesn't support changing label/icon after construction, so we create two `ActionButton` views and conditionally render the right one:
|
||||
|
||||
- **Auto-install** (existing `install_plugin_button`): "Install Warp plugin" / `Icon::Download` → triggers `handle_install_plugin`
|
||||
- **Manual** (new `plugin_instructions_button`): "Plugin install instructions" / `Icon::Info` → emits `ShowPluginInstallModal`
|
||||
|
||||
In `render_cli_mode_footer`, branch on `should_use_manual_install_mode()` to pick which button to render.
|
||||
|
||||
On install failure (`handle_install_plugin` error callback), set `plugin_install_failed = true` to switch to manual mode for the rest of the session.
|
||||
|
||||
## 7. Files Changed
|
||||
|
||||
- **New:** `workspace/view/plugin_install_modal.rs`
|
||||
- **Modified:** `plugin_manager/mod.rs` — `PluginInstallStep`, `PluginInstallInstructions`, new trait method
|
||||
- **Modified:** `plugin_manager/claude.rs` — `LazyLock` implementation
|
||||
- **Modified:** `cli_agent_sessions/mod.rs` — `is_remote` field on `CLIAgentSession`, threaded through `register_listener`
|
||||
- **Modified:** `agent_input_footer/mod.rs` — two-mode chip, failure tracking, remote detection via `session.is_remote`, new action/event variants
|
||||
- **Modified:** `terminal/input.rs`, `terminal/view.rs` — event forwarding, `is_remote` computation at session creation
|
||||
- **Modified:** `workspace/util.rs`, `workspace/view.rs`, `workspace/mod.rs` — modal integration
|
||||
Reference in New Issue
Block a user