Files
ClaudeDo/docs/verification-handoff.md
T

21 KiB
Raw Blame History

Verifikations-Handoff (Stand 2026-07-24)

Manuelle Verifikation am laufenden System. Mika bedient die UI, die Session protokolliert Pass/Fail und bereitet Fixtures vor (Tasks via ClaudeDo-MCP, Git-Setups im Testrepo). Aktive Findings landen in docs/open.md; hier steht der Fortschritt je Abschnitt.

Handoff für die nächste Session

Erledigt (2026-07-24): §1 Detail-Insel/Diff-Viewer (bis auf DiffModal-Fehler-State), §2 Worktree-Pipeline (alle 3), §3 Planning-Walkthrough (inkl. UnfinishedPlanning-Modal: dismiss/Finalize/Discard PASS, Resume BUG), §4 Merge-Editor (Single-Task und Planning-Unit-Konflikt und Abort), §6 Pick-up-Gating (Code), §7 AskUser (Happy-Path + Timeout + UI-Cleanup; Finding: Banner nur in Mission Control), §8 Session Skills (Install/Aktivierung/Seeding/Gegenprobe/Remove; Findings: fehlender Empty-State + abweichendes Agent-Gear-Icon), §9 Attachments (Drag&Drop-UI + MCP + ComposedPreview; Finding: intermittenter erster-Drop-Fehler), §12 RunNow (moot).

Noch offen:

  • §10 Daily Prep/WeeklyBewusst zurückgestellt (2026-07-24): Mika will Daily Prep + Weekly Report ohnehin überarbeiten — Verifikation lohnt erst nach dem Rework.
  • §11 Self-Update/AutostartNicht formell gefahren, aber laut Mika „läuft bisher sehr gut" (2026-07-24) → als OK behandelt; bei Bedarf später gezielt nachtesten.
  • Kanten: §1 DiffModal-Fehler-State, §3 UnfinishedPlanning-Modal, §4 Abortalle erledigt (2026-07-24): §4 Abort PASS, §3 Modal (Finalize/Discard PASS, Resume BUG), §1 via Code-Analyse geklärt (defensiv/unerreichbar).

Vorbedingungen: Worker + App laufen (SignalR 37821, External MCP 47822 — lt. ~/.todo-app/worker.config.json). Testliste ClaudeDoTests (C:\TestRepos\ClaudeDoTests, listId e7992fee6035448394b646d069690e05) für zerstörungsfreie Runs.

Wichtige Gotchas / Lessons (diese Session):

  • NIE model:"haiku" für schreibende Task-/Verifikations-Runs — unter dem Default --permission-mode auto werden haiku-Writes denied (still no-op). Immer sonnet. (Memory auto_permission_haiku_footgun.)
  • Merge-Preflight blockt bei dirty Ziel-Working-Tree (getrackte uncommittete Änderung im main-Checkout) — und Approve schluckt das still (Bug, open.md). Vor Merge-Checks git -C C:\TestRepos\ClaudeDoTests status --short prüfen; fremde WIP nur mit Rücksprache verwerfen (Memory shared_worktree_partial_commits: nur pfad-scoped committen).
  • Konflikt-Fixture-Rezept: Task (sonnet) überschreibt eine getrackte Datei im Branch → danach dieselbe Datei auf main divergent ändern + git commit -- <pfad> → Approve konfliktet. Für Planning-Unit-Konflikt: main-Edit erst scharfschalten, wenn der Parent in WaitingForReview steht.
  • Datenwahrheit notfalls direkt aus der DB: sqlite3 -readonly ~/.todo-app/todo.db "SELECT substr(id,1,8),status,planning_phase,substr(blocked_by_task_id,1,8),title FROM tasks WHERE …".

