--- title: 'TUI rewind picker の Enter 後に live 表示が巻き戻らない問題を調査・修正する' state: 'closed' created_at: '2026-06-13T09:23:07Z' updated_at: '2026-06-13T16:34:06Z' assignee: null readiness: 'implementation_ready' risk_flags: ['tui', 'pod-protocol', 'persistence', 'history-rewind'] queued_by: 'workspace-panel' queued_at: '2026-06-13T10:53:20Z' --- ## Background 通常 TUI の manual rewind は、`Ctrl+R` で rewind targets 画面を開き、対象 user message を選択して `Enter` で Pod-authoritative に巻き戻し、選択 input を composer に復元する仕様である。 実運用中に、rewind targets 画面で `Enter` を押しても画面上は無反応に見え、巻き戻らないことがあると観測された。追加情報として、他のキーは効き、`Enter` を押した後に `Esc` で戻ると、押した回数分だけ `Rewound session:` が表示される。また、一度 `Ctrl+X` で TUI を落としてから Restore すると、巻き戻し後状態として効いている。Pod の状態は通常の停止状態のはずだった。 既存関連 Ticket: - `00001KSKBPBX0` — Pod/TUI: 手動 rewind 導線(closed) - `00001KSKBPGS8` — Pod: 任意ターンからの Fork(複数ターン巻き戻し)(planning、今回の不具合とは別件) ## Observed behavior - `Ctrl+R` で rewind targets 画面を開く。 - 上下移動など、他のキーは効く。 - 対象上で `Enter` を押しても画面上は無反応に見える。 - その後 `Esc` で rewind targets 画面を閉じると、押した `Enter` の回数分だけ `Rewound session:` が表示される。 - `Ctrl+X` で TUI を落としてから Restore すると、巻き戻し後の状態として効いている。 - Pod の状態は通常の停止状態、少なくとも Running 中ではないはずだった。 ## Investigation notes read-only 調査で以下を確認した。 - `Ctrl+R` / picker 中の `Enter` は `crates/tui/src/single_pod.rs` の key handling から `app.submit_rewind_picker()` に到達し、`Method::RewindTo` を返す。 - `submit_rewind_picker()` は `Method::RewindTo { target, expected_head_entries }` を返すだけで、picker を閉じず、applying/pending 状態も持たず、追加 `Enter` を抑止しない。 - Pod 側 `Method::RewindTo` は `crates/pod/src/controller.rs` で Idle 時に `apply_rewind()` され、成功すると `Event::RewindApplied` と `Event::Status { Idle }` を送る。 - `Rewound session:` という文字列は Pod 側ではなく `crates/tui/src/app.rs` の `Event::RewindApplied` handler でのみ生成される。したがって、`Esc` 後にこの表示が出る時点で、Pod 側 rewind は成功し、TUI も最終的には `Event::RewindApplied` を処理している。 - `Event::RewindApplied` handler は、`self.greeting.clone()` が `Some` の場合だけ `restore_snapshot(&entries, greeting)` する。`self.greeting == None` の場合、rewind 後 `entries` payload は transcript restore に使われないが、success alert は push される。 - `restore_snapshot()` は `self.blocks.clear()` する。複数回 `RewindApplied` を処理しても、restore が動いていれば古い `Rewound session:` alert は消えるはずである。`Enter` 回数分 alert が残る観測は、`restore_snapshot()` が呼ばれていない可能性、特に `self.greeting == None` の可能性と整合する。 現時点の有力仮説は、Pod 側 rewind は成功しているが、live TUI が `Event::RewindApplied` の `entries` を使って derived transcript view を再構築できておらず、reconnect/Restore 時の fresh `Event::Snapshot` で初めて表示が正しくなる、というもの。 別途、`Event::RewindApplied` の処理または描画反映が rewind picker 表示中に進まず、`Esc` などの terminal event 後の drain でまとめて処理されている可能性も調査する必要がある。 ## Requirements - `Ctrl+R` で開いた rewind targets view において、eligible な target 上で `Enter` を押した場合、Pod 側 rewind 成功後に live TUI の transcript/composer/view state が直ちに一貫した巻き戻し後状態へ更新される。 - Pod 側 rewind は成功しているのに live TUI が古い transcript 表示のまま残り、TUI restart/Restore で初めて正しい状態になる挙動を解消する。 - `Event::RewindApplied` の `entries` restore が `App::greeting` 欠落等で silently skip されないようにする。 - `RewindApplied` が picker 表示中に処理される場合でも、picker が適切に閉じるか、少なくともユーザーが次に行うべき操作が分かる状態に遷移する。 - apply が拒否または restore 不可能な場合は、無反応ではなく可視 diagnostic / notice を出す。 - `Enter` 連打により同じ target への `RewindTo` が複数積まれ、後から `Rewound session:` がまとめて出る挙動を防ぐ。 - 既存の manual rewind 仕様を維持する: - picker 開始は Idle / Paused の既存仕様を尊重する。 - apply は Pod-authoritative に検証・適用する。 - Running 中は拒否する。 - picker 表示時から head が変わった場合は apply 時に再検証して拒否する。 - 成功時は composer が空なら選択 message を composer に復元する。 - 選択だけでは auto-run しない。 ## Acceptance criteria - 再現条件または失敗条件が実装報告に説明されている。 - `Ctrl+R` → target 選択 → `Enter` で Pod 側 rewind が成功した場合、TUI restart/Restore なしに live TUI の transcript が巻き戻し後状態へ更新される。 - `Event::RewindApplied` に含まれる `entries` が、`App::greeting` 欠落等の理由で silently ignored されない。 - `self.greeting == None` または同等の metadata 欠落が起き得る場合、その経路を修正するか、self-contained event / fresh snapshot request / explicit diagnostic など設計上妥当な挙動にする。 - rewind picker 表示中に `Enter` 成功 event が来た場合、`Esc` 後に初めて `Rewound session:` が出るのではなく、その場で view state / composer / status が一貫して更新される。 - `Enter` 連打が複数 rewind request や成功 notice の後出し表示を生まない。必要なら pending/applying 状態で追加 submit を抑止する。 - apply 不可または restore 不可の場合は、無反応ではなく actionbar / diagnostic / error event 等で理由が見える。 - 既存の Esc cancel、Running 中 rejection、stale-head rejection、composer restore の挙動を壊さない。 - 関連する TUI key handling / rewind view / Pod protocol path の focused test が追加または更新されている。 ## Binding decisions / invariants - TUI がローカルに履歴を削るのではなく、rewind 適用は引き続き Pod が authoritative に検証・適用する。 - Pod 側 rewind が成功したのに live TUI が stale view のまま残る UX は許容しない。 - `Event::RewindApplied` の restore failure を silently skip しない。 - この Ticket では fork / alternate history は実装しない。 - Tool side effect の undo は実装しない。 - rewind semantics は `00001KSKBPBX0` の既存仕様を前提にし、必要な場合のみ不具合修正として局所的に調整する。 ## Implementation latitude 実装者は原因調査の結果に応じて、以下のいずれかまたは複数を修正してよい。 - `Event::RewindApplied` を self-contained にするため、必要な metadata(例: greeting/status 等)を event に含める。 - `App::greeting` が `None` になり得る経路を修正する。 - `RewindApplied` restore 不可時に fresh snapshot を要求する、または明示的 diagnostic を出す。 - rewind picker に applying/pending state を追加し、submit 後の二重 `Enter` を抑止する。 - TUI event loop / socket delivery / wake-up に、picker 表示中の Pod event 処理遅延がある場合は修正する。 - focused tests を追加するために、既存 helper の分離や再利用を行う。 ただし、manual rewind の authority boundary と destructive rewind semantics は変更しない。 ## Readiness - readiness: implementation_ready - risk_flags: [tui, pod-protocol, persistence, history-rewind] ## Escalation conditions - `Event::RewindApplied` を self-contained にするために protocol schema / compatibility への明示判断が必要になった場合。 - `App::greeting` 欠落が broader snapshot / restore / connection lifecycle の設計問題だった場合。 - dedicated view 表示中の Pod events / notices の扱いが TUI 全体の UX/architecture 判断を必要とする場合。 - current active segment / compacted segment / stale head の扱いについて、既存 Ticket の仕様と矛盾する判断が必要になった場合。 - Pod-authoritative rewind ではなく TUI 側ローカル mutation に寄せる設計変更が必要に見える場合。 ## Validation - focused test: `Event::RewindApplied` により live TUI transcript が rewind 後 `entries` で reseed され、picker が閉じる。 - focused test: `App::greeting` 欠落または metadata 欠落時に silently skip せず、設計した failure/recovery path が動く。 - focused test: rewind picker submit 後の二重 `Enter` が複数 `Method::RewindTo` を生成しない、または idempotent/rejected として可視化される。 - focused test: success 後に composer が空なら selected input が復元され、非空なら既存 composer を上書きしない。 - 必要に応じて event loop / pod event wake-up の focused test を追加する。 - `cargo fmt --check` - `cargo check -p protocol -p pod -p tui` - 関連 focused tests ## Related work - `00001KSKBPBX0` — Pod/TUI: 手動 rewind 導線 - `00001KSKBPGS8` — Pod: 任意ターンからの Fork(複数ターン巻き戻し)