Skip to content

fix(desktop): send on Enter from live editor text, not stale composer state#39639

Merged
OutThisLife merged 2 commits into
NousResearch:mainfrom
xxxigm:fix/desktop-composer-enter-dom-race
Jun 10, 2026
Merged

fix(desktop): send on Enter from live editor text, not stale composer state#39639
OutThisLife merged 2 commits into
NousResearch:mainfrom
xxxigm:fix/desktop-composer-enter-dom-race

Conversation

@xxxigm

@xxxigm xxxigm commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #39630
Also addresses #39623 ("type + Enter, no response").

Symptom

In the desktop app, pressing Enter after typing frequently does nothing — the message isn't sent. Adding a trailing space before Enter works around it 100%. IME / Chinese users (e.g. typing 你好) and fast typers are hit disproportionately (~90% of attempts); occasionally the message sends but is missing the last few characters.

Root cause

The chat input is a custom contentEditable div bound to @assistant-ui/react. The submit path read the draft from React/AUI state rather than the DOM:

  • submitDraft() used draft (useAuiState(s => s.composer.text)) and the derived hasComposerPayload.
  • handleEditorKeyDown's Enter branch used hasComposerPayload too.

Both lag the DOM by a render. Typing fires onInputaui.composer().setText(...) (a scheduled state update). If the user presses Enter before React commits that update, draft is still the previous (often empty) value, so:

  • submitDraft() falls through its text.trim() check and drops the message, and
  • with prompts queued, the Enter handler could drain the queue instead of sending.

A trailing space "fixes" it only because the extra onInput event flushes the pending state in time.

Note: draftRef (a useRef) is already synced from the DOM on every input event (handleEditorInput), but the submit path wasn't using it.

Fix (apps/desktop/src/app/chat/composer/index.tsx)

  • submitDraft() now refreshes draftRef from the editor node (composerPlainText(editorRef.current)) and makes all decisions — slash-command detection, queue-vs-cancel while busy, and the send payload — from that live DOM text instead of the stale draft / hasComposerPayload.
  • The Enter handler computes its queue-drain-vs-submit branch from the DOM text as well.

This closes the in-flight-keystroke gap (incl. the IME case, which already waits for compositionend before Enter submits). No behavior change when state and DOM agree.

Test plan

  • tsc -b clean
  • Type a message and press Enter immediately (no trailing space) → sends, every time
  • IME: type 你好, press Enter → sends with full text, no dropped trailing chars
  • While the agent is busy: typing + Enter queues; empty + Enter still stops/cancels
  • With queued prompts present: typing a new message + Enter sends it (doesn't drain the queue); empty + Enter drains the next queued

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 5, 2026
… state

Pressing Enter often did nothing (~90% with IME / fast typing); adding a
trailing space "fixed" it. The composer's submit path read the draft from the
AUI composer state (`useAuiState(s => s.composer.text)`) and the derived
`hasComposerPayload`, both of which lag the contentEditable DOM by a render. On
fast typing or IME composition the final keystroke(s) weren't in state yet, so
`submitDraft()` saw an empty draft and dropped the message. A trailing space
only worked around it by forcing an extra input event that flushed the state.

submitDraft() now refreshes draftRef from the editor node and submits/queues
based on the live DOM text, and the Enter handler decides the queue-drain vs
submit branch from the DOM too. draftRef is already synced on every input
event, so this just closes the in-flight-keystroke gap.

Fixes NousResearch#39630. Also addresses the "typing + Enter does nothing" reports in

NousResearch#39623.
@xxxigm xxxigm force-pushed the fix/desktop-composer-enter-dom-race branch from f000a53 to 22ed461 Compare June 9, 2026 00:16
…#39630)

Pin the contract that the composer's Enter path reads the live DOM editor
text, not the render-lagged composer state: a just-typed message sends even
when state hasn't synced; while busy it queues (never drains the queue or
cancels); an empty Enter while busy is a no-op; and an empty idle Enter
drains the next queued prompt. Faithful DOM-event repro mirroring
handleEditorKeyDown + submitDraft.

@OutThisLife OutThisLife 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.

