event_loop: diff MindState on input submission, fix input display

Extract diff_mind_state() — reused on render tick and input submit.
When pushing user input, lock shared, diff (catches any Mind state
changes), push input, snapshot. The next diff sees the input was
consumed → displays it.

Fixes: user text not appearing in conversation window.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 05:18:35 -04:00
parent 7dc515b985
commit 3b15c690ec

View file

@ -215,6 +215,46 @@ pub async fn cmd_switch_model(
}
}
fn diff_mind_state(
cur: &crate::mind::MindState,
prev: &crate::mind::MindState,
ui_tx: &ui_channel::UiSender,
dirty: &mut bool,
) {
if cur.dmn.label() != prev.dmn.label() || cur.dmn_turns != prev.dmn_turns {
let _ = ui_tx.send(UiMessage::StatusUpdate(ui_channel::StatusInfo {
dmn_state: cur.dmn.label().to_string(),
dmn_turns: cur.dmn_turns,
dmn_max_turns: cur.max_dmn_turns,
prompt_tokens: 0, completion_tokens: 0,
model: String::new(), turn_tools: 0,
context_budget: String::new(),
}));
*dirty = true;
}
// Turn started — input was consumed
if cur.turn_active && !prev.turn_active && !prev.input.is_empty() {
let text = prev.input.join("\n");
let _ = ui_tx.send(UiMessage::UserInput(text));
*dirty = true;
}
if cur.turn_active != prev.turn_active {
*dirty = true;
}
if cur.scoring_in_flight != prev.scoring_in_flight {
if !cur.scoring_in_flight && prev.scoring_in_flight {
let _ = ui_tx.send(UiMessage::Info("[scoring complete]".into()));
}
*dirty = true;
}
if cur.compaction_in_flight != prev.compaction_in_flight {
if !cur.compaction_in_flight && prev.compaction_in_flight {
let _ = ui_tx.send(UiMessage::Info("[compacted]".into()));
}
*dirty = true;
}
}
pub async fn run(
mut app: tui::App,
agent: &Arc<Mutex<Agent>>,
@ -304,39 +344,9 @@ pub async fn run(
// Diff MindState — generate UI messages from changes
{
let cur = shared_mind.lock().unwrap().clone();
if cur.dmn.label() != prev_mind.dmn.label() || cur.dmn_turns != prev_mind.dmn_turns {
let _ = ui_tx.send(UiMessage::StatusUpdate(ui_channel::StatusInfo {
dmn_state: cur.dmn.label().to_string(),
dmn_turns: cur.dmn_turns,
dmn_max_turns: cur.max_dmn_turns,
prompt_tokens: 0, completion_tokens: 0,
model: String::new(), turn_tools: 0,
context_budget: String::new(),
}));
dirty = true;
}
if cur.turn_active && !prev_mind.turn_active && !prev_mind.input.is_empty() {
let text = prev_mind.input.join("\n");
let _ = ui_tx.send(UiMessage::UserInput(text));
dirty = true;
}
if cur.turn_active != prev_mind.turn_active {
dirty = true;
}
if cur.scoring_in_flight != prev_mind.scoring_in_flight {
if !cur.scoring_in_flight && prev_mind.scoring_in_flight {
let _ = ui_tx.send(UiMessage::Info("[scoring complete]".into()));
}
dirty = true;
}
if cur.compaction_in_flight != prev_mind.compaction_in_flight {
if !cur.compaction_in_flight && prev_mind.compaction_in_flight {
let _ = ui_tx.send(UiMessage::Info("[compacted]".into()));
}
dirty = true;
}
prev_mind = cur;
let cur = shared_mind.lock().unwrap();
diff_mind_state(&cur, &prev_mind, &ui_tx, &mut dirty);
prev_mind = cur.clone();
}
while let Ok(notif) = notify_rx.try_recv() {
@ -433,7 +443,10 @@ pub async fn run(
}
}
_ => {
shared_mind.lock().unwrap().input.push(input);
let mut s = shared_mind.lock().unwrap();
diff_mind_state(&s, &prev_mind, &ui_tx, &mut dirty);
s.input.push(input);
prev_mind = s.clone();
}
}
}