93 lines
5.2 KiB
Markdown
93 lines
5.2 KiB
Markdown
# Plan: Rider-style 3-pane merge editor
|
||
|
||
Spec: `docs/superpowers/specs/2026-06-19-rider-merge-editor-design.md`
|
||
|
||
TDD, one focused commit per task (Conventional Commits, `feat(merge): …`).
|
||
Build with `-c Release` per project (a running Worker locks `Debug`).
|
||
Run `ClaudeDo.Ui.Tests` (and `Localization.Tests` for Task 6). No real `claude` CLI in tests.
|
||
Stage ONLY the files each task touches, by explicit path (parallel sessions leave WIP).
|
||
Backend + seam stay unchanged. Implementer/reviewer subagents use **sonnet**.
|
||
|
||
## Task 1 — VM: active-file model + 3-pane reconstruction + readout
|
||
|
||
`ConflictResolverViewModel` / `ConflictModels.cs`, additive (seam untouched).
|
||
|
||
- Add `ActiveFile` (`MergeFile?`), `SelectFileCommand(MergeFile)`, default to first file
|
||
after load. Keep `Files`, `Current`/`CurrentIndex`/`Next`/`Previous` (focused conflict
|
||
for the header arrows), `CanContinue`, binary guard, planning routing — all unchanged.
|
||
- Add computed, per `ActiveFile`:
|
||
- `ActiveOursText` = concat(stable.Text | conflict.Ours)
|
||
- `ActiveTheirsText` = concat(stable.Text | conflict.Theirs)
|
||
- `ActiveResultText` = concat(stable.Text | conflict.Resolution ?? conflict.Ours)
|
||
- `ActiveConflicts` = ordered descriptors (block + segment index) for the view.
|
||
- `PositionText` → `"{conflicts} conflicts · {resolved} resolved"` for the active file;
|
||
keep `CanContinue` = every file resolved AND no binary.
|
||
- Switching files raises a change event the view listens to (reuse/extend
|
||
`CurrentChanged` → e.g. `ActiveFileChanged`).
|
||
- Tests (Ui.Tests): reconstruction text for ours/theirs/result (result seeds unresolved
|
||
with Ours); resolving a block updates `ActiveResultText` + readout; switching files
|
||
preserves each block's `Resolution`; `CanContinue` blocks until all files resolved;
|
||
binary file still blocks. Keep all existing tests green.
|
||
|
||
## Task 2 — View: 3-pane AXAML shell + document assembly + synced scroll
|
||
|
||
`Views/Conflicts/ConflictResolverView.axaml(.cs)`. Visual — verified by running.
|
||
|
||
- Replace AXAML: ModalShell host kept; header row (◀/▶ focus arrows bound to
|
||
Previous/Next, file switcher `ItemsControl`/`ComboBox` over `Files` bound to
|
||
`SelectFileCommand`, right-aligned `PositionText`); `Grid ColumnDefinitions="*,*,*"`
|
||
of three bordered panes with headers **Ours · current (merge target)** /
|
||
**Result** / **Theirs · incoming (task)** (drop Base); footer Continue
|
||
(`IsEnabled=CanContinue`) / Abort; binary banner (kept); `Escape`→Abort (kept).
|
||
- Code-behind: build three `TextDocument`s from `ActiveFile` segments, recording each
|
||
conflict's start line + line count per document; install TextMate per pane by file
|
||
extension; rebuild on `ActiveFileChanged`; Ours/Theirs `IsReadOnly=true`.
|
||
- Proportional synced vertical scroll across the three panes (re-entrancy guard).
|
||
- Push Result edits back to the active block `Resolution` (refined in Task 4).
|
||
|
||
## Task 3 — Result pane: read-only stable, editable conflicts
|
||
|
||
`ConflictResolverView.axaml.cs` + a small `IReadOnlySectionProvider` helper.
|
||
|
||
- Track each conflict's result span in a `TextSegmentCollection<…>` over the Result
|
||
document (anchors auto-adjust on edit).
|
||
- `IReadOnlySectionProvider`: `CanInsert` only strictly inside a conflict span;
|
||
`GetDeletableSegments` intersects with conflict spans only. Stable text becomes
|
||
immutable; conflict regions stay editable.
|
||
- Editing inside a conflict span writes the span text back to the block `Resolution`
|
||
and flips it resolved (updates readout + `CanContinue`).
|
||
|
||
## Task 4 — Color blocks (IBackgroundRenderer) + accept overlay
|
||
|
||
`ConflictResolverView.axaml.cs` + renderer/overlay helpers.
|
||
|
||
- `IBackgroundRenderer` per pane: unresolved conflict = red (Blood tint), resolved =
|
||
green/muted, Ours side = Moss tint, Theirs side = Accent tint — driven by recorded
|
||
spans + block `IsResolved`.
|
||
- Between-pane overlay Canvas (Ours|Result and Result|Theirs): `›` accept-ours / `‹`
|
||
accept-theirs + `✕` dismiss per conflict, positioned at the block's `TextView` visual
|
||
top, recomputed on scroll/resize. Click → `block.AcceptOurs/AcceptTheirs` and replace
|
||
the tracked Result span; resolved blocks recolor.
|
||
|
||
## Task 5 — Polish: readout, focus arrows scroll-to-conflict, resolved styling
|
||
|
||
- ◀/▶ arrows move `Current` and scroll all three panes to that conflict.
|
||
- `M conflicts · K resolved` live readout; Continue tooltip/hint when blocked.
|
||
- Resolved conflict recolors and drops its accept overlay; unresolved stays red.
|
||
(Fold into Task 4 if small.)
|
||
|
||
## Task 6 — Localization + tokens
|
||
|
||
- Add `conflictResolver.*` keys (pane headers, readout, accept tooltips, hints) to
|
||
`locales/en.json` AND `locales/de.json` (keep key parity).
|
||
- Add Tokens.axaml color tokens only if a needed conflict/resolved shade is missing.
|
||
- Run Localization.Tests (parity) + a quick scan for hard-coded strings in the view.
|
||
|
||
## Task 7 — Verify
|
||
|
||
- Build `ClaudeDo.App` + `ClaudeDo.Ui` `-c Release`; run `Ui.Tests` + `Localization.Tests`.
|
||
- Update `src/ClaudeDo.Ui/CLAUDE.md` (Planning/Conflicts paragraph → new 3-pane editor).
|
||
- **Visual verification gap (flag to Mika):** run the app, trigger a real conflict
|
||
(single-task approve + planning unit-merge) and confirm panes/colors/accept/scroll/
|
||
gating/binary render correctly — cannot be asserted in tests.
|