hermes - 💡(How to fix) Fix Desktop app: Chinese IME input breaks composer - text not synced, Enter doesn't send

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

In apps/desktop/src/app/chat/composer/index.tsx, the composingRef mechanism for handling IME composition appears to get stuck:

// Line 156
const composingRef = useRef(false) // true during IME composition (CJK input)

// Line 556 - handleEditorInput
if (composingRef.current) {
  return  // Skips draft update!
}

// Lines 1211-1216 - composition event handlers
onCompositionEnd={() => {
  composingRef.current = false
}}
onCompositionStart={() => {
  composingRef.current = true
}}

When composingRef.current is stuck at true:

  • handleEditorInput skips updating draftRef.current and aui.composer().setText()
  • hasComposerPayload remains false (draft is empty)
  • Voice button shows instead of send button
  • Enter doesn't trigger submitDraft() because !hasComposerPayload is true
  • Only pre-composition text (if any) gets sent

Code Example

// Line 156
const composingRef = useRef(false) // true during IME composition (CJK input)

// Line 556 - handleEditorInput
if (composingRef.current) {
  return  // Skips draft update!
}

// Lines 1211-1216 - composition event handlers
onCompositionEnd={() => {
  composingRef.current = false
}}
onCompositionStart={() => {
  composingRef.current = true
}}

---

onCompositionEnd={(e) => {
  composingRef.current = false
  // Force sync the composed text to draft state
  const editor = e.currentTarget
  const nextDraft = composerPlainText(editor)
  if (nextDraft !== draftRef.current) {
    draftRef.current = nextDraft
    aui.composer().setText(nextDraft)
  }
}}
RAW_BUFFERClick to expand / collapse

Bug Description

When using Chinese IME (Input Method Editor) in the Hermes desktop app (Electron), the composer input breaks:

  1. Voice button stays visible even after typing text (send button doesn't appear)
  2. Enter key doesn't submit the message
  3. Text gets truncated - only characters before IME composition are sent

Root Cause

In apps/desktop/src/app/chat/composer/index.tsx, the composingRef mechanism for handling IME composition appears to get stuck:

// Line 156
const composingRef = useRef(false) // true during IME composition (CJK input)

// Line 556 - handleEditorInput
if (composingRef.current) {
  return  // Skips draft update!
}

// Lines 1211-1216 - composition event handlers
onCompositionEnd={() => {
  composingRef.current = false
}}
onCompositionStart={() => {
  composingRef.current = true
}}

When composingRef.current is stuck at true:

  • handleEditorInput skips updating draftRef.current and aui.composer().setText()
  • hasComposerPayload remains false (draft is empty)
  • Voice button shows instead of send button
  • Enter doesn't trigger submitDraft() because !hasComposerPayload is true
  • Only pre-composition text (if any) gets sent

Steps to Reproduce

  1. Open Hermes desktop app on Windows
  2. Switch to Chinese IME (Microsoft Pinyin, Sogou, etc.)
  3. Type Chinese characters using pinyin
  4. Try to press Enter or look for the send button

Expected Behavior

  • After IME composition completes, draft should update with composed text
  • Send button should appear
  • Enter should submit the message

Environment

  • OS: Windows 10/11
  • Hermes Desktop App (Electron)
  • Chinese IME (Microsoft Pinyin, Sogou, etc.)
  • Electron version: 33.x

Suggested Fix

The compositionend event handler should also trigger a draft sync:

onCompositionEnd={(e) => {
  composingRef.current = false
  // Force sync the composed text to draft state
  const editor = e.currentTarget
  const nextDraft = composerPlainText(editor)
  if (nextDraft !== draftRef.current) {
    draftRef.current = nextDraft
    aui.composer().setText(nextDraft)
  }
}}

Or add a fallback in handleEditorInput that checks if composition has actually ended before skipping.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING