Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,23 @@ claude.ai の設定「**Help improve our AI models**」が ON のアカウント

ファイルを直接編集するほか、一覧画面のコンポーザで **`/prompt`** と入力すると TUI 内エディタが開きます(現在の内容をシード。`Enter` で保存、`Shift+Enter` で改行、`Esc` で取消、空で保存すると削除)。保存内容は**以降の新規セッション**に反映されます(稼働中のセッションは起動時の指示を維持)。

利用できるスラッシュコマンドは、コンポーザで `/` を入力するとパレット表示されます(`/prompt`・`/model`・`/sync`・`/fix-ci`・`/recover`・`/clear`・`/update`・`/help` など)。`/clear` は完了・中断・失敗など**終了済みのセッションを一覧から消去**します(実行中のセッションは残ります)。worktree やコミット履歴はディスク上に残るため作業自体は失われませんが、消去したセッションは codiva を再起動しても一覧に戻りません。
利用できるスラッシュコマンドは、コンポーザで `/` を入力するとパレット表示されます(`/prompt`・`/model`・`/sync`・`/fix-ci`・`/recover`・`/remove`・`/clear`・`/update`・`/help` など)。

### セッションを消す(`x` / `/remove` / `/clear`)

一覧に残っているセッションは、次の 3 通りで片付けられます(いずれも `y` / `n` の確認を挟みます)。

| 操作 | 対象 | 何が消えるか |
|---|---|---|
| `d`(破棄) | 選択中の 1 件 | worktree とブランチ。**行は「破棄」として一覧に残ります**(再起動すると消えます) |
| `x`(= `/remove`) | 選択中の 1 件 | worktree とブランチ+**一覧の行そのもの**。記録ごと消えます |
| `/clear` | 終了済みの**全件**(完了・中断・失敗など。実行中は対象外) | 各セッションの worktree とブランチ+一覧の行。件数を確認してから実行します |

`x` は詳細ビューでも使えます(`Tab` で操作パネルへ切り替え)。削除するとそのセッションは開けなくなるので、自動的に一覧へ戻ります。

**`x` を使うのは、過去の PR に紐づくセッションを片付けたいとき**です。`d`(破棄)だと行が「破棄」として残り、そのブランチの PR がコンフリクトしていたり CI が落ちていたりすると `Ctrl+F`(一括立て直し)の候補として出続けます。`x` は行ごと消すので、一括操作の対象からも完全に外れます。

いずれもローカルの worktree とブランチを消すだけで、**push 済みのリモートブランチと GitHub 上の PR には触りません**(PR を閉じたい場合は GitHub 側で操作してください)。未コミットの変更が残っていても削除は強制実行されるため、残したい作業があるときは先にコミットしてください。

スラッシュを打ち忘れても、**その画面で使えるコマンド名と完全に一致する入力**(`exit` / `help` など)はそのコマンドとして実行されます。実行されるときはコマンドパレットに出るので、`Enter` の前に何が起きるか分かります。`exit の挙動を直して` のように後ろに文字が続く場合、また `?`・`changes` のような別名は通常の指示として扱うので、指示が誤ってコマンドになることはありません。

