Files
ClaudeDo/src/ClaudeDo.Ui/CLAUDE.md
T
Mika Kuns 24f999facd
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 38s
docs: record the manual-task, effort-preset and chip/spinner changes
2026-07-27 15:02:51 +02:00

15 KiB
Raw Blame History

ClaudeDo.Ui

Avalonia UI layer: views, viewmodels, converters, and the SignalR client.

Pattern

MVVM with CommunityToolkit.Mvvm source generators:

  • [ObservableProperty] for bindable properties
  • [RelayCommand] for commands (supports async and CanExecute)
  • 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 (MergeFile/MergeFileSegment/MergeConflictBlock)
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)

ViewModels

  • IslandsShellViewModel — root coordinator; owns the three island VMs and the WorkerClient, wires cross-island events (selection, notes/prep mode, conflict resolution), owns connection state, the update banner, the inline worker-log strip (clickable → Log Visualizer overlay via OpenLogVisualizerCommand; FlashFooterError surfaces UI-action failures + the worker's Serilog Warn/Error there), responsive-layout flags (ShowLists/ShowDetails by window width), PrimeStatus flash, and the modal openers (About, RepoImport, WeeklyReport, WorktreesOverview, WorkerConnection help, LogVisualizer) plus 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 (approve, reject-rerun, reject-park, cancel); planning session lifecycle (open/resume/discard/finalize, QueuePlanningSubtasksAsync); RefineTask, OpenConPtySessionRequested (embedded ConPTY terminal), PickUpInTerminalAsync, ToggleManual (per-task manual flag) and SyncInteractiveSessions (mirrors Mission Control's open ConPTY panes onto the rows); MyDay extras (IsMyDayList, ClearDayCommand, ShowPrepLogCommand) and the pinned Notes pseudo-row (ShowNotesRow, OpenNotesCommand). Raises NotesRequested/PrepRequested events consumed by the shell.
  • DetailsIslandViewModel — the detail pane for a bound TaskRowViewModel. Owns live-log streaming (Log via StreamLineFormatter), debounced title/description editing, subtasks, session-outcome/roadblock split (splits Result at the roadblock marker into two cards), the three-tab work console (output/git/session), child surfacing (ChildOutcomes rows plus ChildrenNeedingAttention/HasChildrenNeedingAttention — children that failed, were cancelled, await review, or reported roadblocks — drive an attention band on the Session tab, which is only visible when HasChildOutcomes), and the modes: IsNotesMode (hosts NotesEditorViewModel), IsPrepMode, computed IsTaskDetailVisible = !IsNotesMode && !IsPrepMode. Three concerns are extracted into section VMs exposed as properties: AgentConfigEditorViewModel (scope=Task; per-task Model/MaxTurns/AgentPath overrides with InheritedBadge + InheritanceResolver, additive SystemPrompt, debounced auto-save; exposed as AgentSettings), MergeSectionViewModel (merge-target selection, mergeability indicator via MergePreviewPresenter over PreviewMergeAsync, OpenDiffAsync and ReviewCombinedDiffCommand — both build a DiffViewerViewModel, call ShowDiffViewer, and fire the DiffViewed callback; HasReviewableDiff reports whether anything is inspectable, feeding the review gate), PrepPanelViewModel (daily-prep panel: PrepLog, PlanDayCommandRunDailyPrepNowAsync, persisted last run via GetLastPrepLogAsync). Attachments: Attachments (ObservableCollection<AttachmentRowViewModel>), IsDragOver, DropStatus, CanAcceptDrop, AddFilesAsync, RemoveAttachmentCommand; loads on task change; ComposedPreview includes attachment paths. Writes directly via new AttachmentStore() + new TaskAttachmentRepository(ctx). Helper rows (ChildOutcomeRowViewModel, SubtaskRowViewModel, LogLineViewModel, AttachmentRowViewModel) live in the same file.
  • 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 it jumps to that Mission Control pane); list row: kind Smart/Virtual/User, count, icon/dot keys, drop hints, IsManual).
  • NotesEditorViewModel — day navigator + bullet CRUD for daily notes via INotesApi.
  • Modal VMsSettingsModalViewModel (four tabs: General, Worktrees, Files prompt-paths, Prime Claude incl. DailyPrepMaxTasks + prime-schedule rows). General hosts the per-model preset table (ModelPresetsModelPresetRowViewModel: effort + max turns per alias) which replaced the single global "Max turns" field, ListSettingsModalViewModel (name, working dir, commit type, "manual list" flag, delete list; hosts shared AgentConfigEditorViewModel as Agent property (scope=List) — save delegates to Agent.SaveAsync()), RepoImportModalViewModel (bulk-create lists from git repos found under chosen parents; already-wired repos disabled), WeeklyReportModalViewModel (range pickers default "since last standup weekday → today", cached per range, markdown via MarkdownView), MergeModalViewModel (single-task merge form, called from the diff modal), WorktreesOverviewModalViewModel (global/per-list worktree rows, batch merge + state ops), UnfinishedPlanningModalViewModel (Resume/FinalizeNow/Discard for a draft planning session), MergeHelperSelectionModalViewModel ("Let Claude handle it": checkbox picker over one list's non-terminal, non-manual tasks, pre-ticks the actionable ones; list-scoped only — Configure(listId, listName), no global scope. Opened from the list row's context menu, which is hidden when the list has no working dir; on confirm ListsIslandViewModel raises LetClaudeHandleRequested → shell → MissionControlViewModel.OpenMergeHelperConPtySessionAsync opens an ad-hoc ConPTY tile running the five-phase handler prompt), WorkerConnectionModalViewModel (offline help), AboutModalViewModel, LogVisualizerViewModel (worker logs, last 30 min, all levels + a warn/error-only filter; loads via GetRecentLogsAsync).
  • Diff stackUnifiedDiffParser (static; parses git diff output into DiffFileViewModels, detecting added/deleted/renamed/binary files and per-line numbers; Flatten injects file-header rows for a combined single-pane view). DiffModels.cs holds shared types: DiffLineViewModel, DiffFileViewModel, DiffLineKind, DiffFileStatus, SubtaskDiffRow, DiffTreeNodeViewModel, DiffTree. DiffViewerViewModel is a single unified read-only diff viewer with two modes: Files (dirty worktree / branch-vs-base / commit-range — loads via GitService, shows a folder file-tree on the left + per-file diff pane on the right, Merge button for live branch source) and Planning (per-subtask diffs via GetPlanningAggregateAsync, subtask list left + flat diff right, combined integration-branch toggle). The Merge button opens the merge form, which routes to ConflictResolverViewModel on conflict. DiffLinesView renders per-file diff content with binary/empty placeholders.
  • ConflictsConflictResolverViewModel (in-app Rider-style 3-pane merge editor for both single-task and planning unit-merge conflicts: single-task starts the conflict merge, parses each conflicted file into stable/conflict MergeFileSegments via the worker's GetMergeConflictDocuments; exposes the active file's three reconstructed documents — ActiveOursText / ActiveResultText / ActiveTheirsText (from MergeFile.OursText/ResultText/TheirsText; Result seeds unresolved conflicts with Ours) — plus ActiveFile/SelectFileCommand (multi-file switcher), Current/Next/Previous (focused-conflict nav), a per-active-file PositionText readout, per-block AcceptOurs/Theirs/Both/Base + MergeFile.Compose, and CanContinue gated on every file resolved + no binary; writes each file via WriteConflictResolution, continue/abort; planning mode via OpenForPlanningAsync(parentId, subtaskId) loads the current subtask's mid-merge conflicts without re-starting the merge and routes continue/abort to ContinuePlanningMerge/AbortPlanningMerge, so a unit-merge conflict re-opens the editor per subtask via the PlanningMergeConflict broadcast). The view (Views/Conflicts/ConflictResolverView) shows the whole file in three AvaloniaEdit panes — MAIN/ours (read-only) | editable Result | INCOMING/theirs (read-only) — with TextMate highlighting by extension (theme StyleInclude in App.axaml); a code-behind IBackgroundRenderer tints each conflict block (unresolved/resolved) across panes, an IReadOnlySectionProvider + TextAnchor regions keep only conflict spans editable in Result (edits flow back to the block); each unresolved conflict starts EMPTY (a thin marker bar); the between-pane gutter controls toggle each side in/out of the result — / add MAIN/INCOMING in click order (first pick on top), clicking again removes that side — so a conflict can take main, incoming, both, or neither; a FilesSummary readout shows how many files still have conflicts, and the three panes share a proportional synced vertical scroll. A conflict overview ruler right of the Result pane (ConflictMap) maps every conflict in the file proportionally (click a tick to jump) — handy for long files. Conflict block tints live in Tokens.axaml (Merge*TintBrush). The editor is reached from review Approve on conflict and from the Merge button in the Diff window (a conflicting MergeTask hands off to the resolver via RequestConflictResolution).

Services

  • WorkerClient / IWorkerClient — SignalR client connecting to http://127.0.0.1:47821/hub, auto-reconnect with exponential backoff. The surface tracks WorkerHub (see src/ClaudeDo.Worker/CLAUDE.md for the canonical method/event list); groups: task execution (RunNow/Cancel/Continue/Reset/SetTaskStatus), review (ApproveReviewAsync(taskId, targetBranch) -> MergeResultDto, reject-to-queue/idle, cancel review, PreviewMergeAsync -> MergePreviewDto), planning sessions (start/resume/discard/finalize, queue subtasks, pending draft count, refine), pick-up-in-terminal + embedded ConPTY launch specs (GetInteractiveLaunchSpecAsync/GetAdHocLaunchSpecAsync), planning aggregate/integration-branch diffs, unit-merge continue/abort, single-task conflict resolving (start/get-conflict-documents/write-resolution/continue/abort), worktrees (overview, set state, force remove, cleanup, reset all), agents, app settings, lists/config, weekly report, daily notes, daily prep (RunDailyPrepNowAsync, ClearMyDayAsync, GetLastPrepLogAsync), prime schedules, recent worker logs (GetRecentLogsAsync). Events mirror HubBroadcaster (task/worktree/list/run updates, prep events, planning-merge events, refine events, worker log). Lifecycle (StartAsync/StopAsync) and a few admin methods live only on the concrete WorkerClient.
  • INotesApi / WorkerNotesApi — daily-note CRUD (ListAsync(day), AddAsync, UpdateAsync, DeleteAsync); UI DTO DailyNoteDto(Id, Date, Text, SortOrder).
  • IPrimeScheduleApi — prime-schedule CRUD (ListAsync, UpsertAsync, DeleteAsync).
  • UpdateCheckService — polls releases, exposes LastCheckStatus/LatestVersion/CheckNowAsync (feeds 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

StatusColorConverter (+ ConnectionColorConverter in the same file), WorkerLogLevelToBrushConverter, DotBrushConverter, EqStatusConverter, IconKeyConverter, CheckboxBorderConverter, StrikeIfTrueConverter, BoolToItalicConverter, BoolToDraftOpacityConverter, NotNullToBoolConverter, UpperCaseConverter, DateOnlyToDateTimeConverter.

Dialog Pattern

Modals use TaskCompletionSource results behind the reusable ModalShell control — the dialog sets the result on save/cancel, and the caller awaits the TCS.

Notes

  • Context menus exist on both list rows and task rows; right-click selects before opening the menu
  • "Run Now" CanExecute re-evaluates when worker connection state changes
  • Icon gotcha: PathIcon fills geometry. Line-art/stroke icons must be defined as filled geometry or rendered as a stroked Path (e.g. Icon.PlanDay via the Path.plan-icon style); a pure stroke path used with PathIcon is invisible.
  • Window key bindings live on MainWindow: Ctrl+K focuses search, Ctrl+N the add-task box. Do not bind bare punctuation gestures — OemQuestion used to hold search focus and silently swallowed # app-wide on a German layout.
  • Ellipse.spinner (IslandStyles) is the shared indeterminate spinner: used for a starting ConPTY pane (InteractiveTerminalViewModel.IsStarting) and in place of the refine button while TaskRowViewModel.IsRefining.
  • ConPtyPaneViewModel resolves its own launch spec (ctor takes a descriptor factory; the host wires handlers and then calls Start()), so the Mission Control tile appears immediately with its spinner while the worker is still preparing the worktree. A failed launch keeps the tile with its inline error banner instead of never appearing.
  • 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" hover overlay. DescriptionStepsCard shows an Attachments list (file name, size, remove button), an "Add file…" picker, and an explicit DropStatus confirmation line. Keys use the details.attachments.* localization namespace (en + de).