# Workspace Split Pane Plan

## Purpose

Capture the agreed design for workspace-owned split terminals so the feature can be implemented later without redoing scope and architecture decisions.

## Agreed Scope

- Split layouts belong to the workspace, not the screen.
- A workspace may only be actively presented on one screen at a time.
- Split layouts should be visible the same way when that workspace is opened on another monitor.
- Nested splits are supported.
- Any two tabs may be joined into a split.
- Closing a pane causes its sibling to expand.
- Split layout must survive relaunch.

## Non-Goals

- Simultaneous live rendering of the same workspace on multiple screens.
- Full multi-user or collaborative state sync.
- Arbitrary freeform pane layout. The model is tree-based binary splits.
- Solving all interaction polish in the first pass.

## Current Constraint

The current workspace model is flat:

- `WorkspaceController` owns `[TerminalSession]`
- one tab is active at a time
- `ContentView` renders exactly one active `SwiftTermView`

This means split panes are not an additive UI feature. They require a workspace model refactor.

## Recommended Model

Do not model splits as "adjacent tabs with special behavior" internally.

Instead:

1. A workspace owns ordered top-level tabs.
2. Each top-level tab owns a pane tree.
3. Pane tree leaves own `TerminalSession` instances.
4. Pane tree internal nodes represent binary splits.
5. The tab bar can visually present grouped/joined tabs based on the active tab's pane composition.

This keeps the domain model clean while still allowing the desired visual metaphor.

## Proposed Domain Types

```swift
typealias WorkspaceTabID = UUID
typealias PaneID = UUID

struct WorkspaceTab: Identifiable, Equatable, Codable {
    var id: WorkspaceTabID
    var rootPane: PaneNode
    var selectedPaneID: PaneID?
    var titleMode: WorkspaceTabTitleMode
}

enum PaneNode: Equatable, Codable, Identifiable {
    case leaf(PaneLeaf)
    case split(PaneSplit)

    var id: PaneID { ... }
}

struct PaneLeaf: Equatable, Codable, Identifiable {
    var id: PaneID
    var sessionID: UUID
}

struct PaneSplit: Equatable, Codable, Identifiable {
    var id: PaneID
    var axis: SplitAxis
    var ratio: Double
    var first: PaneNode
    var second: PaneNode
}

enum SplitAxis: String, Codable {
    case horizontal
    case vertical
}
```

Notes:

- `ratio` should be persisted so user-adjusted divider positions survive relaunch.
- `selectedPaneID` allows focus-based commands such as split active pane, close active pane, and move focus.
- The persisted model should store session identity separately from runtime AppKit/SwiftTerm objects.

## Runtime Ownership

Global:

- workspace summaries
- workspace ordering
- workspace-to-screen assignment
- hotkeys

Workspace:

- ordered top-level tabs
- active top-level tab
- per-tab pane tree
- active/selected pane
- terminal sessions for pane leaves

Screen:

- which workspace is assigned
- notch open/close state
- geometry and transitions

This remains aligned with the existing workspace architecture.

## Persistence Strategy

Split layout must survive relaunch, so workspace persistence needs to grow beyond summary metadata.

Recommended approach:

1. Keep `WorkspaceSummary` for list metadata only.
2. Introduce a persisted `WorkspaceDocument` or `WorkspaceSnapshot` per workspace.
3. Persist:
   - top-level tabs
   - pane tree
   - active tab ID
   - selected pane ID
   - divider ratios
4. Do not attempt to persist shell process contents.
5. On relaunch, recreate sessions for pane leaves and restore layout structure only.

Important distinction:

- layout persistence is required
- terminal process continuity is not

## UI Model

### Main Content

The active top-level tab renders as a recursive split tree.

- leaf node -> one `SwiftTermView`
- split node -> `HSplitView`/`VSplitView` equivalent SwiftUI container with draggable divider

### Tab Bar

Top-level tabs remain the primary navigation unit.

Visual behavior:

- a normal unsplit tab looks like today
- a joined/split tab should look grouped
- grouped tabs should expose child pane titles visually inside the tab item

Recommended first-pass appearance:

- one outer tab pill per top-level tab
- inside that pill, show compact child title chips for each leaf pane
- highlight the selected pane's chip

This gives the "tabs are joined" feel without making pane leaves first-class top-level tabs in the data model.

## Command Model

New actions likely needed:

