feat(wizard): add-account wizard with MSAL OneDrive auth (#23)#29
Merged
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add MatchAsync overloads on Task<Result<T,E>> (Action and Func variants) - Implement AuthService: interactive MSAL sign-in, silent token refresh, sign-out, and token cache registration via MsalCacheHelper - Implement GraphService: GetRootFoldersAsync with drive context caching and paginated Graph API folder enumeration - Implement AccountOnboardingService (in-memory): marks account active, returns Ok result - Implement AddAccountWizardViewModel: all three wizard steps (ProviderSelection → SignIn → SelectFolders), SelectProvider command shows not-implemented banner for Google Drive and Dropbox, SignIn triggers MSAL browser flow, AddAccount fires Completed event - Wire WorkspaceViewModel: CurrentOverlay prop, OpenAddAccountWizard command resolves wizard from DI, Completed/Cancelled event handlers Closes #23 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e, GraphClientFactory (#23) - GivenAnAuthService: 10 tests covering all exception paths in SignInInteractiveAsync (authentication_canceled, user_canceled, OperationCancelled, MsalException, Exception), AcquireTokenSilentAsync (no cached account, MsalUiRequiredException), SignOutAsync (no match / match), and GetCachedAccountIdsAsync - GivenAGraphService: 2 tests exercising the catch branch in GetRootFoldersAsync via a throwing IGraphClientFactory - GivenAGraphClientFactory: 2 tests verifying CreateClient returns non-null and distinct instances per token 171 tests total, 0 failures, 0 warnings. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cancel, Back, and Add account were sized to their label text, making them visually uneven. Fixed width of 96px accommodates the widest label with comfortable padding. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HorizontalContentAlignment="Stretch" only stretches content inside the button, not the button itself. Adding HorizontalAlignment="Stretch" on each tile button ensures all three fill the same available width. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… secrets (#23) - Add Microsoft.Extensions.Configuration.Json and .UserSecrets packages - Add UserSecretsId to .csproj (astar-dev-cloudsync-functional) - Create appsettings.json with placeholder ClientId (copied to output) - App.axaml.cs now builds IConfiguration from appsettings.json then user secrets before building the DI container; ClientId read via configuration["MicrosoftIdentity:ClientId"] with a ?? throw guard - Write .claude/rules/security.md: no-hardcoded-secrets rule, config pattern, dotnet user-secrets usage, and PR review checklist - Reference security.md in CLAUDE.md rules table Set the real value with: dotnet user-secrets set "MicrosoftIdentity:ClientId" "<id>" \ --project src/AStar.Dev.CloudSyncFunctional Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…imeout Folder list was duplicated after sign-in because MatchAsync uses ConfigureAwait(false), causing the success lambda to run on a thread pool thread. When Folders.Clear() fired CollectionChanged(Reset) cross-thread, Avalonia re-read the collection (which already contained the items from the subsequent Adds) before processing the individual Add events, showing every folder twice. Wrapping all ObservableCollection mutations in Dispatcher.UIThread.Post makes the batch atomic on the UI thread. Sign-in previously waited indefinitely for the browser flow. A separate 60-second timeoutCts is linked with the user-cancel CTS so both can cancel MSAL independently. timeoutCts.IsCancellationRequested distinguishes timeout from explicit user cancel, showing "Sign-in timed out. Please try again." rather than silently navigating back to provider selection. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jbarden
approved these changes
May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MatchAsyncoverloads added toTask<Result<T,E>>— Action-based (returnsTask) and Func-based (returnsTask<TOut>) — required for ViewModel async command handlersAuthService— full MSAL implementation: interactive browser sign-in (WithPrompt(Prompt.SelectAccount),WithUseEmbeddedWebView(false)), silent token refresh, sign-out, token cache viaMsalCacheHelper; maps all MSAL exceptions to typedAuthErrordiscriminated unionGraphService—GetRootFoldersAsyncwith per-account drive context caching and paginated Graph API folder enumeration; wraps all Graph exceptions inGraphErrorDUAccountOnboardingService— in-memory implementation: marks account active, returnsOk<OneDriveAccount, PersistenceError>(EF Core/SQLite persistence is out of scope for this issue)AddAccountWizardViewModel— full ReactiveUI ViewModel for all three steps: provider selection (Google Drive/Dropbox show not-implemented banner), MSAL sign-in, folder selection; commands:SelectProvider,SignIn,Back,AddAccount,Cancel; raisesCompleted/Cancelledevents;IDisposablewithCompositeDisposableWorkspaceViewModel—CurrentOverlayreactive property,OpenAddAccountWizardcommand resolves wizard from DI;OnWizardCompletedadds newAccountViewModelin-memory and clears overlayCommits
d430d14— RED: failing unit tests only (157 tests, 27 failing against stubs)cccab26— GREEN: implementation (157/157 pass, 0 errors, 0 warnings)Test plan
dotnet build— 0 errors, 0 warningsdotnet run --project test/AStar.Dev.CloudSyncFunctional.Tests.Unit— 157/157 passCloses #23
🤖 Generated with Claude Code