# Handler-Run: Verknüpfung zu den behandelten Tasks **Date:** 2026-08-07 **Status:** Design approved (Mika), implementation pending **Verified against:** commit `c792765` ## Problem Ein "Let Claude handle it"-Run besitzt seit 2026-08-05 einen echten Task (`IsManual=true`, `HandlerBaseCommit`/`HandlerHeadCommit`, Diff über Commit-Range). Was fehlt: **welche Tasks der Run behandelt hat, ist nirgends persistiert.** Die Auswahl lebt nur in der ConPTY-Session und im Transcript; `HandoffMcpTools.HandoffListHandler` (`src/ClaudeDo.Worker/External/HandoffMcpTools.cs:28-45`) bekommt `survivingTaskIds` als flüchtige Liste. Folge: Nachdem ein Run durch ist und der Diff sichtbar wird, lässt sich nicht mehr nachvollziehen, *was alles gemacht werden sollte* und *welcher Task was produziert hat*. Duplikate, die der Handler in Phase 1 gecancelt hat, verschwinden vollständig aus dem Blickfeld. Zweitens zeigt der Handler-Task in der Liste das Badge **MANUAL**, weil er `IsManual=true` setzt — irreführend, denn es ist kein manueller Reminder. ## Ist-Zustand ### Es gibt kein Task-Kind `TaskEntity` hat **kein `Kind`/`Type`-Enum**. Task-"Arten" sind heute Feld-Kombinationen: | Feld | Bedeutung | |---|---| | `IsManual` | manueller Reminder — Queue/Daily-Prep/Refine überspringen ihn | | `ParentTaskId` | Kind einer Planning-/Improvement-Session | | `PlanningPhase` | Planning-Parent | | `BlockedByTaskId` | Kettenglied, Queue-Picker überspringt es | | `HandlerBaseCommit` | worktree-loser List-Handler-Host (`src/ClaudeDo.Data/Models/TaskEntity.cs:60-61`) | Ein Handler-Task ist also allein durch `HandlerBaseCommit != null` identifiziert. ### `ParentTaskId` ist belegt `TaskRepository.CreateChildAsync` (`src/ClaudeDo.Data/Repositories/TaskRepository.cs:306`) setzt es für Planning-Kinder; `TaskRowViewModel.IsChild`/`ShowAsChild` (`src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs:59,66`) hängen daran und rücken die Zeile im Baum ein. Ein Recycling für Handler→behandelte Tasks würde die Auswahl optisch unter den Handler schieben und mit echten Planning-Kindern kollidieren. ### Badge-Infrastruktur existiert `TaskRowView.axaml:129-144` rendert DRAFT / PLANNED / PLANNING / MANUAL über `Border Classes="badge "`. Basis-Style und Varianten liegen in `src/ClaudeDo.Ui/Design/IslandStyles.axaml:963-990`, die Brushes als theme-fähige Tokens in `Tokens.axaml`. Loc-Keys: `tasks.badgeManual`, `tasks.manualTip` (en.json:163-164). ### Kinder-Panel existiert `DetailsIslandViewModel.LoadChildOutcomesAsync` (`src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs:704-748`) lädt `Where(t => t.ParentTaskId == parentTaskId)` in `ChildOutcomes` (`:248`) und rendert pro Zeile Id/Titel/Status/RoadblockCount/WorktreeState via `ChildOutcomeRowViewModel`; Refresh läuft über `TaskUpdated`/`WorktreeUpdated` (`:814-833`). ## Entscheidungen | Frage | Entscheidung | Begründung | |---|---|---| | Neues `TaskKind`-Enum? | **Nein** | Es gäbe kein Enum zu erweitern — es wäre das erste überhaupt, inkl. Migration und Rückwirkung auf Queue/Filter/UI. Der Bedarf ist eine Beziehung, kein Typ. | | `ParentTaskId` wiederverwenden? | **Nein** | belegt durch Planning-Kinder, kollidiert mit Einrückungs-Logik | | 1:n oder n:m? | **1:n**, eine nullable Spalte | Historie "welcher Run hat den Task mal berührt" bringt nichts, wenn ohnehin der letzte Run derjenige ist, dessen Diff man ansieht. Join-Tabelle = doppelter Code für einen Randfall. | | Wann stempeln? | **Beim Anlegen des Handler-Tasks** | Die UI kennt die Auswahl bereits. Erfasst auch die Tasks, die der Handler in Phase 1 als Duplikat cancelt — genau das "was sollte alles gemacht werden". Ein Stempeln erst in `handoff_list_handler` würde Dedupe-Verlierer verlieren und bei Abbruch vor Phase 2 gar nichts verknüpfen. | | Umfang der Anzeige | **Nur Liste + Endstatus** | Kein Phasen-Protokoll, kein Per-Task-Diff im Panel — der Diff hängt ohnehin am jeweiligen Task. | ## Design ### 1. Daten Neue nullable Spalte auf `TaskEntity`: ```csharp /// Id des Handler-Task-Runs, der diesen Task behandelt hat (null = keiner). public string? HandlerTaskId { get; set; } ``` Konfiguration in `TaskEntityConfiguration`: `HasIndex(t => t.HandlerTaskId)`, kein FK-Constraint (konsistent mit `BlockedByTaskId`-Handhabung; ein gelöschter Handler-Task soll die behandelten Tasks nicht kaskadierend anfassen). EF-Core-Migration `AddHandlerTaskId`. Ein zweiter Run über dieselben Tasks überschreibt die Zuordnung — gewollt (1:n). ### 2. Schreiben Die Auswahl wird durchgereicht: UI → `IWorkerClient.CreateMergeHelperTaskAsync` → `WorkerHub.CreateMergeHelperTask` (`src/ClaudeDo.Worker/Hub/WorkerHub.cs:827-838`) → `InteractiveLaunchSpecService.CreateMergeHelperTaskAsync`. Nach dem Anlegen des Handler-Tasks setzt eine neue Repository-Methode die Zuordnung in einem Batch-Update: ```csharp Task SetHandlerTaskIdAsync(IReadOnlyList taskIds, string handlerTaskId, CancellationToken ct); ``` Der Handler-Task selbst bekommt **kein** `HandlerTaskId` (kein Selbstbezug). Unbekannte Ids werden still übersprungen. ### 3. Badge `TaskRowViewModel`: ```csharp public bool IsHandlerRun => !string.IsNullOrEmpty(HandlerBaseCommit); public string? HandlerBadge => IsHandlerRun ? Loc.T("tasks.badgeHandler") : null; public string? ManualBadge => IsManual && !IsHandlerRun ? Loc.T("tasks.badgeManual") : null; ``` `HandlerBaseCommit` muss dafür auf das Row-ViewModel und in dessen Mapping aufgenommen werden. HANDLER hat Vorrang vor MANUAL — beide Badges nie gleichzeitig. In `TaskRowView.axaml` analog zu `:141-144` ein `Border Classes="badge handler"` mit `ToolTip.Tip="{loc:Tr tasks.handlerTip}"`. In `IslandStyles.axaml` eine `.badge.handler`-Variante mit `{DynamicResource HandlerBadgeBrush}`, Token in `Tokens.axaml` für Light und Dark. Neue Loc-Keys in en.json **und** de.json (Parität ist testgeprüft): - `tasks.badgeHandler` — "HANDLER" / "HANDLER" - `tasks.handlerTip` — "Handler run — lists the tasks it processed" / "Handler-Run — listet die Tasks, die er bearbeitet hat" ### 4. Anzeige Im Detail-Bereich eines Handler-Tasks eine Liste der behandelten Tasks, parallel zum bestehenden Kinder-Panel: - Neue Collection `HandledTasks` auf `DetailsIslandViewModel`, befüllt von `LoadHandledTasksAsync` mit `Where(t => t.HandlerTaskId == taskId)`, sortiert wie die Kinder-Liste. - Zeilen wiederverwenden `ChildOutcomeRowViewModel` (Id, Titel, Status, RoadblockCount, WorktreeState) — keine neue Row-Klasse. - Refresh über dieselben `TaskUpdated`-Events wie `ChildOutcomes`; der bestehende `RefreshChildOutcomeAsync`-Pfad (`:814-833`) wird um die zweite Collection erweitert. - Sichtbar nur wenn `HandledTasks.Count > 0`. - **Keine Klick-Interaktion** — das bestehende `ChildOutcomes`-Template ist eine reine Anzeige (Titel / Roadblock / Status, kein Tapped-Handler). Die neue Liste bleibt identisch; "zum Task springen" wäre neues Verhalten und ist hier nicht enthalten. ### 5. Fehlerfälle - Handler-Task gelöscht → `HandlerTaskId` der behandelten Tasks zeigt ins Leere; die Tasks bleiben normal nutzbar, das Panel existiert schlicht nicht mehr. Kein Cleanup nötig. - Behandelter Task gelöscht → verschwindet aus der Liste (Query läuft live gegen die Tasks). - Leere Auswahl → kein Stempeln, Panel bleibt unsichtbar. ## Tests | Ebene | Test | |---|---| | Data | `SetHandlerTaskIdAsync` stempelt alle übergebenen Ids, ignoriert unbekannte, überschreibt eine vorhandene Zuordnung | | Worker | `CreateMergeHelperTaskAsync` stempelt die übergebene Auswahl und **nicht** den Handler-Task selbst | | Ui | `TaskRowViewModel`: HANDLER schlägt MANUAL (`IsManual=true` + `HandlerBaseCommit` gesetzt → nur HANDLER) | | Ui | `DetailsIslandViewModel`: `HandledTasks` lädt nach `HandlerTaskId`, aktualisiert sich auf `TaskUpdated` | | Localization | Parität en/de — deckt der bestehende Test automatisch ab | ## Bewusst nicht enthalten - Kein `TaskKind`-Enum. - Keine n:m-Historie über mehrere Runs. - Kein Phasen-Protokoll (Dedupe-Begründungen, Umformulierungen) — nur das Ergebnis. - Kein Per-Task-Diff im Panel; der Diff bleibt am jeweiligen Task. - Kein Badge auf den *behandelten* Tasks. ## Offen - **Sichtprüfung durch Mika:** Badge-Farbe im Light- und Dark-Theme, Position des Panels im Detail-Bereich, Verhalten bei vielen behandelten Tasks (Scroll).