# 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` 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` — tab color, applied after tab creation (matching the launch config pattern in `open_launch_config_window`). - `panes: Vec` — 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::()`. 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, 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.