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).
13 KiB
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(extendsObservableObject) - 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/ShowDetailsby window width),PrimeStatusflash, and the modal openers (About, RepoImport, WeeklyReport, WorktreesOverview, WorkerConnection help) plusRestartWorkerAsync/CheckForUpdatesAsync. HostsUpdateCheckService. - 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). RaisesNotesRequested/PrepRequestedevents consumed by the shell. - DetailsIslandViewModel — the detail pane for a bound
TaskRowViewModel. Owns live-log streaming (LogviaStreamLineFormatter), debounced title/description editing, subtasks, session-outcome/roadblock split (splitsResultat the roadblock marker into two cards), the three-tab work console (output/git/session), child surfacing (ChildOutcomesrows plusChildrenNeedingAttention/HasChildrenNeedingAttention— children that failed, were cancelled, await review, or reported roadblocks — drive an attention band on the Session tab, which is only visible whenHasChildOutcomes), and the modes:IsNotesMode(hostsNotesEditorViewModel),IsPrepMode, computedIsTaskDetailVisible = !IsNotesMode && !IsPrepMode. Three concerns are extracted into section VMs exposed as properties: AgentSettingsSectionViewModel (per-task Model/MaxTurns/AgentPath overrides withInheritedBadge+InheritanceResolver, additive SystemPrompt, debounced save), MergeSectionViewModel (merge-target selection, mergeability indicator viaMergePreviewPresenteroverPreviewMergeAsync,OpenDiffAsync— live worktree or commit range after merge —,ReviewCombinedDiffCommand→PlanningDiffViewModel), PrepPanelViewModel (daily-prep panel:PrepLog,PlanDayCommand→RunDailyPrepNowAsync, persisted last run viaGetLastPrepLogAsync). Attachments:Attachments(ObservableCollection<AttachmentRowViewModel>),IsDragOver,DropStatus,CanAcceptDrop,AddFilesAsync,RemoveAttachmentCommand; loads on task change;ComposedPreviewincludes attachment paths. Writes directly vianew 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 VMs —
SettingsModalViewModel(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 stack —
UnifiedDiffParser(static; parsesgit diffoutput intoDiffFileViewModels, detecting added/deleted/renamed/binary files and per-line numbers;Flatteninjects file-header rows for a combined single-pane view).DiffModalViewModelhas two modes: live worktree (branch diff vs base, with a Merge action) and commit-rangebase..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 viaDiffLinesView. - Planning/Conflicts —
PlanningDiffViewModel(per-subtask diffs viaGetPlanningAggregateAsync, 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/conflictMergeFileSegments via the worker'sGetMergeConflictDocuments; exposes the active file's three reconstructed documents —ActiveOursText/ActiveResultText/ActiveTheirsText(fromMergeFile.OursText/ResultText/TheirsText; Result seeds unresolved conflicts with Ours) — plusActiveFile/SelectFileCommand(multi-file switcher),Current/Next/Previous(focused-conflict nav), a per-active-filePositionTextreadout, per-blockAcceptOurs/Theirs/Both/Base+MergeFile.Compose, andCanContinuegated on every file resolved + no binary; writes each file viaWriteConflictResolution, continue/abort; planning mode viaOpenForPlanningAsync(parentId, subtaskId)loads the current subtask's mid-merge conflicts without re-starting the merge and routes continue/abort toContinuePlanningMerge/AbortPlanningMerge, so a unit-merge conflict re-opens the editor per subtask via thePlanningMergeConflictbroadcast). 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 (themeStyleIncludeinApp.axaml); a code-behindIBackgroundRenderertints each conflict block (unresolved/resolved) across panes, anIReadOnlySectionProvider+TextAnchorregions 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; aFilesSummaryreadout 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 inTokens.axaml(Merge*TintBrush). The editor is reached from review Approve on conflict and from the Merge button in the Diff window (a conflictingMergeTaskhands off to the resolver viaRequestConflictResolution).
Services
- WorkerClient / IWorkerClient — SignalR client connecting to
http://127.0.0.1:47821/hub, auto-reconnect with exponential backoff. The surface tracksWorkerHub(seesrc/ClaudeDo.Worker/CLAUDE.mdfor 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 mirrorHubBroadcaster(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 concreteWorkerClient. - INotesApi / WorkerNotesApi — daily-note CRUD (
ListAsync(day),AddAsync,UpdateAsync,DeleteAsync); UI DTODailyNoteDto(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:
PathIconfills geometry. Line-art/stroke icons must be defined as filled geometry or rendered as a strokedPath(e.g.Icon.PlanDayvia thePath.plan-iconstyle); a pure stroke path used withPathIconis invisible. SessionTerminalViewis the reusable log terminal (StyledPropertiesEntries,Label,IsRunning,IsDone,IsFailed) used for both the taskLogand the prepPrepLog.DetailsIslandViewis a pane-wide drag-and-drop file target (DragDrop.AllowDrop, Avalonia 12DataFormat.File) with a "Drop to attach" hover overlay.DescriptionStepsCardshows an Attachments list (file name, size, remove button), an "Add file…" picker, and an explicitDropStatusconfirmation line. Keys use thedetails.attachments.*localization namespace (en + de).