feat(review): gate Approve & Merge behind opening the diff
Approve & Merge is disabled until the pending changes have been inspected: DetailsIslandViewModel tracks ReviewDiffViewed (reset on task switch and on every state change), MergeSectionViewModel raises DiffViewed when a diff or combined diff is opened and exposes HasReviewableDiff, and a hint sits next to the button. A review with nothing to inspect (sandbox run, no worktree) approves straight through. ClaudeDo-Task: f9809a93
This commit is contained in:
@@ -296,6 +296,15 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
|
||||
AgentSettings = new AgentConfigEditorViewModel(worker, AgentConfigScope.Task);
|
||||
Merge = new MergeSectionViewModel(worker, services);
|
||||
Merge.DiffViewed = () => ReviewDiffViewed = true;
|
||||
Merge.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(MergeSectionViewModel.HasReviewableDiff))
|
||||
{
|
||||
ApproveReviewCommand.NotifyCanExecuteChanged();
|
||||
OnPropertyChanged(nameof(ShowReviewDiffHint));
|
||||
}
|
||||
};
|
||||
Prep = new PrepPanelViewModel(worker);
|
||||
|
||||
Notes = new NotesEditorViewModel(_notesApi);
|
||||
@@ -399,6 +408,11 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
DequeueCommand.NotifyCanExecuteChanged();
|
||||
ResetAndRetryCommand.NotifyCanExecuteChanged();
|
||||
ContinueCommand.NotifyCanExecuteChanged();
|
||||
// A state change means a new run/review cycle: the diff must be
|
||||
// re-inspected before merge can be approved again.
|
||||
ReviewDiffViewed = false;
|
||||
ApproveReviewCommand.NotifyCanExecuteChanged();
|
||||
OnPropertyChanged(nameof(ShowReviewDiffHint));
|
||||
AgentSettings.IsRunning = IsRunning;
|
||||
NotifySessionSections();
|
||||
OnPropertyChanged(nameof(CanAcceptDrop));
|
||||
@@ -752,6 +766,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
|
||||
partial void OnTaskChanged(TaskRowViewModel? value)
|
||||
{
|
||||
ReviewDiffViewed = false;
|
||||
Merge.SyncTaskContext(Task?.Id, Task?.Title, Task?.IsPlanningParent == true);
|
||||
NotifySessionSections();
|
||||
OnPropertyChanged(nameof(CanAcceptDrop));
|
||||
@@ -981,7 +996,20 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
private bool CanResetAndRetry() =>
|
||||
Task != null && _worker.IsConnected && ShowResetAndRetry;
|
||||
|
||||
[RelayCommand]
|
||||
// Set once the user opens the diff/combined-diff for the current review. Reset on
|
||||
// task switch and on every state change (a new run means a new diff to read), so
|
||||
// Approve & Merge stays blocked until the pending changes have been inspected.
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ApproveReviewCommand))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowReviewDiffHint))]
|
||||
private bool _reviewDiffViewed;
|
||||
|
||||
// True while a review is pending, there is a diff to inspect, and it has not been
|
||||
// opened yet — drives the "open the diff first" hint next to Approve & Merge.
|
||||
public bool ShowReviewDiffHint =>
|
||||
IsWaitingForReview && Merge.HasReviewableDiff && !ReviewDiffViewed;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanApproveReview))]
|
||||
private async System.Threading.Tasks.Task ApproveReviewAsync()
|
||||
{
|
||||
if (Task is null || !_worker.IsConnected) return;
|
||||
@@ -999,6 +1027,13 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Force the diff to have been opened before a merge can happen — but only when
|
||||
// there is actually something to inspect (a childless sandbox run with no worktree
|
||||
// has no diff, so it approves straight through).
|
||||
private bool CanApproveReview() =>
|
||||
Task != null && _worker.IsConnected && IsWaitingForReview
|
||||
&& (!Merge.HasReviewableDiff || ReviewDiffViewed);
|
||||
|
||||
[RelayCommand(CanExecute = nameof(HasReviewFeedback))]
|
||||
private async System.Threading.Tasks.Task RejectReviewAsync()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user