Files
ClaudeDo/docs/superpowers/plans/2026-06-19-feature-unification-plan.md
Mika Kuns 0993eb0e75 docs(unification): spec + phased plan for one-component-per-feature
Maps duplication into three buckets (parallel impls, entry-point sprawl, dead/leftover) and defines six phased unification slices: groundwork, DialogService, MergeCoordinator, WorktreeActions, AgentConfigeditor, unified DiffViewer.
2026-06-26 16:11:47 +02:00

7.1 KiB
Raw Blame History

Feature unification — phased plan

Date: 2026-06-19 Design: docs/superpowers/specs/2026-06-19-feature-unification-design.md

Six slices, sequenced cheapest/lowest-risk first. Each ends green (dotnet build -c Release + the touched test project) and is independently committable. Phases 01 are detailed here; 25 are scoped, and each gets its own docs/superpowers/plans/2026-06-19-unify-<slice>.md when picked up (per the 2026-06-05 layer-A/B/C convention). Build per-csproj (-c Release) — .slnx needs .NET 9 and a running Worker locks Debug.


Phase 0 — Groundwork (Bucket C). No UX change.

0a. Delete the dead hunks conflict API (C1).

  • Remove TaskMergeService.GetConflictsAsync + the MergeConflicts/ConflictFileContent records it returns (src/ClaudeDo.Worker/Lifecycle/TaskMergeService.cs:250) if unused elsewhere.
  • Remove WorkerHub.GetMergeConflicts (src/ClaudeDo.Worker/Hub/WorkerHub.cs:378) + MergeConflictsDto/ConflictFileDto/ConflictHunkDto if unused.
  • Remove WorkerClient's "GetMergeConflicts" invoke (src/ClaudeDo.Ui/Services/WorkerClient.cs:276) + the IWorkerClient member + every fake override (tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs, TasksIslandViewModelPlanningTests.cs, others — grep GetMergeConflicts).
  • Delete TaskMergeServiceTests.cs:672 GetConflictsAsync_AfterConflictMerge_ReturnsOursAndTheirs.
  • Verify with grep first: GetConflictsAsync and GetMergeConflicts have no callers outside this chain + tests.
  • Acceptance: Worker + Ui build; Worker.Tests + Ui.Tests green; GetMergeConflictDocuments path untouched.

0b. Single task-creation path (C2).

  • Identify the path MCP ExternalMcpService.AddTask uses; expose a thin creation method (repository or a small TaskCreationService) that applies the same defaults (ListId, SortOrder, CreatedAt).
  • Re-point TasksIslandViewModel.AddAsync at it instead of db.Tasks.Add direct EF.
  • Acceptance: quick-add still works; one creation path; Ui.Tests + Worker.Tests green.

