Skip to content

feat: session remove を名前省略時に一覧モーダルで一括削除する#74

Merged
kkyosuke merged 2 commits into
mainfrom
feat/session-remove-modal
Jun 14, 2026
Merged

feat: session remove を名前省略時に一覧モーダルで一括削除する#74
kkyosuke merged 2 commits into
mainfrom
feat/session-remove-modal

Conversation

@kkyosuke

Copy link
Copy Markdown
Owner

目的

session remove名前なし で実行したとき、削除対象を毎回タイプせずに済むよう、セッション一覧モーダルから複数選択して一括削除できるようにする。

変更内容

  • session remove を名前省略で実行すると Effect::OpenRemoveModal を返し、記録済みセッションのチェックリストを画面中央のモーダルに表示する(session remove <name> の直接削除は従来どおり)。
  • /(または j/k)でカーソル移動、Space で選択/解除、Enter で選択したセッションを 1 件ずつ一括削除、Esc でキャンセル。何も選択していない Enter は no-op でモーダルを開いたまま。
  • session remove --force で開いた場合は破棄を伴って削除(未コミット変更ありでも削除)。
  • セッション数が表示枠を超える場合はカーソルが収まるようスクロールし、隠れた件数を ↑ N more / ↓ N more で表示。
  • 状態は home/state.rsRemoveModal)、描画は home/ui.rs、キー処理は home/event.rs。既存のモーダル基盤(widgetsboxed / render_modal)を再利用。
  • ドキュメント更新: document/03-commands/02-tui.md / document/04-orchestration.md / document/design/05-home.md / issues/003-session.md

モーダル UI

┌─ Remove sessions ──────────────────────────┐
│ Select sessions to remove (Space to toggle). │
│                                              │
│ > [x] feature-a                              │
│   [ ] feature-b                              │
│   [x] fix-login                              │
│                                              │
│ 2 selected                                   │
│                                              │
│ Space: toggle   Enter: remove   Esc: cancel  │
└──────────────────────────────────────────────┘
  • > がカーソル行、[x] が選択済み、[ ] が未選択。下部に選択件数とキー操作を表示。

テスト・確認方法

  • cargo fmt --check / cargo clippy --all-targets -- -D warnings / cargo test(656 件)すべてパス。
  • カバレッジ 100%(lines / functions)を維持(scripts/coverage.sh の閾値でゲート通過を確認)。
  • command.rs / state.rs / event.rs / ui.rs に単体テストを追加(モーダルの開閉・カーソル移動・選択トグル・一括削除・スクロール表示・空一覧・キャンセル)。

🤖 Generated with Claude Code

test and others added 2 commits June 14, 2026 22:24
session remove を名前なしで実行するとセッション一覧モーダルを開き、
Space で複数選択して Enter で一括削除できるようにする。

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

# Conflicts:
#	document/03-commands/02-tui.md
#	document/design/05-home.md
#	src/presentation/tui/home/ui.rs
@github-actions

Copy link
Copy Markdown

📊 Test Coverage

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

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

@kkyosuke kkyosuke merged commit 8e183d2 into main Jun 14, 2026
4 checks passed
@kkyosuke kkyosuke deleted the feat/session-remove-modal branch June 14, 2026 20:38
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>
kkyosuke added a commit that referenced this pull request Jun 21, 2026
## 目的

TUI の以下 3 つのバグを修正する。

- task のローディングアニメーション(スピナー)が回らない
- task 実行完了が UI に反映されない(session create しても何かしらの入力があるまで更新されない)
- 没入(Attached) → `Ctrl-O` で切替(Switch) に抜けたとき、`c` を 2 回押さないと session
create に入れない

## 原因

3 症状はすべて同一原因。`src/presentation/tui/term_reader.rs` の
`read_key_timeout`(animate ループのキー待ち経路)が、`crossterm::event::poll`
で到着を待ってから `console` でキーをデコードしていた。

`crossterm::event::poll` は単なる可読性チェックではなく、**tty バイトを実際に読み取って crossterm
内部のパースバッファに溜め込む**(crossterm 0.29 `event/source/unix/tty.rs` の `try_read`
→ `read_complete` → `parser.advance`)。その後 fd を直接読む `console`
にはそのバイトが見えず、次のキー入力までブロックする。この「1 キー分のズレ」が:

- **c 二度押し**: 切替で押した最初の `c` を poll が食べ、`console` は次の押下までブロック。2 回目で初めて
`switch_begin_create` が発火する。
- **スピナー停止 / 完了未反映**: 何らかのバイト・イベントが crossterm バッファに残ると `poll`
が真を返し続け、`console` 読み取りでループが停止。再描画も `tasks.drain_completed()`
も走らず、スピナーが止まり、完了がキー入力まで反映されない。

## 変更内容

到着待ちを **バイトを消費しない方式**に置き換えた。

- `libc` で tty fd を直接ポーリング(可読性チェックのみ・読み取らない)し、その後これまで通り `console`
にデコードさせる。
- `console` 自身の readiness 判定に合わせ、**macOS の tty は `select`**(macOS の tty
は `poll` 不可)、**それ以外は `poll`**。
- raw モード(`RawModeGuard`)は #74 と同じ理由(cooked モードでは改行までキー単位で readable
にならない)でそのまま維持。

ループ側のロジック・他経路の挙動は不変。

## テスト・確認方法

- `cargo fmt` / `cargo clippy --all-targets -- -D warnings` / `cargo
test`(1382 + 各バイナリ)すべてパス。
- lefthook pre-commit(branch-name / fmt / coverage)・pre-push(clippy /
test)通過。
- 仕様・画面・データ構造の変更はなく、ドキュメント更新は不要(内部 I/O 挙動の修正のみ)。

🤖 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