220 Commits
Author SHA1 Message Date
Mika Kuns 43fbfd3fa2 feat(ui): gate OperationTiming.Shared behind a kill switch, off by default
Its job (surfacing the DetailsIsland.BindAsync churn) is done; leave the
InvokeTimedAsync wrappers and Island Record call sites in place as a
chokepoint for next time, but stop writing by default. Shared now only
writes when CLAUDEDO_OP_TIMING=1 is set; the constructor keeps enabled=true
so both TestSetup redirects and OperationTimingTests are unaffected.
2026-08-17 08:30:02 +02:00
Mika Kuns ec9ad1b0e1 chore(claude-do): merge Bind-Churn: DetailsIsland.BindAsync feuert ~1900x/Tag, 91-96
ClaudeDo-Task: 351cacc4-3e11-4b3e-a89e-4efc4bd0448e
2026-08-13 09:00:32 +02:00
Mika Kuns c1ec05a437 fix(ui): stop tests from polluting the live operation-timing.ndjson
DetailsIsland.BindAsync churn (~1900 binds/day, 91-96% cancelled) was not a
UI reactivity bug: OperationTiming.Shared is a static singleton hardcoded to
~/.todo-app/logs/operation-timing.ndjson, and Ui.Tests/Worker.Tests both
construct real DetailsIslandViewModel/TasksIslandViewModel instances that
call Shared.Record directly. Every dotnet test run appended 50-100 lines
straight into the live app's log — pid-burst analysis showed 13 distinct
test-run pids in the ":?" bucket (tests never pass a source) plus a
no-suffix bucket with no pid field at all (pre-dating the pid-per-line
feature). Real user-driven binds that day: 4.

