Skip to content

fix(desktop): reliable composer message queue#40221

Merged
OutThisLife merged 6 commits into
mainfrom
bb/desktop-queue-composer-ux
Jun 6, 2026
Merged

fix(desktop): reliable composer message queue#40221
OutThisLife merged 6 commits into
mainfrom
bb/desktop-queue-composer-ux

Conversation

@OutThisLife

@OutThisLife OutThisLife commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

What

Make the desktop composer message queue actually reliable, and stop it from shoving the chat around.

The queue felt "dumb" — double-enter clunked, single non-queue messages could get interrupted, and multiple queued messages often produced no reply. Root-caused to three real bugs, plus a layout issue where the queue panel resized the whole chat.

Behavior fixes

  1. Drained-after-interrupt sends went silent. cancelRun sets interrupted: true and nothing reset it; the optimistic seed preserved it, and the message stream drops every delta while interrupted. So "Send now" while busy (and any interrupt → drain) submitted the next turn into a muted session — the agent appeared to never respond. Fix: a fresh submit is a new turn, so seed interrupted: false.

  2. Back-to-back drains stalled. The drain fires on the busy → false settle edge, but busyRef (synced from the busy store by a separate effect) can still read true on that same edge, so the drained send hit the busy guard, returned false, and the entry was never removed. Fix: fromQueue sends bypass the busyRef guard (the drain lock serializes them); the user path keeps the guard.

  3. Double-enter interrupted single turns. A hidden 450ms timer meant a natural double-tap after sending stopped the agent. Fix: empty Enter while busy is a no-op; interrupting is explicit — Stop button or Esc. "Send now" while busy promotes to head + interrupts + auto-drains.

Also: clean stop (no [interrupted] marker), and the interrupted completion path now settles busy.

Layout

