Files
Sharepoint-Toolbox/.planning/phases/01-foundation/01-06-SUMMARY.md
Dev b41599d95a docs(01-06): complete WPF shell plan — SUMMARY, STATE, ROADMAP updated
- 2 tasks completed, 12 files modified
- 6 FeatureViewModelBase unit tests added and passing
- Full WPF shell with FeatureTabBase, MainWindowViewModel, LogPanelSink wiring
2026-04-02 12:34:58 +02:00

14 KiB

phase: 01-foundation plan: 06 subsystem: ui tags: [wpf, dotnet10, csharp, mvvm, community-toolkit-mvvm, xaml, serilog, localization, tdd, xunit] # Dependency graph requires: - 01-03 (ProfileService + SettingsService for DI registration) - 01-04 (SessionManager for ConnectCommand + ClearSessionCommand) - 01-05 (TranslationSource.Instance for all XAML bindings and StatusMessage keys) provides: - FeatureViewModelBase: abstract base for all feature ViewModels with CancellationTokenSource lifecycle, IsRunning, IProgress, ProgressUpdatedMessage dispatch - MainWindowViewModel: shell ViewModel with TenantProfiles ObservableCollection, TenantSwitchedMessage dispatch, ProgressUpdatedMessage subscription (live StatusBar) - ProfileManagementViewModel: CRUD on TenantProfile with input validation - SettingsViewModel: language + folder settings, OpenFolderDialog - FeatureTabBase UserControl: ProgressBar + TextBlock + Cancel button strip (shown only when IsRunning) - MainWindow.xaml: full WPF shell — Toolbar, TabControl (8 tabs with FeatureTabBase), RichTextBox LogPanel, StatusBar - App.xaml.cs: DI service registration, LogPanelSink wiring, global exception handlers - ProgressUpdatedMessage: ValueChangedMessage enabling StatusBar live update from feature ops affects: - 01-07 (SettingsView replaces Settings TextBlock placeholder; ProfileManagementDialog uses ProfileManagementViewModel) - 02-xx (all feature ViewModels extend FeatureViewModelBase; all feature tabs replace FeatureTabBase row 0) # Tech tracking tech-stack: added: [] patterns: - FeatureViewModelBase: AsyncRelayCommand + IProgress + CancellationToken — canonical async pattern for all feature ops - RunCommand CanExecute guard via () => !IsRunning — prevents double-execution - NotifyCanExecuteChangedFor(nameof(CancelCommand)) on IsRunning — keeps Cancel enabled state in sync - ProgressUpdatedMessage via WeakReferenceMessenger — decouples feature VMs from MainWindowViewModel StatusBar - LogPanelSink wired after MainWindow resolved — RichTextBox reference required before Serilog reconfiguration - OpenFolderDialog from Microsoft.Win32 — WPF-native folder picker; FolderBrowserDialog (WinForms) not available in WPF-only project - FeatureTabBase row 0 as Phase 2 extension point — stub TextBlock replaced by feature content per phase key-files: created: - SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs - SharepointToolbox/ViewModels/FeatureViewModelBase.cs - SharepointToolbox/ViewModels/MainWindowViewModel.cs - SharepointToolbox/ViewModels/ProfileManagementViewModel.cs - SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs - SharepointToolbox/Views/Controls/FeatureTabBase.xaml - SharepointToolbox/Views/Controls/FeatureTabBase.xaml.cs - SharepointToolbox.Tests/ViewModels/FeatureViewModelBaseTests.cs modified: - SharepointToolbox/MainWindow.xaml - SharepointToolbox/MainWindow.xaml.cs - SharepointToolbox/App.xaml - SharepointToolbox/App.xaml.cs key-decisions: - "ObservableRecipient lambda receivers need explicit cast — Messenger.Register lambda (r, m) types r as object; requires ((FeatureViewModelBase)r).Method() for virtual dispatch" - "FeatureViewModelBase and generated source both use partial class — CommunityToolkit.Mvvm source generator requires abstract partial class; plain abstract class causes CS0260" - "OpenFolderDialog (Microsoft.Win32) replaces FolderBrowserDialog (System.Windows.Forms) — WPF-only project does not reference WinForms; OpenFolderDialog available in .NET 8+ Microsoft.Win32" - "LogPanel exposed via GetLogPanel() method — x:Name='LogPanel' generates a field in the XAML partial class; defining a property with same name causes CS0102 duplicate definition" - "StatusBar middle StatusBarItem binds to ProgressStatus (not ConnectionStatus) — live operation text from ProgressUpdatedMessage, per locked CONTEXT.md decision" - "resx keys tab.search and tab.structure used (not tab.filesearch/tab.folderstructure) — actual keys in Strings.resx established in plan 01-05" patterns-established: - "FeatureViewModelBase pattern: every feature ViewModel inherits this, overrides RunOperationAsync(CancellationToken, IProgress) — no async void anywhere" - "Phase 2 extension point: FeatureTabBase Row 0 is the placeholder — Phase 2 replaces that row with real feature content while keeping the progress/cancel strip in Row 1" - "ObservableCollection only modified on UI thread — LoadProfilesAsync called from Loaded event (UI thread); all collection mutations remain on dispatcher" requirements-completed: - FOUND-01 - FOUND-05 - FOUND-06 - FOUND-07 # Metrics duration: 5min completed: 2026-04-02

