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.
516 lines
30 KiB
Markdown
516 lines
30 KiB
Markdown
# 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:** `sonnet` für Implementierer und Reviewer. Nie haiku, nie opus, nie Fable.
|
||
- **`maxTurns` explizit auf 200 setzen.** `model_presets` ist NULL, sonst bekommt ein
|
||
sonnet-Task 30 Turns und stirbt mit „exited with code 1 and no result".
|
||
- **`serializeOnFileOverlap` auf der Gruppenliste einschalten.** Innerhalb einer Gruppe
|
||
fassen mehrere Pakete dieselben Dateien an.
|
||
- **Bauen:** `dotnet build ClaudeDo.slnx` schlägt auf .NET 8 fehl. Einzelprojekte mit
|
||
`-c Release` bauen (ein laufender Worker sperrt `Debug`):
|
||
```
|
||
dotnet build src/ClaudeDo.App/ClaudeDo.App.csproj -c Release
|
||
dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj -c Release
|
||
```
|
||
- **Testen — pro Gruppe die relevanten Projekte:**
|
||
```
|
||
A, B dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release
|
||
dotnet test tests/ClaudeDo.Localization.Tests/... -c Release (nur wenn Keys berührt)
|
||
C Ui.Tests + ClaudeDo.Worker.Tests (Worker.Tests enthält auch UiVm-Tests)
|
||
D dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release
|
||
P0 Ui.Tests + Localization.Tests
|
||
```
|
||
Fällt ein Test innerhalb der Verify-Kette, erst die Suite **allein** laufen lassen — die
|
||
`Ui.Tests` haben eine bekannte reihenfolgen-abhängige Flakiness, die nichts mit dem Merge zu
|
||
tun hat.
|
||
- **Nie `git add -A`.** Immer explizit nach Pfad stagen und `git commit -- <pfade>` — der
|
||
`main`-Checkout ist von parallelen Sessions geteilt, ein blankes Commit fegt fremde WIP mit.
|
||
- **Nach dem Batch-Merge `main` selbst prüfen.** Approve baut und testet nicht. Neun grüne
|
||
Branches haben `main` schon 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 sonst `Done` mit 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
|
||
|
||
- [x] **P0-1 — `OperationStatus` + `OperationIndicator` + Locale-Keys**
|
||
- [x] **P0-2 — Timing-Hook für die Messung**
|
||
- [x] **P0-3 — Regel in den CLAUDE.md-Dateien**
|
||
|
||
> **Stand 2026-08-17.** P0, A (außer A5), B, C1 und D sind gemergt; der visuelle Pass über A und B
|
||
> ist durch, ohne Fund. Nachtrag zu P0-2: der Timing-Sink liegt seit dem Release-Vorlauf hinter
|
||
> `CLAUDEDO_OP_TIMING=1` und ist **standardmäßig aus** — die 63 `InvokeTimedAsync`-Call-Sites
|
||
> bleiben bewusst stehen. Offen: C2–C5.
|
||
|
||
**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:
|
||
|
||
```csharp
|
||
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 statischer
|
||
`DispatcherTimer`** — geteilter statischer State ist die Ursache der reihenfolgen-abhängigen
|
||
Flakiness in den `Ui.Tests`. Timer-Callbacks kommen vom Threadpool und müssen auf den
|
||
Dispatcher gepostet werden.
|
||
- **Mehrere `OperationStatus` pro VM sind erwünscht.** `WorktreesOverviewModalViewModel`
|
||
braucht getrennte für Refresh, Cleanup und Merge — sonst blockiert ein laufender Refresh die
|
||
Cleanup-Anzeige.
|
||
- **`OperationStatus` transportiert keine Fehler.** Fehlerbehandlung bleibt unverändert über
|
||
`ShowErrorAsync` / `ErrorReported` / `FlashFooterError`.
|
||
- **`OperationIndicator`** nutzt `Ellipse.spinner` aus `Design/IslandStyles.axaml` (14×14,
|
||
Accent, 0.9 s Rotation) plus Label und Elapsed. Ohne dieses Control wird die
|
||
Spinner-StackPanel aus `MergeModalView.axaml` acht Mal von Hand nachgebaut. Werte aus
|
||
`Tokens.axaml` verwenden, keine Inline-Zahlen.
|
||
- **Locale-Keys:** neuer Top-Level-Namespace `ops` in `en.json` und `de.json`, Parität wird von
|
||
`Localization.Tests` erzwungen. 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:
|
||
1. In `WorkerClient` jeden Hub-Invoke mit Dauer loggen.
|
||
2. 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.
|
||
|
||
### Task-Entwürfe (grob)
|
||
|
||
**P0-1 · `OperationStatus` + `OperationIndicator` + `ops`-Locale-Keys**
|
||
Baue das Primitive nach dem Vertrag oben, dazu das Anzeige-Control und den neuen
|
||
Locale-Namespace `ops` in beiden Sprachdateien. Keys für Gruppe A und C vorab anlegen, für D
|
||
keine. Zeitquelle über `TimeProvider`, kein statischer Timer.
|
||
*Dateien:* `Ui/Services/OperationStatus.cs` (neu), `Ui/Views/Controls/OperationIndicator.axaml(.cs)` (neu), `Localization/locales/{en,de}.json`, `tests/ClaudeDo.Ui.Tests/`
|
||
*Fertig wenn:* Unit-Tests für Grace, Stall-ab-Report, Elapsed, Dispose-nach-Exception, Report-Reset. Localization.Tests grün. Kein ViewModel angefasst.
|
||
|
||
**P0-2 · Timing-Hook für die Messung**
|
||
Zwei Hooks: Dauer jedes Hub-Invokes in `WorkerClient`, Dauer des DB-Pfads der Islands. Ziel
|
||
sind auswertbare Zahlen, keine Anzeige. Ausgabe so, dass sie nach einem Tag Nutzung sortierbar
|
||
ist.
|
||
*Dateien:* `Ui/Services/WorkerClient.cs`, der gemeinsame DB-Zugriffspfad der Island-VMs
|
||
*Fertig wenn:* Beide Hooks aktiv, Overhead vernachlässigbar, das Format ist dokumentiert.
|
||
|
||
**P0-3 · Feedback-Regel in den CLAUDE.md-Dateien**
|
||
`src/ClaudeDo.Ui/CLAUDE.md`: jeder `IWorkerClient`-Call in einem `[RelayCommand]` läuft durch
|
||
eine `OperationStatus`, Anzeige über `OperationIndicator`. `src/ClaudeDo.Worker/CLAUDE.md`: ein
|
||
MCP-Tool, das über ~5 s laufen kann, reportet Progress.
|
||
*Fertig wenn:* Beide Regeln stehen, jeweils mit einem Satz Begründung.
|
||
|
||
---
|
||
|
||
## Gruppe A — Bild 1: UI-Stille
|
||
|
||
- [x] **A1 — Detail-Pane** ⚠️ *der gemeldete Fall*
|
||
- [x] **A2 — WorktreesOverview**
|
||
- [x] **A3 — Settings-Tabs** — ohne OnlineInbox-SignIn, siehe Nachzügler unten
|
||
- [x] **A4 — Reports und Planning-UI** — ohne die beiden Planning-Calls im `DiffViewerViewModel`
|
||
- [x] ~~**A5 — Island-DB-Pfade nach Messdaten**~~ — **abgesagt** (2026-08-13): kein DB-Pfad über
|
||
500 ms (höchster p95 372 ms), die Ausreißer saßen alle auf der Hub-Seite. Wie vorgesehen
|
||
geschlossen, ohne Code.
|
||
|
||
**Nachzügler (kein Task):** drei Locale-Keys aus P0 sind ungenutzt geblieben —
|
||
`ops.onlineInbox.signingIn` (A3), `ops.planning.buildingIntegrationBranch` und
|
||
`ops.planning.loadingAggregate` (A4). Die beiden Planning-Calls liegen in
|
||
`DiffViewerViewModel.cs`, einer Datei aus Gruppe B, die A4 nicht anfassen durfte.
|
||
|
||
**Vorbedingung:** P0 gemergt.
|
||
|
||
### Vorgaben
|
||
|
||
- **A1 zuerst.** `DetailsIslandViewModel.ApproveReviewAsync` ist der gemeldete Schmerz. Dazu im
|
||
selben Paket: `SubmitForReviewAsync`, `RejectReviewAsync`, `ParkReviewAsync` und
|
||
`MergeSectionViewModel.PreviewMergeAsync`.
|
||
- **A1 abonniert `IWorkerClient.MergeProgressEvent`**, um das Label mid-flight von „merging" auf
|
||
„verifying" zu schärfen. Das Event existiert seit `cad0582`.
|
||
|
||
**Nachbesserung 2 (bindend):** A verlässt sich darauf, dass dieses Event **bestehen bleibt**.
|
||
C1 baut den generischen Kanal darunter, muss `MergeProgressEvent` aber als dünnen Forwarder
|
||
erhalten. Andernfalls müsste C `DetailsIslandViewModel` ä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. `MergeModalViewModel` macht es richtig
|
||
vor: `+=` im `try`, `-=` im `finally`, plus `if (taskId != TaskId) return;`.
|
||
- **A2:** `IsBusy` existiert dort schon, schaltet aber nur `IsEnabled`. 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.IsLoadingCombined`
|
||
existiert 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.
|
||
- **`DetailsIslandViewModel` ist 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).
|
||
|
||
### Task-Entwürfe (grob)
|
||
|
||
**A1 · Detail-Pane: Approve, Submit, Reject, Park, Preview** ⚠️ *der gemeldete Fall*
|
||
Fünf Commands im Detail-Pane bekommen je eine `OperationStatus` und einen `OperationIndicator`
|
||
neben dem auslösenden Button. Approve abonniert zusätzlich `MergeProgressEvent` **nur für die
|
||
Dauer des Calls** und filtert auf die eigene TaskId, um das Label von „merging" auf „verifying"
|
||
zu schärfen. `DetailsIslandViewModel` nicht umstrukturieren.
|
||
*Dateien:* `Ui/ViewModels/Islands/DetailsIslandViewModel.cs`, `MergeSectionViewModel.cs`, `Ui/Views/Islands/Detail/*`
|
||
*Fertig wenn:* Approve zeigt während des Verify-Gates Phase und Elapsed, Button ist gesperrt, Zustand nach Fehler zurückgesetzt. Visuelle Prüfung offen.
|
||
|
||
**A2 · WorktreesOverview: Anzeige für alle fünf Aktionen**
|
||
`IsBusy` existiert, schaltet aber nur `IsEnabled`. Getrennte `OperationStatus` für Refresh,
|
||
Cleanup, Reset, ForceRemove und Batch-Merge — der Batch-Merge zusätzlich mit Zeilen-Status, weil
|
||
er N Tasks sequenziell durchläuft.
|
||
*Dateien:* `Ui/ViewModels/Modals/WorktreesOverviewModalViewModel.cs`, `Ui/Views/Modals/WorktreesOverviewModalView.axaml`
|
||
*Fertig wenn:* Jede der fünf Aktionen zeigt sichtbar, dass sie läuft; ein laufender Refresh blockiert die Cleanup-Anzeige nicht. Visuelle Prüfung offen.
|
||
|
||
**A3 · Settings-Tabs: Skill-Install, Restore-Defaults, OnlineInbox, RepoImport, Update-Check**
|
||
Fünf Flächen, bei denen `IsBusy` heute nur den Button sperrt. Der Skill-Install ist ein
|
||
`git clone` und der offensichtlichste Kandidat.
|
||
*Dateien:* `Ui/ViewModels/Modals/Settings/*`, `Ui/ViewModels/Modals/RepoImportModalViewModel.cs`, `Ui/Services/UpdateCheckService.cs`, `Ui/Views/Modals/SettingsModalView.axaml`
|
||
*Fertig wenn:* Alle fünf zeigen Aktivität; der Skill-Install zeigt zusätzlich, dass geklont wird. Visuelle Prüfung offen.
|
||
|
||
**A4 · Reports und Planning-UI**
|
||
GenerateWeekReport, RunDailyPrepNow, BuildPlanningIntegrationBranch, GetPlanningAggregate,
|
||
Finalize/QueuePlanningSubtasks. `DiffViewerViewModel.IsLoadingCombined` existiert bereits und
|
||
bleibt unangetastet.
|
||
*Dateien:* `Ui/ViewModels/Modals/WeeklyReportModalViewModel.cs`, Planning-Pfade in `DetailsIslandViewModel`/`TasksIslandViewModel`, zugehörige AXAML
|
||
*Fertig wenn:* Report-Generierung und Planning-Integration zeigen Aktivität; kein Doppel-Indikator im Combined-Diff. Visuelle Prüfung offen.
|
||
|
||
**A5 · Island-DB-Pfade nach Messdaten**
|
||
**Erst die Zahlen aus P0-2 auswerten**, dann nur die Ausreißer umstellen. Kandidaten:
|
||
`ClearCompleted`, Drag-Reorder über viele Zeilen, Laden großer Listen. Nichts auf Verdacht.
|
||
*Dateien:* nach Messergebnis
|
||
*Fertig wenn:* Die Auswertung ist im Task dokumentiert, und jede umgestellte Stelle ist durch eine Messung begründet. Wenn nichts über der Schwelle liegt: Task mit Begründung schließen, nichts bauen.
|
||
|
||
---
|
||
|
||
## Gruppe B — Bild 2: UI-Freeze
|
||
|
||
- [x] **B1 — `UnifiedDiffParser.Parse` auslagern**
|
||
- [x] **B2 — `DiffAlignment.Build` Grenze**
|
||
- [x] **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.Parse` nach
|
||
`Task.Run` und setzt danach den Indikator.
|
||
- **`UnifiedDiffParser` ist statisch und rein** — thread-safe, `Task.Run` ist 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.Build` läuft in
|
||
`DiffTextView.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.TryGetResource` in einem
|
||
Control findet Brushes aus `Tokens.axaml` **nie** und scheitert lautlos; Brushes und Typeface
|
||
nicht im Konstruktor auflösen. `ScrollToVerticalOffset` ist in v12 ein No-op — schreiben über
|
||
`ScrollViewer.Offset`, lesen über `TextView.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).
|
||
|
||
### Task-Entwürfe (grob)
|
||
|
||
**B1 · `UnifiedDiffParser.Parse` vom UI-Thread nehmen**
|
||
Beide Aufrufstellen im `DiffViewerViewModel` nach `Task.Run` verschieben, danach den
|
||
`OperationIndicator` setzen. Reihenfolge ist zwingend: erst auslagern, dann anzeigen — ein
|
||
Spinner auf einem blockierten Dispatcher wird nicht gezeichnet. Das Zurückschreiben in
|
||
Observable-Collections muss auf dem UI-Thread passieren.
|
||
*Dateien:* `Ui/ViewModels/Modals/DiffViewerViewModel.cs`
|
||
*Fertig wenn:* Kein synchroner Parse mehr im VM, Indikator während des Parsens sichtbar. Visuelle Prüfung mit einem großen Diff offen.
|
||
|
||
**B2 · `DiffAlignment.Build` — Grenze gegen unbegrenzte Synchron-Arbeit**
|
||
`Build` läuft in einem Control, nicht in einem VM. Der ausführende Agent entscheidet zwischen
|
||
Auslagern ab einer Zeilenzahl und inkrementellem Aufbau — die Vorgabe ist nur: **eine
|
||
sichtbare, begründete Grenze**. AvaloniaEdit-Fallen aus dem Vorgaben-Abschnitt beachten
|
||
(`TryGetResource` scheitert lautlos, `ScrollToVerticalOffset` ist ein No-op).
|
||
*Dateien:* `Ui/Views/Controls/DiffTextView.axaml.cs`, gemeinsames Boilerplate nach `DiffEditorSetup.cs`
|
||
*Fertig wenn:* Die gewählte Grenze ist im Code kommentiert und begründet; große Diffs blockieren nicht mehr unbegrenzt. Visuelle Prüfung offen.
|
||
|
||
**B3 · Guard-Test: großer Diff blockiert den Dispatcher nicht**
|
||
Headless-Test mit großem Diff-Fixture, der nachweist, dass der Dispatcher während Parse und
|
||
Build weiter Nachrichten verarbeitet. Der einzige Blockade-Test im Vorhaben — soll auch
|
||
zukünftige Regressionen fangen.
|
||
*Dateien:* `tests/ClaudeDo.Ui.Tests/`, Fixture im Testprojekt
|
||
*Fertig wenn:* Der Test fällt gegen den Stand **vor** B1/B2 und ist danach grün.
|
||
|
||
---
|
||
|
||
## Gruppe C — Bild 3: Worker-Stille
|
||
|
||
- [x] **C1 — generischer `OperationProgress`-Kanal**
|
||
- [ ] **C2 — Startup-Recovery sichtbar**
|
||
- [ ] **C3 — Worktree-Anlage sichtbar**
|
||
- [ ] **C4 — Rebase-after-Merge und WorktreeMaintenance**
|
||
- [ ] **C5 — periodische Dienste**
|
||
|
||
> **Einzige offene Gruppe.** C2–C5 sind unblockiert (C1 ist gemergt). Woran man den Reststand
|
||
> erkennt: die vier Keys `ops.worker.startupRecovery`, `.creatingWorktree`, `.rebasingAfterMerge`
|
||
> und `.maintainingWorktrees` liegen ungenutzt in beiden Sprachdateien.
|
||
|
||
**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.
|
||
- **`MergeProgressEvent` bleibt als Forwarder** (siehe Nachbesserung 2 unter Gruppe A). Vier
|
||
Zeilen. Entfernen erst, wenn A und C beide gemergt sind — nicht in dieser Gruppe.
|
||
- **`current`/`total` wird 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.md` führt
|
||
`WorktreeManager.cs:103` als 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`/`WorkerHub` bricht
|
||
handgeschriebene Fakes in **beiden** Testprojekten — `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs`
|
||
und die UiVm-Tests unter `tests/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.
|
||
|
||
### Task-Entwürfe (grob)
|
||
|
||
**C1 · Generischer `OperationProgress`-Kanal, `MergeProgress` darauf umstellen**
|
||
Neues Hub-Event `OperationProgress(opKey, phase, current, total)`, ohne Elapsed auf der Leitung.
|
||
Der Merge-Pfad sendet darüber. **`MergeProgressEvent` bleibt als vierzeiliger Forwarder** —
|
||
Gruppe A hängt daran und darf nicht angefasst werden. Fakes in beiden Testprojekten mitziehen.
|
||
*Dateien:* `Worker/Hub/HubBroadcaster.cs`, `Worker/Hub/WorkerHub.cs`, `Ui/Services/WorkerClient.cs`, `IWorkerClient.cs`, `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs`, UiVm-Fakes in `Worker.Tests`
|
||
*Fertig wenn:* Merge-Phasen laufen über den neuen Kanal, das UI tickt Elapsed lokal, `MergeProgressEvent` funktioniert unverändert weiter, beide Testprojekte grün.
|
||
|
||
**C2 · Worker-Startup-Recovery sichtbar machen**
|
||
Die sechs Recovery-Services beim Worker-Start melden Phase und `i/n` über den Kanal
|
||
(`opKey = "startup-recovery"`). Das UI ersetzt „reconnecting" durch die laufende Phase.
|
||
*Dateien:* `Worker/Lifecycle/*Recovery.cs`, Anzeige im `IslandsShellViewModel`-Umfeld
|
||
*Fertig wenn:* Beim Worker-Start ist sichtbar, welche Recovery läuft. Visuelle Prüfung offen.
|
||
|
||
**C3 · Worktree-Anlage beim Task-Start sichtbar machen**
|
||
**Zuerst prüfen**, ob der im 08-07-Spec gemeldete Broadcast-Gap in `WorktreeManager` inzwischen
|
||
geschlossen ist — nicht doppelt beheben. Danach die stille Lücke zwischen `Queued` und erster
|
||
Ausgabe an der Task-Zeile sichtbar machen.
|
||
*Dateien:* `Worker/Runner/WorktreeManager.cs`, Task-Row-Anzeige
|
||
*Fertig wenn:* Das Prüfergebnis steht im Task; die Anlage-Phase ist an der Zeile sichtbar. Visuelle Prüfung offen.
|
||
|
||
**C4 · Rebase-after-Merge und WorktreeMaintenance in den Kanal**
|
||
`RebaseOthersAfterMergeAsync` läuft heute innerhalb des Merge-Calls **nach** dem
|
||
Phasen-Broadcast und ist damit vollständig unsichtbar. Dazu der Hintergrunddienst über alle
|
||
Worktrees, mit `i/n`.
|
||
*Dateien:* `Worker/Lifecycle/TaskMergeService.cs` (Rebase-Abschnitt), `Worker/Worktrees/WorktreeMaintenanceService.cs`
|
||
*Fertig wenn:* Beide melden Fortschritt; ein Merge zeigt nach dem Verify-Gate die Rebase-Phase statt Stille.
|
||
|
||
**C5 · Periodische Dienste — optional, Abbruch erlaubt**
|
||
Usage, OnlineSync, Prime, Queue sollen **nur Aktivität und Fehler** melden, keinen Tick-Strom.
|
||
Der wackeligste Punkt im Design: kommt der ausführende Agent zum Schluss, dass das nur
|
||
Anzeige-Rauschen erzeugt, **weglassen und begründen** statt durchziehen.
|
||
*Dateien:* `Worker/Usage/`, `Worker/Online/`, `Worker/Prime/`, `Worker/Queue/`
|
||
*Fertig wenn:* Entweder sparsame Meldungen ohne Rauschen — oder eine begründete Absage im Task.
|
||
|
||
---
|
||
|
||
## Gruppe D — Bild 4: MCP-Stille
|
||
|
||
- [x] **D1 — `ProgressReporter` extrahieren**
|
||
- [x] **D2 — die `batch_*`-Tools** — es sind **sieben**, nicht acht (die Achterzahl unten war ein
|
||
Zählfehler beim Schreiben des Plans)
|
||
- [x] **D3 — Worktree- und Diff-Tools** — drei davon; `batch_cleanup_task_worktrees` lief über D2
|
||
- [x] **D4 — Rest und Doku-Regel** — fand zwei echte Lücken, die D1–D3 offen gelassen hatten:
|
||
`continue_merge` reichte sein Progress-Token nicht ins Post-Merge-Verify-Gate, und der
|
||
Unit-Merge-Drain im `PlanningMergeOrchestrator` verlor es nach dem ersten Tick. Dazu
|
||
`list_worktrees`.
|
||
|
||
**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 `Done` bringt und Abhängige dauerhaft blockiert.
|
||
- **D1:** `TaskMergeService.RunReportingProgressAsync` ist 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.
|
||
- **`TaskMergeService` ist 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 7 `batch_*`-Tools mit `i/n`, dann `cleanup_task_worktree`,
|
||
`batch_cleanup_task_worktrees`, `get_task_diff`, `preview_merge_set`. Bereits versorgt und
|
||
nicht anzufassen: die 5 Tools in `ExternalMcpService` mit `IProgress`, `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.
|
||
|
||
### Task-Entwürfe (grob)
|
||
|
||
**D1 · `ProgressReporter` extrahieren**
|
||
`TaskMergeService.RunReportingProgressAsync` als eigenständige Klasse herauslösen, plus einen
|
||
Overload für Element-Fortschritt (`i/n`). **Extrahieren, nicht kopieren** — es soll eine
|
||
Implementierung bleiben. Dies ist die einzige Datei, die D mit C teilt; nur die Progress-Schleife
|
||
anfassen.
|
||
*Dateien:* `Worker/Lifecycle/TaskMergeService.cs`, neue Klasse im Worker-Projekt
|
||
*Fertig wenn:* Der Merge-Pfad nutzt die extrahierte Klasse, Verhalten unverändert, bestehende Merge-Tests grün.
|
||
|
||
**D2 · Die sieben `batch_*`-Tools mit Element-Fortschritt**
|
||
Jedes `batch_*`-Tool reportet pro verarbeitetem Element. Grund: ohne Meldung bricht der
|
||
MCP-Client nach 300 s Stille ab, während der Worker weiterläuft — genau der Mechanismus, der
|
||
einen Merge durchlaufen lässt, den Task aber nie auf `Done` bringt.
|
||
*Dateien:* `Worker/External/BatchMcpTools.cs`
|
||
*Fertig wenn:* Pro Tool ein Test mit Fake-`IProgress`, der mindestens eine Meldung je Element zählt. Englische Klartext-Strings, keine Locale-Keys.
|
||
|
||
**D3 · Worktree- und Diff-Tools mit Progress**
|
||
`cleanup_task_worktree`, `batch_cleanup_task_worktrees`, `get_task_diff`, `preview_merge_set`.
|
||
Bereits versorgt und **nicht** anzufassen: die fünf Tools mit `IProgress` in
|
||
`ExternalMcpService`, `TaskWaitMcpTools`, `merge_task`, `review_task`.
|
||
*Dateien:* die betroffenen `Worker/External/*McpTools.cs`, `ExternalMcpService.cs`
|
||
*Fertig wenn:* Jedes der vier Tools meldet Fortschritt, mit Test.
|
||
|
||
**D4 · Restliche Long-Runner und die Regel im Worker-`CLAUDE.md`**
|
||
Verbleibende Tools durchgehen, die über ~5 s laufen können, und die Regel festschreiben: ein
|
||
MCP-Tool über ~5 s reportet Progress. Ohne die Regel wiederholt sich das Muster beim nächsten
|
||
Tool.
|
||
*Dateien:* restliche `Worker/External/*McpTools.cs`, `src/ClaudeDo.Worker/CLAUDE.md`
|
||
*Fertig wenn:* Die Regel steht mit Begründung; die durchgegangenen Tools sind im Task aufgelistet — auch die, die bewusst nichts bekommen.
|
||
|
||
---
|
||
|
||
## Was nach allen vier Gruppen offen bleibt
|
||
|
||
- ~~**Visuelle Prüfung** für alle Pakete in A und B~~ — **am 2026-08-17 durch**, ohne Fund
|
||
(Worktrees-Übersicht, Settings/Skill-Install, WeeklyReport, großer Diff, Detail-Pane-Approve).
|
||
- ~~**Auswertung von P0-2**~~ — am 2026-08-13 ausgewertet, siehe A5.
|
||
- **`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).
|