--- phase: 01-foundation plan: 06 type: execute wave: 5 depends_on: - 01-03 - 01-04 - 01-05 files_modified: - 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/Views/MainWindow.xaml - SharepointToolbox/Views/MainWindow.xaml.cs - SharepointToolbox/App.xaml.cs - SharepointToolbox.Tests/ViewModels/FeatureViewModelBaseTests.cs autonomous: true requirements: - FOUND-01 - FOUND-05 - FOUND-06 - FOUND-07 must_haves: truths: - "MainWindow displays: top toolbar, center TabControl with 8 feature tabs, bottom RichTextBox log panel (150px), bottom StatusBar" - "Toolbar ComboBox bound to TenantProfiles ObservableCollection; selecting a different item triggers TenantSwitchedMessage" - "FeatureViewModelBase provides CancellationTokenSource lifecycle, IsRunning, IProgress, OperationCanceledException handling" - "Global exception handlers (DispatcherUnhandledException + TaskScheduler.UnobservedTaskException) funnel to log panel + MessageBox" - "LogPanelSink wired to MainWindow RichTextBox after Generic Host starts" - "FeatureViewModelBaseTests: progress reporting, cancellation, and error handling all green" - "All 7 stub feature tabs use FeatureTabBase UserControl — ProgressBar + TextBlock + Cancel button shown only when IsRunning" - "StatusBar middle item shows live operation status text (ProgressStatus from ProgressUpdatedMessage), not static ConnectionStatus" artifacts: - path: "SharepointToolbox/ViewModels/FeatureViewModelBase.cs" provides: "Base class for all feature ViewModels with canonical async command pattern" contains: "CancellationTokenSource" - path: "SharepointToolbox/ViewModels/MainWindowViewModel.cs" provides: "Shell ViewModel with TenantProfiles and connection state" contains: "ObservableCollection" - path: "SharepointToolbox/Views/Controls/FeatureTabBase.xaml" provides: "Reusable UserControl with ProgressBar + TextBlock + Cancel button strip" contains: "ProgressBar" - path: "SharepointToolbox/Views/MainWindow.xaml" provides: "WPF shell with toolbar, TabControl, log panel, StatusBar" contains: "RichTextBox" key_links: - from: "SharepointToolbox/Views/MainWindow.xaml" to: "SharepointToolbox/ViewModels/MainWindowViewModel.cs" via: "DataContext binding in MainWindow.xaml.cs constructor" pattern: "DataContext" - from: "SharepointToolbox/App.xaml.cs" to: "SharepointToolbox/Infrastructure/Logging/LogPanelSink.cs" via: "LoggerConfiguration.WriteTo.Sink(new LogPanelSink(mainWindow.LogPanel))" pattern: "LogPanelSink" - from: "SharepointToolbox/ViewModels/MainWindowViewModel.cs" to: "SharepointToolbox/Core/Messages/TenantSwitchedMessage.cs" via: "WeakReferenceMessenger.Default.Send on ComboBox selection change" pattern: "TenantSwitchedMessage" - from: "SharepointToolbox/ViewModels/MainWindowViewModel.cs" to: "SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs" via: "Messenger.Register in OnActivated — updates ProgressStatus + ProgressPercentage" pattern: "ProgressUpdatedMessage" - from: "SharepointToolbox/Views/MainWindow.xaml StatusBar middle item" to: "SharepointToolbox/ViewModels/MainWindowViewModel.cs ProgressStatus" via: "Binding Content={Binding ProgressStatus}" pattern: "ProgressStatus" - from: "SharepointToolbox/Views/MainWindow.xaml stub TabItems" to: "SharepointToolbox/Views/Controls/FeatureTabBase.xaml" via: "TabItem Content contains " pattern: "FeatureTabBase" --- Build the WPF shell — MainWindow XAML + all ViewModels. Wire LogPanelSink to the RichTextBox. Implement FeatureViewModelBase with the canonical async pattern. Create FeatureTabBase UserControl (per-tab progress/cancel strip). Register global exception handlers. Purpose: This is the first time the application visually exists. All subsequent feature plans add TabItems to the already-wired TabControl. FeatureTabBase gives Phase 2+ a XAML template to extend rather than stub TextBlocks. Output: Runnable WPF application showing the shell with placeholder tabs (using FeatureTabBase), log panel, and status bar with live operation text. @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/phases/01-foundation/01-CONTEXT.md @.planning/phases/01-foundation/01-RESEARCH.md @.planning/phases/01-foundation/01-03-SUMMARY.md @.planning/phases/01-foundation/01-04-SUMMARY.md @.planning/phases/01-foundation/01-05-SUMMARY.md ```csharp public class TenantProfile { string Name; string TenantUrl; string ClientId; } public record OperationProgress(int Current, int Total, string Message) ``` ```csharp public sealed class TenantSwitchedMessage : ValueChangedMessage public sealed class LanguageChangedMessage : ValueChangedMessage ``` ```csharp // ProfileService: GetProfilesAsync(), AddProfileAsync(), RenameProfileAsync(), DeleteProfileAsync() // SessionManager: IsAuthenticated(url), GetOrCreateContextAsync(profile, ct), ClearSessionAsync(url) // SettingsService: GetSettingsAsync(), SetLanguageAsync(code), SetDataFolderAsync(path) ``` ```csharp // TranslationSource.Instance[key] — binding: Source={x:Static loc:TranslationSource.Instance}, Path=[key] ``` // Toolbar (L→R): ComboBox (220px) → Button "Connect" → Button "Manage Profiles..." → separator → Button "Clear Session" // TabControl: 8 tabs (Permissions, Storage, File Search, Duplicates, Templates, Bulk Operations, Folder Structure, Settings) // Log panel: RichTextBox, 150px tall, always visible, x:Name="LogPanel" // StatusBar: tenant name | operation status text | progress % // Per-tab layout: ProgressBar + TextBlock + Button "Cancel" — shown only when IsRunning (CONTEXT.md Gray Areas, locked) Task 1: FeatureViewModelBase + unit tests SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs, SharepointToolbox/ViewModels/FeatureViewModelBase.cs, SharepointToolbox.Tests/ViewModels/FeatureViewModelBaseTests.cs - Test: IsRunning is true while operation executes, false after completion - Test: ProgressValue and StatusMessage update via IProgress on UI thread - Test: Calling CancelCommand during operation causes StatusMessage to show cancellation message - Test: OperationCanceledException is caught gracefully — IsRunning becomes false, no exception propagates - Test: Exception during operation sets StatusMessage to error text — IsRunning becomes false - Test: RunCommand cannot be invoked while IsRunning (CanExecute returns false) Create `ViewModels/` directory. **FeatureViewModelBase.cs** — implement exactly as per research Pattern 2: ```csharp namespace SharepointToolbox.ViewModels; public abstract class FeatureViewModelBase : ObservableRecipient { private CancellationTokenSource? _cts; private readonly ILogger _logger; [ObservableProperty] [NotifyCanExecuteChangedFor(nameof(CancelCommand))] private bool _isRunning; [ObservableProperty] private string _statusMessage = string.Empty; [ObservableProperty] private int _progressValue; public IAsyncRelayCommand RunCommand { get; } public RelayCommand CancelCommand { get; } protected FeatureViewModelBase(ILogger logger) { _logger = logger; RunCommand = new AsyncRelayCommand(ExecuteAsync, () => !IsRunning); CancelCommand = new RelayCommand(() => _cts?.Cancel(), () => IsRunning); IsActive = true; // Activates ObservableRecipient for WeakReferenceMessenger } private async Task ExecuteAsync() { _cts = new CancellationTokenSource(); IsRunning = true; StatusMessage = string.Empty; ProgressValue = 0; try { var progress = new Progress(p => { ProgressValue = p.Total > 0 ? (int)(100.0 * p.Current / p.Total) : 0; StatusMessage = p.Message; WeakReferenceMessenger.Default.Send(new ProgressUpdatedMessage(p)); }); await RunOperationAsync(_cts.Token, progress); } catch (OperationCanceledException) { StatusMessage = TranslationSource.Instance["status.cancelled"]; _logger.LogInformation("Operation cancelled by user."); } catch (Exception ex) { StatusMessage = $"{TranslationSource.Instance["err.generic"]} {ex.Message}"; _logger.LogError(ex, "Operation failed."); } finally { IsRunning = false; _cts?.Dispose(); _cts = null; } } protected abstract Task RunOperationAsync(CancellationToken ct, IProgress progress); protected override void OnActivated() { Messenger.Register(this, (r, m) => r.OnTenantSwitched(m.Value)); } protected virtual void OnTenantSwitched(TenantProfile profile) { // Derived classes override to reset their state } } ``` Also create `Core/Messages/ProgressUpdatedMessage.cs` (needed for StatusBar update): ```csharp public sealed class ProgressUpdatedMessage : ValueChangedMessage { public ProgressUpdatedMessage(OperationProgress progress) : base(progress) { } } ``` **FeatureViewModelBaseTests.cs** — Replace stub. Use a concrete test subclass: ```csharp private class TestViewModel : FeatureViewModelBase { public TestViewModel(ILogger logger) : base(logger) { } public Func, Task>? OperationFunc { get; set; } protected override Task RunOperationAsync(CancellationToken ct, IProgress p) => OperationFunc?.Invoke(ct, p) ?? Task.CompletedTask; } ``` All tests in `[Trait("Category", "Unit")]`. cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~FeatureViewModelBaseTests" 2>&1 | tail -10 All FeatureViewModelBaseTests pass. IsRunning lifecycle correct. Cancellation handled gracefully. Exception caught with error message set. Task 2: FeatureTabBase UserControl, MainWindowViewModel, shell ViewModels, and MainWindow XAML SharepointToolbox/Views/Controls/FeatureTabBase.xaml, SharepointToolbox/Views/Controls/FeatureTabBase.xaml.cs, SharepointToolbox/ViewModels/MainWindowViewModel.cs, SharepointToolbox/ViewModels/ProfileManagementViewModel.cs, SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs, SharepointToolbox/Views/MainWindow.xaml, SharepointToolbox/Views/MainWindow.xaml.cs, SharepointToolbox/App.xaml.cs Create `Views/Controls/`, `ViewModels/Tabs/`, and `Views/` directories. **FeatureTabBase.xaml** — UserControl that every stub feature tab uses as its Content. This gives Phase 2+ a concrete XAML template to replace rather than a bare TextBlock. The progress/cancel strip is Visibility-bound to IsRunning per the locked CONTEXT.md decision. ```xml