Plans cover plain-language permission labels, risk-level color coding, summary counts, detail-level toggle, export integration, and unit tests. PermissionEntry record is NOT modified — uses wrapper pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
23 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 08-simplified-permissions | 03 | execute | 3 |
|
|
true |
|
|
Purpose: This is the visual layer for SIMP-01 (plain labels), SIMP-02 (color-coded summary), and SIMP-03 (detail level toggle). Binds to ViewModel properties created in 08-02. Output: Updated PermissionsView.xaml
<execution_context> @C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/dev/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/08-simplified-permissions/08-01-SUMMARY.md @.planning/phases/08-simplified-permissions/08-02-SUMMARY.md From PermissionsViewModel (updated): ```csharp // New toggle properties [ObservableProperty] private bool _isSimplifiedMode; [ObservableProperty] private bool _isDetailView = true;// Computed collections public IReadOnlyList SimplifiedResults { get; } public IReadOnlyList Summaries { get; } public object ActiveItemsSource { get; } // Switches between Results and SimplifiedResults
// Existing (unchanged) public ObservableCollection Results { get; }
From SimplifiedPermissionEntry:
```csharp
public string ObjectType { get; }
public string Title { get; }
public string Url { get; }
public bool HasUniquePermissions { get; }
public string Users { get; }
public string PermissionLevels { get; } // Raw role names
public string SimplifiedLabels { get; } // Plain-language labels
public RiskLevel RiskLevel { get; } // High/Medium/Low/ReadOnly
public string GrantedThrough { get; }
public string PrincipalType { get; }
From PermissionSummary:
public record PermissionSummary(string Label, RiskLevel RiskLevel, int Count, int DistinctUsers);
From RiskLevel:
public enum RiskLevel { High, Medium, Low, ReadOnly }
1. Added `xmlns:models` namespace for RiskLevel enum reference in DataTriggers
2. Added "Display Options" GroupBox in left panel with Simplified Mode toggle and Detail Level radio buttons
3. Added summary panel (ItemsControl bound to Summaries) between left panel and DataGrid
4. DataGrid now binds to `ActiveItemsSource` instead of `Results`
5. Added "Simplified Labels" column visible only in simplified mode (via DataTrigger on Visibility)
6. Permission Levels column cells are color-coded by RiskLevel using DataTrigger
7. DataGrid visibility controlled by IsDetailView when in simplified mode
8. Summary panel visibility controlled by IsSimplifiedMode
```xml
<UserControl x:Class="SharepointToolbox.Views.Tabs.PermissionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:SharepointToolbox.Localization"
xmlns:models="clr-namespace:SharepointToolbox.Core.Models">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="290" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Left panel: Scan configuration -->
<DockPanel Grid.Column="0" Grid.Row="0" Margin="8">
<!-- Scan Options GroupBox -->
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[grp.scan.opts]}"
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
<StackPanel>
<!-- Site URL -->
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[perm.site.url]}"
Margin="0,0,0,2" />
<TextBox Text="{Binding SiteUrl, UpdateSourceTrigger=PropertyChanged}"
Margin="0,0,0,6" />
<!-- View Sites + selected label -->
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[perm.or.select]}"
Margin="0,0,0,2" />
<Button Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.view.sites]}"
Command="{Binding OpenSitePickerCommand}"
HorizontalAlignment="Left" Padding="8,2" Margin="0,0,0,4" />
<TextBlock Text="{Binding SitesSelectedLabel}"
FontStyle="Italic" Foreground="Gray" Margin="0,0,0,8" />
<!-- Checkboxes -->
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.scan.folders]}"
IsChecked="{Binding ScanFolders}" Margin="0,0,0,4" />
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.inherited.perms]}"
IsChecked="{Binding IncludeInherited}" Margin="0,0,0,4" />
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.recursive]}"
IsChecked="{Binding IncludeSubsites}" Margin="0,0,0,8" />
<!-- Folder depth -->
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[lbl.folder.depth]}"
Margin="0,0,0,2" />
<TextBox Text="{Binding FolderDepth, UpdateSourceTrigger=PropertyChanged}"
Width="60" HorizontalAlignment="Left" Margin="0,0,0,4" />
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.max.depth]}"
IsChecked="{Binding IsMaxDepth, Mode=TwoWay}"
Margin="0,0,0,0" />
</StackPanel>
</GroupBox>
<!-- Display Options GroupBox (NEW for Phase 8) -->
<GroupBox Header="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[grp.display.opts]}"
DockPanel.Dock="Top" Margin="0,0,0,8" Padding="8">
<StackPanel>
<!-- Simplified Mode toggle -->
<CheckBox Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[chk.simplified.mode]}"
IsChecked="{Binding IsSimplifiedMode}" Margin="0,0,0,8" />
<!-- Detail Level radio buttons (only enabled when simplified mode is on) -->
<TextBlock Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[lbl.detail.level]}"
Margin="0,0,0,4"
IsEnabled="{Binding IsSimplifiedMode}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSimplifiedMode}" Value="False">
<Setter Property="Foreground" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<RadioButton Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[rad.detail.simple]}"
IsChecked="{Binding IsDetailView, Converter={StaticResource InvertBoolConverter}, Mode=TwoWay}"
IsEnabled="{Binding IsSimplifiedMode}"
Margin="0,0,0,2" />
<RadioButton Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[rad.detail.detailed]}"
IsChecked="{Binding IsDetailView}"
IsEnabled="{Binding IsSimplifiedMode}"
Margin="0,0,0,0" />
</StackPanel>
</GroupBox>
<!-- Action buttons -->
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.gen.perms]}"
Command="{Binding RunCommand}"
Margin="0,0,4,4" Padding="6,3" />
<Button Grid.Column="1"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[btn.cancel]}"
Command="{Binding CancelCommand}"
Margin="0,0,0,4" Padding="6,3" />
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[rad.csv.perms]}"
Command="{Binding ExportCsvCommand}"
Margin="0,0,4,0" Padding="6,3" />
<Button Grid.Column="1"
Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[rad.html.perms]}"
Command="{Binding ExportHtmlCommand}"
Margin="0,0,0,0" Padding="6,3" />
</Grid>
</StackPanel>
</DockPanel>
<!-- Right panel: Summary + Results -->
<Grid Grid.Column="1" Grid.Row="0" Margin="0,8,8,8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Summary panel (visible only in simplified mode) -->
<ItemsControl Grid.Row="0" ItemsSource="{Binding Summaries}"
Margin="0,0,0,8">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSimplifiedMode}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border CornerRadius="6" Padding="14,10" Margin="0,0,10,4" MinWidth="140">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#F3F4F6" />
<Style.Triggers>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.High}">
<Setter Property="Background" Value="#FEE2E2" />
<Setter Property="BorderBrush" Value="#FECACA" />
<Setter Property="BorderThickness" Value="1" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.Medium}">
<Setter Property="Background" Value="#FEF3C7" />
<Setter Property="BorderBrush" Value="#FDE68A" />
<Setter Property="BorderThickness" Value="1" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.Low}">
<Setter Property="Background" Value="#D1FAE5" />
<Setter Property="BorderBrush" Value="#A7F3D0" />
<Setter Property="BorderThickness" Value="1" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.ReadOnly}">
<Setter Property="Background" Value="#DBEAFE" />
<Setter Property="BorderBrush" Value="#BFDBFE" />
<Setter Property="BorderThickness" Value="1" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<TextBlock Text="{Binding Count}" FontSize="22" FontWeight="Bold" />
<TextBlock Text="{Binding Label}" FontSize="11" Foreground="#555" />
<TextBlock FontSize="10" Foreground="#888" Margin="0,2,0,0">
<Run Text="{Binding DistinctUsers, Mode=OneWay}" />
<Run Text=" user(s)" />
</TextBlock>
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Results DataGrid -->
<DataGrid Grid.Row="1"
ItemsSource="{Binding ActiveItemsSource}"
AutoGenerateColumns="False"
IsReadOnly="True"
VirtualizingPanel.IsVirtualizing="True"
EnableRowVirtualization="True">
<DataGrid.Style>
<Style TargetType="DataGrid">
<Style.Triggers>
<!-- Hide DataGrid when simplified mode is on but detail view is off -->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSimplifiedMode}" Value="True" />
<Condition Binding="{Binding IsDetailView}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Collapsed" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<!-- Row style: color-code by RiskLevel when in simplified mode -->
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.High}">
<Setter Property="Background" Value="#FEF2F2" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.Medium}">
<Setter Property="Background" Value="#FFFBEB" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.Low}">
<Setter Property="Background" Value="#ECFDF5" />
</DataTrigger>
<DataTrigger Binding="{Binding RiskLevel}" Value="{x:Static models:RiskLevel.ReadOnly}">
<Setter Property="Background" Value="#EFF6FF" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Object Type" Binding="{Binding ObjectType}" Width="100" />
<DataGridTextColumn Header="Title" Binding="{Binding Title}" Width="140" />
<DataGridTextColumn Header="URL" Binding="{Binding Url}" Width="200" />
<DataGridTextColumn Header="Unique Perms" Binding="{Binding HasUniquePermissions}" Width="90" />
<DataGridTextColumn Header="Users" Binding="{Binding Users}" Width="140" />
<DataGridTextColumn Header="Permission Levels" Binding="{Binding PermissionLevels}" Width="140" />
<!-- Simplified Labels column (only visible in simplified mode) -->
<DataGridTextColumn Header="Simplified" Binding="{Binding SimplifiedLabels}" Width="200">
<DataGridTextColumn.Visibility>
<Binding Path="DataContext.IsSimplifiedMode"
RelativeSource="{RelativeSource AncestorType=DataGrid}"
Converter="{StaticResource BoolToVis}" />
</DataGridTextColumn.Visibility>
</DataGridTextColumn>
<DataGridTextColumn Header="Granted Through" Binding="{Binding GrantedThrough}" Width="140" />
<DataGridTextColumn Header="Principal Type" Binding="{Binding PrincipalType}" Width="110" />
</DataGrid.Columns>
</DataGrid>
</Grid>
<!-- Bottom: status bar spanning both columns -->
<StatusBar Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1">
<StatusBarItem>
<ProgressBar Width="150" Height="14"
Value="{Binding ProgressValue}"
Minimum="0" Maximum="100" />
</StatusBarItem>
<StatusBarItem Content="{Binding StatusMessage}" />
</StatusBar>
</Grid>
</UserControl>
```
IMPORTANT implementation notes:
1. **InvertBoolConverter** — The "Simple" radio button needs an inverted bool converter to bind to `IsDetailView` (Simple = !IsDetailView). Add this converter to the UserControl.Resources:
```xml
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<local:InvertBoolConverter x:Key="InvertBoolConverter" />
</UserControl.Resources>
```
You will need to create a simple `InvertBoolConverter` class. Add it as a nested helper or in a new file `SharepointToolbox/Core/Converters/InvertBoolConverter.cs`:
```csharp
using System.Globalization;
using System.Windows.Data;
namespace SharepointToolbox.Core.Converters;
/// <summary>
/// Inverts a boolean value. Used for radio button binding where
/// one option is the inverse of the bound property.
/// </summary>
[ValueConversion(typeof(bool), typeof(bool))]
public class InvertBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool b ? !b : value;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value is bool b ? !b : value;
}
```
Add the namespace to the XAML header:
```xml
xmlns:converters="clr-namespace:SharepointToolbox.Core.Converters"
```
And update the resource to use `converters:InvertBoolConverter`.
2. **Row color DataTriggers** — The RiskLevel-based row coloring only takes effect when ActiveItemsSource contains SimplifiedPermissionEntry objects (which have RiskLevel). When binding to raw PermissionEntry (simplified mode off), the triggers simply don't match and rows use default background.
3. **SimplifiedLabels column** — Uses BooleanToVisibilityConverter bound to the DataGrid's DataContext.IsSimplifiedMode. When simplified mode is off, the column is Collapsed.
4. **Summary card "user(s)" text** — Uses `<Run>` elements inside TextBlock for inline binding. The hardcoded "user(s)" text will be replaced with a localization key in plan 08-05.
5. **DataGrid hides when simplified + not detailed** — MultiDataTrigger on IsSimplifiedMode=True AND IsDetailView=False collapses the DataGrid, showing only the summary cards.
cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5
PermissionsView.xaml has: Display Options GroupBox with Simplified Mode checkbox and Simple/Detailed radio buttons. Summary panel with 4 risk-level cards (color-coded). DataGrid binds to ActiveItemsSource with RiskLevel-based row colors. Simplified Labels column appears only in simplified mode. DataGrid hides in Simple mode. InvertBoolConverter created.
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
- PermissionsView.xaml contains bindings for IsSimplifiedMode, IsDetailView, ActiveItemsSource, Summaries
- InvertBoolConverter.cs exists and compiles
- Summary panel uses DataTrigger on RiskLevel for color coding
- DataGrid row style uses DataTrigger on RiskLevel for row background colors
- SimplifiedLabels column visibility bound to IsSimplifiedMode via BoolToVis converter
<success_criteria> The permissions tab visually supports all three SIMP requirements: simplified labels appear alongside raw names (SIMP-01), summary cards show color-coded counts by risk level (SIMP-02), and the Simple/Detailed toggle controls row visibility without re-scanning (SIMP-03). Ready for export integration (08-04) and localization (08-05). </success_criteria>
After completion, create `.planning/phases/08-simplified-permissions/08-03-SUMMARY.md`