fix(01-foundation): revise plans based on checker feedback

- 01-04: wave 3 → 4 (01-03 is also wave 3; parallel executor would race)
- 01-06: wave 4 → 5 (cascades from 01-04 fix); add FeatureTabBase UserControl
  for per-tab progress/cancel strip; bind StatusBar middle item to ProgressStatus
  instead of ConnectionStatus per locked CONTEXT.md decision
- 01-07: wave 5 → 6 (cascades)
- 01-08: wave 6 → 7 (cascades)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Dev
2026-04-02 11:53:41 +02:00
parent eeb9a3bcd1
commit b4a901e52a
4 changed files with 120 additions and 25 deletions

View File

@@ -2,7 +2,7 @@
phase: 01-foundation phase: 01-foundation
plan: 04 plan: 04
type: execute type: execute
wave: 3 wave: 4
depends_on: depends_on:
- 01-02 - 01-02
- 01-03 - 01-03

View File

@@ -2,7 +2,7 @@
phase: 01-foundation phase: 01-foundation
plan: 06 plan: 06
type: execute type: execute
wave: 4 wave: 5
depends_on: depends_on:
- 01-03 - 01-03
- 01-04 - 01-04
@@ -13,6 +13,8 @@ files_modified:
- SharepointToolbox/ViewModels/MainWindowViewModel.cs - SharepointToolbox/ViewModels/MainWindowViewModel.cs
- SharepointToolbox/ViewModels/ProfileManagementViewModel.cs - SharepointToolbox/ViewModels/ProfileManagementViewModel.cs
- SharepointToolbox/ViewModels/Tabs/SettingsViewModel.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
- SharepointToolbox/Views/MainWindow.xaml.cs - SharepointToolbox/Views/MainWindow.xaml.cs
- SharepointToolbox/App.xaml.cs - SharepointToolbox/App.xaml.cs
@@ -31,6 +33,8 @@ must_haves:
- "Global exception handlers (DispatcherUnhandledException + TaskScheduler.UnobservedTaskException) funnel to log panel + MessageBox" - "Global exception handlers (DispatcherUnhandledException + TaskScheduler.UnobservedTaskException) funnel to log panel + MessageBox"
- "LogPanelSink wired to MainWindow RichTextBox after Generic Host starts" - "LogPanelSink wired to MainWindow RichTextBox after Generic Host starts"
- "FeatureViewModelBaseTests: progress reporting, cancellation, and error handling all green" - "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: artifacts:
- path: "SharepointToolbox/ViewModels/FeatureViewModelBase.cs" - path: "SharepointToolbox/ViewModels/FeatureViewModelBase.cs"
provides: "Base class for all feature ViewModels with canonical async command pattern" provides: "Base class for all feature ViewModels with canonical async command pattern"
@@ -38,6 +42,9 @@ must_haves:
- path: "SharepointToolbox/ViewModels/MainWindowViewModel.cs" - path: "SharepointToolbox/ViewModels/MainWindowViewModel.cs"
provides: "Shell ViewModel with TenantProfiles and connection state" provides: "Shell ViewModel with TenantProfiles and connection state"
contains: "ObservableCollection" contains: "ObservableCollection"
- path: "SharepointToolbox/Views/Controls/FeatureTabBase.xaml"
provides: "Reusable UserControl with ProgressBar + TextBlock + Cancel button strip"
contains: "ProgressBar"
- path: "SharepointToolbox/Views/MainWindow.xaml" - path: "SharepointToolbox/Views/MainWindow.xaml"
provides: "WPF shell with toolbar, TabControl, log panel, StatusBar" provides: "WPF shell with toolbar, TabControl, log panel, StatusBar"
contains: "RichTextBox" contains: "RichTextBox"
@@ -56,15 +63,23 @@ must_haves:
pattern: "TenantSwitchedMessage" pattern: "TenantSwitchedMessage"
- from: "SharepointToolbox/ViewModels/MainWindowViewModel.cs" - from: "SharepointToolbox/ViewModels/MainWindowViewModel.cs"
to: "SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs" to: "SharepointToolbox/Core/Messages/ProgressUpdatedMessage.cs"
via: "Messenger.Register<ProgressUpdatedMessage> in OnActivated — updates StatusBar observable properties" via: "Messenger.Register<ProgressUpdatedMessage> in OnActivated — updates ProgressStatus + ProgressPercentage"
pattern: "ProgressUpdatedMessage" 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> <objective>
Build the WPF shell — MainWindow XAML + all ViewModels. Wire LogPanelSink to the RichTextBox. Implement FeatureViewModelBase with the canonical async pattern. Register global exception handlers. 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. 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, log panel, and status bar. Output: Runnable WPF application showing the shell with placeholder tabs (using FeatureTabBase), log panel, and status bar with live operation text.
</objective> </objective>
<execution_context> <execution_context>
@@ -109,7 +124,8 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
// Toolbar (L→R): ComboBox (220px) → Button "Connect" → Button "Manage Profiles..." → separator → Button "Clear Session" // 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) // TabControl: 8 tabs (Permissions, Storage, File Search, Duplicates, Templates, Bulk Operations, Folder Structure, Settings)
// Log panel: RichTextBox, 150px tall, always visible, x:Name="LogPanel" // Log panel: RichTextBox, 150px tall, always visible, x:Name="LogPanel"
// StatusBar: tenant name | operation status | progress % // StatusBar: tenant name | operation status text | progress %
// Per-tab layout: ProgressBar + TextBlock + Button "Cancel" — shown only when IsRunning (CONTEXT.md Gray Areas, locked)
</interfaces> </interfaces>
</context> </context>
@@ -238,8 +254,10 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
</task> </task>
<task type="auto"> <task type="auto">
<name>Task 2: MainWindowViewModel, shell ViewModels, and MainWindow XAML</name> <name>Task 2: FeatureTabBase UserControl, MainWindowViewModel, shell ViewModels, and MainWindow XAML</name>
<files> <files>
SharepointToolbox/Views/Controls/FeatureTabBase.xaml,
SharepointToolbox/Views/Controls/FeatureTabBase.xaml.cs,
SharepointToolbox/ViewModels/MainWindowViewModel.cs, SharepointToolbox/ViewModels/MainWindowViewModel.cs,
SharepointToolbox/ViewModels/ProfileManagementViewModel.cs, SharepointToolbox/ViewModels/ProfileManagementViewModel.cs,
SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs, SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs,
@@ -248,7 +266,53 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
SharepointToolbox/App.xaml.cs SharepointToolbox/App.xaml.cs
</files> </files>
<action> <action>
Create `ViewModels/Tabs/` and `Views/` directories. 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**: **MainWindowViewModel.cs**:
```csharp ```csharp
@@ -294,7 +358,13 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
- `RunOperationAsync`: not applicable — stub throws `NotSupportedException` (Settings tab has no long-running operation) - `RunOperationAsync`: not applicable — stub throws `NotSupportedException` (Settings tab has no long-running operation)
**MainWindow.xaml** — Full shell layout as locked in CONTEXT.md. **MainWindow.xaml** — Full shell layout as locked in CONTEXT.md.
StatusBar MUST have three fields per the locked decision (tenant name | operation status text | progress percentage):
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 ```xml
<Window Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[app.title]}" <Window Title="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[app.title]}"
MinWidth="900" MinHeight="600"> MinWidth="900" MinHeight="600">
@@ -313,11 +383,12 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
Command="{Binding ClearSessionCommand}" /> Command="{Binding ClearSessionCommand}" />
</ToolBar> </ToolBar>
<!-- StatusBar: three fields per locked layout decision --> <!-- StatusBar: three fields per locked layout decision.
Middle field binds to ProgressStatus (live operation text), NOT ConnectionStatus. -->
<StatusBar DockPanel.Dock="Bottom" Height="24"> <StatusBar DockPanel.Dock="Bottom" Height="24">
<StatusBarItem Content="{Binding SelectedProfile.Name}" /> <StatusBarItem Content="{Binding SelectedProfile.Name}" />
<Separator /> <Separator />
<StatusBarItem Content="{Binding ConnectionStatus}" /> <StatusBarItem Content="{Binding ProgressStatus}" />
<Separator /> <Separator />
<StatusBarItem Content="{Binding ProgressPercentage, StringFormat={}{0}%}" /> <StatusBarItem Content="{Binding ProgressPercentage, StringFormat={}{0}%}" />
</StatusBar> </StatusBar>
@@ -328,15 +399,32 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
Background="Black" Foreground="LimeGreen" Background="Black" Foreground="LimeGreen"
FontFamily="Consolas" FontSize="11" /> FontFamily="Consolas" FontSize="11" />
<!-- TabControl --> <!-- TabControl: 7 stub tabs use FeatureTabBase; Settings tab wired in plan 01-07 -->
<TabControl> <TabControl>
<TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.permissions]}"> <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.permissions]}">
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.comingsoon]}" <controls:FeatureTabBase />
HorizontalAlignment="Center" VerticalAlignment="Center" />
</TabItem> </TabItem>
<!-- Repeat for: Storage, File Search, Duplicates, Templates, Bulk Operations, Folder Structure --> <TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.storage]}">
<!-- Settings tab binds to SettingsView (plan 01-07) --> <controls:FeatureTabBase />
<TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.settings]}"> </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" /> <TextBlock Text="Settings (plan 01-07)" HorizontalAlignment="Center" VerticalAlignment="Center" />
</TabItem> </TabItem>
</TabControl> </TabControl>
@@ -344,6 +432,9 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
</Window> </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. **MainWindow.xaml.cs**: Constructor receives `MainWindowViewModel` via DI constructor injection. Sets `DataContext = viewModel`. Calls `viewModel.LoadProfilesAsync()` in `Loaded` event.
**App.xaml.cs** — Update RegisterServices: **App.xaml.cs** — Update RegisterServices:
@@ -397,7 +488,7 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
<verify> <verify>
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj 2>&1 | tail -5</automated> <automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj 2>&1 | tail -5</automated>
</verify> </verify>
<done>Build succeeds with 0 errors. MainWindow.xaml contains RichTextBox x:Name="LogPanel". StatusBar has three StatusBarItems (tenant name, connection status, progress %). All 8 tab headers use TranslationSource bindings. Global exception handlers registered in App.xaml.cs.</done> <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> </task>
</tasks> </tasks>
@@ -406,7 +497,9 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
- `dotnet build SharepointToolbox.sln` passes with 0 errors - `dotnet build SharepointToolbox.sln` passes with 0 errors
- `dotnet test --filter "Category=Unit"` all pass - `dotnet test --filter "Category=Unit"` all pass
- MainWindow.xaml contains `x:Name="LogPanel"` RichTextBox - MainWindow.xaml contains `x:Name="LogPanel"` RichTextBox
- MainWindow.xaml StatusBar has three StatusBarItems: SelectedProfile.Name | ConnectionStatus | ProgressPercentage% - 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` - App.xaml.cs registers `DispatcherUnhandledException` and `TaskScheduler.UnobservedTaskException`
- FeatureViewModelBase contains no `async void` methods (anti-pattern violation) - FeatureViewModelBase contains no `async void` methods (anti-pattern violation)
- ObservableCollection is never modified from Task.Run (pattern 7 compliance) - ObservableCollection is never modified from Task.Run (pattern 7 compliance)
@@ -414,7 +507,7 @@ public sealed class LanguageChangedMessage : ValueChangedMessage<string>
</verification> </verification>
<success_criteria> <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 shows all three fields including live progress percentage. 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> </success_criteria>
<output> <output>

