17 KiB
Feedback für langlaufende Operationen — Vorgaben für die Umsetzung
Dieses Dokument ist absichtlich kein Task-Skript. Es enthält die Vorgaben pro Gruppe. Die konkreten Tasks schreibt der Merge-Helper je Gruppe beim Ausführen — er kennt dann den tatsächlichen Codestand. Was hier steht, ist bindend; was hier nicht steht, entscheidet er. Checkboxen stehen auf Paketebene, damit der Fortschritt sichtbar bleibt.
Design: docs/superpowers/specs/2026-08-11-operation-feedback-design.md — dort stehen die
Belege (Datei:Zeile) und die Begründungen. Dieses Dokument wiederholt sie nicht.
Ziel: Jede Operation, die länger als ~300 ms dauern kann, zeigt an, dass sie läuft, was sie tut und wie lange sie schon läuft — über einen Mechanismus statt pro Fall neu gebaut.
Ausführungsmodell
P0 (Fundament) ── muss ALLEIN und ZUERST landen
│
├── Gruppe A UI-Stille Merge-Helper 1
├── Gruppe B UI-Freeze Merge-Helper 2
└── Gruppe C Worker-Stille Merge-Helper 3
Gruppe D MCP-Stille Merge-Helper 4 ── unabhängig von P0, kann sofort starten
Ein Merge-Helper pro Gruppe. Nach P0 laufen A, B, C und D parallel. Jede Gruppe hält sich
strikt an ihr Datei-Eigentum — das ist die einzige Absicherung gegen gegenseitiges
Überschreiben, weil alle im gemeinsamen main-Checkout arbeiten.
Datei-Eigentum (bindend)
| Gruppe | Besitzt exklusiv | Darf nicht anfassen |
|---|---|---|
| P0 | Ui/Services/OperationStatus.cs (neu), Ui/Views/Controls/OperationIndicator.axaml(.cs) (neu), beide locales/*.json, Ui/Services/WorkerClient.cs (nur Timing-Hook) |
alles andere |
| A | Island-VMs, Modal-VMs, deren AXAML, Ui/Services/UpdateCheckService.cs |
locales/*, WorkerClient, IWorkerClient, Worker-Projekt |
| B | Ui/ViewModels/Modals/DiffViewerViewModel.cs, Ui/Views/Controls/DiffTextView.axaml.cs |
locales/*, Island-VMs, Worker-Projekt |
| C | Worker/Hub/HubBroadcaster.cs, Worker/Hub/WorkerHub.cs, Ui/Services/WorkerClient.cs, IWorkerClient.cs, StubWorkerClient.cs, Worker/Lifecycle/*Recovery.cs, Worker/Runner/WorktreeManager.cs, Worker/Worktrees/WorktreeMaintenanceService.cs |
locales/*, Island-VMs, Worker/External/* |
| D | Worker/External/*McpTools.cs, Worker/External/ExternalMcpService.cs, Worker/Lifecycle/TaskMergeService.cs (nur die Progress-Schleife) |
locales/*, alles im Ui-Projekt |
Locale-Regel: Nur P0 schreibt in en.json/de.json. Braucht eine Gruppe doch einen Key,
den P0 nicht vorgesehen hat: als letzte Änderung des Pakets anhängen und im Merge-Helper
als bekannte Konfliktstelle behandeln. Nie mitten in der Gruppe.
Vorgaben für den Merge-Helper selbst
Gilt für alle vier Gruppen:
- Agent-Modell:
sonnetfür Implementierer und Reviewer. Nie haiku, nie opus, nie Fable. maxTurnsexplizit auf 200 setzen.model_presetsist NULL, sonst bekommt ein sonnet-Task 30 Turns und stirbt mit „exited with code 1 and no result".serializeOnFileOverlapauf der Gruppenliste einschalten. Innerhalb einer Gruppe fassen mehrere Pakete dieselben Dateien an.- Bauen:
dotnet build ClaudeDo.slnxschlägt auf .NET 8 fehl. Einzelprojekte mit-c Releasebauen (ein laufender Worker sperrtDebug):dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj -c Release - Nie
git add -A. Immer explizit nach Pfad stagen undgit commit -- <pfade>— dermain-Checkout ist von parallelen Sessions geteilt, ein blankes Commit fegt fremde WIP mit. - Nach dem Batch-Merge
mainselbst prüfen. Approve baut und testet nicht. Neun grüne Branches habenmainschon zweimal zerlegt, davon einmal durch eine Test-Kollision, die ein reiner src-Build nicht sieht. Also: Build und die betroffenen Testprojekte. - Vor Approve prüfen, ob der Branch überhaupt etwas enthält (
changedFileCount). Ein blockierter Task wird sonstDonemit leerem Branch. - Keine EF-Migration in diesem Vorhaben. Parallele Migrationen aus Geschwister-Branches
löschen sich beim SQLite-Table-Rebuild gegenseitig die Spalten, und die Tests bemerken es
nicht (
EnsureCreated). - Keine Tests, die die echte
claude-CLI starten. - Visuelle Prüfung ist nicht durch Tests ersetzbar. Jedes Paket in A und B endet mit einer offenen visuellen Prüfung für den Nutzer. Nie behaupten, die UI funktioniere.
P0 — Fundament
- P0-1 —
OperationStatus+OperationIndicator+ Locale-Keys - P0-2 — Timing-Hook für die Messung
- P0-3 — Regel in den CLAUDE.md-Dateien
Vorbedingung: keine. Muss allein landen, bevor A/B/C starten.
Der Vertrag (bindend — 19 Pakete hängen daran)
src/ClaudeDo.Ui/Services/OperationStatus.cs, eine ObservableObject-Klasse:
| Member | Verhalten |
|---|---|
bool IsRunning |
sofort true bei Begin. Treibt CanExecute, verhindert Doppelklick |
bool ShowIndicator |
erst nach 300 ms true. Kein Flackern bei schnellen Calls |
string? Label |
lokalisierter Text |
string Elapsed |
mm:ss, lokal getickt |
bool IsStalled |
true nach 60 s ohne Report |
IDisposable Begin(string label) |
startet; Dispose beendet auch im Exception-Fall |
void Report(string label) |
überschreibt Label mid-flight, setzt die Stall-Uhr zurück |
Nachbesserung 1 (bindend): IsStalled bemisst sich an der Zeit seit dem letzten
Report, nicht an der Gesamtdauer. Ein regulär mehrminütiges Verify-Gate darf sich nicht
selbst als hängend melden. Eine Operation ohne jeden Report gilt nach 60 s als stalled — das
ist genau der Fall, der heute wie ein Absturz aussieht.
Benutzung im ViewModel:
using var op = Approve.Begin(Loc.T("ops.merge.merging"));
var result = await _worker.ApproveReviewAsync(...);
Weitere Vorgaben
- Zeitquelle injizierbar über
TimeProvider(in .NET 8 vorhanden). Kein statischerDispatcherTimer— geteilter statischer State ist die Ursache der reihenfolgen-abhängigen Flakiness in denUi.Tests. Timer-Callbacks kommen vom Threadpool und müssen auf den Dispatcher gepostet werden. - Mehrere
OperationStatuspro VM sind erwünscht.WorktreesOverviewModalViewModelbraucht getrennte für Refresh, Cleanup und Merge — sonst blockiert ein laufender Refresh die Cleanup-Anzeige. OperationStatustransportiert keine Fehler. Fehlerbehandlung bleibt unverändert überShowErrorAsync/ErrorReported/FlashFooterError.OperationIndicatornutztEllipse.spinnerausDesign/IslandStyles.axaml(14×14, Accent, 0.9 s Rotation) plus Label und Elapsed. Ohne dieses Control wird die Spinner-StackPanel ausMergeModalView.axamlacht Mal von Hand nachgebaut. Werte ausTokens.axamlverwenden, keine Inline-Zahlen.- Locale-Keys: neuer Top-Level-Namespace
opsinen.jsonundde.json, Parität wird vonLocalization.Testserzwungen. P0 legt die Keys für A und C vorab an — abgeleitet aus den Paketlisten unten.
Nachbesserung 3 (bindend): Gruppe D braucht keine Locale-Keys. MCP-Progress-Meldungen gehen an Agenten, nicht an den Nutzer, und bleiben englische Klartext-Strings.
P0-2: Messung an genau einer Stelle
Zwei Hooks, nicht zwanzig:
- In
WorkerClientjeden Hub-Invoke mit Dauer loggen. - Im DB-Pfad der Islands dasselbe.
Ein Tag Nutzung liefert eine sortierte Liste echter Ausreißer. A5 und C werten sie aus, statt zu raten. Bild 2 und 4 brauchen keine Messung.
Faustregel für den Zweifelsfall: instrumentiert wird, was git aufruft, Netz nutzt, einen Prozess startet, oder O(n) über unbegrenzt viele DB-Zeilen läuft. Einzelzeilen-Reads nicht.
Definition of Done für P0
OperationStatus hat Unit-Tests mit einem Fake-TimeProvider für: 300-ms-Grace, 60-s-Stall
ab letztem Report, Elapsed-Formatierung, Dispose nach Exception, Report überschreibt
Label und setzt die Stall-Uhr zurück. Localization.Tests grün. Kein VM ist umgestellt — P0
liefert nur das Werkzeug.
Gruppe A — Bild 1: UI-Stille
- A1 — Detail-Pane ⚠️ der gemeldete Fall
- A2 — WorktreesOverview
- A3 — Settings-Tabs
- A4 — Reports und Planning-UI
- A5 — Island-DB-Pfade nach Messdaten
Vorbedingung: P0 gemergt.
Vorgaben
-
A1 zuerst.
DetailsIslandViewModel.ApproveReviewAsyncist der gemeldete Schmerz. Dazu im selben Paket:SubmitForReviewAsync,RejectReviewAsync,ParkReviewAsyncundMergeSectionViewModel.PreviewMergeAsync. -
A1 abonniert
IWorkerClient.MergeProgressEvent, um das Label mid-flight von „merging" auf „verifying" zu schärfen. Das Event existiert seitcad0582.Nachbesserung 2 (bindend): A verlässt sich darauf, dass dieses Event bestehen bleibt. C1 baut den generischen Kanal darunter, muss
MergeProgressEventaber als dünnen Forwarder erhalten. Andernfalls müsste CDetailsIslandViewModeländern — eine Datei aus Gruppe A — und die Parallelität wäre zerstört. A darf keinen anderen Kanal verwenden. -
Abo nur für die Dauer des Calls. Der Worker broadcastet an alle Clients; ein dauerhaft registriertes VM bekommt Events für fremde Tasks.
MergeModalViewModelmacht es richtig vor:+=imtry,-=imfinally, plusif (taskId != TaskId) return;. -
A2:
IsBusyexistiert dort schon, schaltet aber nurIsEnabled. Die Anzeige fehlt komplett. Betrifft Refresh, Cleanup, Reset, ForceRemove und Batch-Merge — Batch-Merge zusätzlich mit Zeilen-Status, weil dort N Tasks sequenziell durchlaufen. -
A3: SessionSkill-Install ist ein
git clone— der offensichtlichste Kandidat. Dazu Update/Restore-Defaults, OnlineInbox-SignIn, RepoImport-Scan, Update-Check. -
A4: GenerateWeekReport, RunDailyPrepNow, BuildPlanningIntegrationBranch, GetPlanningAggregate, Finalize/QueuePlanningSubtasks.
DiffViewerViewModel.IsLoadingCombinedexistiert bereits und bleibt — nicht doppeln. -
A5 erst nach Auswertung von P0-2. Nur Stellen umstellen, die die Messung als Ausreißer zeigt. Kein Umstellen auf Verdacht.
-
DetailsIslandViewModelist groß. Nicht umstrukturieren, nur die Commands anfassen. -
Bereits saubere Flächen nicht anfassen:
MergeModalViewModel,ConflictResolverViewModel,DiffViewerViewModel.IsLoadingCombined,UsageMonitorModalViewModel.
Definition of Done pro Paket
Jeder umgestellte Command: IsRunning während des Laufs gesetzt, CanExecute gesperrt,
Zustand nach einer Exception zurückgesetzt — als Test in ClaudeDo.Ui.Tests. Kein
handgebauter Spinner, immer OperationIndicator. Visuelle Prüfung offen und explizit
benannt (Grace-Periode und Layout kann kein Test bestätigen).
Gruppe B — Bild 2: UI-Freeze
- B1 —
UnifiedDiffParser.Parseauslagern - B2 —
DiffAlignment.BuildGrenze - B3 — Guard-Test gegen Dispatcher-Blockade
Vorbedingung: P0 gemergt (für den Indikator in B1).
Vorgaben
- Reihenfolge ist zwingend: erst auslagern, dann anzeigen. Ein Spinner auf einem
blockierten UI-Thread wird nicht gezeichnet. B1 verschiebt
UnifiedDiffParser.ParsenachTask.Runund setzt danach den Indikator. UnifiedDiffParserist statisch und rein — thread-safe,Task.Runist unkritisch. Das Zurückschreiben der Ergebnisse in Observable-Collections muss auf dem UI-Thread passieren.- B2 braucht eine Entscheidung, die der Merge-Helper trifft:
DiffAlignment.Buildläuft inDiffTextView.axaml.cs— in einem Control, nicht in einem VM. Entweder Grenze („ab N Zeilen auslagern") oder inkrementeller Aufbau. Die Wahl gehört ins Paket, nicht hierher; die Vorgabe ist nur: eine sichtbare Grenze definieren, keine unbegrenzte Synchron-Arbeit. - AvaloniaEdit-Fallen (belegt, nicht neu ausprobieren):
this.TryGetResourcein einem Control findet Brushes ausTokens.axamlnie und scheitert lautlos; Brushes und Typeface nicht im Konstruktor auflösen.ScrollToVerticalOffsetist in v12 ein No-op — schreiben überScrollViewer.Offset, lesen überTextView.ScrollOffsetChanged. - Gemeinsames Editor-Boilerplate gehört in
Views/Controls/DiffEditorSetup.cs, nicht ein drittes Mal kopiert.
Definition of Done
B3 ist der einzige Test im Vorhaben, der Blockade prüft: großes Diff-Fixture, Nachweis, dass der Dispatcher weiter Nachrichten verarbeitet. Visuelle Prüfung offen (Verhalten bei großem Diff, Indikator während des Parsens).
Gruppe C — Bild 3: Worker-Stille
- C1 — generischer
OperationProgress-Kanal - C2 — Startup-Recovery sichtbar
- C3 — Worktree-Anlage sichtbar
- C4 — Rebase-after-Merge und WorktreeMaintenance
- C5 — periodische Dienste
Vorbedingung: P0 gemergt. Die Abhängigkeit zur Parallelsession ist mit cad0582
aufgelöst.
Vorgaben
- Der Kanal:
OperationProgress(string opKey, string phase, int current, int total)opKey= TaskId bei task-gebundenen Operationen, sonst ein stabiler String ("worktree-cleanup","startup-recovery","planning-integration:<taskId>"). - Kein Elapsed auf der Leitung. Das UI tickt lokal. Die heutige Implementierung lässt den Worker alle 30 s ticken — damit zeigt sie die ersten 30 Sekunden nur „merging". C1 stellt das um.
MergeProgressEventbleibt als Forwarder (siehe Nachbesserung 2 unter Gruppe A). Vier Zeilen. Entfernen erst, wenn A und C beide gemergt sind — nicht in dieser Gruppe.current/totalwird als Text gezeigt, nie als Prozentbalken. Die meisten Operationen haben kein sinnvolles Total.- C3 zuerst prüfen, nicht bauen:
docs/superpowers/specs/2026-08-07-ui-reaktivitaet-und-listen-performance-design.mdführtWorktreeManager.cs:103als DB-Write-ohne-Broadcast. Ob das inzwischen geschlossen ist, gehört geprüft, bevor es doppelt behoben wird. - C5 ist der wackeligste Punkt im Design. Periodische Dienste (Usage, OnlineSync, Prime, Queue) dürfen nur Aktivität und Fehler melden, keinen Tick-Strom. Wenn der Merge-Helper beim Umsetzen zum Schluss kommt, dass C5 nur Rauschen erzeugt: weglassen und begründen, statt es durchzuziehen.
- Test-Fakes wachsen mit. Ein neues Event auf
IWorkerClient/WorkerHubbricht handgeschriebene Fakes in beiden Testprojekten —tests/ClaudeDo.Ui.Tests/StubWorkerClient.csund die UiVm-Tests untertests/ClaudeDo.Worker.Tests/. - Broadcast-Sparsamkeit: vor einem „fehlenden Broadcast" immer den Aufrufer prüfen. Im 08-07-Spec war ein gemeldetes Loch in Wahrheit schon vom Aufrufer abgedeckt, und der Fix wäre ein Duplikat gewesen.
Definition of Done
Progress-Callbacks in ClaudeDo.Worker.Tests mit einem Fake gezählt — keine echten Timeouts,
keine echte CLI. UI-Seite: die Anzeige beim Worker-Start ersetzt „reconnecting" durch die
laufende Recovery-Phase. Visuelle Prüfung offen.
Gruppe D — Bild 4: MCP-Stille
- D1 —
ProgressReporterextrahieren - D2 — die 8
batch_*-Tools - D3 — Worktree- und Diff-Tools
- D4 — Rest und Doku-Regel
Vorbedingung: keine. Kann sofort starten, parallel zu P0.
Vorgaben
- Warum das zählt: ohne Progress bricht der MCP-Client bei Stille nach 300 s ab, während
der Worker weiterarbeitet. Das ist die dokumentierte Ursache dafür, dass ein Merge zwar
durchläuft, den Task aber nie auf
Donebringt und Abhängige dauerhaft blockiert. - D1:
TaskMergeService.RunReportingProgressAsyncist die einzige existierende Progress-Schleife. Als eigenständige Klasse extrahieren, plus ein Overload für Element-Fortschritt (i/n). Nicht kopieren — extrahieren, damit es eine Implementierung bleibt. TaskMergeServiceist die einzige Datei, die D mit C teilt. D fasst dort ausschließlich die Progress-Schleife an. Wenn beide Gruppen gleichzeitig laufen, ist das die Stelle, die der Merge-Helper im Auge behalten muss.- Keine Locale-Keys (Nachbesserung 3). Englische Klartext-Strings.
- Zielumfang: die 8
batch_*-Tools miti/n, danncleanup_task_worktree,batch_cleanup_task_worktrees,get_task_diff,preview_merge_set. Bereits versorgt und nicht anzufassen: die 5 Tools inExternalMcpServicemitIProgress,TaskWaitMcpTools,merge_task/review_task. - D4 schreibt die Regel ins Worker-
CLAUDE.md: ein MCP-Tool, das über ~5 s laufen kann, reportet Progress. Ohne die Regel wiederholt sich das Muster beim nächsten Tool.
Definition of Done
Für jedes umgestellte Tool ein Test in ClaudeDo.Worker.Tests, der mit einem Fake-IProgress
zählt, dass Meldungen kommen — bei batch_* mindestens eine pro Element. Keine visuelle
Prüfung nötig, diese Gruppe hat keine UI.
Was nach allen vier Gruppen offen bleibt
- Visuelle Prüfung für alle Pakete in A und B — der Nutzer, nicht ein Agent.
- Auswertung von P0-2 steuert A5 und die Feinheiten in C. Vorher ist A5 absichtlich unbestimmt.
MergeProgressEvent-Forwarder entfernen, sobald A und C beide gemergt sind. Ein Aufräum-Task, kein Paket.- Bewusst nicht gebaut: Cancel, Footer-Anzeige für laufende Operationen, Prozentbalken, Änderungen am Installer (der hat bereits eine vollständige Progress-Pipeline).