docs(settings-modal): add design spec
Approved design for a general-settings modal behind the user-footer ⋯ button: Claude defaults, worktree defaults + maintenance actions, About section.
This commit is contained in:
132
docs/superpowers/specs/2026-04-21-settings-modal-design.md
Normal file
132
docs/superpowers/specs/2026-04-21-settings-modal-design.md
Normal file
@@ -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<ClaudeDoDbContext>`.
|
||||||
|
|
||||||
|
### 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<bool>` 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.
|
||||||
Reference in New Issue
Block a user