View File

@@ -2,7 +2,7 @@
phase: 01-foundation phase: 01-foundation
plan: 07 plan: 07
type: execute type: execute
wave: 5 wave: 6
depends_on: depends_on:
- 01-06 - 01-06
files_modified: files_modified:
@@ -242,7 +242,7 @@ public class SettingsViewModel : FeatureViewModelBase
```csharp ```csharp
SettingsTabItem.Content = serviceProvider.GetRequiredService<SettingsView>(); SettingsTabItem.Content = serviceProvider.GetRequiredService<SettingsView>();
``` ```
Add `x:Name="SettingsTabItem"` to the Settings TabItem in XAML. The Settings TabItem already has `x:Name="SettingsTabItem"` from plan 01-06.
Run `dotnet build` and fix any errors. Run `dotnet build` and fix any errors.
</action> </action>

View File

@@ -2,7 +2,7 @@
phase: 01-foundation phase: 01-foundation
plan: 08 plan: 08
type: execute type: execute
wave: 6 wave: 7
depends_on: depends_on:
- 01-07 - 01-07
files_modified: [] files_modified: []
@@ -100,6 +100,8 @@ Output: Confirmed working foundation. Green light for Phase 2.
- Serilog rolling file log + LogPanelSink writing to in-app RichTextBox - Serilog rolling file log + LogPanelSink writing to in-app RichTextBox
- Global exception handlers wired - Global exception handlers wired
- All infrastructure patterns in place (pagination helper, retry helper, FeatureViewModelBase) - All infrastructure patterns in place (pagination helper, retry helper, FeatureViewModelBase)
- Per-tab FeatureTabBase UserControl with ProgressBar + Cancel strip (shown only when IsRunning)
- StatusBar middle field shows live operation status text (ProgressStatus)
</what-built> </what-built>
<how-to-verify> <how-to-verify>
Run the application: `dotnet run --project SharepointToolbox/SharepointToolbox.csproj` Run the application: `dotnet run --project SharepointToolbox/SharepointToolbox.csproj`
@@ -147,7 +149,7 @@ Output: Confirmed working foundation. Green light for Phase 2.
All Phase 1 ROADMAP success criteria met: All Phase 1 ROADMAP success criteria met:
1. User can create, rename, delete, and switch between tenant profiles via the UI 1. User can create, rename, delete, and switch between tenant profiles via the UI
2. MSAL token cache infrastructure ready (interactive login requires a real Azure AD tenant — not testable in this checkpoint) 2. MSAL token cache infrastructure ready (interactive login requires a real Azure AD tenant — not testable in this checkpoint)
3. Per-tab progress bar + cancel button infrastructure built (no long-running ops in Phase 1 to demo, but FeatureViewModelBase tests prove the pattern) 3. Per-tab progress bar + cancel button infrastructure built (FeatureTabBase UserControl wired in all 7 stub tabs; FeatureViewModelBase tests prove the pattern)
4. Log panel surfaces errors in red; global exception handlers registered 4. Log panel surfaces errors in red; global exception handlers registered
5. Language switches between EN and FR dynamically without restart 5. Language switches between EN and FR dynamically without restart
</verification> </verification>