Skip to content

feat(wizard): add-account wizard with MSAL OneDrive auth (#23)#29

Merged
jaybarden1 merged 7 commits into
mainfrom
feature/23-add-account-wizard
May 27, 2026
Merged

feat(wizard): add-account wizard with MSAL OneDrive auth (#23)#29
jaybarden1 merged 7 commits into
mainfrom
feature/23-add-account-wizard

Conversation

@jaybarden1

@jaybarden1 jaybarden1 commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • MatchAsync overloads added to Task<Result<T,E>> — Action-based (returns Task) and Func-based (returns Task<TOut>) — required for ViewModel async command handlers
  • AuthService — full MSAL implementation: interactive browser sign-in (WithPrompt(Prompt.SelectAccount), WithUseEmbeddedWebView(false)), silent token refresh, sign-out, token cache via MsalCacheHelper; maps all MSAL exceptions to typed AuthError discriminated union
  • GraphServiceGetRootFoldersAsync with per-account drive context caching and paginated Graph API folder enumeration; wraps all Graph exceptions in GraphError DU
  • AccountOnboardingService — in-memory implementation: marks account active, returns Ok<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; raises Completed/Cancelled events; IDisposable with CompositeDisposable
  • WorkspaceViewModelCurrentOverlay reactive property, OpenAddAccountWizard command resolves wizard from DI; OnWizardCompleted adds new AccountViewModel in-memory and clears overlay

Commits

  • d430d14 — RED: failing unit tests only (157 tests, 27 failing against stubs)
  • cccab26 — GREEN: implementation (157/157 pass, 0 errors, 0 warnings)

Test plan

  • Build: dotnet build — 0 errors, 0 warnings
  • Tests: dotnet run --project test/AStar.Dev.CloudSyncFunctional.Tests.Unit — 157/157 pass
  • UI: "+ Add account…" button opens wizard overlay
  • Provider selection: OneDrive tile advances to sign-in step
  • Provider selection: Google Drive / Dropbox tiles show not-implemented banner
  • Sign-in: "Sign in with Microsoft" opens system browser, completes MSAL flow
  • Sign-in: cancelling browser returns to sign-in step with no error
  • Folder selection: folders load from Graph API after successful sign-in
  • "Add account" completes wizard and new account appears in sidebar
  • Cancel at any step closes overlay with no state change
  • Back button returns to previous step

Closes #23

🤖 Generated with Claude Code

jaybarden1 and others added 2 commits May 27, 2026 01:07
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>
@jaybarden1 jaybarden1 requested a review from a team May 27, 2026 00:21
@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown

Test results

0 tests  ±0   0 ✅ ±0   0s ⏱️ ±0s
0 suites ±0   0 💤 ±0 
0 files   ±0   0 ❌ ±0 

Results for commit f8f692d. ± Comparison against base commit ff5df2a.

♻️ This comment has been updated with latest results.

jaybarden1 and others added 5 commits May 27, 2026 01:36
…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>
@jaybarden1 jaybarden1 enabled auto-merge (squash) May 27, 2026 01:25
@jaybarden1 jaybarden1 merged commit a343982 into main May 27, 2026
5 of 6 checks passed
@jaybarden1 jaybarden1 deleted the feature/23-add-account-wizard branch May 27, 2026 01:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(wizard): Add Account wizard — OneDrive MSAL flow with Google/Dropbox stubs

2 participants