Skip to content

Commit ead7528

Browse files
committed
fix(progress): clear spinner before split prompt and stream output
The spinner was running continuously through split detection, hiding the Confirm dialog and making the tool appear to hang. Now: - Finish analysis spinner before interactive split prompt - Restart a fresh spinner for LLM generation phase - Clear spinner on first streaming token (seamless transition) - Use progress.phase() for generation messages (consistent UX) - Add Progress::take_bar() to move bar into spawned tasks cleanly
1 parent 7d00c9a commit ead7528

2 files changed

Lines changed: 27 additions & 9 deletions

File tree

src/app.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ impl App {
157157

158158
debug!(count = symbols.len(), "symbols extracted");
159159

160+
// Finish analysis spinner before any interactive prompts
161+
progress.finish();
162+
160163
// Step 3.5: Split detection
161164
if !self.cli.no_split {
162165
let is_interactive = std::io::stdout().is_terminal() && std::io::stdin().is_terminal();
@@ -225,14 +228,13 @@ impl App {
225228
// Step 5: Generate commit message(s)
226229
let num_candidates = self.cli.generate;
227230

231+
// Restart spinner for LLM generation phase
232+
let mut progress = Progress::new(self.cli.verbose);
228233
progress.phase(&format!(
229234
"Contacting {} ({})...",
230235
self.config.provider, self.config.model
231236
));
232237

233-
// Finish spinner before streaming output begins
234-
progress.finish();
235-
236238
let provider = llm::create_provider(&self.config)?;
237239
debug!(provider = provider.name(), "verifying provider");
238240
provider.verify().await?;
@@ -245,28 +247,38 @@ impl App {
245247
}
246248

247249
if num_candidates > 1 {
248-
eprintln!(
249-
"{} Generating candidate {}/{}...",
250-
style("info:").cyan(),
250+
progress.phase(&format!(
251+
"Generating candidate {}/{}...",
251252
i + 1,
252253
num_candidates
253-
);
254+
));
254255
} else {
255-
eprintln!("{} Generating...\n", style("info:").cyan());
256+
progress.phase("Generating...");
256257
}
257258

258259
let (tx, mut rx) = mpsc::channel::<String>(64);
259260

260261
// Only stream output for single generation
261262
let show_stream = num_candidates == 1;
262263
let cancel_for_printer = self.cancel_token.clone();
264+
let spinner = progress.take_bar();
265+
263266
let print_handle = tokio::spawn(async move {
267+
let mut first = true;
264268
loop {
265269
tokio::select! {
266270
_ = cancel_for_printer.cancelled() => break,
267271
token = rx.recv() => {
268272
match token {
269-
Some(t) if show_stream => eprint!("{}", t),
273+
Some(t) if show_stream => {
274+
if first {
275+
if let Some(ref bar) = spinner {
276+
bar.finish_and_clear();
277+
}
278+
first = false;
279+
}
280+
eprint!("{}", t);
281+
}
270282
Some(_) => {} // Suppress streaming for multi-gen
271283
None => break,
272284
}

src/services/progress.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ impl Progress {
8080
bar.finish_and_clear();
8181
}
8282
}
83+
84+
/// Take ownership of the underlying progress bar (for sending to spawned tasks).
85+
/// After calling this, the `Progress` struct will no longer display or clear the bar.
86+
pub fn take_bar(&mut self) -> Option<ProgressBar> {
87+
self.bar.take()
88+
}
8389
}
8490

8591
impl Drop for Progress {

0 commit comments

Comments
 (0)