Phase 1 Plan 06: WPF Shell Summary

FeatureViewModelBase with AsyncRelayCommand/CancellationToken/IProgress pattern + full WPF shell (Toolbar, 8-tab TabControl with FeatureTabBase, LogPanel, live-StatusBar) wired to Serilog, DI, and global exception handlers

Performance

  • Duration: 5 min
  • Started: 2026-04-02T10:28:10Z
  • Completed: 2026-04-02T10:33:00Z
  • Tasks: 2
  • Files modified: 12

Accomplishments

  • FeatureViewModelBase implements full async operation lifecycle: CancellationTokenSource creation/disposal, IsRunning guard on RunCommand.CanExecute, IProgress dispatching ProgressUpdatedMessage, OperationCanceledException caught gracefully, generic Exception caught with error message, finally block ensures IsRunning=false
  • MainWindowViewModel subscribes to ProgressUpdatedMessage via WeakReferenceMessenger — StatusBar middle item shows live operation status text from any running feature ViewModel
  • FeatureTabBase UserControl provides the canonical Phase 2 extension point: Row 0 contains the "coming soon" stub, Row 1 contains the progress/cancel strip (Visibility bound to IsRunning)
  • All 7 stub feature TabItems use <controls:FeatureTabBase /> — none contain bare TextBlocks
  • App.xaml.cs registers all services in DI, wires LogPanelSink to the RichTextBox after MainWindow is resolved from the container, and installs both DispatcherUnhandledException and TaskScheduler.UnobservedTaskException handlers
  • All 42 unit tests pass (6 new FeatureViewModelBase + 36 existing), 1 skipped (interactive MSAL), 0 errors, 0 warnings

Task Commits

  1. Task 1 (TDD): FeatureViewModelBase + ProgressUpdatedMessage + unit tests - 3c09155 (feat)
  2. Task 2: WPF shell — FeatureTabBase, ViewModels, MainWindow, App.xaml.cs - 5920d42 (feat)

Plan metadata: (docs commit follows)