Made Shared settable and added a [ModuleInitializer]-based TestSetup in
both test projects that redirects it to a per-process temp file before any
test runs.
2026-08-13 08:35:09 +02:00
Mika Kuns b8caa27027 feat(worker): add generic OperationProgress channel, port MergeProgress onto it
Merge/verify phases now broadcast over a generic (opKey, phase, current,
total) Hub event instead of a merge-specific one, so future producers
(worktree cleanup, startup recovery, planning integration) can reuse it.
IWorkerClient.MergeProgressEvent stays as a thin forwarder for existing
consumers (elapsed seconds riding in the generic "current" slot).
2026-08-13 08:31:23 +02:00
Mika Kuns c795827162 test(ui): [B3] guard against a synchronous diff parse blocking the dispatcher
Adds DiffDispatcherGuardTests, the one blockade-regression test in the
operation-feedback effort. Uses a queueing SynchronizationContext (Option B —
this project has no bootstrapped Avalonia dispatcher anywhere) to prove
DiffViewerViewModel's UnifiedDiffParser.Parse offload ([B1]) truly defers past
the calling thread rather than racing on timing. [B2]'s DiffAlignment.Build
offload in DiffTextView is deliberately not covered — three escalating
attempts to construct that control safely in-process each broke under the
full test suite (dispatcher thread-affinity crashes, then process-wide
Application state polluting an unrelated test), and Avalonia.Headless.XUnit
collides with this project's xUnit v2 stack. Verified the test fails against
the pre-[B1] code and passes with it; full suite green aside from a
pre-existing, unrelated flaky test.
2026-08-12 14:55:37 +02:00
Mika Kuns ea4db9d0bd chore(claude-do): merge [B1] UnifiedDiffParser.Parse vom UI-Thread nehmen (DiffViewe
ClaudeDo-Task: e2003fc8-f029-44d0-a66a-51b57a95583f
2026-08-12 13:51:18 +02:00
mika kuns 589b9e75f3 fix(ui): unfreeze the operation spinner and attribute rebind churn
Three causes behind the stuck indicator:
- OperationStatus.End did not retire the generation, so tick work posted
  while the dispatcher was blocked drained afterwards and set
  ShowIndicator back to true on a finished operation.
- The startup update check ran in Task.Run; OperationStatus writes its
  observable properties on the calling thread, so the final
  ShowIndicator=false never reached the binding.
- OperationIndicator overwrote its own DataContext from the Status
  property, which re-targeted the call-site binding. It now scopes the
  DataContext to an inner panel and collapses itself when Status is
  null; every call site switched from DataContext= to Status=.

Also gates the footer connection pill in the command body instead of
CanExecute (a disabled command greyed out the ONLINE chip), and extends
OperationTiming: pid per line, 4 MB rollover, fast successes dropped
(failures always kept), and DetailsIsland.BindAsync now carries the
selection trigger via TasksIslandViewModel.SelectFrom.
2026-08-12 13:50:05 +02:00
Mika Kuns f6994f32d5 fix(ui): show ParseOp indicator on the planning-mode diff parse too
ParseOp only covered LoadFilesAsync; OnDisplayedDiffChanged's parse (driven
by ToggleCombinedAsync and OnSelectedSubtaskChanged) had no visible
feedback even though the offload was already correct. Both call sites now
go through one ParseOffUiThreadAsync helper (Begin on the UI thread before
Task.Run, End via `using` regardless of a stale generation), so the
already-visible toolbar indicator covers the planning path without
duplicating the begin/offload/end ceremony.
2026-08-12 13:41:05 +02:00
Mika Kuns 9fc8ed391e fix(ui): offload UnifiedDiffParser.Parse off the UI thread in DiffViewerViewModel
Both call sites (LoadFilesAsync, OnDisplayedDiffChanged) ran the parser
synchronously on the dispatcher, freezing the window on a large diff. The
Files-mode parse+tree-build now runs via Task.Run behind a ParseOp
OperationStatus wired to the toolbar; the planning-mode parse is
fire-and-forget with a generation counter so a fast second DisplayedDiff
change can't write stale PlanningFiles.
2026-08-12 13:19:20 +02:00
mika kuns b9827eac01 fix(ui): connection pill opens worker help only when disconnected
The footer pill fired OpenWorkerConnectionHelpCommand unconditionally, so
clicking it on a connected worker showed the "WORKER NOT REACHABLE" dialog.
Gate the command on !IsConnected (not IsOffline — the retry loop stays in
"connecting" forever while the worker is down, which is exactly when the
dialog's "Start Worker" is needed) and re-evaluate CanExecute on connection
state changes.
2026-08-12 10:58:10 +02:00
Mika Kuns 69b07fff26 chore(claude-do): merge [A4] WeeklyReport, DailyPrep und Planning-Aktionen auf Opera
ClaudeDo-Task: 7c6f2f66-8cd7-4abb-8151-aed42ae739b5
2026-08-12 10:10:57 +02:00
Mika Kuns ec4cbcbaf9 chore(claude-do): [A3] Settings + Update-Check: OperationStatus für Skill-Inst
Vorgaben: `docs/superpowers/plans/2026-08-11-operation-feedback.md`, Gruppe A, Entwurf A3. P0-1 ist gemergt: `src/ClaudeDo.Ui/Services/OperationStatus.cs` + `src/ClaudeDo.Ui/Views/Controls/OperationIndicator.axaml`.

SCOPE-ÄNDERUNG (Nutzer, 2026-08-12): OnlineInbox Sign-In/Sign-Out ist AUS DEM SCOPE GENOMMEN — vorerst unwichtig. `ViewModels/Modals/Settings/OnlineInboxSettingsViewModel.cs` und die

ClaudeDo-Task: 2356806f-f16e-4652-b615-4562e95c3a65
2026-08-12 09:55:04 +02:00
Mika Kuns 5fca459579 chore(claude-do): merge [A2] WorktreesOverview + Reset-All: getrennte OperationStatu
ClaudeDo-Task: cec07e2c-2814-4a43-898c-a6be07a13f01
2026-08-12 09:54:58 +02:00
Mika Kuns bea85a9ee5 feat(ui): route detail-pane review actions through OperationStatus
Approve, Submit, Reject, and Park each get their own OperationStatus so the
button locks and shows an OperationIndicator for the duration of the call;
Approve additionally scopes a MergeProgressEvent subscription to its own
task id to sharpen the label from "merging" to "verifying" mid-flight.
MergeSectionViewModel's preview refresh gets the same treatment for display
only, since it has no command button to gate.
2026-08-12 09:42:15 +02:00
Mika Kuns bbd6151ba6 feat(claude-do): [A4] WeeklyReport, DailyPrep und Planning-Aktionen auf OperationStatus umstellen
WeeklyReportModalViewModel.Generate, PrepPanelViewModel.PlanDayAsync und die beiden
Planning-Aktionen (QueuePlanningSubtasksAsync, FinalizePlanningSessionAsync) laufen jetzt
durch je eine OperationStatus + OperationIndicator statt handgebauter Spinner. Die beiden
Planning-Aktionen teilen sich eine Instanz, angezeigt im Tasks-Island-Header, weil sie aus
einem sofort schliessenden Kontextmenue ausgeloest werden und es keine dauerhafte
Pro-Zeilen-Flaeche gibt, an die ein Indikator gehaengt werden koennte.
2026-08-12 09:39:42 +02:00
Mika Kuns a0d5db0db0 feat(ui): separate OperationStatus per worktree action (refresh/cleanup/reset/force-remove/batch-merge)
Replaces the shared IsBusy/IsMerging flags in WorktreesOverviewModalViewModel and the
reset flow in WorktreesSettingsTabViewModel with dedicated OperationStatus instances
shown via OperationIndicator, so a running Refresh no longer blocks the Cleanup
indicator. ForceRemove gains a CanExecute guard against re-entrancy while it's running,
and the reconcile tick's busy guard now checks all four action statuses instead of the
old IsBusy||IsMerging pair.
2026-08-12 09:39:10 +02:00
Mika Kuns 3e3f44bfb9 feat(claude-do): merge [P0-2] Timing-Hook für Hub-Invokes und Island-DB-Pfad
ClaudeDo-Task: a96c2c16-62e0-48f2-b80d-d98b113c7583
2026-08-12 08:43:09 +02:00
Mika Kuns ca65849280 feat(ui): add OperationStatus feedback primitive and OperationIndicator control
Foundation for long-running-operation feedback (P0-1). OperationStatus tracks
IsRunning/ShowIndicator(300ms grace)/Elapsed/IsStalled(60s since last Report)
via an injectable TimeProvider, no static timer. OperationIndicator binds a
status to the shared spinner style. Pre-provisions ops.* locale keys for
groups A and C; no ViewModel wired up yet.
2026-08-12 08:31:38 +02:00
Mika Kuns a614feb96d feat(ui): add operation-timing NDJSON sink for hub invokes and bulk DB paths
Two chokepoints per P0-2: WorkerClient.InvokeTimedAsync(<T>) wraps all 63
_hub.InvokeAsync call sites (plus TryInvokeAsync), and Stopwatch blocks
around the Island VMs' bulk DB load/write paths (list load, task list load,
reorders, batch delete, reconcile tick, count refresh). Single-row reads
stay untouched per the plan's rule of thumb. No display, no behavior change.
2026-08-12 08:31:35 +02:00
mika kuns cad0582b37 fix(merge): conventional merge-commit default and live verify progress
The merge commit message was hand-rolled per caller ("Merge task: <title>",
"Merge <branch>", "Merge subtask") and ignored the task's commit type. Every
caller now passes a blank message and TaskMergeService fills in
CommitMessageBuilder.BuildMerge -> {commitType}(list-slug): merge <title> plus the
ClaudeDo-Task trailer; the merge modal prefills it from GetMergeTargets.