Expand Down
7 changes: 5 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ Claude Code の実画面に寄せる: 画面は**端末の縦幅いっぱい**
`flexShrink={0}`**(横方向の縮小でアスキーアートが折り返して崩れるのを防ぐ。縦の譲り合いには効かない)。
- `SessionList`: 一覧画面。`Banner` + 一覧 + 下部 `PromptInput`/`StatusFooter`。フォーカスは
`composer`(起動時既定。タイピング + 矢印キャレット移動)と `list`(↑↓選択・Enter/→ = 詳細を開く・
m/d = マージ/破棄)の2ゾーンで Tab 切替。選択セッションの `PermissionDialog` は list フォーカス時のみ
m/d/x = マージ/破棄/削除)の2ゾーンで Tab 切替。選択セッションの `PermissionDialog` は list フォーカス時のみ
アクティブ。マウスクリック(`core/mouse.ts` + `useAbsolutePosition`)で行選択・キャレット移動。
コンポーザ上のドラッグで範囲選択し、離すとクリップボードへコピー(OSC 52 = `utils/clipboard.ts`。
純粋ロジックは `core/text-selection.ts`、状態は共有フック `useDragSelection`)。詳細ビューの
Expand All @@ -461,7 +461,8 @@ Claude Code の実画面に寄せる: 画面は**端末の縦幅いっぱい**
多く描くと行が虫食いで欠落する)、
`streamingText` のタイピング風プレビュー、
下部の追加指示コンポーザ(`manager.send(id, text)`)を持つ。Tab で入力↔操作パネルを切替し、
操作パネルで m/d = マージ/破棄。`pendingPermission` があれば `PermissionDialog` に委譲。単一 `useInput` の
操作パネルで m/d/x = マージ/破棄/削除(`x` は行ごと消すので成功時は一覧へ戻る)。
`pendingPermission` があれば `PermissionDialog` に委譲。単一 `useInput` の
state machine(panel = input | actions)でタイピングとキー操作の衝突を防ぐ。
- `PromptInput` / `StatusFooter`: presentational。キー処理は view の単一 `useInput` に集約(ロジックは持たない)。`PromptInput` は複数行対応(純粋モデルは `core/text-buffer.ts`、キー対応は `ui/input.ts` の `editText`/`resolveEnter`)。幅を超えたテキストは**折り返す**(truncate しない): 折り返し後の表示行・キャレット位置・クリック逆算・選択範囲はすべて純粋な `core/composer-layout.ts`(`composerLayout`)が算出し、折り返し幅は Box の実測値(`useComposerWidth`)を描画・当たり判定・↑↓ 移動で共有する。IME 対応で実端末カーソルをキャレットに重ねる(`useCursor`)。
- 再描画スロットリング: SessionManager の通知を UI 側で ~100ms にスロットルする。
Expand Down Expand Up @@ -765,6 +766,8 @@ TUI は alt screen + マウスレポート(?1002/?1006)で動くため、異
| 復元は「メタ + SDK resume」で、ログは永続しない | state.json を小さく保つ。会話履歴は SDK の resume が持つので二重管理しない。復元直後はアイドル表示、追加指示で継続 |
| 復元セッションは遅延 resume(起動時に起こさない) | セッション毎に ~1GiB のサブプロセスを起動時に乱立させない。触られたものだけ起こす |
| 終了は `abort()` ではなく `stop()`(quiet) | 実行中セッションを failed にせず resumable のまま保存するため(quit と「1件破棄」を区別) |
| 「破棄(`d`)」と「削除(`x` / `/remove` / `/clear`)」を分ける | 破棄は worktree を消して行を `archived` として残す(作業の記録が見える)。だがブランチに古い PR が付いていると、その行は一括立て直し(`Ctrl+F` = `recoverableSessions`)の候補として毎回挙がり続ける。削除は store から行ごと落とすので、記録も一括操作の対象も同時に消える。`/clear` も worktree/ブランチを残さない(残すと「消したのにディスクに残る」ぶんが見えない負債になる) |
| 削除で worktree の除去に失敗したら行を残す | ディスクにディレクトリが残っているのに一覧から消すと、存在するものが見えなくなる。エラーを出して行を残し、`/clear` は成功した件数だけ数える |
| 通知の発火判定は純粋関数・遷移時のみ | テスト可能にし、ストリーミングの連続更新で鳴り続けるのを防ぐ。OS I/O は utils に隔離し best-effort |
| 設定検証は `toConfig()` に集約・不正値は既定へ | 設定ミスで TUI をクラッシュさせない。SDK union は実行時リテラルで検証(型が変われば型エラー) |
| 分離手段は git worktree | 同一リポジトリの並列作業では最軽量。ブランチがそのまま成果物になる。Docker 等はMVPではオーバーキル |
Expand Down
31 changes: 31 additions & 0 deletions docs/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,37 @@ UI なし。すべてユニットテストで駆動する。
> 実績メモ: lint / typecheck / test / build 緑。ヘッダの行数は増えていない(プラン + モデルの行に
> 並べただけなので `bannerCaretAt` の「行 index = 表示行」も不変)。実機での体感確認はユーザーに依頼。

## Phase 23: セッションを 1 件ずつ削除する(`x` / `/remove`) ✅

> 一覧から 1 件だけ消す手段が無かった。`d`(破棄)は worktree とブランチを消すが行を
> `archived` として残すため、**古い PR が付いたセッションが `Ctrl+F`(一括立て直し)の
> 候補に挙がり続ける**。`/clear` は行を消す代わりに worktree を残していたので、
> 「消したのにディスクには残る」ぶんが見えない負債になっていた。両方を揃える。

