103 lines
7.2 KiB
Markdown
103 lines
7.2 KiB
Markdown
# TECH.md — APP-3904: Don't override pane/tab representation for expanded edit tool call
|
|
|
|
## Problem
|
|
|
|
When an agent edit tool call is expanded into a pane (via `ExpandEditToPane`), the vertical tabs sidebar changes its representation for that tab from the original pane (e.g. "Create Korean Poem File" with agent icon and metadata) to a generic "Requested Edit" code-diff pane. The entire sidebar row — title, icon, subtitle, badge — changes to reflect the replacement `CodeDiffPane` instead of the original agent conversation pane.
|
|
|
|
The sidebar should continue showing the original pane's full representation while a temporary replacement is active.
|
|
|
|
## Relevant code
|
|
|
|
- `app/src/pane_group/pane/code_diff_pane.rs:22-45` — `CodeDiffPane::from_view` hardcodes `PaneConfiguration` title to "Requested Edit"
|
|
- `app/src/workspace/view.rs:6428-6460` — `open_code_diff` creates the `CodeDiffPane` and calls `replace_pane` with `is_temporary: true`
|
|
- `app/src/pane_group/tree.rs:55-112` — `HiddenPane` / `HiddenPaneReason::TemporaryReplacement` tracks the original→replacement mapping
|
|
- `app/src/pane_group/tree.rs:368-372` — `PaneData::is_temporary_replacement` checks if a pane is a replacement
|
|
- `app/src/pane_group/mod.rs:4456-4502` — `PaneGroup::replace_pane` orchestrates temporary replacement; original stays in `pane_contents`
|
|
- `app/src/workspace/view/vertical_tabs.rs:2065-2136` — `PaneProps::new` resolves display properties (typed, title, subtitle, icon, badge) from the pane's configuration and type
|
|
- `app/src/workspace/view/vertical_tabs.rs:2348-2391` — `PaneGroup::resolve_pane_type` maps `PaneId` → `TypedPane` which drives icon/badge/kind
|
|
|
|
## Current state
|
|
|
|
The `ExpandEditToPane` feature flag controls how code diff views are opened:
|
|
- **Enabled**: The focused pane is temporarily replaced with a `CodeDiffPane`. The original pane is hidden via `HiddenPaneReason::TemporaryReplacement(replacement_id)` and kept in `pane_contents` for later restoration.
|
|
- **Disabled**: The diff opens in a new tab.
|
|
|
|
When the sidebar renders tab rows, `PaneProps::new` resolves all display properties from the visible pane. For a temporary replacement, the visible pane is the `CodeDiffPane`, so the sidebar shows:
|
|
- Icon: `WarpIcon::Diff` (instead of the original terminal/agent icon)
|
|
- Title: "Requested Edit" (hardcoded in `CodeDiffPane::from_view`)
|
|
- Type: `TypedPane::CodeDiff` (loses all terminal-specific metadata like conversation title, working directory, git branch)
|
|
- Badge/subtitle: empty
|
|
|
|
The pane header ("Requested Edit" with Refine/Done/Accept buttons) shown *inside* the pane content area is correct and is rendered by `CodeDiffView::render_header_content` — that is not affected by this change.
|
|
|
|
## Proposed changes
|
|
|
|
### 1. Add `original_pane_for_replacement` lookup to `PaneData`
|
|
|
|
In `app/src/pane_group/tree.rs`, add a method that returns the original hidden pane's ID given a replacement pane ID:
|
|
|
|
```rust
|
|
pub fn original_pane_for_replacement(&self, replacement_pane_id: PaneId) -> Option<PaneId>
|
|
```
|
|
|
|
This scans `hidden_panes` for a `TemporaryReplacement` entry whose associated replacement ID matches. It follows the same pattern as the existing `is_temporary_replacement` method.
|
|
|
|
### 2. Expose through `PaneGroup`
|
|
|
|
In `app/src/pane_group/mod.rs`, add a thin delegation method:
|
|
|
|
```rust
|
|
pub fn original_pane_for_replacement(&self, replacement_pane_id: PaneId) -> Option<PaneId>
|
|
```
|
|
|
|
### 3. Update `PaneProps::new` to use original pane for display
|
|
|
|
In `app/src/workspace/view/vertical_tabs.rs`, modify `PaneProps::new` so that when the requested `pane_id` is a temporary replacement, it resolves the `PaneConfiguration` and `TypedPane` from the original hidden pane:
|
|
|
|
```
|
|
let display_pane_id = pane_group
|
|
.original_pane_for_replacement(pane_id)
|
|
.unwrap_or(pane_id);
|
|
let display_pane = pane_group.pane_by_id(display_pane_id)?;
|
|
let pane_configuration = display_pane.pane_configuration();
|
|
let typed = pane_group.resolve_pane_type(display_pane_id, app);
|
|
```
|
|
|
|
This makes the sidebar row render the original pane's icon, title, subtitle, badge, and all terminal-specific metadata (conversation title, working directory, git branch, status indicators).
|
|
|
|
Fields that should still use the replacement `pane_id`:
|
|
- `pane_id` — click/focus targets the visible replacement pane
|
|
- `is_focused` — the replacement pane is what actually holds focus
|
|
- `is_being_dragged` — drag state belongs to the visible pane
|
|
|
|
## End-to-end flow
|
|
|
|
1. User is in an agent conversation (terminal pane, sidebar shows "Create Korean Poem File" with agent icon)
|
|
2. Agent produces an edit tool call; user expands it
|
|
3. `open_code_diff` creates a `CodeDiffPane` and calls `replace_pane(focused_pane_id, new_pane, true)`, which:
|
|
- Adds the original terminal pane to `hidden_panes` as `TemporaryReplacement(replacement_id)`
|
|
- Swaps the tree node to the `CodeDiffPane`
|
|
4. Sidebar re-renders. `PaneProps::new` receives the replacement `pane_id`:
|
|
- Looks up `original_pane_for_replacement(pane_id)` → finds the hidden terminal pane
|
|
- Resolves `typed`, `pane_configuration`, title, icon, etc. from the original terminal pane
|
|
- Sidebar row shows "Create Korean Poem File" with agent icon and metadata (unchanged)
|
|
5. User accepts/rejects the edit → `close_temporary_replacement_pane` reverts to the original pane
|
|
6. Sidebar naturally shows the original pane again (no special handling needed)
|
|
|
|
## Risks and mitigations
|
|
|
|
- **Original pane removed prematurely**: For temporary replacements, `replace_pane` explicitly skips removing the original from `pane_contents`. The original pane is guaranteed to exist for lookup. No new risk.
|
|
- **Multiple temporary replacements**: If multiple diffs are expanded in sequence on the same pane, the previous replacement is reverted first (via `close_temporary_replacement_pane`) before a new one is created. The lookup remains 1:1.
|
|
- **Non-`ExpandEditToPane` path**: When the flag is disabled, diffs open in a new tab (not a replacement). `original_pane_for_replacement` returns `None`, and `PaneProps::new` falls through to the existing behavior. No regression.
|
|
- **Detail sidecar**: The detail sidecar (hover popup) also resolves from `PaneProps`. Using the original pane's type means the sidecar will show terminal-specific detail (working directory, git branch, etc.) instead of code-diff detail. This is the correct behavior since the tab still conceptually represents the agent conversation.
|
|
|
|
## Testing and validation
|
|
|
|
- Manual: expand an agent edit tool call with `ExpandEditToPane` enabled. Verify the sidebar row keeps the original pane's icon, title, subtitle, and any badges. Verify clicking the sidebar row still focuses the diff pane. Verify accept/reject restores the original pane normally.
|
|
- Manual: repeat with `ExpandEditToPane` disabled. Verify no regression — diff opens in a new tab with "Requested Edit" title as before.
|
|
- Unit test: Add a test in `tree_tests.rs` for `original_pane_for_replacement` — verify it returns `Some(original_id)` after a temporary replacement and `None` otherwise.
|
|
|
|
## Follow-ups
|
|
|
|
- Consider whether `CodeDiffPane` still needs a hardcoded "Requested Edit" title at all, since the pane header text comes from `CodeDiffView::render_header_content` independently. The `PaneConfiguration` title is only relevant when the pane is shown in a new tab (non-replacement path). Leaving it as-is is safe.
|