fix(ui): suppress auto-open conflict resolver during MCP-driven merges
review_task/continue_merge on a planning parent always leaves conflicts in the tree, and the UI auto-opened the resolver on every PlanningMergeConflict broadcast regardless of who started the merge -- so a running Claude session resolving a unit-merge conflict could race a human editing the same shared checkout in a resolver window neither of them asked for. PlanningMergeOrchestrator.StartAsync now takes an externallyDriven flag (set by ExternalMcpService's MCP-driven review_task path, left false for the UI's ApproveReview) that rides along on the PlanningMergeConflict broadcast. The UI only auto-opens the resolver when it's false; otherwise it shows a persistent banner with a manual "Open resolver" button, cleared on PlanningMergeAborted/PlanningCompleted. A new GetActiveExternalConflictsAsync query (checked against GitService.IsMidMergeAsync rather than the in-memory flag alone) lets the UI resync the banner on reconnect instead of trusting a one-shot broadcast that isn't replayed after a restart. The childless single-task conflict path was checked and needed no change -- it only broadcasts the generic TaskUpdated, never PlanningMergeConflict.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Review, merge & conflict resolution
|
||||
|
||||
> **Explore-note — verify before trusting.** Distilled map of a subsystem, not authoritative.
|
||||
> Last verified against commit `f6cb825` (2026-08-05).
|
||||
> Last verified against commit `8247a74` (2026-08-06).
|
||||
> Drift check: `git log --oneline f6cb825..HEAD -- src/ClaudeDo.Worker/Lifecycle src/ClaudeDo.Worker/State src/ClaudeDo.Worker/Planning src/ClaudeDo.Ui/ViewModels/Conflicts`
|
||||
> Stable structure only (no line numbers). See docs/explore-notes/README.md.
|
||||
|
||||
@@ -129,6 +129,23 @@ mid-merge conflicts **without re-starting the merge** and routes continue/abort
|
||||
`ContinuePlanningMerge` / `AbortPlanningMerge`, so a unit-merge conflict re-opens the editor
|
||||
per subtask via the `PlanningMergeConflict` broadcast.
|
||||
|
||||
**Except when an MCP session is driving the merge.** `PlanningMergeOrchestrator.StartAsync` takes
|
||||
an `externallyDriven` bool (default `false`; `ExternalMcpService.review_task`'s parent-with-children
|
||||
path passes `true`, since a running Claude session — not the UI — will resolve conflicts via
|
||||
`continue_merge`/`abort_merge`). The flag rides along on the `PlanningMergeConflict` broadcast
|
||||
(4th arg); `IslandsShellViewModel.OnPlanningMergeConflict` only auto-opens the resolver when it's
|
||||
`false` — otherwise it shows a persistent, non-auto-dismissing banner
|
||||
(`IsExternalMergeBannerVisible`) with a manual "Open resolver" button, cleared on
|
||||
`PlanningMergeAborted`/`PlanningCompleted`. This exists because two parties (a human and the
|
||||
driving session) could otherwise end up editing the same shared checkout at once.
|
||||
`PlanningMergeOrchestrator.GetActiveExternalConflictsAsync` (hub: `GetActiveExternalPlanningMergeConflicts`)
|
||||
re-derives this state by checking `GitService.IsMidMergeAsync` rather than trusting the in-memory
|
||||
flag alone, so a UI restart mid-merge (or a stale entry left behind if something resolved the
|
||||
repo outside the normal Continue/Abort path) can't show a phantom banner — the Ui calls it on
|
||||
`ConnectionRestoredEvent`. The childless single-task conflict path
|
||||
(`TaskMergeService.ApproveAndMergeAsync`) has no equivalent broadcast — it only sends the generic
|
||||
`TaskUpdated` — so it never auto-opened the resolver and needed no change.
|
||||
|
||||
### View
|
||||
|
||||
Three **AvaloniaEdit** panes showing the whole file: MAIN/ours (read-only) | editable Result |
|
||||
|
||||
Reference in New Issue
Block a user