Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# PRODUCT.md — Restore Open Files and Markdown Editors
|
||||
|
||||
**GitHub Issue:** [warpdotdev/warp-external#371](https://github.com/warpdotdev/warp-external/issues/371)
|
||||
**Figma:** none provided
|
||||
|
||||
## Summary
|
||||
|
||||
When Warp is closed and restarted, code editor panes (open files in the built-in editor) and markdown viewer/editor panes should be restored to their previous state, just as terminal tabs, AI conversations, notebooks, and other pane types already are. Today, code editor panes are persisted to SQLite but skipped during restoration, and while markdown file panes are successfully restored, the overall experience for file-based panes has gaps.
|
||||
|
||||
## Problem
|
||||
|
||||
Users who open files in Warp's built-in code editor lose all open editor tabs when they quit and relaunch the app. Those panes are silently dropped during session restoration, which is surprising given that terminal sessions and other pane types are reliably restored. This forces users to manually reopen files, losing their working context.
|
||||
|
||||
## Goals
|
||||
|
||||
1. **Restore code editor panes on restart.** Every code pane that was open when the app was quit should be restored to the same position in the pane tree with the same file(s) open.
|
||||
2. **Restore all tabs within a code pane.** A code pane can contain multiple file tabs. All open tabs (and the active tab index) should be restored, not just the single active file.
|
||||
3. **Preserve markdown viewer display mode.** When a markdown file is open in the `FileNotebookView` (rendered mode), it should restore to the correct display mode (rendered vs. raw).
|
||||
4. **Handle missing files gracefully.** If a persisted file path no longer exists on disk at restoration time, the pane should still be restored (e.g. showing an error state or empty editor) rather than causing the entire tab/pane-tree restoration to fail.
|
||||
5. **Preserve code pane source semantics where possible.** When restoring a file-backed code pane, preserve enough source information to retain the expected deduplication behavior for that pane (for example, restoring file-tree panes as `CodeSource::FileTree` rather than collapsing everything to `CodeSource::Link`).
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **Restoring unsaved edits / dirty buffer content.** This spec does not cover persisting uncommitted text changes in editor buffers. Only the file path and tab structure are restored; the editor reloads content from disk.
|
||||
- **Restoring scroll position or cursor location.** Precise viewport state within a file is out of scope for this change.
|
||||
- **Restoring code diff panes.** `CodeDiff` panes are tied to ephemeral AI actions and are not meaningful to restore across sessions.
|
||||
- **Restoring code review panes.** Code review panes already have their own restoration path and are out of scope.
|
||||
- **Remote file restoration.** Only locally-accessible files are restored. Remote/SSH file paths are skipped.
|
||||
|
||||
## User Experience
|
||||
|
||||
### Code editor panes
|
||||
|
||||
1. **Quit with open code panes.** The user has one or more code editor panes open, some containing multiple file tabs. When the user quits Warp (Cmd+Q or window close), the state of each code pane is persisted, including:
|
||||
- The list of open file paths (in tab order).
|
||||
- Which tab is active.
|
||||
- Whether each tab is a preview tab.
|
||||
2. **Restart and restore.** On relaunch, each persisted code pane reappears in the same position in the tab/pane tree. All file tabs within the pane are reopened. The previously active tab is focused.
|
||||
3. **File no longer exists.** If a file was deleted between quit and relaunch, the corresponding tab is still created but the editor shows its standard file-not-found / error state. Other valid tabs in the same pane are unaffected.
|
||||
4. **Code pane with no valid file.** If a code pane was open to a file that can no longer be located, the pane is restored showing an empty/error state rather than being dropped entirely.
|
||||
5. **Deduplication.** If the same file was open in two different code panes (in different tabs), both panes are restored independently. The `CodeManager` already handles deduplication within a single pane group — this behavior is preserved.
|
||||
6. **Reopening from the same surface.** If a restored pane originally came from the file tree, reopening that same file from the file tree should focus the restored pane rather than create a duplicate. The restored pane should preserve the source behavior needed for that workflow.
|
||||
|
||||
### Markdown file panes
|
||||
|
||||
1. **Already restored.** Markdown files opened in `FileNotebookView` (the rendered markdown viewer) are already persisted and restored via the `NotebookPaneSnapshot::LocalFileNotebook` path. No change is needed for basic restoration.
|
||||
2. **Display mode.** The markdown display mode (rendered vs. raw) should be preserved. If it was in "raw" (code editor) mode, it should reopen in raw mode, and vice versa.
|
||||
|
||||
### Edge cases
|
||||
|
||||
- **Empty code pane (no file open).** A code pane with no file loaded (e.g. a new unsaved buffer with no path) is not restored. This matches the existing behavior for empty notebook panes.
|
||||
- **Binary files.** If a persisted path points to a binary file, the editor shows its standard binary-file handling. This is existing behavior and unchanged.
|
||||
- **Permissions errors.** If a file exists but cannot be read, the editor shows its standard error state. This is existing behavior and unchanged.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. When the user quits Warp with one or more code editor panes open and relaunches, all code panes are restored in the correct position within the pane tree (correct window, correct tab, correct split position).
|
||||
2. Multiple file tabs within a single code pane are all restored, in the correct order, with the correct active tab.
|
||||
3. Markdown file panes (`FileNotebookView`) continue to be restored correctly (this is already working and must not regress).
|
||||
4. A code pane whose persisted file path no longer exists on disk is still restored (showing an error/empty state) rather than being silently dropped.
|
||||
5. The pane tree structure is not corrupted by code pane restoration failures — a single invalid file path does not prevent other panes from being restored.
|
||||
|
||||
## Validation
|
||||
|
||||
1. **Manual test — single code pane, single file.** Open a file in the code editor, quit Warp, relaunch. Verify the file is reopened in the same tab/pane position.
|
||||
2. **Manual test — multi-tab code pane.** Open 3 files in the same code pane (as tabs), set the 2nd tab as active, quit, relaunch. Verify all 3 tabs are restored with the 2nd tab active.
|
||||
3. **Manual test — split pane with code + terminal.** Have a horizontal split with a terminal on the left and a code editor on the right, quit, relaunch. Verify both panes are restored in the correct split positions.
|
||||
4. **Manual test — deleted file.** Open a file, quit, delete the file from disk, relaunch. Verify the code pane still appears (with an error state for the missing file) and does not crash the restoration.
|
||||
5. **Manual test — markdown viewer.** Open a markdown file in the rendered markdown viewer, quit, relaunch. Verify the file is shown in the same display mode.
|
||||
6. **Unit/integration tests.** The snapshot→restore round trip for `CodePaneSnapShot` should be covered by tests that serialize a code pane snapshot and verify the code pane is created on restoration.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Should preview tabs be restored?** Preview tabs (file tree single-click) are ephemeral by design. This spec proposes restoring them as preview tabs to preserve user context, but an alternative is to drop preview-only tabs on restoration to keep the session clean.
|
||||
@@ -0,0 +1,151 @@
|
||||
# TECH.md — Restore Open Files and Markdown Editors
|
||||
|
||||
**GitHub Issue:** [warpdotdev/warp-external#371](https://github.com/warpdotdev/warp-external/issues/371)
|
||||
**Product Spec:** `specs/GH371/product.md`
|
||||
|
||||
## Problem
|
||||
|
||||
Code editor panes were persisted to SQLite on quit but restoration was disabled — the `restore_pane_leaf` function returned `Err("Can't restore code panes")` for `LeafContents::Code`, causing the code pane to be silently dropped from the restored pane tree.
|
||||
|
||||
Additionally, the original snapshot only captured a single file path (the active tab's path), but `CodeView` supports multiple open tabs (`tab_group: Vec<TabData>`). Multi-tab state was lost.
|
||||
|
||||
Markdown file panes (`FileNotebookView` via `NotebookPaneSnapshot::LocalFileNotebook`) are already correctly persisted and restored — no changes were needed for basic markdown restoration.
|
||||
|
||||
## Relevant Code
|
||||
|
||||
- `app/src/pane_group/mod.rs (1676-1679)` — `restore_pane_leaf`: the `LeafContents::Code` match arm that returns an error instead of restoring
|
||||
- `app/src/pane_group/pane/code_pane.rs:210-212` — `CodePane::snapshot()`: serializes only the active tab's path
|
||||
- `app/src/pane_group/pane/code_pane.rs:38-45` — `CodePane::new()`: constructor that takes a `CodeSource` and optional line/column
|
||||
- `app/src/app_state.rs:197-210` — `CodePaneTabSnapshot` struct and `CodePaneSnapShot::Local` enum with `tabs`, `active_tab_index`, and `source`
|
||||
- `app/src/persistence/sqlite.rs (1124-1133)` — `save_pane_state`: writes the code pane to SQLite
|
||||
- `app/src/persistence/sqlite.rs (2423-2431)` — reads code pane from SQLite on startup
|
||||
- `crates/persistence/src/schema.rs (115-121)` — `code_panes` table schema
|
||||
- `crates/persistence/src/model.rs (601-606)` — `NewCodePane` insert model
|
||||
- `crates/persistence/src/model.rs (430-437)` — `CodePane` queryable model
|
||||
- `app/src/code/view.rs:230-239` — `CodeView` struct with `tab_group` and `active_tab_index`
|
||||
- `app/src/code/editor_management.rs:103-131` — `CodeSource` enum
|
||||
- `app/src/pane_group/pane/file_pane.rs:152-155` — `FilePane::snapshot()`: already works
|
||||
|
||||
## Current State
|
||||
|
||||
Persistence and restoration of code panes are both implemented. `CodePaneSnapShot::Local` stores all open tabs, the active tab index, and the `CodeSource`:
|
||||
|
||||
```rust
|
||||
pub struct CodePaneTabSnapshot {
|
||||
pub path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub enum CodePaneSnapShot {
|
||||
Local {
|
||||
tabs: Vec<CodePaneTabSnapshot>,
|
||||
active_tab_index: usize,
|
||||
source: Option<CodeSource>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`restore_pane_leaf` destructures this single variant to reconstruct the `CodePane` via `CodeView::restore()`, which reopens each tab and selects the active one.
|
||||
|
||||
Markdown file panes (`FilePane`/`FileNotebookView`) continue to work via `NotebookPaneSnapshot::LocalFileNotebook`.
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### Implementation
|
||||
|
||||
Code pane restoration and multi-tab persistence were implemented together.
|
||||
|
||||
#### Snapshot model
|
||||
|
||||
`CodePaneSnapShot` has a single `Local` variant that holds all tabs, the active tab index, and the full `CodeSource`:
|
||||
|
||||
```rust
|
||||
pub struct CodePaneTabSnapshot {
|
||||
pub path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub enum CodePaneSnapShot {
|
||||
Local {
|
||||
tabs: Vec<CodePaneTabSnapshot>,
|
||||
active_tab_index: usize,
|
||||
source: Option<CodeSource>,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The `source` field stores the full `CodeSource` enum (serialized as JSON in SQLite) so that restored panes retain the correct source semantics. Variants whose extra data cannot be reconstructed at restore time fall back to `CodeSource::FileTree` if a path is available.
|
||||
|
||||
#### Snapshot creation
|
||||
|
||||
`CodePane::snapshot()` iterates the tab group, skipping preview tabs, and stores the source:
|
||||
|
||||
```rust
|
||||
LeafContents::Code(CodePaneSnapShot::Local {
|
||||
tabs,
|
||||
active_tab_index,
|
||||
source,
|
||||
})
|
||||
```
|
||||
|
||||
#### SQLite persistence
|
||||
|
||||
Tabs are stored in a dedicated `code_pane_tabs` table (one row per tab, ordered by `tab_index`). The legacy `local_path` column on `code_panes` is still written for backward compatibility. The `CodeSource` is serialized as JSON into a `source` column on `code_panes`.
|
||||
|
||||
#### Restoration
|
||||
|
||||
In `restore_pane_leaf`, the `LeafContents::Code` arm (gated behind `#[cfg(feature = "local_fs")]`) destructures the snapshot and calls `CodeView::restore(&tabs, active_tab_index, source, ctx)` to rebuild the pane with all tabs.
|
||||
|
||||
### Phase 3: Markdown display mode preservation (optional enhancement)
|
||||
|
||||
The `FileNotebookView` display mode (rendered vs. raw) is not currently persisted. To support this:
|
||||
|
||||
1. Add a `display_mode` field to `NotebookPaneSnapshot::LocalFileNotebook`.
|
||||
2. Persist via an optional column in `notebook_panes` (or encoded in `local_path` metadata).
|
||||
3. On restore, pass the display mode to `FileNotebookView`.
|
||||
|
||||
This is lower priority since the rendered mode is the default and switching modes on restore is a single click.
|
||||
|
||||
## End-to-End Flow
|
||||
|
||||
### Quit (persist)
|
||||
|
||||
```
|
||||
User quits → Workspace::snapshot_app_state()
|
||||
→ PaneGroup::snapshot() walks the pane tree
|
||||
→ CodePane::snapshot() → CodePaneSnapShot::Local { tabs, active_tab_index, source }
|
||||
→ save_app_state() writes to SQLite
|
||||
→ code_panes row + code_pane_tabs rows
|
||||
```
|
||||
|
||||
### Launch (restore)
|
||||
|
||||
```
|
||||
App starts → read_sqlite_data()
|
||||
→ reads code_panes + code_pane_tabs → CodePaneSnapShot::Local
|
||||
→ restore_pane_leaf()
|
||||
→ CodeView::restore(&tabs, active_tab_index, source, ctx)
|
||||
→ pane inserted into pane tree at correct position
|
||||
```
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
1. **Stale file paths.** A persisted path may no longer exist. The `CodeView`/`LocalCodeEditorView` already handles this gracefully (shows file-not-found state). The pane is created regardless. No extra handling is needed.
|
||||
|
||||
2. **CodeManager deduplication.** `CodePane::pre_attach()` checks `CodeManager` for duplicate paths in the same tab. During restoration, panes are created and attached in tree order, so the first pane to attach with a given path "wins". Subsequent panes with the same path would be deduplicated to the first. This matches existing runtime behavior and is acceptable.
|
||||
|
||||
3. **Migration safety.** The new DB columns are nullable, so the migration is backward-compatible. Existing databases with only `local_path` continue to work.
|
||||
|
||||
4. **Preview tabs and `pre_attach`.** Restored preview tabs may trigger `pre_attach` deduplication if the file is already open elsewhere. This is the same behavior as at runtime and acceptable.
|
||||
|
||||
## Testing and Validation
|
||||
|
||||
1. **Unit test for snapshot round-trip.** Test that `CodePane::snapshot()` produces a `CodePaneSnapShot` that, when written to and read from SQLite, matches the original data (single tab and multi-tab).
|
||||
|
||||
2. **Pane tree integrity test.** Verify that restoring a pane tree containing a mix of terminal, code, and notebook panes produces the correct tree structure with all panes present.
|
||||
|
||||
3. **Manual validation.** Follow the manual test steps in the product spec.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- **Scroll position and cursor restoration.** Could be added to `CodePaneTabSnapshot` in the future.
|
||||
- **Unsaved buffer content.** Persisting dirty buffers is significantly more complex and out of scope.
|
||||
- **Markdown display mode.** Phase 3 is optional and can be a separate PR.
|
||||
Reference in New Issue
Block a user