chore: complete v1.0 milestone

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>
This commit is contained in:
Dev
2026-04-07 09:15:14 +02:00
parent b815c323d7
commit 724fdc550d
959 changed files with 6852 additions and 728 deletions

View File

@@ -0,0 +1,119 @@
---
phase: 1
title: Foundation
status: ready-for-planning
created: 2026-04-02
---
# Phase 1 Context: Foundation
## Decided Areas (from prior research + STATE.md)
These are locked — do not re-litigate during planning or execution.
| Decision | Value |
|---|---|
| Runtime | .NET 10 LTS + WPF |
| MVVM framework | CommunityToolkit.Mvvm 8.4.2 |
| SharePoint library | PnP.Framework 1.18.0 |
| Auth | MSAL.NET 4.83.1 + Extensions.Msal 4.83.3 + Desktop 4.82.1 |
| Token cache | MsalCacheHelper — one `IPublicClientApplication` per ClientId |
| DI host | Microsoft.Extensions.Hosting 10.x |
| Logging | Serilog 4.3.1 + rolling file sink → `%AppData%\SharepointToolbox\logs\` |
| JSON | System.Text.Json (built-in) |
| JSON persistence | Write-then-replace (`file.tmp` → validate → `File.Move`) + `SemaphoreSlim(1)` per file |
| Async pattern | `AsyncRelayCommand` everywhere — zero `async void` handlers |
| Trimming | `PublishTrimmed=false` — accept ~150200 MB EXE |
| Architecture | 4-layer MVVM: View → ViewModel → Service → Infrastructure |
| Cross-VM messaging | `WeakReferenceMessenger` for tenant-switched events |
| Session holder | Singleton `SessionManager` — only class that holds `ClientContext` objects |
| Localization | .resx resource files (EN default, FR overlay) |
## Gray Areas — Defaults Applied (user skipped discussion)
### 1. Shell Layout
**Default:** Mirror the existing tool's spatial contract — users are already trained on it.
- **Window structure:** `MainWindow` with a top `ToolBar`, a center `TabControl` (feature tabs), and a bottom docked log panel.
- **Log panel:** Always visible, 150 px tall, not collapsible in Phase 1 (collapsibility is cosmetic — defer to a later phase). Uses a `RichTextBox`-equivalent (`RichTextBox` XAML control) with color-coded entries.
- **Tab strip:** `TabControl` with one `TabItem` per feature area. Phase 1 delivers a shell with placeholder tabs for all features so navigation is wired from day one.
- **Tabs to stub out:** Permissions, Storage, File Search, Duplicates, Templates, Bulk Operations, Folder Structure, Settings — all stubbed with a `"Coming soon"` placeholder `TextBlock` except Settings (partially functional in Phase 1 for profile management and language switching).
- **Status bar:** `StatusBar` at the very bottom (below the log panel) showing: current tenant display name | operation status text | progress percentage.
### 2. Tenant Selector Placement
**Default:** Prominent top-toolbar presence — tenant context is the most critical runtime state.
- **Toolbar layout (left to right):** `ComboBox` (tenant display name list, ~220 px wide) → `Button "Connect"``Button "Manage Profiles..."` → separator → `Button "Clear Session"`.
- **ComboBox:** Bound to `MainWindowViewModel.TenantProfiles` ObservableCollection. Selecting a different item triggers a tenant-switch command (WeakReferenceMessenger broadcast to reset all feature VMs).
- **"Manage Profiles..." button:** Opens a modal `ProfileManagementDialog` (separate Window) for CRUD — create, rename, delete profiles. Inline editing in the toolbar would be too cramped.
- **"Clear Session" button:** Clears the MSAL token cache for the currently selected tenant and resets connection state. Lives in the toolbar (not buried in settings) because MSP users need quick access when switching client accounts mid-session.
- **Profile fields:** Name (display label), Tenant URL, Client ID — matches existing `{ name, tenantUrl, clientId }` JSON schema exactly.
### 3. Progress + Cancel UX
**Default:** Per-tab pattern — each feature tab owns its progress state. No global progress bar.
- **Per-tab layout (bottom of each tab's content area):** `ProgressBar` (indeterminate or 0100) + `TextBlock` (operation description, e.g. "Scanning site 3 of 12…") + `Button "Cancel"` — shown only when an operation is running (`Visibility` bound to `IsRunning`).
- **`CancellationTokenSource`:** Owned by each ViewModel, recreated per operation. Cancel button calls `_cts.Cancel()`.
- **`IProgress<OperationProgress>`:** `OperationProgress` is a shared record `{ int Current, int Total, string Message }` — defined in the `Core/` layer and used by all feature services. Concrete implementation uses `Progress<T>` which marshals to the UI thread automatically.
- **Log panel as secondary channel:** Every progress step that produces a meaningful event also writes a timestamped line to the log panel. The per-tab progress bar is the live indicator; the log is the audit trail.
- **Status bar:** `StatusBar` at the bottom updates its operation text from the active tab's progress events via WeakReferenceMessenger — so the user sees progress even if they switch away from the running tab.
### 4. Error Surface UX
**Default:** Log panel as primary surface; modal dialog only for blocking errors.
- **Non-fatal errors** (an operation failed, a SharePoint call returned an error): Written to log panel in red. The per-tab status area shows a brief summary (e.g. "Completed with 2 errors — see log"). No modal.
- **Fatal/blocking errors** (auth failure, unhandled exception): `MessageBox.Show` modal with the error message and a "Copy to Clipboard" button for diagnostics. Keep it simple — no custom dialog in Phase 1.
- **No toasts in Phase 1:** Toast/notification infrastructure is a cosmetic feature — defer. The log panel is always visible and sufficient.
- **Log entry format:** `HH:mm:ss [LEVEL] Message` — color coded: green = info/success, orange = warning, red = error. `LEVEL` maps to Serilog severity.
- **Global exception handler:** `Application.DispatcherUnhandledException` and `TaskScheduler.UnobservedTaskException` both funnel to the log panel + a fatal modal. Neither swallows the exception.
- **Empty catch block policy:** Any `catch` block must do exactly one of: log-and-recover, log-and-rethrow, or log-and-surface. Empty catch = build defect. Enforce via code review on every PR in Phase 1.
## JSON Compatibility
Existing file names and schema must be preserved exactly — users have live data in these files.
| File | Schema |
|---|---|
| `Sharepoint_Export_profiles.json` | `{ "profiles": [{ "name": "...", "tenantUrl": "...", "clientId": "..." }] }` |
| `Sharepoint_Settings.json` | `{ "dataFolder": "...", "lang": "en" }` |
The C# `SettingsService` must read these files without migration — the field names are the contract.
## Localization
- **EN strings are the default `.resx`** — `Strings.resx` (neutral/EN). FR is `Strings.fr.resx`.
- **Key naming:** Mirror existing PowerShell key convention (`tab.perms`, `btn.run.scan`, `menu.language`, etc.) so the EN default content is easily auditable against the existing app.
- **Dynamic switching:** `CultureInfo.CurrentUICulture` swap + `WeakReferenceMessenger` broadcast triggers all bound `LocalizedString` markup extensions to re-evaluate. No app restart needed.
- **FR completeness:** FR strings will be stubbed with EN fallback in Phase 1 — FR completeness is a Phase 5 concern.
## Infrastructure Patterns (Phase 1 Deliverables)
These are shared helpers that all feature phases reuse. They must be built and tested in Phase 1 before any feature work begins.
1. **`SharePointPaginationHelper`** — static helper that wraps `CamlQuery` with `RowLimit ≤ 2,000` and `ListItemCollectionPosition` looping. All list enumeration in the codebase must call this — never raw `ExecuteQuery` on a list.
2. **`AsyncRelayCommand` pattern** — a thin base or example `FeatureViewModel` that demonstrates the canonical async command pattern: create `CancellationTokenSource`, bind `IsRunning`, bind `IProgress<OperationProgress>`, handle `OperationCanceledException` gracefully.
3. **`ObservableCollection` threading rule** — results are accumulated in `List<T>` on a background thread, then assigned as `new ObservableCollection<T>(list)` via `Dispatcher.InvokeAsync`. Never modify an `ObservableCollection` from `Task.Run`.
4. **`ExecuteQueryRetryAsync` wrapper** — wraps PnP Framework's retry logic. All CSOM calls use this; surface retry events as log + progress messages ("Throttled — retrying in 30s…").
5. **`ClientContext` disposal** — always `await using`. Unit tests verify `Dispose()` is called on cancellation.
## Deferred Ideas (out of scope for Phase 1)
- Log panel collapsibility (cosmetic, Phase 3+)
- Dark/light theme toggle (cosmetic, post-v1)
- Toast/notification system (Phase 3+)
- FR locale completeness (Phase 5)
- User access export, storage charts, simplified permissions view (v1.x features, Phase 5)
## code_context
| Asset | Path | Notes |
|---|---|---|
| Existing profile JSON schema | `Sharepoint_ToolBox.ps1:6872` | `Save-Profiles` shows exact field names |
| Existing settings JSON schema | `Sharepoint_ToolBox.ps1:147152` | `Save-Settings` shows `dataFolder` + `lang` |
| Existing localization keys (EN) | `Sharepoint_ToolBox.ps1:27952870` (approx) | Full EN key set for `.resx` migration |
| Existing tab names | `Sharepoint_ToolBox.ps1:3824` | 9 tabs: Perms, Storage, Templates, Search, Dupes, Transfer, Bulk, Struct, Versions |
| Log panel pattern | `Sharepoint_ToolBox.ps1:617` | Color + timestamp format to mirror |