Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
# Onboarding Tab Config Modal — Product Spec
|
||||
|
||||
Linear: [APP-3680](https://linear.app/warpdotdev/issue/APP-3680/onboarding-tab-config-flow-new-tab-config-created-for-user)
|
||||
Figma: [House of Agents — node 7077-23101](https://figma.com/design/CsBdBW4YoLgSAbr5eSkwV6/House-of-Agents?node-id=7077-23101&m=dev)
|
||||
|
||||
## Problem
|
||||
|
||||
After completing onboarding, users land in an empty terminal tab with no guidance on configuring their first working session. There is no streamlined way to pick a session type (terminal vs. agent), choose a project directory, enable worktree support, and optionally persist that setup as a reusable tab config — all in a single flow.
|
||||
|
||||
## Summary
|
||||
|
||||
A new modal ("Create your default tab config") appears after onboarding completes, overlayed on the terminal. It collects three inputs — session type, directory, and worktree preference — then creates a persistent tab config TOML in `~/.warp/tab_configs/` and opens it in the current tab.
|
||||
|
||||
## Goals
|
||||
|
||||
- Let users configure their first session immediately after onboarding in a single modal.
|
||||
- Support Built in agent (Oz), third-party CLI agents (Claude, Codex, Gemini), and Terminal as session types.
|
||||
- Always persist the configuration as a reusable tab config TOML in `~/.warp/tab_configs/`.
|
||||
- Keep the modal implementation reusable so it can be surfaced in other contexts later (e.g., from a menu or command palette).
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Worktree name generation (Moira is handling this in parallel; hardcode the branch name for now).
|
||||
- Editing or managing existing tab configs from this modal.
|
||||
- Showing this modal on every new tab — it only appears post-onboarding in the open source onboarding rev.
|
||||
- Web/WASM support — this is local-only (`local_fs`).
|
||||
|
||||
## User Experience
|
||||
|
||||
### When the modal appears
|
||||
|
||||
The modal appears once, immediately after the user completes the onboarding slide flow. It only appears when `OpenWarpNewSettingsModes` is enabled — this is the new onboarding path. Users on the old onboarding flow never see this modal. It is rendered as a centered overlay on top of the terminal workspace (not as a full-screen onboarding slide). The user cannot interact with the terminal behind it while the modal is open.
|
||||
|
||||
### Modal layout (per Figma)
|
||||
|
||||
- **Title:** "Create your default tab config"
|
||||
- **Subtitle:** "Select if you'd like to work in the terminal versus with an agent of your choosing."
|
||||
- **Session type:** A row of selectable pill-style buttons that wrap. Options (in order): Built in agent, Claude, Codex, Gemini, Terminal. Only one can be selected at a time. Built in agent is selected by default.
|
||||
- **Select directory:** A button that opens a native OS folder picker. Displays the selected path left-aligned (defaults to `~`). Text is semibold, no folder icon.
|
||||
- **Enable worktree support:** A checkbox. Disabled with a tooltip ("Select a git repository to enable worktree support") when the selected directory is not a git repository. Unchecked by default.
|
||||
- **"Get warping" button:** Primary action button with Enter keyboard shortcut.
|
||||
|
||||
### Session type behavior
|
||||
|
||||
Each session type determines what happens when the user clicks "Get warping":
|
||||
|
||||
- **Terminal:** Opens a terminal session in the selected directory. No command is auto-run.
|
||||
- **Oz:** Opens the tab into agent view / Oz agent mode (not a CLI command). The tab starts in the selected directory with the Oz agent UI active.
|
||||
- **Claude / Codex / Gemini:** Opens a terminal session in the selected directory and auto-runs the corresponding CLI command (`claude`, `codex`, `gemini`).
|
||||
|
||||
### Directory selection
|
||||
|
||||
Clicking the directory button opens a native OS folder picker (folders only, same as the existing project slide picker). After selection, the button text updates to show the selected path. The default before any selection is `~` (the user's home directory).
|
||||
|
||||
### Enable worktree support
|
||||
|
||||
When checked and a git repo is selected, the session (and tab config, if saved) will include git worktree creation commands. The worktree branch name is hardcoded for now (e.g., `"my-feature-branch"`).
|
||||
|
||||
**Disabled state:** The checkbox is visually disabled and non-interactive when the selected directory does not contain a `.git` directory (or is not inside a git repo). A tooltip explains: "Select a git repository to enable worktree support." If the user changes the directory from a git repo to a non-git directory, the checkbox unchecks automatically and becomes disabled.
|
||||
|
||||
### "Get warping"
|
||||
|
||||
Clicking "Get warping" always saves a tab config and opens it:
|
||||
|
||||
1. Writes a new TOML file to `~/.warp/tab_configs/`. The file is named `startup_config.toml` (or `startup_config_1.toml`, `startup_config_2.toml`, etc. if the name is taken).
|
||||
2. The TOML file contains:
|
||||
- `name = "Startup Config"` (or with a numeric suffix matching the file name).
|
||||
- A single `[[panes]]` entry with `type`, `cwd`, and optional `commands` (see "Tab config TOML generation" below).
|
||||
- If worktree is enabled, `[params]` section with `worktree_branch_name` (type `text`, default `"my-feature-branch"`).
|
||||
3. Dismisses the modal.
|
||||
4. Opens the newly created tab config in the current tab (replacing it), using the same flow as `open_tab_config` — which means if the config has params (worktree case), the params modal appears first.
|
||||
5. If write fails, falls back to opening the tab config without persisting it.
|
||||
|
||||
### Tab config TOML generation
|
||||
|
||||
The TOML structure depends on the combination of selections:
|
||||
|
||||
**Terminal only (no worktree):**
|
||||
```toml
|
||||
name = "Startup Config"
|
||||
|
||||
[[panes]]
|
||||
id = "main"
|
||||
type = "terminal"
|
||||
cwd = "/absolute/path/to/dir"
|
||||
```
|
||||
|
||||
**CLI agent (e.g., Claude), no worktree:**
|
||||
```toml
|
||||
name = "Startup Config"
|
||||
|
||||
[[panes]]
|
||||
id = "main"
|
||||
type = "terminal"
|
||||
cwd = "/absolute/path/to/dir"
|
||||
commands = ["claude"]
|
||||
```
|
||||
|
||||
**Terminal + worktree:**
|
||||
```toml
|
||||
name = "Startup Config"
|
||||
title = "{{worktree_branch_name}}"
|
||||
|
||||
[[panes]]
|
||||
id = "main"
|
||||
type = "terminal"
|
||||
cwd = "/absolute/path/to/dir"
|
||||
worktree_name_autogenerated = true
|
||||
commands = [
|
||||
"git worktree add -b {{worktree_branch_name}} ../{{worktree_branch_name}}",
|
||||
"cd ../{{worktree_branch_name}}",
|
||||
]
|
||||
|
||||
[params.worktree_branch_name]
|
||||
type = "text"
|
||||
description = "New worktree branch name"
|
||||
default = "my-feature-branch"
|
||||
```
|
||||
|
||||
**CLI agent + worktree:**
|
||||
```toml
|
||||
name = "Startup Config"
|
||||
title = "{{worktree_branch_name}}"
|
||||
|
||||
[[panes]]
|
||||
id = "main"
|
||||
type = "terminal"
|
||||
cwd = "/absolute/path/to/dir"
|
||||
worktree_name_autogenerated = true
|
||||
commands = [
|
||||
"git worktree add -b {{worktree_branch_name}} ../{{worktree_branch_name}}",
|
||||
"cd ../{{worktree_branch_name}}",
|
||||
"claude",
|
||||
]
|
||||
|
||||
[params.worktree_branch_name]
|
||||
type = "text"
|
||||
description = "New worktree branch name"
|
||||
default = "my-feature-branch"
|
||||
```
|
||||
|
||||
**Oz session:** Uses `type = "agent"` on the pane, which causes the tab to open in agent view via `PaneMode::Agent`. The `DefaultSessionMode` setting is also set to `Agent` so future new tabs default to agent view. When the feature flag for this new onboarding modal is off, the existing behavior (where selecting `AgentDrivenDevelopment` sets the mode to `Agent`) remains unchanged.
|
||||
```toml
|
||||
name = "Startup Config"
|
||||
|
||||
[[panes]]
|
||||
id = "main"
|
||||
type = "agent"
|
||||
cwd = "/absolute/path/to/dir"
|
||||
```
|
||||
|
||||
### Keyboard interaction
|
||||
|
||||
- **Enter:** Activates "Get warping" (same as clicking the button).
|
||||
- **Escape:** Closes the modal without taking any action — the user lands on an empty terminal tab.
|
||||
- Arrow keys / Tab: Navigate between session type pills and checkboxes.
|
||||
|
||||
### Dismissal
|
||||
|
||||
The modal can be dismissed by:
|
||||
- Clicking "Get warping" (takes action).
|
||||
- Pressing Escape (no action taken).
|
||||
- Clicking outside the modal (no action taken).
|
||||
- There is no explicit close/X button in the Figma.
|
||||
|
||||
### Reusability
|
||||
|
||||
The modal's core logic (collecting session type, directory, worktree preference, save preference) should be a self-contained view that produces a structured output (e.g., a struct with all selected values). The caller decides what to do with that output. For the onboarding flow, the caller replaces the current tab and optionally writes a tab config. This makes the modal reusable in other contexts.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. After completing onboarding, the modal appears overlayed on the terminal.
|
||||
2. Selecting "Terminal" + a directory + "Get warping" writes a tab config TOML to `~/.warp/tab_configs/` and replaces the current tab with a session in that directory.
|
||||
3. Selecting a CLI agent + a directory + "Get warping" writes a tab config TOML, replaces the current tab, sets the working directory, and auto-runs the agent CLI command.
|
||||
4. Selecting "Built in agent" + a directory + "Get warping" writes a tab config TOML, replaces the current tab, and opens Oz agent view in that directory.
|
||||
5. The written TOML appears in the + tab menu.
|
||||
6. The worktree checkbox is disabled when the selected directory is not a git repo, and enabled when it is.
|
||||
7. Checking worktree + save produces a TOML with `{{worktree_branch_name}}` params and worktree commands.
|
||||
8. File naming avoids collisions (appends `_1`, `_2`, etc.).
|
||||
9. Escape dismisses the modal without side effects.
|
||||
10. The modal does not appear again after the user completes it once (or dismisses via Escape).
|
||||
|
||||
## Validation
|
||||
|
||||
- **Unit tests:** TOML generation for each combination of session type × worktree × directory produces the expected output.
|
||||
- **Manual testing:** Walk through the full onboarding flow, verify the modal appears, select each session type, toggle worktree and save, confirm correct tab behavior.
|
||||
- **Tab config integration:** After saving, verify the config appears in the + menu, can be opened, and the params modal works for worktree configs.
|
||||
- **UI verification:** Compare the rendered modal against the Figma mock (session type pills, directory button, checkbox, CTA button).
|
||||
|
||||
## Resolved Decisions
|
||||
|
||||
1. **Oz in tab config TOML:** Oz uses `type = "agent"` on the pane node, which maps to `PaneMode::Agent` and causes the pane to enter agent view automatically. `DefaultSessionMode` is also set to `Agent` so future new tabs default to agent view. This must not break the existing behavior when the feature flag for this modal is disabled.
|
||||
2. **Worktree base branch:** The `{{branch}}` param is omitted. The `git worktree add` command uses HEAD implicitly (no base branch argument). The worktree branch name is hardcoded to `"my-feature-branch"` as a default. **TODO(moira):** Once worktree name generation is ready, replace the hardcoded `"my-feature-branch"` default with the generated name and revisit whether a base branch param should be added.
|
||||
3. **Session type list:** Hardcoded to Built in agent, Claude, Codex, Gemini, Terminal (in that order). Not dynamically derived from the `CLIAgent` enum.
|
||||
@@ -0,0 +1,251 @@
|
||||
# Onboarding Tab Config Modal — Tech Spec
|
||||
|
||||
Product spec: `specs/APP-3680/PRODUCT.md`
|
||||
|
||||
## Current State
|
||||
|
||||
**Onboarding completion:** `handle_agent_onboarding_event` (`app/src/root_view.rs:2062`) handles `OnboardingCompleted`. It applies settings, then calls `start_agent_onboarding_tutorial` on the workspace, which dispatches the legacy guided-tour flow to the terminal view.
|
||||
|
||||
**Tab replacement:** There is no existing "replace current tab" API. `add_tab_with_pane_layout` (`app/src/workspace/view.rs:8157`) always adds a new tab. `close_tab` (`app/src/workspace/view.rs:7805`) removes by index.
|
||||
|
||||
**Tab config TOML writing:** `create_and_open_new_tab_config` (`app/src/workspace/view.rs:4739`) writes the template to `~/.warp/tab_configs/` via `find_unused_tab_config_path` (`app/src/user_config/mod.rs:173`). The filesystem watcher (`app/src/user_config/native.rs:266`) auto-reloads tab configs.
|
||||
|
||||
**DefaultSessionMode:** `DefaultSessionMode` (`app/src/settings/ai.rs:252`) has `Terminal` and `Agent` variants. Set during onboarding via `apply_agent_settings` (`app/src/settings/onboarding.rs:122`).
|
||||
|
||||
**Feature flags:** `TabConfigs`, `AgentOnboarding`, `OpenWarpNewSettingsModes`, and `AgentView` are the relevant flags (`warp_core/src/features.rs`). We'll add a new flag for this modal.
|
||||
|
||||
## Relevant Code
|
||||
|
||||
- `app/src/root_view.rs:2062` — `handle_agent_onboarding_event`, where `OnboardingCompleted` is handled
|
||||
- `app/src/workspace/view/onboarding.rs` — `OnboardingTutorial` enum and `start_agent_onboarding_tutorial`
|
||||
- `app/src/workspace/view.rs:8157` — `add_tab_with_pane_layout`
|
||||
- `app/src/workspace/view.rs:4688-4734` — `open_tab_config_with_params` and `open_tab_config`
|
||||
- `app/src/workspace/view.rs:4739` — `create_and_open_new_tab_config`
|
||||
- `app/src/tab_configs/tab_config.rs` — `TabConfig`, `TabConfigPaneNode`, `TabConfigPaneType`, `render_tab_config`
|
||||
- `app/src/user_config/mod.rs:173` — `find_unused_tab_config_path`
|
||||
- `app/src/settings/onboarding.rs:122` — `apply_agent_settings`, where `DefaultSessionMode` is set
|
||||
- `app/src/settings/ai.rs:252` — `DefaultSessionMode` enum
|
||||
- `app/src/terminal/cli_agent.rs:94` — `CLIAgent::command_prefix()`
|
||||
- `app/src/modal.rs` — `Modal<T>` and `ModalViewState<T>` pattern
|
||||
- `app/src/workspace/one_time_modal_model.rs` — one-time modal tracking pattern
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
### 1. New feature flag
|
||||
|
||||
No new feature flag needed. Gate the modal behind both `FeatureFlag::OpenWarpNewSettingsModes` (this is the new onboarding path) and `FeatureFlag::TabConfigs` (the modal produces a tab config, so the tab config system must be enabled). Both flags must be on for the modal to appear. When either is off, the old onboarding flow runs unchanged.
|
||||
|
||||
### 2. Add `Serialize` to tab config types
|
||||
|
||||
`TabConfigParamType` already derives both `Serialize` and `Deserialize`. Add `Serialize` to:
|
||||
- `TabConfigPaneType` — so pane type is included in serialized TOML
|
||||
- `TabConfigPaneNode` — so pane nodes can be serialized
|
||||
- `TabConfig` — so full configs can be written to disk
|
||||
|
||||
These are simple data structs — adding `Serialize` is a natural extension that enables writing tab configs programmatically (not just reading them from TOML).
|
||||
|
||||
### 3. `SessionType` enum
|
||||
|
||||
Add a small enum in `app/src/tab_configs/mod.rs` (or a new submodule) that reuses `CLIAgent`:
|
||||
|
||||
```rust
|
||||
pub enum SessionType {
|
||||
Terminal,
|
||||
Oz,
|
||||
CliAgent(CLIAgent),
|
||||
}
|
||||
```
|
||||
|
||||
This wraps the existing `CLIAgent` (`app/src/terminal/cli_agent.rs:82`) and adds Terminal/Oz as first-class variants. `SessionType` provides helpers:
|
||||
- `command_prefix() -> Option<&str>` — delegates to `CLIAgent::command_prefix()` for CLI agents, `None` for Terminal/Oz.
|
||||
- `icon() -> Icon` — delegates to `CLIAgent::icon()`, with `Icon::Terminal` for Terminal and `Icon::Oz` for Oz.
|
||||
- `display_name() -> &str` — delegates to `CLIAgent::display_name()` for CLI agents.
|
||||
- `pill_label() -> &str` — short label for the modal pills (e.g., "Claude" instead of "Claude Code").
|
||||
|
||||
### 4. `TabConfig` builder: `build_tab_config`
|
||||
|
||||
Add a function in `app/src/tab_configs/session_config.rs`:
|
||||
|
||||
```rust
|
||||
fn build_tab_config(
|
||||
session_type: &SessionType,
|
||||
directory: &Path,
|
||||
enable_worktree: bool,
|
||||
) -> TabConfig
|
||||
```
|
||||
|
||||
This builds a `TabConfig` with a single `TabConfigPaneNode` using the new flat `[[panes]]` schema. The logic:
|
||||
- Sets `name = "Startup Config"`
|
||||
- Creates a single pane with `id = "main"`, `cwd` set to the absolute directory path
|
||||
- Sets `pane_type` to `TabConfigPaneType::Agent` for Oz, `TabConfigPaneType::Terminal` for Terminal and CLI agents
|
||||
- Appends worktree commands + `worktree_branch_name` param when `enable_worktree` is true, with `worktree_name_autogenerated = true`
|
||||
- Appends `session_type.command_prefix()` to commands when it's a CLI agent
|
||||
- Sets `title = "{{worktree_branch_name}}"` when worktree is enabled
|
||||
|
||||
Pure function, easily unit-tested. The existing `render_tab_config` and `TabConfig::default_param_values` work on its output unchanged.
|
||||
|
||||
### 5. `write_tab_config`
|
||||
|
||||
Add a function in `app/src/tab_configs/` to serialize and write:
|
||||
|
||||
```rust
|
||||
fn write_tab_config(config: &TabConfig, dir: &Path) -> Result<PathBuf>
|
||||
```
|
||||
|
||||
Uses `toml::to_string_pretty(config)` (now possible with `Serialize`), finds an unused path via the shared `find_unused_toml_path(dir, "startup_config")` helper (generalized from `find_unused_tab_config_path` in `user_config/mod.rs`), and writes. Returns the path. The filesystem watcher auto-reloads.
|
||||
|
||||
### 6. Modal view: `SessionConfigModal`
|
||||
|
||||
Create `app/src/tab_configs/session_config_modal.rs`. This is a self-contained `View` that renders the Figma layout:
|
||||
- Session type pill buttons using `Wrap::row()` for flex-wrap (hardcoded list in order: Built in agent (Oz), Claude, Codex, Gemini, Terminal)
|
||||
- Directory picker button (opens native `FilePickerConfiguration::folders_only()`), displays `~` via `warp_util::path::user_friendly_path()`, left-aligned text with semibold weight, no folder icon
|
||||
- "Enable worktree support" checkbox (disabled when directory is not a git repo)
|
||||
- "Get warping" button using `ActionButton` with `PrimaryTheme` and `with_full_width(true)`, includes Enter keystroke badge via `with_keybinding()`
|
||||
|
||||
The modal always saves a tab config — there is no "Save as tab config" checkbox.
|
||||
|
||||
**State:** The modal holds:
|
||||
- `selected_session_type: SessionType`
|
||||
- `selected_directory: PathBuf` (default: home dir)
|
||||
- `is_git_repo: bool` (recomputed on directory change via `std::path::Path::join(".git").is_dir()`)
|
||||
- `enable_worktree: bool`
|
||||
- `MouseStateHandle` for each interactive element
|
||||
|
||||
**Output struct:** The modal collects its inputs into a plain struct:
|
||||
```rust
|
||||
pub struct SessionConfigSelection {
|
||||
pub session_type: SessionType,
|
||||
pub directory: PathBuf,
|
||||
pub enable_worktree: bool,
|
||||
}
|
||||
```
|
||||
|
||||
**Event:** The modal emits:
|
||||
```rust
|
||||
pub enum SessionConfigModalEvent {
|
||||
Completed(SessionConfigSelection),
|
||||
Dismissed,
|
||||
}
|
||||
```
|
||||
|
||||
The caller converts the selection into a `TabConfig` via `build_tab_config` when needed. The modal does not know what the caller does with the selection.
|
||||
|
||||
**Git repo detection:** When the directory changes, check if `selected_directory.join(".git").is_dir()` or walk up parents looking for `.git`. If not a git repo, set `is_git_repo = false`, force `enable_worktree = false`, and render the worktree checkbox as disabled with a tooltip.
|
||||
|
||||
### 7. Hosting the modal in `Workspace`
|
||||
|
||||
Add to `Workspace`:
|
||||
```rust
|
||||
session_config_modal: ModalViewState<Modal<SessionConfigModal>>,
|
||||
```
|
||||
|
||||
Follow the same pattern as `tab_config_params_modal` (`app/src/workspace/view.rs:4723`). The workspace subscribes to `SessionConfigModalEvent` and handles both variants.
|
||||
|
||||
### 8. Handling `SessionConfigModalEvent::Completed`
|
||||
|
||||
The workspace handler in a new method `handle_session_config_completed`:
|
||||
|
||||
**Step 1: Apply DefaultSessionMode.** If `session_type == Oz`, set `DefaultSessionMode::Agent`. Otherwise, set `DefaultSessionMode::Terminal`. (Only when the feature flag is on — when off, the existing onboarding path handles this.)
|
||||
|
||||
**Step 2: Build a `TabConfig`.** Call `build_tab_config(&selection.session_type, &selection.directory, selection.enable_worktree)`. This produces the canonical `TabConfig` regardless of the save path.
|
||||
|
||||
**Step 3: Open the tab.** Always save: call `write_tab_config(&config, &tab_configs_dir())` to persist the TOML, then call `open_tab_config(config)`, which handles the params modal flow for worktree configs (user gets to pick branch name). If write fails, fall back to `open_tab_config_with_params` without persisting.
|
||||
Agent view entry for Oz is handled automatically by `PaneMode::Agent` in the tab config pane node — `pane_tree_from_template` enters agent view when it sees `PaneMode::Agent`. No manual `enter_agent_view_on_active_tab()` call is needed.
|
||||
|
||||
**Step 4: Replace current tab.**
|
||||
**Step 4: Replace current tab.** After adding the new tab, use `remove_tab` directly (not `close_tab`) to remove the old empty tab. `close_tab` would trigger a window close when it's the last tab, but by this point there are always 2+ tabs since the new one was just added. The old tab is at `old_tab_index` (captured before step 3).
|
||||
|
||||
### 9. Triggering the modal after onboarding
|
||||
|
||||
In `handle_agent_onboarding_event` (`app/src/root_view.rs:2080`), after the existing `OnboardingCompleted` handling, when both `FeatureFlag::OpenWarpNewSettingsModes.is_enabled()` and `FeatureFlag::TabConfigs.is_enabled()`:
|
||||
|
||||
Instead of calling `start_agent_onboarding_tutorial` directly, dispatch a new `WorkspaceAction::ShowSessionConfigModal`. The workspace opens the modal. On `Completed`, the workspace replaces the tab and applies settings. On `Dismissed`, fall through to the existing tutorial path (or just leave the empty tab).
|
||||
|
||||
When either flag is off (old onboarding), the existing path (`start_agent_onboarding_tutorial`) runs unchanged.
|
||||
|
||||
|
||||
## End-to-End Flow
|
||||
|
||||
1. User completes onboarding slides → `OnboardingCompleted` fires.
|
||||
2. `root_view` applies settings, transitions to `Terminal` state with the workspace.
|
||||
3. `root_view` dispatches `WorkspaceAction::ShowSessionConfigModal` (flag-gated).
|
||||
4. Workspace opens `session_config_modal` as a centered overlay.
|
||||
5. User selects session type, picks directory, optionally toggles worktree, clicks "Get warping".
|
||||
6. Modal emits `SessionConfigModalEvent::Completed(selection)`.
|
||||
7. Workspace calls `handle_session_config_completed`:
|
||||
- Sets `DefaultSessionMode` if Oz.
|
||||
- Calls `build_tab_config` to produce a `TabConfig`.
|
||||
- Calls `write_tab_config` then `open_tab_config` (always saves).
|
||||
- Closes the old empty tab.
|
||||
8. Modal is dismissed. User is in their configured session.
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
**Risk: Breaking existing onboarding.** All new behavior is gated behind both `FeatureFlag::OpenWarpNewSettingsModes` and `FeatureFlag::TabConfigs`. When either is off, `handle_agent_onboarding_event` follows the identical code path as today. No changes to `OnboardingTutorial`, `SelectedSettings`, or `apply_onboarding_settings`.
|
||||
|
||||
**Risk: Tab index math when replacing.** Closing the wrong tab index would lose user work. Mitigated by: the old tab is always empty (just created by onboarding), and we close with `skip_confirmation = true`. We also use the tab index arithmetic described above, which can be validated in tests.
|
||||
|
||||
**Risk: Git repo detection on directory change.** Checking `.git` is synchronous I/O. For the onboarding modal (called once), this is acceptable. If reused in a hot path later, it should be made async.
|
||||
|
||||
**Risk: Adding `Serialize` to `TabConfig`.** Low risk — these are plain data structs with simple fields. Adding `Serialize` alongside existing `Deserialize` is a standard pattern. No behavioral change to existing deserialization paths.
|
||||
|
||||
## Testing and Validation
|
||||
|
||||
### `build_tab_config` (unit tests)
|
||||
These enforce the TOML generation rules from the product spec:
|
||||
- Terminal + directory, no worktree → `TabConfig` with `cwd` set, empty commands, no params.
|
||||
- CLI agent (Claude) + directory, no worktree → commands = `["claude"]`, no params.
|
||||
- Terminal + directory + worktree → commands include worktree creation + cd, params contain `worktree_branch_name` with default `"my-feature-branch"`, title = `"{{worktree_branch_name}}"`.
|
||||
- CLI agent (Gemini) + directory + worktree → commands include worktree creation + cd + `"gemini"` (in that order), params contain `worktree_branch_name`.
|
||||
- Oz + directory, no worktree → `cwd` set, `pane_type = Agent`, no commands, no params.
|
||||
- Oz + directory + worktree → `pane_type = Agent` with worktree commands, no agent CLI command.
|
||||
- Directory path is always absolute in `panes[0].cwd`.
|
||||
|
||||
### TOML round-trip (unit tests)
|
||||
- For each `build_tab_config` output, serialize via `toml::to_string_pretty`, deserialize back as `TabConfig`, verify all fields match.
|
||||
- Validates that `Serialize` on `TabConfig` produces TOML that the existing `Deserialize` path can read — catches any drift between the two.
|
||||
|
||||
### `write_tab_config` (unit tests with temp dir)
|
||||
- Write to an empty temp dir → file is `startup_config.toml`.
|
||||
- Write again → file is `startup_config_1.toml`.
|
||||
- Write a third time → file is `startup_config_2.toml`.
|
||||
- Written file content deserializes to a valid `TabConfig` matching the input.
|
||||
- Directory is created if it doesn't exist.
|
||||
|
||||
### `SessionType` helpers (unit tests)
|
||||
- `SessionType::Terminal.command_prefix()` → `None`.
|
||||
- `SessionType::Oz.command_prefix()` → `None`.
|
||||
- `SessionType::CliAgent(CLIAgent::Claude).command_prefix()` → `Some("claude")`.
|
||||
- Display names and icons return the expected values for each variant.
|
||||
|
||||
### `render_tab_config` integration (unit tests)
|
||||
These verify the full pipeline from `build_tab_config` → `render_tab_config` produces the correct `PaneTemplateType`:
|
||||
- Terminal + directory → `PaneTemplate` with correct `cwd`, empty commands.
|
||||
- CLI agent + directory → `PaneTemplate` with correct `cwd`, commands = `["claude"]`.
|
||||
- Worktree config with default param values → commands have `"my-feature-branch"` substituted in.
|
||||
|
||||
### Git repo detection (unit tests with temp dir)
|
||||
- Create a temp dir with `.git/` → `is_git_repo` returns true.
|
||||
- Temp dir without `.git/` → returns false.
|
||||
- Switching from a git dir to a non-git dir forces `enable_worktree` to false.
|
||||
|
||||
### DefaultSessionMode (unit test or integration)
|
||||
- Selecting Oz sets `DefaultSessionMode::Agent`.
|
||||
- Selecting Terminal sets `DefaultSessionMode::Terminal`.
|
||||
- Selecting a CLI agent sets `DefaultSessionMode::Terminal`.
|
||||
- When `OpenWarpNewSettingsModes` is off, `DefaultSessionMode` is not touched by this code path.
|
||||
|
||||
### Feature flag gating (integration)
|
||||
- When either `OpenWarpNewSettingsModes` or `TabConfigs` is off, `OnboardingCompleted` follows the old tutorial path — modal is never shown.
|
||||
- When both `OpenWarpNewSettingsModes` and `TabConfigs` are on, `OnboardingCompleted` dispatches `ShowSessionConfigModal`.
|
||||
|
||||
### UI verification
|
||||
- Compare rendered modal against Figma mock.
|
||||
- Verify worktree checkbox is visually disabled when directory is not a git repo.
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- **Worktree name generation:** Replace hardcoded `"my-feature-branch"` once Moira's worktree name generation is ready.
|
||||
- **Reusability:** Surface the modal from the + tab menu or command palette.
|
||||
- **Async git detection:** If the modal is reused in hot paths, make `.git` detection async.
|
||||
- **Programmatic tab config editing:** With `Serialize` on `TabConfig`, future features could read → modify → write tab configs (e.g., a tab config editor UI).
|
||||
Reference in New Issue
Block a user