diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index d5737d5..027e8ab 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -422,7 +422,7 @@ "planningBadge": { "active": "PLANUNG", "finalized": "GEPLANT" }, "taskRow": { "createdPrefix": "Erstellt {0}", "stepsText": "{0}/{1} Schritte" }, "tasksIsland": { "completedHeader": "ABGESCHLOSSEN", "completedHeaderCount": "ABGESCHLOSSEN · {0}" }, - "diff": { "loadFailed": "Diff konnte nicht geladen werden: {0}", "noChanges": "Keine Änderungen anzuzeigen." }, + "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)." }, "merge": { "commitMessage": "Merge-Aufgabe: {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}", "unknownStatus": "Unbekannter Status: {0}", "mergeFailed": "Merge fehlgeschlagen: {0}" }, "conflictResolution": { "vsCodeError": "VS Code konnte nicht gestartet werden: {0}. Die Pfade sind oben aufgeführt — kopiere sie manuell.", "subtaskPrefix": "Konflikte in Teilaufgabe: {0}", "targetPrefix": "Zusammenführen in: {0}" }, diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index 4457637..3aeacbf 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -422,7 +422,7 @@ "planningBadge": { "active": "PLANNING", "finalized": "PLANNED" }, "taskRow": { "createdPrefix": "Created {0}", "stepsText": "{0}/{1} steps" }, "tasksIsland": { "completedHeader": "COMPLETED", "completedHeaderCount": "COMPLETED · {0}" }, - "diff": { "loadFailed": "Failed to load diff: {0}", "noChanges": "No changes to show." }, + "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)." }, "merge": { "commitMessage": "Merge task: {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}", "unknownStatus": "Unknown status: {0}", "mergeFailed": "Merge failed: {0}" }, "conflictResolution": { "vsCodeError": "Could not launch VS Code: {0}. Paths are listed above — copy them manually.", "subtaskPrefix": "Conflicts in subtask: {0}", "targetPrefix": "Merging into: {0}" }, diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/DiffModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/DiffModalViewModel.cs index c92caaf..82050a3 100644 --- a/src/ClaudeDo.Ui/ViewModels/Modals/DiffModalViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Modals/DiffModalViewModel.cs @@ -110,6 +110,12 @@ public sealed partial class DiffModalViewModel : ViewModelBase Files.Clear(); StatusMessage = null; + if (FromCommitRange && (BaseRef is null || HeadCommit is null)) + { + StatusMessage = Loc.T("vm.diff.unavailable"); + return; + } + string raw; try { diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/DiffModalViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/DiffModalViewModelTests.cs new file mode 100644 index 0000000..3fb1d5b --- /dev/null +++ b/tests/ClaudeDo.Ui.Tests/ViewModels/DiffModalViewModelTests.cs @@ -0,0 +1,54 @@ +using System.IO; +using ClaudeDo.Localization; +using ClaudeDo.Ui.Localization; +using ClaudeDo.Ui.ViewModels.Modals; + +namespace ClaudeDo.Ui.Tests.ViewModels; + +public class DiffModalViewModelTests +{ + public DiffModalViewModelTests() + { + var dir = AppContext.BaseDirectory; + while (dir is not null && !Directory.Exists(Path.Combine(dir, "src", "ClaudeDo.Localization", "locales"))) + dir = Path.GetDirectoryName(dir); + Loc.Current = new Localizer( + LocaleStore.Load(Path.Combine(dir!, "src", "ClaudeDo.Localization", "locales")), "en"); + } + + [Fact] + public async Task LoadAsync_CommitRange_NullHeadCommit_ShowsUnavailableState() + { + var vm = new DiffModalViewModel(null!) + { + WorktreePath = "/some/repo", + BaseRef = "abc123", + HeadCommit = null, + FromCommitRange = true, + }; + + await vm.LoadAsync(); + + Assert.Empty(vm.Files); + Assert.NotNull(vm.StatusMessage); + Assert.Contains("no longer available", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); + } + + [Fact] + public async Task LoadAsync_CommitRange_NullBaseRef_ShowsUnavailableState() + { + var vm = new DiffModalViewModel(null!) + { + WorktreePath = "/some/repo", + BaseRef = null, + HeadCommit = "def456", + FromCommitRange = true, + }; + + await vm.LoadAsync(); + + Assert.Empty(vm.Files); + Assert.NotNull(vm.StatusMessage); + Assert.Contains("no longer available", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); + } +}