Commit Graph
100 Commits
Author SHA1 Message Date
mika kuns 106f9a374a feat(usage): parse the TokenTracker sessions export defensively 2026-08-24 13:34:01 +02:00
mika kuns 16952b6432 feat(usage): add TokenTracker export models and test fixture 2026-08-24 13:32:46 +02:00
mika kuns 1cb574bd96 feat(usage): reproduce TokenTracker session_hash for task attribution 2026-08-24 13:31:29 +02:00
mika kuns 0e74650d70 docs(usage): spec for TokenTracker as the token-analytics backend 2026-08-24 10:29:56 +02:00
Mika Kuns 664816881a refactor(claude-do): merge TaskRowViewModel: CanX und DisabledReason aus einer Gate-Met
ClaudeDo-Task: 6c4b27aa-1b5e-4d73-98f5-dcafa3935247
2026-08-24 10:08:55 +02:00
Mika Kuns 67083e1a4a refactor(claude-do): merge Dedupe: FormatRemaining aus UsagePill und UsageMonitorModal
ClaudeDo-Task: 94ea71ad-22a1-4612-8743-425d5fb43d75
2026-08-24 10:02:22 +02:00
Mika Kuns 9c59c77a28 refactor(claude-do): merge Dedupe: ShellOpen-Helper für "Pfad im Explorer öffnen" (5 Co
ClaudeDo-Task: 4084c060-3cf0-4474-930a-ea79695b9ba6
2026-08-24 09:54:37 +02:00
Mika Kuns b2a1b79029 refactor(ui): dedupe path-open helper across five view models
ListsIslandViewModel.OpenInExplorer, MergeSectionViewModel.OpenWorktree,
WorktreesOverviewModalViewModel.OpenInExplorer, AboutModalViewModel.OpenPath and
TasksIslandViewModel.OpenTaskWorktree each reimplemented "open this path in the
shell" with their own existence check, launch mechanism and error handling.
Replace all five with the new ShellOpen.Path helper and route failures through
the existing ErrorReported -> footer-strip convention instead of bare catch {}.
2026-08-24 09:38:41 +02:00
Mika Kuns d309302043 refactor(claude-do): merge Dedupe: "Liste hat ein verlinktes Repo?" — ein Prädikat stat
ClaudeDo-Task: 594ee78e-7112-427a-9cde-76f6610e7fd8
2026-08-24 09:38:36 +02:00
Mika Kuns baba921696 refactor(ui): derive TaskRowViewModel CanX/DisabledReason pairs from one gate method
SendToQueue, Cancel, Refine, Planning and OpenWorktree each had a CanX property
and a hand-written negation of it for the DisabledReason tooltip, kept in sync
only by a comment's promise. Replace each pair with a private gate method
returning (Can, Reason) so there is one source of truth per condition, and add
a state-matrix test pinning down the CanX == (Reason == null) invariant that
the old comment only asserted.
2026-08-24 09:36:00 +02:00
Mika Kuns 9578c95074 refactor(ui): unify list-linked-repo predicate into RepoLinkage
ListsIslandViewModel and SettingsModalViewModel each re-implemented "does
this list have a linked WorkingDir" with different whitespace handling.
RepoLinkage.IsLinked/IsLinkedInDb is now the single definition; both
callers derive from it, closing the whitespace-only WorkingDir gap where
the Settings modal disagreed with the ListsIsland banner.
2026-08-24 09:28:20 +02:00
Mika Kuns e164f1e33f refactor(ui): dedupe FormatRemaining between UsagePill and UsageMonitorModal
Both viewmodels carried byte-identical copies of the reset-countdown
formatter. Moved it to UsageTimeFormat.FormatRemaining and pointed
both call sites at it; locale keys and output unchanged.
2026-08-24 09:27:15 +02:00
Mika Kuns 79438c00d2 feat(Data): Improve App Prompts 2026-08-24 09:25:43 +02:00
mika kuns 29171b104b fix(ui): failureReason "usage_limit" nachziehen + Worktree-Gate prüft die Platte
Zwei liegengebliebene Consumer aus den letzten beiden Commits:

- TaskRunner klassifiziert seit 07dd7570 "usage_limit", aber weder
  TaskRowViewModel.FailureReasonLabel noch vm.failureReason (de/en) noch
  die get_task-Tool-Beschreibung kannten den Wert — die UI zeigte
  "Grund unbekannt", das MCP-Doc listete weiterhin max_turns|timeout|error.
- TaskRowViewModel.CanOpenWorktree prüfte nur auf einen nicht-leeren
  String. Die Zeile behält den Path eines gemergten/verworfenen Worktrees,
  also war der Menüpunkt aktiv und Process.Start warf in den Footer.
  Jetzt zusätzlich Directory.Exists — dieselbe Prüfung, die
  WorktreesOverviewModalViewModel und MergeSectionViewModel schon machen.
2026-08-24 09:05:31 +02:00
mika kuns 3dfae30fff fix(worker): Usage-Limit-Auto-Continue verbrennt nicht mehr alle Kandidaten außer dem ersten
OverrideSlotService hält genau einen Slot, und ein Usage-Limit stoppt
naturgemäß alle laufenden Tasks gleichzeitig — "mehr Kandidaten als
Kapazität" ist also der Normalfall. Der Coordinator stempelte
UsageLimitAutoContinuedAt aber VOR dem ContinueTask (der Marker ist die
Dedupe-Guard), sodass Kandidat 2..n den Marker bekamen, deren
ContinueTask mit "override slot busy" in den catch flog und sie danach
dauerhaft aus GetUsageLimitAutoContinueCandidatesAsync ausgeschlossen
waren — ein Continue, der nie lief.

- Vorab-Check auf CurrentSlot: bei belegtem Slot bricht der Tick ab,
  statt die restlichen Kandidaten zu verbrennen.
- Neuer TaskRepository.ReleaseUsageLimitAutoContinueClaimAsync gibt den
  Claim zurück, wenn ContinueTask wirft; danach return, der 30s-Backstop
  holt den Rest im nächsten Tick.
- Regressionstest mit zwei Kandidaten, deterministisch über eine
  TaskCompletionSource im FakeClaudeProcess (StartInSlot setzt _slot
  synchron unter dem Lock, bevor die Arbeit startet).
2026-08-24 09:05:20 +02:00
mika kuns 55bfb765e6 feat(claude-do): merge feat(worker): Toggle "Continue on session limit reset" — lim
ClaudeDo-Task: 6a8a7151695d4b93bd6d13e66d9307c5
2026-08-21 19:16:20 +02:00
mika kuns 07dd75700d feat(worker): Toggle "Continue on session limit reset" für Usage-Limit-Abbrüche
Klassifiziert einen echten Usage-Limit-Abbruch als eigene FailureReason
"usage_limit" (TaskRunner.ClassifyFailureReason: nur bei terminal_reason
"api_error" plus einem Limit-Muster im gerenderten Fehlertext, nicht an
Status==Failed allein). Neuer Toggle AutoContinueOnUsageLimit (app_settings,
Default aus) unter Settings → General → "Usage limit stop":

- UsageLimitAutoContinueCoordinator feuert pro Task genau einmal ContinueTask
  über OverrideSlotService, sobald das 5h-Fenster (UsageState.Snapshot.FiveHour
  .ResetsAt) tatsächlich zurückgesetzt ist; ein persistenter Marker
  (TaskEntity.UsageLimitAutoContinuedAt) verhindert einen zweiten Anlauf bei
  einem erneuten Limit-Treffer.
- QueueService schedult zusätzlich einen exakten Wake-Timer auf den
  Reset-Zeitpunkt, statt nur auf den 30s-Backstop zu warten.
- Fail-open durchgängig: kein Snapshot/keine Reset-Zeit → kein Timer, kein
  Continue, kein Throw. Toggle aus ändert das heutige Verhalten nicht.

Migration AddUsageLimitAutoContinue fügt beide Spalten hinzu; die von
`dotnet ef migrations add` mitgescaffoldete leere UpdateData auf app_settings
(columns/values: []) erzeugte ungültiges SQL ("near WHERE") und wurde entfernt
— TaskNumberMigrationTests deckte das über den vollen Migrate()-Pfad auf.
2026-08-21 18:43:02 +02:00
mika kuns 290dd1b614 feat(claude-do): merge feat(ui): Hauptfenster-Close mit offenen Mission-Control-Ses
ClaudeDo-Task: a963f289b38449a3a8d0c52fc9bfd9e7
2026-08-21 18:20:44 +02:00
mika kuns 0892883a82 feat(ui): confirm main-window close when Mission Control sessions are open
Closing the main window used to silently kill every open Mission Control
ConPTY session (App's ShutdownMode.OnMainWindowClose tears the process down
without warning). Intercept Window.Closing, and when at least one pane is
open, ask via the existing ConfirmAsync pattern before proceeding; cancelling
leaves the window and sessions alive, confirming force-closes as before.
2026-08-21 18:14:48 +02:00
mika kuns 89d30d1463 feat(claude-do): merge feat(ui): "Worktree öffnen" im Kontextmenü der Task-Zeile
ClaudeDo-Task: 9be17e29e0b54f42ba9385b0cebe89d5
2026-08-21 18:14:45 +02:00
mika kuns eb2160dfc2 fix(claude-do): merge fix(worker): List-Handler-Session kennt ihre eigene Handler-
ClaudeDo-Task: 997d0ffa-48ae-42d7-bea0-3aed4cf81768
2026-08-21 18:09:02 +02:00
mika kuns b598ec2fac fix(claude-do): merge fix(ui): Usage-Pill bleibt nach App-Start leer — Initial-Loa
ClaudeDo-Task: cdb71ff5aefd4ae9819352a845e0be89
2026-08-21 18:03:03 +02:00
mika kuns 9c4a63d2c5 fix(worker): thread handler task id into list-handler kickoff prompts
Neither the initial nor the handoff kickoff ever told a list-handler
session its own handler task id, so handoff_list_handler(taskId, ...)
was unrenderable -- the handoff chain broke exactly where it was needed
(#200/#201 on 2026-08-21).

Add {handlerTaskId} to both MergeHelperInitialDefault and
MergeHelperHandoffDefault, thread a handlerTaskId parameter through
BuildForMergeHelperAsync (interface, WorkerHub.GetMergeHelperLaunchSpec,
IWorkerClient/WorkerClient, and the MissionControlViewModel call site,
which already had the id from CreateMergeHelperTaskAsync but never
passed it on), and render it in BuildForMergeHelperHandoffAsync from the
taskId parameter it already receives.

RenderTemplate leaves unknown/missing tokens untouched, so a
user-edited override without the new token still renders fine -- no
forced migration for override users.
2026-08-21 18:00:16 +02:00
mika kuns ac9b0dab29 feat(ui): "Worktree öffnen" im Kontextmenü der Task-Zeile
Group 1 of the task-row context menu gets a fourth gated entry, right
after "Open interactive session": it opens the task's worktree folder
via Process.Start/UseShellExecute, mirroring ListsIslandViewModel's
OpenInExplorer. Missing worktree greys the item out with a tooltip
reason instead of hiding it, keeping Group 1's fixed length. Failures
report through TasksIslandViewModel.ErrorReported (footer strip), not
a silent catch.
2026-08-21 17:58:31 +02:00
mika kuns 7f94bf4aed fix(ui): UsagePillViewModel loads snapshot on connection restore
The ctor-time LoadAsync races WorkerClient.StartAsync, so the SignalR
call fails silently and the pill only fills on the next worker poll
(up to 900s). Subscribe to ConnectionRestoredEvent, which also fires
on the first successful connect, to retry the cached snapshot.
2026-08-21 17:47:49 +02:00
mika kuns 93be76a144 feat(claude-do): merge feat(mcp): update_task kann isManual setzen + List-Handler s
ClaudeDo-Task: 0af82db9dd3e43eb9071f98f807ef452
2026-08-21 16:56:48 +02:00
mika kuns ea2271cc3e feat(mcp): update_task kann isManual setzen + List-Handler stellt seinen Task auf Review
Teil A: update_task bekommt einen optionalen isManual-Parameter (null =
unveraendert); TaskDto/TaskRefDto spiegeln IsManual zurueck.

Teil B: neuer submit_task_for_review MCP-Tool (LifecycleMcpTools) laesst
einen Handler-Task selbst auf WaitingForReview gehen -- fuer einen
worktree-losen Task wird HandlerHeadCommit vom aktuellen HEAD gestempelt,
sonst werden offene Aenderungen committet. Die Submit-Logik ist aus
WorkerHub.SubmitTaskForReview in InteractiveReviewSubmissionService
extrahiert; der Hub ruft sie jetzt nur noch auf. Der Merge-Prompt-Default
weist die Endzweige (merge ohne Rerun, merge_final) an, den eigenen
Handler-Task nach der Summary einzureichen.
2026-08-21 16:51:18 +02:00
mika kuns 36720d33ae fix(claude-do): merge fix(worker): Interaktive Session auf einem Nicht-Repo-Workin
ClaudeDo-Task: 15e126d564874973be2a5bbe7d796646
2026-08-21 16:51:15 +02:00
mika kuns 302d657644 feat(claude-do): merge feat(ui): Usage-Monitor zeigt hinter der Restzeit die Reset-
ClaudeDo-Task: 533f0a42c08e4044a8eaa9235f960dbe
2026-08-21 16:45:30 +02:00
mika kuns ff37a97ade fix(claude-do): merge fix(ui): Description-Editbox nutzt die volle Höhe der Descri
ClaudeDo-Task: 2e98a45690e842b7a498034dc9483a15
2026-08-21 16:39:47 +02:00
mika kuns 0d1dd169ac fix(claude-do): fix(worker): Interaktive Session auf einem Nicht-Repo-Workin
Eine interaktive ("Quick") Session soll auf Pfaden, die kein Git-Repo sind, keinen Worktree erzwingen, sondern direkt im ausgewählten Ordner starten. Der User hat entschieden: BEIDE Einstiegspunkte prüfen — den task-gebundenen Pfad fixen, den Ad-hoc-Pfad verifizieren.

## Pfad 1 (der eigentliche Fix) — task-gebundene Session

- Einstieg: Kontextmenü der Task-Zeile "Open interactive session" — src/

ClaudeDo-Task: 15e126d564874973be2a5bbe7d796646
2026-08-21 16:36:11 +02:00
mika kuns a6d165a177 fix(ui): Description-Editbox nutzt volle Kartenhoehe statt 320px-Deckel
Die Edit-TextBox war per festem MaxHeight auf 320px begrenzt, auch wenn die
Description-Card per Splitter groesser gezogen wurde. Die Card begrenzt die
Hoehe bereits selbst (2/3-Cap in UpdateRowLimits), daher stretcht die TextBox
jetzt ohne eigenen Deckel bis zur verfuegbaren Card-Hoehe.
2026-08-21 16:28:35 +02:00
mika kuns 8f983851b4 feat(ui): Usage-Monitor zeigt Reset-Uhrzeit hinter der Restzeit 2026-08-21 16:22:26 +02:00
mika kuns 8dce2d0898 chore(claude-do): merge feat(ui): Erststart-Banner und Textkette Repo→Task→Queue→Rev
ClaudeDo-Task: 60ad0392-ef5e-4204-9445-415788ccd45b
2026-08-21 15:54:21 +02:00
mika kuns 111dd0fb0d feat(ui): Erststart-Banner und Textkette Repo→Task→Queue→Review (UX-Struktur C)
HasNoLinkedRepo on ListsIslandViewModel drives a dismiss-less banner under the
Lists-Island header when no User list has a linked repo; recomputed on load
and after list CRUD / list-settings save. Sharpens the #192 empty-state texts
into one readable chain (banner → no-repo list → repo-linked list) and adds a
line to the WorkConsole review gate spelling out that Approve also merges the
whole unit.
2026-08-21 15:25:04 +02:00
mika kuns c47cdc1c7c chore(claude-do): merge feat(ui): Settings-Modal auf Sidebar-Kategorien umbauen (UX-
ClaudeDo-Task: 314cf7a5-ed81-4b55-895c-abc30301d02e
2026-08-21 15:08:32 +02:00
mika kuns 56c3719e98 feat(ui): Settings-Modal auf Sidebar-Kategorien umbauen
TabControl bleibt Content-Host, TabStrip wird retempliert (nur
PART_SelectedContentHost) und durch eine zweigruppige Sidebar
(BASIS/ERWEITERT) ersetzt. General wird in Allgemein/Ausfuehrung/Berichte
gesplittet, Session Skills (Checkbox-Liste aus General + Skills-Tab)
zu einer Liste zusammengelegt, und ein Repo-Hinweisstreifen erscheint
auf Worktrees/Prime Claude/Session Skills/Berichte solange keine Liste
ein WorkingDir hat. Fensterbreite 580 -> 700.
2026-08-21 15:04:15 +02:00
mika kuns a80a31eeac chore(claude-do): merge feat(ui): Task-Zeile entdichten und Kontextmenü gruppieren (
ClaudeDo-Task: a43e22e7-ba96-4ee0-8aa6-482f7c567db6
2026-08-21 14:39:10 +02:00
mika kuns 51dc7bd61b feat(ui): Task-Zeile entdichten und Kontextmenü gruppieren (UX-Struktur A)
Gruppe 1 (Send to queue, Remove from queue, Cancel execution, Open quick
session, Refine task) ist jetzt fix sichtbar und gated per IsEnabled + Grund
statt komplett auszublenden. Planning/Schedule wandern in Untermenüs (Mark as
existierte bereits); der Planning-Kopf zeigt einen Grund, wenn er sonst leer
wäre. Refine-Button, ungesetzter Star und Dequeue-X erscheinen nur bei
Hover-oder-Selektion (TaskRowViewModel.ShowRowActions); ein gesetzter Star und
der Refining-Spinner bleiben immer sichtbar. Der Branch-Chip wandert aus der
Zeile in eine Meta-Zeile in TaskHeaderBar.

A3 headless verifiziert: Avalonia 12 zeigt ToolTips auf IsEnabled=false
Controls nur mit ToolTip.ShowOnDisabled="True" (Default ist false) — dieses
Attached Property existiert bereits und wird für die neuen Disabled-Reason-
Tooltips genutzt.
2026-08-21 14:34:14 +02:00
mika kuns c3ef460a40 chore(claude-do): merge [C5] Periodische Dienste: nur Aktivität und Fehler melden —
ClaudeDo-Task: a18f685b-ecbd-40b6-ae0b-83ada726eae6
2026-08-21 13:56:54 +02:00
mika kuns c143eb2481 chore(claude-do): merge [C4] Rebase-after-Merge sichtbar machen + WorktreeMaintenanc
ClaudeDo-Task: 0a64e32d-6308-4f2d-a69e-fb8b3cabd01a
2026-08-21 13:51:01 +02:00
mika kuns ab449d51eb chore(claude-do): merge [C3] Worktree-Anlage beim Task-Start an der Task-Zeile sicht
ClaudeDo-Task: 68254f28-13f4-40e8-94c0-e15d2fe58b37
2026-08-21 13:45:24 +02:00
mika kuns beca1d8c99 chore(claude-do): merge [C2] Startup-Recovery über den OperationProgress-Kanal sicht
ClaudeDo-Task: c264783d-2952-4e8f-9ef6-4dd0d493d8a7
2026-08-21 13:39:42 +02:00
mika kuns 6ec81f653b feat(worker): surface startup recovery via OperationProgress channel
The six Lifecycle/*Recovery hosted services now broadcast one
OperationProgress("startup-recovery", <phase>, current, total) message each
after they finish, instead of leaving the UI on a bare "connecting" text
during worker startup. IslandsShellViewModel subscribes and swaps in
"Recovering... (i/n)" (existing ops.worker.startupRecovery key, no locale
changes) while Worker.IsReconnecting is true, and clears it once actually
connected so a later transient reconnect doesn't replay stale text.

OperationProgress broadcasts to Clients.All with no replay-on-connect, so a
UI that hasn't finished its SignalR handshake yet can miss some or all of
these messages and simply keep showing "connecting" as before -- accepted
rather than adding a cached-state + reconnect-replay path (mirroring
RefreshExternalMergeConflictsAsync) for what is a fast, best-effort,
local-only startup sweep with no UI-visible failure mode beyond that.
2026-08-21 13:36:25 +02:00
mika kuns eba0d842b5 feat(worker): report worktree-creation phase on OperationProgress
Fills the silent gap between Queued and the first agent output: WorktreeManager
now broadcasts a "creating_worktree" phase (before the initial git worktree add
and again for the self-heal retry section) through the existing OperationProgress
channel, and the task row shows it via the pre-existing but unused
ops.worker.creatingWorktree locale key until the next entity refresh clears it.

Confirmed the 2026-08-07 triage finding still holds: TaskRunner already
broadcasts WorktreeUpdated right after WorktreeManager.CreateAsync
(TaskRunner.cs:342-346, :501) -- no second broadcast added there.
2026-08-21 13:35:26 +02:00
mika kuns 42de97e16a feat(worker): surface rebase and worktree-maintenance progress on OperationProgress
RebaseOthersAfterMergeAsync now broadcasts a "rebasing" phase with i/n over the
WaitingForReview branches it checks, so the merge/continue_merge callers stop
showing the stalled "Merging…" phase while the best-effort rebase loop runs
(rebase still runs before the verify gate; a failed rebase still leaves the
merge itself successful). WorktreeMaintenanceService gained an optional
HubBroadcaster to report the same i/n shape per worktree during
cleanup/reset, with no new UI surface (deliberately out of scope). Both
review-action viewmodels now also listen on OperationProgressEvent (which
carries the total that the elapsed-seconds-only MergeProgressEvent drops) to
render "Rebasing other worktrees… (i/n)".
2026-08-21 13:33:02 +02:00
mika kuns 2986896223 docs(worker): C5 periodische Dienste abgesagt — kein Mehrwert über bestehende Broadcasts
Usage/Prime/OnlineSync melden Aktivität schon (UsageUpdated, Prime*-Events, TaskUpdated),
Fehler aller vier Dienste laufen bereits über BroadcastLogSink in den WorkerLog-Footer,
und QueueServices einzige bedeutsamen Zustandswechsel (Gate/Throttle) sind schon
change-gated broadcastet. Ein zusätzlicher OperationProgress-Kanal wäre Duplikat mit
Tick-Strom-Risiko am Backstop-Timer statt Mehrwert.
2026-08-21 13:14:14 +02:00
mika kuns dcda067b48 chore(claude-do): merge fix(ui): Status-Writes über eine geguardete TaskStateService
ClaudeDo-Task: 4460f1f3-dde7-4c6d-9fa3-d19c2c8ff034
2026-08-21 12:00:06 +02:00
mika kuns 7f337b36c7 fix(worker): route done-toggle and dequeue through guarded TaskStateService transitions
The task-list done toggle (both islands) and RemoveFromQueue wrote TaskEntity.Status
directly via EF, bypassing TaskStateService: no TaskUpdated broadcast, no guard against
a concurrent picker claim (lost update), and no status-based filter. Added guarded
MarkDoneAsync/UnmarkDoneAsync/DequeueToIdleAsync transitions plus matching hub methods
(SetTaskDone/UnsetTaskDone/DequeueTask) and IWorkerClient wrappers; the three UI call
sites now route through the hub with optimistic-then-revert row updates and
ErrorReported on failure. RemoveFromQueueAsync dequeues each queued child individually
through the same guarded path instead of cascading via a raw EF update.

Also closes two hub guard gaps: UpdateListConfig's delete branch now preserves a list's
SerializeOnFileOverlap flag instead of dropping it, and SubmitTaskForReview's Idle/Failed
status gate now runs before either mutation branch so a Done/Cancelled task can't get
committed or stamped and then rejected.
2026-08-21 11:47:24 +02:00
mika kuns ce98f65ca5 chore(claude-do): merge fix(ui): Reactivity — Description-Save-Race, UsageMonitor-Le
ClaudeDo-Task: 3b82ebfd-c9a9-409d-befd-427b851a4bab
2026-08-21 11:47:11 +02:00
mika kuns 52efe3ec48 fix(ui): Reactivität — Description-Save-Race, UsageMonitor-Leak, Listen-Live-Refresh
Drei unabhängige Reactivity-Bugs aus dem Polish-Audit 2026-08-20:

1. Description-Autosave überschrieb den falschen Task, weil SaveDescriptionAsync
   Task.Id/EditableDescription erst nach dem 400ms-Debounce las statt Row+Wert an
   der Aufrufstelle zu capturen (wie SaveTitleAsync es schon tat). Bind() cancelt
   jetzt zusätzlich einen laufenden Title-/Description-Save der vorherigen Row.
2. UsageMonitorModalViewModel abonnierte UsageUpdatedEvent erst nach dem
   Erst-Load-Await — schloss man das Modal währenddessen, lief das Unsubscribe in
   Close() ins Leere und die VM hing für immer am WorkerClient. Ein _isClosed-Flag
   wird jetzt nach dem Await geprüft, bevor abonniert wird.
3. Per MCP erstellte Listen blieben unsichtbar: RefreshRowAsync hatte keinen
   Add-Zweig für unbekannte Ids und verglich zudem die falsche Id-Form (der
   Worker broadcastet die rohe DB-Id, nie die "user:"-prefixte Row-Id). Ein
   Reconnect lud zudem nur Counts statt der vollen Listen neu.
2026-08-21 11:41:58 +02:00
mika kuns 7e91aaa255 feat(claude-do): merge feat(ui): Empty States für die drei Islands (UX-Audit #4)
ClaudeDo-Task: 96503cc2-576c-4189-bd09-30c5e6aed5f0
2026-08-21 10:49:43 +02:00
mika kuns b997af4423 fix(ui): notify IsTasksEmptyRepoHintVisible on every list switch
IsTasksEmptyRepoHintVisible reads _currentList?.Kind directly, but Kind
isn't itself observed — the NotifyPropertyChangedFor chain only fires
when IsLetClaudeVisible's value actually changes. Switching from a
Smart list to an empty User list without a WorkingDir changes Kind
while IsLetClaudeVisible (and Has*) stay false in both, so the hint
never notified and stayed stale.
2026-08-21 10:34:46 +02:00
mika kuns 9159f1b55c feat(ui): Empty States für Detail-Pane und Task-Liste (UX-Audit #4)
Zentrierter, gedimmter Platzhalter (Mission-Control-Muster) im
Detail-Pane bei fehlender Selektion (nicht in Notes-/Prep-Modus) und
in der Task-Liste bei 0 sichtbaren Tasks; User-Listen ohne
WorkingDir bekommen zusätzlich den Repo-Verknüpfungs-Hinweis.
2026-08-21 10:27:02 +02:00
mika kuns 36bb7a83b8 fix(claude-do): merge fix(ui): Aktions- und Bestätigungs-Konsistenz (UX-Audit #3)
ClaudeDo-Task: bd2a0206-78aa-4c1c-99a5-1c241423c5c8
2026-08-21 10:12:44 +02:00
mika kuns 2935eef75a fix(ui): Aktions- und Bestätigungs-Konsistenz (UX-Audit #3)
Sechs Konsistenz-Fixes: Delete-Task ins Zeilen-Kontextmenü (routet über den
Worker wie DetailsIslandViewModel.DeleteTaskAsync, damit ein gelöschtes Child
den WaitingForChildren-Parent korrekt weiterschaltet); EnqueueCommand als
"Send to queue"-Button im Detail-Pane verdrahtet; Settings-Eintrag im
Listen-Kontextmenü ergänzt; Worktree-Discard und Reset-All laufen jetzt über
das jeweils vorhandene Confirm-Hook (ConfirmAction / neuer ConfirmAsync-Hook
auf WorktreesSettingsTabViewModel) statt ohne Rückfrage bzw. über ein
Inline-Reveal-Banner; killSessionTip/closeSession und die deutschen
Queue-Strings im usageMonitor vereinheitlicht.

Kein zweiter Dialog-Mechanismus eingeführt — überall die vorhandenen Hooks
wiederverwendet.
2026-08-21 09:51:51 +02:00
mika kuns cc60cb4f0a feat(claude-do): merge feat(ui): Precondition-Hinweise nach Approve-Gate-Muster (UX
ClaudeDo-Task: 9a678a36-2ea3-4fa6-b4eb-4c506de72cd8
2026-08-21 09:34:31 +02:00
mika kuns dbb73bae38 fix(ui): surface precondition hints for silently-disabled review buttons (UX-Audit #2)
Cancel now shows an amber hint + tooltip while a merge drain is in
progress (ShowMergeDrainHint mirrors IsMergeDraining). Continue,
Reset & Retry, Open Diff, Worktree, and Review Combined Diff gain
ToolTip.Tip + ToolTip.ShowOnDisabled explaining why they're locked.
No CanExecute or behavior changed, only added communication.
2026-08-21 09:31:56 +02:00
mika kuns 4fc64732ec fix(claude-do): merge fix(ui): Silent No-Ops auf Fehler-Feedback umstellen (UX-Aud
ClaudeDo-Task: a251fcdd-d999-47f3-bc48-4d320c4ddabe
2026-08-21 09:16:13 +02:00
mika kuns 99bb5be5da fix(ui): silent no-ops raise ErrorReported instead of swallowing failures (UX-Audit #1)
Stop/Enqueue/Dequeue/Reset&Retry (DetailsIslandViewModel), status/cancel/reject
commands (TasksIslandViewModel), Mission Control's drag-enqueue and queue
refresh, and "Open findings folder" (ListsIslandViewModel) used to catch {}
or silently return on a blocked precondition. They now report through the
existing ErrorReported -> FlashFooterError path, with new en/de locale keys
and Ui.Tests covering each converted command.
2026-08-21 09:13:00 +02:00
mika kuns fdd0e1e56a chore(claude-do): merge fix(worker): Stuck-Running-Fenster in Continue- und Stop-Pfa
ClaudeDo-Task: 6db1a9e2-aa1d-4911-a763-76b136f97946
2026-08-20 15:14:18 +02:00
mika kuns 69ba37e3f3 chore(claude-do): merge fix(worker): Unit-Merge-Fehler propagieren statt als Erfolg
ClaudeDo-Task: 3dc0f26a-53cb-40bb-9f22-5df2e6d61c84
2026-08-20 15:05:01 +02:00
mika kuns f205843020 fix(worker): propagate unit-merge failures instead of reporting success
A child merge that came back blocked/verify_failed/untracked_collision during a
parent/children unit merge used to vanish: DrainAsync only logged it server-side,
PlanningMergeAborted carried no reason, and ApproveReview/review_task always
reported StatusMerged for a task with children regardless of the real outcome,
so a failed unit merge left the parent stuck with no visible error.

- PlanningMergeOrchestrator.StartAsync/ContinueAsync/DrainAsync now return a
  PlanningMergeResult(Status, Reason) instead of void, and PlanningMergeAborted
  carries that reason to the UI.
- WorkerHub.ApproveReview and ExternalMcpService.ReviewTask's approve branch
  propagate the real status/reason for a parent with children instead of
  hardcoding "merged" (or masking a non-conflict failure as "conflict").
- StartAsync now requires the parent to already be WaitingForReview for
  improvement parents too, not just planning ones, so a stale caller can no
  longer trigger a partial child merge.
- HasActiveMerge now also covers the window between the last child merging and
  FinalizeParentDoneAsync completing, closing a gap where a concurrent Cancel
  could race the parent's own approve-to-Done transition.
- IslandsShellViewModel.OnPlanningMergeAborted flashes the reason via
  FlashFooterError instead of only clearing the external-merge banner.
2026-08-20 15:02:17 +02:00
mika kuns 389c9126c8 fix(worker): Stuck-Running-Fenster in Continue- und Stop-Pfad schließen
TaskRunner.ContinueAsync: Claim, SeedAsync und SetupMcpConfigAsync liefen
vor dem try/catch (anders als RunAsync) - warf einer davon nach dem
Running-Claim, propagierte die Exception ungefangen bis zu
OverrideSlotService.RunContinueInSlotAsync, das nur loggt. Task blieb
Running. Fix: derselbe Aufbau wie RunAsync (Claim+Seed+MCP-Setup im
try, MarkFailed im catch, mcpToken/mcpConfigPath vor dem try auf null).
SetupMcpConfigAsync bekommt zusätzlich einen onTokenRegistered-Callback,
damit die äußere mcpToken-Variable den Token sofort nach dem Register
sieht - sonst hätte ein Fehler zwischen Register und Rückgabe (z.B.
File.WriteAllTextAsync) den Token in der TaskRunTokenRegistry geleakt
(betrifft denselben Aufruf in RunAsync mit, daher dort ebenfalls
verdrahtet - RunAsync-Struktur selbst unverändert).

QueueService.RunInSlotAsync: der Stop-Button (CancelTask) cancelt die
Slot-CTS direkt ohne TaskStateService-Schreibzugriff. Traf das die
Pre-Dispatch-DB-Reads, loggte der OCE-Catch nur und die vom Picker
bereits auf Running geclaimte Task blieb dort für immer hängen. Fix:
Status im Catch neu lesen und nur wenn er noch Running ist über
TaskStateService.CancelAsync auf Cancelled setzen - ein Ursprung, der
bereits selbst einen Terminalstatus geschrieben hat (z.B. CancelReview),
wird nicht überschrieben. Kommentar korrigiert.

TDD: neue Tests in ContinueAsyncExceptionTests (Seed-/MCP-Setup-Fehler
nach Claim -> Failed, kein Token-Leak) und QueueServiceSlotFailureTests
(Stop während Pre-Dispatch -> Cancelled statt Running; ein bereits
terminal geschriebener Status wird nicht gestompt) vorher rot, jetzt
grün. Worker.Tests: 1213/1213 grün, Worker baut in Release.
2026-08-20 15:00:14 +02:00
mika kuns 4cf08f8159 docs(ui): design and plan for the four structural UX themes 2026-08-20 13:46:23 +02:00
mika kuns f9a8ed761a docs: record the operation-feedback state before the v2.10.0 cut
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 43s
Plan checkboxes stood at zero although P0, A, B, C1 and D are all merged --
anyone reading it would have assumed nothing was built. Ticks the merged
packages, records A5's cancellation and the visual pass, and names the two
leftovers (the three unused ops.* keys from A3/A4, C2-C5 as the only open
group). Also corrects the batch_* tool count from eight to seven and puts the
OperationTiming kill-switch note in the Ui CLAUDE.md back into English.
2026-08-17 11:45:03 +02:00
mika kuns 0e314e44dd Merge remote-tracking branch 'origin/main' 2026-08-17 11:35:02 +02:00
Mika Kuns 9c4f0d1872 feat(claude-do): merge [D4] Restliche MCP-Long-Runner versorgen + Progress-Regel im
ClaudeDo-Task: 8e7f70d8-e821-45c8-835b-7743c201f599
2026-08-17 10:00:35 +02:00
Mika Kuns 23aab26daf feat(worker): report progress for continue_merge and unit-merge verify gate
continue_merge and the parent/children unit-merge drain (PlanningMergeOrchestrator)
re-run the post-merge verify gate but never forwarded their IProgress token into it,
so a slow verify command on either path went silent past Claude Code's 300s MCP
idle-abort even though D1-D3 already fixed this for merge_task/review_task's
childless path. list_worktrees also gets elapsed-time progress: many tracked
worktrees means many concurrent git subprocess spawns.

Worker CLAUDE.md's existing progress rule now points at ProgressReporter as the
one implementation instead of a fresh polling loop.
2026-08-17 09:56:59 +02:00
Mika Kuns 1341c5f4f3 feat(claude-do): merge [D3] Worktree- und Diff-MCP-Tools mit Progress (3 Tools in E
ClaudeDo-Task: cc93dd2d-f870-4820-9f69-5388372ee73b
2026-08-17 09:32:40 +02:00
Mika Kuns aeb1a5eb82 feat(worker): report progress for cleanup_task_worktree, get_task_diff, preview_merge_set
Wraps the single-element git/worktree long-runners in ProgressReporter.RunAsync
(time-based, ExternalMcpService.ProgressReportInterval) and adds i/n reporting
to preview_merge_set's per-task loop, so these MCP calls survive Claude Code's
~300s idle-silence abort instead of leaving the caller with no signal that work
is still happening.
2026-08-17 09:29:51 +02:00
Mika Kuns 0de3816fd0 feat(claude-do): merge [D2] Die 7 batch_*-MCP-Tools mit Element-Fortschritt (i/n)
ClaudeDo-Task: 286efaaf-b38e-47d4-9655-0c2d87e4b329
2026-08-17 09:06:27 +02:00
Mika Kuns 53e26f0340 feat(worker): report per-item progress on all 7 batch_* MCP tools
Each batch tool now sends an i/n progress ping via ProgressReporter.ReportItem
after processing every item, so a waiting agent doesn't see the MCP client's
300s idle-silence abort while the worker keeps looping. Covers BatchGetTasks,
BatchAddTasks, BatchUpdateTaskStatus, BatchCancelTasks, BatchDeleteTasks,
BatchSetMyDay, and BatchCleanupTaskWorktrees (the slowest of the seven, since
it does git work per task).
2026-08-17 08:56:32 +02:00
Mika Kuns 9099b22eea refactor(claude-do): merge OperationTiming hinter Kill-Switch, Default aus (vor dem näc
ClaudeDo-Task: ae170c8b-33cf-4cf3-932e-865fbdd2afa6
2026-08-17 08:52:57 +02:00
Mika Kuns ce1a26d407 refactor(claude-do): merge [D1] ProgressReporter aus TaskMergeService extrahieren (+ i/
ClaudeDo-Task: 47c76093-633e-4f3b-b49b-b34873de0469
2026-08-17 08:39:06 +02:00
Mika Kuns 555933a93c refactor(worker): extract ProgressReporter from TaskMergeService
Pulls the MCP idle-timeout progress loop out of TaskMergeService into a
standalone ProgressReporter (Lifecycle namespace) shared by the verify
gate and preview-verify call sites, and adds a per-item i/n overload
(ReportItem) for upcoming batch progress in D2/D3.
2026-08-17 08:36:21 +02:00
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 8a1e2c9698 chore(claude-do): merge [C1] Generischer OperationProgress-Kanal, MergeProgress dara
ClaudeDo-Task: 543947ed-b501-4df8-b30d-1c14804244fd
2026-08-13 08:53:17 +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 db32e5307f chore(claude-do): merge [B3] Guard-Test: großer Diff blockiert den Dispatcher nicht
ClaudeDo-Task: e1edd8cd-9392-4616-b2e2-e0511ff59568
2026-08-12 15:00:14 +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 a9af650407 chore(claude-do): merge [B2] DiffAlignment.Build — begründete Grenze gegen unbegrenz
ClaudeDo-Task: ee8117fa-283a-4470-a823-ce57f52a40be
2026-08-12 14:11:41 +02:00
Mika Kuns c9afc81f0a fix(ui): offload DiffAlignment.Build off the UI thread above a line threshold
DiffTextView.ReloadFile ran DiffAlignment.Build synchronously for both the
constructor and the File-property path, so a large diff (or several at once
in Planning mode's per-file ItemsControl) could freeze the UI thread.
Diffs at or under 500 raw lines still build inline (no placeholder frame);
larger ones build via Task.Run and a generation counter discards the result
if File changed again before the build finished.
2026-08-12 14:07:06 +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 030b3ceb6c chore(claude-do): merge [A3] Settings + Update-Check: OperationStatus für Skill-Inst
ClaudeDo-Task: 2356806f-f16e-4652-b615-4562e95c3a65
2026-08-12 10:02:35 +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 5a74a67660 chore(claude-do): merge [A1] Detail-Pane: Approve, Submit, Reject, Park, Merge-Previ
ClaudeDo-Task: 120cb6a4-ad0d-426a-bb72-e3c508698b8f
2026-08-12 09:46:14 +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 d3abd4b88b docs(claude-do): merge [P0-3] Feedback-Regel in Ui- und Worker-CLAUDE.md
ClaudeDo-Task: 771e3484-cd9f-4d81-a890-82c336d1fee4
2026-08-12 08:50:59 +02:00
Mika Kuns 8098b7f4ec docs(claude-do): document OperationStatus and MCP progress-reporting rules 2026-08-12 08:44:30 +02:00