Skip to content

fix(desktop): keep the draft space when typing right after a mention pick - #6875

Open
matt2e wants to merge 2 commits into
mainfrom
fix-flakey-test
Open

fix(desktop): keep the draft space when typing right after a mention pick#6875
matt2e wants to merge 2 commits into
mainfrom
fix-flakey-test

Conversation

@matt2e

@matt2e matt2e commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

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, producing hello @bob abcworld instead of hello @bob a world.

With hello world drafted and @bo typed after hello, picking the suggestion leaves hello @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.ts now 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 / mentionTextInputInsertPos become insertionForMentionTextInput / mentionTextInputInsertion, returning {insertAt, text} so the handler can rewrite the payload, not just the position.

Tests

  • Helper-level cases for all four anchors (caret at chip edge, caret past the trailing space, run rewritten from the chip edge, draft space rewritten alone), plus space/NBSP variants and a non-whitespace replacement that must be left alone.
  • A plugin-level test drives the real handleTextInput prop 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).

matt2e and others added 2 commits August 26, 2026 20:58
…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 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: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 --check passed
  • 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
jedwards27 previously approved these changes Aug 26, 2026

@jedwards27 jedwards27 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

: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 --check passed
  • 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
jedwards27 dismissed their stale review August 26, 2026 12:44

Duplicate submission from CLI delivery; canonical approval is review 5030502975.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants