# Plan: マルチターミナル（グリッド＋タブ）

複数の `claude` ターミナルを1画面で同時に扱える「ターミナル・マルチプレクサ」化。
グリッド分割・ディレクトリ別起動・状態通知・タブ切替を、できる限り簡単に使える形で。

> base: `dev_tool`

---

## ゴール（ユーザー要望）

1. 1画面で複数ターミナルを起動。**2x2 / 3x2 / 4x2 / 3x3** のレイアウトを選べる。
2. ターミナルごとに**起動 dir** を決められる。**設定でプリセット**しておき、そこから選べると良い。
3. 各ターミナルが**どの dir か**わかる。
4. **直前の命令プロンプト**が見える。
5. 1つ選ぶと**拡大**、戻すと分割 view に戻る。
6. **ユーザー入力待ち / 作業終了**を UI と**音**でわかる。
7. 分割画面自体を**タブ切替**でき、複数面で使える。
8. タブで裏に回った画面でも、入力待ち/終了を**タブ上のバッジ等で**わかる。
9. 全体を**なるべく簡単に**使える。

---

## 現状の土台（何が既にあるか）

| 要望 | 既存実装 | 不足 |
|---|---|---|
| 複数ターミナル同時 | サーバは `ptys` Map で**複数 PTY 同時稼働**・背景セッション維持済み | フロントが単一 active セッション前提（`App.vue` の `activeId` 1個） |
| 状態通知 | アクティビティ pub/sub が**セッション毎に `working`/`waiting` を配信** | グリッド/タブ各セルへのバッジ表示・音 |
| 直前プロンプト | サーバは `last-prompt`/初回ユーザーメッセージから**タイトル算出済み** | per-terminal にライブ表示（pub/sub に prompt を載せる or API） |
| dir 別起動 | `--cwd`/`CLAUDE_CWD` は**サーバ全体で1つ**。`/ws?session=` のみ | **セッション毎の cwd**（`/ws` が cwd を受ける or サーバが session→cwd を保持） |
| 拡大/戻す | `Terminal.vue` は `sessionId` を受けて WS 接続する**再利用可能な部品** | グリッド⇄拡大のレイアウト state |

ポイント: **サーバの根っこ（複数PTY・per-session activity）は既に対応済み**。主にフロントの再構成＋「per-session cwd」「prompt のライブ配信」のサーバ小改修。

---

## アーキテクチャ方針

### データモデル（フロント）
```
Tab (= 1つの面 / グリッド)         // 複数タブOK。各タブで複数セッションを並列に動かす
  ├ layout: "2x2" | "3x2" | "4x2" | "3x3"
  ├ cells: Cell[]            // セル数 = レイアウトのマス目
  └ expandedCellId | null    // 拡大中セル

Cell
  ├ id            // セルの固有id（レイアウト位置とは独立）
  ├ sessionId     // 紐づく claude セッション（未起動なら null = 空セル）
  ├ cwd           // このターミナルの作業dir（起動時に確定）
  └ (派生) status: idle|working|waiting  // pub/sub から
         lastPrompt                       // pub/sub/API から
```
- **1つのタブ（面）の中で複数セッションを並列**に動かす（要望: 並列でガンガン）。**複数タブ**で面を増やせる。タブ=1セッションではない。
- **空セルは自動起動しない**。ユーザーがセルで起動を選んだ時点で初めて PTY を spawn（遅延起動）。
- **起動時に「過去セッションを引き継ぐ（resume）」か「新規（new, dir を選ぶ）」を選択** — これがセッション選択の入り口（独立サイドバーは持たず、起動ピッカーに集約）。
- localStorage にタブ構成（レイアウト・各セルの cwd/sessionId）を永続化 → 再起動で復元。
- セッション自体はサーバの背景 PTY として生存するので、タブを切り替えても**裏のターミナルは動き続ける**（既存の background-session 挙動がそのまま効く）。

### 同時セッション上限
- **アクティブセッションは全タブ合計で最大 20**（pub/sub チャンネル/PTY/claude プロセスの現実的上限）。
- 20 到達時は**新規セル起動・新規タブ追加をブロック**（UI で無効化＋理由表示）。resume での再アタッチは既存セッションなのでカウント据え置き。
- カウントはサーバ側（`ptys` の生存数）を真実とし、フロントに配信して導線を制御。

### サーバ改修（最小）
1. **per-session cwd**: `/ws?session=<id>&cwd=<abs>` を受け、`spawnClaudePty` がそのセッションだけ別 cwd で起動。resume 時は元 cwd（.jsonl 記録）を尊重。`cwd` は検証（絶対・実在・dir）。
   - ※ これは「任意 dir を開く」系（#41/#44 の流れ）と地続き。プリセットも乗る。
2. **prompt のライブ配信**: `UserPromptSubmit` フック受信時に、`activity` の publish に**直近 prompt 文字列**を載せる（または `/api/sessions` の項目に `lastPrompt` を含める）。サイドバーのタイトル算出ロジックを流用。
3. （任意）**プリセット保存**: `GET/POST /api/config` に `cwdPresets: {label, path}[]` を追加し `~/.mulmoterminal/config.json` に永続化。設定画面から編集。