A merge whose list has a verify command holds the MergeTask call for minutes (5m46s
on this repo), during which the modal only disabled its button - no spinner, no
message, so a landed merge looked like a dead app. TaskMergeService now broadcasts
MergeProgress(taskId, phase, elapsedSeconds) for the merging and verifying phases
(re-reported every 30s) plus a WorkerLog line when verify starts; the modal shows a
spinner and the localized phase.
2026-08-11 19:14:36 +02:00
mika kuns fc9df7f9ac Merge task: UI polish: ListBox Padding scroll clip, 4s log rebuild, ClassifyItems side effects 2026-08-11 18:34:36 +02:00
mika kuns e55c84cda4 fix(ui): stop ClearCompletedAsync from mutating chain-display fields
ClassifyItems() layers ApplyChainGrouping on top of partitioning, which
writes ShowAsChainMember/ChainStep/ChainAfterLabel on every row.
ClearCompletedAsync only needed the completed-section count/rows, so it
got that mutation as an unwanted side effect of counting.

Split the pure overdue/open/completed partition into PartitionItems();
ClassifyItems() now composes it with the chain-grouping mutation for
Regroup's actual render path. ClearCompletedAsync uses PartitionItems
directly. Regroup's behavior is unchanged.
2026-08-11 18:32:59 +02:00
mika kuns d327a1007e fix(ui): reconcile the log visualizer tick instead of clearing Rows
The 4s reconcile tick called Apply(), which did Rows.Clear() + refill on
every tick -- resetting the user's scroll position and selection while
they're mid-read, exactly what the overlay exists to avoid.

