diff --git a/.gitignore b/.gitignore index ecbd5bb..bb3037c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk .DS_Store +/.superpowers diff --git a/docs/superpowers/specs/2026-10-28-cross-session-search-design.md b/docs/superpowers/specs/2026-10-28-cross-session-search-design.md new file mode 100644 index 0000000..5bc60bc --- /dev/null +++ b/docs/superpowers/specs/2026-10-28-cross-session-search-design.md @@ -0,0 +1,222 @@ +# 全セッション検索 設計仕様 + +作成日:2026-10-28 +状態:設計中(利用者の承認待ち) + +## 1. この文書が定めるもの + +左ペインの左上に検索窓を置き、打った語を**全セッションの出力**から探し、 +当たった文を左ペインに新しい順で並べ、選べばその場所へ飛ぶ。 +この文書はその仕様を定める。 + +## 2. なぜ要るか + +セッションが 14 本並ぶと、「どれに何を頼んだか」が分からなくなる。 + +左ペインの行が出している手がかりは 3 つある。名前(既定は作業ディレクトリ)、 +`OSC 0` で名乗った題名、枝である。同じリポジトリで複数の PR を並行して作ると、 +この 3 つが**全部同じ文字になる**。作業ディレクトリが同じで、枝を切る前はどれも +`main` で、題名も似る。題名は末尾を落として詰めるため、違いが落ちた側にあれば +画面からも消える。 + +状態の点(動いている・返事待ち・暇)は「どれが私待ちか」には答えるが、 +「どれに SES マッチの話を頼んだか」には答えない。 + +答えられる手がかりは一つしかない。**自分が打った文そのもの**である。 +それは termit の中に、すでにある。スクロールバックに載っている。 + +## 3. 非目標 + +- **結果を端末の上に重ねて広く出すこと。** 検討したが採らなかった。 + 窓は左上に置き、結果も左ペインの中で完結させる。 +- **`⌘F`(いまのセッションの中の検索)の置き換え。** 併存させる。 +- **`^R`(コマンド履歴の検索)の置き換え。** 併存させる。 +- **正規表現。** 素の文字列一致だけを持つ。 +- **検索語の記憶、履歴、絞り込み条件。** +- **セッション間通信と管理セッション。** 別の企画として保留する(第 11 節)。 + +## 4. 反目標 + +次のことが起きるなら、この機能は無いほうがよい。 + +- **打鍵に対して端末が遅くなること。** 検索のために 1 フレームでも落とすなら、 + 機能ごと要らない。 +- **結果が多すぎて、どれが新しいか分からなくなること。** + 14 本ぶんの当たりが区別なく並ぶのは、いまの一覧と同じ困り方である。 + +## 5. 利用者が決めたこと + +- 検索窓は左ペインの左上に置く。 +- 当たった文は左ペインの中に一覧で出す。 +- 並びは**新しい順**。 +- **1 件は 1 当たり**とする。セッション単位にまとめない。 + 行の時刻を作る工事(第 8 節)が要ることを承知のうえで、こちらを選んだ。 + +## 6. 画面 + +**窓は見出しの帯に置く。** 帯(`sidebar::HEADER` = 34pt)に検索窓を置き、 +`+` は右端に残す。窓は常に見えている。 + +**打っているあいだ、セッションの一覧は結果の一覧に差し替わる。** +語を空にすると、元の一覧に戻る。 + +1 件は 2 段とする。 + +``` +┌────────────────────────┐ +│ 🔍 SES │ ← 見出しの帯。右端に + +├────────────────────────┤ +│ ● main ⌘4 │ ← 1 段目:どのセッションか(13pt) +│ …この SESマッチ機能… │ ← 2 段目:当たった文の前後(12pt) +│ │ +│ ● main ⌘6 │ +│ …SESマッチの一覧を… │ +└────────────────────────┘ +``` + +1 段目は既存の行と同じ組みにする。状態の点、名前、`⌘` の札。 +2 段目は当たった文で、当たった語に `search_hit` の色を敷く。 + +**2 段目は当たった語を中心にして詰める。** 幅は 20 字ほどしかない。 +既存の `fit_tail`(末尾を落とす)でも `fit_head`(先頭を落とす)でも、 +当たった語そのものが落ちうる。当たりを真ん中に置き、前後を均等に落とす +三つ目の詰め方が要る。 + +**2 段目にも伏せ字を通す。** いまの credential masking は描画の経路 +(`main.rs` の `masked_cells`)にしかない。結果の行はそこを通らないため、 +`secret.rs` の `Redactor::redact` を通してから出す。通さないと、 +端末では `••••` に見えている鍵が、結果の一覧では素のまま出る。 + +## 7. 何を探すか + +- **対象は全セッション**。動いているものも、終わったものも、docker 越しのものも、 + grid は同じなので区別しない。 +- **ただし代替画面にいるセッションは、いま出ている画面しか探せない。** + `vim` や `less` が開いているあいだ、本来のスクロールバックは alacritty の + `inactive_grid` にあり、外から読む口が無い。相手が代替画面を使わず本画面へ + 流していくものであれば、この穴には当たらない。穴として受け入れ、README に書く。 +- **範囲は画面とスクロールバック**(既定 10000 行/セッション)。 +- **素の文字列一致、大文字小文字は無視する。** 正規表現は持たない。 +- 行の名前・題名・枝も対象に含める(私の判断。第 12 節)。 + +## 8. 「新しい順」をどう決めるか + +1 件は 1 当たりである。14 本ぶんの当たりを一列に並べるのだから、 +**行から時刻が引けなければならない。** grid の行は時刻を持たない。 + +**外から取れる数では足りない。** `Grid::history_size()` はスクロールバックの +上限(既定 10000 行)で頭打ちになる。エージェントは数分でそこへ届き、 +届いたあとは 1 時間前の行といま出た行の区別が付かない。 +termit 自身の `CommandTracker` が同じ壁に当たっている +(`src/pty.rs` の「履歴からも押し出された」)。 + +**押し出された行を自前で数える。** 数える場所は pty の経路である。 +`Term` へ渡す前に `Handler` を包み、**本画面にいるあいだの** `linefeed` と +`scroll_up` を数えて、セッションごとに `pushed`(これまでに押し出した行の総数、 +単調増加)を持つ。代替画面にいるあいだは数えない。履歴へ入らないからである。 +包みは全メソッドを素通しするだけの定型なので、macro で畳む。 + +**行の通し番号は `pushed + l` で出る。** alacritty の `Line` は画面の上端を 0、 +履歴側を負で数える。履歴の最も古い行の通し番号は `pushed - history_size` で、 +`Line(l)` の通し番号はそこから数えて `pushed + l` になる(`l` は負でもよい)。 + +**目盛りは `(pushed, Instant)` の環状の記録にする。** pty から 1 回読むごとに +1 つ刻む。当たりの通し番号から二分探索で時刻が出る。数千件で 32KB ほど。 +分解能は 1 秒あれば足りる。溢れた古い側は、残っている最古の時刻として扱う。 + +**この通し番号は `Enter` でも効く。** 結果を取ってから選ぶまでに出力が流れると、 +覚えた `Match` の位置はずれる。通し番号で覚えておけば、押すときの `pushed` から +いまの行位置が `通し番号 - pushed` で出る。`history_size` を超えて負の側へ +出ていれば、その行はもう消えている。 + +**一覧が 1 本で埋まらないようにする。** いちばんよく喋っているセッションの +当たりが新しい順の上位を占めると、残りの 13 本が一覧に出てこない。 +これは第 4 節の反目標そのものである。1 本あたりの採用数に上限を置き +(既定 20 件)、全体の上限(200 件)の中で各本に席を残す。 +同じ文が続けて当たるとき(全画面 UI の描き直しで同じ行が何度も履歴へ入る)は、 +連続するぶんを 1 件にまとめる。 + +## 9. 操作 + +| キー | 動き | +|---|---| +| `⌘⇧F` | 検索窓へ入る | +| `↑` `↓` | 件を移動する | +| `Enter` | そのセッションへ切り替え、その位置まで送り、`⌘F` と同じ強調を点ける | +| `Esc` | 窓を出て、元の一覧へ戻る | +| クリック | 窓に入る/件を選ぶ | + +**`^` の鍵は増やさない。** README は Ctrl を 6 つしか取らないと約束している。 +`⌘` の 1 文字はジャンプの札(`⌘A` `⌘G` `⌘J` …)で埋まっているため、 +修飾を足して `⌘⇧F` とする。`⌘F`(いまのセッションの中)の隣として覚えられる。 + +**`⌘⇧F` は、いまは `⌘F` として通ってしまう。** `src/input.rs` の物理キー側の +分岐は、Cmd のときに Shift を除いていない。先にここを分けないと、新しい鍵が +`FindInScreen` に落ちる。 + +**日本語入力は確定した文字だけを受ける。** 変換中の文字列は検索に渡さない。 +`main.rs` の既存の扱い(`preedit` を `Ime::Commit` まで渡さない)に合わせる。 + +## 10. 速さ + +**この節がこの機能の生死を決める。** + +14 本 × 10000 行を毎打鍵で走ると間に合わない見込みがある。 +ただし、この仕様は見込みで作りを決めない。先に測る。 + +- **測る。** `--bench` に「n 本 × m 行を 1 語で走る」を足し、1 回あたりの時間を出す。 + 数字が出てから、同期で走らせるか別スレッドへ出すかを決める。 +- **打鍵ごとに走らせない。** 打ち終わって一拍おいてから走る。 +- **新しい方から走り、上限(既定 200 件)で止める。** + 新しい順という並びは、この早じまいと噛み合う。古い当たりは、そもそも要らない。 +- **描画を止めない。** 検索中も 1 フレームを落とさないこと。 + `TERMIT_FRAME_LOG=1` の毎フレーム出力で確かめる。 + +**同期では走らせられないことは、すでに測ってある。** `docs/performance.md` の +1 フレームは 16.61ms で、うち 16.27ms が `get_current_texture` の中にある。 +UI スレッドに残っている余白は 0.3ms ほどで、10000 行の走査はそこに入らない。 +走るのは別スレッドになる。 + +**錠は塊ごとに放す。** 各セッションの `Term` は `FairMutex` の下にあり、 +pty の読み手が 64KiB ごとにそれを取る。10000 行を取りっぱなしで走れば、 +その読み手が止まり、エージェントの出力そのものが止まる。 +数百行ずつ走っては放し、取り直す。全文を写してから走る案は採らない。 +14 本 × 10000 行 × 80 桁で 10M 文字を超え、常駐 85MB の看板に合わない。 + +目標:10000 行 × 14 本で、1 回の検索が描画を 1 フレームも落とさず、 +どのセッションの出力も目に見えて止めない。 + +## 11. 隣にある企画(この文書では扱わない) + +セッション間で文字をやり取りし、1 本の「管理セッション」が他に訊いて回り、 +仕事を割り振る案がある。docker 越しでも届くよう、ソケットではなく +`OSC` を 1 本足して pty そのものを通す形まで出ている。 +画面に出た文字が別のセッションへの入力になりうるため、既定では塞ぐことになる。 + +この文書では扱わない。検索が先である。 + +## 12. 私(設計者)が決めたこと + +利用者が言っていないが、仕様として決めたもの。覆してよい。 + +- 鍵は `⌘⇧F`。先に `src/input.rs` の Cmd + Shift を分ける。 +- 素の文字列一致、大文字小文字は無視、正規表現なし。 +- 窓は常に見えていて、`⌘⇧F` は近道にすぎない。 +- 行の名前・題名・枝も探す対象に含める。 +- 画面の中の当たりも含める。いま出ているものこそ知りたい。 +- **結果は一度取ったら、次の打鍵まで固める。** 出力を追いかけて並べ直さない。 +- **`Enter` のときに場所を取り直す。** 走っているセッションでは、当たった行は + 数百ミリ秒で流れる。第 8 節の通し番号から今の行位置を出し直し、 + もう消えていれば、そのセッションへ切り替えて `⌘F` に語を渡すだけにする。 +- **1 本あたりの採用は 20 件まで。連続する同じ文は 1 件にまとめる**(第 8 節)。 +- 走査は別スレッド、錠は数百行ごとに放す(第 10 節)。 +- 結果の 2 段目は `Redactor::redact` を通す(第 6 節)。 +- 代替画面にいるセッションの穴は受け入れ、README に書く(第 7 節)。 + +## 13. 作る人に委ねること + +- 打ち終わってから走り出すまでの間(デバウンス)の長さ。 +- 当たりを中心に残す詰め方の実装。 +- 件数が左ペインに収まらないときの送り方。 +- 名前・題名・枝に当たったときの時刻の扱いと、`Enter` の動き。 +- 結果の一覧に、いまの一覧と同じ並べ替え・ドラッグを効かせるかどうか(効かせない想定)。