Files

119 lines
7.4 KiB
Markdown

# Finalize TOML Schema for Tab Configs — Tech Spec
Linear: [APP-3575](https://linear.app/warpdotdev/issue/APP-3575/finalize-toml-schema-for-tab-configs)
## Problem
The recursive `[layout]` / `[[layout.panes]]` TOML format becomes unreadable at depth 3+. We need to replace it with a flat `[[panes]]` array where nodes reference children by string ID, add tab color support, and ship a bundled Oz skill for generating configs.
## Relevant code
- `app/src/tab_configs/tab_config.rs``TabConfig`, `TabConfigPaneNode`, `render_tab_config`, `resolve_pane_tree`
- `app/src/tab_configs/tab_config_tests.rs` — existing tests for parsing and rendering
- `app/src/tab_configs/mod.rs` — public exports
- `app/src/workspace/view.rs:4702-4780``open_tab_config_with_params`, `open_tab_config`, `create_and_open_new_tab_config`
- `app/src/launch_configs/launch_config.rs``PaneTemplateType`, `SplitDirection`, `CommandTemplate` (the output types tab configs produce)
- `app/src/pane_group/mod.rs:1181-1333``pane_tree_from_template` (consumes `PaneTemplateType` to create panes)
- `app/src/user_config/native.rs:228-232``load_tab_configs`
- `app/src/user_config/util.rs:167-180``parse_tab_config_dir_entry` (TOML parsing)
- `app/resources/tab_configs/new_tab_config_template.toml` — default template
- `resources/bundled/skills/tab-configs/SKILL.md` — bundled Oz skill
- `app/src/ai/skills/skill_manager.rs` — bundled skill loading from `resources/bundled/skills/`
## Current state
Tab configs are TOML files in `~/.warp/tab_configs/`. They previously used a recursive `TabConfigLayout` struct with nested `panes: Vec<TabConfigLayout>` for child splits, which became impractical at depth 3+.
There is no `color` field on `TabConfig` — launch configs support color via `TabTemplate.color` but tab configs don't.
There is no bundled skill for generating tab configs.
## Proposed changes
### 1. New struct: `TabConfigPaneNode`
A flat node in the `[[panes]]` array. Distinguished as split vs leaf by the presence of `split` + `children`. No `type` field — all leaves are terminal panes (a `type` discriminator will be added when non-terminal pane types are supported).
Fields: `id`, `pane_type` (serde-renamed to `type`), `split`, `children`, `is_focused`, `cwd`, `commands`, `worktree_name_autogenerated`.
`worktree_name_autogenerated` (bool, default false): when `true`, the param-fill UI auto-generates the worktree branch name instead of showing a free-text input. The app infers "is this a worktree config?" by scanning `commands` for `git worktree` — no separate `worktree` flag is needed.
### 1b. New enum: `TabConfigPaneType`
Terminal / Agent / Cloud — maps to `PaneMode` at render time.
### 1c. New enum: `PaneMode` (in `launch_config.rs`)
Terminal (default) / Agent / Cloud — added as a field on `PaneTemplateType::PaneTemplate` with `#[serde(default)]` so launch config deserialization is unaffected.
### 2. Updated `TabConfig`
Add two fields:
- `color: Option<AnsiColorIdentifier>` — tab color, applied after tab creation (matching the launch config pattern in `open_launch_config_window`).
- `panes: Vec<TabConfigPaneNode>` — flat pane list. The first entry is the root of the pane tree.
Remove the old `layout: TabConfigLayout` field and the `TabConfigLayout` struct entirely. The legacy `[layout]` format is no longer supported.
### 3. `resolve_pane_tree` function
Converts the flat `panes` list into a `PaneTemplateType` tree:
1. Index all nodes by ID. Reject duplicate IDs.
2. Root = first entry.
3. Recursively resolve: split nodes look up children by ID; leaf nodes produce `PaneTemplate { cwd, commands, is_focused }`.
4. Focus handling: if any pane has explicit `is_focused = true`, use it. Otherwise auto-focus the first leaf (matching legacy `render_layout` behavior).
5. On error (missing refs, <2 children), return `Err` and the caller falls back to a single empty terminal.
### 4. Updated `render_tab_config`
Always calls `resolve_pane_tree`. On error (empty panes, missing refs, missing `type` on a leaf, etc.), falls back to a single empty terminal pane with a logged warning. Leaf resolution maps `TabConfigPaneType` to `PaneMode` and sets it on the produced `PaneTemplate`.
### 5. Updated `open_tab_config_with_params` in workspace view
After `add_tab_with_pane_layout`, apply `tab_config.color` to the new tab's `selected_color` — the same pattern launch configs use at `workspace/view.rs:2386`.
### 6. Updated template
`app/resources/tab_configs/new_tab_config_template.toml` uses the new flat format. Active (uncommented) content is a single-pane terminal with `commands = []`. Commented examples show two-pane split, 2x2 grid, and parameterized configs.
### 7. Bundled Oz skill
`resources/bundled/skills/tab-configs/SKILL.md` contains the full schema reference, examples, validation rules, and common natural-language-to-layout mappings. The build script (`script/prepare_bundled_resources`) copies it into the app bundle automatically.
## End-to-end flow
1. User creates/edits a `.toml` file in `~/.warp/tab_configs/`.
2. Filesystem watcher in `WarpConfig` detects the change and calls `load_tab_configs`.
3. `parse_tab_config_dir_entry` calls `toml::from_str::<TabConfig>()`. The `panes` field deserializes from `[[panes]]` entries.
4. User selects the config from the `+` menu → `open_tab_config` is called.
5. If params exist, the param-fill modal opens. Otherwise `open_tab_config_with_params` is called directly.
6. `render_tab_config` builds param contexts, calls `resolve_pane_tree`, returns `(Option<String>, PaneTemplateType)`.
7. Workspace calls `add_tab_with_pane_layout(PanesLayout::Template(pane_template), ...)`.
8. `PaneGroup::pane_tree_from_template` recursively creates panes from the `PaneTemplateType` tree. For `PaneMode::Agent`, it creates a terminal session then enters agent mode. For `PaneMode::Cloud`, it creates an ambient agent pane via `create_ambient_agent_terminal`.
9. If `tab_config.color` is set, the tab's `selected_color` is updated.
## Risks and mitigations
- **Flat pane list validation**: Invalid configs (missing refs, cycles) are caught at render time with descriptive error messages logged and a fallback to a single terminal pane. The user's tab config file is never modified.
- **Skill generating invalid TOML**: The skill embeds validation rules and examples. The schema is simple enough (terminal-only, no type field) that generation errors are unlikely.
## Testing and validation
- Unit tests in `tab_config_tests.rs`:
- Parse single flat pane.
- Parse and render flat two-pane split.
- Parse and render flat 2x2 grid.
- Explicit `is_focused` honored.
- Auto-focus first leaf when no explicit focus.
- Invalid flat pane tree (missing child ref) returns error.
- Duplicate IDs rejected.
- Tab color deserialized correctly.
- Manual: build and run Warp, create tab configs, verify pane layouts open correctly.
- Manual: invoke the `tab-configs` skill, confirm it generates a working config.
## Follow-ups
- Add non-terminal pane types (notebook, code, settings, etc.) with a `type` field discriminator.
- Consider cycle detection in validation (currently not implemented — the recursive resolver will stack overflow on cycles, but configs are small enough that this is not a practical concern).
- Consider orphan pane warnings (panes not referenced by any `children` and not the root).
- Investigate whether the Oz skill should auto-open the created config in the user's editor.