Skip to content

feat: 埋め込みターミナルをセッション切り替え後も生かし続ける#66

Merged
kkyosuke merged 2 commits into
mainfrom
feat/persist-embedded-terminal
Jun 14, 2026
Merged

feat: 埋め込みターミナルをセッション切り替え後も生かし続ける#66
kkyosuke merged 2 commits into
mainfrom
feat/persist-embedded-terminal

Conversation

@kkyosuke

Copy link
Copy Markdown
Owner

目的

右ペインの埋め込みターミナル(terminal / agent)を開いたまま、別セッションへ切り替えられるようにする。これまでは Ctrl-O でデタッチ=シェル終了だったため、claude を起動したまま別の worktree を見に行く、といった操作ができなかった。

変更内容

永続化の仕組み

  • home/terminal_pool.rs(新規): PtySession を worktree パスをキーにした TerminalPool で保持。スタック上ではなくホーム画面のライフタイム中ずっと生かすことで、デタッチやセッション切り替えで shell が死ななくなる。get-or-spawn 方式(agent 起動コマンドは初回 spawn 時のみ送信、死んだ shell は再 spawn)。
  • home/terminal_pane.rs: run がプールから借りた &mut PtySession を駆動し、離脱理由を PaneExit で返す形に変更。Ctrl-O をリーダーキー化(resolve_leader、約 400ms のフォローアップ待ち)。
  • home/mod.rs: プールを所有し、open_terminal クロージャがプール経由で PTY を取得。

入力(Ctrl-O リーダー)

入力 動作
Ctrl-O 単独 デタッチ(シェル / Agent は生存
Ctrl-On / ] 次セッションのターミナルへ切り替え
Ctrl-Op / [ 前セッションのターミナルへ切り替え
Ctrl-OCtrl-O 本物の Ctrl-O(0x0f)をシェルへ送出

状態・イベント(テスト対象)

  • home/state.rs: PaneExit enum と focus_next_worktree / focus_prev_worktree(カーソル移動+パス返却)。
  • home/event.rs: OpenTerminal / OpenAgent をセッション切り替えサブループ化(SwitchNext/SwitchPrev で再ルート、Detach/Closed/Err で離脱)。
  • home/ui.rs: フッターを Ctrl-O: detach / Ctrl-O n,p: switch session に更新。

ドキュメント

  • document/03-commands/02-tui.md / document/design/05-home.md / document/04-orchestration.md(永続化セクション新設)/ README.md を更新。
  • scripts/coverage.sh: I/O 専用の terminal_pool.rs をカバレッジ除外に追加(pty.rs と同様)。

スコープ外(フォローアップ候補)

  • 画面を離れるとプールが drop し全 shell 終了(ワークスペース内の切り替えが対象)。
  • 明示的な :terminal close コマンド・アイドル kill は未実装。

テスト・確認方法

  • cargo fmt --check / cargo clippy --all-targets -- -D warnings / cargo test(599 passed)。
  • カバレッジ 100%(Lines / Functions)を SSoT(scripts/coverage.sh)の除外条件で確認。
  • 切り替えサブループの全分岐(次/前/空リスト/デタッチ/クローズ/エラー)と focus_next/prev_worktree を単体テストで網羅。

🤖 Generated with Claude Code

test and others added 2 commits June 14, 2026 20:00
`terminal` / `agent` で開いた埋め込みシェルをスタック上ではなく worktree
パスをキーにしたターミナルプールで保持し、デタッチやセッション切り替えで
終了しないようにする。`Ctrl-O` をリーダーキー化し、単独でデタッチ、
`Ctrl-O n`/`p` で次/前セッションのターミナルへ切り替え、`Ctrl-O Ctrl-O`
で本物の Ctrl-O をシェルへ送る。

- terminal_pool.rs: PtySession を画面ライフタイム中保持する get-or-spawn
- terminal_pane.rs: 借用した PTY を駆動し PaneExit を返す + リーダー解釈
- state.rs: PaneExit / focus_next_prev_worktree
- event.rs: セッション切り替えサブループ(再ルート / デタッチ / クローズ)
- ui.rs: フッターを切り替えキー案内に更新
- docs: コマンド / 画面 / オーケストレーション / README を更新

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📊 Test Coverage

🚀 いまのカバレッジ (Lines): 100.00% — この調子でいこう!

🎉✨ パーフェクト!全ファイル Lines カバレッジ 100% を達成しました 🏆🐰

@kkyosuke kkyosuke merged commit 5752af9 into main Jun 14, 2026
4 checks passed
@kkyosuke kkyosuke deleted the feat/persist-embedded-terminal branch June 14, 2026 11:09
kkyosuke pushed a commit that referenced this pull request Jun 14, 2026
main の #66(TerminalPool による埋め込みターミナルのバックグラウンド常駐・
Ctrl-O リーダーでのセッション切替)と #64(コマンドヒント、issue 027)を取り込み、
本ブランチの入力待ち検知・通知を新アーキテクチャへ統合した。

