fix(claude-do): merge fix(ui): Aktions- und Bestätigungs-Konsistenz (UX-Audit #3)
ClaudeDo-Task: bd2a0206-78aa-4c1c-99a5-1c241423c5c8
This commit is contained in:
@@ -167,6 +167,7 @@
|
|||||||
"manualTip": "Manuelle Erinnerung — Claude nimmt sie nie auf",
|
"manualTip": "Manuelle Erinnerung — Claude nimmt sie nie auf",
|
||||||
"ctxMarkManual": "Als manuell markieren",
|
"ctxMarkManual": "Als manuell markieren",
|
||||||
"ctxMarkClaudeTask": "Als Claude-Aufgabe markieren",
|
"ctxMarkClaudeTask": "Als Claude-Aufgabe markieren",
|
||||||
|
"ctxDeleteTask": "Aufgabe löschen",
|
||||||
"approve": "Genehmigen",
|
"approve": "Genehmigen",
|
||||||
"approveTip": "Genehmigen — als Erledigt markieren",
|
"approveTip": "Genehmigen — als Erledigt markieren",
|
||||||
"reject": "Ablehnen",
|
"reject": "Ablehnen",
|
||||||
@@ -201,12 +202,15 @@
|
|||||||
"contextOpenExplorer": "Im Explorer öffnen",
|
"contextOpenExplorer": "Im Explorer öffnen",
|
||||||
"contextOpenTerminal": "Im Terminal öffnen",
|
"contextOpenTerminal": "Im Terminal öffnen",
|
||||||
"contextOpenFindings": "Findings-Ordner öffnen",
|
"contextOpenFindings": "Findings-Ordner öffnen",
|
||||||
|
"contextSettings": "Einstellungen…",
|
||||||
"newList": "Neue Liste",
|
"newList": "Neue Liste",
|
||||||
"addReposTip": "Repos als Listen hinzufügen"
|
"addReposTip": "Repos als Listen hinzufügen"
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"deleteTaskTip": "Aufgabe löschen",
|
"deleteTaskTip": "Aufgabe löschen",
|
||||||
"killSessionTip": "Laufende Sitzung beenden",
|
"killSessionTip": "Laufende Sitzung beenden",
|
||||||
|
"sendToQueue": "In Warteschlange einreihen",
|
||||||
|
"sendToQueueTip": "Diese Aufgabe in die Warteschlange einreihen, damit Claude sie beim nächsten Lauf übernimmt",
|
||||||
"closeTip": "Schließen",
|
"closeTip": "Schließen",
|
||||||
"copyTaskIdTip": "Aufgaben-ID kopieren",
|
"copyTaskIdTip": "Aufgaben-ID kopieren",
|
||||||
"starTip": "Favorit",
|
"starTip": "Favorit",
|
||||||
@@ -285,7 +289,7 @@
|
|||||||
"running": "Läuft",
|
"running": "Läuft",
|
||||||
"focusMode": "Fokus",
|
"focusMode": "Fokus",
|
||||||
"overviewMode": "Übersicht",
|
"overviewMode": "Übersicht",
|
||||||
"closeSession": "Sitzung schließen",
|
"closeSession": "Sitzung beenden",
|
||||||
"conptyLaunchFailed": "Interaktive Sitzung konnte nicht geöffnet werden: {0}",
|
"conptyLaunchFailed": "Interaktive Sitzung konnte nicht geöffnet werden: {0}",
|
||||||
"conptyStarting": "Sitzung wird gestartet…",
|
"conptyStarting": "Sitzung wird gestartet…",
|
||||||
"mergeHelperTitle": "Merge-Helfer",
|
"mergeHelperTitle": "Merge-Helfer",
|
||||||
@@ -454,8 +458,8 @@
|
|||||||
"refreshHint": "Abruf alle 5 Min., während ein Task läuft, sonst alle 15 Min.",
|
"refreshHint": "Abruf alle 5 Min., während ein Task läuft, sonst alle 15 Min.",
|
||||||
"staleFormat": "Werte veraltet (Stand {0})",
|
"staleFormat": "Werte veraltet (Stand {0})",
|
||||||
"staleGateHint": "Das Gate greift in diesem Zustand nicht.",
|
"staleGateHint": "Das Gate greift in diesem Zustand nicht.",
|
||||||
"gateBlockedFormat": "Queue pausiert — {0}",
|
"gateBlockedFormat": "Warteschlange pausiert — {0}",
|
||||||
"throttleFormat": "Queue gedrosselt: {0}/{1} Slots ({2})",
|
"throttleFormat": "Warteschlange gedrosselt: {0}/{1} Slots ({2})",
|
||||||
"resetIn": "Reset in {0}",
|
"resetIn": "Reset in {0}",
|
||||||
"dragHint": "Marker ziehen oder Wert unten eintragen.",
|
"dragHint": "Marker ziehen oder Wert unten eintragen.",
|
||||||
"legendSoft": "Soft · 2 Slots",
|
"legendSoft": "Soft · 2 Slots",
|
||||||
@@ -678,7 +682,7 @@
|
|||||||
"planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" },
|
"planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" },
|
||||||
"taskRow": { "createdPrefix": "Erstellt {0}", "stepsText": "{0}/{1} Schritte" },
|
"taskRow": { "createdPrefix": "Erstellt {0}", "stepsText": "{0}/{1} Schritte" },
|
||||||
"queue": { "baseDirtyWarning": "Achtung: Das Repo dieser Liste hat uncommittete Änderungen ({0} geändert, {1} untracked) — ein neuer Worktree startet vom letzten Commit und enthält sie nicht." },
|
"queue": { "baseDirtyWarning": "Achtung: Das Repo dieser Liste hat uncommittete Änderungen ({0} geändert, {1} untracked) — ein neuer Worktree startet vom letzten Commit und enthält sie nicht." },
|
||||||
"tasksIsland": { "completedHeader": "ABGESCHLOSSEN", "completedHeaderCount": "ABGESCHLOSSEN · {0}", "planningOpenFailed": "Planungssitzung konnte nicht geöffnet werden: {0}", "planningResumeFailed": "Planungssitzung konnte nicht fortgesetzt werden: {0}", "approveFailed": "Genehmigen & Mergen fehlgeschlagen: {0}", "cancelReviewFailed": "Prüfung abbrechen fehlgeschlagen: {0}", "sendToQueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "queuePlanBlockedInteractive": "Plan kann nicht in die Warteschlange gestellt werden — {0} hat eine offene interaktive Sitzung und muss zuerst geschlossen werden.", "moveRunningRejected": "Ein laufender Task kann nicht in eine andere Liste verschoben werden.", "moveWorktreeRejected": "Verschieben nicht möglich — dieser Task hat einen aktiven Worktree, der auf sein aktuelles Repo zeigt.", "moveRepoConfirm": "Unterschiedliche Repos — {0} → {1}. Task trotzdem verschieben?", "moveConfirmUnavailable": "Verschieben nicht möglich — der Bestätigungsdialog ist nicht verfügbar.", "quickClaudeNoWorkingDir": "Für diese Liste ist kein Arbeitsverzeichnis konfiguriert.", "quickClaudeDirMissing": "Arbeitsverzeichnis existiert nicht mehr: {0}", "setStatusFailed": "Status konnte nicht aktualisiert werden: {0}", "cancelFailed": "Abbrechen fehlgeschlagen: {0}", "rejectToQueueFailed": "Zurückweisen in die Warteschlange fehlgeschlagen: {0}", "rejectToIdleFailed": "Zurückweisen fehlgeschlagen: {0}" },
|
"tasksIsland": { "completedHeader": "ABGESCHLOSSEN", "completedHeaderCount": "ABGESCHLOSSEN · {0}", "planningOpenFailed": "Planungssitzung konnte nicht geöffnet werden: {0}", "planningResumeFailed": "Planungssitzung konnte nicht fortgesetzt werden: {0}", "approveFailed": "Genehmigen & Mergen fehlgeschlagen: {0}", "cancelReviewFailed": "Prüfung abbrechen fehlgeschlagen: {0}", "sendToQueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "queuePlanBlockedInteractive": "Plan kann nicht in die Warteschlange gestellt werden — {0} hat eine offene interaktive Sitzung und muss zuerst geschlossen werden.", "moveRunningRejected": "Ein laufender Task kann nicht in eine andere Liste verschoben werden.", "moveWorktreeRejected": "Verschieben nicht möglich — dieser Task hat einen aktiven Worktree, der auf sein aktuelles Repo zeigt.", "moveRepoConfirm": "Unterschiedliche Repos — {0} → {1}. Task trotzdem verschieben?", "moveConfirmUnavailable": "Verschieben nicht möglich — der Bestätigungsdialog ist nicht verfügbar.", "quickClaudeNoWorkingDir": "Für diese Liste ist kein Arbeitsverzeichnis konfiguriert.", "quickClaudeDirMissing": "Arbeitsverzeichnis existiert nicht mehr: {0}", "setStatusFailed": "Status konnte nicht aktualisiert werden: {0}", "cancelFailed": "Abbrechen fehlgeschlagen: {0}", "rejectToQueueFailed": "Zurückweisen in die Warteschlange fehlgeschlagen: {0}", "rejectToIdleFailed": "Zurückweisen fehlgeschlagen: {0}", "deleteTaskConfirm": "\"{0}\" löschen? Dies kann nicht rückgängig gemacht werden.", "deleteTaskFailed": "Löschen fehlgeschlagen: {0}" },
|
||||||
"diff": { "loadFailed": "Diff konnte nicht geladen werden: {0}", "noChanges": "Keine Änderungen anzuzeigen.", "unavailable": "Diff nicht mehr verfügbar — Commit-Bereich unvollständig." },
|
"diff": { "loadFailed": "Diff konnte nicht geladen werden: {0}", "noChanges": "Keine Änderungen anzuzeigen.", "unavailable": "Diff nicht mehr verfügbar — Commit-Bereich unvollständig." },
|
||||||
"planningDiff": { "hubError": "Kombinierte Vorschau konnte nicht erstellt werden (Hub-Fehler).", "conflict": "Kombinierte Vorschau nicht möglich: Teilaufgabe {0} steht im Konflikt mit einer früheren Teilaufgabe ({1} Dateien).", "buildFailed": "Kombinierte Vorschau konnte nicht erstellt werden: {0}" },
|
"planningDiff": { "hubError": "Kombinierte Vorschau konnte nicht erstellt werden (Hub-Fehler).", "conflict": "Kombinierte Vorschau nicht möglich: Teilaufgabe {0} steht im Konflikt mit einer früheren Teilaufgabe ({1} Dateien).", "buildFailed": "Kombinierte Vorschau konnte nicht erstellt werden: {0}" },
|
||||||
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Wird zusammengeführt…", "progressVerifying": "Verify-Kommando der Liste läuft… ({0})", "workerOfflineBranches": "Worker offline — Branches können nicht aufgelistet werden.", "loadBranchesFailed": "Branches konnten nicht geladen werden: {0}", "merged": "Zusammengeführt.", "conflict": "Merge-Konflikt — Ziel-Branch wiederhergestellt. Manuell oder über Fortsetzen lösen, dann erneut versuchen.", "blocked": "Blockiert: {0}", "verifyFailed": "Merge ist gelandet, aber das Verify-Kommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf Erledigt gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "unknownStatus": "Unbekannter Status: {0}", "mergeFailed": "Merge fehlgeschlagen: {0}" },
|
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Wird zusammengeführt…", "progressVerifying": "Verify-Kommando der Liste läuft… ({0})", "workerOfflineBranches": "Worker offline — Branches können nicht aufgelistet werden.", "loadBranchesFailed": "Branches konnten nicht geladen werden: {0}", "merged": "Zusammengeführt.", "conflict": "Merge-Konflikt — Ziel-Branch wiederhergestellt. Manuell oder über Fortsetzen lösen, dann erneut versuchen.", "blocked": "Blockiert: {0}", "verifyFailed": "Merge ist gelandet, aber das Verify-Kommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf Erledigt gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "unknownStatus": "Unbekannter Status: {0}", "mergeFailed": "Merge fehlgeschlagen: {0}" },
|
||||||
@@ -694,7 +698,7 @@
|
|||||||
"filesTab": { "workerOffline": "Worker offline.", "noneBundled": "Keine Standard-Agenten mitgeliefert.", "allPresent": "Alle Standard-Agenten bereits vorhanden.", "restored": "{0} Standard-Agent(en) wiederhergestellt.", "restoreFailed": "Wiederherstellung fehlgeschlagen: {0}", "resetToDefault": "Auf den mitgelieferten Standard zurückgesetzt." },
|
"filesTab": { "workerOffline": "Worker offline.", "noneBundled": "Keine Standard-Agenten mitgeliefert.", "allPresent": "Alle Standard-Agenten bereits vorhanden.", "restored": "{0} Standard-Agent(en) wiederhergestellt.", "restoreFailed": "Wiederherstellung fehlgeschlagen: {0}", "resetToDefault": "Auf den mitgelieferten Standard zurückgesetzt." },
|
||||||
"sessionSkillsTab": { "installed": "Installiert: {0}", "installFailed": "Installation fehlgeschlagen: {0}", "updated": "Aktualisiert.", "updateFailed": "Aktualisierung fehlgeschlagen: {0}", "removed": "Entfernt.", "removeFailed": "Entfernen fehlgeschlagen: {0}" },
|
"sessionSkillsTab": { "installed": "Installiert: {0}", "installFailed": "Installation fehlgeschlagen: {0}", "updated": "Aktualisiert.", "updateFailed": "Aktualisierung fehlgeschlagen: {0}", "removed": "Entfernt.", "removeFailed": "Entfernen fehlgeschlagen: {0}" },
|
||||||
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "{0} Worktree(s) entfernt.", "blocked": "Zwangsentfernung nicht möglich: {0} Aufgabe(n) laufen noch. Brich sie zuerst ab.", "removedFrom": "{0} Worktree(s) von {1} Aufgabe(n) entfernt.", "cleanupFailed": "Aufräumen fehlgeschlagen: {0}", "resetFailed": "Zurücksetzen fehlgeschlagen: {0}" },
|
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "{0} Worktree(s) entfernt.", "blocked": "Zwangsentfernung nicht möglich: {0} Aufgabe(n) laufen noch. Brich sie zuerst ab.", "removedFrom": "{0} Worktree(s) von {1} Aufgabe(n) entfernt.", "cleanupFailed": "Aufräumen fehlgeschlagen: {0}", "resetFailed": "Zurücksetzen fehlgeschlagen: {0}" },
|
||||||
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "Liste", "cleanupFailed": "Aufräumen fehlgeschlagen.", "cleanupFailedDetailed": "Aufräumen fehlgeschlagen: {0}", "removed": "{0} Worktree(s) entfernt.", "discardFailed": "Worktree konnte nicht verworfen werden.", "keepFailed": "Worktree konnte nicht behalten werden.", "cannotForceRunning": "Eine laufende Aufgabe kann nicht zwangsweise entfernt werden.", "forceRemoveFailed": "Zwangsentfernung fehlgeschlagen.", "forceRemoveFailedDetailed": "Zwangsentfernung fehlgeschlagen: {0}", "batchProgress": "Merge {0}/{1}…", "batchDone": "{0} gemergt, {1} zu lösen." },
|
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "Liste", "cleanupFailed": "Aufräumen fehlgeschlagen.", "cleanupFailedDetailed": "Aufräumen fehlgeschlagen: {0}", "removed": "{0} Worktree(s) entfernt.", "discardConfirm": "Worktree für \"{0}\" verwerfen? Nicht committete Arbeit geht verloren.", "discardFailed": "Worktree konnte nicht verworfen werden.", "keepFailed": "Worktree konnte nicht behalten werden.", "cannotForceRunning": "Eine laufende Aufgabe kann nicht zwangsweise entfernt werden.", "forceRemoveFailed": "Zwangsentfernung fehlgeschlagen.", "forceRemoveFailedDetailed": "Zwangsentfernung fehlgeschlagen: {0}", "batchProgress": "Merge {0}/{1}…", "batchDone": "{0} gemergt, {1} zu lösen." },
|
||||||
"listSettings": { "untitled": "Unbenannt" },
|
"listSettings": { "untitled": "Unbenannt" },
|
||||||
"detailsIsland": { "verifyFailed": "Merge ist erfolgt, aber das Verifikationskommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf 'Erledigt' gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "stopOffline": "Worker offline — Task kann nicht gestoppt werden.", "stopFailed": "Stoppen fehlgeschlagen: {0}", "enqueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "dequeueFailed": "Aus der Warteschlange entfernen fehlgeschlagen: {0}", "resetAndRetryFailed": "Zurücksetzen & erneut versuchen fehlgeschlagen: {0}" },
|
"detailsIsland": { "verifyFailed": "Merge ist erfolgt, aber das Verifikationskommando der Liste ist fehlgeschlagen — die Aufgabe wurde nicht auf 'Erledigt' gesetzt.", "untrackedCollision": "Merge abgelehnt — er würde eine unversionierte Datei im Ziel-Arbeitsverzeichnis überschreiben.", "stopOffline": "Worker offline — Task kann nicht gestoppt werden.", "stopFailed": "Stoppen fehlgeschlagen: {0}", "enqueueFailed": "In die Warteschlange stellen fehlgeschlagen: {0}", "dequeueFailed": "Aus der Warteschlange entfernen fehlgeschlagen: {0}", "resetAndRetryFailed": "Zurücksetzen & erneut versuchen fehlgeschlagen: {0}" },
|
||||||
"lists": { "localSuffix": "{0} / lokal", "smartMyDay": "Mein Tag", "smartImportant": "Wichtig", "smartPlanned": "Geplant", "virtualQueue": "Warteschlange", "virtualRunning": "Läuft", "virtualReview": "Prüfung", "newList": "Neue Liste", "findingsNotFound": "Für diese Liste gibt es noch keine Findings.", "findingsOpenFailed": "Findings-Ordner konnte nicht geöffnet werden: {0}" },
|
"lists": { "localSuffix": "{0} / lokal", "smartMyDay": "Mein Tag", "smartImportant": "Wichtig", "smartPlanned": "Geplant", "virtualQueue": "Warteschlange", "virtualRunning": "Läuft", "virtualReview": "Prüfung", "newList": "Neue Liste", "findingsNotFound": "Für diese Liste gibt es noch keine Findings.", "findingsOpenFailed": "Findings-Ordner konnte nicht geöffnet werden: {0}" },
|
||||||
|
|||||||
@@ -167,6 +167,7 @@
|
|||||||
"manualTip": "Manual reminder — Claude never picks this up",
|
"manualTip": "Manual reminder — Claude never picks this up",
|
||||||
"ctxMarkManual": "Mark as manual",
|
"ctxMarkManual": "Mark as manual",
|
||||||
"ctxMarkClaudeTask": "Mark as Claude task",
|
"ctxMarkClaudeTask": "Mark as Claude task",
|
||||||
|
"ctxDeleteTask": "Delete task",
|
||||||
"approve": "Approve",
|
"approve": "Approve",
|
||||||
"approveTip": "Approve — mark Done",
|
"approveTip": "Approve — mark Done",
|
||||||
"reject": "Reject",
|
"reject": "Reject",
|
||||||
@@ -201,12 +202,15 @@
|
|||||||
"contextOpenExplorer": "Open in Explorer",
|
"contextOpenExplorer": "Open in Explorer",
|
||||||
"contextOpenTerminal": "Open in Terminal",
|
"contextOpenTerminal": "Open in Terminal",
|
||||||
"contextOpenFindings": "Open findings folder",
|
"contextOpenFindings": "Open findings folder",
|
||||||
|
"contextSettings": "Settings…",
|
||||||
"newList": "New list",
|
"newList": "New list",
|
||||||
"addReposTip": "Add repos as lists"
|
"addReposTip": "Add repos as lists"
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"deleteTaskTip": "Delete task",
|
"deleteTaskTip": "Delete task",
|
||||||
"killSessionTip": "Kill the running session",
|
"killSessionTip": "Kill the running session",
|
||||||
|
"sendToQueue": "Send to queue",
|
||||||
|
"sendToQueueTip": "Queue this task so Claude picks it up on the next run",
|
||||||
"closeTip": "Close",
|
"closeTip": "Close",
|
||||||
"copyTaskIdTip": "Copy task ID",
|
"copyTaskIdTip": "Copy task ID",
|
||||||
"starTip": "Star",
|
"starTip": "Star",
|
||||||
@@ -285,7 +289,7 @@
|
|||||||
"running": "Running",
|
"running": "Running",
|
||||||
"focusMode": "Focus",
|
"focusMode": "Focus",
|
||||||
"overviewMode": "Overview",
|
"overviewMode": "Overview",
|
||||||
"closeSession": "Close session",
|
"closeSession": "Kill session",
|
||||||
"conptyLaunchFailed": "Couldn't open interactive session: {0}",
|
"conptyLaunchFailed": "Couldn't open interactive session: {0}",
|
||||||
"conptyStarting": "Starting session…",
|
"conptyStarting": "Starting session…",
|
||||||
"mergeHelperTitle": "Merge Helper",
|
"mergeHelperTitle": "Merge Helper",
|
||||||
@@ -678,7 +682,7 @@
|
|||||||
"planningBadge": { "active": "PLANNING", "finalized": "PLANNED" },
|
"planningBadge": { "active": "PLANNING", "finalized": "PLANNED" },
|
||||||
"taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" },
|
"taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" },
|
||||||
"queue": { "baseDirtyWarning": "Heads up: this list's repo has uncommitted changes ({0} modified, {1} untracked) — a new worktree starts from the last commit and won't include them." },
|
"queue": { "baseDirtyWarning": "Heads up: this list's repo has uncommitted changes ({0} modified, {1} untracked) — a new worktree starts from the last commit and won't include them." },
|
||||||
"tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}", "planningOpenFailed": "Couldn't open planning session: {0}", "planningResumeFailed": "Couldn't resume planning session: {0}", "approveFailed": "Approve & merge failed: {0}", "cancelReviewFailed": "Cancel review failed: {0}", "sendToQueueFailed": "Send to queue failed: {0}", "queuePlanBlockedInteractive": "Can't queue the plan — {0} has an open interactive session and must be closed first.", "moveRunningRejected": "Can't move a running task to another list.", "moveWorktreeRejected": "Can't move — this task has an active worktree pointing at its current repo.", "moveRepoConfirm": "Different repos — {0} → {1}. Move the task anyway?", "moveConfirmUnavailable": "Can't move — the confirmation dialog isn't available.", "quickClaudeNoWorkingDir": "This list has no working directory configured.", "quickClaudeDirMissing": "Working directory no longer exists: {0}", "setStatusFailed": "Failed to update status: {0}", "cancelFailed": "Cancel failed: {0}", "rejectToQueueFailed": "Reject to queue failed: {0}", "rejectToIdleFailed": "Reject failed: {0}" },
|
"tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}", "planningOpenFailed": "Couldn't open planning session: {0}", "planningResumeFailed": "Couldn't resume planning session: {0}", "approveFailed": "Approve & merge failed: {0}", "cancelReviewFailed": "Cancel review failed: {0}", "sendToQueueFailed": "Send to queue failed: {0}", "queuePlanBlockedInteractive": "Can't queue the plan — {0} has an open interactive session and must be closed first.", "moveRunningRejected": "Can't move a running task to another list.", "moveWorktreeRejected": "Can't move — this task has an active worktree pointing at its current repo.", "moveRepoConfirm": "Different repos — {0} → {1}. Move the task anyway?", "moveConfirmUnavailable": "Can't move — the confirmation dialog isn't available.", "quickClaudeNoWorkingDir": "This list has no working directory configured.", "quickClaudeDirMissing": "Working directory no longer exists: {0}", "setStatusFailed": "Failed to update status: {0}", "cancelFailed": "Cancel failed: {0}", "rejectToQueueFailed": "Reject to queue failed: {0}", "rejectToIdleFailed": "Reject failed: {0}", "deleteTaskConfirm": "Delete \"{0}\"? This cannot be undone.", "deleteTaskFailed": "Delete failed: {0}" },
|
||||||
"diff": { "loadFailed": "Failed to load diff: {0}", "noChanges": "No changes to show.", "unavailable": "Diff no longer available — commit range incomplete." },
|
"diff": { "loadFailed": "Failed to load diff: {0}", "noChanges": "No changes to show.", "unavailable": "Diff no longer available — commit range incomplete." },
|
||||||
"planningDiff": { "hubError": "Could not build combined preview (hub error).", "conflict": "Cannot build combined preview: subtask {0} conflicts with an earlier subtask ({1} files).", "buildFailed": "Could not build combined preview: {0}" },
|
"planningDiff": { "hubError": "Could not build combined preview (hub error).", "conflict": "Cannot build combined preview: subtask {0} conflicts with an earlier subtask ({1} files).", "buildFailed": "Could not build combined preview: {0}" },
|
||||||
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Merging…", "progressVerifying": "Running the list's verify command… ({0})", "workerOfflineBranches": "Worker offline — cannot list branches.", "loadBranchesFailed": "Failed to load branches: {0}", "merged": "Merged.", "conflict": "Merge conflict — target branch restored. Resolve manually or via Continue, then retry.", "blocked": "Blocked: {0}", "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "unknownStatus": "Unknown status: {0}", "mergeFailed": "Merge failed: {0}" },
|
"merge": { "commitMessage": "chore: merge {0}", "progressMerging": "Merging…", "progressVerifying": "Running the list's verify command… ({0})", "workerOfflineBranches": "Worker offline — cannot list branches.", "loadBranchesFailed": "Failed to load branches: {0}", "merged": "Merged.", "conflict": "Merge conflict — target branch restored. Resolve manually or via Continue, then retry.", "blocked": "Blocked: {0}", "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "unknownStatus": "Unknown status: {0}", "mergeFailed": "Merge failed: {0}" },
|
||||||
@@ -694,7 +698,7 @@
|
|||||||
"filesTab": { "workerOffline": "Worker offline.", "noneBundled": "No default agents bundled.", "allPresent": "All default agents already present.", "restored": "Restored {0} default agent(s).", "restoreFailed": "Restore failed: {0}", "resetToDefault": "Reset to the bundled default." },
|
"filesTab": { "workerOffline": "Worker offline.", "noneBundled": "No default agents bundled.", "allPresent": "All default agents already present.", "restored": "Restored {0} default agent(s).", "restoreFailed": "Restore failed: {0}", "resetToDefault": "Reset to the bundled default." },
|
||||||
"sessionSkillsTab": { "installed": "Installed: {0}", "installFailed": "Install failed: {0}", "updated": "Updated.", "updateFailed": "Update failed: {0}", "removed": "Removed.", "removeFailed": "Remove failed: {0}" },
|
"sessionSkillsTab": { "installed": "Installed: {0}", "installFailed": "Install failed: {0}", "updated": "Updated.", "updateFailed": "Update failed: {0}", "removed": "Removed.", "removeFailed": "Remove failed: {0}" },
|
||||||
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "Removed {0} worktree(s).", "blocked": "Cannot force-remove: {0} task(s) still running. Cancel them first.", "removedFrom": "Removed {0} worktree(s) from {1} task(s).", "cleanupFailed": "Cleanup failed: {0}", "resetFailed": "Reset failed: {0}" },
|
"worktreesTab": { "workerOffline": "Worker offline.", "removed": "Removed {0} worktree(s).", "blocked": "Cannot force-remove: {0} task(s) still running. Cancel them first.", "removedFrom": "Removed {0} worktree(s) from {1} task(s).", "cleanupFailed": "Cleanup failed: {0}", "resetFailed": "Reset failed: {0}" },
|
||||||
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "list", "cleanupFailed": "Cleanup failed.", "cleanupFailedDetailed": "Cleanup failed: {0}", "removed": "Removed {0} worktree(s).", "discardFailed": "Failed to discard worktree.", "keepFailed": "Failed to keep worktree.", "cannotForceRunning": "Cannot force-remove a running task.", "forceRemoveFailed": "Force remove failed.", "forceRemoveFailedDetailed": "Force remove failed: {0}", "batchProgress": "Merging {0}/{1}…", "batchDone": "Merged {0}, {1} need resolution." },
|
"worktreesOverview": { "titleAll": "Worktrees", "titleList": "Worktrees — {0}", "listFallback": "list", "cleanupFailed": "Cleanup failed.", "cleanupFailedDetailed": "Cleanup failed: {0}", "removed": "Removed {0} worktree(s).", "discardConfirm": "Discard the worktree for \"{0}\"? Uncommitted work will be lost.", "discardFailed": "Failed to discard worktree.", "keepFailed": "Failed to keep worktree.", "cannotForceRunning": "Cannot force-remove a running task.", "forceRemoveFailed": "Force remove failed.", "forceRemoveFailedDetailed": "Force remove failed: {0}", "batchProgress": "Merging {0}/{1}…", "batchDone": "Merged {0}, {1} need resolution." },
|
||||||
"listSettings": { "untitled": "Untitled" },
|
"listSettings": { "untitled": "Untitled" },
|
||||||
"detailsIsland": { "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "stopOffline": "Worker offline — can't stop the task.", "stopFailed": "Stop failed: {0}", "enqueueFailed": "Queue failed: {0}", "dequeueFailed": "Remove from queue failed: {0}", "resetAndRetryFailed": "Reset & retry failed: {0}" },
|
"detailsIsland": { "verifyFailed": "Merge landed, but the list's verify command failed — the task was kept out of Done.", "untrackedCollision": "Merge refused — it would overwrite an untracked file in the target working directory.", "stopOffline": "Worker offline — can't stop the task.", "stopFailed": "Stop failed: {0}", "enqueueFailed": "Queue failed: {0}", "dequeueFailed": "Remove from queue failed: {0}", "resetAndRetryFailed": "Reset & retry failed: {0}" },
|
||||||
"lists": { "localSuffix": "{0} / local", "smartMyDay": "My Day", "smartImportant": "Important", "smartPlanned": "Planned", "virtualQueue": "Queue", "virtualRunning": "Running", "virtualReview": "Review", "newList": "New list", "findingsNotFound": "No findings yet for this list.", "findingsOpenFailed": "Couldn't open findings folder: {0}" },
|
"lists": { "localSuffix": "{0} / local", "smartMyDay": "My Day", "smartImportant": "Important", "smartPlanned": "Planned", "virtualQueue": "Queue", "virtualRunning": "Running", "virtualReview": "Review", "newList": "New list", "findingsNotFound": "No findings yet for this list.", "findingsOpenFailed": "Couldn't open findings folder: {0}" },
|
||||||
|
|||||||
@@ -1146,6 +1146,42 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
|||||||
"vm.queue.baseDirtyWarning", warning.ModifiedCount, warning.UntrackedCount));
|
"vm.queue.baseDirtyWarning", warning.ModifiedCount, warning.UntrackedCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Row-level delete for the task context menu. Routed through the worker (mirrors
|
||||||
|
// DetailsIslandViewModel.DeleteTaskAsync) so a deleted child correctly advances a
|
||||||
|
// WaitingForChildren parent instead of bypassing TaskStateService.
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task DeleteTaskAsync(TaskRowViewModel? row)
|
||||||
|
{
|
||||||
|
if (row is null || _worker is null) return;
|
||||||
|
if (ConfirmAsync is not null)
|
||||||
|
{
|
||||||
|
var ok = await ConfirmAsync(Loc.T("vm.tasksIsland.deleteTaskConfirm", row.Title));
|
||||||
|
if (!ok) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleted;
|
||||||
|
string? error;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(deleted, error) = await _worker.DeleteTaskAsync(row.Id);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorReported?.Invoke(Loc.T("vm.tasksIsland.deleteTaskFailed", ex.Message));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!deleted)
|
||||||
|
{
|
||||||
|
ErrorReported?.Invoke(error ?? Loc.T("vm.tasksIsland.deleteTaskFailed", "unknown error"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Items.Remove(row);
|
||||||
|
Regroup();
|
||||||
|
UpdateSubtitle();
|
||||||
|
TasksChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task SendToQueueAsync(TaskRowViewModel? row)
|
private async Task SendToQueueAsync(TaskRowViewModel? row)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,12 +15,15 @@ public sealed partial class WorktreesSettingsTabViewModel : ViewModelBase
|
|||||||
[ObservableProperty] private bool _worktreeAutoCleanupEnabled;
|
[ObservableProperty] private bool _worktreeAutoCleanupEnabled;
|
||||||
[ObservableProperty] private int _worktreeAutoCleanupDays = 7;
|
[ObservableProperty] private int _worktreeAutoCleanupDays = 7;
|
||||||
|
|
||||||
[ObservableProperty] private bool _showResetConfirm;
|
|
||||||
[ObservableProperty] private string _statusMessage = "";
|
[ObservableProperty] private string _statusMessage = "";
|
||||||
[ObservableProperty] private bool _isBusy;
|
[ObservableProperty] private bool _isBusy;
|
||||||
|
|
||||||
public OperationStatus ResetStatus { get; } = new();
|
public OperationStatus ResetStatus { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>Wired by <c>WindowDialogService.ShowSettingsAsync</c>. A missing hook (null)
|
||||||
|
/// must not reset silently — <see cref="ConfirmResetAll"/> aborts instead.</summary>
|
||||||
|
public Func<string, Task<bool>>? ConfirmAsync { get; set; }
|
||||||
|
|
||||||
public IReadOnlyList<string> WorktreeStrategies { get; } = new[] { "sibling", "central" };
|
public IReadOnlyList<string> WorktreeStrategies { get; } = new[] { "sibling", "central" };
|
||||||
|
|
||||||
public WorktreesSettingsTabViewModel(IWorkerClient worker) => _worker = worker;
|
public WorktreesSettingsTabViewModel(IWorkerClient worker) => _worker = worker;
|
||||||
@@ -52,13 +55,13 @@ public sealed partial class WorktreesSettingsTabViewModel : ViewModelBase
|
|||||||
finally { IsBusy = false; }
|
finally { IsBusy = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand] private void RequestResetConfirm() => ShowResetConfirm = true;
|
|
||||||
[RelayCommand] private void CancelResetConfirm() => ShowResetConfirm = false;
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task ConfirmResetAll()
|
private async Task ConfirmResetAll()
|
||||||
{
|
{
|
||||||
ShowResetConfirm = false; StatusMessage = "";
|
if (ConfirmAsync is null) return;
|
||||||
|
if (!await ConfirmAsync(Loc.T("settings.worktrees.confirmRemoveAll"))) return;
|
||||||
|
|
||||||
|
StatusMessage = "";
|
||||||
using var op = ResetStatus.Begin(Loc.T("ops.worktrees.resetting"));
|
using var op = ResetStatus.Begin(Loc.T("ops.worktrees.resetting"));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -290,6 +290,8 @@ public sealed partial class WorktreesOverviewModalViewModel : ViewModelBase
|
|||||||
private async Task Discard(WorktreeOverviewRowViewModel? row)
|
private async Task Discard(WorktreeOverviewRowViewModel? row)
|
||||||
{
|
{
|
||||||
if (row is null || row.State != WorktreeState.Active) return;
|
if (row is null || row.State != WorktreeState.Active) return;
|
||||||
|
if (ConfirmAction is not null && !await ConfirmAction(Loc.T("vm.worktreesOverview.discardConfirm", row.TaskTitle))) return;
|
||||||
|
|
||||||
var (ok, err) = await _worker.SetWorktreeStateAsync(row.TaskId, WorktreeState.Discarded);
|
var (ok, err) = await _worker.SetWorktreeStateAsync(row.TaskId, WorktreeState.Discarded);
|
||||||
if (ok) row.State = WorktreeState.Discarded;
|
if (ok) row.State = WorktreeState.Discarded;
|
||||||
else StatusMessage = err ?? Loc.T("vm.worktreesOverview.discardFailed");
|
else StatusMessage = err ?? Loc.T("vm.worktreesOverview.discardFailed");
|
||||||
|
|||||||
@@ -414,6 +414,17 @@
|
|||||||
Command="{Binding ResetReviewCommand}" />
|
Command="{Binding ResetReviewCommand}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Send an Idle task to the queue for Claude to pick up on the next run. -->
|
||||||
|
<StackPanel Spacing="8">
|
||||||
|
<Border Height="1" Background="{DynamicResource LineBrush}" />
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="{StaticResource SpaceSm}">
|
||||||
|
<Button Classes="btn" Content="{loc:Tr details.sendToQueue}"
|
||||||
|
ToolTip.Tip="{loc:Tr details.sendToQueueTip}"
|
||||||
|
ToolTip.ShowOnDisabled="True"
|
||||||
|
Command="{Binding EnqueueCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Submit an interactive (ConPTY) session for review: an Idle/Failed task that
|
<!-- Submit an interactive (ConPTY) session for review: an Idle/Failed task that
|
||||||
still has a worktree. Commits the worktree, then moves it to WaitingForReview. -->
|
still has a worktree. Commits the worktree, then moves it to WaitingForReview. -->
|
||||||
<StackPanel Spacing="8" IsVisible="{Binding CanSubmitForReview}">
|
<StackPanel Spacing="8" IsVisible="{Binding CanSubmitForReview}">
|
||||||
|
|||||||
@@ -139,6 +139,10 @@
|
|||||||
IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
IsVisible="{Binding WorkingDir, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
|
||||||
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenInTerminalCommand}"
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenInTerminalCommand}"
|
||||||
CommandParameter="{Binding}"/>
|
CommandParameter="{Binding}"/>
|
||||||
|
<Separator/>
|
||||||
|
<MenuItem Header="{loc:Tr lists.contextSettings}"
|
||||||
|
Command="{Binding $parent[UserControl].((vm:ListsIslandViewModel)DataContext).OpenListSettingsCommand}"
|
||||||
|
CommandParameter="{Binding}"/>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</Border.ContextMenu>
|
</Border.ContextMenu>
|
||||||
<Grid ColumnDefinitions="20,*,Auto">
|
<Grid ColumnDefinitions="20,*,Auto">
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ public partial class TaskRowView : UserControl
|
|||||||
menu.Items.Add(MakeItem("tasks.ctxClearSchedule", OnClearScheduleClick, row.HasSchedule));
|
menu.Items.Add(MakeItem("tasks.ctxClearSchedule", OnClearScheduleClick, row.HasSchedule));
|
||||||
menu.Items.Add(MakeItem("tasks.ctxAddToMyDay", OnAddToMyDayClick, row.CanAddToMyDay));
|
menu.Items.Add(MakeItem("tasks.ctxAddToMyDay", OnAddToMyDayClick, row.CanAddToMyDay));
|
||||||
menu.Items.Add(MakeItem("tasks.ctxRemoveFromMyDay", OnRemoveFromMyDayClick, row.IsMyDay));
|
menu.Items.Add(MakeItem("tasks.ctxRemoveFromMyDay", OnRemoveFromMyDayClick, row.IsMyDay));
|
||||||
|
menu.Items.Add(new Separator());
|
||||||
|
menu.Items.Add(MakeItem("tasks.ctxDeleteTask", OnDeleteTaskClick));
|
||||||
|
|
||||||
menu.Open(border);
|
menu.Open(border);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@@ -114,6 +116,12 @@ public partial class TaskRowView : UserControl
|
|||||||
await vm.RemoveFromMyDayCommand.ExecuteAsync(row);
|
await vm.RemoveFromMyDayCommand.ExecuteAsync(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void OnDeleteTaskClick(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
|
||||||
|
await vm.DeleteTaskCommand.ExecuteAsync(row);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnOpenPlanningSessionClick(object? sender, RoutedEventArgs e)
|
private void OnOpenPlanningSessionClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
|
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)
|
||||||
|
|||||||
@@ -244,27 +244,14 @@
|
|||||||
<Button Classes="btn" Content="{loc:Tr settings.worktrees.cleanupFinished}"
|
<Button Classes="btn" Content="{loc:Tr settings.worktrees.cleanupFinished}"
|
||||||
Command="{Binding Worktrees.CleanupWorktreesCommand}"
|
Command="{Binding Worktrees.CleanupWorktreesCommand}"
|
||||||
HorizontalAlignment="Left"/>
|
HorizontalAlignment="Left"/>
|
||||||
<StackPanel>
|
|
||||||
<Button Content="{loc:Tr settings.worktrees.forceRemoveAll}" Classes="danger"
|
|
||||||
Command="{Binding Worktrees.RequestResetConfirmCommand}"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
IsVisible="{Binding !Worktrees.ShowResetConfirm}"/>
|
|
||||||
<Border Classes="danger-box"
|
|
||||||
IsVisible="{Binding Worktrees.ShowResetConfirm}">
|
|
||||||
<StackPanel Spacing="8">
|
|
||||||
<TextBlock Text="{loc:Tr settings.worktrees.confirmRemoveAll}"
|
|
||||||
Foreground="{DynamicResource TextBrush}" TextWrapping="Wrap"/>
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<Button Classes="btn" Content="{loc:Tr settings.cancel}" Command="{Binding Worktrees.CancelResetConfirmCommand}"/>
|
<Button Content="{loc:Tr settings.worktrees.forceRemoveAll}" Classes="danger"
|
||||||
<Button Content="{loc:Tr settings.worktrees.removeAll}" Classes="danger"
|
|
||||||
Command="{Binding Worktrees.ConfirmResetAllCommand}"
|
Command="{Binding Worktrees.ConfirmResetAllCommand}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
IsEnabled="{Binding !Worktrees.ResetStatus.IsRunning}"/>
|
IsEnabled="{Binding !Worktrees.ResetStatus.IsRunning}"/>
|
||||||
<ctl:OperationIndicator Status="{Binding Worktrees.ResetStatus}"/>
|
<ctl:OperationIndicator Status="{Binding Worktrees.ResetStatus}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
<TextBlock Classes="meta" Text="{Binding Worktrees.StatusMessage}"
|
<TextBlock Classes="meta" Text="{Binding Worktrees.StatusMessage}"
|
||||||
IsVisible="{Binding Worktrees.StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{Binding Worktrees.StatusMessage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ public sealed class WindowDialogService : IDialogService
|
|||||||
|
|
||||||
public async Task ShowSettingsAsync(SettingsModalViewModel vm)
|
public async Task ShowSettingsAsync(SettingsModalViewModel vm)
|
||||||
{
|
{
|
||||||
|
vm.Worktrees.ConfirmAsync = ConfirmAsync;
|
||||||
var dlg = new SettingsModalView { DataContext = vm };
|
var dlg = new SettingsModalView { DataContext = vm };
|
||||||
await dlg.ShowDialog(ActiveOwner());
|
await dlg.ShowDialog(ActiveOwner());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,4 +76,25 @@ public class WorktreesOverviewModalErrorTests
|
|||||||
|
|
||||||
Assert.False(vm.RefreshStatus.IsRunning);
|
Assert.False(vm.RefreshStatus.IsRunning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Discard_WhenConfirmDeclined_LeavesStateUnchanged()
|
||||||
|
{
|
||||||
|
var worker = new ThrowingWorker();
|
||||||
|
var vm = NewVm(worker);
|
||||||
|
vm.ConfirmAction = _ => Task.FromResult(false);
|
||||||
|
var row = new WorktreeOverviewRowViewModel
|
||||||
|
{
|
||||||
|
TaskId = "task-1",
|
||||||
|
TaskTitle = "Task 1",
|
||||||
|
TaskStatus = ClaudeDo.Data.Models.TaskStatus.Idle,
|
||||||
|
State = ClaudeDo.Data.Models.WorktreeState.Active,
|
||||||
|
};
|
||||||
|
vm.AddRowForTest(row);
|
||||||
|
|
||||||
|
await vm.DiscardCommand.ExecuteAsync(row);
|
||||||
|
|
||||||
|
Assert.Equal(ClaudeDo.Data.Models.WorktreeState.Active, row.State);
|
||||||
|
Assert.Null(vm.StatusMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,16 @@ public class WorktreesSettingsTabViewModelTests
|
|||||||
throw new Exception(ExceptionMessage);
|
throw new Exception(ExceptionMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class CountingWorker : StubWorkerClient
|
||||||
|
{
|
||||||
|
public int ResetCallCount;
|
||||||
|
public override Task<WorktreeResetDto?> ResetAllWorktreesAsync()
|
||||||
|
{
|
||||||
|
ResetCallCount++;
|
||||||
|
return Task.FromResult<WorktreeResetDto?>(new WorktreeResetDto(0, 0, false, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CleanupWorktrees_WhenWorkerThrows_ShowsExceptionMessage()
|
public async Task CleanupWorktrees_WhenWorkerThrows_ShowsExceptionMessage()
|
||||||
{
|
{
|
||||||
@@ -42,12 +52,33 @@ public class WorktreesSettingsTabViewModelTests
|
|||||||
public async Task ConfirmResetAll_WhenWorkerThrows_ShowsExceptionMessage()
|
public async Task ConfirmResetAll_WhenWorkerThrows_ShowsExceptionMessage()
|
||||||
{
|
{
|
||||||
var worker = new ThrowingWorker();
|
var worker = new ThrowingWorker();
|
||||||
var vm = new WorktreesSettingsTabViewModel(worker);
|
var vm = new WorktreesSettingsTabViewModel(worker) { ConfirmAsync = _ => Task.FromResult(true) };
|
||||||
|
|
||||||
await vm.ConfirmResetAllCommand.ExecuteAsync(null);
|
await vm.ConfirmResetAllCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
Assert.Contains(worker.ExceptionMessage, vm.StatusMessage);
|
Assert.Contains(worker.ExceptionMessage, vm.StatusMessage);
|
||||||
Assert.False(vm.ResetStatus.IsRunning);
|
Assert.False(vm.ResetStatus.IsRunning);
|
||||||
Assert.False(vm.ShowResetConfirm);
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConfirmResetAll_WhenConfirmDeclined_DoesNotCallWorker()
|
||||||
|
{
|
||||||
|
var worker = new CountingWorker();
|
||||||
|
var vm = new WorktreesSettingsTabViewModel(worker) { ConfirmAsync = _ => Task.FromResult(false) };
|
||||||
|
|
||||||
|
await vm.ConfirmResetAllCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
Assert.Equal(0, worker.ResetCallCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ConfirmResetAll_WhenConfirmHookMissing_DoesNotCallWorker()
|
||||||
|
{
|
||||||
|
var worker = new CountingWorker();
|
||||||
|
var vm = new WorktreesSettingsTabViewModel(worker);
|
||||||
|
|
||||||
|
await vm.ConfirmResetAllCommand.ExecuteAsync(null);
|
||||||
|
|
||||||
|
Assert.Equal(0, worker.ResetCallCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user