fix(tui/auto-review): defer baseline capture (fixes #459)

This commit is contained in:
James Peter
2025-12-23 04:34:47 +10:00
parent 3dc58632f5
commit 367c3d2c1b
3 changed files with 112 additions and 4 deletions
+5
View File
@@ -3419,6 +3419,11 @@ impl App<'_> {
widget.handle_ghost_snapshot_finished(job_id, result, elapsed);
}
}
AppEvent::AutoReviewBaselineCaptured { turn_sequence, result } => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.handle_auto_review_baseline_captured(turn_sequence, result);
}
}
}
}
if self.alt_screen_active {
+6
View File
@@ -139,6 +139,12 @@ pub(crate) enum AppEvent {
elapsed: Duration,
},
/// Background auto-review baseline capture finished (non-blocking).
AutoReviewBaselineCaptured {
turn_sequence: u64,
result: Result<GhostCommit, GitToolingError>,
},
/// Internal: flush any pending out-of-order ExecEnd events that did not
/// receive a matching ExecBegin within a short pairing window. This lets
/// the TUI render a fallback "Ran call_<id>" cell so output is not lost.
+101 -4
View File
@@ -12628,10 +12628,7 @@ impl ChatWidget<'_> {
// covers changes made during the turn, not pre-existing local edits.
self.auto_review_baseline = None;
if self.config.tui.auto_review_enabled {
match self.capture_auto_turn_commit("auto review baseline snapshot", None) {
Ok(commit) => self.auto_review_baseline = Some(commit),
Err(err) => tracing::warn!("failed to capture auto review baseline: {err}"),
}
self.spawn_auto_review_baseline_capture();
}
// Don't add loading cell - we have progress in the input area
@@ -18040,6 +18037,82 @@ Have we met every part of this goal and is there no further work to do?"#
result
}
fn capture_auto_review_baseline_for_path(
repo_path: PathBuf,
) -> Result<GhostCommit, GitToolingError> {
#[cfg(test)]
if let Some(stub) = CAPTURE_AUTO_TURN_COMMIT_STUB.lock().unwrap().as_ref() {
return stub("auto review baseline snapshot", None);
}
let hook_repo = repo_path.clone();
let options =
CreateGhostCommitOptions::new(repo_path.as_path()).message("auto review baseline snapshot");
let hook = move || bump_snapshot_epoch_for(&hook_repo);
let result = create_ghost_commit(&options.post_commit_hook(&hook));
if result.is_ok() {
bump_snapshot_epoch_for(&repo_path);
}
result
}
fn spawn_auto_review_baseline_capture(&mut self) {
let turn_sequence = self.turn_sequence;
let repo_path = self.config.cwd.clone();
let app_event_tx = self.app_event_tx.clone();
if let Ok(handle) = tokio::runtime::Handle::try_current() {
handle.spawn(async move {
let result = tokio::task::spawn_blocking(move || {
ChatWidget::capture_auto_review_baseline_for_path(repo_path)
})
.await
.unwrap_or_else(|err| {
Err(GitToolingError::Io(io::Error::new(
io::ErrorKind::Other,
format!("auto review baseline task failed: {err}"),
)))
});
app_event_tx.send(AppEvent::AutoReviewBaselineCaptured {
turn_sequence,
result,
});
});
} else {
std::thread::spawn(move || {
let result = ChatWidget::capture_auto_review_baseline_for_path(repo_path);
app_event_tx.send(AppEvent::AutoReviewBaselineCaptured {
turn_sequence,
result,
});
});
}
}
pub(crate) fn handle_auto_review_baseline_captured(
&mut self,
turn_sequence: u64,
result: Result<GhostCommit, GitToolingError>,
) {
if turn_sequence != self.turn_sequence {
tracing::debug!(
"ignored auto review baseline for stale turn_sequence={turn_sequence}"
);
return;
}
if self.auto_review_baseline.is_some() {
tracing::debug!("auto review baseline already set; skipping update");
return;
}
match result {
Ok(commit) => {
self.auto_review_baseline = Some(commit);
}
Err(err) => {
tracing::warn!("failed to capture auto review baseline: {err}");
}
}
}
#[cfg(any(test, feature = "test-helpers"))]
#[allow(dead_code)]
fn git_diff_name_only_between(
@@ -28432,6 +28505,30 @@ use code_core::protocol::OrderMeta;
assert_eq!(calls.load(Ordering::SeqCst), 1);
}
#[test]
fn task_started_defers_auto_review_baseline_capture() {
let _stub_lock = AUTO_STUB_LOCK.lock().unwrap();
let _rt = enter_test_runtime_guard();
let _capture_guard = CaptureCommitStubGuard::install(|_, _| {
Ok(GhostCommit::new("baseline".to_string(), None))
});
let mut harness = ChatWidgetHarness::new();
let chat = harness.chat();
chat.config.tui.auto_review_enabled = true;
chat.handle_code_event(Event {
id: "turn-1".to_string(),
event_seq: 0,
msg: EventMsg::TaskStarted,
order: None,
});
assert!(
chat.auto_review_baseline.is_none(),
"baseline capture should not block TaskStarted"
);
}
#[test]
fn background_review_completion_resumes_auto_and_posts_summary() {
let _rt = enter_test_runtime_guard();