Skip to content

fix(desktop): recover from a stale IME composition flag that silently blocks all sends#44149

Open
AIalliAI wants to merge 1 commit into
NousResearch:mainfrom
AIalliAI:fix/44135-stale-ime-composition-flag
Open

fix(desktop): recover from a stale IME composition flag that silently blocks all sends#44149
AIalliAI wants to merge 1 commit into
NousResearch:mainfrom
AIalliAI:fix/44135-stale-ime-composition-flag

Conversation

@AIalliAI

Copy link
Copy Markdown
Contributor

Summary

Defensive fix for #44135 — Desktop composer Enter (and the Send button) silently doing nothing, with no error and no session.create / prompt.submit ever reaching the gateway.

The composer guards submission behind composingRef (set on compositionstart, cleared on compositionend) so that Enter confirming a CJK IME preedit doesn't split the message (#37483 class). But if compositionend is ever missed — focus jump, input-source switch, or a programmatic DOM mutation aborting the preedit — the flag wedges true and from then on every path out of the composer is silently swallowed:

  • handleEditorKeyDown returns before the Enter branch (composer/index.tsx)
  • the form onSubmit guard blocks the Send button too

No error surfaces, nothing hits the gateway, and the state persists until the composer instance remounts. For Chinese IME users even ASCII typing runs through composition (Pinyin ABC mode), so one wedge kills all sending — matching the reporter's "first new sessions, then everything" progression (pre-decoupling, composer instances differed per thread and degraded one by one as each was touched).

Fix

Two recovery points, each grounded in an invariant Chromium (Electron's only engine) guarantees:

  1. keydown self-heal — every keydown during a genuine composition carries nativeEvent.isComposing === true. If the ref says composing but the native flag says no, the ref is stale: clear it before the guard reads it.
  2. blur reset — a composition cannot survive focus loss (Chromium commits the preedit and fires compositionend on blur). Clear the flag unconditionally on blur; this is what un-wedges the Send button path, which has no native composition flag to consult.

Genuine IME protection is unchanged: Enter with isComposing=true is still swallowed, and the new test locks that in alongside the recovery behavior.

Testing

  • New enter-stale-ime-flag.test.tsx (harness-mirror style, same as enter-submit-dom-race.test.tsx): wedged-flag Enter recovery, genuine-composition swallow + post-compositionend send, Send-button unblock after blur with a missed compositionend.
  • npx vitest run --environment jsdom src/app/chat/composer/ — 7 files, 25 tests pass.
  • tsc -b and eslint clean on touched files.

Note for the reporter's setup: the bootstrap install.sh HTTP 404s in their log are a separate, expected fallback path (locally-built app stamped to an unpushed commit; bootstrap-runner.cjs falls back to the installed checkout's script) and are not related to this bug.

Fixes #44135

🤖 Generated with Claude Code

…he composer

A missed compositionend (focus jump, input-source switch, programmatic DOM
swap mid-preedit) left composingRef stuck true, and the stuck flag silently
swallowed every Enter in handleEditorKeyDown and every Send-button submit via
the form onSubmit guard — no error, no RPC, until the composer remounted. For
CJK IME users (where even ASCII typing runs through composition) this read as
"Enter has no effect; messages cannot be sent", degrading composer instance
by composer instance.

Recover in two places, both grounded in invariants Chromium guarantees:

- keydown: every keydown during a genuine composition carries
  isComposing=true, so when the native flag says we're not composing, clear
  the stale ref before the guard reads it.
- blur: a composition never survives focus loss, so clear the flag
  unconditionally — this is what unblocks the Send button path, which has no
  native composition flag to consult.

