- SUMMARY.md with task commits and decisions
- STATE.md updated to plan 4 of 6
- ROADMAP.md progress updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add RiskLevelColors helper for risk-level color coding
- Add BuildHtml(IReadOnlyList<SimplifiedPermissionEntry>) with risk summary cards, Simplified column, and color-coded Risk badges
- Add WriteAsync overload for simplified entries
- Original PermissionEntry methods unchanged
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Display Options GroupBox with Simplified Mode toggle and Simple/Detailed radio buttons
- Add summary panel with color-coded risk level cards bound to Summaries collection
- DataGrid binds to ActiveItemsSource, rows color-coded by RiskLevel via DataTriggers
- SimplifiedLabels column visible only in simplified mode via BooleanToVisibilityConverter
- DataGrid collapses in Simple mode via MultiDataTrigger on IsSimplifiedMode+IsDetailView
- Create InvertBoolConverter for radio button inverse binding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add BuildCsv(IReadOnlyList<SimplifiedPermissionEntry>) overload with SimplifiedLabels and RiskLevel columns
- Add WriteAsync overload for simplified entries
- Original PermissionEntry methods unchanged
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- IsSimplifiedMode toggle switches between raw and simplified labels
- IsDetailView toggle controls individual vs summary row display
- SimplifiedResults and Summaries computed from cached Results
- ActiveItemsSource provides correct collection for DataGrid binding
- Mode toggles rebuild from cache without re-running scan
- OnTenantSwitched resets simplified state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SUMMARY.md with self-check passed
- STATE.md updated to Phase 8, Plan 1 complete
- ROADMAP.md progress updated for Phase 8
- SIMP-01 and SIMP-02 requirements marked complete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- RiskLevel enum with High, Medium, Low, ReadOnly tiers
- PermissionLevelMapping maps 11 standard SharePoint roles to plain-language labels
- Case-insensitive lookup with Medium fallback for unknown roles
- GetHighestRisk and GetSimplifiedLabels for row-level formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
People picker ListBox used MouseBinding which fires before SelectedItem
updates, causing null CommandParameter. Replaced with SelectionChanged
event handler in code-behind.
AuditUsersAsync created TenantProfile with empty ClientId, causing
ArgumentException in SessionManager. Added currentProfile parameter
to pass the authenticated tenant's ClientId through.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extended CreateViewModel helper to return (vm, auditMock, graphMock) 3-tuple
- Updated all 8 existing tests to use _ discard for the new graphMock slot
- Added Test 9: SearchQuery_debounced_calls_SearchUsersAsync verifying that
setting SearchQuery to "Ali" calls SearchUsersAsync after 300ms debounce
- All 9 ViewModel tests pass; full suite 177 passed / 22 skipped
- Convert User column to DataGridTemplateColumn with orange 'Guest' pill badge on IsExternalUser=true
- Add ObjectType DataGridTextColumn between Object and Permission Level
- Convert Permission Level column to DataGridTemplateColumn with red warning icon on IsHighPrivilege=true
- 12 tests: user filtering, claim format matching, Direct/Group/Inherited
access type classification, Full Control + SCA high-privilege detection,
external user flagging (#EXT#), semicolon user/level splitting, multi-site scan
- Add 17 audit.* keys and tab.userAccessAudit to Strings.resx (English)
- Add matching French translations with proper Unicode accented characters to Strings.fr.resx
- Add UserAccessAuditTabItem to MainWindow.xaml TabControl before SettingsTabItem
- Wire UserAccessAuditView content and SitePickerDialog factory in MainWindow.xaml.cs
- Register IUserAccessAuditService, IGraphUserSearchService, export services, ViewModel and View in App.xaml.cs
- Create UserAccessAuditView.xaml with two-panel layout: people picker, site picker, scan options, color-coded DataGrid with grouping, summary banner
- Create UserAccessAuditView.xaml.cs code-behind with ViewModel constructor injection
- [Rule 3] UserAccessAuditView was missing (07-05 not executed); created inline to unblock 07-07
- Two-panel layout (290px left + * right) following PermissionsView pattern
- Left panel: people picker with autocomplete list + removable user pills, site picker button, scan option checkboxes, run/cancel/export buttons
- Right panel: 3-card summary banner (TotalAccessCount, SitesCount, HighPrivilegeCount), filter TextBox, group-by ToggleButton, color-coded DataGrid
- DataGrid: color-coded rows by AccessType (Direct=blue, Group=green, Inherited=gray), warning icon for high privilege, Guest badge for external users, access type icons
- GroupStyle with Expander headers showing group name + item count
- Status bar with ProgressBar + StatusMessage
- BuildHtml produces self-contained HTML with inline CSS and JS
- Stats cards: Total Accesses, Users Audited, Sites Scanned, High Privilege, External Users
- Per-user summary cards with high-privilege border highlight and guest badge
- Dual-view toggle (By User / By Site) with JS toggleView()
- Collapsible group headers per user and per site via toggleGroup()
- Sortable columns via sortTable() within each group
- Text filter via filterTable() scoping to active view
- Color-coded access type badges: Direct (blue), Group (green), Inherited (gray)
- High-privilege rows with bold text and warning icon
- External user guest badge (orange pill)
- UTF-8 without BOM encoding (matching HtmlExportService pattern)
- Scans permissions via IPermissionsService.ScanSiteAsync per site
- Filters PermissionEntry results to matching target user logins (case-insensitive contains)
- Splits semicolon-delimited users/logins/levels into per-user UserAccessEntry rows
- Classifies AccessType: Inherited (!HasUniquePermissions), Group (GrantedThrough), Direct
- Flags IsHighPrivilege (Full Control, Site Collection Administrator) and IsExternalUser (#EXT#)
- BuildCsv per-user CSV with summary section (user, totals, sites, high-privilege, date)
- WriteAsync groups entries by UserLogin, writes one file per user (audit_{email}_{date}.csv)
- WriteSingleFileAsync combines all users in one file for SaveFileDialog export
- RFC 4180 CSV escaping, UTF-8 with BOM for Excel compatibility
- SanitizeFileName strips invalid path chars from email addresses
- IUserAccessAuditService.AuditUsersAsync: scan sites and filter by user logins
- IGraphUserSearchService.SearchUsersAsync: Graph API people-picker autocomplete
- GraphUserResult record: DisplayName, UserPrincipalName, Mail
- UserAccessEntry record with 12 fields for user-centric audit results
- AccessType enum: Direct, Group, Inherited
- Pre-computed IsHighPrivilege and IsExternalUser fields for grid display
All Phase 6 global site selection features verified:
- Toolbar button, site count label, single/multi-site pre-fill
- Transfer pre-fill, local override, clear-reverts, tenant switch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace GetSitePropertiesFromSharePoint("", true) with modern
GetSitePropertiesFromSharePointByFilters using null StartIndex
- Use ctx.Clone(adminUrl) instead of creating new AuthenticationManager
for admin URL, eliminating second browser auth prompt
Resolves: UAT issue "Must specify valid information for parsing in the string"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix two pre-existing blockers found during UAT:
- ProfileManagementViewModel: add NotifyCanExecuteChanged on property changes
- SessionManager: open browser in openBrowserCallback (was no-op)
Remaining blocker: SitePickerDialog parsing error from PnP Framework.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Message broadcast: GlobalSitesChangedMessage carries site list to receivers
- Base class: FeatureViewModelBase.GlobalSites updated on message receive
- Storage tab: SiteUrl pre-filled from first global site
- Storage tab: local override prevents global from overwriting SiteUrl
- Storage tab: clearing SiteUrl reverts to global site (override reset)
- Permissions tab: SelectedSites pre-populated from global sites
- Permissions tab: local picker override blocks subsequent global updates
- Tenant switch: resets local override so new global sites apply cleanly
- Transfer tab: SourceSiteUrl pre-filled from first global site
- MainWindowViewModel: GlobalSitesSelectedLabel reflects site count
- Add 06-04-SUMMARY.md with all task details and self-check
- Update STATE.md: progress bar 80%, decisions, session record
- Update ROADMAP.md: phase 6 now 4/5 plans complete (In Progress)
- Mark SITE-02 complete in REQUIREMENTS.md
- TransferViewModel: add _hasLocalSourceSiteOverride field
- Override OnGlobalSitesChanged to pre-fill SourceSiteUrl from first global site
- Add OnSourceSiteUrlChanged partial to detect local user input
- Reset _hasLocalSourceSiteOverride on tenant switch
- BulkMembersViewModel confirmed excluded: no SiteUrl field, CSV-driven, no OnGlobalSitesChanged override added
- SUMMARY.md created for plan 06-03
- STATE.md updated: progress 60%, decisions logged, session recorded
- ROADMAP.md updated: phase 6 now 3/5 summaries (In Progress)