Reconcile granularly instead: find the overlap between the previous and
latest snapshot (the ring buffer is append-mostly, evicting from the
front as entries age out of the 30-min window), trim rows for evicted
entries off the tail, and insert new entries at the head. A tick with no
change now leaves Rows untouched. The WarnErrorOnly toggle still goes
through the full Apply() rebuild, unchanged.
2026-08-11 18:32:39 +02:00
mika kuns e343baf21a fix(ui): close the outgoing ConPTY pane on merge-helper handoff
Each handoff tile is a live claude process with the full mcp__claudedo__* surface; leaving the
outgoing phase's pane open leaked one process per phase. Close it via CloseConPtySession before
opening the next phase's tile, restoring the one-pane-per-TaskId invariant so
OpenConPtySessionAsync's dedupe and OnPaneSubmitForReview can go back to FirstOrDefault.
2026-08-11 16:55:25 +02:00
mika kuns 79b35801ae fix(ui): stop the reconcile ticks from destroying user state
The 4s reconcile tick was added to three long-lived surfaces. On two of
them it reloads by rebuilding every row instance, which silently threw
away whatever the user had built up since the overlay opened; on the
third it healed a row's data but left it in the wrong section.

- WorktreesOverview: LoadAsync clears Rows, SelectedCount, ConflictRows
  and BatchProgress, so batch-merge ticks, the highlighted row and a
  finished batch's outcome badges were wiped every four seconds --
  assembling a multi-worktree selection was impossible. Carry that state
  across the reload, keyed by task id, and re-point SelectedRow at the
  fresh instance (or clear it when the worktree is gone).
- MergeHelperSelection: the remember/restore had no re-entrancy guard, so
  a second tick entering between the first one's reload and its restore
  snapshotted rows already back at IsTickedByDefault and wrote that
  default back, re-ticking what the user had unticked. One tick at a time,
  and hold the row instances instead of a value snapshot so a tick landed
  during the await survives.
- TasksIsland: the tick deliberately never called Regroup because Phase 2b
  owned Rows in parallel. 2b has landed, so a healed task that went Done
  stayed in the Open section under a stale count, and a healed depends-on
  link never pulled its dependent under the chain head. Regroup when a
  patch moved a grouping input, gated on a cheap key so an idle tick stays
  free.
