Repository navigation
fix(search): 返答が 2 件以上でもすべての返答を強調し、原文から写す端の入力 3 件を直す (#3661) - #3667
Merged
Merged
Conversation
## 返答が 2 件以上のとき Chat (ChatTranscript) と History (HistoryPane) は返答ごとに applyHistoryHighlights を呼び、そのたびに CSS.highlights.set が namespace の登録を上書きし、現在の 1 件を 持たない返答の呼び出しが overlay を消していた(Issue の説明どおり、コードで確認)。 選んだ直し方: namespace ごとに範囲を足す(コンテナ → 範囲の Map を namespace ごとに 持ち、登録はその和)。「集めてから 1 回だけ登録」を選ばなかった理由: - 両画面とも 1 返答 1 呼び出しの形のまま直せる。既存テスト(HistoryPane.test / ChatTranscript-parity-2232)が applyHistoryHighlights の呼び出しを spy しており、 バッチ API に替えるとそれらの期待値を変えることになる - 仮想化で後から mount された行の呼び出しも、そのまま和に入る 各呼び出しは自分のコンテナの範囲だけを置き換え、文書から外れたコンテナは落とす。 clearHistoryHighlights(各 effect の cleanup)で全部を忘れる。空のヒットの呼び出しは 自分の分だけ落とす(残りが無ければ従来どおり clear)。 overlay は currentIndex >= 0 の呼び出しだけが動かし(別コンテナにあれば作り直す)、 -1 の呼び出しは自分の中にある overlay だけを消す。ターミナルは 1 コンテナなので従来と同じ。 ## 端の入力 1. 
: alignSubsequence は空白を前後の非空白の文字のあいだにある原文の空白に だけ置き、無ければ -1(置かない)にした。描画どおりでない文字参照(コード外の < など)は 1 文字としてその & に置き、名前の文字(NewLine の e など)を後続の 置き場にしない。コード内の & は書いたとおり描かれるので従来どおり(陰性対照あり)。 2. 思考の 2 節が 1 つのコードブロック: rawSpans は原文に無い行(toRaw が両端とも null) を隙間として飛ばす。片端だけ null なら従来どおり null。 3. 置けなかったヒット: 契約の既定どおり「強調しない。n/m の件数と移動は変えない」。 件数・移動は useHistorySearch 側の matchPositions で決まり、画面の範囲は数えて いないので食い違っても移動は壊れない(現在の 1 件が置けなければ overlay が出ない だけで、行のスクロールは各画面が message 要素で行う)。mermaid の経路 (mapRawPositionsToDom)は以前から置けないものを null にしており、それにそろえた。 ## 整合性レビュー(Codex)P2 の対応: 複数単位を描く文字参照 指摘: `fj` は `fj` の 2 文字を描くが、参照全体を `f` の 1 文字で消費していたため 続く `j` の照合に失敗し、段落全体の写像が null になって後続の通常語まで強調されなかった (`fj sentinel sentinel` で件数 2 のまま強調も overlay も無し)。`𝔄` のような サロゲートペア(1 文字・2 単位)も同じ形で落ちていた。 直し方: referenceAt は参照が描く文字列を返す。数値参照は micromark と同じ規則で復号 (制御文字・サロゲート・非文字・U+10FFFF 超は U+FFFD)、名前は DOMParser の HTML 復号 (`<b>&name;</b>` を parse して textContent。名前は正規表現で `&name;` のみに限る)で 得て、結果をキャッシュする。描いた文字列が本文のその位置にあれば、その UTF-16 の単位 すべてを参照の `&` に対応させる。空白だけを描く参照は従来どおり飛ばす(空白は保留側で 置く)。復号できない名前(Markdown が知らない名前)は参照とみなさず、書いたとおりの 文字列として照合する。DOM が無い環境ではすべての名前を書いたとおりとみなす。 textarea.innerHTML を使う形は tests/unit/docs/design-doc-identifier-audit が `innerHTML` の使用を検出して不合格にしたため、src で既に使っている DOMParser にした。 本文に無い指摘として書いていた「2 文字を描く参照・未知の名前は 1 文字として扱う」は これで解消した。 確かめたこと: 陽性(直す前のコミットの実装で落ちることを確認)— lib: `fj`・`𝔄`・ `😀` の後の通常語が置ける、`fj` 全体のヒットが描いた 2 文字を覆う。 画面(Chat・History): `fj sentinel sentinel` と `𝔄 sentinel sentinel` で 2 件目が強調され、現在の 1 件の overlay が出る。陰性(直す前後とも通る)— 未知の名前 `&unknownname;` は書いたとおり描かれ、そのまま置ける。インラインコード内の `fj` は書いたとおりに置かれ、画面でもコード内の `fj` が強調される。 ## 既存テストの期待値を 1 か所変えた(要確認) tests/unit/components/worktree/chat-search-highlight-3554-interleaved.test.tsx の 「ChatLiveTurnBubble places the answer’s hit on the answer’s word」の `expect(ranges).toHaveLength(2)` と ranges[0]/[1] の 2 行。ライブの吹き出しは折り畳んだ 思考を描かないので、思考側のヒットは置けず、旧実装は生 offset へのフォールバックで チップのラベル(i18n キーの "script.t")を光らせていた — 端の入力 3 そのもの。 契約の決定(3 は強調しない)と「3554 のテストが通ること」を両立させるため、 it/describe 名は変えずに「範囲は 1 件で、それは答えの中」に変えた。差し戻すなら 3 の扱いを変える必要がある。 ## 確かめたこと - 新規: tests/unit/components/worktree/chat-search-highlight-3661.test.tsx(両画面を親から 描画: 返答 2 件の全範囲の文字と返答の要素・overlay の位置、3/4 に進めたときの overlay の移動、返答 6 件、陰性対照の返答 1 件、端 1・3、Chat の端 2)、 tests/unit/lib/terminal-highlight-3661.test.ts(複数コンテナの登録と overlay、端 1〜3、 陰性対照: namespace 分離・ターミナル 1 コンテナ・コード内参照・通常改行・sourceText 無し) - 旧実装(HEAD の 2 ファイル)で新規テストの陽性 11+11 件が落ち、陰性は通ることを確認 - npx vitest run tests/unit/lib/terminal-highlight tests/unit/components/worktree/chat-search tests/unit/components/worktree/chat-mermaid tests/unit/components/HistoryPane.test.tsx tests/unit/components/worktree/ChatTranscript tests/unit/components/worktree/HistoryPane tests/unit/lib/chat tests/unit/hooks/useTerminalSearch tests/unit/guards tests/unit/docs: 全件通過 - npm run lint / npx tsc --noEmit: 通過 ## 対になる場所を探した結果 - applyHistoryHighlights の呼び出し元: ChatTranscript・HistoryPane の 2 か所だけ (どちらも lib 側の修正で直る)。applyTerminalHighlights は useTerminalSearch の 1 コンテナのみで挙動不変。スマホ(WorktreeDetailMobile / MobileTerminalTab)・SessionTile も同じ コンポーネントを描くだけで、`new Highlight` は terminal-highlight.ts の 1 か所だけ - mapRawPositionsToDom(mermaid)の置けないヒット: 以前から null で、フォールバック無し - 型の写し(src/cli/types/): 該当なし - 説明: docs/architecture.md(ja/en)の makeHistoryNamespace の節は namespace 分離の 説明のみで、今回の変更と矛盾しない。applyHistoryHighlights の JSDoc は更新した ## 本文に無い指摘 - tests/unit/components/worktree/chat-search-highlight-3554-interleaved.test.tsx:273 旧期待値が端の入力 3 の誤強調を固定していた(上記のとおり変更) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
返答が 2 件以上のとき
Chat (ChatTranscript) と History (HistoryPane) は返答ごとに applyHistoryHighlights
を呼び、そのたびに CSS.highlights.set が namespace の登録を上書きし、現在の 1 件を
持たない返答の呼び出しが overlay を消していた(Issue の説明どおり、コードで確認)。
選んだ直し方: namespace ごとに範囲を足す(コンテナ → 範囲の Map を namespace ごとに
持ち、登録はその和)。「集めてから 1 回だけ登録」を選ばなかった理由:
ChatTranscript-parity-2232)が applyHistoryHighlights の呼び出しを spy しており、
バッチ API に替えるとそれらの期待値を変えることになる
各呼び出しは自分のコンテナの範囲だけを置き換え、文書から外れたコンテナは落とす。
clearHistoryHighlights(各 effect の cleanup)で全部を忘れる。空のヒットの呼び出しは
自分の分だけ落とす(残りが無ければ従来どおり clear)。
overlay は currentIndex >= 0 の呼び出しだけが動かし(別コンテナにあれば作り直す)、
-1 の呼び出しは自分の中にある overlay だけを消す。ターミナルは 1 コンテナなので従来と同じ。
端の入力
だけ置き、無ければ -1(置かない)にした。描画どおりでない文字参照(コード外の
< など)は 1 文字としてその & に置き、名前の文字(NewLine の e など)を後続の
置き場にしない。コード内の & は書いたとおり描かれるので従来どおり(陰性対照あり)。
を隙間として飛ばす。片端だけ null なら従来どおり null。
件数・移動は useHistorySearch 側の matchPositions で決まり、画面の範囲は数えて
いないので食い違っても移動は壊れない(現在の 1 件が置けなければ overlay が出ない
だけで、行のスクロールは各画面が message 要素で行う)。mermaid の経路
(mapRawPositionsToDom)は以前から置けないものを null にしており、それにそろえた。
整合性レビュー(Codex)P2 の対応: 複数単位を描く文字参照
指摘:
fjはfjの 2 文字を描くが、参照全体をfの 1 文字で消費していたため続く
jの照合に失敗し、段落全体の写像が null になって後続の通常語まで強調されなかった(
fj sentinel sentinelで件数 2 のまま強調も overlay も無し)。𝔄のようなサロゲートペア(1 文字・2 単位)も同じ形で落ちていた。
直し方: referenceAt は参照が描く文字列を返す。数値参照は micromark と同じ規則で復号
(制御文字・サロゲート・非文字・U+10FFFF 超は U+FFFD)、名前は DOMParser の HTML 復号
(
<b>&name;</b>を parse して textContent。名前は正規表現で&name;のみに限る)で得て、結果をキャッシュする。描いた文字列が本文のその位置にあれば、その UTF-16 の単位
すべてを参照の
&に対応させる。空白だけを描く参照は従来どおり飛ばす(空白は保留側で置く)。復号できない名前(Markdown が知らない名前)は参照とみなさず、書いたとおりの
文字列として照合する。DOM が無い環境ではすべての名前を書いたとおりとみなす。
textarea.innerHTML を使う形は tests/unit/docs/design-doc-identifier-audit が
innerHTMLの使用を検出して不合格にしたため、src で既に使っている DOMParser にした。本文に無い指摘として書いていた「2 文字を描く参照・未知の名前は 1 文字として扱う」は
これで解消した。
確かめたこと: 陽性(直す前のコミットの実装で落ちることを確認)— lib:
fj・𝔄・😀の後の通常語が置ける、fj全体のヒットが描いた 2 文字を覆う。画面(Chat・History):
fj sentinel sentinelと𝔄 sentinel sentinelで2 件目が強調され、現在の 1 件の overlay が出る。陰性(直す前後とも通る)— 未知の名前
&unknownname;は書いたとおり描かれ、そのまま置ける。インラインコード内のfjは書いたとおりに置かれ、画面でもコード内の
fjが強調される。既存テストの期待値を 1 か所変えた(要確認)
tests/unit/components/worktree/chat-search-highlight-3554-interleaved.test.tsx の
「ChatLiveTurnBubble places the answer’s hit on the answer’s word」の
expect(ranges).toHaveLength(2)と ranges[0]/[1] の 2 行。ライブの吹き出しは折り畳んだ思考を描かないので、思考側のヒットは置けず、旧実装は生 offset へのフォールバックで
チップのラベル(i18n キーの "script.t")を光らせていた — 端の入力 3 そのもの。
契約の決定(3 は強調しない)と「3554 のテストが通ること」を両立させるため、
it/describe 名は変えずに「範囲は 1 件で、それは答えの中」に変えた。差し戻すなら
3 の扱いを変える必要がある。
確かめたこと
描画: 返答 2 件の全範囲の文字と返答の要素・overlay の位置、3/4 に進めたときの overlay
の移動、返答 6 件、陰性対照の返答 1 件、端 1・3、Chat の端 2)、
tests/unit/lib/terminal-highlight-3661.test.ts(複数コンテナの登録と overlay、端 1〜3、
陰性対照: namespace 分離・ターミナル 1 コンテナ・コード内参照・通常改行・sourceText 無し)
tests/unit/components/worktree/chat-mermaid tests/unit/components/HistoryPane.test.tsx
tests/unit/components/worktree/ChatTranscript tests/unit/components/worktree/HistoryPane
tests/unit/lib/chat tests/unit/hooks/useTerminalSearch tests/unit/guards tests/unit/docs: 全件通過
対になる場所を探した結果
(どちらも lib 側の修正で直る)。applyTerminalHighlights は useTerminalSearch の
1 コンテナのみで挙動不変。スマホ(WorktreeDetailMobile / MobileTerminalTab)・SessionTile も同じ
コンポーネントを描くだけで、
new Highlightは terminal-highlight.ts の 1 か所だけ説明のみで、今回の変更と矛盾しない。applyHistoryHighlights の JSDoc は更新した
本文に無い指摘
旧期待値が端の入力 3 の誤強調を固定していた(上記のとおり変更)
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Refs #3661