0c. Prune stale worktrees (C3).

  • git worktree list; remove the orphaned .claude/worktrees/* entries (confirm each is unwanted with Mika before git worktree remove).
  • Acceptance: only intended worktrees remain; no tracked files change.

C4 (naming alignment) intentionally NOT in this phase — see design.


Phase 1 — DialogService (B3B5). Lowmedium.

Goal: one IDialogService replaces the scattered Show* Func seams and the duplicate open-commands.

  • New IDialogService (Ui/Services) with typed methods: OpenListSettings(ListNavItemViewModel), OpenRepoImport(), OpenWorktreesOverview(string? listId), OpenWeeklyReport(), OpenAbout(), OpenWorkerConnectionHelp(). Implementation owns the factories + ModalShell/TCS wiring currently in MainWindow.axaml.cs + IslandsShellViewModel.cs:59-71.
  • Inject it into ListsIslandViewModel, TasksIslandViewModel, IslandsShellViewModel. Collapse the three List-Settings doors (Lists context menu, Tasks header, shell bridge IslandsShellViewModel.cs:190-194) to one dialogs.OpenListSettings(row) call; same for Repo Import (2→1) and Worktrees Overview (2→1, keep the listId? param for global-vs-per-list).
  • Keep ModalShell/TCS dialog pattern; this only centralizes opening.
  • Update fakes/ctors per the IWorkerClient-fakes hazard (ctor changes ripple to Ui.Tests).
  • Acceptance: every dialog opens via one method; no duplicate open-commands; Ui.Tests green; visual gap flagged (open each dialog from each former door).

Phase 2 — MergeCoordinator (B1). Medium.

Goal: delete the five RequestConflictResolution seams; one coordinator.

  • New IMergeCoordinator (Ui) MergeAsync(taskId, targetBranch) = the body of IslandsShellViewModel.RequestConflictResolutionAsync (:49) plus the "open MergeModal → on conflict open resolver" flow currently split across MergeModalViewModel:108 and DiffModalViewModel:103.
  • Remove the Func<string,string,Task>? RequestConflictResolution from WorktreesOverviewModalViewModel:83, DiffModalViewModel:75, MergeModalViewModel:33, MergeSectionViewModel:51, and the DetailsIslandViewModel:347 delegate; inject the coordinator instead.
  • Re-point doors: review Approve, Diff Merge button, WorktreesOverview single + batch (:331), Details merge section.
  • Update seam tests (WorktreesOverviewBatchMergeTests.cs:145, DetailsIslandConflictSeamTests.cs:84) to assert via the coordinator.
  • Acceptance: one merge entry API; resolver still opens for single-task AND planning conflict; Ui.Tests green; visual gap flagged (force a conflict from Approve and from the Diff Merge button).

Phase 3 — WorktreeActions (A3). Medium.

Goal: one per-task worktree-actions VM reused by overview rows + Details.

  • New WorktreeActionsViewModel(taskId) with Merge/Diff/Discard/Keep/ForceRemove over IWorkerClient (uses the Phase-2 coordinator for Merge, the Phase-5 viewer for Diff — until then, current calls).
  • WorktreesOverviewModalViewModel rows compose one each; MergeSectionViewModel hosts one for the active task. Remove the duplicated commands.
  • Acceptance: both surfaces drive the same VM; Ui.Tests green; visual gap flagged.

Phase 4 — AgentConfigEditor (A2). Medium.

Goal: one config editor for Global | List | Task scope.

  • New AgentConfigEditorViewModel(scope) over InheritanceResolver exposing Model/SystemPrompt/AgentPath/MaxTurns + reset commands + InheritedBadge state; persists via the scope's hub method (UpdateListConfig / UpdateTaskAgentSettings / app settings).
  • Embed in SettingsModalViewModel, ListSettingsModalViewModel, and the Details AgentSettingsSectionViewModel host; delete the duplicated field/reset logic.
  • Acceptance: identical editor in all three scopes; Localization parity; Ui.Tests green; visual gap flagged.

Phase 5 — DiffViewer (A1 + B2). High; last.

Goal: one diff component replaces DiffModal + WorktreeModal + PlanningDiff.

  • New DiffViewerViewModel with DiffSource enum/abstraction (DirtyWorktree | BranchVsBase | CommitRange | PlanningAggregate | IntegrationBranch) and an optional file-tree pane (port WorktreeModal's tree + Avalonia-12 selection workaround); reuse UnifiedDiffParser + DiffLinesView; keep PlanningDiff's combined-mode toggle as a source switch.
  • Re-point all B2 doors to open it with the right source. Remove the three old VMs/views.
  • Update DiffModalViewModelTests, PlanningDiffViewModelTests.
  • Acceptance: every diff door opens the one viewer; whole-unified AND file-tree layouts work; Ui.Tests green; visual gap flagged (worktree-dirty, post-merge commit-range, planning per-subtask + integration).

Sequencing rationale

0 (delete/no-UX) → 1 (isolated, unblocks nothing but cheap) → 2 (coordinator; 3 & 5 lean on it for Merge/Diff) → 3 → 4 (independent) → 5 (biggest, most UX-sensitive, benefits from 2's coordinator). Stop after any phase and the app is shippable.

Per-phase commits

Conventional Commits, one per phase (or per sub-step in Phase 0): e.g. refactor(merge): single MergeCoordinator replaces 5 conflict seams. Stage by path (never git add -A — concurrent sessions). Commit the spec + this plan first.