Testrepo-/Fixture-Zustand: Aufgeräumt (2026-07-24, §7+§8-Session-Ende) — alle verif §7/§8-Tasks gelöscht, deren Worktrees + Branches entfernt, ClaudeDoTests-main unangetastet bei ecad650 (Tree clean; die §7-Runs liefen isoliert in Worktrees, main nie berührt). ponytail-Skills nach dem Remove-Test wieder deinstalliert (session_skills leer, ~/.todo-app/session-skills/ leer). Keine verif-Tasks und keine claudedotests-Worktrees übrig. Die übrigen claudedo/*-Branches im Testrepo stammen aus früheren Sessions — nicht anfassen. Die nächste Session baut Fixtures frisch (Rezepte s. Gotchas oben).

Erfasste Folge-Tasks (Liste „Claude do", Idle, brauchen Brainstorm vor Umsetzung): f9809a93 Approve erzwingt Diff/Review vor Merge (+ blocked-Merge-Silent-Fail-Fix), 5d627df8 Planning-Session über embedded ConPTY statt wt (+ Planning-Permission-Prompt).

Fix-Session: Alle fixbaren Findings sind für eine frische Session in docs/fix-plan-2026-07-24.md aufbereitet (gruppiert nach Fixbarkeit: A mechanisch, B error-surfacing, C entscheidungsbedürftig, D investigation, E nits). Volltext je Finding bleibt in docs/open.md.

Nacharbeit: Erledigtes aus docs/open.md austragen; dieses File löschen, sobald §10 (nach Rework) + evtl. §11-Nachtest erledigt sind.


1. Detail-Insel & Diff-Viewer (reines Durchklicken)

  • [~] Detail-Insel komplett: Output/Git/Session-Tabs, Merge-Sektion, Agent-Settings-Overrides (InheritedBadge korrekt), Prep-Panel — nach dem VM-Split (DetailsIslandViewModel → Sektions-VMs) alles gebunden, keine leeren Panels. — TEILWEISE (2026-07-24, User-Sichtprüfung an verif §1 diff matrix): Output/Git-Tabs, Merge-Sektion, Agent-Badges vorhanden & gebunden, keine leeren Panels. Git-Tab: +1 37, „merges cleanly" korrekt. BUG: OUTCOME-Karte zeigt rohes JSON (s. open.md). Session-Tab fehlt — erwartet (nur bei Parent mit Kindern, HasChildOutcomes; wird in §4/§3 mit echtem Parent geprüft). Minor: TurnsText 0/max bei terminalem Reload (open.md). Prep-Panel → §10.
  • [~] Diff-Viewer: Dateiliste, Added/Deleted/Renamed/Binary-Erkennung, Commit-Range-Diff nach einem Merge. — Added/Deleted/Binary PASS; Renamed erkannt + „R"-Badge, aber schwach dargestellt (kein alt→neu-Pfad, „+0 0" — nit, s. open.md); „Review Combined Diff" korrekt ausgegraut (kein Planning-Parent). Commit-Range-Diff nach Merge: PASS (2026-07-24) — Diff des gemergten Done-Tasks rendert über base..head trotz entferntem Worktree.
  • DiffModal-Fehler-State: Commit-Range ohne aufgezeichnete Commits → „Diff nicht mehr verfügbar" statt Crash/leer. — GEKLÄRT via Code-Analyse (2026-07-24): der Fehler-State ist defensiv/unerreichbar. vm.diff.unavailable (DiffViewerViewModel.cs:117-119) feuert nur bei FromCommitRange && (BaseRef==null || HeadCommit==null) oder WorktreePath==null. Alle Aufrufer sind gegated: MergeSectionViewModel.OpenDiffAsync ruft ConfigureCommitRange nur unter CanDiffMergedRange (base und head non-null) und ConfigureWorktree nur unter hasLiveWorktree (Pfad non-null + existiert); WorktreesOverviewModalViewModel übergibt row.Path (non-null). Damit können die Null-Checks über die UI nie wahr werden — der „Crash/leer"-Fall, den der Check absichern wollte, ist durch die CanExecute-/Modus-Gates ausgeschlossen. (Fehlende Commit-Objekte bei vorhandenem base+head → GetCommitRangeDiffAsync wirft → „loadFailed", nicht „unavailable".) Nicht manuell auslösbar; kein Bug.
  • „children need attention"-Band auf dem Session-Tab eines Parents mit failed/blocked Kind. — PASS (2026-07-24, in §3): Band + OUTCOMES-Liste mit Roadblock-Hinweis auf dem Session-Tab des Planning-Parents (verif §3, Roadblock-Kind).

2. Worktree-Pipeline (3 falsifizierbare Fälle)

  • Happy-Path: Task mit WorkingDir → worktrees.state='active', head_commit gesetzt, diff_stat non-empty, Branch claudedo/<id[:8]> existiert auf Disk. — PASS (2026-07-24) via Task e63eb2f5 (sonnet): Worktree active, headCommit 8a3ae86 (ahead=1 vom Base), diff_stat non-empty (review-cancel.txt), Branch claudedo/e63eb2f5… auf Disk. (Mein erster Versuch mit model=haiku schlug fehl — das war ein haiku/auto-Footgun, keine Pipeline-Sache; s. open.md.)
  • No-Changes-Run: → status='Done', head_commit IS NULL, diff_stat IS NULL. — PASS (2026-07-24); Präzisierung: aktueller Flow endet in WaitingForReview (nicht Done, das ist erst nach Approve) — Doc-„Done" ist veraltet. Kein neuer Commit, leerer Diff bestätigt.
  • Kein Git-Repo (WorkingDir = C:\Temp): → status='Failed', KEINE worktrees-Row, Git-Fehler im Log. — PASS (2026-07-24); Fehler: „Worktree creation failed: working_dir is not a git repository".

3. Planning-Flow-Walkthrough

  • Draft → Finalize → Kette: Finalize queued NICHT automatisch (Kinder bleiben Idle); „Queue plan" setzt alle nicht-terminalen Kinder Queued, Kette läuft sequenziell durch (blocked-by löst sich je Vorgänger). — PASS (2026-07-24, End-to-End mit verif §3): 3 Kinder gedraftet, Finalize → Parent WaitingForChildren+finalized, Kinder Idle+Kette (clean→conflict→roadblock), Queue plan → sequenzieller Durchlauf, alle Done. Begleit-Findings (open.md): „Waiting for Improvements"-Mislabel, Kind-Badge-Live-Refresh (Draft→Planned erst nach Listenwechsel), Kette nicht visualisiert, planning-aktiver Parent zeigt „Idle", Dequeue-X fehlt auf wartenden Kindern, Planning-Permission-Prompt, Planning nutzt wt statt ConPTY.
  • Parent landet nach letztem terminalen Kind in WaitingForReview; Approve merged die ganze Unit (Parent-Worktree falls Active + jedes Done-Kind in Reihenfolge). — PASS (2026-07-24): Parent → WaitingForReview auch mit Roadblock-Kind; Approve → Unit-Merge (child-clean clean, child-conflict → Konflikt-Editor pro Subtask → aufgelöst → Continue, child-roadblock no-op) → Parent Done, Merge gelandet.
  • UnfinishedPlanning-Modal: Resume / FinalizeNow / Discard. — GETESTET (2026-07-24, verif §3 unfinished-planning + verif §3 resume-discard): Modal wird via Rechtsklick→„Resume planning session" ausgelöst, zeigt Titel + „N draft task(s) waiting to be finalized" + Buttons Discard/Finalize/Resume + ×. × (dismiss): No-op (State bleibt active). Finalize: Parent → finalized+WaitingForChildren, Kinder Idle+Blocked-by-Kette (= Planned) — PASS. Discard: Draft-Kinder gelöscht, Parent → none+idle — funktional PASS. Resume: BUG — macht nichts (planning_session_id nie erfasst → ResumeAsync wirft „No Claude session ID captured yet", vom UI im leeren catch verschluckt; s. open.md). Beide State-ändernden Aktionen reproduzieren das Kind-Rows-Live-Refresh-Finding (Finalize: Badges stale; Discard: gelöschte Rows bleiben bis Reload; open.md).

4. Merge-Editor (Rider-Style 3-Pane) mit echtem Konflikt

Konflikt provozieren: gleiche Datei auf main ändern, während der Task-Branch sie ändert. Beide Wege testen: (a) Single-Task-Approve mit Konflikt, (b) Planning-Unit-Merge mit Konflikt in einem Subtask (PlanningMergeConflict → Editor öffnet pro Subtask).

Verifiziert 2026-07-24 (Single-Task-Approve mit Konflikt, 2 Dateien, verif §4): End-to-End PASS — Approve→Konflikt→3-Pane-Resolver→beide Dateien auflösen→Continue→Merge→Task Done (Merge-Commit gelandet). Sauberer additiver Approve (verif §1b, kein Konflikt) → direkt gemergt → Done, kein Editor: PASS (2026-07-24). ⚠ Vorbedingung-Fund: ein dirty Ziel-Working-Tree (getrackte uncommittete Änderung) blockt den Merge und Approve schluckt das still (s. open.md „Approve & Merge schluckt blocked"). Planning-Unit-Merge-Konflikt (Weg b): PASS (2026-07-24, in §3) — Approve eines Planning-Parents mit einem konfliktenden Subtask öffnet den 3-Pane-Editor pro Subtask (PlanningMergeConflict), Continue führt den Unit-Merge fort → Parent Done.

  • Drei Panes: MAIN read-only | Result editierbar | INCOMING read-only; Konfliktblöcke rot, aufgelöst grün, in allen Panes. — PASS.
  • Gutter-Toggle /: Seite rein/raus, Klickreihenfolge = Reihenfolge im Result; main/incoming/beide/keine möglich. — PASS.
  • Nur Konfliktregionen im Result editierbar (Stable read-only); Edits fließen in den Block zurück. — PASS.
  • [~] Synchrones vertikales Scrollen; File-Switcher bei mehreren Dateien; M conflicts · K resolved-Readout; Conflict-Ruler (Klick springt). — PASS, aber Multi-File schlecht erkennbar (2 Konfliktdateien schwer zu sehen — UX-Nit, open.md).
  • [~] Continue erst aktiv, wenn ALLE Konflikte in ALLEN Dateien gelöst; Binär-Guard greift. — funktional PASS (merged erst nach Auflösung beider Dateien), aber Button ist klickbar statt disabled solange offen → stummer No-op (UX-Bug, open.md). Binär-Guard hier n/a.
  • Abort: Tree sauber, Task bleibt WaitingForReview. — PASS (2026-07-24, verif §4 abort): Single-Task-Approve mit divergentem main→Konflikt→3-Pane-Editor→Abort: Editor schließt, Task bleibt WaitingForReview, main-Tree sauber (kein MERGE_HEAD, kein dirty), main-Inhalt + HEAD unverändert.
  • [FEATURE-WUNSCH] farbliches Hervorheben der übernommenen/eingefügten Zeilen im Result-Pane (open.md).
  • Bekannte Kanten (nur gegenprüfen, nicht als Fail werten): leere Ours-Seite → null-lange Result-Region (Accept geht, Handtippen fummelig); Gutter-Y-Ausrichtung bei sehr hohen Fenstern/großem Scroll; vertikaler Drift nachfolgender Blöcke nach Konflikt mit ungleicher Zeilenzahl.

5. Embedded ConPTY / Mission Control

  • Kritisch: Task-basiert (Kontextmenü „Open ConPTY session"): frischer Task → Worktree wird on-demand angelegt, claude startet — Task-Prompt wirklich GESENDET. — PASS (2026-07-24): Agent antwortete mit dem Marker CONPTY-PROMPT-RECEIVED-4Q7, Worktree on-demand angelegt.
  • [~] Ad-hoc: „New session"-Button → Ordnerwahl → freie Session im gewählten Verzeichnis. — funktioniert, aber Button unsichtbar: Icon.Plus ist Strich-Only → PathIcon rendert nichts (Bug, open.md). Über Tooltip/Klick auf die leere Header-Fläche erreichbar; Ordnerwahl + Ad-hoc-Session funktionieren.
  • Grid↔Tabs-Toggle; Close killt den Prozess + entfernt die Kachel; mehrere Sessions parallel; Pane-Resize reflowt das TUI. — PASS (2026-07-24): Resize/Reflow ok; Close entfernt Kachel UND killt den Prozess (verifiziert: ConPTY-PID 50964, Kind von ClaudeDo.App, nach Close tot).
  • [~] Resume: im Worktree-Dir per claude --continue möglich (ClaudeDo persistiert die ConPTY-Session-Id bewusst nicht). — wie designt: erneutes „Open ConPTY session" resumt NICHT, sondern startet frisch und sendet den Prompt erneut (UX-Falle, open.md). In-App-Resume gibt es nicht; nur claude-eigenes --continue/--resume im Worktree-Dir.

6. Pick up in terminal

  • Sichtbarkeit: Kontextmenü-Eintrag + Terminal-Button (ArrowOut) NUR bei WaitingForReview und Failed; bei Idle/Running/Queued/Done nicht. — PASS (2026-07-24, Code+Test): CanPickUpInTerminal => Status is WaitingForReview or Failed (TaskRowViewModel.cs:69, DetailsIslandViewModel.cs:898), gebunden in TaskRowView.axaml:56 + TaskHeaderBar.axaml:34, Notify bei Statuswechsel, Unit-Test CanPickUpInTerminal_OnlyForReviewOrFailed. (Klick→Terminal + Fehler-Surfacing bleiben manueller UI/CLI-Check.)
  • Klick → neues Windows-Terminal im Worktree-Verzeichnis, claude --resume <id> nimmt die Session mit Kontext wieder auf.
  • Fehlerfälle surfacen sauber (Footer-Strip bzw. Fehlerdialog): laufende/gequeuete Task, keine persistierte Session-Id, kein aktiver Worktree.
  • Bekannte Kante: parked-Idle (reject-park) hat oft Session+Worktree, zeigt die Aktion aber bewusst NICHT (Idle nicht unterscheidbar). Nervt das in der Praxis → CanPickUpInTerminal erweitern.

7. AskUser (Frage aus laufendem Task)

Runtime-Toolname ist mcp__claudedo_run__ask_user (snake_case, nicht AskUser). Fixture: sonnet-Task mit Prompt „zwei sich widersprechende, irreversible Optionen — du MUSST ask_user fragen, nicht raten" erzwingt den Call zuverlässig.

  • Task, dessen Prompt eine Rückfrage erzwingt → Frage erscheint im Task-Monitor (Mission Control), Prozess wartet. — PASS (2026-07-24, verif §7 askuser 2): Banner in Mission Control, Run blockiert im ask_user-Tool-Call. BUG: in der Task-Detail-Insel erscheint KEIN Banner (nur Mission Control) → s. open.md.
  • Antwort inline absenden → Run läuft mit der Antwort weiter. — PASS (2026-07-24): Antwort „A" inline gesendet → Agent bekam „option A", benannte merge-playground.txtrenamed-by-agent.txt um, schrieb askuser-result.txt = „USER CHOSE: A", Task → WaitingForReview.
  • Timeout-/Cleanup-Verhalten der PendingQuestionRegistry (Frage unbeantwortet lassen): Task schlägt kontrolliert fehl, UI räumt die Frage auf (MCP_TOOL_TIMEOUT-Gotcha). — PASS (2026-07-24): Backend (verif §7 askuser) nach exakt 3 min Fallback zurück, Agent hielt sich an „MUST NOT guess", meldete CLAUDEDO_BLOCKED, Repo untouched, Registry im finally aufgeräumt, Run endete success → WaitingForReview. UI-Cleanup visuell bestätigt (verif §7 timeout-ui, MC-Kachel offen/subscribt bis zum Timeout): Banner verschwindet beim Timeout automatisch (TaskQuestionResolvedClearPendingQuestion).

8. Session Skills (E2E + UI)

  • Settings → Skills: https://github.com/DietrichGebert/ponytail installieren → 6 Skills erscheinen (ponytail, -help, -review, -audit, -debt, -gain), auf Commit gepinnt, Dateien unter ~/.todo-app/session-skills/<name>/. — PASS (2026-07-24): 6 Rows in session_skills, alle auf Commit 16f29800 gepinnt, subpath=skills/<name>, Dateien inkl. SKILL.md am erwarteten Ort. UI-Install nahm die URL an. Nit: Skills-Tab hat keinen Empty-State (s. open.md).
  • Skill per-Task (Agent-Settings-Flyout) oder global aktivieren → Task laufen lassen → im Worktree liegt .claude/skills/<name>/, Agent kann ihn nutzen, git status im Worktree bleibt sauber (info/exclude greift). — PASS (2026-07-24, per-Task, verif §8 skill-activate): ponytail-help per Flyout aktiviert (task.SessionSkills=["ponytail-help"]), Run → .claude/skills/ponytail-help/SKILL.md im Worktree, info/exclude bekam /.claude/skills/ponytail-help/, git status clean, Auto-Commit enthält NUR skill-proof.txt (Skill NICHT im Tree). Agent hat den Skill nachweislich genutzt (skill-proof.txt = ponytail-help-Referenzkarte verbatim). Global-Aktivierung (AppSettings.SessionSkills, General-Tab) nutzt denselben Seeding-Union-Pfad — funktional äquivalent, nicht separat gefahren.
  • Gegenprobe: nicht-aktivierte/andere interaktive Session sieht den Skill NICHT (kein Leak nach ~/.claude). — PASS (2026-07-24): ~/.claude/skills/ enthält nur die vorbestehenden globalen Skills, KEIN ponytail. Seeder schreibt per Konstruktion nur nach <workingDir>/.claude/skills (SessionSkillSeeder), nie nach ~/.claude.
  • [~] UI: Skills-Tab (Install-Zeile, Karten mit Update/Remove), Checkbox-Listen im General-Tab + AgentConfigEditor (Flyout-Höhe!), leerer Zustand (0 Skills — Empty-State fehlt evtl., dann entscheiden), lange Namen/URLs (Trimming). — TEILWEISE (2026-07-24): Install-Zeile nimmt URL an (PASS); AgentConfigEditor-Flyout-Checkbox-Liste zeigt alle 6 Skills, Höhe ok, Trimming ok (User: „funktioniert wie erhofft"). Empty-State fehlt (nackte Fläche — open.md). Agent-Settings-Gear ⚙ weicht vom Listen-Gear ab (open.md). Skills-Tab-Karten (Name/Beschreibung/Pinned-Commit + Update/Remove) und General-Tab-Checkbox-Liste (6 Skills) sichtgeprüft (User: „sieht alles gut aus"). Remove PASS (2026-07-24): ein Remove-Klick auf eine ponytail-Karte entfernte alle 6 Skills per Quell-URL (session_skills leer, alle Verzeichnisse unter ~/.todo-app/session-skills/ weg). Update nicht separat gefahren (Code-Pfad SessionSkillRegistry.UpdateAsync).

9. Attachments (Drag & Drop + MCP)

  • Drop aufs Detail-Pane: „Drop to attach"-Overlay, Datei erscheint in der Liste, landet unter ~/.todo-app/attachments/<taskId>/; „Add file…"-Picker; Remove-Button. — PASS (2026-07-24, verif §9 attachments-ui): Overlay/Highlight beim Drüberziehen, Drop-Round-Trip (drop-me.txt in Liste + Disk + DB, 29 B), „Add file…"-Picker (pick-me.md), Remove (Datei aus Liste + Disk + DB) — alle bestätigt. Finding: der ALLERERSTE Drop der Session schlug einmalig mit inline „An error occurred" fehl (nichts persistiert), danach fehlerfrei — intermittierend, nicht reproduzierbar (s. open.md).
  • ComposedPreview enthält die Attachment-Pfade („## Reference files"). — PASS (2026-07-24, Code+Test): TaskPromptComposer.cs:31 emittiert „## Reference files", ComposedPreview reicht Pfade durch, TaskRunner.cs:132 injiziert zur Laufzeit; TaskPromptComposerTests decken es ab.
  • MCP: add_task_attachment / list_task_attachments / remove_task_attachment; Running-Task verweigert add/remove. — PASS (2026-07-24) für add/list/remove-Round-Trip inkl. Datei unter ~/.todo-app/attachments/<taskId>/ (71 B, korrekt, nach Remove weg). Running-Task-Verweigerung ist code-guarded (AttachmentMcpTools), aber ohne dauerhaft laufende Task nicht live geprüft → manueller Rest.

10. Daily Prep (Prime) & Weekly Report

  • Prime-Trigger: Schedule feuert bzw. „Plan day" manuell → Prep-Log streamt live, daily-prep.log enthält letzten Run, MyDay-Auswahl respektiert DailyPrepMaxTasks.
  • Weekly Report: Range-Default „seit letztem Standup-Wochentag → heute", Markdown rendert, Cache pro Range.

11. Self-Update & Autostart (am Gerät)

  • Update-Banner → Update durchführen → danach „up to date".
  • Autostart: Logoff/Logon startet den Worker (Startup-.lnk); Update-Pfad erhält den Autostart; Uninstall entfernt die .lnk.

12. Status-Bar / RunNow (Mini-Codecheck, erst messen)

  • Worker trennen/verbinden → prüfen, ob RunNow-Enable pro Task-Row sauber re-evaluiert (Connection-State lebt in IslandsShellViewModel). Nur fixen, wenn tatsächlich kaputt. — PASS/moot (2026-07-24, Code): Es gibt kein per-Row-RunNow-Control in der UI; RunNowAsync (IWorkerClient/WorkerClient) hat keinen VM/View-Aufrufer (RunNow nur via MCP run_task_now). Die realen Detail-Pane-Aktionen (Enqueue/Dequeue/Continue/ResetAndRetry) re-evaluieren korrekt bei Connection-Change (DetailsIslandViewModel.cs:317-323). Nebenbefund: RunNowAsync in der UI ist toter Code.