fix(desktop): keep the draft space when typing right after a mention pick - #6875
fix(desktop): keep the draft space when typing right after a mention pick#6875matt2e wants to merge 2 commits into
Conversation
…pick
Desktop Smoke E2E (2) failed on mentions.spec.ts ("typing a mention
before existing text does not interleave spaces"): picking a suggestion
mid-draft and typing immediately could swallow the draft's space
("hello @bob abcworld"). Chromium sometimes rewrites the whole
whitespace run around the caret as replace(" " -> "\u00A0a"), and the
settling-time text-input redirect only recognized a caret or a selected
trailing space, so the rewrite fell through to the destructive default
replacement.
Teach the redirect to recognize a space-run rewrite (space or NBSP) and
insert only the typed remainder after the mention's trailing space. The
race pre-dates this branch (reproduces on main at ~5-25% per local run);
300+ consecutive passes after the fix.
(cherry picked from commit 05302ca)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Toohey <contact@matttoohey.com>
Review 5028139009 on PR #6850 reproduced "hello @bob abcworld" at the head that was meant to fix it. It was right, and the gap is deterministic rather than a stale-build artifact: the redirect only recognized a rewrite anchored at the chip edge. Chromium also anchors it one position later, replacing just the draft's own space, and selectionAfterMentionTrailingSpace returns `from` unchanged there (its lookbehind sees "hello @bob " ending in a space), so the `next !== from + 1` guard bailed and the destructive default ate the separator, giving "hello @bob aworld" and then "hello @bob abcworld". Replace the shape matching with the invariant it was approximating. While autocomplete is settling the user cannot have selected anything — a selection cancels settlement — so a whitespace-only replacement next to a mention's trailing space is always the browser normalizing whitespace, never an intentional delete. Keep the document's spaces and insert only the typed characters after the token, whichever edge the rewrite is anchored at. The evidence is now deterministic instead of racy. The new unit test drives the plugin's real handleTextInput prop through all four anchors and falls back to ProseMirror's default when it declines, exactly as the browser does with the return value; against the previous production code it fails with the reviewed value ("hello @bob aworld"). Browser runs: mentions.spec.ts 73/73, messaging.spec.ts 91/91, and the reviewer's parallel --repeat-each repro 85/85 — against a rebuilt bundle after evicting a stale port-4173 server that was serving another worktree's dist, the known confound in this harness. This branch also carries out the review's primary ask: the composer fix now lands on its own instead of riding the sidebar-geometry PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Matt Toohey <contact@matttoohey.com>
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
No unresolved author-actionable defect found at exact head 272257967f67f6cf80653fa23e35ff0e40c07acc (base db514b153a8cb17631d6e80b91c95fdc45deb147).
The change is narrowly scoped to the per-editor post-autocomplete settlement path. It preserves the existing draft separator across Chromium/ProseMirror whitespace-rewrite shapes, inserts only the stripped incoming payload, and cancels on explicit selection/navigation so main/thread editors cannot share stale settlement state. Both assigned lanes independently cleared the change.
Validation on the exact head:
- Desktop package: 5,558/5,558 passed
- Focused plugin suite: 43/43 passed; removing the new look-behind made the intended regressions fail
- Rebuilt exact mention journey: 10/10 passed in the systems lane
- Product lane: 50/50 focused E2E repetitions across routing, ArrowLeft, pointer chip-edge, mid-draft rapid typing, and Enter selection; additional spacing and punctuation probes passed 5/5 each
- Desktop check, typecheck, build, and
git diff --checkpassed - Required GitHub Desktop checks are green: Core, macOS build, all Smoke shards, and both Integration shards
Author action: none.
Verification owner: reviewer/tooling for optional packaged Tauri/WKWebView IME, accessibility, and real thread-panel observation. Those are confidence gaps, not evidence of a defect. CI owns continued gate health.
Residual risk is limited to native IME/AT and thread-panel observation not exercised in this review; the extension’s per-editor isolation is covered in unit tests.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
No unresolved author-actionable defect found at exact head 272257967f67f6cf80653fa23e35ff0e40c07acc (base db514b153a8cb17631d6e80b91c95fdc45deb147).
The change is narrowly scoped to the per-editor post-autocomplete settlement path. It preserves the existing draft separator across Chromium/ProseMirror whitespace-rewrite shapes, inserts only the stripped incoming payload, and cancels on explicit selection/navigation so main/thread editors cannot share stale settlement state. Both assigned lanes independently cleared the change.
Validation on the exact head:
- Desktop package: 5,558/5,558 passed
- Focused plugin suite: 43/43 passed; removing the new look-behind made the intended regressions fail
- Rebuilt exact mention journey: 10/10 passed in the systems lane
- Product lane: 50/50 focused E2E repetitions across routing, ArrowLeft, pointer chip-edge, mid-draft rapid typing, and Enter selection; additional spacing and punctuation probes passed 5/5 each
- Desktop check, typecheck, build, and
git diff --checkpassed - Required GitHub Desktop checks are green: Core, macOS build, all Smoke shards, and both Integration shards
Author action: none.
Verification owner: reviewer/tooling for optional packaged Tauri/WKWebView IME, accessibility, and real thread-panel observation. Those are confidence gaps, not evidence of a defect. CI owns continued gate health.
Residual risk is limited to native IME/AT and thread-panel observation not exercised in this review; the extension’s per-editor isolation is covered in unit tests.
Duplicate submission from CLI delivery; canonical approval is review 5030502975.
Problem
Desktop Smoke E2E flaked on
mentions.spec.ts("typing a mention before existing text does not interleave spaces"): picking a suggestion mid-draft and typing immediately could swallow the draft's own space, producinghello @bob abcworldinstead ofhello @bob a world.With
hello worlddrafted and@botyped afterhello, picking the suggestion leaveshello @bob world— the mention's trailing space followed by the draft's space. Chromium reconciles that whitespace run by re-emitting it as a replacement (replace(" " -> " a")), anchored at either edge of the run. The settling-time text-input redirect only recognized a caret or a rewrite anchored at the chip edge, so the other shapes fell through to ProseMirror's destructive default and ate the separator.The race pre-dates this branch and reproduces on
main.Fix
mentionHighlightExtension.tsnow redirects on the invariant instead of on a specific rewrite shape. While autocomplete is settling the user cannot have a selection (a selection cancels settlement), so a whitespace-only replacement adjacent to a mention's trailing space is always the browser normalizing whitespace — never an intentional delete. The handler keeps every space already in the document and inserts only the typed characters after the token, whichever edge the rewrite is anchored at. Non-whitespace replacements still fall through as real edits.insertPosForMentionTextInput/mentionTextInputInsertPosbecomeinsertionForMentionTextInput/mentionTextInputInsertion, returning{insertAt, text}so the handler can rewrite the payload, not just the position.Tests
handleTextInputprop and falls back to ProseMirror's default when it declines — exactly as the browser does with the return value — so it fails against the previous production code rather than depending on winning a timing race in a headless browser.Desktop unit suite passes on pre-push (5558/5558).