2026-08-11 16:39:20 +02:00
mika kuns 18babe3dee feat(ui): compute dependency-chain step/grouping in TasksIslandViewModel
Slice 1 of dependency-chain display: TaskRowViewModel gains DependsOnTaskId
plus the extrinsic ShowAsChainMember/ChainStep/ChainAfterLabel contract for
Slice 2's rail/badge rendering. Regroup's ClassifyItems walks each row's
DependsOnTaskId chain per section, pulls dependents directly under their
head regardless of SortOrder, and falls back to a flat row + label when the
head isn't in the same section (mirrors the ParentInView precedent).
Planning children never join a chain group - parent indent wins - and only
ever carry the label.

No AXAML changes; that's Slice 2.
2026-08-11 14:38:16 +02:00
mika kuns 38af549a80 feat(ui,worker): surface task numbers in row/detail UI and worker log
Slice 4/5 of task-numbers: TaskRowViewModel.Number renders as a dimmed
"#123" before the row title; DetailsIslandViewModel.TaskIdBadge now
shows "#123" instead of the unusable "#T<guid-prefix>" handle; and the
curated WorkerLog business events in TaskRunner, TaskMergeService, and
TaskResetService prefix their quoted title with "#<Number>".
2026-08-11 13:51:20 +02:00
mika kuns 8da1a12389 fix(test): give Ui.Tests task seeds a unique Number
The task-numbers slice added a UNIQUE index on tasks.number. Ui.Tests seed
TaskEntity rows directly instead of going through TaskRepository, so every row
kept the default Number = 0 and 31 tests failed with
'UNIQUE constraint failed: tasks.number'.
2026-08-11 11:32:01 +02:00
mika kuns c8244c67f3 feat(ui): wire list-handler nextPhase through the handoff hub event
HandoffRequested now carries nextPhase end to end (IWorkerClient ->
WorkerClient -> MissionControlViewModel -> GetMergeHelperHandoffLaunchSpecAsync),
and the outgoing tile is left open on handoff instead of being closed --
lookups that need the active pane for a task now use LastOrDefault since a
handler task's ConPtySessions can hold more than one pane.
2026-08-11 09:40:14 +02:00
mika kuns d3ba279b54 fix(ui): stop the merge-helper tick from re-ticking unticked candidates 2026-08-11 08:32:52 +02:00
mika kuns 2fe40f3d65 Merge claudedo/923d3b07183c4ca8bcd15aed12f9df39 2026-08-10 16:21:01 +02:00
mika kuns ec6c230822 refactor(ui): flatten TasksIsland's task list into one virtualized ListBox
Replace the three ItemsControl sections (Overdue/Open/Completed) with a single
flat Rows collection (HeaderRow | TaskRowViewModel) bound to a ListBox +
VirtualizingStackPanel, so the realized container count stays bounded instead
of growing with the list (measured: ~7-8 realized containers at any scroll
position with 1000 source rows). Group headers become regular row entries,
each section is simply omitted from Rows when empty, and the Completed
section's Clear-completed action moves onto the header row itself.

SectionFor/ReorderAsync/FindNextInSameSection now read a row's section off
the nearest preceding HeaderRow in Rows instead of three separate bound
collections, and ScrollSelectedIntoView goes through ListBox.ScrollIntoView so
it still works for rows outside the realized window. Drag/reorder behavior,
grouping/counts, and the Clear-completed button are unchanged.

