Files
ClaudeDo/src/ClaudeDo.Ui/CLAUDE.md
Mika Kuns 8716dd8e3a docs(attachments): document task file attachments across project docs
Data/Worker/Ui CLAUDE.md + docs/open.md updated for TaskAttachmentEntity,
AttachmentStore, AttachmentMcpTools, AttachmentOrphanRecovery, the run-prompt
injection, and the detail-pane drag-and-drop UI (incl. a manual verification
item).
2026-06-26 16:11:48 +02:00

13 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
  Modals/     — About, Diff, ListSettings, Merge, RepoImport, Settings (+ Settings/ tab VMs),
                UnfinishedPlanning, WeeklyReport, WorkerConnection, Worktree,
                WorktreesOverview, UnifiedDiffParser
  Planning/   — PlanningDiffViewModel
  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
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, responsive-layout flags (ShowLists/ShowDetails by window width), PrimeStatus flash, and the modal openers (About, RepoImport, WeeklyReport, WorktreesOverview, WorkerConnection help) 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); RunInteractivelyAsync, RefineTask; 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: AgentSettingsSectionViewModel (per-task Model/MaxTurns/AgentPath overrides with InheritedBadge + InheritanceResolver, additive SystemPrompt, debounced save), MergeSectionViewModel (merge-target selection, mergeability indicator via MergePreviewPresenter over PreviewMergeAsync, OpenDiffAsync — live worktree or commit range after merge —, ReviewCombinedDiffCommandPlanningDiffViewModel), 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; list row: kind Smart/Virtual/User, count, icon/dot keys, drop hints).
  • 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), ListSettingsModalViewModel (name, working dir, commit type, per-list Model/SystemPrompt/AgentPath/MaxTurns with inherited-badge + reset, delete list), 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), WorkerConnectionModalViewModel (offline help), AboutModalViewModel.
  • 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). DiffModalViewModel has two modes: live worktree (branch diff vs base, with a Merge action) and commit-range base..head (FromCommitRange = true — shows a merged task's diff after its worktree is gone; no merge action). The view renders a file list with binary/empty placeholders via DiffLinesView.
  • Planning/ConflictsPlanningDiffViewModel (per-subtask diffs via GetPlanningAggregateAsync, toggle to combined integration-branch diff, conflict warnings), ConflictResolverViewModel (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, interactive terminal, refine), 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. 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), WorktreeStateColorConverter, 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.
  • 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).