diff --git a/.yoi/tickets/00001KSKBPAXR/item.md b/.yoi/tickets/00001KSKBPAXR/item.md index 56af3a0d..9cb0b0c4 100644 --- a/.yoi/tickets/00001KSKBPAXR/item.md +++ b/.yoi/tickets/00001KSKBPAXR/item.md @@ -1,8 +1,8 @@ --- title: "内部 Worker / 内部 Pod の Workflow 化" -state: "planning" +state: 'closed' created_at: "2026-05-27T00:00:03Z" -updated_at: "2026-05-27T00:00:03Z" +updated_at: '2026-06-13T09:56:34Z' --- ## Migration reference diff --git a/.yoi/tickets/00001KSKBPAXR/resolution.md b/.yoi/tickets/00001KSKBPAXR/resolution.md new file mode 100644 index 00000000..adcadedf --- /dev/null +++ b/.yoi/tickets/00001KSKBPAXR/resolution.md @@ -0,0 +1,11 @@ +## Resolution + +ユーザー指示により close する。 + +この Ticket は legacy migration 由来の「内部 Worker / 内部 Pod を Workflow と同一仕様で扱う」構想だったが、現在の設計では Workflow と Prompt resource / internal prompt は別 boundary として整理されている。 + +- public builtin workflow / Yoi dogfood workflow の分離、`resources/workflows/.md`、workspace override、builtin provenance は関連 Ticket で対応済み。 +- internal prompt は `resources/prompts/internal/*` と `PromptCatalog` / `resources/prompts/internal.toml` 側の責務として扱う。 +- 元の要件に残る `INSOMNIA`、`.insomnia/workflow`、旧 `tickets/*.md` 前提は current Yoi 設計と一致しない。 + +したがって、この Ticket は実装 routing せず、退役 / superseded として完了扱いにする。将来、internal prompt の remaining gap や internal Workflow substrate が必要になった場合は、現在の Prompt resource / Workflow boundary を前提にした別の concrete Ticket として作成する。 \ No newline at end of file diff --git a/.yoi/tickets/00001KSKBPAXR/thread.md b/.yoi/tickets/00001KSKBPAXR/thread.md index bb9ff4a8..8ab1d753 100644 --- a/.yoi/tickets/00001KSKBPAXR/thread.md +++ b/.yoi/tickets/00001KSKBPAXR/thread.md @@ -5,3 +5,81 @@ Migrated from tickets/internal-worker-workflow.md. No legacy review file was present at migration time. --- + + + +## Plan + +## Intake refinement + +既存 Ticket `00001KSKBPAXR` の body / thread / artifacts を確認した。artifacts は `.gitkeep` のみで、thread は migration 記録のみだった。重複 Ticket は作成しない。 + +### 現状整理 + +この Ticket は legacy migration 時点の前提を多く含んでいる。 + +- 旧名 `INSOMNIA`、旧 path `.insomnia/workflow/.md`、旧 `tickets/*.md` 参照が残っている。 +- その後、Workflow / prompt resource 境界の設計は更新されている。 + - `00001KTRKZ14C` は closed。public builtin workflow と Yoi dogfood workflow の分離、`resources/workflows/.md`、`WorkflowSource::Builtin`、workspace override、resident core/optional 方針を記録済み。 + - `00001KTGFMW70` は closed。embedded builtin Workflow resources、Workflow-required builtin Knowledge fallback/provenance、workspace override を実装済み。 + - 現在の internal prompt は `resources/prompts/internal/{memory_extract_system,memory_consolidation_system,compact_system}.md` と `PromptCatalog` / `resources/prompts/internal.toml` 側で扱われている。 + +### Intake 判断 + +現時点で、この Ticket を元のまま「内部 Worker / 内部 Pod を Workflow と同一仕様で実行する」実装 Ticket として route するのは危険。現在の設計では、Workflow は手続き・procedural flow、Prompt resources は system prompt / role behavior / internal worker prompt を所有する別 boundary であり、両者を混ぜると prompt-context / workflow-boundary / tool authority の責務が曖昧になる。 + +したがって readiness は `requirements_sync_needed`。Orchestrator に渡す前に、人間/maintainer が次のいずれかを選ぶ必要がある。 + +1. **退役 / superseded 扱い**: この legacy Ticket は `00001KTRKZ14C`、`00001KTGFMW70`、および現在の `PromptCatalog` internal prompt resource 化で実質的に置き換えられたとして、Orchestrator/human が close する。 +2. **PromptCatalog follow-up へ retarget**: Workflow 化ではなく、internal worker prompt の remaining gap を concrete に切り直す。例: extract / consolidation / compact の workspace/user/prompt-pack override、provenance diagnostics、test coverage、docs の不足確認。 +3. **真の internal Workflow 呼び出し substrate を新設**: 既存の Prompt resource / Workflow boundary を変更する設計 Ticket として再定義する。この場合は、なぜ PromptCatalog では不足か、tool surface 表明を workflow frontmatter に載せる authority model をどう安全にするか、`user_invocable: false` と resident/launch provenance をどう扱うかを先に設計判断する必要がある。 + +### Binding decisions / invariants for any refinement + +- Workflow prose、Prompt fragments/internal prompts、Knowledge records は別 resource boundary として扱う。混ぜる場合は明示的な設計判断が必要。 +- 内部 Worker prompt を model-visible context に載せる場合も、turn を跨ぐ volatile hidden injection にならないよう、既存の history / prompt context 原則に従う。 +- `resources/prompts` にある internal prompt は PromptCatalog の責務であり、Workflow loader の責務へ silently 移さない。 +- `resources/workflows` の builtin workflow は procedural flow の resource であり、Yoi dogfood semantics を public builtin slug に隠さない。 +- `INSOMNIA` / `.insomnia` / legacy `tickets/*.md` 参照は current Ticket routing 前に Yoi / `.yoi` / canonical Ticket ID へ読み替えまたは整理する。 + +### Risk flags / reviewer focus + +- `prompt-context` +- `workflow-boundary` +- `runtime-resource` +- `tool-authority` +- `memory-prompt` +- `migration-compat` + +### Open question + +この Ticket は退役させるか、PromptCatalog follow-up に切り直すか、internal Workflow substrate の新設設計として再定義するか。現時点ではこの人間判断がないため、`ready` にはしない。 + +--- + + + +## State changed + +Ticket を closed にしました。 + + +--- + + + +## 完了 + +## Resolution + +ユーザー指示により close する。 + +この Ticket は legacy migration 由来の「内部 Worker / 内部 Pod を Workflow と同一仕様で扱う」構想だったが、現在の設計では Workflow と Prompt resource / internal prompt は別 boundary として整理されている。 + +- public builtin workflow / Yoi dogfood workflow の分離、`resources/workflows/.md`、workspace override、builtin provenance は関連 Ticket で対応済み。 +- internal prompt は `resources/prompts/internal/*` と `PromptCatalog` / `resources/prompts/internal.toml` 側の責務として扱う。 +- 元の要件に残る `INSOMNIA`、`.insomnia/workflow`、旧 `tickets/*.md` 前提は current Yoi 設計と一致しない。 + +したがって、この Ticket は実装 routing せず、退役 / superseded として完了扱いにする。将来、internal prompt の remaining gap や internal Workflow substrate が必要になった場合は、現在の Prompt resource / Workflow boundary を前提にした別の concrete Ticket として作成する。 + +--- diff --git a/.yoi/tickets/00001KTZY8HK2/artifacts/.gitkeep b/.yoi/tickets/00001KTZY8HK2/artifacts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.yoi/tickets/00001KTZY8HK2/item.md b/.yoi/tickets/00001KTZY8HK2/item.md new file mode 100644 index 00000000..efea4b20 --- /dev/null +++ b/.yoi/tickets/00001KTZY8HK2/item.md @@ -0,0 +1,15 @@ +--- +title: 'Profile extend の authority field を明示的に置換できるようにする' +state: 'planning' +created_at: '2026-06-13T07:31:09Z' +updated_at: '2026-06-13T07:31:25Z' +assignee: null +--- + +## 背景 + +LocalTicketBackend によって作成されました。 + +## 受け入れ条件 + +- 未定 diff --git a/.yoi/tickets/00001KTZY8HK2/thread.md b/.yoi/tickets/00001KTZY8HK2/thread.md new file mode 100644 index 00000000..d9c73c1b --- /dev/null +++ b/.yoi/tickets/00001KTZY8HK2/thread.md @@ -0,0 +1,31 @@ + + +## 作成 + +LocalTicketBackend によって作成されました。 + +--- + + + +## Plan + +背景: +- `yoi.profile.extend("builtin:default", { scope = yoi.scope.workspace_read() })` は、Lua レベルの deep merge により base の `scope.intent = "workspace_write"` と override の object が merge される。 +- その後の `PodManifestConfig::merge` でも scope allow/deny は加算されるため、role profile が `workspace_read()` を指定しても `builtin:default` 由来の direct workspace write が残り得る。 +- 今回の暫定対応では Orchestrator profile を `scope = "workspace_read"` / `delegation_scope = "workspace_write"` にして、object merge ではなく scalar replacement を使った。 + +要件: +- Profile 継承は維持する。 +- `scope` / `delegation_scope` のような authority-bearing field について、意図的に inherited value を置換またはクリアできる API を設計する。 +- role profile が default profile の model / compaction / feature defaults 等を継承しつつ、direct authority だけを安全に narrower scope へ置換できるようにする。 +- 空 object `{}` で消せるようにするか、`yoi.profile.replace(...)` / `yoi.scope.replace(...)` / `replace = true` などの明示 API にするかは設計で決める。 +- 不注意な権限拡張や hidden fallback を避け、resolved profile の authority が読みやすいこと。 + +受け入れ条件: +- Orchestrator role profile が `builtin:default` を継承しても direct workspace write を要求しないことを test で確認する。 +- 既存 profile API 互換を壊す場合は、移行対象を明示する。 +- `scope` と `delegation_scope` の merge/replace semantics が docs または test 名から読み取れる。 + + +--- diff --git a/.yoi/tickets/00001KV04NJ8D/artifacts/.gitkeep b/.yoi/tickets/00001KV04NJ8D/artifacts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.yoi/tickets/00001KV04NJ8D/item.md b/.yoi/tickets/00001KV04NJ8D/item.md new file mode 100644 index 00000000..5c89b119 --- /dev/null +++ b/.yoi/tickets/00001KV04NJ8D/item.md @@ -0,0 +1,123 @@ +--- +title: 'TUI rewind picker の Enter 後に live 表示が巻き戻らない問題を調査・修正する' +state: 'ready' +created_at: '2026-06-13T09:23:07Z' +updated_at: '2026-06-13T10:02:13Z' +assignee: null +readiness: 'implementation_ready' +risk_flags: ['tui', 'pod-protocol', 'persistence', 'history-rewind'] +--- + +## 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(複数ターン巻き戻し) diff --git a/.yoi/tickets/00001KV04NJ8D/thread.md b/.yoi/tickets/00001KV04NJ8D/thread.md new file mode 100644 index 00000000..7a046720 --- /dev/null +++ b/.yoi/tickets/00001KV04NJ8D/thread.md @@ -0,0 +1,102 @@ + + +## 作成 + +LocalTicketBackend によって作成されました。 + +--- + + + +## Plan + +## 調査・修正計画 + +ユーザー合意により、実装時はまず一時 diagnostic を入れて live 挙動を確認し、原因を確定してから本修正する方針とする。問題が解消したら一時ログは外して完了する。 + +### Phase 1: 一時 diagnostic の追加 + +`Event::RewindApplied` 周辺と rewind picker submit 周辺に、秘密情報を含まない短い一時ログまたは TUI diagnostic を入れる。 + +確認する値: + +- `Event::RewindApplied` が `Enter` 直後に処理されるか、`Esc` 後まで遅れるか。 +- `Event::RewindApplied` 処理時の `App::greeting.is_some()`。 +- `restore_snapshot(&entries, greeting)` を呼べているか。 +- `entries.len()`。 +- `rewind_picker.is_some()` / applying 状態。 +- `input.is_empty()` と composer restore 分岐。 +- `pod_status`。 + +### Phase 2: live 再現確認 + +一時 diagnostic 入りの binary で、既知の手順を再現する。 + +1. TUI 起動。 +2. `Ctrl+R` で rewind targets を開く。 +3. target を選択して `Enter`。 +4. 画面が無反応なら少し待つ。 +5. `Esc` で戻る。 +6. diagnostic から、以下のどれに該当するか判断する。 + +判断観点: + +- `RewindApplied` が `Enter` 直後に処理され、`greeting=false` なら、live TUI が rewind 後 `entries` を restore できず stale 表示になっている可能性が高い。 +- `RewindApplied` が `Esc` 後まで処理されないなら、event loop / socket delivery / wake-up 側を主因として追う。 +- `RewindApplied` が `Enter` 直後に処理され、`greeting=true` かつ restore 済みなら、draw / overlay / picker close / scroll state の問題を疑う。 + +### Phase 3: 本修正 + +原因に応じて修正する。 + +- `App::greeting` 欠落で restore が skip されている場合: + - `RewindApplied` restore failure を silent success にしない。 + - `greeting` を失う経路を修正するか、`RewindApplied` を self-contained にする、または fresh snapshot request / explicit diagnostic の妥当な方針を実装する。 +- picker 中に Pod event 処理が遅れる場合: + - single-pod TUI event loop / `PodClient` wake-up / drain ordering / connection gating を修正し、Pod event で即時 redraw されるようにする。 +- restore は動いているが表示が stale の場合: + - `restore_snapshot()` 後の picker close、draw、scroll、overlay state を修正する。 + +### Phase 4: 二重 submit 防止 + +主因修正とは別に、rewind picker submit 後の `Enter` 連打を防ぐ。 + +- `RewindPickerState` に applying/pending 状態を持たせる、または同等の idempotency guard を追加する。 +- submit 後は追加 `Enter` で複数 `Method::RewindTo` を生成しない。 +- 必要なら `Applying rewind...` のような状態表示を出す。 +- 成功 / failure / rejection で pending を解除または picker を閉じる。 + +### Phase 5: focused test と一時ログ削除 + +- 原因に対応する focused regression test を追加する。 +- `Event::RewindApplied` で live TUI transcript が巻き戻し後 `entries` に reseed され、picker が閉じることを確認する。 +- metadata 欠落時に silently skip しないことを確認する。 +- pending 中の追加 `Enter` が複数 `Method::RewindTo` を生成しないことを確認する。 +- composer restore 分岐を確認する。 +- live 確認で問題が解消したら、一時 diagnostic / debug log を削除する。 + +### Validation + +- focused tests +- `cargo fmt --check` +- `cargo check -p protocol -p pod -p tui` + +この計画は、原因未確定のまま protocol/schema 変更へ飛ばず、まず live diagnostic で `greeting` 欠落・event 処理遅延・表示更新不整合のどれかを切り分けることを重視する。 + +--- + + + +## Intake summary + +ユーザー依頼を `00001KV04NJ8D` として具体化し、read-only 調査結果と追加観測を反映した。Pod 側 rewind は成功しているが live TUI が `Event::RewindApplied` の反映または snapshot restore を即時実行できていない可能性を主仮説として記録済み。合意済み計画として、一時 diagnostic を入れて live 再現で `RewindApplied` timing / `App::greeting` / `restore_snapshot()` / picker pending を切り分け、原因修正後に一時ログを外し、focused tests と `cargo fmt --check` / `cargo check -p protocol -p pod -p tui` で検証する。 + +--- + + + +## State changed + +ユーザーが Ticket の ready 化を明示したため、Orchestrator が routing できる状態にする。 + +--- diff --git a/.yoi/tickets/00001KV0723PC/artifacts/.gitkeep b/.yoi/tickets/00001KV0723PC/artifacts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.yoi/tickets/00001KV0723PC/item.md b/.yoi/tickets/00001KV0723PC/item.md new file mode 100644 index 00000000..ee5d57ec --- /dev/null +++ b/.yoi/tickets/00001KV0723PC/item.md @@ -0,0 +1,75 @@ +--- +title: 'Panel Quit 時の断続的な遅延を調査して解消する' +state: 'ready' +created_at: '2026-06-13T10:04:55Z' +updated_at: '2026-06-13T10:05:07Z' +assignee: null +readiness: 'spike_needed' +risk_flags: ['tui-panel', 'shutdown-latency', 'async-cancellation'] +--- + +## Background + +Panel から Quit するときに、終了まで遅延が発生することがある。ユーザー観測の断続的な UX 劣化として扱い、原因を調査したうえで修正する。 + +関連しそうな既存記録として、Panel の非同期遷移/フリーズ回避に関する `00001KTFMMZP0` は closed。今回の主対象は Quit 操作の遅延であり、同一目的の未完了 Ticket は確認できなかった。 + +## Request snapshot + +- 依頼: 「PanelからQuitするときに遅延が発生することがある。調査して修正チケット切って」 +- handoff workspace: `yoi` +- workspace_orchestrator_pod: `yoi-orchestrator` + +## Requirements + +- `yoi panel` / workspace Panel の Quit 操作で、ユーザー入力後に不必要な待ちが発生する原因を特定する。 +- Quit は、通常の端末復旧や安全な最小限の cleanup を除き、進行中の reload / notice dispatch / snapshot 読み込み / Pod 状態観測などの完了待ちでブロックされないこと。 +- 断続的な遅延であっても、原因が再発しにくい形で修正する。単に poll interval を短くするだけの対症療法にしない。 +- 既存の Panel 役割、Ticket 操作、Companion/Orchestrator composer target、row selection semantics を壊さない。 + +## Acceptance criteria + +- Quit 入力(現状の `Ctrl+C` / `Ctrl+D` 経路を含む)が、Panel の background reload や queue-attention notice などの非本質的処理待ちで目に見えて遅延しない。 +- 遅延原因と修正方針が implementation report に説明されている。 +- 可能な範囲で unit test または小さな regression test が追加され、少なくとも Quit 経路が pending background work によってブロックされないことを検証する。 +- 自動化が難しい場合は、manual validation 手順と観測結果を implementation report に残す。 + +## Binding decisions / invariants + +- Quit はユーザーの明示的な終了意思であり、Panel の観測/reload/通知送信の完了を待つために遅延してはならない。 +- ただし端末状態復旧、描画 backend の正常終了、Rust drop による安全な abort など、最小限の終了処理は維持する。 +- Quit 改善のために Ticket lifecycle authority、Pod authority boundary、Panel row/action semantics を変更しない。 +- `resources/prompts` や durable Ticket schema の変更を伴う必要は、現時点では想定しない。必要になった場合は escalation する。 + +## Implementation latitude + +- まず `crates/tui/src/multi_pod.rs` の Panel event loop / `PendingReload` / Quit handling 周辺を調査する。 +- 必要に応じて quit 受付前後の await、background task abort/drop、terminal event polling、queue-attention notice dispatch、snapshot load の相互作用を確認する。 +- 修正手段は実装者に委ねるが、終了時に不要な await を避ける、Quit 前に cancellable background work を abort する、または event loop の優先度を調整するなど、設計上説明可能な変更にする。 +- 再現が難しい場合は、遅延し得るコードパスを単体で再現できる test seam を作ることを優先してよい。 + +## Readiness + +- readiness: spike_needed +- 理由: 現象は明確だが断続的であり、現時点では再現条件・遅延箇所・影響する async path が未特定。先に短い調査/計測または code-path analysis が必要。 +- risk_flags: [tui-panel, shutdown-latency, async-cancellation] + +## Open questions + +- 遅延の典型時間、発生頻度、再現しやすい Panel 状態(reload 中、Orchestrator notice 中、Pod 多数、dirty workspace 等)は未提供。実装者は必要なら調査中に記録する。 + +## Escalation conditions + +- 修正に Panel の public UX、Ticket lifecycle semantics、Pod shutdown semantics、authority boundary の変更が必要になりそうな場合は Orchestrator/maintainer に戻す。 +- 端末 cleanup や Pod process lifecycle を犠牲にしないと遅延を解消できない場合は、方針判断を求める。 +- 遅延の原因が Panel 外(OS 端末、shell、external command、specific provider/network)にある場合は、証拠と切り分け結果を残して routing し直す。 + +## Validation + +- 変更内容に応じて `cargo test -p yoi-tui` または該当 crate の focused test を実行する。 +- 必要に応じて `cargo check` / `git diff --check` を実行する。 +- 可能なら `yoi panel` を実際に起動し、background reload があり得る状態でも Quit が速やかに戻ることを手動確認する。 + +## Related work + +- `00001KTFMMZP0`: Panel の非同期遷移/フリーズ回避に関する closed Ticket。今回の修正で既存判断と矛盾しないか参考にする。 diff --git a/.yoi/tickets/00001KV0723PC/thread.md b/.yoi/tickets/00001KV0723PC/thread.md new file mode 100644 index 00000000..1043a85e --- /dev/null +++ b/.yoi/tickets/00001KV0723PC/thread.md @@ -0,0 +1,23 @@ + + +## 作成 + +LocalTicketBackend によって作成されました。 + +--- + + + +## Intake summary + +Panel から Quit するときに断続的な遅延が発生する問題について、調査・修正用 Ticket を作成した。現象は明確だが再現条件や遅延箇所は未特定のため readiness は `spike_needed`。Orchestrator はまず `crates/tui/src/multi_pod.rs` の Panel event loop / `PendingReload` / Quit handling / queue-attention notice dispatch / snapshot reload 周辺を調査し、Quit が非本質的な background work の完了待ちでブロックされない修正へ routing できる。関連 closed Ticket `00001KTFMMZP0` は Panel 非同期遷移改善で、今回の Quit 遅延とは別の follow-up として扱う。 + +--- + + + +## State changed + +要件・受け入れ条件・binding decisions・調査焦点が揃っており、Orchestrator が spike/実装修正へ routing できるため ready にする。 + +--- diff --git a/.yoi/tickets/00001KV072V89/artifacts/.gitkeep b/.yoi/tickets/00001KV072V89/artifacts/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.yoi/tickets/00001KV072V89/item.md b/.yoi/tickets/00001KV072V89/item.md new file mode 100644 index 00000000..e17de731 --- /dev/null +++ b/.yoi/tickets/00001KV072V89/item.md @@ -0,0 +1,85 @@ +--- +title: 'Workspace panel の View item をマウスで選択できるようにする' +state: 'ready' +created_at: '2026-06-13T10:05:19Z' +updated_at: '2026-06-13T10:05:28Z' +assignee: null +readiness: 'implementation_ready' +risk_flags: ['tui-input', 'mouse-capture', 'panel-ux'] +--- + +## Background + +`yoi panel` で、マウスによる選択を端末側のテキスト選択に依存する現行スタイルではなく、Panel/View の item に限ってアプリ内でネイティブに選択できるようにしたい。 + +現状の TUI は端末テキスト選択を温存するため、mouse capture を限定している箇所がある。Yoi には既に `EnableWheelMouseCapture` があり、`?1000h` + `?1006h` により wheel と button press/release を扱える一方、drag-motion tracking は要求しない方針になっている。この方針は、click-to-select では維持できる可能性が高い。 + +参考 UX として、`./ghq.local/github.com/anomalyco/opencode` にある OpenCode TUI の select/dialog/autocomplete 系の挙動を参照する。OpenCode は TSX component tree 側で `onMouseOver` / `onMouseDown` / `onMouseUp` を使える構造だが、Yoi は ratatui なので DOM-like handler の移植ではなく、描画済み item rectangle を `MouseEvent { column, row }` で hit-test する設計になる。 + +## Requirements + +- `yoi panel` の View item / row に対するマウスクリックで、対応する item が selected row になる。 +- 選択可能範囲は Panel/View の item 領域に限定する。 +- item 外クリックは composer 入力や既存状態を不必要に壊さない。 +- composer の通常テキスト入力・既存キーボード操作は維持する。 +- クリック選択と既存の `↑` / `↓` / Enter / Esc / Tab 等の意味が矛盾しない。 +- mouse wheel scrolling など既存のマウス挙動がある場合は、不要に退行させない。 +- UI help / actionbar / diagnostics に、必要なら mouse selection が可能であることを短く反映する。 + +## Acceptance criteria + +- Panel の表示中に View item / row をクリックすると、その item が selected になる。 +- selected item に対する既存の blank Enter / actionbar action / detail 表示が、クリック後の選択に対して働く。 +- item 外クリックでは不正な selection change や composer draft loss が起きない。 +- composer 文字入力、矢印キー選択、Tab target switching、Esc の既存挙動が保たれる。 +- 端末 text selection を完全に代替する汎用ドラッグ選択はこの Ticket では実装しない。 +- Focused TUI tests が、row hit testing / click selection / non-row click no-op / existing keyboard behavior preservation をカバーする。 +- 妥当な検証として少なくとも focused `cargo test -p tui ...`、`cargo fmt --check`、`git diff --check` を実施する。 + +## Binding decisions / invariants + +- 対象は `yoi panel` の View item selection。single-Pod conversation history 全体の block focus / navigation mode はこの Ticket の主目的にしない。 +- マウス操作で selected-Pod direct-send semantics を復活させない。 +- composer text entry を優先する既存方針を壊さない。 +- クリックは selection のための操作であり、Queue / Open / Close などの destructive or workflow state mutation action を即時実行しない。実行は既存の明示 action path に委ねる。 +- terminal の任意範囲テキスト選択を再現する汎用 selection 機能は作らない。 +- 既存の least-intrusive な `EnableWheelMouseCapture` 方針を優先し、drag tracking を有効化して端末選択への副作用を増やす変更は避ける。 + +## Implementation latitude + +- 具体的な hitbox 管理、row coordinate mapping、描画時の layout rect 保存方法は実装者が調査して選べる。 +- ratatui では OpenCode の DOM-like mouse handlers は使えないため、render 時に item rect registry を作り、`MouseEvent { column, row }` を hit-test する実装が有力。 +- 必要なら最初は Panel rows のみ対象にし、詳細 pane 内の個別要素クリックは範囲外にしてよい。 +- OpenCode 風に hover で active selection を動かすかどうかは、この Ticket の必須条件ではない。MVP は click/down による selection だけでよい。 +- click release で item action を実行する挙動は MVP では必須にしない。安全側では selection のみに留める。 +- UI 表示文言は短く保ち、status bar を冗長化しない。 +- 外部 crate を使う場合は、`tui-widget-list` の hit-test、`ratatui-interact` の click region/focus 管理などを候補として検討できる。ただし既存 Panel 構造を大きく置き換える必要がある場合は自前の小さい hit-test registry を優先する。 + +## Readiness + +- readiness: implementation_ready +- risk_flags: [tui-input, mouse-capture, panel-ux] + +## Escalation conditions + +- Mouse capture を有効化すると端末の通常選択・貼り付け・wheel・IME・composer 入力に副作用が出る場合。 +- Click selection と Enter / blank composer open / Ticket Queue などの workflow action の境界が曖昧になる場合。 +- Panel 以外の TUI view、single-Pod history block focus、drag selection まで自然に巻き込みたくなる場合。 +- ratatui/crossterm の制約で item hit testing のために大きな描画アーキテクチャ変更が必要になる場合。 +- 外部 crate 導入が Panel の ViewModel / custom rendering を大きく歪める場合。 + +## Validation + +- Focused tests around workspace panel / multi_pod mouse event handling. +- Existing workspace panel keyboard/composer tests. +- `cargo test -p tui workspace_panel --lib` または該当 focused tests。 +- `cargo fmt --check` +- `git diff --check` +- 必要に応じて `cargo check --workspace --all-targets` + +## Related work + +- `00001KSKBPPMR` — TUI navigation mode / block focus design。広い設計背景。 +- `00001KTJ0B3G0` — bare letter shortcuts removal。composer 入力優先の既存決定。 +- `00001KTFEVH3R` — Panel row/action simplification。selected-row actionbar 方針。 +- `./ghq.local/github.com/anomalyco/opencode` — OpenCode TUI の select/dialog/autocomplete 系 mouse UX 参考。 diff --git a/.yoi/tickets/00001KV072V89/thread.md b/.yoi/tickets/00001KV072V89/thread.md new file mode 100644 index 00000000..03abb971 --- /dev/null +++ b/.yoi/tickets/00001KV072V89/thread.md @@ -0,0 +1,23 @@ + + +## 作成 + +LocalTicketBackend によって作成されました。 + +--- + + + +## Intake summary + +`yoi panel` の View item / row をマウスクリックでアプリ内選択できるようにする Ticket として materialize した。参考 UX は `./ghq.local/github.com/anomalyco/opencode` の OpenCode TUI selection/dialog/autocomplete 系。ratatui では DOM-like handler ではなく、描画時の item rect registry と `MouseEvent { column, row }` hit-test が有力。MVP は click/down による selection のみで、Queue/Open/Close 等の workflow action 即時実行、汎用 drag/text selection、single-Pod history block focus は範囲外。既存 composer 入力優先、selected-Pod direct-send 非復活、least-intrusive mouse capture 方針を invariant とする。 + +--- + + + +## State changed + +要件、受け入れ条件、binding invariants、OpenCode 参考、実装裁量、escalation conditions、validation が整理され、Orchestrator が routing 可能な状態になったため ready にする。 + +--- diff --git a/crates/tui/src/single_pod.rs b/crates/tui/src/single_pod.rs index 3379c732..af648ca4 100644 --- a/crates/tui/src/single_pod.rs +++ b/crates/tui/src/single_pod.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::future::Future; use std::io; use std::path::PathBuf; @@ -12,8 +13,8 @@ use crossterm::event::{ self, DisableMouseCapture, Event as TermEvent, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind, }; -use crossterm::execute; use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; +use crossterm::{Command, execute}; use protocol::{Method, PodStatus}; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; @@ -30,6 +31,33 @@ use crate::{multi_pod, picker, spawn, ui}; type FullscreenTerminal = Terminal>; +/// Enable the narrowest standard xterm mouse mode that still reports wheel +/// events. Crossterm's `EnableMouseCapture` also enables button-event +/// tracking (`?1002h`), which requests drag-motion reports and interferes +/// with terminal text selection more aggressively. Normal tracking (`?1000h`) +/// reports button presses, releases, and wheel notches, but does not request +/// drag-motion reports; the TUI ignores the non-wheel events. +#[derive(Debug, Clone, Copy)] +struct EnableWheelMouseCapture; + +impl Command for EnableWheelMouseCapture { + fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { + // 1000: normal mouse tracking (includes wheel button presses) + // 1006: SGR extended coordinates used by crossterm's parser + f.write_str("\x1B[?1000h\x1B[?1006h") + } + + #[cfg(windows)] + fn execute_winapi(&self) -> io::Result<()> { + Ok(()) + } + + #[cfg(windows)] + fn is_ansi_code_supported(&self) -> bool { + true + } +} + fn resolve_socket(pod_name: &str, override_path: Option) -> PathBuf { if let Some(p) = override_path { return p; @@ -245,10 +273,10 @@ pub(crate) async fn run_spawn( fn enter_fullscreen() -> Result> { let mut stdout = io::stdout(); - // Do not enable mouse capture: terminal-native drag selection is more - // important than receiving mouse events in Yoi. Scroll-wheel handling below - // remains best-effort for terminals that still emit mouse events. - execute!(stdout, EnterAlternateScreen)?; + // Enable only normal mouse tracking for wheel events. Avoid crossterm's + // full mouse capture because it requests drag-motion events and breaks + // terminal-native text selection. + execute!(stdout, EnterAlternateScreen, EnableWheelMouseCapture)?; let backend = CrosstermBackend::new(stdout); Ok(Terminal::new(backend)?) } @@ -256,8 +284,13 @@ fn enter_fullscreen() -> Result> fn enter_fullscreen_existing( terminal: &mut FullscreenTerminal, ) -> Result<(), Box> { - // Keep mouse capture disabled for terminal-native drag selection. - execute!(terminal.backend_mut(), EnterAlternateScreen)?; + // Re-enable the same least-intrusive wheel mouse mode after returning from + // nested inline screens. + execute!( + terminal.backend_mut(), + EnterAlternateScreen, + EnableWheelMouseCapture + )?; Ok(()) } @@ -901,6 +934,16 @@ mod tests { use super::*; use protocol::{Event, RewindTarget, RewindTargetId, Segment}; + #[test] + fn wheel_mouse_capture_uses_normal_tracking_without_drag_capture() { + let mut ansi = String::new(); + Command::write_ansi(&EnableWheelMouseCapture, &mut ansi).unwrap(); + + assert_eq!(ansi, "\x1B[?1000h\x1B[?1006h"); + assert!(!ansi.contains("?1002h")); + assert!(!ansi.contains("?1003h")); + } + #[tokio::test] async fn terminal_event_is_selected_before_ready_pod_event() { let (tx, mut rx) = mpsc::unbounded_channel(); diff --git a/resources/profiles/orchestrator.lua b/resources/profiles/orchestrator.lua index a4c86eae..f02b20b2 100644 --- a/resources/profiles/orchestrator.lua +++ b/resources/profiles/orchestrator.lua @@ -2,7 +2,7 @@ return yoi.profile.extend("builtin:default", { slug = "orchestrator", description = "Orchestrator role profile with bundled reusable policy", - scope = yoi.scope.workspace_read(), + scope = "workspace_read", worker = { instruction = "$yoi/role/orchestrator", @@ -17,5 +17,5 @@ return yoi.profile.extend("builtin:default", { ticket_orchestration = { enabled = true }, }, - delegation_scope = yoi.scope.workspace_write(), + delegation_scope = "workspace_write", })