8.2 KiB
8.2 KiB
Review: TUI に Task ストア状態を表示する
前提・要件の確認
TUI 側の TaskStore 取り回し
Appに最新TaskStoreを持つ:crates/tui/src/app.rs:84pub task_store: TaskStoreで保持。型は TUI 内 mirror(crates/tui/src/task.rs)でtools依存なし。要件通り。ToolCallDoneでTaskCreate/TaskUpdateを反映:crates/tui/src/app.rs:569-586、name は当該ブロックから取得しtask_store.apply_tool_call(name, &arguments)を呼ぶ。完了タイミングのみで部分引数は混入しない。要件通り。- 部分引数(streaming 中)を適用しない:
Event::ToolCallArgsDelta(app.rs:561-568)はargs_stream.push_strのみでtask_storeは触らない。要件通り。 SystemMessage観測時、[Session TaskStore snapshot]始まりなら snapshot を適用:crates/tui/src/app.rs:383-386(push_history_itemの "system" ブランチ)からtask_store.apply_system_message_text(&text)を呼ぶ。Event::SystemMessageはapp.rs:494-497でpush_history_itemを経由するため live 経路もカバー。要件通り。- 初回 history replay でも同じ経路:
restore_historyがtask_store = TaskStore::new()でリセット後(app.rs:857-858)、push_history_itemを順に通すため自動復元される。要件通り。 - 不正 JSON や未知フィールドを黙って無視:
apply_tool_callの各 match arm でif let Ok(...)ガード、apply_system_message_textもparse_snapshot_textがOptionを返す。crates/tui/src/task.rs:213-220のテストmalformed_arguments_are_silently_ignoredで挙動確認済み。要件通り。
ミニビュー
- 配置
history / mini-view / separator / status / input:crates/tui/src/ui.rs:70-77のLayout::verticalで確認。要件通り。 - 0 件時は領域ごと畳む:
task_mini_view_heightがis_empty()で 0 を返し、Constraint::Length(0)で行ごと消える(ui.rs:99-106)。要件通り。 - 最大 3 件 active を表示 + サマリ 1 行:
MINI_VIEW_MAX_ACTIVE = 3(ui.rs:94)とmini_view_summary_lineで実装。subject の改行はentry.subject.lines().next()で先頭行のみ(ui.rs:141)。要件通り。 - 件数サマリ: pending / inprogress / completed / deleted の内訳を1行で表示(
ui.rs:153-167)。要件通り。
サイドペイン
- トグルキー
Ctrl-T:crates/tui/src/main.rs:436-439。要件通り。 - 横分割は history 領域内のみ:
draw_history内でLayout::horizontal分割し、status / input / separator は外側chunksの全幅を維持(ui.rs:326-334)。要件通り。 - 全件(pending / inprogress / completed / deleted)+ description を含めて列挙:
draw_task_side_pane(ui.rs:386-460)。各エントリはtaskid+ status mark + subject + description を表示。要件通り。 - スクロール対応:
task_pane_scrollをAppに保持、PageUp/PageDownがペイン open 時に優先(main.rs:471-489)。最大値で clamp(ui.rs:449-452)。要件通り。
レイアウトと一貫性
- ミニビューとサイドペインは同じ
App::task_storeを参照する単一情報源(ui.rs:81, 399)。要件通り。 - 既存の completion popup は
chunks[4](input rect) を使うため、ミニビューが挟まっても座標計算に影響なし(ui.rs:86-88, 191-246)。 - スクロール / status line は
chunks[0]のinnerを使うため、サイドペインが開いてもスクロール計算は分割後の history rect に追従(ui.rs:347-350)。 - 副作用無し。
完了条件
すべて満たしている。cargo test -p tui 71 件合格、cargo build --workspace クリーン。
アーキテクチャ・スコープ
tools依存を持ち込まない方針 が一貫して守られている。TUI 内task.rsmirror はtools::TaskStore全機能ではなく TUI が必要とする最小サブセット(apply_tool_call/apply_system_message_text/tasks()/counts())に絞っている。feedback_llm_worker_scope/ 「TUI は表示専門レイヤ」の方針に整合。serdeはcargo addで追加(crates/tui/Cargo.toml:19)。memory のfeedback_cargo_add遵守。- protocol 表面は無変更。
Event::SystemMessage/Event::ToolCallDone/Event::Historyの既存経路にすべて乗っている。チケットの「protocol 表面を増やさない」目標を達成。 - LLM コンテキスト加工原則: history からの純粋な再構成変換であり context への割り込みではない。整合済み。
- ファイル分割: 新規
task.rsモジュール(180行 + テスト)として切り出されており、app.rs/ui.rsへの変更も既存パターン(cache.rs / scroll.rs と同じ位置付け)に整合。コードベースを歪めていない。
指摘事項
Non-blocking / Follow-up
- snapshot text フォーマット契約の自動検出機構が無い:
crates/tui/src/task.rs:168-179parse_snapshot_textはcrates/tools/src/task.rs:393-404parse_compact_snapshot_textと byte-for-byte 同一の手書きクローン。tools::render_snapshotのフォーマット(フェンス記号・改行配置・ヘッダ文字列)が変わると、TUI は無言で snapshot 検出を失敗するだけで CI からは見えない。Doc コメント(task.rs:8-17)と TUI 側ユニットテストの手書き fixture(wrap_snapshotヘルパ)で「契約に乗っているつもり」を表明しているが、tools::render_snapshotの出力をそのまま食わせる意味でのリンク確認は無い。- 落としどころ案:
crates/toolsをdev-dependenciesだけに入れ、#[cfg(test)]でtools::render_snapshot(...)の実出力をtui::task::TaskStore::apply_system_message_textに流すクロステストを 1 本足す。本番ビルドの依存は増やさず、契約破綻だけ拾える。今のスコープ外でも構わない。
- 落としどころ案:
- History replay 経路に「retained TaskCreate → 末尾 snapshot で上書き」シナリオの App テストが無い:
tools側にはtrailing_snapshot_supersedes_pre_compact_taskcreates_in_retained(crates/tools/src/task.rs:584-628)があるが、TUI のApp::handle_pod_event(Event::History { ... })経由で同じ並びを通す App レベルテストは未追加。実装上はpush_history_itemを順に呼ぶだけなので動くはずで、history_replay_reconstructs_task_store(app.rs:1404-1448)とsnapshot_text_replaces_state_and_advances_next_id(task.rs:251-284)の組み合わせで間接的にカバーはされている。リグレッション保護として 1 本欲しい程度の話。 - 狭い端末でのトグル無反応:
task_side_pane_width(ui.rs:373-384)はarea_width < 60なら 0 を返すため、task_pane_open == trueでも視覚的に何も起きない。Ctrl-T が "効いていない" ように見えるリスクがある。レアケースなので非ブロッキング。
Nits
crates/tui/src/task.rs:165-166のTaskSnapshotはtasksフィールドのみだが、本家tools::TaskSnapshotには他フィールドが追加される可能性がある。#[serde(deny_unknown_fields)]を付けないことで forward-compat を保っている(適切)が、その意図を 1 行コメントしておくと将来読み手に親切。task_status_mark(ui.rs:172-184)の戻り値タプルに型エイリアスを入れると、サイドペイン側でも mini-view 側でも使われている共有意図がコードから読みやすい。今のままで実害は無い。
判断
Approve — チケットの前提・方針・要件・完了条件をすべて満たしており、設計の意図(tools 非依存・protocol 不変・history からの純粋再構成)も忠実に実装されている。tools 側との snapshot フォーマット契約はテキスト一致でしか担保されていないが、これはチケットが意識的に選んだトレードオフであり、必要であれば dev-dependency でクロステストを 1 本足す程度のフォローアップで十分。コードベースを歪める箇所は無い。