fix(desktop): commit IME-finalised text on compositionend so Enter submits - #39435
Closed
maxmilian wants to merge 1 commit into
Closed
fix(desktop): commit IME-finalised text on compositionend so Enter submits#39435maxmilian wants to merge 1 commit into
maxmilian wants to merge 1 commit into
Conversation
maxmilian
force-pushed
the
fix/desktop-ime-compositionend-submit
branch
from
June 5, 2026 01:13
d5294ac to
c607e20
Compare
…bmits On Windows/Electron, the chat composer's contentEditable skips state writes while `composingRef` is true and relies on a trailing `input` event after `compositionend` to push the finalised text into `draftRef` + the assistant-ui composer state. That input event isn't always delivered, so the visible editor holds Chinese/Japanese/Korean text while the tracked draft stays empty — and Enter calls `submitDraft()`, sees a stale/empty draft, and no-ops (NousResearch#39025, dup NousResearch#39112 / NousResearch#39107). Add a `syncComposerDraft(editor, prevDraft, setText)` helper that commits the live DOM text back into app state, and: - call it from `compositionend` (not just the flaky input event) so the draft is correct the moment composition ends; `handleEditorInput` reuses it; - call it at the top of `submitDraft()` and read the synchronously-updated `draftRef` (via the helper's return) for the submit/queue decision, so a finalised message sent in the same tick as `compositionend` isn't lost to React's async state update; `queueCurrentDraft()` likewise reads `draftRef`. The existing `composingRef`/`isComposing` keydown guard is unchanged, so Enter during active composition still confirms the IME candidate instead of submitting. Tests: unit tests for `syncComposerDraft` (commits stale draft, no-ops when in sync, null-editor safe) and a DOM repro that submits IME-finalised text on Enter when the post-composition input event is dropped — asserting against the same synchronous read path submitDraft uses — and confirms Enter during composition does not submit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
maxmilian
force-pushed
the
fix/desktop-ime-compositionend-submit
branch
from
June 5, 2026 02:07
c607e20 to
818a1a4
Compare
maxmilian
marked this pull request as ready for review
June 5, 2026 02:28
6 tasks
Contributor
Author
|
Closing — superseded by the |
This was referenced Jul 28, 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.
Summary
Fixes #39025 (dup #39112 / #39107). On the Windows desktop app, after typing with a Chinese/Japanese/Korean IME, pressing Enter can fail to send: the text is visible in the composer but Hermes behaves as if there is no draft.
Root cause
apps/desktop/src/app/chat/composer/index.tsxskips state writes while IME composition is active:and relies on a clean
inputevent aftercompositionendto push the finalised text intodraftRef+aui.composer().setText(...). On Windows/Electron that trailing input event isn't always delivered, leaving:draft/hasComposerPayload): empty/staleEnter then runs
submitDraft(), which checks the stale draft and no-ops. (Thanks to @sunwz1115 for the precise diagnosis and a tested fix shape in the issue.)Fix
Add
syncComposerDraft(editor, prevDraft, setText)(inrich-editor.ts) that commits the live contentEditable text back into app state, and call it fromcompositionend— not just the flakyinputevent — so the draft is correct the moment composition ends.handleEditorInputreuses the same helper.Because
compositionendis a reliably-delivered event that fires before the user's submit-Enter (a separate event, with a React flush in between), syncing there makes the draft fresh for every downstream path (submitDraft,queueCurrentDraft,hasComposerPayload) without touching any of them — minimal blast radius.The existing
composingRef/nativeEvent.isComposingkeydown guard is unchanged, so Enter during active composition still confirms the IME candidate instead of submitting.Tests
apps/desktopvitest:rich-editor.test.ts:syncComposerDraftcommits a stale draft, no-ops when already in sync, and is null-editor safe.ime-submit-repro.test.tsx: DOM repro — IME-finalised text submits on Enter even when the post-compositioninputevent is dropped; Enter during active composition does not submit. (Mirror-harness style, same as the existingslash-nav-dom-repro.test.tsx, exercising the realsyncComposerDraft.)tsc -b+eslintclean; all 4 composer test files pass (15 tests).(Note: the desktop vitest suite has ~52 pre-existing failures in this local environment from a jsdom/Node
localStoragequirk, unrelated to this change.)Scope
submitDraft/queueCurrentDraft— syncing oncompositionendmakes their existing reads correct.ui-tui/.../textInput.tsx, no DOM composition events) and is out of scope here.Follow-up
compositionendis ever observed (not reported), a belt-and-suspenders sync at the top ofsubmitDraftreading live DOM text would cover it.