feat(ui): Online Inbox settings tab + auth-code/PKCE login

New Settings tab: enable toggle, config fields, sign-in/out + status.
OnlineLoginService runs the PKCE loopback flow (Duende.IdentityModel.OidcClient
7.1.0), opens the system browser, captures the callback, hands the refresh
token to the Worker. en/de localized. Fixes: loopback callback URL built from
host:port base (avoids doubled redirect path); PollIntervalSeconds threaded
through the state DTO so it loads instead of resetting to 60.

Visual layout + the live sign-in round-trip need manual verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-10 11:02:14 +02:00
parent 17c7ff517a
commit 80a2de6c74
11 changed files with 415 additions and 3 deletions

View File

@@ -17,6 +17,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
public WorktreesSettingsTabViewModel Worktrees { get; }
public FilesSettingsTabViewModel Files { get; }
public PrimeClaudeTabViewModel Prime { get; }
public OnlineInboxSettingsViewModel OnlineInbox { get; }
[ObservableProperty] private string _validationError = "";
[ObservableProperty] private bool _isBusy;
@@ -25,6 +26,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
public Action? CloseAction { get; set; }
public SettingsModalViewModel(IWorkerClient worker, PrimeClaudeTabViewModel prime,
IOnlineLoginService onlineLoginService,
ILocalizer localizer, AppSettings appSettings)
{
_worker = worker;
@@ -36,6 +38,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
Worktrees = new WorktreesSettingsTabViewModel(worker);
Files = new FilesSettingsTabViewModel(worker);
Prime = prime;
OnlineInbox = new OnlineInboxSettingsViewModel(worker, onlineLoginService);
}
public async Task LoadAsync()
@@ -65,6 +68,7 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
else StatusMessage = Loc.T("vm.settingsModal.workerOffline");
await Prime.LoadAsync();
await OnlineInbox.LoadAsync();
}
finally { IsBusy = false; }
}