Files Created/Modified

  • SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs — ValueChangedMessage for StatusBar live update dispatch
  • SharepointToolbox/ViewModels/FeatureViewModelBase.cs — Abstract partial base with CancellationTokenSource lifecycle, RunCommand/CancelCommand, IProgress, TenantSwitchedMessage registration
  • SharepointToolbox/ViewModels/MainWindowViewModel.cs — Shell ViewModel; TenantProfiles ObservableCollection; sends TenantSwitchedMessage on profile selection; subscribes ProgressUpdatedMessage for live StatusBar
  • SharepointToolbox/ViewModels/ProfileManagementViewModel.cs — CRUD on TenantProfile via ProfileService; AddCommand/RenameCommand/DeleteCommand with input validation
  • SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs — Language + DataFolder settings; OpenFolderDialog; delegates to SettingsService; extends FeatureViewModelBase
  • SharepointToolbox/Views/Controls/FeatureTabBase.xaml — UserControl: Row 0 = "coming soon" stub, Row 1 = ProgressBar + StatusMessage + Cancel button (Visibility=IsRunning)
  • SharepointToolbox/Views/Controls/FeatureTabBase.xaml.cs — Standard code-behind; no extra logic
  • SharepointToolbox/MainWindow.xaml — Full DockPanel shell: Toolbar (ComboBox + 3 buttons), TabControl (8 tabs), LogPanel (150px RichTextBox), StatusBar (SelectedProfile.Name | ProgressStatus | ProgressPercentage%)
  • SharepointToolbox/MainWindow.xaml.cs — DI constructor injection of MainWindowViewModel; DataContext set; LoadProfilesAsync on Loaded; GetLogPanel() accessor for App.xaml.cs
  • SharepointToolbox/App.xaml — Added BoolToVisibilityConverter resource
  • SharepointToolbox/App.xaml.cs — Full DI registration; LogPanelSink wired post-MainWindow-resolve; DispatcherUnhandledException + TaskScheduler.UnobservedTaskException global handlers
  • SharepointToolbox.Tests/ViewModels/FeatureViewModelBaseTests.cs — 6 unit tests: IsRunning lifecycle, IProgress updates, cancellation status message, OperationCanceledException grace, Exception error message, CanExecute guard

Decisions Made

  • ObservableRecipient lambda receivers need explicit cast: Messenger.Register<T> types the r parameter as object in the lambda signature; calling an instance method requires ((FeatureViewModelBase)r).Method() for correct virtual dispatch.
  • FeatureViewModelBase declared as abstract partial class — CommunityToolkit.Mvvm source generator generates a companion partial class for [ObservableProperty] attributes; plain abstract class causes CS0260 missing partial modifier.
  • OpenFolderDialog (Microsoft.Win32) used instead of FolderBrowserDialog (System.Windows.Forms) — WPF-only project does not reference WinForms; OpenFolderDialog available natively in .NET 8+ via Microsoft.Win32.
  • LogPanel exposed via GetLogPanel() method — x:Name="LogPanel" in XAML generates a backing field in the generated partial class; adding a property with the same name causes CS0102 duplicate definition error.

Deviations from Plan

Auto-fixed Issues

1. [Rule 3 - Blocking] Added partial modifier to FeatureViewModelBase

  • Found during: Task 1 (dotnet test — GREEN attempt)
  • Issue: CS0260 — CommunityToolkit.Mvvm source generator produces a companion partial class for [ObservableProperty]; class declared without partial keyword causes conflict
  • Fix: Changed public abstract class FeatureViewModelBase to public abstract partial class FeatureViewModelBase
  • Files modified: SharepointToolbox/ViewModels/FeatureViewModelBase.cs
  • Verification: Build succeeded, 6/6 FeatureViewModelBaseTests pass
  • Committed in: 3c09155 (Task 1 commit)

2. [Rule 1 - Bug] Fixed ObservableRecipient lambda receiver type

  • Found during: Task 1 (dotnet test — GREEN attempt)
  • Issue: CS1061 — Messenger.Register lambda types r as object; calling r.OnTenantSwitched() fails because method is not defined on object
  • Fix: Added explicit cast: ((FeatureViewModelBase)r).OnTenantSwitched(m.Value)
  • Files modified: SharepointToolbox/ViewModels/FeatureViewModelBase.cs
  • Verification: Build succeeded, tests pass
  • Committed in: 3c09155 (Task 1 commit)