- split active pane horizontally
- split active pane vertically
- focus next pane
- focus previous pane
- close active pane
- resize focused split divider
- join tab A into tab B
- detach pane into new top-level tab

Semantics:

- joining any two tabs merges one tab's root pane into the other's tree
- the source top-level tab is removed
- the destination top-level tab remains
- sibling expansion on close is standard tree collapse

## Join Semantics

Joining any two tabs should work as:

1. Choose destination tab.
2. Choose source tab.
3. Replace destination root with a new split node:
   - first child = old destination root
   - second child = source root
4. Remove source tab from workspace order.
5. Select a predictable pane, preferably the source pane that was just added.

An explicit split axis should be required for the join action.

## Close Semantics

When closing a pane:

- if the pane has a sibling, the sibling expands into the parent's position
- if the pane was the only leaf in a top-level tab:
  - close the whole top-level tab if more than one tab exists
  - otherwise create a replacement shell pane, matching current single-tab safety behavior

## Focus Semantics

Pane focus must become explicit.

Recommended rules:

- mouse click focuses that pane
- splitting focuses the new pane
- joining focuses the moved-in pane
- closing a pane focuses the surviving sibling
- top-level tab switch restores the previously selected pane in that tab

## Migration Path

Implement in stages to reduce risk.

### Stage 1: Domain Refactor

- Replace flat workspace tabs with top-level tab objects.
- Introduce pane tree types.
- Keep only one leaf per tab initially so behavior is unchanged.

### Stage 2: Runtime Layout Rendering

- Render pane trees recursively in `ContentView`.
- Add active pane selection.
- Keep persistence off until runtime behavior is stable.

### Stage 3: Split Actions

- Split active pane horizontally/vertically.
- Close pane with sibling expansion.
- Basic focus movement.

### Stage 4: Joined Tab UI

- Update tab bar to show grouped child pane chips.
- Surface active pane clearly.

### Stage 5: Join / Unjoin Flows

- Join any two tabs with explicit axis choice.
- Support promoting a pane back to its own top-level tab if needed.

### Stage 6: Persistence

- Persist pane trees and top-level tab state.
- Recreate sessions on launch.

### Stage 7: Hotkeys and Polish

- Add pane-focused shortcuts.
- Add divider dragging polish.
- Improve visual grouped-tab affordances.

## Main Risks

### 1. Model complexity

The current flat `[TerminalSession]` model is simple. Tree-based layout introduces more state, more edge cases, and more focus semantics.

Mitigation:

- refactor the data model before touching complex UI
- keep top-level tabs and pane leaves distinct

### 2. SwiftTerm view ownership

`TerminalView` cannot be mounted in multiple places safely.

Mitigation:

- preserve current rule: only one active presenting screen per workspace
- keep one runtime `TerminalSession` per pane leaf

### 3. Persistence mismatch

Persisting layout is easy compared with persisting process state.

Mitigation:

- persist layout and selection only
- recreate shell sessions on launch

### 4. Joined-tab UX ambiguity

If grouped tabs are also used as pane chips, the interaction model can get confusing.

Mitigation:

- preserve top-level tabs as the real navigation unit
- use internal chips only as secondary indicators/actions

## Recommended First Implementation Boundary

The first deliverable should include:

- nested binary split trees
- active pane focus
- split and close pane actions
- sibling expansion
- grouped tab appearance
- join any two tabs
- persisted layout across relaunch
- same workspace layout when moved to another screen

The first deliverable should not include:

- simultaneous same-workspace rendering on multiple screens
- drag-and-drop tree editing
- restoring running shell process contents

## Files Likely Impacted

- `CommandNotch/CommandNotch/Models/WorkspaceController.swift`
- `CommandNotch/CommandNotch/Models/WorkspaceRegistry.swift`
- `CommandNotch/CommandNotch/Models/WorkspaceStore.swift`
- `CommandNotch/CommandNotch/ContentView.swift`
- `CommandNotch/CommandNotch/Components/TabBar.swift`
- `CommandNotch/CommandNotch/Models/TerminalSession.swift`
- workspace-related tests

Likely new files:

- pane tree domain types
- persisted workspace document types
- split-pane rendering view(s)
- pane-focused command helpers

## Decision Summary

The feature is feasible.

The correct architecture is:

- workspace-owned split tree
- top-level tabs remain the primary unit
- grouped tab visuals are a UI layer over the pane tree
- one presenting screen per workspace
- persisted layout, not persisted process contents

That is the implementation direction to use when this work is resumed.
