The composer never locks
The chat box used to go dead for the whole turn, which is exactly when you most want to add something. Now Enter queues, the queue drains in order when the turn lands, ⌘⏎ interrupts, and a stopped turn keeps what you typed. Release notes 2026-09-15 — vocion-core#352.

Status: shipped. vocion-core #352 is merged onto the release branch release/2026-09-15-rail (the commit inside it is bfb5b3b1) and deployed to a deployment we operate. The pull request is still landing on main individually behind that release branch.
The chat composer went disabled for the length of a turn. You asked for something, the agent started working, and the box you would have typed into greyed out — for thirty seconds, sometimes for two minutes.
That is the exact window in which you think of the thing you should have said. And skip the ones already closed. Group the rest by owner. By the time the box came back, either you had lost the thought or the agent had already done the work you were about to redirect.
The box no longer greys out. Not at any point in the turn.
Queued messages sit directly above the box, oldest first, each with an ✕ to drop it. Click one to pull it back into the composer and edit it. Three are visible; the rest fold into “+N more” so a phone keeps its viewport.
What each key does now
| Gesture | Idle | While a turn is streaming |
|---|---|---|
Enter | sends | queues — never interrupts |
⌘⏎ / Ctrl+⏎ | sends | stops the turn and sends immediately |
Esc (empty box) | — | stops the turn |
Esc (with text) | — | nothing |
That last row is deliberate. A stray Esc must not eat a half-typed thought.
Enter alone always queues, and never kills a running turn. Ending a turn somebody wanted is not something to do by surprise, so the interrupt has its own gesture. While you are streaming with something in the box, the primary action becomes Queue and Stop steps back to a quiet ghost beside it — still one primary action on screen.
The placeholder changes to “Queue a message… ⌘⏎ to send now” while a turn runs, so the affordance is discoverable without a tooltip. The ? shortcuts sheet lists the new gestures.
What happens to the queue
When the turn lands, the queued messages send themselves — one per completed turn, in order, as separate turns, with nobody touching anything.
When the turn is stopped or fails, they are kept and the composer says so.
“The turn ended early — these were not sent.” Your typing is never dropped quietly, and it is never sent without you noticing either.
The queue survives a rail resize, a dock collapse, and a route change inside the workspace — it is held in sessionStorage, keyed per conversation, so switching threads shows that thread's queue. A brand-new thread carries its queue across when it acquires an id. A reload in a fresh tab starts empty, by design: a queue is not worth a server round-trip, and a new tab is a new train of thought.
What this is not
It is not mid-turn steering. A queued message does not reach the turn that is already running — it waits for the next one. We looked hard at whether it could, and the answer today is no, for a reason worth reading if you are building on the same stack: Why mid-turn steering is not possible yet. Queue-and-flush is the honest behaviour, and it is what you would get from most agent CLIs anyway.
Where this lives in the repo
vocion-core#352, shipped on release/2026-09-15-rail (bfb5b3b1).
- Pure reducer:
packages/core/src/features/dashboard/chat/queueReducer.ts— ordering, drop, edit-back, flush-on-complete, hold-on-failure — with 23 unit tests and no browser - React skin:
useSendQueue.ts(ids,sessionStorage, the drain effect) andcomposerQueue.ts, which gives the page, the rail and the artifact pane the same translated props - Keys:
ChatComposer.tsx—handleKeyDown - Session:
useChatSession.tsgained a synchronousstreamingRef(⌘⏎ stops and sends inside one handler, so a guard reading the renderedisStreamingwould refuse the send) and aturnOutcome, which is what the queue flushes on
Two things were fixed in the same change because stop-and-send exposed them: the stop path now finalises the stopped turn's own rows, and an aborted fetch's catch recognises its own controller. Without that, a stop-and-send wrote the stopped turn's tail onto the next turn's message.
Not verified: the browser test project has fifteen pre-existing failures in the chat directory on this base branch, unchanged by this PR; the new composer and queue suites pass.
The screenshots above are rendered from fixtures in the browser test runner with the real stylesheet — no workspace data of any kind.
Related: One conversation surface on every page.