chore(claude-do): merge fix(ui): Listen-Settings-Modal verschluckt Ladefehler nach d

ClaudeDo-Task: 9256fcfe-6cad-4350-a979-0c49cd5b9799
This commit is contained in:
mika kuns
2026-09-02 22:38:49 +02:00
5 changed files with 71 additions and 5 deletions
@@ -58,6 +58,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
if (row is null || Dialogs is null || _services is null) return;
var rawId = row.Id.StartsWith("user:", StringComparison.Ordinal) ? row.Id["user:".Length..] : row.Id;
var vm = _services.GetRequiredService<ListSettingsModalViewModel>();
vm.ErrorReported += msg => ErrorReported?.Invoke(msg);
vm.BeginLoad(rawId, row.Name, row.WorkingDir, row.DefaultCommitType, row.IsManual, row.FindingsTracked, row.IconKey);
await Dialogs.ShowListSettingsAsync(vm);
if (vm.Deleted) await LoadAsync();
@@ -408,6 +409,7 @@ public sealed partial class ListsIslandViewModel : ViewModelBase, IDisposable
if (Dialogs is not null && _services is not null)
{
var vm = _services.GetRequiredService<ListSettingsModalViewModel>();
vm.ErrorReported += msg => ErrorReported?.Invoke(msg);
vm.BeginLoad(entity.Id, entity.Name, entity.WorkingDir, entity.DefaultCommitType, entity.IsManual, entity.FindingsTracked, entity.Icon);
await Dialogs.ShowListSettingsAsync(vm);
if (!vm.Saved && !vm.Deleted)
@@ -34,6 +34,9 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
public Func<string, Task<bool>>? ConfirmAsync { get; set; }
public Func<string, Task>? ShowErrorAsync { get; set; }
// Mirrors UsageMonitorModalViewModel.ErrorReported — the caller wires this into FlashFooterError.
public event Action<string>? ErrorReported;
[ObservableProperty] private string _name = "";
[ObservableProperty] private string _workingDir = "";
[ObservableProperty] private string _defaultCommitType = CommitTypeRegistry.DefaultType;
@@ -65,6 +68,11 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
[NotifyCanExecuteChangedFor(nameof(DeleteCommand))]
private bool _isLoading;
// Set when the core load (name/agent/verify command) threw — keeps Save gated even after
// IsLoading resets to false in LoadAsync's finally, so a failed load can't clobber the
// stored config with blank fields.
private bool _loadFailed;
// True once GetTicketProjectsAsync has completed successfully for the current load — Save
// only writes an explicit TicketProjectId when this is true, so a slow/unreachable ticket
// system leaves the stored link untouched instead of wiping it to "none".
@@ -128,8 +136,9 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
string? icon = null,
CancellationToken ct = default)
{
ListConfigDto? cfg;
ListConfigDto? cfg = null;
IsLoading = true;
_loadFailed = false;
try
{
ListId = listId;
@@ -147,8 +156,18 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
VerifyCommand = cfg?.VerifyCommand ?? "";
PowerUseAllowed = cfg?.PowerUseAllowed ?? false;
}
catch (Exception ex)
{
// BeginLoad fires this without awaiting it — catching here (instead of letting it
// propagate) turns what would be an unobserved task exception, with the modal stuck
// open and every field blank, into a reported error and a locked Save button.
_loadFailed = true;
ErrorReported?.Invoke(Loc.T("vm.listSettings.loadFailed", ex.Message));
}
finally { IsLoading = false; }
if (_loadFailed) return;
// Deliberately NOT awaited: an unreachable ticket system (10s HTTP timeout) must not hold
// up the rest of the modal. The dropdown just stays empty until this finishes.
_ = LoadTicketProjectsAsync(cfg?.TicketProjectId);
@@ -179,15 +198,16 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
: TicketProjects[0];
_ticketProjectsLoaded = true;
}
catch (Exception)
catch (Exception ex)
{
// Ticket system unreachable/slow — leave the dropdown as-is. Save's tri-state (below)
// skips writing TicketProjectId when _ticketProjectsLoaded is false, so the stored
// link survives instead of being wiped to "none".
ErrorReported?.Invoke(Loc.T("vm.listSettings.ticketProjectsLoadFailed", ex.Message));
}
}
private bool CanSave() => !IsLoading;
private bool CanSave() => !IsLoading && !_loadFailed;
private bool CanDelete() => !IsLoading;
[RelayCommand(CanExecute = nameof(CanSave))]