From b4741137d0a63e0cde8090bff1e1df49a1d6bc91 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Tue, 21 Apr 2026 13:32:58 +0200 Subject: [PATCH] docs(settings-modal): add design spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Approved design for a general-settings modal behind the user-footer ⋯ button: Claude defaults, worktree defaults + maintenance actions, About section. --- .../specs/2026-04-21-settings-modal-design.md | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-21-settings-modal-design.md diff --git a/docs/superpowers/specs/2026-04-21-settings-modal-design.md b/docs/superpowers/specs/2026-04-21-settings-modal-design.md new file mode 100644 index 0000000..385f6d4 --- /dev/null +++ b/docs/superpowers/specs/2026-04-21-settings-modal-design.md @@ -0,0 +1,132 @@ +# Settings Modal — Design + +**Date:** 2026-04-21 +**Status:** Approved for planning + +## Goal + +Add a general-settings modal reachable from the **⋯** button in the user footer of the Lists island (`ListsIslandView.axaml:68`). The modal exposes app-wide defaults for Claude runs, worktree behavior, and maintenance actions (cleanup / force-remove worktrees), plus a read-only "About" section with paths. + +## Scope + +**In scope** + +- Claude defaults: instructions, model, max turns, permission mode +- Worktree defaults: strategy, central root, auto-cleanup toggle + days +- Worktree maintenance actions: cleanup finished, force-remove all +- About section: version, data/logs/config paths with "Open in Explorer" +- Single settings row persisted in SQLite +- SignalR surface for read/update + maintenance +- `ClaudeArgsBuilder` merge behavior for per-task overrides + +**Out of scope** + +- Worker-side infrastructure settings (hub URL, auto-start worker) — stays in `worker.config.json` +- Per-task "inherit defaults" toggle (always inherit; task values override per rule below) +- Any UI-layer tests (project has none today) + +## Architecture + +### Persistence + +New single-row entity `AppSettingsEntity` (Id = 1) in SQLite. Access via new `AppSettingsRepository` with `GetAsync` / `UpdateAsync`. Seeded by a new EF migration `AddAppSettings`. + +Fields: + +| Column | Type | Seed default | +|---|---|---| +| `DefaultClaudeInstructions` | text | `""` | +| `DefaultModel` | string | `sonnet` | +| `DefaultMaxTurns` | int | `30` | +| `DefaultPermissionMode` | string | `acceptEdits` | +| `WorktreeStrategy` | string | `sibling` | +| `CentralWorktreeRoot` | string? | `null` | +| `WorktreeAutoCleanupEnabled` | bool | `false` | +| `WorktreeAutoCleanupDays` | int | `7` | + +Rationale for DB over `worker.config.json`: transactional writes from the UI, no file-watcher dance, and the Worker already uses `IDbContextFactory`. + +### Merge rules for per-task overrides + +`ClaudeArgsBuilder` gains a dependency on `AppSettingsRepository` and merges at build time: + +- **Instructions:** `global + "\n\n" + task` (skip separator if either side empty) +- **Model / MaxTurns / PermissionMode:** `task ?? global` (task value wins when set) + +No `TaskEntity` schema change. + +### SignalR surface (new hub methods) + +| Method | Returns | Notes | +|---|---|---| +| `GetAppSettings()` | `AppSettingsDto` | Single row | +| `UpdateAppSettings(dto)` | void | Full-row replace | +| `CleanupFinishedWorktrees()` | `int removedCount` | Skips Active | +| `ResetAllWorktrees()` | `{ removed, tasksAffected }` | Fails if any task is Running | + +Maintenance logic lives in a new `WorktreeMaintenanceService` in the Worker; the hub stays thin. Service uses existing `GitService` + `WorktreeRepository`. + +**Running-task guard:** `ResetAllWorktrees()` checks for any `Running` tasks before touching anything. If present, returns an error — the modal surfaces *"Cannot force-remove: N task(s) still running. Cancel them first."* + +Affected worktrees after force-remove are marked `Discarded` in `WorktreeRepository`. + +## UI + +### Entry point + +`ListsIslandView.axaml:68` ⋯ button binds to a new `OpenSettingsCommand` on `IslandsShellViewModel`. Command resolves `SettingsModalViewModel` and shows `SettingsModalView` via the existing modal pattern (`TaskCompletionSource` on save/cancel — same as `WorktreeModalView` / `DiffModalView`). + +### Layout + +Single scrollable modal, ~560 px wide, matches existing modal chrome (header eyebrow, monospace labels, close affordance). No tabs. + +**Sections (top to bottom):** + +1. **CLAUDE DEFAULTS** — instructions textarea (6 lines), model picker, max-turns numeric, permission-mode picker +2. **WORKTREES** — strategy picker, central-root folder picker, auto-cleanup toggle + days, then the two maintenance buttons +3. **ABOUT** — version (read-only), data folder / logs folder / worker.config path, each with "Open in Explorer" icon button + +Footer: `[ Cancel ]` `[ Save ]`, right-aligned. Save button disabled while the form is invalid. + +### Destructive action UX + +- **Cleanup finished** — single click, inline result line under the button (`"Removed 3 worktree(s)."`), auto-clears ~4 s +- **Force-remove all** — click reveals an inline confirm row: *"Remove ALL N worktrees? Uncommitted work will be lost."* with red `Remove All` and neutral `Cancel`. Two-click confirm, no typed string (matches the delete-task confirm already in the app) + +Both run against the worker over SignalR and leave the modal open on completion. + +### Open-in-Explorer + +Uses `Process.Start("explorer.exe", path)`. Windows-only is acceptable (app ships Windows-only via WPF installer). + +### Validation + +Modal-side only, block `Save`: + +- Max turns: integer 1–200 +- Auto-cleanup days: integer 1–365 (required only when toggle is on) +- Central root: required when Strategy = Central; must be an existing directory +- Instructions: no length cap + +Invalid fields get a red eyebrow with the specific error text. + +## Testing (ClaudeDo.Worker.Tests) + +- **`AppSettingsRepositoryTests`** — round-trip `Get` / `Update` on real SQLite +- **`ClaudeArgsBuilderTests`** — four merge cases: both empty, only global, only task, both set (prepend + separator behavior) +- **`WorktreeMaintenanceServiceTests`** — real git worktree fixtures: + - cleanup skips `Active`, removes `Merged` / `Discarded` / `Kept` + - force-remove fails while any task is `Running` + - force-remove succeeds otherwise and flips affected worktrees to `Discarded` + +No UI-layer tests (project has none today). + +## Build order (high level) + +1. Data: entity + configuration + migration + repository +2. Worker: `WorktreeMaintenanceService` + `ClaudeArgsBuilder` wiring + hub methods + DTO +3. UI: SignalR client methods on `WorkerClient`, `SettingsModalViewModel`, `SettingsModalView` +4. Wire ⋯ button on `ListsIslandView` → `IslandsShellViewModel.OpenSettingsCommand` +5. Tests (Worker.Tests) + +Detailed step-by-step sequencing belongs in the implementation plan, not here.