Auto-scroll-while-dragging and the cross-section reorder bug are explicitly
out of scope (follow-up tasks).
2026-08-10 16:18:55 +02:00
mika kuns 1cad264967 feat(ui): add reconcile tick to self-heal lost worker broadcasts
TasksIslandViewModel now runs a periodic tick that diffs the flat Items
collection against SQLite and patches properties in place (capped at 500
rows, sharing the Phase 1 delta sequence guard so it never overtakes a
fresher broadcast). Never rebuilds rows or triggers Regroup/LoadForList, so
it stays decoupled from the parallel Phase 2b virtualization work. The same
cadence now refreshes the long-lived Worktrees Overview, Log Visualizer, and
Merge Helper selection overlays; short-lived modals are untouched.
2026-08-10 15:56:05 +02:00
mika kuns 66f7a8ae99 Merge branch 'claudedo/9f963a215bb34ca4a28050d4f39178d3' 2026-08-10 15:19:57 +02:00
mika kuns d3155e6868 feat(queue): warn when the base branch has uncommitted changes
Queuing (update_task_status, batch_update_task_status) and run_task_now
now surface a non-blocking baseDirty warning (separate modified/untracked
counts) when the list's working dir has uncommitted changes at enqueue
time, since a new worktree forks from the commit tip and silently misses
them. BaseDirtyChecker caches per working dir for a few seconds so a
batch queue over many tasks in one list only shells out to git once. The
UI surfaces the same warning via the footer error strip on queue actions.
2026-08-10 14:30:33 +02:00
mika kuns 659bf3e743 feat(ui): show child tasks as children in the merge-helper picker
MergeHelperSelectionModalViewModel now loads ParentTaskId and orders
rows hierarchy-aware (parent immediately followed by its children,
mirroring TasksIslandViewModel.Regroup). Children render indented with
the same indent-track visual as the main task list; ticking a parent
cascades selection to its children, while a child can still be
deselected on its own. A child whose parent got filtered out (e.g.
already Done) renders top-level with an explicit hint instead of
disappearing. Confirm keeps returning parents ahead of their children.
2026-08-10 13:47:55 +02:00
mika kuns 549aee097b feat(ui): default to the Git tab for manual and interactive tasks
A hand-driven task produces no streamed agent output, so the Output tab
opened empty. Bind now selects git for manual/interactive rows and resets
to output for autonomous ones.
2026-08-07 14:01:26 +02:00
mika kuns 6dff72f27c Merge branch 'main' into worktree-diff-side-by-side
# Conflicts:
#	src/ClaudeDo.Ui/CLAUDE.md
2026-08-07 11:16:49 +02:00
mika kuns df9e85a28b Merge branch 'worktree-phase1-reaktivitaet' 2026-08-07 11:01:54 +02:00
mika kuns 7eeb8f5086 feat(usage): split throttle thresholds per bucket, add draggable gauge markers 2026-08-07 11:01:45 +02:00
mika kuns cf80fe3cd6 feat(diff): add persisted side-by-side and wrap toggles to the diff viewer 2026-08-07 09:42:43 +02:00
mika kuns eeee9591aa test(ui): restore the language settings tests dropped in the previous commit 2026-08-07 09:24:43 +02:00
mika kuns 59827757f0 feat(diff): persist diff view mode and wrap preference in ui config 2026-08-07 09:22:16 +02:00
mika kuns ac099dd1a8 fix(ui): drop stale delta refreshes so the newest task state wins 2026-08-07 09:18:52 +02:00
mika kuns ebbee6005d feat(diff): highlight changed words inside paired diff lines 2026-08-07 09:17:26 +02:00
mika kuns cba7d01c31 fix(ui): retry the task delta refresh instead of swallowing the error 2026-08-07 09:16:21 +02:00
mika kuns 3cd0f0879e feat(diff): align parsed diff lines into side-by-side rows 2026-08-07 09:13:59 +02:00
mika kuns 30efbcbf2f Merge branch 'claudedo/a935c68eed6341bfb96f0bb82f13354e' 2026-08-06 14:43:57 +02:00
mika kuns 96723f4582 Merge claudedo/28d494e791e842fc966eb8691181e6da 2026-08-06 14:41:37 +02:00
mika kuns 967359d6e7 Merge claudedo/ab96ad81d417402088aa669ed52259e9 2026-08-06 14:41:08 +02:00