- 競合した terminal_manager の常駐保持を main の TerminalPool に統合し削除
- ベル監視・SessionMonitor・通知を TerminalPool に取り込み(MonitorHandle で公開)
- event_loop / terminal_pane に monitor を渡し、サイドバーに ◆ 入力待ちマーカー
- issue 番号衝突を解消(agent-wait-notify を 027→028 に採番)
- ドキュメント(02-architecture / 03-commands / 04-orchestration / 05-home / README / issues)を更新

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kkyosuke added a commit that referenced this pull request Jun 21, 2026
## 目的

統括(Overview)/在席(Focus) モードで、バックグラウンドのエージェントが状態遷移してもサイドバーのバッジや update
通知が**次のキー入力まで反映されない**バグ(issue
#66)を修正する。「ターミナルが固まったように見え、キーを連打すると更新される」という症状の原因。

## 原因

`src/presentation/tui/home/event/mod.rs`
のイベントループは、install/セッションタスクが動いていないと `reader.read_key()`
で**無期限ブロック**する。そのため背景エージェントが `◆ waiting`(入力待ち)/ `✓
done`(完了)へ遷移しても、ループ先頭の `apply_badges(monitor.snapshot())` /
`set_update(...)` が次のキー入力まで再実行されず、画面が更新されなかった。没入(Attached) は別経路
`terminal_pane::drive` がバッジ監視するため影響を受けず、統括/在席にだけ穴があった。

## 変更内容

- `animate`(定期起床する条件)に **`state.has_live_sessions()`** を追加。

  ```rust
  let animate = install_task::handle().is_active(now)
      || tasks.is_active(now)
      || state.has_live_sessions();
  ```

- ライブな埋め込みセッションがある間は無入力でも `read_key_timeout(ANIM_TICK=110ms)`
で起床してループを再反復し、バッジ・update 通知が ~200ms 以内(ウォッチャー間隔 `POLL_INTERVAL=200ms`
が上限)で反映される。
- セッション無し・install/タスク無しの真のアイドル時は従来どおり `read_key()` でブロックし、無駄な起床を出さない(#68
のアイドル起床懸念に逆行しない)。update 通知のためだけの常時起床はさせない(`UpdateHandle`
は「チェック中」と「最新版」を区別できず、常態で永久起床になるため)。
- issue #66 を `done` に更新。

## テスト・確認方法

- 回帰テスト
`a_live_session_wakes_the_loop_without_a_key`(`event/tests.rs`)を追加。install/task
無し・ライブセッション有りで、ループがキー入力なしにタイムアウト起床することを検証する(**修正を外すと blocking
経路で失敗する**設計)。
- `cargo fmt` / `cargo clippy --all-targets -- -D warnings` / `cargo
test`(1376 passed)。
- カバレッジ lines / functions ともに 100%(`coverage_enforce` パス)。
- ドキュメントは [design/05-home.md](document/design/05-home.md) の Agent
状態バッジ(☾/▶/◆/✓)の記述が既に意図挙動を述べており、本修正は実装をそれに一致させるものなので変更不要。

Closes #66

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: test <test@example.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kkyosuke pushed a commit that referenced this pull request Jun 21, 2026
没入(Attached)で Ctrl-O を押して切替(Switch)に抜けると、ヘッダーは
switch になるのに ↑/↓・j/k でセッションを移動できなかった。

原因は `read_key_timeout`(animate 時のポーリング読み取り)が、ターミナルが
cooked(canonical) モードのまま `crossterm::event::poll` を呼んでいたこと。
イベントループは console の「1 キーごとに raw を on/off する」読み取りに
依存しており、その合間は cooked モードのまま(AlternateScreenGuard / echo は
ECHO を落とすだけで canonical は維持)。canonical な tty は改行で 1 行確定
するまで readable にならないため、Enter を伴わない矢印・j/k は poll から
ready に見えず、ティックを繰り返すだけで一度も読まれなかった。

animate は #66 で has_live_sessions() が加わり live セッションがあれば常に真
になるため、没入から Ctrl-O で抜けた直後(セッションが生きている)は必ずこの
経路に入り、矢印が握り潰されていた。

poll とデコードを raw モードで囲い(RAII の RawModeGuard で退出時に cooked へ
復元)、各キーが即時に届くようにした。console がブロッキング経路で使う
「読み取りの間だけ raw」と同じ挙動を timeout 経路にも適用しただけ。

term_reader.rs は端末 I/O 専用ラッパでカバレッジ除外。
fmt / clippy -D warnings / test(1382 passed) 通過。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
kkyosuke added a commit that referenced this pull request Jun 21, 2026
## 目的

没入(Attached)で `Ctrl-O` を押して切替(Switch)に抜けると、ヘッダーのモードは switch になるのに
**↑/↓・`j`/`k` でセッションを移動できない**バグを修正する(issue #74)。`Enter` だけは効き、live
セッションが無い状態(統括から `Ctrl-O`)の切替では矢印が効く——という「live セッションがあるときだけ navigation
が死ぬ」症状。

## 原因

`src/presentation/tui/term_reader.rs` の `read_key_timeout`(`animate`
時のポーリング読み取り)が、**ターミナルが cooked(canonical) モードのまま**
`crossterm::event::poll` を呼んでいた。

- イベントループは `console` の「1 キーごとに raw を on/off する」読み取りに依存しており、読み取りの**合間は
cooked モード**のまま(`AlternateScreenGuard` / `echo.rs` は `ECHO` を落とすだけで
canonical は維持)。
- canonical な tty は、ラインディシプリンが**改行で 1 行確定するまで** readable にならない。そのため
`Enter` を伴わない矢印・`j`/`k` は `poll` から ready に見えず、`Ok(None) => continue`
のティックを繰り返すだけで**一度も読まれなかった**。
- `animate` は #66 で `state.has_live_sessions()` が加わり **live
セッションがあれば常に真**になるため、没入から `Ctrl-O`
で抜けた直後(セッションが生きている)は必ずこの経路に入り矢印が握り潰されていた。
- 無入力時は `read_key()`(`console` がその読み取りの間だけ raw 化)なので矢印が通る。これが「live
が無ければ動く/あると動かない」の差。

## 変更内容

- `read_key_timeout` の `poll` +デコードを **raw モードで囲う**(RAII の
`RawModeGuard` で退出時に cooked へ復元)。各キーが即時に届き、`poll` がキー単位で ready
を返すようになる。`console` がブロッキング経路で使う「読み取りの間だけ raw」と同じ挙動を timeout
経路にも適用しただけで、ループの他の挙動は不変。

## テスト・確認方法

- `cargo fmt` / `cargo clippy --all-targets -- -D warnings` / `cargo
test`(1382 passed、カバレッジ 100% 維持)。`term_reader.rs` は端末 I/O
専用ラッパでカバレッジ除外(`scripts/coverage.sh`)。
- 手動: 没入 → `Ctrl-O` → 切替 で ↑/↓・`j`/`k` がセッションを移動できること。live
セッションがある状態の統括/在席でもキー入力が遅延・欠落しないこと。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: test <test@example.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant