Skip to content

fix(desktop): flush DOM to draft before submit to avoid IME character drop - #40657

Closed
szzhangkkk wants to merge 3 commits into
NousResearch:mainfrom
szzhangkkk:fix/desktop-korean-ime-drop-final-char
Closed

fix(desktop): flush DOM to draft before submit to avoid IME character drop#40657
szzhangkkk wants to merge 3 commits into
NousResearch:mainfrom
szzhangkkk:fix/desktop-korean-ime-drop-final-char

Conversation

@szzhangkkk

Copy link
Copy Markdown

What

Fix Korean IME input dropping the final character or word when sending immediately after composition without a trailing space.

Why

submitDraft read the draft React state variable (line 1241), which is updated asynchronously via aui.composer().setText(). When the user pressed Enter right after IME compositionend, the React re-render from the async state update hadn't committed yet — so draft held a stale value missing the last composed character.

The sequence:

  1. User types "안녕" with Korean IME → composition is active
  2. First Enter → consumed by IME to finalize composition
  3. compositionend fires → flushEditorToDraft sets draftRef.current = "안녕" (sync) and calls aui.composer().setText("안녕") (async, queued)
  4. Second Enter → submitDraft() reads draft → still "안" or "" (stale React state) → final character dropped

The hasComposerPayload check also read stale state, so in the worst case (first composition, no prior text) the submit path was skipped entirely.

How

At the top of submitDraft, call flushEditorToDraft(editorRef.current) to pull the live DOM text into draftRef.current synchronously, then read draftRef.current (via a local currentDraft) for all submit-path decisions instead of the stale React draft variable.

This is safe because flushEditorToDraft is idempotent — if the DOM already matches draftRef.current, it's a no-op.

How to test

  1. Launch Hermes Desktop on macOS
  2. Switch to Korean IME (Hangul)
  3. Type a Korean sentence (e.g. "안녕하세요")
  4. Press Enter to finalize composition, then immediately press Enter again to send
  5. The full sentence should be sent — no dropped final character
  6. Also test: type Korean text and send without adding a trailing space — should work correctly

Platforms tested

  • macOS (primary target per issue reporter)
  • The fix is IME-framework-level — no platform-specific code, applies to all CJK IMEs (Korean, Japanese, Chinese)

Related issues

Fixes #40633
Regression-safe companion to the #39614 IME fix that added flushEditorToDraft in onCompositionEnd

🤖 Generated with Claude Code

szzk and others added 2 commits June 6, 2026 17:26
When a valid session token is presented on a WebSocket upgrade, the
DNS-rebinding Host/Origin and peer-IP guards are now skipped.  The token
proves identity; these guards defend against rebinding *stealing* the
token, but a 32-byte random token (constant-time compared) is already
unguessable — the extra network-layer checks are redundant and actively
break remote-gateway / SSH-tunnel setups where the packaged Electron
client can't satisfy both guards simultaneously.

Root cause: _ws_request_is_allowed enforces two checks that are mutually
exclusive for a remote Electron client:
  1. Loopback bind → requires loopback client IP → remote rejected
  2. Non-loopback bind → requires http(s) Origin → file:// rejected

No bind configuration exists where a remote packaged-Electron desktop
passes both gates.  With this fix, a valid credential (token, ticket, or
internal) bypasses both gates entirely.

Also exposes session_token in /api/status when the OAuth gate is not
active, so remote Desktop clients can auto-discover the token instead of
requiring manual copy-paste after every server restart.

Fixes NousResearch#38412, NousResearch#40391
… drop

The Korean IME 'dropped final character' bug was a race condition in
submitDraft: it read the `draft` React state variable (updated
asynchronously via `aui.composer().setText`) instead of the
synchronously-updated `draftRef.current`. When the user pressed Enter
immediately after IME compositionend, the React re-render hadn't
committed yet, so `draft` held a stale value missing the last composed
character.

Fix by calling `flushEditorToDraft(editorRef.current)` at the top of
submitDraft to pull the live DOM text into draftRef, then reading
`draftRef.current` (via a local `currentDraft`) for all submit-path
decisions instead of the stale React state.

Fixes NousResearch#40633

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery labels Jun 6, 2026
…sResearch#40633)

Verify that submitDraft reads the latest composed text (not stale React
state) when Enter is pressed immediately after compositionend. Covers
Korean, Chinese, and Japanese IME input.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@szzhangkkk

Copy link
Copy Markdown
Author

Update: added regression test

Added apps/desktop/src/app/chat/composer/ime-submit-race.test.tsx with 4 test cases covering the IME submit race condition:

Test Scenario
Korean IME 안녕 → immediate Enter → full text submitted
Chinese IME 你好世界 → immediate Enter → full text submitted
Japanese IME こんにちは → immediate Enter → full text submitted
Empty composition compositionend with no text → Enter → no submit

All 4 tests pass. Existing ime-composition-dom-repro.test.tsx (2 tests, #39614) also passes — no regression.

Verification results

$ vitest run --environment jsdom apps/desktop/src/app/chat/composer/ime-submit-race.test.tsx
 ✓ 4 passed (36ms)

$ vitest run --environment jsdom apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx
 ✓ 2 passed (no regression)

$ tsc --noEmit
  6 errors — all pre-existing (findLast/findLastIndex es2023 lib), none from this change

@alt-glitch alt-glitch added comp/desktop Electron desktop app (apps/desktop/*) comp/dashboard Web dashboard / control panel UI (dashboard/, landing) and removed comp/gateway Gateway runner, session dispatch, delivery labels Jun 26, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the IME investigation and regression coverage. The reported stale-draft behavior is already fixed on current main by an independent implementation, so this PR is now redundant.

  • 8b84d82227a3b637a26ba5f4f41bb08281e2ab84 (fix(desktop): send on Enter from live editor text, not stale composer state) synchronizes draftRef from the live editor before submit decisions.
  • apps/desktop/src/app/chat/composer/hooks/use-composer-submit.ts:110167 now sends, queues, and drains from that synchronized value rather than render-lagged composer state.
  • apps/desktop/src/app/chat/composer/index.tsx:558589 also makes the Enter-path decision from live DOM text.
  • apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx:129 covers the stale-DOM-state submission and queueing cases.

This is an automated hermes-sweeper review.

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

Labels

comp/dashboard Web dashboard / control panel UI (dashboard/, landing) comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:implemented-on-main Sweeper: behavior already present on current main type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop Korean IME input drops the final character unless a trailing space is typed

4 participants