MergeTargetsDto/MergeTargets bekommen TargetDirty (via HasChangesAsync,
includeUntracked: false). Das Merge-Modal zeigt Warnung + Stash-Retry
sofort in InitializeAsync statt erst nach dem dirty_target-Fehlschlag;
der REVIEW-Block in der Detail-Pane zeigt denselben Hinweis neben
Approve. Der dirty_target-Rueckgabepfad bleibt als Absicherung bestehen.
Behebt Befund F6 aus docs/test-session-2026-09-16.md.
F4: DetailsIslandView/TasksIslandView hatten je eine view-lokale Confirm/Error-Dialog-Kopie,
teils mit hartkodiertem "Delete"-Knopf fuer jede Aktion (inkl. der Stash+Merge-Bestaetigung,
die nichts loescht). Beide Kopien entfernt; DetailsIslandViewModel/TasksIslandViewModel routen
Confirm/Error jetzt durch den geteilten Dialogs-Seam (IDialogService, propagiert vom Shell wie
schon bei Lists/Tasks) statt ueber eine vom Code-behind gesetzte Func-Property.
IDialogService.ConfirmAsync bekommt einen optionalen confirmLabel-Parameter (Default: lokalisiertes
"Confirm"); nur die echten Delete-Aktionen (Task loeschen, abgeschlossene Tasks leeren) uebergeben
das lokalisierte "Delete"-Label.
F5: "Cancel"/"Confirm"/"Delete"/"OK" und die Titel "Confirm"/"Error" durch Loc.T mit neuen Keys
unter "dialog.*" ersetzt (en.json + de.json). Mission Controls eigene Dialog-Kopie bleibt bestehen
(eigenes Fenster, WindowDialogService ist an den Haupt-Owner gebunden) — nur ihre Labels wurden
lokalisiert.
Out of scope (siehe Task-Beschreibung): die Nachrichtentexte der Dialoge bleiben unlokalisiert.
Overlap detection was file-name-only: 9/15 tasks touching en.json/de.json in
run #316 all got flagged even though only one pair actually collided on a
key, and a removed method with no textual file conflict (#297 vs #286) went
unflagged entirely.
- removedSymbols per task entry: a line-pattern scan of `-` diff lines for
C# method/property/field/type/event declarations (skips .axaml/.json).
- symbolWarnings at the set level: cross-references those names against
every other task's `+` lines, catching same-symbol collisions across
files that overlaps/subsets (both purely file-based) can't see.
- overlaps narrows to key-path level for .json files (base commit vs. task
branch head, both sides parsed and flattened) so two tasks adding
different locale keys to the same file no longer collide; unparsable
JSON on either side falls back to the old file-level check.
- GitService.ShowFileAsync: `git show ref:path`, used to read a task
branch's json content at its base/head commit.
Out of scope per the task: no Roslyn/parser, no AXAML analysis, and the
merge-helper prompt in PromptFiles.cs doesn't mention the new fields yet.
Build: dotnet build src/ClaudeDo.App (pulls in Ui+Data) + src/ClaudeDo.Worker, both clean.
Tests: dotnet test tests/ClaudeDo.Worker.Tests --filter "FullyQualifiedName~MergeMcpTools|FullyQualifiedName~ExternalMcpToolSchema" -- 51 passed, 0 failed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Befund F7 aus der Sichtpruefung 2026-09-16. TaskNumber war bereits im
WorktreeMaintenanceService-Join und in WorktreeOverviewRow vorhanden, fehlte
aber im Wire-DTO und in der UI-Kette; jetzt bis zur Row-/Conflict-Anzeige
durchgereicht (#<number> dimmed vor dem Titel, ausgeblendet bei 0). DIFF-Spalte
bekommt CharacterEllipsis-Trimming + Tooltip statt hartem Cut, Spalte von 80
auf 110px verbreitert (Header und Row synchron).
Ctrl+V mit einem Bild in der Zwischenablage schreibt es als temporaeres
PNG unter ~/.claudeDo/tmp/ und fuegt den Pfad in den Composer ein, genau
wie ein Datei-Drop -- Claude liest die Datei selbst. Text-Paste laeuft
unveraendert ueber den bestehenden bracketed-paste-Pfad.
Avalonia 12.0.4 liest Clipboard-Bitmaps bereits selbst (IClipboard.
TryGetDataAsync + AsyncDataTransferExtensions.TryGetBitmapAsync), kein
Win32-P/Invoke noetig. Alte Tmp-Dateien (>24h) werden beim App-Start
aufgeraeumt (ClaudeDo.App/Program.cs), keine eigene Aufraeum-Infrastruktur.
ToggleFolderAsync war ein async [RelayCommand]; AsyncRelayCommand sperrt
sich waehrend des DB-Writes selbst, schnelle Klicks gingen verloren
(Befund F1, docs/test-session-2026-09-16.md).
Variante A: Toggle ist jetzt synchron (reiner UI-State), Persistierung
fire-and-forget mit einem CancellationTokenSource pro Ordner, damit ein
zweiter Klick vor dem ersten Schreibvorgang diesen abbricht - die DB
landet immer beim zuletzt sichtbaren Zustand, nie bei einem veralteten.
Gleiches Muster wie SaveTitleAsync/SaveDescriptionAsync in
DetailsIslandViewModel.
Gegenprueft: RemoveFromFolderAsync/CreateListAsync haben denselben
async-RelayCommand-Schnitt, sind aber Einzelklick-Aktionen (Kontextmenue/
Button), kein Rapid-Click-Ziel wie der Ordner-Toggle - bewusst nicht
umgebaut. Die Modal-oeffnenden Commands sind unkritisch, ein Modal
blockiert ohnehin.
preview_merge baute bisher immer, sobald die Liste ein Verify-Kommando
konfiguriert hatte -- anders als das Geschwister-Tool preview_merge_set,
dessen runVerify-Parameter per Default false ist. Ein einzelnes preview_merge
im Merge-Helper-Recheck stiess dadurch einen ungewollten Scratch-Build an.
runVerify (Default false) jetzt symmetrisch bei preview_merge ergaenzt,
Description-Text und die Merge-Helper-Prompt-Doku angepasst.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Der Queue-Picker gatete eine task_dependencies-Kante auf status='done'.
review_task(skipVerify) laesst einen erfolgreich gemergten Task aber
bewusst auf WaitingForReview stehen, bis verify_merges einmal zentral
laeuft -- damit blockierte jeder Nachfolger ueber den gesamten Batch plus
Full-Suite-Verify hinweg, bei freien Slots.
Eine Kante gilt jetzt als erfuellt, wenn der Vorgaenger 'done' ist ODER
sein Merge gelandet ist (worktrees.state = 'merged'). TaskBlocking
(get_task/list_tasks/wait_for_task_change) und der UI-Wartehinweis folgen
derselben Regel, damit "blocked" nicht meldet, was der Picker claimt.
Abgeleitet: revert_merge/abort_merge setzen den Worktree auf Active und
blockieren wieder; reject_rerun/reject_park haben nie gemergt und bleiben
unerfuellt; ein Task ohne Worktree wird nur ueber 'done' erfuellt; ein
fehlgeschlagener verify_merges gibt Nachfolger frei, weil der Code so
oder so auf main liegt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein dirty Ziel-Arbeitsverzeichnis gibt jetzt den eigenen Status
dirty_target zurueck statt blocked. Die UI bietet daraufhin einen
Retry an, der per git merge --autostash stasht, merged und die
Aenderungen danach wiederherstellt - opt-in pro Merge:
- Approve & Merge: Bestaetigungsdialog
- Merge-Modal: Button "Aenderungen stashen und mergen"
- MCP (merge_task/review_task) wirft weiterhin, kein autostash dort
Die oberste Sidebar-Ebene ist jetzt eine gemischte TopLevel-Collection, so
dass eine Liste auch oberhalb eines Ordners stehen kann. DropAsync deckt
alle vier Quelle/Ziel-Kombinationen ab, ReorderSidebarAsync numeriert die
gesamte sichtbare Reihenfolge ueber lists und list_folders hinweg.
ImportTickets sendete nur ListUpdated. Die UI liest das als Aenderung der
Listen-Metadaten und patcht nur bestehende Zeilen, also tauchten die
importierten Tasks erst nach Schliessen und Neuoeffnen der Liste auf.
TicketImportResult liefert jetzt die angelegten Task-Ids, der Hub
broadcastet pro Id ein TaskUpdated - derselbe Pfad, den jeder andere
out-of-band Creator nutzt.
RevParseHeadAsync and CommitAsync's identity-error detection match
English-only substrings against git stderr. Under a non-English OS/git
locale these silently fail to match, falling through to a generic
error. Force LC_ALL=C and LANGUAGE=C on the git ProcessStartInfo so
git always emits English output regardless of ambient locale.
The tile's own X killed a live Claude session with no confirmation, unlike
the semantically similar Abandon-run action. Gate the close behind the same
ConfirmAsync mechanism, but only when Terminal.IsRunning -- a dead session
(StartError or exited-after-startup) still closes immediately since the X
there is pure cleanup.
TaskRowViewModel had its own ParseDiffStat expecting "+12"/"-3" tokens that
`git diff --stat` never emits, so the row badge always showed 0/0. Meanwhile
DetailsIslandViewModel's parser matched only the English words
"insertion"/"deletion", so it broke on a localized git (e.g. German Windows:
"Einfügung(en)"/"Löschung(en)").
Row and detail badge now share one parser, anchored on git's untranslated
(+)/(-) suffix instead of the language-dependent word before it, so it reads
correctly in any locale. Also corrected the Worker CLAUDE.md's branch-name
claim (`claudedo/{taskId[:8]}`) to match the actual full-GUID branch name.
Adds a Shutdown entry after the Worker menu's Restart/Update Claude CLI
items. Confirms via IDialogService.ConfirmAsync, then kills this
installation's ClaudeDo.Worker process(es) (shared kill loop, extracted
from RestartWorkerService) and force-closes the window through the same
_forceClose path MainWindow already uses for the Mission-Control close
confirm, so no second confirmation dialog shows.
A standalone parent (PlanningPhase.None) with existing Idle/Queued children
now runs itself first, then sequences its children instead of flat-enqueuing
all of them at once: SubmitForChildrenAsync calls
PlanningChainCoordinator.SetupChainAsync(enqueue: true) right after the
Running -> WaitingForChildren transition, mirroring the planning path but
firing immediately instead of waiting for an explicit "Queue plan". Planning
parents are left untouched (they queue via QueuePlanAsync), and a child
already Running makes SetupChainAsync throw, which is now caught and logged
so it can't fail the parent's own transition.
PasteAsync silently swallowed every exception and returned void, so a
package update renaming Iciclecreek's private write seam would break
Ctrl+V/drop with zero user-visible signal. PasteAsync/SendTextAsync now
return bool (mirroring SendLineAsync), and the view checks the result,
routing a failure through InteractiveTerminalViewModel.ErrorReported ->
ConPtyPaneViewModel.ErrorReported -> the existing footer error strip.
WorkerConnectionModalViewModel's StartWorker/RerunInstaller swallowed
missing-exe and Process.Start failures behind an empty catch and closed
the dialog anyway, looking like success. Now both show an inline error
and keep the dialog open; only a real launch closes it.
IslandsShellViewModel.RestartWorkerAsync never checked whether the
relaunched worker actually stayed up, and its status property was never
bound in any view. RestartWorkerService now fails if the new process
exits within a short survival window, and the outcome routes through
the footer error/success strip instead of the dead RestartWorkerStatus.
InstallArtifactLocator.Find() is now virtual and its two subclasses
unsealed so tests can fake exe resolution without touching the real
filesystem/registry.
Three usability-audit findings in Settings:
- An unparseable PowerUse window (e.g. "9:0") used to save silently as null
(= round the clock), the opposite of what the typo implied. Validate() now
rejects an unparseable start/end unless blank; the worker-side read-time
tolerance is unchanged.
- "Effort" column header gets a tooltip explaining it's the model's reasoning
effort (cost/depth tradeoff).
- The "Prime Claude" tab gets a tooltip explaining scheduled Claude runs
(Ping / Fill My Day / custom prompt).
New locale keys added to en+de. Updated the PowerUse round-trip test that
asserted the old silent-normalize behavior.
Group C of the operation-feedback batch is fully merged; the two consumers
(DetailsIslandViewModel.ApproveReviewAsync, MergeModalViewModel) now handle
the verifying phase from the generic OperationProgressEvent handler instead
of a separate forwarder event.
ConnectedClientTracker zaehlt verbundene UI-Clients (WorkerHub.OnConnectedAsync/
OnDisconnectedAsync). StartListHandler prueft ihn vor dem Broadcast und liefert
bei 0 verbundenen Clients Requested=false plus Reason statt unbedingtem true.
Tool-Beschreibung und external-mcp.md an das reale Verhalten angepasst.
Reuses RepoScanner.IsRepo (now public) to validate WorkingDir whenever it
changes in ListSettingsModalViewModel — Browse or typing. A missing folder
or a non-repo folder shows an inline warning and gates Save via the same
CanExecute pattern the load-failure gate already uses; an empty WorkingDir
is untouched. Previously this only surfaced as a Failed task deep in
WorktreeManager on first run.
Also localizes the two hardcoded EN strings in the same file (delete
confirm, planning-sessions block) into vm.listSettings.* keys.
Usability-Audit 2026-09-10 found three gaps versus the dependency-chain-display
spec: a Queued task blocked on an unmet task_dependencies edge rendered the same
chip as one about to run, a cross-list predecessor silently dropped the
"after #N" chip, and the detail pane showed nothing about a wait at all.
- TaskRowViewModel gains IsBlockedByDependency/DependencyWaitLabel (extrinsic,
assigned by TasksIslandViewModel like the existing chain fields); IsQueued/
IsWaiting/StatusChipClass/StatusLabel/StatusChipTooltip now account for it
alongside the existing BlockedByTaskId planning-chain link.
- TasksIslandViewModel.ApplyDependencyWaitState computes the blocked flag and
wait label per row; a new RefreshExternalDependencyCacheAsync backfills
Number/Title/Status for predecessors not loaded into the current list's
Items (reusing the already-open DbContext, no new hub method), so
BuildAfterLabel/blocking both resolve a listenfremder Vorgänger correctly.
- New "waiting" chip style (IslandStyles.axaml) and locale keys (en+de);
TaskHeaderBar shows a "Waiting on #N" line bound straight to the same row.
Tests: TasksIslandChainTests (blocked/unblocked chip, cross-list predecessor),
DetailsIslandDependencyWaitTests (new), TaskRowViewModelNotificationsTests
updated for the new notification set.
InteractiveTerminalViewModel.HasExitedAfterStartup now covers every
post-startup death that ExitedAfterStartup alone missed (crash, clean
exit, quiet exit inside the grace window) -- previously the tile just
stayed looking alive/thinking with zero signal. ConPtyPaneView binds a
new banner to it (message + Retry + Close); CanRetry simplifies to
"any HasExited state" since the same descriptor factory can relaunch
either kind of death. HandlerRestartCoordinator's worker-side
auto-restart path is untouched.
en.json and de.json each defined the checks object twice; JSON silently
kept the second block's values, so the first block's friendlier titles/hints
(e.g. "Permission Mode" over the raw "--permission-mode auto support" flag)
were dead text. Merged into one block per key, keeping the friendlier title
and the more actionable hint.
Added a Utf8JsonReader-based duplicate-top-level-key test, since
JsonDocument accepts duplicates without error and the existing parity test
can't see this failure mode either.
TaskRunner.BuildFailureMarkdown returned a hardcoded German string for
TerminalReason == "max_turns", the only worker-generated string not in
English. Surfaced as the outcome/roadblock card for one of the most likely
first failures, looking broken to non-German users.
Closes the last unrouted --permission-mode call site: haiku default models were
hardcoding raw "auto", which the CLI silently downgrades to interactive "default"
and hangs the report generation. Also drops the author's personal C:\Private
default exclude in favor of an empty default (config stays via ReportExcludedPaths).
Mutex was Local\ (per Windows session), so a second RDP/fast-user-switching
session bypassed the single-instance guard. A Kestrel bind failure on top of
that had no try/catch, so a second worker (or any process on 47821/47822)
died as an unhandled crash with nothing in the log to explain why the UI
never connects. Mutex is now Global\, bind failures and any last-resort
unhandled exception log a Fatal line and exit(1) instead of crashing raw.
Neuer Eintrag im Worker-Menü der Titelleiste, der die Claude CLI über
das konfigurierte claude_bin (ExecutableResolver, inkl. Shim-Handling)
mit `claude update` aktualisiert. Ausgabe/Fehler landen im Footer-Strip
(FlashFooterSuccess/FlashFooterError), Menüpunkt ist während des Laufs
deaktiviert. 5-Minuten-Timeout tötet den Prozessbaum.
Data: folder delete keeps its lists (ON DELETE SET NULL), rename patches
name/icon without resetting sort order or collapse state.
Sidebar: grouping on load, folder badge as the sum of its children, both
move routes, and the persisted collapse state.
Drops the LoadAsync/AddRowAsync normalization of a dangling folder_id:
FK enforcement is on for every connection, so the row it guarded against
cannot exist, and ContainerOf already falls back to the top level.
Ctrl+V wurde von TerminalControl als rohes Control-Byte an das Kind
weitergegeben und tat nichts; jetzt wird es im Tunnel abgefangen und geht
ueber TerminalView.PasteAsync, damit Bracketed Paste greift und ein
mehrzeiliger Paste nicht auf der ersten Zeile abschickt. Gedroppte Dateien
werden als Pfade in den Composer getippt, ohne Enter.
Sechs Markdown-Sheets (git, Conventional Commits, Claude CLI, Claude-Config,
dotnet CLI, PowerShell) als EmbeddedResource, gerendert im modelosen, per
Topmost anpinnbaren CheatSheetsWindow. MarkdownView bekommt dafuer das Opt-in
CommandRows: `befehl` - beschreibung wird zu Mono-Chip + Copy-Button, jede
##-Sektion landet in einem Expander. Enthaelt ausserdem: Mission Control
wird beim Oeffnen aus dem Minimiert-Zustand geholt, nicht nur aktiviert.
Schalter, Zeitfenster und Handoff-Prozent lagen im Usage-Monitor und mussten
in SettingsModalViewModel nur load->save durchgeschleift werden, damit ein
Settings-Speichern sie nicht zurueckwirft. Jetzt gehoeren sie dem General-Tab,
der Monitor ist wieder nur Anzeige + Threshold-Drag, und "Jetzt aktualisieren"
sitzt als Icon in der Titelleiste (neuer ModalShell.HeaderActions-Slot).
Eine manuelle Liste (Erinnerungen) hat kein WorkingDir. BuildForTaskAsync
warf dafuer bisher "no working directory configured", die interaktive
Session liess sich also nicht oeffnen. Jetzt faellt der Pfad auf das
User-Verzeichnis zurueck; Session-Skills werden dort bewusst nicht
geseedet, weil das Ziel das echte ~/.claude/skills waere.
Ein "Abandon run"-Knopf an Handler-Panes fragt HandlerRoundState.Get(handlerTaskId)
nach den noch offenen (Queued/Running) Tasks des Laufs, bestaetigt mit Anzahl,
cancelt jeden ueber den bestehenden CancelTaskAsync-Pfad und schliesst danach die
Session -- ein Schritt statt Tile schliessen + Queue separat abbrechen. Ohne
offene Tasks (oder ohne Round-Eintrag) schliesst die Aktion direkt, ohne Dialog.
Nur an den beiden Handler-Panes sichtbar (IsListHandler), normale Task-/Ad-hoc-
Panes bleiben unveraendert.
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.
BeginLoad's fire-and-forget LoadAsync now catches instead of leaving an
unobserved task exception, reports it via the new ErrorReported event
(wired into FlashFooterError like the other modal VMs), and keeps Save
locked so the failure can't clobber list_config with blank fields.
LoadTicketProjectsAsync's empty catch now reports too; behavior for the
dropdown itself is unchanged.
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.