The queue list rendered in-flow inside the composer root, so its height fed --composer-measured-height (which drives the thread's bottom padding) — queuing a message visibly resized the chat. Now the panel:

  • floats out of flow as an overlay above the composer (no measured-height contribution → chat layout frozen)
  • shares a border flush with the composer top edge (no gap)
  • is collapsed by default (compact "N queued" pill, expandable)
  • capped at 40vh with internal scroll

Tests

  • New regression tests in use-prompt-actions.test.tsx: interrupted-reset, fromQueue busy-bypass, guarded user path. Stash-toggle teeth-checked (fix-asserting tests go red with the fix removed).
  • Unblocked the prompt-actions suite by completing its stale @/hermes mock.
  • tsc -b clean; queue + prompt-action suites green (20/20).

We can't use assistant-ui's native queue primitive — we're on ExternalStoreRuntime — so the nanostore queue is owned here; the bugs were all control-flow.

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: bb/desktop-queue-composer-ux vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9870 on HEAD, 9870 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5119 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

The queue felt 'dumb' because of three real bugs:

1. Drained-after-interrupt sends went silent. cancelRun sets
   interrupted:true and nothing reset it; submitPromptText's optimistic
   seed preserved it, and the message stream drops every delta while
   interrupted. So Send-now-while-busy and any interrupt+drain submitted
   the next turn into a muted session. Fix: a fresh submit is a new turn —
   seed interrupted:false.

2. Back-to-back queue drains stalled. The drain fires on the busy->false
   settle edge, but busyRef (synced from the busy store by a separate
   effect) can still read true on that same edge, so the drained send hit
   the busy guard, returned false, and the entry was never removed. Fix:
   fromQueue sends bypass the busyRef guard (the queue drain lock
   serializes them); the user path keeps the guard.

3. Double-enter-to-interrupt killed single non-queue turns. The hidden
   450ms timer meant a natural double-tap after sending stopped the agent.
   Fix: empty Enter while busy is a no-op; interrupting is explicit —
   Stop button or Esc.

Also: clean stop (no [interrupted] marker), Send-now works while busy
(promote + interrupt + auto-drain), settle on the interrupted completion
path. Adds regression tests and unblocks the prompt-actions suite by
completing its stale @/hermes mock.
… resize

The queue list rendered in-flow inside the composer root, so its height
fed --composer-measured-height (the composer rect drives the thread's
bottom padding + last-message clearance). Queuing a message grew that
rect and the whole chat visibly resized.

Anchor the panel out of flow above the composer (absolute bottom-full,
capped at 40vh with internal scroll). It no longer contributes to the
measured height, so the thread layout stays put and the list overlays the
(already faded) chat. Still collapsible via the panel's own
disclosure header.
…omposer

- Default the queue disclosure to collapsed (compact 'N queued' pill)
  instead of expanded.
- Drop the gap and merge the panel into the composer: square bottom
  corners, no bottom border/radius, and overlap down by the Root's pt-2
  (-mb-2) so the panel's borderless bottom lands on the composer surface's
  top border — one continuous bordered shape.
@OutThisLife OutThisLife force-pushed the bb/desktop-queue-composer-ux branch from f7c8a7b to 3d2b253 Compare June 6, 2026 01:15
@OutThisLife OutThisLife changed the title fix(desktop): reliable Cursor-style composer message queue fix(desktop): reliable composer message queue Jun 6, 2026
@OutThisLife OutThisLife merged commit 0cbcc75 into main Jun 6, 2026
20 checks passed
@OutThisLife OutThisLife deleted the bb/desktop-queue-composer-ux branch June 6, 2026 01:21
changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
* fix(desktop): make composer message queue reliable

The queue felt 'dumb' because of three real bugs:

1. Drained-after-interrupt sends went silent. cancelRun sets
   interrupted:true and nothing reset it; submitPromptText's optimistic
   seed preserved it, and the message stream drops every delta while
   interrupted. So Send-now-while-busy and any interrupt+drain submitted
   the next turn into a muted session. Fix: a fresh submit is a new turn —
   seed interrupted:false.

2. Back-to-back queue drains stalled. The drain fires on the busy->false
   settle edge, but busyRef (synced from the busy store by a separate
   effect) can still read true on that same edge, so the drained send hit
   the busy guard, returned false, and the entry was never removed. Fix:
   fromQueue sends bypass the busyRef guard (the queue drain lock
   serializes them); the user path keeps the guard.

3. Double-enter-to-interrupt killed single non-queue turns. The hidden
   450ms timer meant a natural double-tap after sending stopped the agent.
   Fix: empty Enter while busy is a no-op; interrupting is explicit —
   Stop button or Esc.

Also: clean stop (no [interrupted] marker), Send-now works while busy
(promote + interrupt + auto-drain), settle on the interrupted completion
path. Adds regression tests and unblocks the prompt-actions suite by
completing its stale @/hermes mock.

* fix(desktop): float the queue panel as an overlay so the chat doesn't resize

The queue list rendered in-flow inside the composer root, so its height
fed --composer-measured-height (the composer rect drives the thread's
bottom padding + last-message clearance). Queuing a message grew that
rect and the whole chat visibly resized.

Anchor the panel out of flow above the composer (absolute bottom-full,
capped at 40vh with internal scroll). It no longer contributes to the
measured height, so the thread layout stays put and the list overlays the
(already faded) chat. Still collapsible via the panel's own
disclosure header.

* fix(desktop): queue panel collapsed by default + shared border with composer

- Default the queue disclosure to collapsed (compact 'N queued' pill)
  instead of expanded.
- Drop the gap and merge the panel into the composer: square bottom
  corners, no bottom border/radius, and overlap down by the Root's pt-2
  (-mb-2) so the panel's borderless bottom lands on the composer surface's
  top border — one continuous bordered shape.

* style(desktop): tighten queue panel padding

* style(desktop): trim queue-ux comments to house style

* style(desktop): drop 'Cursor' references from comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant