fix(find): search both sides of the diff during AI review (#111) - #156
Merged
Conversation
While an AI review is open the editor runs unifiedMergeView: the DOCUMENT holds only the proposed text and the removed original lines are block widgets that are not part of the document. The find controller searched state.doc, so any match inside a removed line simply did not exist to it. Blaze-Leo reported seeing four hits on screen while the bar said two, with next/previous skipping the ones he was looking at. Rebuild the searchable surface the way it is rendered: the document plus, for each chunk, original[fromA..toA], ordered by on-screen position. A chunk's removed lines sort before its new text because @codemirror/merge mounts the deletion as a block widget at fromB. Replace is deliberately asymmetric. Removed text is the version being replaced, it is not in the document, and its offsets index a different string, so splicing at them would corrupt unrelated text. replaceActive no-ops on a removed match and replaceAll only takes document offsets. The bar counts every match while decorations can only mark document text, so those are two different numbering systems; activeDocIndex translates. Passing the bar index straight through would have emphasised the wrong match whenever a removed one appeared earlier in the list. Removed lines get the active highlight through the CSS Custom Highlight API, since decorations cannot reach widget DOM. That path is best-effort and isolated: counting, ordering, navigation and replace safety never call into it, so if it is skipped the count is still right and the user is still scrolled to the correct chunk. It bails rather than mispaint when the query spans lines or the DOM yields fewer occurrences than the model did, and it skips the chunk's Accept/Reject buttons so their labels neither inflate the count nor shift the ordinal. previewFind's text-node walking moved to domTextMatches so reader-mode find and this share one implementation; it had no tests before and now does. Outside review the region list is empty and the search collapses to exactly the previous document-only behaviour. Verified: 46 new tests. reviewFind.test.ts covers the offset and ordering arithmetic, reviewFind.integration.test.ts asserts the fromA/toA/fromB contract against the real @codemirror/merge so a library change cannot silently break it while unit tests still pass, and reviewFindHighlight.test.ts drives the highlighter against a stand-in chunk. Build clean, 359 tests pass.
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.
Fixes the Ctrl+F-during-review bug from #111, reported by @Blaze-Leo.
The bug
While an AI review is open the editor runs
unifiedMergeView. The document holds only the proposed text; the removed original lines are block widgets that are not part of the document at all. The find controller searchedstate.doc, so a match inside a removed line did not exist as far as find was concerned.That is exactly it: four on screen, two counted, and next/previous silently skipping the ones you are looking at.
On the second half of the report ("positions of rendering ... not visually correct"): I could not reproduce mispositioning as a separate defect. Decorations are placed by document offset and CodeMirror accounts for widget heights when it lays out, so the surviving highlights land correctly. The wrong-looking positions follow from the missing matches, and go away with them.
The fix
Rebuild the searchable surface the way it is actually rendered: the document, plus
original[fromA..toA]for each chunk, ordered by on-screen position. A chunk''s removed lines sort before its new text because@codemirror/mergemounts the deletion as a block widget atfromB.Outside review the region list is empty and the search collapses to exactly the previous document-only behaviour, so the normal path is unchanged.
Replace is deliberately asymmetric
Removed text is the version being replaced. It is not in the document, and its offsets index a different string, so splicing the document at them would corrupt unrelated text.
replaceActiveno-ops on a removed match andreplaceAllonly ever takes document offsets. There are tests specifically for this.Two numbering systems
The bar counts every match; decorations can only mark document text. Those indices are not the same, so
activeDocIndextranslates between them. Passing the bar index straight through would have emphasised the wrong match whenever a removed one appeared earlier in the list. Also tested.The highlight is best-effort on purpose
Removed lines get the active highlight via the CSS Custom Highlight API, because decorations cannot reach widget DOM. That path is deliberately isolated: counting, ordering, navigation and replace safety never call into it. If it is skipped you still get the right count and still land on the right chunk.
It bails rather than mispaint when:
It also skips the chunk''s Accept/Reject buttons, so those labels neither inflate the count nor shift the ordinal. Searching "Accept" during a review finds nothing in the chrome, as it should.
Refactor included
previewFind''s text-node walking moved intodomTextMatchesso reader-mode find and this share one implementation. It had no tests before; it does now.Verification
bun run build(tsc + vite) cleanbun run test: 359 tests, 36 files, up from 313. 46 new tests.reviewFind.test.ts(20) — offset and ordering arithmetic, including the interleaving of multiple chunks and the seam case where a regex must not match across removed/new textreviewFind.integration.test.ts(8) — builds a realunifiedMergeViewstate and asserts thefromA/toA/fromBcontract, so a future@codemirror/mergechange cannot silently break this while the unit tests still pass. Includes finding text that exists only in removed lines.reviewFindHighlight.test.ts(10) — drives the highlighter against a stand-in chunk, including the button-skip and refuse-to-mispaint casesdomTextMatches.test.ts(8) — new coverage for the extracted walkerWhat I could not verify
I have no Rust toolchain or real webview here, so the Custom Highlight paint on the removed side is unverified on a real build — jsdom has no Custom Highlight API, so the tests exercise it through a stand-in. The logic it guards is fully tested and the failure mode is benign (scroll without paint). A Test Build is queued so this can be confirmed on a real window.
No version bump and no release in this PR.