fix(tui): show live submit disposition in composer hint (#345)

When the user has typed something into the composer and hits Enter, the
message goes to one of four fates depending on engine state:

- Immediate (idle + online) — most common, sends right away
- Steer (busy + tool execution) — forwards mid-turn
- QueueFollowUp (busy + streaming text) — parks for after TurnComplete
- Queue (offline) — parks on offline queue

Previously the user had no way to tell which would fire BEFORE pressing
Enter. The disposition flips with fast-changing internal state (whether
the model is currently streaming text vs. running a tool, whether
network connectivity has just dropped) and only the post-submit status
toast hinted at the result — which is too late if you wanted a different
behaviour.

Fix: extend the composer's bottom hint line so when the composer has
non-empty content, it shows what Enter will do RIGHT NOW. The hint flips
live with engine state, so the user sees the real behaviour before
pressing Enter:

  ↵ steer into current turn        (sky blue, busy + tool execution)
  ↵ queue for next turn            (muted, busy + streaming)
  ↵ offline queue (no engine)      (warning yellow, offline)

The Immediate case stays unhinted — that's the default and surfacing it
would be noise.

Closes #345.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hunter Bown
2026-05-02 10:13:45 -05:00
parent c88b980d52
commit bc13dbfee7
+31 -3
View File
@@ -362,14 +362,42 @@ impl Renderable for ComposerWidget<'_> {
Style::default().fg(palette::TEXT_MUTED),
),
]))
} else if self.slash_menu_entries.is_empty() {
None
} else {
} else if !self.slash_menu_entries.is_empty() {
Some(Line::from(vec![
Span::styled(" Up/Down move ", Style::default().fg(palette::TEXT_MUTED)),
Span::styled("Tab accept ", Style::default().fg(palette::TEXT_MUTED)),
Span::styled("Esc close", Style::default().fg(palette::TEXT_MUTED)),
]))
} else if !input_text.trim().is_empty() {
// Live disambiguation for #345: when there's content in the
// composer, show what `Enter` will do RIGHT NOW so the user
// never has to guess between Immediate / Steer / QueueFollowUp /
// Queue. The disposition flips with engine state so this hint
// is the only reliable cue before pressing Enter.
use crate::tui::app::SubmitDisposition;
let (label, color) = match self.app.decide_submit_disposition() {
SubmitDisposition::Immediate => (None, palette::TEXT_MUTED),
SubmitDisposition::Steer => (
Some("↵ steer into current turn"),
palette::DEEPSEEK_SKY,
),
SubmitDisposition::QueueFollowUp => (
Some("↵ queue for next turn"),
palette::TEXT_MUTED,
),
SubmitDisposition::Queue => (
Some("↵ offline queue (no engine)"),
palette::STATUS_WARNING,
),
};
label.map(|text| {
Line::from(vec![Span::styled(
format!(" {text} "),
Style::default().fg(color),
)])
})
} else {
None
};
let mut block = Block::default()