Skip to content

v0.8.5 fix(ux): pressing Enter while agent is working has 4 different behaviors with no visible signal #345

@Hmbown

Description

@Hmbown

Problem

When the user presses Enter on a non-empty composer while the agent is already working, the message goes to one of four different fates with no UI indication of which one fired. Behavior depends on fast-changing internal state the user has no visibility into.

From crates/tui/src/tui/app.rs:759-772:

pub enum SubmitDisposition {
    /// Engine idle and online: send immediately.
    Immediate,
    /// Offline mode: park on `queued_messages`.
    Queue,
    /// Engine busy and online: forward as a mid-turn steer.
    Steer,
    /// Model is actively streaming text; park on `queued_messages` for
    /// dispatch after TurnComplete.
    QueueFollowUp,
}

So pressing Enter mid-turn does one of:

  1. Steer — interrupts the model with a redirect (engine busy, not streaming).
  2. QueueFollowUp — silently parks for after the turn completes (engine busy, currently streaming text).
  3. Queue — parks because we're offline.
  4. (Or sends immediately if the timing was right.)

The boundary between Steer and QueueFollowUp is "is the model emitting text right now?" — which flips back and forth multiple times within a single turn (text → tool call → text → tool call → final text). The user pressing Enter at the wrong millisecond can't tell whether they're steering or queuing.

What's missing

  • No visual signal of the active disposition while typing. The send-button or footer should change icon/label depending on what Enter will do right now.
  • No explicit "steer" vs "queue" key. Today both go through Enter and the disposition is implicit. Power users want a deterministic chord (e.g. Ctrl+Enter = always steer, Shift+Enter = always queue, Enter = use disposition).
  • Queued messages are visually quiet. The composer probably hints at queue depth somewhere but the user reports inconsistent feedback. Verify: is there a queued-count indicator, and does it survive the streaming → idle transition reliably?
  • Steer feedback is unclear. When a steer succeeds, the user should see something in the transcript or footer confirming "your message was injected mid-turn at sequence N." Right now this may or may not surface.

Fix

Two layers:

1. Make the disposition visible before Enter

In the composer chrome (or send-button hint, or footer), show what Enter will do right now:

  • → send — Immediate
  • ↳ steer — Steer (engine running tools)
  • ⏱ queue (after turn) — QueueFollowUp (model streaming text)
  • 📭 queue (offline) — Queue (offline)

Update on every state change (TurnStarted, StreamingStarted, StreamingFinished, TurnComplete, OfflineEnter/Exit).

2. Give power users explicit chords

  • Enter — current behavior (auto-disposition).
  • Ctrl+Enter — force Steer (warn if not applicable).
  • Shift+Enter — force Queue (always parks for after turn).

Document in /help and the keybindings.json.

3. Confirm queue/steer outcomes

  • Steer success → toast or transcript marker "message steered into turn".
  • Queue → footer chip showing depth (📥 2 queued) that's always visible until drained.

Acceptance criteria

  • Disposition hint visible in the composer or footer; updates live with engine state.
  • Ctrl+Enter and Shift+Enter chords land with explicit semantics.
  • Queue depth chip in footer survives streaming/idle transitions.
  • Steer success has visible feedback (transcript or toast).
  • Test: dispatch each of the 4 dispositions in a fake-engine integration test and assert the correct visible signal.
  • /help and keybindings docs updated.

Touches: crates/tui/src/tui/app.rs::decide_submit_disposition, the composer view (crates/tui/src/tui/views/), footer renderer in tui/ui.rs, key dispatch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions