2 Commits

Author SHA1 Message Date
Taylor Eernisse
0b6b168043 chore(beads): Update issue tracker metadata
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:02:17 -05:00
Taylor Eernisse
1d003aeac2 fix(sync): Replace text-only progress with animated bars for docs/embed stages
Stages 3 (generate-docs) and 4 (embed) reported progress by appending
"(N/M)" text to the stage spinner message, while stages 1-2 (ingest)
used dedicated indicatif progress bars with animated [====> ] rendering
registered with the global MultiProgress. This visual inconsistency
was introduced when progress callbacks were wired through in 266ed78.

Replace the spinner.set_message() callbacks with proper ProgressBar
instances that match the ingest stage pattern:
- Create a bar-style ProgressBar registered via multi().add()
- Use the same template/progress_chars as the ingest discussion bars
- Lazy-init the tick via AtomicBool to avoid showing the bar before
  the first callback fires (matching how ingest enables ticks only
  at DiscussionSyncStarted)
- Update set_length on every callback for the docs stage, since the
  regenerator's estimated_total can grow if new dirty items are
  queued during processing (using .max() internally)
- Clean up both the sub-bar and stage spinner on completion/error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 15:02:13 -05:00
3 changed files with 63 additions and 14 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
bd-3pz
bd-1ht

View File

@@ -3,6 +3,8 @@
use console::style;
use indicatif::{ProgressBar, ProgressStyle};
use serde::Serialize;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tracing::Instrument;
use tracing::{info, warn};
@@ -160,16 +162,39 @@ pub async fn run_sync(
options.robot_mode,
);
info!("Sync stage {current_stage}/{total_stages}: generating documents");
let docs_spinner = spinner.clone();
// Create a dedicated progress bar matching the ingest stage style
let docs_bar = if options.robot_mode {
ProgressBar::hidden()
} else {
let b = crate::cli::progress::multi().add(ProgressBar::new(0));
b.set_style(
ProgressStyle::default_bar()
.template(
" {spinner:.blue} Processing documents [{bar:30.cyan/dim}] {pos}/{len}",
)
.unwrap()
.progress_chars("=> "),
);
b
};
let docs_bar_clone = docs_bar.clone();
let tick_started = Arc::new(AtomicBool::new(false));
let tick_started_clone = Arc::clone(&tick_started);
let docs_cb: Box<dyn Fn(usize, usize)> = Box::new(move |processed, total| {
if total > 0 {
docs_spinner.set_message(format!(
"Processing documents... ({processed}/{total})"
));
if !tick_started_clone.swap(true, Ordering::Relaxed) {
docs_bar_clone.enable_steady_tick(std::time::Duration::from_millis(100));
}
// Update length every callback — the regenerator's estimated_total
// can grow if new dirty items are queued during processing.
docs_bar_clone.set_length(total as u64);
docs_bar_clone.set_position(processed as u64);
}
});
let docs_result = run_generate_docs(config, false, None, Some(docs_cb))?;
result.documents_regenerated = docs_result.regenerated;
docs_bar.finish_and_clear();
spinner.finish_and_clear();
} else {
info!("Sync: skipping document generation (--no-docs)");
@@ -185,19 +210,43 @@ pub async fn run_sync(
options.robot_mode,
);
info!("Sync stage {current_stage}/{total_stages}: embedding documents");
let embed_spinner = spinner.clone();
// Create a dedicated progress bar matching the ingest stage style
let embed_bar = if options.robot_mode {
ProgressBar::hidden()
} else {
let b = crate::cli::progress::multi().add(ProgressBar::new(0));
b.set_style(
ProgressStyle::default_bar()
.template(
" {spinner:.blue} Generating embeddings [{bar:30.cyan/dim}] {pos}/{len}",
)
.unwrap()
.progress_chars("=> "),
);
b
};
let embed_bar_clone = embed_bar.clone();
let tick_started = Arc::new(AtomicBool::new(false));
let tick_started_clone = Arc::clone(&tick_started);
let embed_cb: Box<dyn Fn(usize, usize)> = Box::new(move |processed, total| {
embed_spinner.set_message(format!(
"Embedding documents... ({processed}/{total})"
));
if total > 0 {
if !tick_started_clone.swap(true, Ordering::Relaxed) {
embed_bar_clone.enable_steady_tick(std::time::Duration::from_millis(100));
}
embed_bar_clone.set_length(total as u64);
embed_bar_clone.set_position(processed as u64);
}
});
match run_embed(config, options.full, false, Some(embed_cb)).await {
Ok(embed_result) => {
result.documents_embedded = embed_result.embedded;
embed_bar.finish_and_clear();
spinner.finish_and_clear();
}
Err(e) => {
// Graceful degradation: Ollama down is a warning, not an error
embed_bar.finish_and_clear();
spinner.finish_and_clear();
if !options.robot_mode {
eprintln!(" {} Embedding skipped ({})", style("warn").yellow(), e);