Files
ClaudeDo/src/ClaudeDo.Ui/CLAUDE.md
T

10 KiB

ClaudeDo.Ui

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

Deeper detail: review-merge (diff stack + conflict resolver) · conpty-sessions (Mission Control tiles) · usage-monitoring (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.

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.
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.

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.
  • 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).