### フロント構成（コンポーネント）
- `WorkspaceTabs.vue` — タブバー（追加/削除/切替、タブに集約バッジ）。
- `TerminalGrid.vue` — レイアウトに応じた CSS Grid。セル数分 `TerminalCell` を配置。拡大時は1セルだけ全面。
- `TerminalCell.vue` — ヘッダ（dir 表示・直前 prompt・状態ドット・拡大ボタン）＋ `Terminal.vue`（既存を再利用）。空セルは「dir を選んで起動」UI。
- `Terminal.vue` — `cwd` prop を追加して `/ws?...&cwd=` 接続（他はほぼ流用）。
- 設定モーダル（新規）— cwd プリセットの編集、デフォルトレイアウト、音 on/off。

### 状態・通知の流れ
- グローバルに**全セッションの activity を購読**（`SESSIONS_CHANNEL`）。各 Cell は自分の `sessionId` のイベントで status/prompt を更新。
- **音**: status が `waiting`（入力待ち）/ Stop（終了）に遷移した瞬間に Web Audio で短いビープ。設定で on/off・種類。フォアグラウンドのセルは控えめ、裏タブは強調、なども可。
- **裏タブ表示**: 各タブの「配下セルの集約 status」を計算 → タブに ● / 件数バッジ。`waiting` が最優先で目立たせる。
- ブラウザ通知（Notification API）も任意で（タブ非アクティブ時）。

---

## 実装フェーズ（小さく出す）

- **P0: グリッド土台** — `TerminalGrid` + `TerminalCell` で `Terminal.vue` を N 枚表示（2x2固定）。既存の単一表示と共存（まず1タブ）。
- **P1: レイアウト選択** — 2x2/3x2/4x2/3x3 切替（CSS Grid のテンプレ）。空セルから「セッション選択 or 新規起動」。
- **P2: 拡大/戻す** — セルクリックで `expandedCellId`、もう一度で分割へ。
- **P3: 状態 & 直前プロンプト & dir 表示** — Cell ヘッダに dir / lastPrompt / status ドット。サーバの prompt ライブ配信を追加。
- **P4: 音 & 通知** — waiting/終了で音、設定で on/off。
- **P5: per-session cwd & プリセット** — `/ws?cwd=` 対応＋設定画面のプリセット。セルごとに dir を選んで起動。
- **P6: タブ（複数の面）& 上限20** — `WorkspaceTabs`、タブ集約バッジ（裏で入力待ち/終了を表示）、サーバの生存セッション数を配信して新規起動/新規タブを20でブロック。
- **P7: 永続化 & 仕上げ** — タブ構成を localStorage 保存、キーボード操作、空状態の導線、テスト。

各フェーズを個別 PR（base=`dev_tool`）で。P0→P3 で「複数 dir のターミナルを並べて状態がわかる」一旦の完成形になり、P4–P6 が体験強化。

---

## 決定事項（確定）

1. **タブ = 1つの面（グリッド）、その中で複数セッションを並列**。タブ=1セッションではない。複数タブで面を増やせる。
2. **空セルは自動起動しない。選択した時点で起動**（遅延起動）。
3. **セル起動ピッカー = 「過去セッションを引き継ぐ(resume)」or「新規(new, dir 選択)」**。独立サイドバーは持たず、これに集約（要望5）。
4. **拡大時のみ GUI パネル＋Tools を表示。分割時は端末のみ**（狭いため）。
5. **同時セッション上限 20**。超過時は新規セル起動・新規タブ追加をブロック。
6. 新規時の dir は **設定のプリセットから選択**（手入力も可）。

## 残論点（実装中に確定でOK）

- **音の既定**: デフォルト on、waiting と終了で別音、設定で on/off。タブ非アクティブ時はブラウザ通知も任意。
- **拡大時の対象セッション以外**: 裏で動き続ける（背景 PTY）。拡大解除で分割へ即復帰。
- **タブ削除時のセッション**: 端末を閉じる（reap）か、背景に残すか（残す＝再アタッチ可だが上限を圧迫）。既定は「確認のうえ reap」。

---

## 留意点

- claude プロセスは**端末ごとに1つ**。**全タブ合計20セッションで打ち止め**（決定5）。到達時は導線を無効化。
- per-session cwd は**未起動セッションの新規起動時のみ**指定可。resume は元 cwd 固定（崩すと壊れる）。
- GUI ストアは `~/.mulmoterminal/`（sessionId キー）に集約済みなので、複数端末・複数 dir でも衝突しない（#41）。
- 音/Notification はブラウザのユーザー操作後でないと鳴らない制約あり（初回クリックで AudioContext を解放）。

---

## 非ゴール（今回スコープ外）

- ターミナル間のペイン同期/ブロードキャスト入力（tmux 的な同時入力）。
- リモート/Docker 実行（別途。PR #30 参照）。
- グリッドの自由リサイズ（まずは定型レイアウトのみ）。
