notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit bfa3c342d1f0c68a1fe9c0162a767a0d10e03d88
parent 5b8b01f91e44189515f7c1162e23795bac625735
Author: William Casarin <jb55@jb55.com>
Date:   Tue, 24 Feb 2026 11:39:44 -0800

dave: allow message queuing while Claude is working

Previously the Ask button was replaced by Stop while Claude was
working, and handle_user_send() unconditionally dispatched to the
backend (overwriting the active stream). This meant users couldn't
send follow-up messages during a turn, and Shift+Enter while working
was a latent bug that would lose the current stream.

Now the Ask button is always visible (Stop shown alongside for local
sessions), and handle_user_send() skips backend dispatch when already
streaming. Queued messages stay in chat and are automatically
dispatched by the existing needs_redispatch_after_stream_end() logic
when the current turn finishes.

This also helps unstick remote sessions: sending a message from a
remote device forces the local side to re-evaluate session state,
and the message gets dispatched once the stream ends (or immediately
if the process had crashed and the channel disconnected).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Diffstat:
Mcrates/notedeck_dave/src/lib.rs | 7+++++++
Mcrates/notedeck_dave/src/ui/dave.rs | 33+++++++++++++++++++--------------
2 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs @@ -2185,6 +2185,13 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr if session.is_remote() { return; } + + // If already streaming, queue the message in chat without dispatching. + // needs_redispatch_after_stream_end() will dispatch it when the + // current turn finishes. + if session.is_streaming() { + return; + } } self.send_user_message(app_ctx, ui.ctx()); } diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs @@ -1086,8 +1086,25 @@ impl<'a> DaveUi<'a> { ui.with_layout(Layout::right_to_left(Align::Max), |ui| { let mut dave_response = DaveResponse::none(); - // Show Stop button when working, Ask button otherwise - if self.flags.contains(DaveUiFlags::IsWorking) { + // Always show Ask button (messages queue while working) + if ui + .add( + egui::Button::new(tr!( + i18n, + "Ask", + "Button to send message to Dave AI assistant" + )) + .min_size(egui::vec2(60.0, 44.0)), + ) + .clicked() + { + dave_response = DaveResponse::send(); + } + + // Show Stop button alongside Ask for local working sessions + if self.flags.contains(DaveUiFlags::IsWorking) + && !self.flags.contains(DaveUiFlags::IsRemote) + { if ui .add( egui::Button::new(tr!( @@ -1109,18 +1126,6 @@ impl<'a> DaveUi<'a> { .color(ui.visuals().warn_fg_color), ); } - } else if ui - .add( - egui::Button::new(tr!( - i18n, - "Ask", - "Button to send message to Dave AI assistant" - )) - .min_size(egui::vec2(60.0, 44.0)), - ) - .clicked() - { - dave_response = DaveResponse::send(); } let r = ui.add(