diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 1c24e2c..e59ce1a 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -104,7 +104,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Foundation | 5/8 | In Progress| | +| 1. Foundation | 6/8 | In Progress| | | 2. Permissions | 0/? | Not started | - | | 3. Storage and File Operations | 0/? | Not started | - | | 4. Bulk Operations and Provisioning | 0/? | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 35f0f72..c97b9c8 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: planning -stopped_at: Completed 01-foundation-04-PLAN.md -last_updated: "2026-04-02T10:26:38.489Z" +stopped_at: Completed 01-foundation-06-PLAN.md +last_updated: "2026-04-02T10:34:40.978Z" last_activity: 2026-04-02 — Roadmap created, requirements mapped, all 42 v1 requirements assigned to phases progress: total_phases: 5 completed_phases: 0 total_plans: 8 - completed_plans: 5 + completed_plans: 6 percent: 13 --- @@ -55,6 +55,7 @@ Progress: [█░░░░░░░░░] 13% | Phase 01-foundation P03 | 8 | 2 tasks | 7 files | | Phase 01-foundation P05 | 4min | 2 tasks | 8 files | | Phase 01-foundation P04 | 4 | 2 tasks | 4 files | +| Phase 01-foundation P06 | 5 | 2 tasks | 12 files | ## Accumulated Context @@ -81,6 +82,10 @@ Recent decisions affecting current work: - [Phase 01-foundation]: SessionManager is the single holder of ClientContext instances — callers must not store returned contexts - [Phase 01-foundation]: CacheDirectory is a constructor parameter (no-arg defaults to AppData) — enables test isolation without real filesystem writes - [Phase 01-foundation]: Interactive login test marked Skip in unit suite — browser/WAM MSAL flow cannot run in automated CI +- [Phase 01-foundation]: ObservableRecipient lambda receivers need explicit cast to FeatureViewModelBase for virtual dispatch +- [Phase 01-foundation]: FeatureViewModelBase declared as abstract partial class — CommunityToolkit.Mvvm source generator requires partial keyword +- [Phase 01-foundation]: OpenFolderDialog (Microsoft.Win32) used in WPF instead of FolderBrowserDialog (System.Windows.Forms) +- [Phase 01-foundation]: LogPanel exposed via GetLogPanel() method — x:Name generates field in XAML partial class, property with same name causes CS0102 ### Pending Todos @@ -94,6 +99,6 @@ None yet. ## Session Continuity -Last session: 2026-04-02T10:26:38.487Z -Stopped at: Completed 01-foundation-04-PLAN.md +Last session: 2026-04-02T10:34:40.976Z +Stopped at: Completed 01-foundation-06-PLAN.md Resume file: None diff --git a/.planning/phases/01-foundation/01-06-SUMMARY.md b/.planning/phases/01-foundation/01-06-SUMMARY.md new file mode 100644 index 0000000..251fa87 --- /dev/null +++ b/.planning/phases/01-foundation/01-06-SUMMARY.md @@ -0,0 +1,212 @@ +--- +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 `` — 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` 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*