3. [Rule 3 - Blocking] Replaced FolderBrowserDialog with OpenFolderDialog

  • Found during: Task 2 (dotnet build)
  • Issue: System.Windows.Forms namespace not available in WPF-only project; FolderBrowserDialog import fails
  • Fix: Replaced with Microsoft.Win32.OpenFolderDialog (available in .NET 8+ natively) and updated method accordingly
  • Files modified: SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs
  • Verification: Build succeeded with 0 errors
  • Committed in: 5920d42 (Task 2 commit)

4. [Rule 3 - Blocking] Exposed LogPanel via GetLogPanel() method instead of property

  • Found during: Task 2 (dotnet build)
  • Issue: CS0102 — x:Name="LogPanel" in XAML generates a field in the partial class; defining a property LogPanel in code-behind causes duplicate definition
  • Fix: Renamed the accessor to GetLogPanel() method; updated App.xaml.cs to call mainWindow.GetLogPanel()
  • Files modified: SharepointToolbox/MainWindow.xaml.cs, SharepointToolbox/App.xaml.cs
  • Verification: Build succeeded with 0 errors
  • Committed in: 5920d42 (Task 2 commit)

5. [Rule 1 - Bug] Used correct resx key names (tab.search, tab.structure)

  • Found during: Task 2 (XAML authoring)
  • Issue: Plan referenced tab.filesearch and tab.folderstructure but Strings.resx from plan 01-05 defines tab.search and tab.structure
  • Fix: Used the actual keys from Strings.resx: tab.search and tab.structure
  • Files modified: SharepointToolbox/MainWindow.xaml
  • Verification: Build succeeded; keys resolve correctly via TranslationSource
  • Committed in: 5920d42 (Task 2 commit)

Total deviations: 5 auto-fixed (2 Rule 1 bugs, 3 Rule 3 blocking build issues) Impact on plan: All fixes necessary for compilation and correct operation. No scope creep. Plan intent fully preserved.

Issues Encountered

None beyond the auto-fixed deviations above.

User Setup Required

None — no external service configuration required.

Next Phase Readiness

  • FeatureViewModelBase ready for all Phase 2 feature ViewModels to inherit — override RunOperationAsync and call RunCommand.ExecuteAsync(null) from UI
  • FeatureTabBase Row 0 is the Phase 2 extension point — replace the stub TextBlock row with real feature content
  • x:Name="SettingsTabItem" on Settings TabItem — plan 01-07 can replace the placeholder TextBlock with SettingsView
  • MainWindowViewModel.ManageProfilesCommand wired — plan 01-07 opens ProfileManagementDialog using ProfileManagementViewModel
  • All 42 unit tests green; 0 build errors/warnings — foundation ready for Phase 2 feature planning

Self-Check: PASSED

  • FOUND: SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs
  • FOUND: SharepointToolbox/ViewModels/FeatureViewModelBase.cs
  • FOUND: SharepointToolbox/ViewModels/MainWindowViewModel.cs
  • FOUND: SharepointToolbox/ViewModels/ProfileManagementViewModel.cs
  • FOUND: SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs
  • FOUND: SharepointToolbox/Views/Controls/FeatureTabBase.xaml
  • FOUND: SharepointToolbox/Views/Controls/FeatureTabBase.xaml.cs
  • FOUND: SharepointToolbox/MainWindow.xaml (contains RichTextBox x:Name="LogPanel")
  • FOUND: SharepointToolbox/MainWindow.xaml.cs
  • FOUND: SharepointToolbox/App.xaml (contains BoolToVisibilityConverter)
  • FOUND: SharepointToolbox/App.xaml.cs (contains DispatcherUnhandledException)
  • Commit 3c09155: feat(01-06): implement FeatureViewModelBase with async/cancel/progress pattern
  • Commit 5920d42: feat(01-06): build WPF shell — MainWindow XAML, ViewModels, LogPanelSink wiring

Phase: 01-foundation Completed: 2026-04-02