Commit Graph
100 Commits
Author SHA1 Message Date
mika kuns df3e7ba0c7 fix(ui): Dequeue-X auf blockierten Queue-Zeilen anzeigen und Kette konsistent halten
CanRemoveFromQueue schloss IsWaiting (queued+blocked) faelschlich aus, sodass ein
Kettenglied in der Mitte nicht entfernbar war. Zusaetzlich unblockt
TaskStateService.DequeueToIdleAsync jetzt den Nachfolger eines dequeueten
Kettenglieds, sonst bliebe er dauerhaft auf einen nicht mehr laufenden
Vorgaenger blockiert.
2026-09-02 22:34:50 +02:00
mika kuns 77c70d74cb perf(claude-do): merge perf: Listen-Settings-Modal öffnet erst nach 7 seriellen Rou
ClaudeDo-Task: 1a07fa19b27347c1b65591ca6c0c0980
2026-09-02 22:19:55 +02:00
mika kuns f24654dfb8 perf(ui): show list settings modal before its seven-round-trip load finishes
OpenListSettingsAsync/CreateListAsync used to await LoadAsync (name/agent
config/list_config x2/ticket settings/ticket projects, all serial) before
showing the window -- an unreachable ticket system alone could add a 10s
HTTP timeout to every open. LoadAsync now runs via BeginLoad (fire-and-forget,
mirrors UsageMonitorModalViewModel), the modal shows immediately and fields
fill in behind it.

GetListConfigAsync no longer runs twice per open -- AgentConfigEditorViewModel
exposes the row it already fetched (LastLoadedListConfig) instead of the
caller re-fetching it. Ticket-project loading is split into its own
fire-and-forget step so a slow/unreachable ticket system can't block the
rest of the fields, and its tri-state Save logic now also treats "never
finished loading" like "not available" -- leaving the stored
TicketProjectId link untouched instead of overwriting it with 0. Save/Delete
are gated (CanExecute) on the core load being done, so a save can't land
before the fields it writes are populated.

