# Phase 4 — AgentConfigEditor (A2) Date: 2026-06-23 (picked up after reordering Phase 3 ↔ 4) Umbrella: `docs/superpowers/plans/2026-06-19-feature-unification-plan.md` Design: `docs/superpowers/specs/2026-06-19-feature-unification-design.md` (A2) ## Reordering note Phase 3 (WorktreeActions) was deferred. Its premise — overview rows and the Details merge section each owning duplicate worktree commands — only half-holds: Details has no Discard/Keep/ForceRemove, and the two Diff doors open different VMs (`WorktreeModal` vs `DiffModal`) that only Phase 5 unifies. So Phase 3's clean form depends on Phase 5 (Diff) and a fuller MergeCoordinator (Merge); doing it now would build throwaway per-surface delegates. **Phase 3 is folded into Phase 5.** Phase 4 (independent, clean dedup) runs now. ## Scope decision: List + Task only (global left as-is) The design names three scopes (Global | List | Task). Verified against the tree on 2026-06-23, only **List and Task genuinely duplicate**: - **List** (`ListSettingsModalViewModel`, "AGENT" section): Model / MaxTurns / SystemPrompt / AgentFile, each with `InheritedBadge` + `↺` reset; 2-tier (list→global) badges computed with inline logic (does **not** use the existing `InheritanceResolver.ResolveList` — which is currently dead code); explicit Save. - **Task** (`AgentSettingsSectionViewModel`, TaskHeaderBar gear flyout): same four fields; 3-tier (task→list→global) badges via `InheritanceResolver.Resolve`; `EffectiveMaxTurns` + `EffectiveSystemPromptHint`; `IsRunning` gate; debounced auto-save. **Global** (`GeneralSettingsTabViewModel`, Settings → General) is the root: no inheritance, no badges, no agent file, no reset — three plain controls (model combo, max-turns numeric, instructions textbox) plus a global-only PermissionMode, interleaved with unrelated settings (Language, parallelism, report paths, standup weekday) and saved batched into one `AppSettingsDto` via the modal Save. Embedding the shared editor there buys ~3 plain fields at the cost of a degenerate no-badges/no-agent/no-reset mode plus surgery on the settings save path and a relayout of the most settings-dense view. **Not worth it — global stays as-is.** (Confirmed with Mika 2026-06-23.) The real maintenance hazard is the **VM logic** (two copies of badge/reset/inheritance that already drifted), and the **view** (3 of 4 field blocks are pixel-identical). Both collapse cleanly for List+Task. ## Target One `AgentConfigEditorViewModel` + one `AgentConfigEditor` UserControl, instantiated per surface with a scope. The two host VMs keep only their non-agent concerns and host the editor as a child. ### `ViewModels/Agent/AgentConfigEditorViewModel.cs` (new) - `enum AgentConfigScope { List, Task }` - ctor `(IWorkerClient worker, AgentConfigScope scope)` - Unified bindable surface (single names both views bind to): `Model` (string?), `MaxTurns` (decimal?), `SystemPrompt` (string), `SelectedAgent` (AgentInfo?); `ModelOptions`, `Agents`; `ModelBadge`/`TurnsBadge`/`AgentBadge`, `ModelInheritedHint`/`TurnsInheritedHint`, `EffectiveSystemPromptHint`; `EffectiveMaxTurns` (int), `IsRunning`/`IsEnabled`. - Reset commands: `ResetModel`, `ResetTurns`, `ResetAgent`, `ResetAll`. - Badges via `InheritanceResolver`: scope==Task → `Resolve(own, list, global)`; scope==List → `ResolveList(own, global)` (adopts the dead method). One `BadgeFor` helper covers both (List scope never yields the `List` source). - Load: `LoadForListAsync(listId)` and `LoadForTaskAsync(TaskEntity entity)` — both pull agents + app-settings (global defaults); Task also pulls the list tier + `EffectiveSystemPromptHint`. Localizer-change re-badges (port the `Loc.LanguageChanged` handler + `IDisposable`). - Save: `SaveAsync()` is scope-aware — List builds `UpdateListConfigDto` → `UpdateListConfigAsync`; Task builds `UpdateTaskAgentSettingsDto` → `UpdateTaskAgentSettingsAsync`. Task scope also auto-saves debounced (300ms) on field changes; List does not (the modal Save button calls `SaveAsync`). `SaveAsync` is directly callable (tests bypass the debounce). - Task-only `Clear()` + `TaskId`. ### `Views/Controls/AgentConfigEditor.axaml` (+ .axaml.cs) (new) - `x:DataType` = `AgentConfigEditorViewModel`; host sets `DataContext="{Binding Agent}"`. - The four field blocks (model/turns/systemprompt/agent) with `InheritedBadge` + `↺` reset, lifted verbatim from the existing two views (they already match). Agent combo shows Name + Description (both scopes; harmless for task). `EffectiveSystemPromptHint` line gated on non-empty (hides for List). - `StyledProperty ShowAgentBrowse` (default false). True → render the Browse button + path line; the browse file-picker code-behind lives here (moved from `ListSettingsModalView`). - Shared localization namespace `settings.agentEditor.*` (model/maxTurns/systemPrompt/ agentFile/promptPrepended). Reset tooltip reuses `settings.inherit.resetToInherited`. ### Re-point hosts - `ListSettingsModalViewModel`: drop the agent fields/badges/resets/option-lists; add `public AgentConfigEditorViewModel Agent { get; }` (scope=List). `LoadAsync` → `Agent.LoadForListAsync(listId)`. `SaveAsync` keeps `UpdateListAsync` (name/dir) and adds `await Agent.SaveAsync()`. Keep working-dir browse (`BrowseClicked`). - `ListSettingsModalView.axaml`: replace the AGENT section body with ``; the section-header "Reset agent settings" button binds `Agent.ResetAllCommand`. Remove the agent browse code-behind (moved into the control). - `DetailsIslandViewModel`: `AgentSettings` becomes `AgentConfigEditorViewModel` (scope=Task). Preserve the call sites: ctor, `EffectiveMaxTurns`→`TurnsText` PropertyChanged hook, `IsRunning` push, `Dispose`, `Clear`, `TaskId`, `LoadForTaskAsync(entity, ct)`. - `TaskHeaderBar.axaml`: replace the flyout field blocks with `` (ShowAgentBrowse=false). Keep the gear button + heading. - Delete `AgentSettingsSectionViewModel.cs`. ## Tests - New `tests/ClaudeDo.Ui.Tests/ViewModels/AgentConfigEditorViewModelTests.cs`: - List scope: badges resolve override-vs-global; resets clear; `SaveAsync` builds the right `UpdateListConfigDto` (via `StubWorkerClient`). - Task scope: badges resolve override/list/global; `EffectiveMaxTurns`/ `EffectiveSystemPromptHint` from list tier; resets clear; `SaveAsync` builds the right `UpdateTaskAgentSettingsDto`. - `InheritanceResolverTests` unchanged (resolver untouched). - Existing DetailsIsland* tests must stay green (they construct the VM but don't name the moved members). ## Acceptance - `dotnet build -c Release` clean for Ui (+ App). - `Ui.Tests` + `Localization.Tests` green. - One editor VM + one control drive both List and Task; duplicated field/badge/reset logic deleted; `ResolveList` now has a real caller. - Visual gap flagged: open List Settings → Agent, and a task's gear flyout — verify badges, ↺ resets, reset-all, agent browse (list only), system-prompt hint (task), and that list Save persists + task auto-saves. ## Commit `refactor(agent-config): single AgentConfigEditor for list + task scopes`. Stage by path. Commit this plan with it.