Archive 5 phases (36 plans) to milestones/v1.0-phases/. Archive roadmap, requirements, and audit to milestones/. Evolve PROJECT.md with shipped state and validated requirements. Collapse ROADMAP.md to one-line milestone summary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
22 KiB
phase, verified, status, score, re_verification
| phase | verified | status | score | re_verification |
|---|---|---|---|---|
| 01-foundation | 2026-04-02T11:15:00Z | passed | 11/11 must-haves verified | false |
Phase 1: Foundation Verification Report
Phase Goal: Establish the complete WPF .NET 10 application skeleton with authentication infrastructure, persistence layer, localization system, and all shared patterns that every subsequent phase will build upon. Verified: 2026-04-02T11:15:00Z Status: PASSED Re-verification: No — initial verification
Goal Achievement
Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | dotnet test produces zero failures (44 pass, 1 skip for interactive MSAL) | VERIFIED | Live run: Failed=0, Passed=44, Skipped=1, Total=45, Duration=192ms |
| 2 | Solution contains two projects (SharepointToolbox WPF + SharepointToolbox.Tests xUnit) | VERIFIED | SharepointToolbox.slnx references both .csproj files; both directories confirmed |
| 3 | App.xaml has no StartupUri; Generic Host entry point with [STAThread] Main | VERIFIED | App.xaml confirmed no StartupUri; App.xaml.cs has [STAThread] + Host.CreateDefaultBuilder |
| 4 | All NuGet packages present with correct versions; PublishTrimmed=false | VERIFIED | csproj: CommunityToolkit.Mvvm 8.4.2, MSAL 4.83.3, PnP.Framework 1.18.0, Serilog 4.3.1 |
| 5 | Core models, messages, and infrastructure helpers provide typed contracts | VERIFIED | TenantProfile, OperationProgress, TenantSwitchedMessage, LanguageChangedMessage, helpers |
| 6 | Persistence layer uses write-then-replace with SemaphoreSlim(1); JSON schema matches live data | VERIFIED | ProfileRepository.cs and SettingsRepository.cs both implement .tmp + File.Move pattern |
| 7 | Authentication layer provides per-ClientId MSAL PCA isolation; SessionManager is sole holder | VERIFIED | MsalClientFactory has per-clientId Dictionary + SemaphoreSlim; SessionManager confirmed |
| 8 | TranslationSource enables runtime culture switching without restart | VERIFIED | TranslationSource.cs: PropertyChangedEventArgs(string.Empty) on culture change |
| 9 | Serilog wired to rolling file + LogPanelSink; ILogger injectable via DI | VERIFIED | App.xaml.cs wires LogPanelSink after MainWindow resolved; all services use ILogger |
| 10 | WPF shell shows toolbar, 8-tab TabControl with FeatureTabBase, log panel, live StatusBar | VERIFIED | MainWindow.xaml confirmed: ToolBar, 8 TabItems (7 with FeatureTabBase), RichTextBox x:Name="LogPanel", StatusBar with ProgressStatus binding |
| 11 | ProfileManagementDialog + SettingsView complete Phase 1 UX; language switch immediate | VERIFIED | Both views exist with DI injection; SettingsTabItem.Content set from code-behind; FR translations confirmed real (Connexion, Annuler, Langue) |
Score: 11/11 truths verified
Required Artifacts
| Artifact | Provides | Status | Details |
|---|---|---|---|
SharepointToolbox.slnx |
Solution with both projects | VERIFIED | Exists; .slnx format (dotnet new sln in .NET 10 SDK) |
SharepointToolbox/SharepointToolbox.csproj |
WPF .NET 10 project with all NuGet packages | VERIFIED | Contains PublishTrimmed=false, StartupObject, all 9 packages |
SharepointToolbox/App.xaml.cs |
Generic Host entry point with [STAThread] | VERIFIED | [STAThread] Main, Host.CreateDefaultBuilder, LogPanelSink wiring, DI reg |
SharepointToolbox/App.xaml |
No StartupUri; BoolToVisibilityConverter | VERIFIED | No StartupUri; BooleanToVisibilityConverter resource present |
SharepointToolbox.Tests/SharepointToolbox.Tests.csproj |
xUnit test project referencing main project | VERIFIED | References main project; xunit 2.9.3; Moq 4.20.72; net10.0-windows |
SharepointToolbox/Core/Models/TenantProfile.cs |
Profile model with TenantUrl field | VERIFIED | Plain class; Name/TenantUrl/ClientId matching JSON schema |
SharepointToolbox/Core/Models/OperationProgress.cs |
Shared progress record for IProgress | VERIFIED | record OperationProgress with Indeterminate factory |
SharepointToolbox/Core/Models/AppSettings.cs |
Settings model with DataFolder + Lang | VERIFIED | Exists in Core/Models; camelCase-compatible |
SharepointToolbox/Core/Messages/TenantSwitchedMessage.cs |
WeakReferenceMessenger broadcast message | VERIFIED | Extends ValueChangedMessage |
SharepointToolbox/Core/Messages/LanguageChangedMessage.cs |
Language change broadcast message | VERIFIED | Extends ValueChangedMessage |
SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs |
StatusBar live update message | VERIFIED | Extends ValueChangedMessage |
SharepointToolbox/Core/Helpers/SharePointPaginationHelper.cs |
CSOM pagination via ListItemCollectionPosition | VERIFIED | Contains ListItemCollectionPosition do/while loop; [EnumeratorCancellation] |
SharepointToolbox/Core/Helpers/ExecuteQueryRetryHelper.cs |
Throttle-aware retry with IProgress surfacing | VERIFIED | ExecuteQueryRetryAsync with exponential backoff; IProgress |
SharepointToolbox/Infrastructure/Logging/LogPanelSink.cs |
ILogEventSink writing to RichTextBox via Dispatcher | VERIFIED | Implements ILogEventSink; uses Application.Current?.Dispatcher.InvokeAsync |
SharepointToolbox/Infrastructure/Auth/MsalClientFactory.cs |
Per-ClientId IPublicClientApplication + cache | VERIFIED | SemaphoreSlim; per-clientId Dictionary; MsalCacheHelper; GetCacheHelper() |
SharepointToolbox/Infrastructure/Persistence/ProfileRepository.cs |
File I/O with SemaphoreSlim + write-then-replace | VERIFIED | SemaphoreSlim(1,1); .tmp write + JsonDocument.Parse + File.Move |
SharepointToolbox/Infrastructure/Persistence/SettingsRepository.cs |
Settings file I/O with write-then-replace | VERIFIED | Same pattern as ProfileRepository; camelCase serialization |
SharepointToolbox/Services/ProfileService.cs |
CRUD on TenantProfile with validation | VERIFIED | 54 lines; GetProfilesAsync/AddProfileAsync/RenameProfileAsync/DeleteProfileAsync |
SharepointToolbox/Services/SettingsService.cs |
Get/SetLanguage/SetDataFolder with validation | VERIFIED | 39 lines; validates "en"/"fr" only; delegates to SettingsRepository |
SharepointToolbox/Services/SessionManager.cs |
Singleton holding all ClientContext instances | VERIFIED | IsAuthenticated/GetOrCreateContextAsync/ClearSessionAsync; NormalizeUrl |
SharepointToolbox/Localization/TranslationSource.cs |
Singleton INotifyPropertyChanged string lookup | VERIFIED | PropertyChangedEventArgs(string.Empty) on culture switch; missing key returns "[key]" |
SharepointToolbox/Localization/Strings.resx |
27 EN Phase 1 UI strings | VERIFIED | 29 data entries confirmed; all required keys present (tab., toolbar., etc.) |
SharepointToolbox/Localization/Strings.fr.resx |
27 FR keys with real translations | VERIFIED | 29 data entries; real French strings confirmed: Connexion, Annuler, Langue |
SharepointToolbox/Localization/Strings.Designer.cs |
ResourceManager accessor for dotnet build | VERIFIED | Exists; manually maintained; no VS ResXFileCodeGenerator dependency |
SharepointToolbox/ViewModels/FeatureViewModelBase.cs |
Abstract base with CancellationTokenSource lifecycle | VERIFIED | CancellationTokenSource; RunCommand/CancelCommand; IProgress |
SharepointToolbox/ViewModels/MainWindowViewModel.cs |
Shell ViewModel with TenantProfiles + ProgressStatus | VERIFIED | ObservableCollection; TenantSwitchedMessage dispatch; ProgressUpdatedMessage subscription |
SharepointToolbox/ViewModels/ProfileManagementViewModel.cs |
CRUD dialog ViewModel | VERIFIED | Exists; AddCommand/RenameCommand/DeleteCommand |
SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs |
Language + folder settings ViewModel | VERIFIED | BrowseFolderCommand; delegates to SettingsService |
SharepointToolbox/Views/Controls/FeatureTabBase.xaml |
Reusable UserControl with ProgressBar + Cancel | VERIFIED | ProgressBar + TextBlock + Button; Visibility bound to IsRunning via BoolToVisibilityConverter |
SharepointToolbox/Views/MainWindow.xaml |
WPF shell with toolbar, TabControl, log panel | VERIFIED | RichTextBox x:Name="LogPanel"; 7 FeatureTabBase tabs; StatusBar ProgressStatus binding |
SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml |
Modal dialog for profile CRUD | VERIFIED | Window; 3 input fields (Name/TenantUrl/ClientId); TranslationSource bindings |
SharepointToolbox/Views/Tabs/SettingsView.xaml |
Settings tab with language + folder controls | VERIFIED | Language ComboBox (en/fr); DataFolder TextBox; BrowseFolderCommand button |
| All 7 test files | Unit/integration tests (728 lines total) | VERIFIED | ProfileServiceTests 172L, SettingsServiceTests 123L, MsalClientFactoryTests 75L, SessionManagerTests 103L, FeatureViewModelBaseTests 125L, TranslationSourceTests 83L, LoggingIntegrationTests 47L |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
| App.xaml.cs | App.xaml | x:Class + no StartupUri + Page not ApplicationDefinition | VERIFIED | App.xaml has no StartupUri; csproj demotes to Page |
| App.xaml.cs | LogPanelSink | LoggerConfiguration.WriteTo.Sink(new LogPanelSink(mainWindow.GetLogPanel())) | VERIFIED | Line 48 of App.xaml.cs confirmed wired |
| App.xaml.cs | All DI services | RegisterServices — all 10 services registered | VERIFIED | ProfileRepository, SettingsRepository, MsalClientFactory, SessionManager, ProfileService, SettingsService, MainWindowViewModel, ProfileManagementViewModel, SettingsViewModel, MainWindow, ProfileManagementDialog, SettingsView |
| MainWindowViewModel | TenantSwitchedMessage | WeakReferenceMessenger.Default.Send in OnSelectedProfileChanged | VERIFIED | Confirmed in MainWindowViewModel.cs line 72 |
| MainWindowViewModel | ProgressUpdatedMessage | Messenger.Register in OnActivated — updates ProgressStatus | VERIFIED | ProgressStatus and ProgressPercentage updated in OnActivated |
| MainWindow.xaml StatusBar | ProgressStatus | Binding Content={Binding ProgressStatus} | VERIFIED | Line 31 of MainWindow.xaml confirmed |
| MainWindow.xaml stub tabs | FeatureTabBase | TabItem Content = controls:FeatureTabBase | VERIFIED | 7 of 8 tabs use FeatureTabBase; SettingsTabItem uses DI-resolved SettingsView |
| MainWindow.xaml.cs | SettingsView (via DI) | SettingsTabItem.Content = serviceProvider.GetRequiredService() | VERIFIED | Line 24 of MainWindow.xaml.cs confirmed |
| MainWindow.xaml.cs | ProfileManagementDialog factory | viewModel.OpenProfileManagementDialog = () => serviceProvider.GetRequiredService() | VERIFIED | Line 21 confirmed |
| FeatureViewModelBase | ProgressUpdatedMessage | WeakReferenceMessenger.Default.Send in Progress callback | VERIFIED | Line 49 of FeatureViewModelBase.cs |
| SessionManager | MsalClientFactory | _msalFactory.GetOrCreateAsync + GetCacheHelper (tokenCacheCallback) | VERIFIED | SessionManager.cs lines 56-72 confirmed |
| ProfileRepository | Sharepoint_Export_profiles.json | { "profiles": [...] } wrapper via camelCase STJ | VERIFIED | ProfilesRoot class with Profiles list; camelCase serialization |
| SettingsRepository | Sharepoint_Settings.json | { "dataFolder", "lang" } via camelCase STJ | VERIFIED | SettingsRepository.cs with camelCase serialization |
| TranslationSource | Strings.resx | Strings.ResourceManager (via Strings.Designer.cs) | VERIFIED | TranslationSource.cs line 17: Strings.ResourceManager |
Requirements Coverage
| Requirement | Plans | Description | Status | Evidence |
|---|---|---|---|---|
| FOUND-01 | 01, 06, 08 | WPF .NET 10 + MVVM architecture | SATISFIED | SharepointToolbox.csproj net10.0-windows + UseWPF; CommunityToolkit.Mvvm; FeatureViewModelBase + MainWindowViewModel MVVM pattern |
| FOUND-02 | 03, 07, 08 | Multi-tenant profile registry (create/rename/delete/switch) | SATISFIED | ProfileService CRUD + ProfileManagementDialog UI; ProfileServiceTests 10 tests pass |
| FOUND-03 | 04, 08 | MSAL token cache per tenant; authenticated across tenant switches | SATISFIED | MsalClientFactory per-clientId PCA + MsalCacheHelper; SessionManager caches ClientContext |
| FOUND-04 | 04, 08 | Interactive Azure AD OAuth login via browser; no secrets stored | SATISFIED | SessionManager.GetOrCreateContextAsync uses AuthenticationManager.CreateWithInteractiveLogin; no client secrets in code |
| FOUND-05 | 02, 06, 08 | Long-running operations report progress in real-time | SATISFIED | OperationProgress record; IProgress in FeatureViewModelBase; ProgressUpdatedMessage to StatusBar |
| FOUND-06 | 06, 08 | User can cancel any long-running operation | SATISFIED | CancellationTokenSource lifecycle in FeatureViewModelBase; CancelCommand; FeatureTabBase Cancel button |
| FOUND-07 | 02, 06, 08 | Errors surface with actionable messages; no silent failures | SATISFIED | Global DispatcherUnhandledException + TaskScheduler.UnobservedTaskException; FeatureViewModelBase catches Exception; LogPanelSink colors errors red |
| FOUND-08 | 02, 05, 08 | Structured logging (Serilog) | SATISFIED | Serilog 4.3.1 + Serilog.Sinks.File + Serilog.Extensions.Hosting; rolling daily log; LogPanelSink for in-app panel |
| FOUND-09 | 05, 07, 08 | Localization supporting EN and FR with dynamic language switching | SATISFIED | TranslationSource.Instance; PropertyChangedEventArgs(string.Empty); SettingsView language ComboBox; real FR translations |
| FOUND-10 | 03, 08 | JSON-based local storage compatible with current app format for migration | SATISFIED | ProfileRepository uses { "profiles": [...] } schema; camelCase field names match existing JSON |
| FOUND-11 | Phase 5 | Self-contained single EXE distribution (deferred) | N/A | Explicitly deferred to Phase 5 — not in scope for Phase 1 |
| FOUND-12 | 03, 07, 08 | Configurable data output folder for exports | SATISFIED | SettingsService.SetDataFolderAsync; SettingsView DataFolder TextBox + Browse button; persists to settings.json |
Orphaned requirements: None — all Phase 1 requirements are claimed by plans. FOUND-11 is correctly assigned to Phase 5.
Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
ViewModels/Tabs/SettingsViewModel.cs |
92 | throw new NotSupportedException(...) in RunOperationAsync |
INFO | Intentional — Settings tab has no long-running operation; per-plan design decision |
No blockers or warnings found. The single NotSupportedException is by design — SettingsViewModel extends FeatureViewModelBase but has no long-running operation; the throw is the correct implementation per the plan spec.
Build note: dotnet build produces MSB3026/MSB3027 file-lock errors because the application is currently running (process 4480 has the .exe locked). These are environment-state errors, not source code compilation errors. The test suite ran successfully with --no-build (44/44 pass), confirming the previously compiled artifacts are correct. Source code itself has 0 C# errors or warnings.
Human Verification Required
The following items were confirmed by human during plan 01-08 visual checkpoint and cannot be re-verified programmatically:
1. WPF Shell Launch and Layout
Test: Run dotnet run --project SharepointToolbox/SharepointToolbox.csproj
Expected: Window shows toolbar at top, 8-tab TabControl, 150px log panel (black background, green text), status bar at bottom
Why human: Visual layout cannot be verified by grep; WPF rendering requires runtime
Status: Confirmed by human in plan 01-08 (2026-04-02)
2. Dynamic Language Switching
Test: Open Settings tab, change to French, observe tab headers change immediately Expected: Tab headers switch to French without restart Why human: Runtime WPF binding behavior; TranslationSource.PropertyChanged must actually trigger binding refresh Status: Confirmed by human in plan 01-08 (2026-04-02)
3. Profile Management Dialog
Test: Click "Manage Profiles...", add/rename/delete a profile, verify toolbar ComboBox updates Expected: Modal dialog opens; all 3 CRUD operations work; ComboBox refreshes after dialog closes Why human: Dialog modal flow; ComboBox refresh timing; runtime interaction required Status: Confirmed by human in plan 01-08 (2026-04-02)
4. Log Panel Rendering
Test: Observe startup messages in log panel Expected: Timestamped entries in HH:mm:ss [LEVEL] message format; info=green, warn=orange, error=red Why human: WPF RichTextBox rendering; color coding; Dispatcher dispatch timing Status: Confirmed by human in plan 01-08 (2026-04-02)
5. MSAL Interactive Login Flow
Test: Select a profile with real Azure AD ClientId + TenantUrl, click Connect Expected: Browser/WAM opens for interactive authentication; on success, connection established Why human: Requires real Azure AD tenant; browser interaction; cannot run in automated test Status: Intentionally deferred to Phase 2 integration testing — infrastructure in place
Gaps Summary
No gaps found. All 11 observable truths are verified. All 11 requirement IDs (FOUND-01 through FOUND-12, excluding FOUND-11 which is Phase 5) are satisfied. All required artifacts exist and are substantive. All key links are wired and confirmed by code inspection.
The phase goal is fully achieved: the application has a complete WPF .NET 10 skeleton with:
- Generic Host + DI container wired
- Per-tenant MSAL authentication infrastructure (no interactive login in tests — expected)
- Write-then-replace file persistence with JSON schema compatibility
- Runtime culture-switching localization (EN + real FR translations)
- FeatureViewModelBase pattern establishing the async/cancel/progress contract for all feature phases
- WPF shell with toolbar, 8-tab TabControl, log panel, and live status bar
- 44 automated tests green; 1 interactive MSAL test correctly skipped
Verified: 2026-04-02T11:15:00Z Verifier: Claude (gsd-verifier)