Not measured with OperationTiming against a live worker in this session (no
running app instance available) -- verified instead via 4 new unit tests
that assert GetListConfigAsync fires exactly once and that a hung/failing
ticket-projects call neither blocks LoadAsync's other fields nor corrupts
the saved TicketProjectId. Before/after OperationTiming numbers and a visual
pass are still open follow-ups.
2026-09-02 22:18:54 +02:00
mika kuns b05b6fbaec fix(ui): Mission Control oeffnen, wenn ein Handler-Run per MCP startet oder ein Handoff eintrifft 2026-09-02 22:05:34 +02:00
mika kuns d258ab4164 test(worker): start_list_handler ins erwartete MCP-Tool-Inventar aufnehmen
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 42s
2026-09-02 21:57:16 +02:00
mika kuns 574edf6c1a Merge branch 'claudedo/f2f0c6ee1e974c09bc8a8261978cba34'
feat(worker): Handler-Session vor dem Usage-Limit sauber uebergeben (#275); Konflikte mit start_list_handler aufgeloest, WorkerLog-Aufruf im Fire-and-forget-Pfad wieder abgesichert
2026-09-02 21:50:26 +02:00
mika kuns fe8186514f feat(worker): Handler-Session vor dem Usage-Limit warnen und Handoff bis zum Reset zurueckhalten
Eine PowerUse-Handler-Session soll am 5h-Limit nicht mehr mitten im Merge
sterben, sondern vorher selbst sauber uebergeben:

- HandlerUsageGuard (neu) warnt die laufende Session genau einmal pro Runde,
  sobald FiveHour.Utilization den Schwellwert PowerUseHandoffPct erreicht.
  Die Warnung wird per HandlerUsageWarning-Broadcast an Mission Control
  geschickt und dort in das Pane der Handler-TaskId getippt.
- handoff_list_handler broadcastet den Handoff dann nicht sofort, sondern parkt
  ihn bis ResetsAt + 60 s; das Ergebnis traegt deferredUntil. Ohne ResetsAt
  wird wie bisher sofort gesendet.
- Der geparkte Handoff liegt im HandlerRoundState (handler-rounds.json) und
  wird vom Guard-Tick ausgeloest, also auch nach einem Worker-Neustart wieder
  eingeplant bzw. sofort gesendet, wenn er ueberfaellig ist.
- Review-Befund aus #272: HandlerRestartCoordinator wartet nicht mehr selbst
  bis ResetsAt, sondern parkt den Neustart im selben Slot. AutoRestartUsed wird
  erst gesetzt, wenn der Broadcast wirklich rausging - ein Worker-Absturz
  waehrend der Wartezeit laesst die Runde nicht mehr dauerhaft tot zurueck.
  Zurueckgehaltener Handoff und Auto-Neustart teilen sich einen Slot, also
  oeffnet nie beides dieselbe Runde. UsageLimitProofPct entfaellt zugunsten von
  PowerUseHandoffPct.
- PtyTerminalSession.SendLineAsync schreibt in die laufende Session. Gegen eine
  echte claude-Session verifiziert: Text und Enter muessen zwei getrennte
  Writes sein, die Enter-Bytes kommen von Terminal.GenerateKeyInput.
2026-09-02 21:35:29 +02:00
mika kuns c2166a0d4f feat(worker): start_list_handler MCP-Tool startet einen List-Handler-Run wie der Button 2026-09-02 21:05:55 +02:00
mika kuns c3a418f077 Merge branch 'claudedo/b346a534f0fc4635bd7614a00ab2a20e' 2026-09-02 20:55:43 +02:00
mika kuns 2ec3af96d5 chore(claude-do): merge PowerUse-Zeitfenster + Handoff-Schwellwert als Settings (Usa
ClaudeDo-Task: aa446890-82f0-49b9-aef9-13a788d9c514
2026-09-02 19:27:01 +02:00
mika kuns 016b9203f8 feat(worker): Handler-Session nach Usage-Limit-Reset automatisch neu starten
Eine List-Handler-ConPTY-Session, die mitten in einer Phase stirbt, war bisher
ein stilles totes Tile: InteractiveTerminalViewModel.OnSessionProcessExited
setzte nur bei einem Tod innerhalb des 5s-Grace-Fensters ein sichtbares Banner.

Neu:
- HandlerRoundState (JSON unter ~/.claudeDo/handler-rounds.json, keine
  EF-Migration): Phase + Task-Set der laufenden Runde, geschrieben von
  BuildForMergeHelperAsync ("triage") und BuildForMergeHelperHandoffAsync.
  Ohne das existieren beide nur im transienten HandoffRequested-Broadcast.
- HandlerRestartCoordinator: entscheidet den Neustart, fail-closed. Beleg fuer
  "war das Usage-Limit" ist FiveHour.Utilization >= 95 zum Todeszeitpunkt --
  eine ConPTY-Session hat kein stdout, TaskRunner.IsUsageLimitMessage hat hier
  nichts zu lesen. Nur bei PowerUse (beide Keys), max ein Neustart pro Runde,
  Warten bis FiveHour.ResetsAt, dann HandoffRequested erneut broadcasten.
  Verbraucht kein Handoff-Budget (HandoffRoundTracker.Register wird nicht
  angefasst). WorkerLog-Zeilen beim Tod und beim Neustart.
- Ui meldet den Exit-Code (nur dort bekannt) via
  WorkerHub.ReportInteractiveSessionExited, ohne selbst zu filtern.

Sibling zu UsageLimitAutoContinueCoordinator, kein Umbau davon: der sieht nur
headless Runs.
2026-09-02 19:17:51 +02:00
mika kuns 69898be5c9 feat(usage): PowerUse-Zeitfenster + Handoff-Schwellwert als Settings
Drei neue app_settings-Spalten (Migration AddPowerUseWindow):
power_use_window_start/end (HH:mm, lokal, nullable) und
power_use_handoff_pct (NOT NULL DEFAULT 95, geclampt 50-99).

Die Fensterlogik liegt als reine Funktion in PowerUseWindow
(Contains/Normalize/IsOpen): beide Grenzen leer oder nur eine gesetzt =
rund um die Uhr, Ende vor Start wrappt ueber Mitternacht, Grenzen
halboffen. Geprueft wird in ComposeHandlerPromptAsync, also pro Phase
zum Zeitpunkt des Prompt-Baus - die Uhr ist per Func<DateTime>
injizierbar. Bedienung im Usage-Monitor neben dem PowerUse-Toggle
(Commit auf Blur/Enter, ein Write fuer alle vier Felder); in
Settings->General unsichtbar, aber load->save durchgereicht.

Nebenbefund, gleicher Pfad: AppSettingsRepository.UpdateAsync kopiert
Feld fuer Feld und hat ModelPresets, PowerUseEnabled und
ListHandlerMaxHandoffs nie geschrieben - jeder Save dieser drei war
still wirkungslos. Mit ergaenzt und per Round-trip-Test gepinnt.
2026-09-02 19:09:47 +02:00
mika kuns 20b3b4ce2b docs(claude-do): Handler-Prompt praezisieren (Wave-Plan, Task-Split, review_task-Aufruf) 2026-09-02 18:43:15 +02:00
Mika Kuns 42356241e3 feat(claude-do): PowerUse fuer List-Handler-Runs + konfigurierbares Handoff-Budget 2026-09-02 15:56:10 +02:00
Mika Kuns 675b36e923 Merge branch 'main' into feat/poweruse-list-handler
Kollision mit #264 (Handoff-Budget im Brief sichtbar machen): beide Seiten haben
HandoffRoundTracker, HandoffMcpTools und InteractiveLaunchSpecService angefasst.

Der Konflikt war inhaltlich, nicht nur textuell. #264 liest den Cap an zwei
Stellen aus der Konstante (GetStatus und das roundInfo-Rendering), waehrend
dieser Branch ihn zum Parameter aus app_settings gemacht hat. Ein reiner
Text-Merge haette kompiliert und trotzdem gelogen: Register haette den
konfigurierten Cap durchgesetzt, waehrend der Brief dem Modell weiter "2 Runden
uebrig" gemeldet haette, obwohl es 39 hat.

Aufgeloest so, dass der Cap eine einzige Quelle hat:
- GetStatus(taskId, maxHandoffs) nimmt den Cap als Parameter, mit demselben
  Clamp wie Register. Die Rechnerei liegt damit an einer Stelle.
- GetHandoffStatus liest das Setting und gibt es weiter.
- Das roundInfo-Rendering benutzt GetStatus statt eigener Arithmetik.

Zwei Tests nageln genau diesen Drift fest, damit er beim naechsten Merge nicht
unbemerkt zurueckkommt: einer auf GetHandoffStatus, einer auf den Brief.

MaxHandoffs -> DefaultMaxHandoffs in #264s vier Test-Referenzen; die laufen alle
mit Default-Settings, die Semantik bleibt.
2026-09-02 15:48:43 +02:00
Mika Kuns 416997d7e0 docs: Test-Scoping fuer Implementer-Tasks, PowerUse in den Explore-Notes
Die Build-Sektion hat bisher ein Rezept mit allen sechs Testprojekten vorgegeben;
Implementer haben daraus "volle Suite vor fertig" gelesen. Bei bis zu 5
parallelen Tasks kostet das Stunden - und kann das nicht finden, wofuer es
teuer ist: jeder Task arbeitet in seinem eigenen Worktree mit nur seiner eigenen
Aenderung, eine Kollision zwischen zwei parallelen Tasks ist fuer beide
unsichtbar. Kollisionen zeigen sich nach dem Merge, und genau dort laeuft
verify_merges den Verify-Command einmal pro Batch.

Neue Regel: Solution-Build immer (42s, faengt projektuebergreifende
Compile-Brueche), Tests nur --no-build --filter auf das Beruehrte, und melden
welcher Filter benutzt wurde. Die Messwerte stehen dabei, damit die Regel nicht
als Geschmacksfrage gelesen wird.

Messung 2026-09-02, sequenziell nach einem Build: Worker.Tests 507s, Ui 33s,
Data 8s, Rest je 3s, Build 42s. Parallel ueber die Solution ist es schlechter,
nicht besser (Worker 801s, Ui 82s) - diese Tests legen echte Git-Worktrees an
und spawnen Prozesse, sie drangeln sich statt sich zu teilen.

Ausserdem korrigiert: die Zeile "dotnet build ClaudeDo.slnx requires .NET 9"
war veraltet.
2026-09-02 15:44:13 +02:00
Mika Kuns 193752540b feat(ui): PowerUse-Toggle im Usage-Monitor, Opt-in pro Liste
Der Toggle sitzt unter den Gauges im Usage-Monitor, weil "das restliche Fenster
ausnutzen" eine Entscheidung ist, die man beim Blick auf das restliche Fenster
trifft. Er speichert sofort per Read-modify-write, gleiche Disziplin wie
SaveStagesAsync, damit er keine Felder ueberschreibt, die er nicht besitzt.

Zwei Stellen, die das Opt-in still ausgeschaltet haetten:
- WorkerHub.UpdateListConfig loescht die Config-Zeile, wenn alle Felder leer
  sind. PowerUseAllowed muss in diese Leer-Pruefung, sonst verschwindet das
  Opt-in sofort wieder, wenn es das einzige gesetzte Feld ist.
- SettingsModalViewModel baut das AppSettingsDto positional neu auf. Ohne
  Round-trip haette jedes Speichern der Settings PowerUseEnabled auf den
  Record-Default false zurueckgesetzt. Gleiches Muster wie _throttleStages.

Das Handoff-Budget ist als Spinner in Settings -> General editierbar, weil es
auch fuer normale (interaktive) Handler-Runs gilt und nicht an PowerUse haengt.
Das Addendum selbst ist ueber Settings -> Files anpassbar.
2026-09-02 15:44:13 +02:00
Mika Kuns 453d61ca58 feat(worker): PowerUse-Addendum an die Handler-Prompts, Cap aus den Settings
HandoffRoundTracker.Register nimmt den Cap jetzt als Parameter statt aus einer
Konstante; HandoffMcpTools liest ihn pro Aufruf aus app_settings. Werte unter 1
werden auf 1 geklemmt (in WorkerHub UND in Register): der Cap ist das Einzige,
was eine entlaufene Handoff-Kette begrenzt, "0 = unbegrenzt" darf kein Aufrufer
waehlen koennen.

InteractiveLaunchSpecService.ComposeHandlerPromptAsync haengt das PowerUse-
Addendum an den Phasen-Prompt, aber nur wenn globaler Schalter und Listen-Opt-in
beide gesetzt sind. Bewusst ein Addendum statt einer zweiten Prompt-Garnitur:
die gemeinsamen 95% bleiben an einer Stelle, und ein Run ohne PowerUse bekommt
einen byte-identischen Prompt wie vorher. Das Addendum ist als Override der
interaktiven Regeln formuliert (nie fragen, entscheiden und protokollieren;
Konflikte loesen oder abbrechen, nie im Tree liegen lassen; Arbeit ernten, wenn
eine Runde sonst leer laeuft).

Drei Tests pinnen alle vier Ecken des UND, weil die Sicherheitsaussage des
Features genau daran haengt.
2026-09-02 15:43:48 +02:00
Mika Kuns 3cf9628f46 feat(data): PowerUse-Flags und konfigurierbares Handoff-Budget
app_settings bekommt power_use_enabled (Master-Schalter, wird im Usage-Monitor
gesetzt) und list_handler_max_handoffs (Default 4 = zwei Merge-Runden).
list_config bekommt power_use_allowed als Opt-in pro Liste.

Ein Handler-Run laeuft nur unbeaufsichtigt, wenn BEIDE Schalter gesetzt sind.
Damit kann der globale Schalter ueber Nacht an bleiben, ohne dass der Handler
einer nicht zugestimmten Liste sein Verhalten aendert.

UpdateListConfigDto.PowerUseAllowed ist bewusst bool? (tri-state, null =
gespeicherten Wert behalten), analog zu TicketProjectId: SetConfigAsync kopiert
verbatim, ein non-nullable bool haette jedem fremden Writer erlaubt, das Opt-in
per Auslassung zu widerrufen. Der Test dazu nagelt genau das fest.

Migration ist drei reine ALTER TABLE ADD COLUMN, kein Table-Rebuild.
2026-09-02 15:43:32 +02:00
Mika Kuns b00bdf28ae Merge branch 'claudedo/0a185f8e823d42fc9a5b97c5eb91c616' 2026-09-02 15:30:04 +02:00
Mika Kuns 91a37984c6 feat(claude-do): merge Handoff-Budget im Brief sichtbar machen und pro Task mitzähl
ClaudeDo-Task: bdcd601a-2fed-44d8-9164-6d2d008e7f31
2026-09-02 15:28:59 +02:00
Mika Kuns 7ee7e33654 feat(claude-do): merge Mehrfach-Abhängigkeiten: dependsOnTaskId kann nur einen Vorg
ClaudeDo-Task: cda94cd3-71c3-447f-8971-9d57be939cad
2026-09-02 15:18:54 +02:00
Mika Kuns c25732cff8 feat(claude-do): depends_on als Menge statt Einzelfeld
Ein Task kann jetzt auf beliebig viele Vorgaenger warten. Die Einzelspalte
tasks.depends_on_task_id wird durch die Beziehungstabelle task_dependencies
ersetzt (Composite-PK, beide FKs ON DELETE CASCADE -- das reproduziert das
alte ON DELETE SET NULL: geloeschter Vorgaenger = Kante weg = Task wieder
pickbar). BlockedByTaskId bleibt unberuehrt.

- Migration AddTaskDependencies kopiert jede bestehende depends_on_task_id
  verlustfrei in die Tabelle, bevor die Spalte fallengelassen wird
- QueuePicker gatet per doppelt negiertem NOT EXISTS: kein Task mit 0 Kanten
  wird blockiert, alle Kanten muessen done sein, eine Kante auf einen
  verschwundenen Task blockiert weiter
- SetDependsOnAsync ersetzt die ganze Menge; Zykluspruefung ist jetzt eine
  BFS ueber den Graphen, faengt also auch A->B->C->A
- blockedReason nennt alle offenen Vorgaenger (External/TaskBlocking, von
  ExternalMcpService und TaskWaitMcpTools gemeinsam genutzt)
- add_task/update_task nehmen dependsOnTaskIds; die alte Skalarform bleibt
  als Ein-Element-Menge inkl. ""-Clear-Sentinel gueltig
- UI: Fan-in-Zeile bleibt flach und listet alle Vorgaenger im After-Label,
  die lineare Kette rendert unveraendert
2026-09-02 15:08:05 +02:00
Mika Kuns 2330b426c6 feat(claude-do): Expose HandoffRoundTracker budget via an MCP tool (e.g. get_
The handoff-budget task (round/remaining/final now surfaced in the handoff.md brief via HandoffRoundTracker.GetStatus, wired through InteractiveLaunchSpecService.BuildForMergeHelperHandoffAsync) only reaches the session at the start of a handoff round. There is no way to query the current round/remaining budget for a handler task mid-session or from outside (e.g. the UI, or a script). Consider add

ClaudeDo-Task: 0a185f8e-823d-42fc-9a5b-97c5eb91c616
2026-09-02 14:43:44 +02:00
Mika Kuns 81a5fc5d33 feat(worker): surface the handoff round budget in the merge-helper handoff brief
A handler session only learned the chain was capped when handoff_list_handler
threw at the end of its work, stranding open tasks. The round count already
lived in HandoffRoundTracker the whole time -- it just never reached the
brief. InteractiveLaunchSpecService now reads HandoffRoundTracker.GetState
(read-only, doesn't advance the round) and renders round N of MaxHandoffs
plus the remaining count into the handoff brief, replacing the old one-line
finalNote. The rejection error also names the next action (start a new
handler run) instead of just failing.
2026-09-02 14:26:56 +02:00
Mika Kuns 84d955dfc7 docs(claude-do): plan for multi-dependency depends_on relation table 2026-09-02 14:15:39 +02:00
Mika Kuns 20685827fb feat(claude-do): merge Merge-Session soll die Queue selbst beobachten statt fertige
ClaudeDo-Task: 1788adc4-c671-4942-ab78-1ef1812da494
2026-09-02 13:59:27 +02:00
Mika Kuns 4ee1d05f06 fix(worker): carry ExpectsNoDiff into the relocated merge MCP tools
Conflict resolution for the #266 merge: #247 had moved PreviewMerge/PreviewMergeSet
and MergePreviewSetEntryDto out of ExternalMcpService into MergeMcpTools, so #266's
in-place edits to those had to be re-applied at the new location, together with the
PreviewMergeSet_ReportsExpectsNoDiffPerTask test.
2026-09-02 13:59:22 +02:00
Mika Kuns 6c1ea1ae25 feat(claude-do): Merge-Session soll die Queue selbst beobachten statt fertige
## WO — verifiziert am 2026-09-02, nicht neu suchen

- Merge-Phasen-Prompt: `src/ClaudeDo.Data/PromptFiles.cs`, Konstante `MergeHelperMergeDefault` (ab Z. 495). Der neue Warte-Abschnitt gehört zwischen "## Verify once — after the last merge" (Z. 526) und "## Handoff / end" (Z. 542) — also nach dem Verify, vor der Handoff-Entscheidung.
- Brief-Template: `MergeHelperInitialDefault` (Z. 555) und `Mer

ClaudeDo-Task: 1788adc4-c671-4942-ab78-1ef1812da494
2026-09-02 13:49:58 +02:00
Mika Kuns 7e847638cf Merge branch 'claudedo/9a02814991ef42a5a9a063d5ea579d62' 2026-09-02 13:49:56 +02:00
Mika Kuns aadc926ffe refactor(claude-do): merge refactor(worker): move the merge and worktree MCP tools out
ClaudeDo-Task: 1862e160-ffde-4a50-aa03-bb7251f56088
2026-09-02 13:43:52 +02:00
Mika Kuns c2ef446fe1 test(data,worker): cover ExpectsNoDiff persistence and MCP wiring
Data.Tests: default-false and true-roundtrip on TaskEntity.
Worker.Tests: add_task/update_task set/clear/leave-unchanged, and
preview_merge_set surfaces the flag per task.
2026-09-02 13:39:25 +02:00
Mika Kuns 7bcca3647e refactor(claude-do): refactor(worker): move the merge and worktree MCP tools out
`src/ClaudeDo.Worker/External/ExternalMcpService.cs` ist **1897 Zeilen** (nachgezählt 2026-09-02; die Datei ist seit dem Anlegen dieses Tasks von 1696 weiter gewachsen): ~30 DTO-Records oben plus ~25 Tool-Methoden — während **13** Geschwisterdateien `*McpTools.cs` im selben Ordner das Topic-Split-Muster schon tragen (`ListMcpTools`, `ConfigMcpTools`, `BatchMcpTools`, `NotesMcpTools`, `QueueStateMc

ClaudeDo-Task: 1862e160-ffde-4a50-aa03-bb7251f56088
2026-09-02 13:28:33 +02:00
Mika Kuns 244ba9b72f chore: merge main into task branch 2026-09-02 13:13:59 +02:00
Mika Kuns 0b506f529a feat(data): add ExpectsNoDiff flag for verification/audit tasks
An empty diff from a review/audit/verification task looks identical to
one that silently failed to commit. ExpectsNoDiff (TaskEntity, add_task/
update_task, preview_merge_set, review_task via TaskRefDto) lets the
merge helper tell them apart instead of treating every 0-changed-files
result as suspicious.
2026-09-02 13:13:13 +02:00
Mika Kuns 7a0b97df59 refactor(claude-do): refactor(worker): move the merge and worktree MCP tools out
`src/ClaudeDo.Worker/External/ExternalMcpService.cs` ist **1897 Zeilen** (nachgezählt 2026-09-02; die Datei ist seit dem Anlegen dieses Tasks von 1696 weiter gewachsen): ~30 DTO-Records oben plus ~25 Tool-Methoden — während **13** Geschwisterdateien `*McpTools.cs` im selben Ordner das Topic-Split-Muster schon tragen (`ListMcpTools`, `ConfigMcpTools`, `BatchMcpTools`, `NotesMcpTools`, `QueueStateMc

ClaudeDo-Task: 1862e160-ffde-4a50-aa03-bb7251f56088
2026-09-02 13:02:10 +02:00
Mika Kuns a0c277100e test(worker): assert the full external MCP tool-name inventory
Guardrail for the upcoming ExternalMcpService.cs topic split: proves the
move is name-preserving by pinning the complete set of registered tool
names (computed via the SDK's own McpServerTool.Create, not a
reimplementation of the naming rule) before any file is moved.
2026-09-02 13:02:10 +02:00
Mika Kuns 651ddbc31c chore(claude-do): merge feat(worktrees): warn about git worktrees ClaudeDo doesn't k
ClaudeDo-Task: 765fed13-db66-44e4-9285-c4b2d415b443
2026-09-02 13:01:57 +02:00
Mika Kuns 477a216567 chore(claude-do): merge refactor(tests): one IWorkerClient test double instead of tw
ClaudeDo-Task: 9ff7ec49-36c9-4b09-8747-e57bf74e37f2
2026-09-02 13:00:14 +02:00
Mika Kuns 6a4fa15372 fix(claude-do): merge batch_get_tasks: failureReason wird im fields-Parameter abge
ClaudeDo-Task: df16476b-5a10-446c-9ebc-99ddcc64e476
2026-09-02 12:59:52 +02:00
Mika Kuns 411eb7487b chore(claude-do): merge TaskRowViewModel: manuelle OnPropertyChanged-Kette auf [Noti
ClaudeDo-Task: 87976462-5845-4e01-8233-32afadca02bd
2026-09-02 12:59:35 +02:00
Mika Kuns ce70ba1c46 chore(claude-do): merge test(data): assert the migrated schema matches the EF model
ClaudeDo-Task: 9b8d47d7-e42f-4ea5-8c86-fd10a420e7f6
2026-09-02 12:59:18 +02:00
Mika Kuns 21769eb928 chore(claude-do): merge fix(localization): make the locale-parity test actually able
ClaudeDo-Task: dee67d25-ac34-4a71-b71d-6bbb1130943f
2026-09-02 12:59:07 +02:00
Mika Kuns 9b3f7ac5c8 chore(claude-do): merge fix(tests): Ui.Tests order-dependent flakiness — Loc.Current
ClaudeDo-Task: 618729f9-2d23-48e0-8952-7f17a7eb69a3
2026-09-02 12:58:44 +02:00
Mika Kuns 25f6691aa0 feat(claude-do): merge List-Handler-Prompt: Abhängigkeits-Audit in der Triage + Sch
ClaudeDo-Task: 04e9bc67-2612-4124-b513-83a02ecaa602
2026-09-02 12:58:31 +02:00
Mika Kuns dc1e67d07b fix(tests): stop Ui.Tests order-dependent flakiness on shared Loc.Current
Loc.Current (src/ClaudeDo.Ui/Localization/Loc.cs) is a process-wide mutable
static; which localizer is active depended on xUnit's parallel test
scheduling. Ui.Tests: disable collection parallelization (cheap, pure VM
suite) and install the real localizer in a module initializer so no test
can ever observe the uninitialized key-echo default. Worker.Tests: install
the same module initializer but leave parallelization on — disabling it
measured a 27% wall-clock regression on its real-SQLite/git suite, and the
initializer alone already removes the race. AgentConfigEditorViewModelTests
now installs its own localizer instead of relying on ambient state.
2026-09-02 12:55:51 +02:00
Mika KunsandClaude Haiku 4.5 912201770f fix(worker): add failureReason field to batch_get_tasks
The tool description claimed Failed tasks carry failureReason, but the field
was missing from BatchTaskDetailDto and rejected when requested in `fields`.
Add failureReason to the DTO, KnownDetailFields, and BuildDetail so list
handlers and other callers can read failure reasons without per-task get_task
calls. Tested: 31 BatchMcpToolsTests pass, including two new cases for
failureReason retrieval on Failed and Idle tasks.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-09-02 12:20:02 +02:00
Mika Kuns 9f90e75a85 refactor(tests): share StubWorkerClient between Ui.Tests and Worker.Tests
Worker.Tests carried its own hand-rolled FakeWorkerClient implementing the
whole IWorkerClient surface, duplicating StubWorkerClient in Ui.Tests. Every
hub-signature change had to be applied twice. Link StubWorkerClient.cs into
Worker.Tests via a csproj Compile Include and subclass it there; add
SetTaskStatusCalls tracking to the shared stub (the one behavior Worker.Tests
needed that Ui.Tests didn't have).
2026-09-02 12:17:00 +02:00
Mika Kuns 9e80564665 feat(data): add dependency audit and diff-review threshold to list-handler prompts
Triage phase now audits dependsOnTaskId chains before handoff: distinguishes real
from artificial (plan-numbered) dependencies, resolves artificial ones via
update_task, groups survivors into a wave plan, and checks the wave count against
the 4-handoff cap (2 merge rounds) so a chain too long to finish says so explicitly
instead of stranding mid-run.

Merge phase now reads a task's own diff when preview_merge_set's changedFileCount
is below 5, reserving the sonnet review subagent for larger diffs where the
context-protection cost is actually worth it.

Updates DefaultFor_merge_helper_merge_never_reads_diffs_itself, renamed to
DefaultFor_merge_helper_merge_gates_the_diff_subagent_on_a_file_count_threshold,
to match the new threshold behavior.
2026-09-02 12:16:44 +02:00
Mika Kuns b24ff614ff chore(claude-do): TaskRowViewModel: manuelle OnPropertyChanged-Kette auf [Noti
src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs enthält ~107 handgeschriebene OnPropertyChanged(nameof(...))-Aufrufe in partial void OnXChanged-Methoden. Das Projekt nutzt an anderen Stellen den Generator: [NotifyPropertyChangedFor] in DetailsIslandViewModel.cs, ConflictResolverViewModel.cs, AgentConfigEditorViewModel.cs, Installer/WizardViewModel.cs.

Das ist die eine Datei, in der eine ve

ClaudeDo-Task: 87976462-5845-4e01-8233-32afadca02bd
2026-09-02 12:16:06 +02:00
Mika Kuns 25ead4c8dd chore(claude-do): feat(worktrees): warn about git worktrees ClaudeDo doesn't k
From `docs/open.md` (re-verified against the code 2026-09-02, still open): nothing reconciles `git worktree list` against the `worktrees` table. `ExternalMcpService.ListWorktrees` and the Worktrees overview read DB rows only; `WorktreeManager` calls git solely inside its self-heal path (`ListWorktreePathsForBranchAsync`). Observed once: 22 git-registered worktrees + 26 `claudedo/*` branches in `C:

ClaudeDo-Task: 765fed13-db66-44e4-9285-c4b2d415b443
2026-09-02 12:09:12 +02:00
Mika Kuns e099d4f6cf test(data): assert migrated schema matches EF model (schema-drift guard)
Sibling migrations branched off the same parent have silently dropped
each other's columns during a SQLite table rebuild before, and every
other Data.Tests fixture uses EnsureCreated, which skips the migration
chain entirely and would never catch that.
2026-09-02 11:53:28 +02:00
Mika Kuns 4c78a5d6fd fix(localization): make locale-parity test able to detect a malformed locale file
LocaleStore.Load silently skips a *.json that fails to parse, so a broken
de.json left store.Available with only "en" and the parity loop compared
English against itself, always green. Add two guards before the loop:
loaded-locale count must match the number of *.json files in the folder,
and "de" must be present via TryGet, both with failure messages naming the
missing file. Also independently JsonDocument.Parse every *.json so a
syntax error surfaces as a syntax error with the file name, not as
"locale missing". LocaleStore's catch-and-skip is untouched -- a broken
translation must still not crash the app at runtime.
2026-09-02 11:52:34 +02:00
mika kuns 56bc1651a8 fix(ui): don't leave an orphaned list behind on cancel
"New list" pre-creates the DB row before opening List Settings.
Cancelling or closing that modal left an untitled empty list behind;
CreateListAsync now rolls the row back unless the modal reports Saved.

Also moves the new-list/import-repo buttons up next to the "My Lists"
label instead of a row below the list, and puts Notes above Important
in the smart-list nav order.
2026-09-02 10:00:43 +02:00
mika kuns 80fe188a48 feat(ui): add "move to list" to the task row context menu
Reuses the existing drag-drop move (MoveTaskToListAsync) so the same
running/worktree guards apply — only ListId changes.

Also count WaitingForReview tasks (not Done-with-a-branch) for the
review portion of the status pill, and localize the pill and open-task
count strings that were still hardcoded English.
2026-09-02 10:00:21 +02:00
mika kuns c347e4fc5f perf(worker): switch to workstation GC to cut idle memory
Sdk.Web defaults to Server GC, one heap per core with a ~350 MB
gen0 budget each — an idle worker sat at ~230 MB private. Workstation
GC drops that to ~40 MB with no server-side concurrency to lose.
2026-09-02 09:59:51 +02:00
mika kuns 6be62f02cc refactor(ui): remove the stale-worker banner
The banner compared the running worker's build SHA against the
selected list's HEAD via a git ancestor check on every list switch
and reconnect. It added a git-rev-parse MSBuild target, a hub call,
and a poll loop for a corner case that hasn't come up in practice —
not worth the moving parts.
2026-09-02 09:59:39 +02:00
mika kuns b6e5876b07 fix(ui): stop treating git worktrees and submodules as repos
.git as a file (worktrees, submodules) isn't a standalone repo — the
scanner was flooding the import list with worktree entries alongside
their real repo.
2026-09-02 09:59:12 +02:00
mika kuns 30d0611b8e fix(worker): fix Bandel ticket sync and reactivate the integration
The status PATCH needs statusName alongside statusId or the API 400s,
so every write-back had been silently failing. The board's flat
assigneeName is null on every live ticket; the real assignee list is
ticketAssignees[]. Import now also picks up In Bearbeitung tickets,
not just Offen ones.

Both issues are live-verified against the real API, so the feature
flag flips back on.
2026-09-02 09:59:06 +02:00
mika kuns c31f79d3d4 feat(ui): wählbares Listen-Icon und aufgeräumter Task-Detail-Header
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 41s
Listen bekommen eine Icon-Spalte (Migration AddListIcon) und einen Icon-Picker
in den Listeneinstellungen; 34 kuratierte Geometrien aus der Icon-Bibliothek,
Fallback auf "Folder" bei unbekanntem Key.

Der Metadata-Footer im Task-Detail entfällt; created-at wandert in die
Header-Zeile neben das ID-Badge, der Close-Button (und CloseDetailsCommand)
fällt weg.
2026-08-28 14:17:38 +02:00
mika kuns 22e70ffcb4 feat(ui): Mission Control als Session-Hub
- "+" öffnet ein Menü mit allen Listen (working dir) statt direkt den
  Ordner-Dialog; "Ordner wählen…" bleibt als Fallback erste Zeile, damit
  das Menü auch bei fehlgeschlagener Listen-Abfrage nutzbar bleibt
- Rail-Rows: Kontextmenü "Sitzung öffnen" (gesperrt für Running — der
  Worktree gehört dort dem autonomen Lauf)
- Aktivitäts-Strip statt reiner Queue: WaitingForReview/Failed/Roadblock
  kommen dazu, Attention zuerst, unterschieden nur über Farbe (Aktionen
  bleiben in der Task-Detailansicht)
2026-08-28 14:17:38 +02:00
Mika Kuns 8d597df448 fix(worker): make re-approve after revert_merge do a real merge, not a silent no-op
ApproveAndMergeAsync treated any non-Active worktree (including Kept, the
state RevertMergeAsync leaves behind) the same as "never had a worktree",
so re-approving a reverted task ran only the verify gate and marked the
task Done without merging anything back.

Now a Kept worktree is distinguished: if its branch is gone (the common
case, since approve normally deletes it), approve returns Blocked instead
of a fake "merged". If the branch survives, its tip is already an ancestor
of the target (from the original merge), so a plain `git merge` would
silently no-op ("Already up to date."); cherry-picking the original merge
commit's own diff (-m 1) is what actually restores the content, recorded
as a fresh MergeCommit.
2026-08-28 14:17:38 +02:00
Mika Kuns 1affb39716 fix(worker): survive a worker restart when cancelling a mid-merge unit-merge parent
CancelAsync only checked PlanningMergeOrchestrator's in-memory HasActiveMerge, which is
empty after a restart. IActiveMergeState gains an async fallback that checks on-disk
(GitService.IsMidMergeAsync) for a WaitingForReview parent with children, so the guard
still blocks cancel until the merge is continued or aborted.
2026-08-28 14:17:38 +02:00
mika kuns 7e147deddc Merge remote-tracking branch 'gitea-ssh/main'
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 42s
2026-08-27 16:46:45 +02:00
mika kuns 1a24152261 test(data): Migration des Legacy-AppData-Verzeichnisses abdecken
Paths.MigrateLegacyAppDataRoot war ungetestet: Verschieben von ~/.todo-app
nach ~/.claudeDo, Pfad-Rewrite in den Config-Dateien und der No-Op-Fall.
2026-08-27 16:43:22 +02:00
mika kuns 84219a9f88 refactor: Datei-Scope-Serialisierung entfernen
ScopeGlobs auf Tasks und SerializeOnFileOverlap auf der Listen-Config waren
ungenutzt: der Scope wurde nie befuellt, also hat der Queue-Picker nie
serialisiert. ScopeOverlap, das Picker-Gate, die DTO-Felder, die UI-Option und
die Spalten fallen weg (Migration DropFileScopeSerialization).
2026-08-27 16:43:14 +02:00
mika kuns 4e55f9161c feat(ui): Listeneinstellungen mit Kategorie-Sidebar
Body wie im Settings-Modal: Sidebar-Liste steuert einen retemplateten
TabControl (Allgemein/Agent/Verifikation) statt einer langen Scroll-Spalte.
2026-08-27 16:43:00 +02:00
mika kuns cb69e2a7f5 docs: Ticketsystem-Anbindung als pausiert vermerken 2026-08-27 14:14:30 +02:00
mika kuns 8329c92598 feat(ui): Ticketsystem-Anbindung hinter einen Aus-Schalter legen
Die Bandel-API ist unter dem eingetragenen Hostnamen nicht erreichbar
(Verbindung aktiv abgelehnt), das Feature bliebe also eine Einrichtung,
die nicht funktionieren kann. TicketSettingsTabViewModel.FeatureEnabled
versteckt alle drei Einstiegspunkte: Settings-Tab, Projekt-Dropdown in
den Listen-Einstellungen, Kontextmenue-Eintrag.

Gespeicherte Base-URL, PAT und Projektverknuepfungen bleiben erhalten
und greifen beim Umlegen sofort wieder.
2026-08-27 14:13:31 +02:00
mika kuns 3322152b9c fix(worker): Base-URL ohne Schema laesst die Ticket-Anbindung scheitern
Ein blanker Hostname ist eine relative URI; HttpClient lehnt sie mit
InvalidOperationException ab. Die lief an dem zu engen catch-Filter
vorbei, flog roh aus dem Hub und wurde von TryInvokeAsync zu null
verschluckt - die UI meldete "Worker not reachable", obwohl der Worker
lief und die Ursache eine Eingabe war.

- TicketSystemConfig ergaenzt fehlendes http://
- BandelTicketClient uebersetzt jede Transport-Exception in eine
  lesbare TicketApiException
- TestTicketConnection faengt alles und antwortet mit dem Grund
- GetTicketSettings liefert die effektive URL, damit die Ergaenzung
  sichtbar ist
2026-08-27 14:03:52 +02:00
mika kuns 2fe2ac83b1 fix(ui): Import-Erfolgsmeldung nicht mehr als Fehler einfaerben
Der Footer-Strip trug schon immer ein Level, es gab nur keinen
Einstiegspunkt jenseits von FlashFooterError. "3 neue Tasks" in Rot
liest sich als Fehlschlag.
2026-08-27 13:41:03 +02:00
mika kuns fb1ceac192 docs: Ticketsystem-Anbindung in den CLAUDE.md-Dateien
Tickets/-Ordner, TaskStateService.NotifyAsync, ticket_api_base_url und TicketRef/
TicketProjectId dokumentiert. Verbatim-Copy-Warnung um TicketProjectId ergänzt und
den PermissionMode-Vorbestandsbug (SetConfigAsync liess das Feld im Update-Zweig
aus, gefixt in f0a3a186) vermerkt. docs/open.md um die fuenf offenen Punkte aus der
manuellen Verifikation des Specs ergänzt, inkl. Hinweis dass die Bandel-Response-
Shapes nie live verifiziert wurden.
2026-08-27 13:40:29 +02:00
mika kuns e91ade9cad feat(ui): Ticket-Projekt pro Liste und Import ueber das Kontextmenue 2026-08-27 13:36:44 +02:00
mika kuns 020f93d55e feat(ui): Settings-Tab fuer die Ticketsystem-Anbindung 2026-08-27 13:29:49 +02:00
mika kuns f0a3a186dd feat(hub): Ticket-Settings, Projektliste und Import ueber den Hub 2026-08-27 13:22:54 +02:00
mika kuns 2e236736a7 feat(worker): Ticket-Import pro Liste 2026-08-27 13:05:38 +02:00
mika kuns 5aafeb6c3d feat(worker): Ticket-Status-Rueckmeldung ueber TaskStateService 2026-08-27 12:53:38 +02:00
mika kuns 1065278021 feat(worker): BandelTicketClient fuer die Ticketsystem-REST-API 2026-08-27 12:42:22 +02:00
mika kuns e9dfea905f docs(worker): IsConfigured-Kosten praezise beschreiben 2026-08-27 12:35:04 +02:00
mika kuns 2a3c937f70 feat(worker): TicketSystemConfig + ticket_api_base_url in worker.config.json 2026-08-27 12:34:24 +02:00
mika kuns 41bfc242fa refactor(worker): OnlineTokenStore zu DpapiTokenStore verallgemeinert 2026-08-27 12:27:34 +02:00
mika kuns 3171690bae feat(data): ticket_ref auf tasks, ticket_project_id auf list_config 2026-08-27 12:17:59 +02:00
mika kuns 5a13ad4492 docs(tickets): Implementierungsplan, Base-URL in worker.config.json 2026-08-27 12:15:15 +02:00
mika kuns 63184ea3b3 docs(tickets): Design fuer die Ticketsystem-Anbindung 2026-08-27 12:05:40 +02:00
mika kuns 1ef677609f refactor(ui): Icon-Faerbung zentral im ICON-PAINTING-Block
Inline Foreground/Stroke an Icons entfernt — ein lokaler Wert schlaegt jeden
Style, dadurch erreichten Hover/Checked/Disabled das Icon nicht. Farbe wird
jetzt per Klasse gewaehlt (.danger, .star-btn.on, .active), alle Zustaende
liegen in einem geordneten Block in IslandStyles.axaml.

- Path.plan-icon -> Path.icon-stroke (gilt jetzt fuer Button und ToggleButton)
- Icon.Activity als gefuellte Kontur (PathIcon fuellt, Stroke-Polyline gab Blobs)
- BloodBrightBrush als Hover-Ton fuer destruktive Icons
- icon-btn:disabled bleibt transparent statt Fluent-Grau
2026-08-27 11:29:44 +02:00
mika kuns d474fd13ea docs: neue Config-Felder + Migration-Fixture-Falle in den CLAUDE.md-Dateien 2026-08-27 10:40:26 +02:00
mika kuns 13b81cac68 feat(settings): claude_bin im Dateien-Tab setzbar, mit Auflösungs-Check
Bisher nur per Hand in worker.config.json. Neuer Abschnitt CLAUDE CLI mit
eigenem Save-Button (die Datei gehoert dem Worker, nicht app_settings) und einer
Zeile, wohin der Wert per ExecutableResolver tatsaechlich aufloest — 'nicht im
PATH gefunden' war bisher erst am fehlgeschlagenen Run zu sehen. WorkerConfig ist
DI-Singleton und ClaudeProcess loest pro Spawn auf, also greift die Aenderung ab
dem naechsten Run ohne Neustart.

SaveOnlineInbox/SaveClaudeBin teilen jetzt ein SaveKey(), damit beide dieselbe
read-modify-write-Semantik haben (alle anderen Keys bleiben unberuehrt).
Die zugehoerigen Locale-Keys sind im vorigen Commit mitgelaufen.
2026-08-27 10:39:25 +02:00
mika kuns 31d53b0033 feat(ui): Throttle-Stufen im Ausfuehrung-Tab sichtbar machen
Die vier UsageThrottle-Prozente waren nur per Gauge-Drag im Usage-Monitor
erreichbar und wurden im Settings-Modal unsichtbar durchgeschleift. Jetzt
read-only angezeigt plus Button in den Usage-Monitor. Der Button schliesst die
Settings absichtlich: Save() schreibt die beim Oeffnen gelesenen Throttle-Werte
zurueck und wuerde ein zwischenzeitliches Draggen ueberschreiben.
2026-08-27 10:39:08 +02:00
mika kuns 4464dc6ff1 feat(config): Permission-Modus pro Liste und pro Task ueberschreibbar
Bisher gab es nur AppSettings.DefaultPermissionMode global — ein Task, der plan
oder acceptEdits braucht, erzwang das Umstellen der globalen Einstellung. Neue
Spalten tasks.permission_mode und list_config.permission_mode, Auflösung
task -> list -> global im EffectiveRunConfigResolver (den TaskRunner und
get_effective_run_config gemeinsam nutzen), ComboBox mit Inherited-Badge im
geteilten Agent-Editor.

MigrationBaselineTests: das Fixture baute per EnsureCreated das heutige Schema
und stempelte Legacy-History darauf — jede Migration nach dem Squash lief damit
in 'duplicate column name'. Es migriert jetzt gezielt bis InitialCreate und
prueft 'nichts pending' statt 'InitialCreate ist die einzige Zeile'.
2026-08-27 10:28:57 +02:00
mika kuns 5cc99bfec6 feat(ui): Listen-Einstellungen — Checkbox fuer serialize_on_file_overlap
Das Flag war bisher nur ueber MCP set_list_config erreichbar. UpdateListConfigDto
fuehrt es tri-state (null = gespeicherten Wert behalten), damit nur das
Listen-Modal es setzen/loeschen kann und kein anderer Aufrufer es per Omission
verliert.
2026-08-27 10:15:17 +02:00
mika kuns 24e1d648ee fix(ui): MergeHelperSelectionModal — Task-Nummer anzeigen, Titel wrappen, Status-Spalte verbreitern 2026-08-27 09:10:44 +02:00
mika kuns 18755910bd fix(ui): Branch-Zeile im Detail-Header — Icon stroked rendern, ohne Task ausblenden
Icon.GitBranch ist Line-Art und wurde per PathIcon gefuellt (nur Knoten sichtbar).
Zusaetzlich brach {Binding Task.HasBranch} bei leerer Auswahl (Task=null) und
IsVisible fiel auf den Default true zurueck — FallbackValue=False blendet die
Zeile jetzt aus, solange kein Task gebunden ist.
2026-08-27 08:57:57 +02:00
mika kuns d7a3aa80c3 fix(ui): Review-Merge-Button-Icon stroked rendern statt gefuellt — Linien waren unsichtbar 2026-08-27 08:52:12 +02:00
mika kuns 741c7ac58d feat(ui): Review-Merge-Button — Claude nimmt Waiting-for-Review pro Liste ab
Neuer Button in der virtuellen Review-Liste: öffnet das Merge-Helper-Auswahlmodal
im Review-Modus (alle WaitingForReview-Tasks repo-verknüpfter Listen, gruppiert
unter Listen-Headern, alle vorgetickt) und startet pro gewählter Liste eine
ConPTY-Handler-Session direkt in der Merge-Phase — via bestehendem Handoff-Spec
(nextPhase "merge"), kein Triage/Wait davor. Handler-Host-Task wie beim vollen
"Let Claude handle it"-Lauf (HandlerBaseCommit, IsManual).
2026-08-27 08:44:33 +02:00
Mika Kuns d0c726baa0 fix(claude-do): merge Task-Delete mit offener ConPTY-Session blocken + Ghost-Selec
ClaudeDo-Task: df60c0ca-40e5-4284-b887-1cb45244947c
2026-08-26 15:47:20 +02:00
Mika Kuns 89f0371279 fix(claude-do): merge Modify/Delete- und Rename-Konflikte im Merge sichtbar machen
ClaudeDo-Task: 059bcf98-34d5-4ca0-822d-317980fadf8d
2026-08-26 15:46:51 +02:00
Mika Kuns c1ab042d0a fix(ui): block delete on interactive-session tasks, clear ghost selection
Delete (row context menu + detail header) now gates on HasInteractiveSession
the same way SendToQueue already does, so an open ConPTY session's claude
process can't be orphaned by deleting its task out from under it.

Row removal (in-UI delete and the delta-refresh path for externally deleted
tasks) now clears SelectedTask via SelectFrom(null, "row-removed") when the
removed row was selected, so the detail pane no longer keeps showing a
deleted task with live buttons.
2026-08-26 15:46:40 +02:00
Mika Kuns bce0c9d897 fix(claude-do): merge ScheduledFor als UTC speichern — geplante Tasks starten aktu
ClaudeDo-Task: ea2b5950-baff-4631-ad0d-6c156a01267e
2026-08-26 15:46:26 +02:00
Mika Kuns f70aec495b fix(ui): store ScheduledFor as real UTC instead of mislabeled local time
The DB's global UtcConverter only tags DateTimes as Utc on read
(SpecifyKind), it never converts on write. SetScheduledForAsync persisted
the ThemedDatePicker's Local/Unspecified wall-clock value verbatim, so
QueuePicker's comparison against DateTime.UtcNow fired scheduled tasks late
by the local UTC offset (e.g. 2h in CEST). Convert to UTC at the write
boundary, and ToLocalTime() at the read/compare sites (overdue checks in
TaskRowViewModel/TasksIslandViewModel, the date-picker's edit seed) so
existing scheduled/overdue display doesn't shift.

Existing DB rows hold local wall-clock values mistagged as Utc; no
migration added (few rows, one-time 2h-class shift accepted per the
originating audit finding).
2026-08-26 15:46:10 +02:00
Mika Kuns 421f06866e fix(claude-do): merge Status-Force-Bypässe auf laufende Tasks schließen (MCP + UI-
ClaudeDo-Task: 3b2c4015-226b-41a1-80c4-3a8d5537f2a5
2026-08-26 15:45:56 +02:00