108 lines
10 KiB
Markdown
108 lines
10 KiB
Markdown
# ClaudeDo.Ui
|
|
|
|
Avalonia UI layer: views, viewmodels, converters, and the SignalR client.
|
|
|
|
Deeper detail: [review-merge](../../docs/explore-notes/review-merge.md) (diff stack + conflict
|
|
resolver) · [conpty-sessions](../../docs/explore-notes/conpty-sessions.md) (Mission Control
|
|
tiles) · [usage-monitoring](../../docs/explore-notes/usage-monitoring.md) (usage pill + modal).
|
|
|
|
## Pattern
|
|
|
|
MVVM with CommunityToolkit.Mvvm source generators:
|
|
- `[ObservableProperty]` for bindable properties, `[RelayCommand]` for commands
|
|
- All ViewModels inherit `ViewModelBase` (extends `ObservableObject`)
|
|
- All views use compiled bindings (`x:DataType`)
|
|
|
|
## Layout: Islands
|
|
|
|
`MainWindow` hosts three "islands" (lists | tasks | details). There is **no**
|
|
MainWindowViewModel, StatusBarView, or task/list editor modal — the root coordinator is
|
|
`IslandsShellViewModel`, and task/list editing happens inline in the islands.
|
|
|
|
```
|
|
ViewModels/
|
|
IslandsShellViewModel.cs — root coordinator
|
|
Islands/ — ListsIsland, TasksIsland, DetailsIsland, TaskRow, ListNavItem,
|
|
NotesEditor, MergePreviewPresenter
|
|
Agent/ — AgentConfigEditorViewModel (scope-parameterized: List | Task)
|
|
Modals/ — About, DiffViewer (+ DiffModels), ListSettings, Merge, MergeHelperSelection,
|
|
RepoImport, Settings (+ Settings/ tab VMs), UnfinishedPlanning, WeeklyReport,
|
|
WorkerConnection, WorktreesOverview, UnifiedDiffParser
|
|
Conflicts/ — ConflictResolverViewModel + ConflictModels
|
|
Views/ — mirrors the VM layout; Islands/Detail/ holds TaskHeaderBar,
|
|
DescriptionStepsCard, WorkConsole; plus AgentStripView, SessionTerminalView
|
|
Views/Controls/ — MarkdownView, ModalShell, ThemedDatePicker, DiffLinesView, InheritedBadge,
|
|
AgentConfigEditor
|
|
Design/ — Tokens.axaml (design tokens; merged before styles)
|
|
+ IslandStyles.axaml (component styles + the filled icon geometry library)
|
|
```
|
|
|
|
## Core ViewModels
|
|
|
|
- **IslandsShellViewModel** — root coordinator. Owns the three island VMs and the `WorkerClient`, wires cross-island events (selection, notes/prep mode, conflict resolution), connection state, the update banner, the inline worker-log strip (clickable → Log Visualizer overlay; `FlashFooterError` surfaces UI-action failures + the worker's Warn/Error there), responsive-layout flags (`ShowLists`/`ShowDetails` by window width), `PrimeStatus` flash, the modal openers, and `RestartWorkerAsync`/`CheckForUpdatesAsync`. Hosts `UpdateCheckService`.
|
|
- **ListsIslandViewModel** — smart lists (My Day, Important, Planned, virtual queued/running/review), user lists, selection, list CRUD, drag-reorder, badge counts, opens list settings / repo import / worktrees overview, `OpenInExplorer`/`OpenInTerminal`.
|
|
- **TasksIslandViewModel** — open/overdue/completed groups for the selected list with hierarchy-aware regrouping; task CRUD, drag-reorder, toggle done/star, schedule, enqueue/dequeue, cancel; review actions; planning session lifecycle; `RefineTask`, `OpenConPtySessionRequested`, `ToggleManual`, `SyncInteractiveSessions`; MyDay extras (`IsMyDayList`, `ClearDayCommand`, `ShowPrepLogCommand`) and the pinned Notes pseudo-row. Raises `NotesRequested`/`PrepRequested` for the shell.
|
|
- **DetailsIslandViewModel** — the detail pane for a bound `TaskRowViewModel`. Owns live-log streaming (`Log` via `StreamLineFormatter`), debounced title/description editing, subtasks, the session-outcome/roadblock split, the three-tab work console (`output`/`git`/`session`), child surfacing (`ChildOutcomes` + `ChildrenNeedingAttention` drive an attention band on the Session tab), attachments, and the modes `IsNotesMode`/`IsPrepMode`/computed `IsTaskDetailVisible`. Failures raise `ErrorReported`, wired by the shell into `FlashFooterError`.
|
|
- Three concerns are extracted into section VMs exposed as properties: `AgentSettings` (`AgentConfigEditorViewModel`, scope=Task), `MergeSectionViewModel`, `PrepPanelViewModel`. Helper rows live in the same file.
|
|
- The ROADBLOCK card's reply field (`RoadblockReplyDraft`/`SendRoadblockReplyCommand`, gated by `CanReplyToRoadblock` on `LatestRunSessionId`) resumes the session over the same `ContinueTaskAsync` transport as `ContinueCommand`, but with the user's own text.
|
|
- Attachments write directly via `new AttachmentStore()` + `new TaskAttachmentRepository(ctx)`; `ComposedPreview` includes attachment paths.
|
|
- **TaskRowViewModel** / **ListNavItemViewModel** — lightweight display VMs. Task row: status, planning phase, parent/blocked links, roadblock count, computed `IsDraft`/`IsPlanned`/`IsChild`/`IsPlanningParent`/`CanRefine`, plus `IsManual` (→ MANUAL badge; suppresses `CanSendToQueue`/`CanRefine`/`CanOpenPlanningSession`) and `HasInteractiveSession` (→ accent "Interactive" chip instead of "Parked"; tapping jumps to that Mission Control pane). List row: kind Smart/Virtual/User, count, icon/dot keys, drop hints, `IsManual`.
|
|
- **NotesEditorViewModel** — day navigator + bullet CRUD via `INotesApi`.
|
|
- **UsagePillViewModel** — one shared instance backs the `UsagePill` in both the footer and the Mission Control header → [usage-monitoring](../../docs/explore-notes/usage-monitoring.md).
|
|
|
|
## Modal VMs
|
|
|
|
| VM | Notes |
|
|
|---|---|
|
|
| `SettingsModalViewModel` | Four tabs: General, Worktrees, Files (prompt paths), Prime Claude. General hosts the per-model preset table (`ModelPresetRowViewModel`: effort + max turns per alias) which **replaced** the single global "Max turns" field. |
|
|
| `ListSettingsModalViewModel` | Name, working dir, commit type, "manual list" flag, `VerifyCommand`, delete. Hosts the shared `AgentConfigEditorViewModel` as `Agent` (scope=List) — ⚠️ save delegates to `Agent.SaveAsync(verifyCommand)` because both land in the same `list_config` row via one `UpdateListConfig` call and would otherwise clobber each other. |
|
|
| `WeeklyReportModalViewModel` | Range pickers default "since last standup weekday → today", cached per range. |
|
|
| `MergeHelperSelectionModalViewModel` | "Let Claude handle it" picker → [conpty-sessions](../../docs/explore-notes/conpty-sessions.md). |
|
|
| `UsageMonitorModalViewModel` | Opened from the usage pill; gauges are **dynamic** per `UsageSnapshotDto.Limits` row. |
|
|
|
|
Self-explanatory: `RepoImportModalViewModel` (bulk-create lists from git repos; already-wired
|
|
repos disabled), `MergeModalViewModel`, `WorktreesOverviewModalViewModel`,
|
|
`UnfinishedPlanningModalViewModel`, `LogVisualizerViewModel` (last 30 min, all levels + a
|
|
warn/error filter), `WorkerConnectionModalViewModel`, `AboutModalViewModel`.
|
|
|
|
## Diff & Conflicts
|
|
|
|
`UnifiedDiffParser` (static) + `DiffModels.cs` shared types + `DiffViewerViewModel` (one unified
|
|
read-only viewer, Files and Planning modes) + `DiffLinesView`.
|
|
`ConflictResolverViewModel` is an in-app Rider-style 3-pane AvaloniaEdit merge editor for both
|
|
single-task and planning unit-merge conflicts. Full detail →
|
|
[review-merge](../../docs/explore-notes/review-merge.md).
|
|
|
|
## Services
|
|
|
|
- **WorkerClient / IWorkerClient** — SignalR client on `http://127.0.0.1:47821/hub`, auto-reconnect with exponential backoff. The surface **tracks `WorkerHub`** — treat `src/ClaudeDo.Worker/Hub/WorkerHub.cs` as the canonical method list rather than duplicating it here. Events mirror `HubBroadcaster`. Lifecycle (`StartAsync`/`StopAsync`) and a few admin methods live only on the concrete `WorkerClient`.
|
|
- **INotesApi / WorkerNotesApi** — daily-note CRUD; UI DTO `DailyNoteDto(Id, Date, Text, SortOrder)`.
|
|
- **IPrimeScheduleApi** — prime-schedule CRUD.
|
|
- **UpdateCheckService** — polls releases; `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` feed the shell's update banner.
|
|
- **InheritanceResolver** — resolves the task → list → global override chain to `(value, source)` for the inherited badges.
|
|
- **RepoScanner**, **InstallArtifactLocator**/**InstallerLocator**/**WorkerLocator**, **ForegroundHelper** (Win32 foreground before launching a terminal), **FocusClearing**.
|
|
|
|
## Converters
|
|
|
|
In `Converters/` — grep rather than list: log-level brush, dot brush,
|
|
status equality, icon key, strike/italic/opacity toggles, null→bool,
|
|
uppercase.
|
|
|
|
## Dialog Pattern
|
|
|
|
Modals use `TaskCompletionSource` results behind the reusable `ModalShell` control — the dialog
|
|
sets the result on save/cancel, the caller awaits the TCS.
|
|
|
|
## Gotchas
|
|
|
|
- **`PathIcon` *fills* its geometry.** Line-art/stroke icons must be authored as filled geometry or rendered with a stroked `Path` (e.g. `Icon.PlanDay` via the `Path.plan-icon` style). A pure stroke path in a `PathIcon` is **invisible**.
|
|
- **`NumericUpDown.Value` is `decimal?` and goes null while the box is empty** — i.e. every time the user clears a value to type a new one. Bound TwoWay to a non-nullable `int`/`decimal`, that null throws `InvalidCastException`. Either bind a `decimal?` property (as `AgentConfigEditorViewModel.MaxTurns` does) or add `Converter={StaticResource KeepLastNumber}`, which drops the null via `BindingOperations.DoNothing`.
|
|
- **Never bind bare punctuation gestures.** Window key bindings live on `MainWindow` (`Ctrl+K` search, `Ctrl+N` add-task). `OemQuestion` once held search focus and silently swallowed `#` app-wide on a German layout.
|
|
- **`FocusClearing`'s Escape handler is scoped to `MainWindow`** (`AddClassHandler<MainWindow>`, not `<TopLevel>`) — it clears focus from a TextBox on Escape, mirroring click-outside. Modals are separate `Window` instances that bind their own Escape → close, so it never runs there. Mission Control's ConPTY tiles are in `MissionControlWindow`, also unaffected, so **Escape always reaches the PTY**.
|
|
- **Review gate:** Approve & Merge stays disabled until the diff has been opened once, and re-locks per run → [review-merge](../../docs/explore-notes/review-merge.md).
|
|
- Context menus exist on both list and task rows; right-click selects before opening the menu.
|
|
- "Run Now" CanExecute re-evaluates when worker connection state changes.
|
|
- `Ellipse.spinner` (IslandStyles) is the shared indeterminate spinner (starting ConPTY pane, refining task row).
|
|
- `SessionTerminalView` is the reusable log terminal (StyledProperties `Entries`, `Label`, `IsRunning`, `IsDone`, `IsFailed`) — used for both the task `Log` and the prep `PrepLog`.
|
|
- `DetailsIslandView` is a pane-wide drag-and-drop file target (`DragDrop.AllowDrop`, Avalonia 12 `DataFormat.File`) with a "Drop to attach" overlay; `DescriptionStepsCard` shows the attachments list, an "Add file…" picker, and an explicit `DropStatus` line. Keys use the `details.attachments.*` locale namespace (en + de).
|