- [x] `SessionManager.remove(id, opts)`: 破棄 + `store.remove` + `onPersist`(= state.json からも消える)。
worktree が既に無い行(過去に `d` した archived)も**エラーにせず**行だけ落とす。
共通の後始末は private `forget(id)` に切り出し(`stop()` → `sessions`/`worktreeMeta`/`prs`/`store`)
- [x] `SessionManager.clear()` を async 化し、**worktree とブランチも削除**(`ClearOutcome`
= `{ cleared, error }`)。git はリポジトリ全体のロックを取るので**直列**。除去に失敗した行は
**残す**(ディスクにあるものを一覧から隠さない)
- [x] `CommandAction`/`COMMANDS` に `remove`、`Messages` に `command.remove` / `action.removePrompt` /
`action.clearPrompt(n)` / `detail.removeAction`(ja/en 対)。フッタヒントに `x: 削除`
- [x] `useLifecycleAction` を `merge | discard | remove | clear` に拡張(`clear` だけ id 不要)。
`onDone(ok, action)` にして、詳細ビューは削除成功時だけ `onBack()`(消えたセッションの
「見つかりません」を見せない)
- [x] 一覧: `x` キー + `/remove`、`/clear` は**件数付きの確認**を挟む(0 件なら聞かない)。
詳細: 操作パネルの `x` + `/remove`
- [x] `CommandPalette` に `flexShrink={0}`(コマンドが 1 つ増えて `/help` 一覧が縦に入らなくなると、
Yoga が枠を縮めて行が混ざり `/diffpt` のような読めない表示になっていた)
- [x] テスト: `session-manager.spec.ts`(remove の 4 ケース / clear の worktree 削除・失敗時に行を残す)/
`commands.spec.ts`(`/re` は recover と remove の両方に前方一致)/ `tests/commands.test.tsx`
(`/clear` の確認 → y、0 件では何もしない、`/remove`、`x` の n/y)
- [x] ドキュメント: `README.md`(「セッションを消す」節 = `d` / `x` / `/clear` の対比表)/
`docs/ARCHITECTURE.md`(キー割当 + 設計判断 2 行)

> 実績メモ: lint / typecheck / test 緑。リモートブランチと GitHub の PR には触らない(ローカルのみ)。
> 未コミット変更は `force` で消えるため、確認文で明示している。実機での体感確認はユーザーに依頼。

---

## 各 Phase 共通の完了チェック
Expand Down
6 changes: 6 additions & 0 deletions src/core/commands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ describe('matchCommands', () => {
expect(matchCommands('/mo').map((c) => c.name)).toEqual(['model']);
expect(matchCommands('/pr').map((c) => c.name)).toEqual(['prompt']);
expect(matchCommands('/cl').map((c) => c.name)).toEqual(['clear']);
// `/re` は「立て直し」と「削除」の両方に当たる(レジストリ順で出す)。
expect(matchCommands('/re').map((c) => c.name)).toEqual(['recover', 'remove']);
expect(matchCommands('/rem').map((c) => c.name)).toEqual(['remove']);
});
it('does not match the retired /quit alias', () => {
expect(matchCommands('/q').map((c) => c.name)).toEqual([]);
Expand All @@ -125,6 +128,9 @@ describe('runCommand', () => {
it('resolves /clear to the clear command', () => {
expect(runCommand('/clear')).toEqual({ kind: 'run', command: findCommand('clear') });
});
it('resolves /remove to the remove command', () => {
expect(runCommand('/remove')).toEqual({ kind: 'run', command: findCommand('remove') });
});
it('resolves /update to the update command', () => {
expect(runCommand('/update')).toEqual({ kind: 'run', command: findCommand('update') });
});
Expand Down
2 changes: 2 additions & 0 deletions src/core/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type CommandAction =
| 'model'
| 'diff'
| 'prompt'
| 'remove'
| 'clear'
| 'update'
| 'sync'
Expand Down Expand Up @@ -51,6 +52,7 @@ export const COMMANDS: readonly CommandSpec[] = [
// そのまま完全一致で引ける(別名 `fixci` はハイフンを打ち忘れたとき用)。
{ name: 'fix-ci', aliases: ['fixci'], action: 'fixCi', describe: (m) => m.command.fixCi },
{ name: 'recover', action: 'recover', describe: (m) => m.command.recover },
{ name: 'remove', action: 'remove', describe: (m) => m.command.remove },
{ name: 'clear', action: 'clear', describe: (m) => m.command.clear },
{ name: 'update', action: 'update', describe: (m) => m.command.update },
{ name: 'help', aliases: ['?'], action: 'help', describe: (m) => m.command.help },
Expand Down
Loading
Loading