Skip to content

fix(composer): keep the caret after the first typed character - #1316

Merged
baxen merged 1 commit into
mainfrom
mbot/composer-focus-caret
Oct 18, 2026
Merged

baxen merged 1 commit into
mainfrom
mbot/composer-focus-caret

Conversation

@baxen

@baxen baxen commented Oct 18, 2026

Copy link
Copy Markdown
Collaborator

Typing "any" sometimes came out as "nya": after the first character, the caret jumped back in front of it.

Cause

When the browser inserts a typed character, ProseMirror reads the change in a microtask. If a focus event reaches the composer inside that window, onFocus restores ProseMirror's own caret, which still predates the character:

  • On Mac WebKit it clears the native selection.
  • It calls editor.focus().
  • In prosemirror-view, view.focus() defers the pending DOM records by 20ms (domObserver.stop()), then writes the stale caret with selectionToDOM.

The deferred read then adopts that stale caret, and the next keys land in front of the character. Natively, this happens when typing is what refocuses the composer (for example, right after returning to the window).

#909 already guards the restore with nativeEditing, which is set in beforeinput. But the flag cleared in a microtask, which runs before the browser has even inserted the character. It now clears when the task ends, so the guard covers the whole window.

An earlier attempt deferred the restore itself to a microtask instead. That reversed the race for a focus followed by an insertion in the same task, and composer-links.spec caught it.

Tests

  • New composer-focus.spec case: type hello , have the host focus the editor during the next keystroke's input event, then type any. It expects hello any. On main it gets hello nya in both Chromium and WebKit.
  • Full Vitest run is green (10103 passed). composer-focus and composer-links pass on both engines. The full browser suite is running and I'll post its results here.
  • An independent agent review found no blockers. It verified the ordering against prosemirror-view 1.42.3.

Human test

In the native app, focus a composer and type a short word right after switching back to the Buzz window (Cmd+Tab back, then type straight away). The word should come out in order.

🤖 Generated with Claude Code

Typing "any" could come out as "nya". After the browser inserts a typed
character, ProseMirror reads it in a microtask. A focus event in that
window ran the composer's focus restore, which wrote ProseMirror's caret
from before the character back into the DOM; the read then adopted it,
and the next keys landed in front of the character.

The restore already skips focus that arrives during native editing, but
the flag cleared in a microtask, before ProseMirror's read. It now clears
when the task ends.

Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
@baxen
baxen marked this pull request as ready for review October 18, 2026 17:36
@baxen
baxen requested a review from a team as a code owner October 18, 2026 17:36
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T17:39:02.046133Z c9c8e69 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account, integrating independent Rocket (lifecycle/performance), Mantis (correctness/concurrency/security), and Groot (product/accessibility/focus) reviews.

No actionable findings. Source assessment: 9/10 for minimalness, elegance, and correctness.

Reviewed head c9c8e695916a3b28c97985356bdc94bce966a30a, target base 4c5f9e36cd89ea37ad6e854c1e7818145d2eca16, merge-base 7d8bafc83d523edd95b06e7bfa52697829ee0e94. The feature diff contains two files; dependency differences between the endpoint trees are base-branch advancement, not changes introduced by this PR.

  • EditableInput.tsx:1424–1434,1795–1811: keeping the guard until a later task covers the native insertion/read window without moving caret ownership or suppressing the host focus callback. The one-shot callback only clears a ref and does not touch a destroyed editor.
  • composer-focus.spec.mjs:40–69: the added browser case injects focus during input capture, then checks text order and focus. Native editing/event ordering justifies this browser-level test. Existing ordinary-return and explicit range-restoration paths remain intact in source, including restored-draft/error recovery callers.
  • Public description and commit message reviewed; no attached images or actionable disclosure finding.

Validation: source-only; no local tests, app launches, or PR code executed. Hosted checks associated with this head show CI required and all executed checks passing; Windows native validation and Vitest flake check were skipped. Read-only hosted flake logs show the new case passing 10 repetitions in Chromium and 10 in WebKit. These results do not prove native window refocus behavior. The author’s reported base-failure reproduction was not independently rerun; native Cmd+Tab/IME, rendered accessibility/themes/viewports, timer-throttling behavior, and human acceptance remain unverified. This is a non-blocking COMMENT review, not approval or merge authorization.

@baxen
baxen merged commit 1e0cbe4 into main Oct 18, 2026
35 checks passed
@baxen
baxen deleted the mbot/composer-focus-caret branch October 18, 2026 17:49
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