chore: complete v1.0 milestone
All checks were successful
Release zip package / release (push) Successful in 10s
All checks were successful
Release zip package / release (push) Successful in 10s
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:
271
.planning/milestones/v1.0-phases/01-foundation/01-07-PLAN.md
Normal file
271
.planning/milestones/v1.0-phases/01-foundation/01-07-PLAN.md
Normal file
@@ -0,0 +1,271 @@
|
||||
---
|
||||
phase: 01-foundation
|
||||
plan: 07
|
||||
type: execute
|
||||
wave: 6
|
||||
depends_on:
|
||||
- 01-06
|
||||
files_modified:
|
||||
- SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml
|
||||
- SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml.cs
|
||||
- SharepointToolbox/Views/Tabs/SettingsView.xaml
|
||||
- SharepointToolbox/Views/Tabs/SettingsView.xaml.cs
|
||||
- SharepointToolbox/Views/MainWindow.xaml
|
||||
autonomous: true
|
||||
requirements:
|
||||
- FOUND-02
|
||||
- FOUND-09
|
||||
- FOUND-12
|
||||
must_haves:
|
||||
truths:
|
||||
- "ProfileManagementDialog opens as a modal window from the Manage Profiles button"
|
||||
- "User can add a new profile (Name + Tenant URL + Client ID fields) and it appears in the toolbar ComboBox"
|
||||
- "User can rename and delete existing profiles in the dialog"
|
||||
- "SettingsView has a language ComboBox (English / French) and a data folder TextBox with Browse button"
|
||||
- "Changing language in SettingsView switches the UI language immediately without restart"
|
||||
- "Data folder setting persists to Sharepoint_Settings.json"
|
||||
artifacts:
|
||||
- path: "SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml"
|
||||
provides: "Modal dialog for profile CRUD"
|
||||
contains: "ProfileManagementViewModel"
|
||||
- path: "SharepointToolbox/Views/Tabs/SettingsView.xaml"
|
||||
provides: "Settings tab content with language and folder controls"
|
||||
contains: "TranslationSource"
|
||||
key_links:
|
||||
- from: "SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml"
|
||||
to: "SharepointToolbox/ViewModels/ProfileManagementViewModel.cs"
|
||||
via: "DataContext = viewModel (constructor injected)"
|
||||
pattern: "DataContext"
|
||||
- from: "SharepointToolbox/Views/Tabs/SettingsView.xaml"
|
||||
to: "SharepointToolbox/Localization/TranslationSource.cs"
|
||||
via: "Language ComboBox selection sets TranslationSource.Instance.CurrentCulture"
|
||||
pattern: "TranslationSource"
|
||||
- from: "SharepointToolbox/Views/MainWindow.xaml"
|
||||
to: "SharepointToolbox/Views/Tabs/SettingsView.xaml"
|
||||
via: "Settings TabItem ContentTemplate or direct UserControl reference"
|
||||
pattern: "SettingsView"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Build the two user-facing views completing Phase 1 UX: ProfileManagementDialog (profile CRUD modal) and SettingsView (language + data folder). Wire SettingsView into the MainWindow Settings tab.
|
||||
|
||||
Purpose: These are the last two user-visible pieces before the visual checkpoint. After this plan the application is functional enough for a human to create a tenant profile, connect, and switch language.
|
||||
Output: ProfileManagementDialog + SettingsView wired into the shell.
|
||||
</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-06-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- From ProfileManagementViewModel (plan 01-06) -->
|
||||
```csharp
|
||||
public class ProfileManagementViewModel : ObservableObject
|
||||
{
|
||||
public ObservableCollection<TenantProfile> Profiles { get; }
|
||||
public TenantProfile? SelectedProfile { get; set; }
|
||||
public string NewName { get; set; }
|
||||
public string NewTenantUrl { get; set; }
|
||||
public string NewClientId { get; set; }
|
||||
public IAsyncRelayCommand AddCommand { get; }
|
||||
public IAsyncRelayCommand RenameCommand { get; }
|
||||
public IAsyncRelayCommand DeleteCommand { get; }
|
||||
}
|
||||
```
|
||||
|
||||
<!-- From SettingsViewModel (plan 01-06) -->
|
||||
```csharp
|
||||
public class SettingsViewModel : FeatureViewModelBase
|
||||
{
|
||||
public string SelectedLanguage { get; set; } // "en" or "fr"
|
||||
public string DataFolder { get; set; }
|
||||
public RelayCommand BrowseFolderCommand { get; }
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Locked UI spec from CONTEXT.md -->
|
||||
// ProfileManagementDialog: modal Window, fields: Name + Tenant URL + Client ID
|
||||
// Profile fields: { name, tenantUrl, clientId } — JSON schema
|
||||
// SettingsView: language ComboBox (English/French) + DataFolder TextBox + Browse button
|
||||
// Language switch: immediate, no restart, via TranslationSource.Instance.CurrentCulture
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: ProfileManagementDialog XAML and code-behind</name>
|
||||
<files>
|
||||
SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml,
|
||||
SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml.cs
|
||||
</files>
|
||||
<action>
|
||||
Create `Views/Dialogs/` directory.
|
||||
|
||||
**ProfileManagementDialog.xaml** — modal Window (not UserControl):
|
||||
```xml
|
||||
<Window x:Class="SharepointToolbox.Views.Dialogs.ProfileManagementDialog"
|
||||
Title="Manage Profiles" Width="500" Height="480"
|
||||
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
|
||||
ResizeMode="NoResize">
|
||||
<Grid Margin="12">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <!-- Existing profiles list -->
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" /> <!-- Add/Edit fields -->
|
||||
<RowDefinition Height="Auto" /> <!-- Action buttons -->
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Profile list -->
|
||||
<Label Content="Profiles" Grid.Row="0" />
|
||||
<ListBox Grid.Row="1" Margin="0,0,0,8"
|
||||
ItemsSource="{Binding Profiles}"
|
||||
SelectedItem="{Binding SelectedProfile}"
|
||||
DisplayMemberPath="Name" />
|
||||
|
||||
<!-- Input fields -->
|
||||
<Grid Grid.Row="2" Margin="0,0,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="100" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.name]}"
|
||||
Grid.Row="0" Grid.Column="0" />
|
||||
<TextBox Text="{Binding NewName, UpdateSourceTrigger=PropertyChanged}"
|
||||
Grid.Row="0" Grid.Column="1" Margin="0,2" />
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.url]}"
|
||||
Grid.Row="1" Grid.Column="0" />
|
||||
<TextBox Text="{Binding NewTenantUrl, UpdateSourceTrigger=PropertyChanged}"
|
||||
Grid.Row="1" Grid.Column="1" Margin="0,2" />
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.clientid]}"
|
||||
Grid.Row="2" Grid.Column="0" />
|
||||
<TextBox Text="{Binding NewClientId, UpdateSourceTrigger=PropertyChanged}"
|
||||
Grid.Row="2" Grid.Column="1" Margin="0,2" />
|
||||
</Grid>
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.add]}"
|
||||
Command="{Binding AddCommand}" Width="60" Margin="4,0" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.rename]}"
|
||||
Command="{Binding RenameCommand}" Width="60" Margin="4,0" />
|
||||
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.delete]}"
|
||||
Command="{Binding DeleteCommand}" Width="60" Margin="4,0" />
|
||||
<Button Content="Close" Width="60" Margin="4,0"
|
||||
Click="CloseButton_Click" IsCancel="True" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
```
|
||||
|
||||
**ProfileManagementDialog.xaml.cs**:
|
||||
- Constructor receives `ProfileManagementViewModel` via DI (register as `Transient` in App.xaml.cs — already done in plan 01-06)
|
||||
- Sets `DataContext = viewModel`
|
||||
- `CloseButton_Click`: calls `this.Close()`
|
||||
- `Owner` set by caller (`MainWindowViewModel.ManageProfilesCommand` opens as `new ProfileManagementDialog { Owner = Application.Current.MainWindow }.ShowDialog()`)
|
||||
|
||||
After adding: the Add command in `ProfileManagementViewModel` must also trigger `MainWindowViewModel.TenantProfiles` refresh. Implement by having `ProfileManagementViewModel` accept a callback or raise an event. The simplest approach: `MainWindowViewModel.ManageProfilesCommand` reloads profiles after the dialog closes (dialog is modal — `ShowDialog()` blocks until closed).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj 2>&1 | tail -5</automated>
|
||||
</verify>
|
||||
<done>Build succeeds. ProfileManagementDialog.xaml contains all three input fields (Name, Tenant URL, Client ID). All labels use TranslationSource bindings.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: SettingsView XAML and MainWindow Settings tab wiring</name>
|
||||
<files>
|
||||
SharepointToolbox/Views/Tabs/SettingsView.xaml,
|
||||
SharepointToolbox/Views/Tabs/SettingsView.xaml.cs,
|
||||
SharepointToolbox/Views/MainWindow.xaml
|
||||
</files>
|
||||
<action>
|
||||
Create `Views/Tabs/` directory.
|
||||
|
||||
**SettingsView.xaml** — UserControl (embedded in TabItem):
|
||||
```xml
|
||||
<UserControl x:Class="SharepointToolbox.Views.Tabs.SettingsView">
|
||||
<StackPanel Margin="16">
|
||||
<!-- Language -->
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.language]}" />
|
||||
<ComboBox Width="200" HorizontalAlignment="Left"
|
||||
SelectedValue="{Binding SelectedLanguage}"
|
||||
SelectedValuePath="Tag">
|
||||
<ComboBoxItem Tag="en"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.lang.en]}" />
|
||||
<ComboBoxItem Tag="fr"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.lang.fr]}" />
|
||||
</ComboBox>
|
||||
|
||||
<Separator Margin="0,12" />
|
||||
|
||||
<!-- Data folder -->
|
||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.folder]}" />
|
||||
<DockPanel>
|
||||
<Button DockPanel.Dock="Right"
|
||||
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[settings.browse]}"
|
||||
Command="{Binding BrowseFolderCommand}" Width="80" Margin="8,0,0,0" />
|
||||
<TextBox Text="{Binding DataFolder, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
**SettingsView.xaml.cs**: Constructor receives `SettingsViewModel` via DI. Sets `DataContext = viewModel`. Calls `viewModel.LoadAsync()` in `Loaded` event to populate current settings.
|
||||
|
||||
Add `LoadAsync()` to SettingsViewModel if not present — loads current settings from SettingsService and sets `SelectedLanguage` and `DataFolder` properties.
|
||||
|
||||
**MainWindow.xaml** — Update Settings TabItem to use SettingsView (replace placeholder TextBlock):
|
||||
```xml
|
||||
<TabItem Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[tab.settings]}">
|
||||
<views:SettingsView />
|
||||
</TabItem>
|
||||
```
|
||||
Add namespace: `xmlns:views="clr-namespace:SharepointToolbox.Views.Tabs"`
|
||||
|
||||
Also register `SettingsView` in DI in App.xaml.cs (if not already):
|
||||
```csharp
|
||||
services.AddTransient<SettingsView>();
|
||||
```
|
||||
And resolve it in MainWindow constructor to inject into the Settings TabItem Content, OR use a DataTemplate approach. The simpler approach for Phase 1: resolve `SettingsView` from DI in `MainWindow.xaml.cs` constructor and set it as the TabItem Content directly:
|
||||
```csharp
|
||||
SettingsTabItem.Content = serviceProvider.GetRequiredService<SettingsView>();
|
||||
```
|
||||
The Settings TabItem already has `x:Name="SettingsTabItem"` from plan 01-06.
|
||||
|
||||
Run `dotnet build` and fix any 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. SettingsView.xaml contains language ComboBox with "en"/"fr" options and data folder TextBox with Browse button. MainWindow.xaml Settings tab shows SettingsView (not placeholder TextBlock).</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- `dotnet build SharepointToolbox.sln` passes with 0 errors
|
||||
- `dotnet test --filter "Category=Unit"` still passes (no regressions)
|
||||
- ProfileManagementDialog has all three input fields using TranslationSource keys
|
||||
- SettingsView language ComboBox has Tag="en" and Tag="fr" items
|
||||
- MainWindow Settings TabItem Content is SettingsView (not placeholder)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
All Phase 1 UI is built. Application runs and shows: shell with 8 tabs, log panel, status bar, language switching, profile management dialog, and settings. Ready for the visual checkpoint in plan 01-08.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-foundation/01-07-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user