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
This commit is contained in:
@@ -104,7 +104,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5
|
|||||||
|
|
||||||
| Phase | Plans Complete | Status | Completed |
|
| Phase | Plans Complete | Status | Completed |
|
||||||
|-------|----------------|--------|-----------|
|
|-------|----------------|--------|-----------|
|
||||||
| 1. Foundation | 5/8 | In Progress| |
|
| 1. Foundation | 6/8 | In Progress| |
|
||||||
| 2. Permissions | 0/? | Not started | - |
|
| 2. Permissions | 0/? | Not started | - |
|
||||||
| 3. Storage and File Operations | 0/? | Not started | - |
|
| 3. Storage and File Operations | 0/? | Not started | - |
|
||||||
| 4. Bulk Operations and Provisioning | 0/? | Not started | - |
|
| 4. Bulk Operations and Provisioning | 0/? | Not started | - |
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: planning
|
status: planning
|
||||||
stopped_at: Completed 01-foundation-04-PLAN.md
|
stopped_at: Completed 01-foundation-06-PLAN.md
|
||||||
last_updated: "2026-04-02T10:26:38.489Z"
|
last_updated: "2026-04-02T10:34:40.978Z"
|
||||||
last_activity: 2026-04-02 — Roadmap created, requirements mapped, all 42 v1 requirements assigned to phases
|
last_activity: 2026-04-02 — Roadmap created, requirements mapped, all 42 v1 requirements assigned to phases
|
||||||
progress:
|
progress:
|
||||||
total_phases: 5
|
total_phases: 5
|
||||||
completed_phases: 0
|
completed_phases: 0
|
||||||
total_plans: 8
|
total_plans: 8
|
||||||
completed_plans: 5
|
completed_plans: 6
|
||||||
percent: 13
|
percent: 13
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ Progress: [█░░░░░░░░░] 13%
|
|||||||
| Phase 01-foundation P03 | 8 | 2 tasks | 7 files |
|
| Phase 01-foundation P03 | 8 | 2 tasks | 7 files |
|
||||||
| Phase 01-foundation P05 | 4min | 2 tasks | 8 files |
|
| Phase 01-foundation P05 | 4min | 2 tasks | 8 files |
|
||||||
| Phase 01-foundation P04 | 4 | 2 tasks | 4 files |
|
| Phase 01-foundation P04 | 4 | 2 tasks | 4 files |
|
||||||
|
| Phase 01-foundation P06 | 5 | 2 tasks | 12 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## 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]: 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]: 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]: 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
|
### Pending Todos
|
||||||
|
|
||||||
@@ -94,6 +99,6 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-02T10:26:38.487Z
|
Last session: 2026-04-02T10:34:40.976Z
|
||||||
Stopped at: Completed 01-foundation-04-PLAN.md
|
Stopped at: Completed 01-foundation-06-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
212
.planning/phases/01-foundation/01-06-SUMMARY.md
Normal file
212
.planning/phases/01-foundation/01-06-SUMMARY.md
Normal file
@@ -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<OperationProgress>, 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<OperationProgress> + 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<T> 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<OperationProgress>) — 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<OperationProgress> 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<OperationProgress> for StatusBar live update dispatch
|
||||||
|
- `SharepointToolbox/ViewModels/FeatureViewModelBase.cs` — Abstract partial base with CancellationTokenSource lifecycle, RunCommand/CancelCommand, IProgress<OperationProgress>, 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*
|
||||||
Reference in New Issue
Block a user