The genuine-IME protection (NousResearch#37483 class) is untouched: Enter with
isComposing=true is still swallowed.

Fixes NousResearch#44135

Co-Authored-By: Claude Fable 5 <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 11, 2026
questionjie-max pushed a commit to questionjie-max/hermes-agent that referenced this pull request Jun 11, 2026
…osition

Three remaining fixes for CJK IME in the desktop composer:

1. handleEditorInput now calls flushEditorToDraft() — it was left as a
   no-op after the composingRef guard was removed (NousResearch#39614 fix), meaning
   the Send button never appeared during IME typing because draft state
   was never synced on input events.

2. onCompositionEnd auto-submits when imeEnterRef is set — Enter during
   IME composition is consumed by the IME to commit the preedit, so
   submitDraft() never fires. Detect this and auto-submit for one-Enter
   CJK send instead of requiring a second Enter.

3. onBlur resets composingRef unconditionally — prevents a stale
   composition flag from permanently blocking the Send button's
   form-submit guard (NousResearch#44135).

Closes NousResearch#44135
Complements NousResearch#44149
questionjie-max added a commit to questionjie-max/hermes-agent that referenced this pull request Jun 11, 2026
…osition

Three remaining fixes for CJK IME in the desktop composer:

1. handleEditorInput now calls flushEditorToDraft() — it was left as a
   no-op after the composingRef guard was removed (NousResearch#39614), meaning the
   Send button never appeared during IME typing.

2. onCompositionEnd auto-submits when imeEnterRef is set — Enter during
   IME composition is consumed by the IME to commit the preedit, so
   submitDraft() never fires. Detect this and auto-submit for one-Enter
   CJK send.

3. onBlur resets composingRef unconditionally — prevents a stale
   composition flag from permanently blocking the Send button (NousResearch#44135).

Closes NousResearch#44135
Complements NousResearch#44149
@liuhao1024

Copy link
Copy Markdown
Contributor

✅ Verification — stale IME composition flag recovery is well-designed

Checked:

  • The self-heal logic (composingRef.current && !event.nativeEvent.isComposing) correctly trusts Chromium's per-keydown isComposing flag to recover from a missed compositionend
  • The blur handler unconditionally clears composingRef.current — safe because a composition never survives focus loss in any browser
  • The form onSubmit guard (composingRef.current ? return : submitDraft()) is preserved and still works correctly with the new self-heal
  • Test coverage is thorough: covers genuine composition (Enter swallowed), wedge recovery (Enter sends after stale flag), and blur recovery (Send button works after missed compositionend)

The approach is defensive without being over-eager — it only clears the flag when the native event explicitly says composition is not active. LGTM.

questionjie-max pushed a commit to questionjie-max/hermes-agent that referenced this pull request Jun 11, 2026
…osition

Two interacting problems prevented CJK IME users from sending messages
via Enter in the desktop composer:

1. handleEditorInput skipped draft updates when composingRef was true, so
   the React draft state stayed empty during IME typing. This meant
   hasComposerPayload was always false — the Send button never appeared,
   and the submit path had nothing to send.

2. (Upstream already fixed) submitDraft() now uses draftRef.current

Fixes:
- Remove the composingRef guard from handleEditorInput. Draft now
  updates during IME composition so the Send button appears as soon as
  text is in the editor. The Enter guard in handleEditorKeyDown still
  blocks premature submission via event.nativeEvent.isComposing.
- Add imeEnterRef to track Enter during IME composition. On
  compositionend, if set, the committed text is synced from the DOM
  into state and submitDraft() is scheduled — CJK users get one-Enter
  send instead of needing a second Enter.
- Self-heal stale composition flag: clear composingRef on keydown when
  nativeEvent.isComposing is false, and reset on blur unconditionally
  (upstream fix from NousResearch#44149 applied locally).

Closes NousResearch#44135

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review: PR #44149

Verdict: Approved — well-reasoned defensive fix for stale IME composition flag.

Summary

Files changed: apps/desktop/src/app/chat/composer/index.tsx (+17, -1), new test file (+129)

Adds recovery from a stale composingRef that silently blocks Enter and Send button when compositionend is missed (focus jumps, input-source switches, DOM mutations).

Assessment

Correctness: Two recovery points grounded in Chromium invariants: (1) keydown self-heal clears the flag when native isComposing says we're not composing; (2) blur unconditionally clears the flag. Both are safe — genuine IME composition is still protected.

No issues found.


Reviewed by Hermes Agent

@AIalliAI

Copy link
Copy Markdown
Contributor Author

Requesting maintainer review — this is ready to land from my side. Standalone fork CI is pending first-run approval here; the rollup branch in #44061 carrying this session's batch is fully green on upstream CI (all test shards, typecheck, e2e).

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Desktop app composer Enter key has no effect — cannot send messages in new or existing sessions

4 participants