LGTM — approving. This is the right layer for the "Enter does nothing / drops the message" bug (#39630, and the #39623 "type + Enter, no response" reports).

Root cause is accurate: the submit path read render-lagged AUI state (draft / hasComposerPayload), so a keystroke (or IME composition) that hadn't committed to React state yet was invisible at Enter time — the message dropped, and a trailing space only "worked around" it because the extra input event flushed state. The fix reads the live DOM text in both the keydown branch (hasLivePayload) and at the top of submitDraft (re-syncs draftRef + setText), then routes every branch — queue-drain, busy no-op, queue, send — off the live payload. Consistent and correct.

The regression test reproduces the race deterministically (mutates textContent without firing input, the exact IME/fast-type window) and covers all four branches.

Note for whoever merges: the red nix (macos/ubuntu) checks are a transient cache.nixos.org outage (HTTP2 framing / connection-reset / broken-pipe on narinfo+nar downloads), not this diff — every other check (test shards, e2e, builds, ruff/ty, supply-chain) is green. Re-running the nix jobs now.

@OutThisLife OutThisLife enabled auto-merge (squash) June 10, 2026 00:49
@OutThisLife OutThisLife merged commit 8b84d82 into NousResearch:main Jun 10, 2026
20 of 22 checks passed
wachoo pushed a commit to wachoo/hermes-agent that referenced this pull request Jun 10, 2026
… state (NousResearch#39639)

* fix(desktop): send on Enter from live editor text, not stale composer state

Pressing Enter often did nothing (~90% with IME / fast typing); adding a
trailing space "fixed" it. The composer's submit path read the draft from the
AUI composer state (`useAuiState(s => s.composer.text)`) and the derived
`hasComposerPayload`, both of which lag the contentEditable DOM by a render. On
fast typing or IME composition the final keystroke(s) weren't in state yet, so
`submitDraft()` saw an empty draft and dropped the message. A trailing space
only worked around it by forcing an extra input event that flushed the state.

submitDraft() now refreshes draftRef from the editor node and submits/queues
based on the live DOM text, and the Enter handler decides the queue-drain vs
submit branch from the DOM too. draftRef is already synced on every input
event, so this just closes the in-flight-keystroke gap.

Fixes NousResearch#39630. Also addresses the "typing + Enter does nothing" reports in

NousResearch#39623.

* test(desktop): cover Enter-submit from live editor text (NousResearch#39630)

Pin the contract that the composer's Enter path reads the live DOM editor
text, not the render-lagged composer state: a just-typed message sends even
when state hasn't synced; while busy it queues (never drains the queue or
cancels); an empty Enter while busy is a no-op; and an empty idle Enter
drains the next queued prompt. Faithful DOM-event repro mirroring
handleEditorKeyDown + submitDraft.
changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
… state (NousResearch#39639)

* fix(desktop): send on Enter from live editor text, not stale composer state

Pressing Enter often did nothing (~90% with IME / fast typing); adding a
trailing space "fixed" it. The composer's submit path read the draft from the
AUI composer state (`useAuiState(s => s.composer.text)`) and the derived
`hasComposerPayload`, both of which lag the contentEditable DOM by a render. On
fast typing or IME composition the final keystroke(s) weren't in state yet, so
`submitDraft()` saw an empty draft and dropped the message. A trailing space
only worked around it by forcing an extra input event that flushed the state.

submitDraft() now refreshes draftRef from the editor node and submits/queues
based on the live DOM text, and the Enter handler decides the queue-drain vs
submit branch from the DOM too. draftRef is already synced on every input
event, so this just closes the in-flight-keystroke gap.

Fixes NousResearch#39630. Also addresses the "typing + Enter does nothing" reports in

NousResearch#39623.

* test(desktop): cover Enter-submit from live editor text (NousResearch#39630)

Pin the contract that the composer's Enter path reads the live DOM editor
text, not the render-lagged composer state: a just-typed message sends even
when state hasn't synced; while busy it queues (never drains the queue or
cancels); an empty Enter while busy is a no-op; and an empty idle Enter
drains the next queued prompt. Faithful DOM-event repro mirroring
handleEditorKeyDown + submitDraft.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) 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.

Desktop] Enter key doesn't send message — must add trailing space

3 participants