---
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<OperationProgress>, 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<ProgressUpdatedMessage> 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 <controls:FeatureTabBase />"
      pattern: "FeatureTabBase"
---

<objective>
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.
</objective>

<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.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

<interfaces>
<!-- From Core/Models (plan 01-02) -->
```csharp
public class TenantProfile { string Name; string TenantUrl; string ClientId; }
public record OperationProgress(int Current, int Total, string Message)
```

<!-- From Core/Messages (plan 01-02) -->
```csharp
public sealed class TenantSwitchedMessage : ValueChangedMessage<TenantProfile>
public sealed class LanguageChangedMessage : ValueChangedMessage<string>
```

<!-- From Services (plans 01-03, 01-04) -->
```csharp
// ProfileService: GetProfilesAsync(), AddProfileAsync(), RenameProfileAsync(), DeleteProfileAsync()
// SessionManager: IsAuthenticated(url), GetOrCreateContextAsync(profile, ct), ClearSessionAsync(url)
// SettingsService: GetSettingsAsync(), SetLanguageAsync(code), SetDataFolderAsync(path)
```

<!-- From Localization (plan 01-05) -->
```csharp
// TranslationSource.Instance[key] — binding: Source={x:Static loc:TranslationSource.Instance}, Path=[key]
```

<!-- Shell layout (locked in CONTEXT.md) -->
// 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)
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: FeatureViewModelBase + unit tests</name>
  <files>
    SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs,
    SharepointToolbox/ViewModels/FeatureViewModelBase.cs,
    SharepointToolbox.Tests/ViewModels/FeatureViewModelBaseTests.cs
  </files>
  <behavior>
    - Test: IsRunning is true while operation executes, false after completion
    - Test: ProgressValue and StatusMessage update via IProgress<OperationProgress> 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)
  </behavior>
  <action>
    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<FeatureViewModelBase> _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<FeatureViewModelBase> 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<OperationProgress>(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<OperationProgress> progress);

        protected override void OnActivated()
        {
            Messenger.Register<TenantSwitchedMessage>(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<OperationProgress>
    {
        public ProgressUpdatedMessage(OperationProgress progress) : base(progress) { }
    }
    ```

    **FeatureViewModelBaseTests.cs** — Replace stub. Use a concrete test subclass:
    ```csharp
    private class TestViewModel : FeatureViewModelBase
    {
        public TestViewModel(ILogger<FeatureViewModelBase> logger) : base(logger) { }
        public Func<CancellationToken, IProgress<OperationProgress>, Task>? OperationFunc { get; set; }
        protected override Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> p)
            => OperationFunc?.Invoke(ct, p) ?? Task.CompletedTask;
    }
    ```
    All tests in `[Trait("Category", "Unit")]`.
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --filter "FullyQualifiedName~FeatureViewModelBaseTests" 2>&1 | tail -10</automated>
  </verify>
  <done>All FeatureViewModelBaseTests pass. IsRunning lifecycle correct. Cancellation handled gracefully. Exception caught with error message set.</done>
</task>

<task type="auto">
  <name>Task 2: FeatureTabBase UserControl, MainWindowViewModel, shell ViewModels, and MainWindow XAML</name>
  <files>
    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
  </files>
  <action>
    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
    <UserControl x:Class="SharepointToolbox.Views.Controls.FeatureTabBase"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="*" />       <!-- Feature content area (Phase 2+ replaces this) -->
          <RowDefinition Height="Auto" />    <!-- Progress/cancel strip -->
        </Grid.RowDefinitions>

        <!-- Placeholder content — Phase 2+ replaces Row 0 -->
        <TextBlock Grid.Row="0"
                   Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.comingsoon]}"
                   HorizontalAlignment="Center" VerticalAlignment="Center" />

        <!-- Per-tab progress/cancel strip (locked CONTEXT.md: shown only when IsRunning) -->
        <Grid Grid.Row="1" Margin="8,4"
              Visibility="{Binding IsRunning, Converter={StaticResource BoolToVisibilityConverter}}">
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
          </Grid.ColumnDefinitions>
          <ProgressBar Grid.Column="0" Height="16" Minimum="0" Maximum="100"
                       Value="{Binding ProgressValue}" />
          <TextBlock Grid.Column="1" Margin="8,0" VerticalAlignment="Center"
                     Text="{Binding StatusMessage}" />
          <Button Grid.Column="2"
                  Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.cancel]}"
                  Command="{Binding CancelCommand}" Width="70" />
        </Grid>
      </Grid>
    </UserControl>
    ```

    **FeatureTabBase.xaml.cs**: Standard code-behind with no extra logic (DataContext is set by the parent TabItem's DataContext chain).

    Add `BoolToVisibilityConverter` to App.xaml resources if not already present:
    ```xml
    <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
    ```

    **MainWindowViewModel.cs**:
    ```csharp
    [ObservableProperty] private TenantProfile? _selectedProfile;
    [ObservableProperty] private string _connectionStatus = "Not connected";
    [ObservableProperty] private string _progressStatus = string.Empty;
    [ObservableProperty] private int _progressPercentage;
    public ObservableCollection<TenantProfile> TenantProfiles { get; } = new();

    // ConnectCommand: calls SessionManager.GetOrCreateContextAsync(SelectedProfile)
    // ClearSessionCommand: calls SessionManager.ClearSessionAsync(SelectedProfile.TenantUrl)
    // ManageProfilesCommand: opens ProfileManagementDialog as modal
    // OnSelectedProfileChanged (partial): sends TenantSwitchedMessage via WeakReferenceMessenger
    // LoadProfilesAsync: called on startup, loads from ProfileService
    ```

    Override `OnActivated()` to register for `ProgressUpdatedMessage` from any active feature ViewModel:
    ```csharp
    protected override void OnActivated()
    {
        base.OnActivated();
        Messenger.Register<ProgressUpdatedMessage>(this, (r, m) =>
        {
            r.ProgressStatus = m.Value.Message;
            r.ProgressPercentage = m.Value.Total > 0
                ? (int)(100.0 * m.Value.Current / m.Value.Total)
                : 0;
        });
    }
    ```
    This wires the StatusBar operation text and progress % to live updates from any running feature operation.

    **ProfileManagementViewModel.cs**: Wraps ProfileService for dialog binding.
    - `ObservableCollection<TenantProfile> Profiles`
    - `AddCommand`, `RenameCommand`, `DeleteCommand`
    - Validates inputs (non-empty Name, valid URL format, non-empty ClientId)

    **SettingsViewModel.cs** (inherits FeatureViewModelBase):
    - `string SelectedLanguage` bound to language ComboBox
    - `string DataFolder` bound to folder TextBox
    - `BrowseFolderCommand` opens FolderBrowserDialog
    - On language change: updates `TranslationSource.Instance.CurrentCulture` + calls `SettingsService.SetLanguageAsync`
    - `RunOperationAsync`: not applicable — stub throws `NotSupportedException` (Settings tab has no long-running operation)

    **MainWindow.xaml** — Full shell layout as locked in CONTEXT.md.

    StatusBar middle item MUST bind to `ProgressStatus` (live operation text from ProgressUpdatedMessage),
    NOT `ConnectionStatus`. Per locked CONTEXT.md: "operation status text" means the live progress text.

    The 7 stub feature tabs MUST use `<controls:FeatureTabBase />` as their Content,
    NOT bare TextBlocks. This gives Phase 2 a XAML template to extend.

    ```xml
    <Window Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[app.title]}"
            MinWidth="900" MinHeight="600">
      <DockPanel>
        <!-- Toolbar -->
        <ToolBar DockPanel.Dock="Top">
          <ComboBox Width="220" ItemsSource="{Binding TenantProfiles}"
                    SelectedItem="{Binding SelectedProfile}"
                    DisplayMemberPath="Name" />
          <Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[toolbar.connect]}"
                  Command="{Binding ConnectCommand}" />
          <Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[toolbar.manage]}"
                  Command="{Binding ManageProfilesCommand}" />
          <Separator />
          <Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[toolbar.clear]}"
                  Command="{Binding ClearSessionCommand}" />
        </ToolBar>

        <!-- StatusBar: three fields per locked layout decision.
             Middle field binds to ProgressStatus (live operation text), NOT ConnectionStatus. -->
        <StatusBar DockPanel.Dock="Bottom" Height="24">
          <StatusBarItem Content="{Binding SelectedProfile.Name}" />
          <Separator />
          <StatusBarItem Content="{Binding ProgressStatus}" />
          <Separator />
          <StatusBarItem Content="{Binding ProgressPercentage, StringFormat={}{0}%}" />
        </StatusBar>

        <!-- Log Panel -->
        <RichTextBox x:Name="LogPanel" DockPanel.Dock="Bottom" Height="150"
                     IsReadOnly="True" VerticalScrollBarVisibility="Auto"
                     Background="Black" Foreground="LimeGreen"
                     FontFamily="Consolas" FontSize="11" />

        <!-- TabControl: 7 stub tabs use FeatureTabBase; Settings tab wired in plan 01-07 -->
        <TabControl>
          <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.permissions]}">
            <controls:FeatureTabBase />
          </TabItem>
          <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.storage]}">
            <controls:FeatureTabBase />
          </TabItem>
          <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.filesearch]}">
            <controls:FeatureTabBase />
          </TabItem>
          <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.duplicates]}">
            <controls:FeatureTabBase />
          </TabItem>
          <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.templates]}">
            <controls:FeatureTabBase />
          </TabItem>
          <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.bulk]}">
            <controls:FeatureTabBase />
          </TabItem>
          <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.folderstructure]}">
            <controls:FeatureTabBase />
          </TabItem>
          <!-- Settings tab: placeholder TextBlock replaced by SettingsView in plan 01-07 -->
          <TabItem x:Name="SettingsTabItem"
                   Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.settings]}">
            <TextBlock Text="Settings (plan 01-07)" HorizontalAlignment="Center" VerticalAlignment="Center" />
          </TabItem>
        </TabControl>
      </DockPanel>
    </Window>
    ```

    Add namespace in Window opening tag:
    `xmlns:controls="clr-namespace:SharepointToolbox.Views.Controls"`

    **MainWindow.xaml.cs**: Constructor receives `MainWindowViewModel` via DI constructor injection. Sets `DataContext = viewModel`. Calls `viewModel.LoadProfilesAsync()` in `Loaded` event.

    **App.xaml.cs** — Update RegisterServices:
    ```csharp
    services.AddSingleton<MsalClientFactory>();
    services.AddSingleton<SessionManager>();
    services.AddSingleton<ProfileService>();
    services.AddSingleton<SettingsService>();
    services.AddSingleton<MainWindowViewModel>();
    services.AddTransient<ProfileManagementViewModel>();
    services.AddTransient<SettingsViewModel>();
    services.AddSingleton<MainWindow>();
    ```

    Wire LogPanelSink AFTER MainWindow is resolved (it needs the RichTextBox reference):
    ```csharp
    host.Start();
    App app = new();
    app.InitializeComponent();
    var mainWindow = host.Services.GetRequiredService<MainWindow>();

    // Wire LogPanelSink now that we have the RichTextBox
    Log.Logger = new LoggerConfiguration()
        .WriteTo.File(/* rolling file path */)
        .WriteTo.Sink(new LogPanelSink(mainWindow.LogPanel))
        .CreateLogger();

    app.MainWindow = mainWindow;
    app.MainWindow.Visibility = Visibility.Visible;
    ```

    **Global exception handlers** in App.xaml.cs (after app created):
    ```csharp
    app.DispatcherUnhandledException += (s, e) =>
    {
        Log.Fatal(e.Exception, "Unhandled UI exception");
        MessageBox.Show(
            $"A fatal error occurred:\n{e.Exception.Message}\n\nCheck log for details.",
            "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error);
        e.Handled = true;
    };
    TaskScheduler.UnobservedTaskException += (s, e) =>
    {
        Log.Fatal(e.Exception, "Unobserved task exception");
        e.SetObserved();
    };
    ```

    Run `dotnet build SharepointToolbox/SharepointToolbox.csproj` — fix any XAML or CS compilation errors.
  </action>
  <verify>
    <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj 2>&1 | tail -5</automated>
  </verify>
  <done>Build succeeds with 0 errors. MainWindow.xaml contains RichTextBox x:Name="LogPanel". StatusBar middle StatusBarItem binds to ProgressStatus (not ConnectionStatus). All 7 stub feature TabItems contain &lt;controls:FeatureTabBase /&gt; (not bare TextBlocks). Settings TabItem has x:Name="SettingsTabItem". All 8 tab headers use TranslationSource bindings. Global exception handlers registered in App.xaml.cs.</done>
</task>

</tasks>

<verification>
- `dotnet build SharepointToolbox.sln` passes with 0 errors
- `dotnet test --filter "Category=Unit"` all pass
- MainWindow.xaml contains `x:Name="LogPanel"` RichTextBox
- MainWindow.xaml StatusBar middle StatusBarItem binds to `ProgressStatus` (live operation text)
- MainWindow.xaml 7 stub TabItems contain `controls:FeatureTabBase` (not TextBlocks)
- FeatureTabBase.xaml contains ProgressBar + TextBlock + Button with Visibility bound to IsRunning
- App.xaml.cs registers `DispatcherUnhandledException` and `TaskScheduler.UnobservedTaskException`
- FeatureViewModelBase contains no `async void` methods (anti-pattern violation)
- ObservableCollection is never modified from Task.Run (pattern 7 compliance)
- MainWindowViewModel.OnActivated() subscribes to ProgressUpdatedMessage and updates ProgressStatus + ProgressPercentage
</verification>

<success_criteria>
Application compiles and launches to a visible WPF shell. FeatureViewModelBase tests green. All ViewModels registered in DI. Log panel wired to Serilog. StatusBar middle field shows live operation status text (ProgressStatus). All 7 stub feature tabs include the progress/cancel strip template via FeatureTabBase.
</success_criteria>

<output>
After completion, create `.planning/phases/01-foundation/01-06-SUMMARY.md`
</output>
