86 Commits
v2.0 ... v1.1

Author SHA1 Message Date
Dev
fd442f3b4c chore: archive v1.1 Enhanced Reports milestone
Some checks failed
Release SharePoint Toolbox v2 / release (push) Failing after 14s
v1.1 shipped with 4 phases (25 plans), 10/10 requirements complete:
- Global site selection (toolbar picker, all tabs consume)
- User access audit (Graph people-picker, direct/group/inherited)
- Simplified permissions (plain-language labels, risk levels, detail toggle)
- Storage visualization (LiveCharts2 pie/donut + bar charts)

Post-phase polish: centralized site selection (removed per-tab pickers),
claims prefix stripping, StorageMetrics backfill, chart tooltip fix,
summary stats in app + HTML exports.

205 tests passing, 10,484 LOC.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 10:21:02 +02:00
Dev
fa793c5489 docs(phase-09): mark phase complete in roadmap — 4/4 plans executed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:42:50 +02:00
Dev
713cf91d00 docs(09-04): complete StorageViewModel chart unit tests plan
- SUMMARY.md with 7 passing tests documented
- STATE.md updated to plan 4/4, phase 9 complete
- ROADMAP.md phase 09 marked complete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:41:55 +02:00
Dev
712b949eb2 test(09-04): add StorageViewModel chart unit tests
- 7 tests covering chart series from metrics, bar series structure,
  donut/bar toggle, top-10+Other aggregation, no-Other for <=10,
  tenant switch cleanup, and empty data handling
- Added LiveChartsCore.SkiaSharpView.WPF to test project
- Uses reflection to set FileTypeMetrics (private setter) directly

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:40:26 +02:00
Dev
e2321666c6 docs(09-03): complete ViewModel chart properties and View XAML plan summary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:37:20 +02:00
Dev
a8d79a8241 feat(09-03): add chart panel to StorageView with toggle and localization
- Update StorageView.xaml: DataGrid top, GridSplitter, chart panel bottom
- Add PieChart and CartesianChart with MultiDataTrigger visibility
- Add radio buttons for donut/bar chart toggle in left panel
- Create BytesLabelConverter for chart tooltip formatting
- Add stor.chart.* localization keys in EN and FR resx files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:35:35 +02:00
Dev
70048ddcdf feat(09-03): extend StorageViewModel with chart data properties and toggle
- Add IsDonutChart toggle, FileTypeMetrics collection, PieChartSeries, BarChartSeries, BarXAxes, BarYAxes
- Add UpdateChartSeries method with top-10 + Other aggregation
- Call CollectFileTypeMetricsAsync after storage scan in RunOperationAsync
- Clear chart data on tenant switch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:27:54 +02:00
Dev
3ec776ba81 docs(09-02): complete CollectFileTypeMetricsAsync plan
- SUMMARY.md with implementation details and deviation log
- STATE.md updated to plan 2 of 4, 92% progress
- ROADMAP.md and REQUIREMENTS.md updated (VIZZ-02 complete)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:25:25 +02:00
Dev
81e3dcac6d feat(09-02): implement CollectFileTypeMetricsAsync in StorageService
- CamlQuery with RecursiveAll scope enumerates files across all non-hidden document libraries
- Paginated 500-item batches avoid list view threshold issues
- Files grouped by extension (case-insensitive) with summed size and count
- Results returned as IReadOnlyList<FileTypeMetric> sorted by TotalSizeBytes descending
- Existing CollectStorageAsync, LoadFolderNodeAsync, CollectSubfoldersAsync unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:24:09 +02:00
Dev
18fe97f975 docs(09-01): complete LiveCharts2 foundation plan
- Add 09-01-SUMMARY.md with task details and self-check
- Update STATE.md position to Phase 9, Plan 1 of 4
- Update ROADMAP.md and REQUIREMENTS.md progress

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:22:50 +02:00
Dev
39c31dadfa feat(09-01): extend IStorageService with CollectFileTypeMetricsAsync
- Add CollectFileTypeMetricsAsync method signature to IStorageService
- Returns IReadOnlyList<FileTypeMetric> for chart visualization data
- Existing CollectStorageAsync signature unchanged
- CS0535 expected until StorageService implements in Plan 09-02

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:21:02 +02:00
Dev
60cbb977bf feat(09-01): add LiveCharts2 NuGet and FileTypeMetric data model
- Add LiveChartsCore.SkiaSharpView.WPF 2.0.0-rc5.4 package reference
- Create FileTypeMetric record with Extension, TotalSizeBytes, FileCount
- Include DisplayLabel computed property for chart label binding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:20:38 +02:00
Dev
a63a698282 docs(09-storage-visualization): create phase plan — 4 plans in 4 waves
Wave 1: LiveCharts2 NuGet + FileTypeMetric model + IStorageService extension
Wave 2: StorageService file-type enumeration implementation
Wave 3: ViewModel chart properties + View XAML + localization
Wave 4: Unit tests for chart ViewModel behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 15:16:16 +02:00
Dev
666e918810 docs(08-06): complete unit tests for simplified permissions plan
- SUMMARY.md with 17 tests added across 3 test files
- STATE.md updated: Phase 08 complete (6/6 plans)
- ROADMAP.md updated: Phase 08 marked complete

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:22:35 +02:00
Dev
22a51c05ef test(08-06): add simplified mode tests to PermissionsViewModelTests
- IsSimplifiedMode default false, toggle rebuilds SimplifiedResults
- IsDetailView toggle does not re-compute simplified data
- Summaries contains correct risk breakdown after toggle
- Helper method CreateViewModelWithResults for test reuse

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:21:06 +02:00
Dev
0f25fd67f8 test(08-06): add PermissionLevelMapping and PermissionSummaryBuilder unit tests
- 9 tests for PermissionLevelMapping: known roles, unknown fallback, case insensitivity, splitting, risk ranking, labels
- 4 tests for PermissionSummaryBuilder: risk levels, empty input, distinct users, wrapping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:20:12 +02:00
Dev
a8a58f1ffc docs(08-05): complete localization keys and export wiring plan
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:18:37 +02:00
Dev
f503e6c0ca feat(08-05): wire export commands to use simplified overloads
- ExportCsvAsync branches on IsSimplifiedMode to call simplified WriteAsync overload
- ExportHtmlAsync branches on IsSimplifiedMode to call simplified WriteAsync overload
- Standard PermissionEntry export path unchanged when simplified mode is off

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:17:14 +02:00
Dev
60ddcd781f feat(08-05): add EN/FR localization keys for simplified permissions UI
- Add 6 keys to Strings.resx: chk.simplified.mode, grp.display.opts, lbl.detail.level, rad.detail.detailed, rad.detail.simple, lbl.summary.users
- Add matching French translations to Strings.fr.resx with proper XML entities for accented characters
- Wire hardcoded "user(s)" text in PermissionsView.xaml summary cards to lbl.summary.users localization key

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:16:40 +02:00
Dev
1f5aa2b668 docs(08-03): complete Permissions View Simplified Mode UI plan
- Created 08-03-SUMMARY.md with task results and self-check
- Updated STATE.md with metrics and decisions
- Updated ROADMAP.md plan progress for phase 08

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:14:42 +02:00
Dev
12d4932484 docs(08-04): complete export services simplified overloads plan
- 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>
2026-04-07 14:14:26 +02:00
Dev
899ab7d175 feat(08-04): add simplified export overloads to HtmlExportService
- 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>
2026-04-07 14:13:08 +02:00
Dev
163c506e0b feat(08-03): add simplified mode UI to PermissionsView
- 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>
2026-04-07 14:12:57 +02:00
Dev
fe19249f82 feat(08-04): add simplified export overloads to CsvExportService
- 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>
2026-04-07 14:12:18 +02:00
Dev
c970342497 docs(08-02): complete ViewModel Toggle Logic plan summary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:11:08 +02:00
Dev
e2c94bf6d1 feat(08-02): add simplified mode properties to PermissionsViewModel
- 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>
2026-04-07 14:09:57 +02:00
Dev
3c70884022 docs(08-01): complete Permission Data Models and Mapping Layer plan
- 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>
2026-04-07 14:08:03 +02:00
Dev
6609f2a70a feat(08-01): add SimplifiedPermissionEntry wrapper and PermissionSummary model
- SimplifiedPermissionEntry wraps PermissionEntry with computed labels and risk level
- Passthrough properties preserve DataGrid binding compatibility
- PermissionSummary record for grouped risk-level counts
- PermissionSummaryBuilder always returns all 4 risk levels for consistent UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:06:47 +02:00
Dev
f1390eaa1c feat(08-01): add RiskLevel enum and PermissionLevelMapping helper
- 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>
2026-04-07 14:06:17 +02:00
Dev
c871effa87 docs(08-simplified-permissions): create phase plan (6 plans, 5 waves)
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>
2026-04-07 14:00:08 +02:00
Dev
dcdbd8662d docs(phase-07): complete phase execution — human verified and approved
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:45:08 +02:00
Dev
00252fd137 fix(07): fix people picker selection and audit service authentication
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>
2026-04-07 13:44:53 +02:00
Dev
0af73df65c docs(07-10): complete debounced search test plan summary
- Created 07-10-SUMMARY.md documenting gap closure for verification gap 3
- Updated STATE.md: progress 100%, metrics recorded, decision logged, session updated
- Updated ROADMAP.md: Phase 7 complete (10/10 plans, 10/10 summaries)
2026-04-07 13:16:26 +02:00
Dev
d7ff32ee94 docs(07-09): complete DataGrid visual indicators plan summary 2026-04-07 13:15:23 +02:00
Dev
67a2053a94 test(07-10): add debounced search unit test for UserAccessAuditViewModel
- 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
2026-04-07 13:15:16 +02:00
Dev
33833dce5d feat(07-09): add guest badge, warning icon, and ObjectType column to DataGrid
- 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
2026-04-07 13:14:29 +02:00
Dev
855e4df49b docs(07-08): complete unit tests plan summary
- 07-08-SUMMARY.md: 32 tests across 4 files, all passing
- STATE.md: advance plan, record metrics and decisions
- ROADMAP.md: phase 7 complete (8/8 plans)
2026-04-07 13:00:18 +02:00
Dev
35b2c2a109 test(07-08): add export and ViewModel unit tests
- UserAccessCsvExportServiceTests (5): summary section, data header, RFC 4180
  quote escaping, 7-column count, WriteSingleFileAsync multi-user output
- UserAccessHtmlExportServiceTests (7): DOCTYPE, stats cards, dual-view sections,
  access type badges, filterTable JS, toggleView JS, HTML entity encoding
- UserAccessAuditViewModelTests (8): AuditUsersAsync invocation, results population,
  summary properties computation, tenant switch reset, GlobalSitesChanged update,
  override guard, CanExport false/true states
2026-04-07 12:58:58 +02:00
Dev
5df95032ee test(07-08): add UserAccessAuditService unit tests
- 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
2026-04-07 12:57:21 +02:00
Dev
34c1776dcc docs(07-07): complete integration wiring plan summary
- Add 07-07-SUMMARY.md for MainWindow/DI/localization integration
- Update STATE.md: progress 92%, new decisions, session record
- Update ROADMAP.md: phase 7 showing 7/8 summaries
2026-04-07 12:55:02 +02:00
Dev
a2531ea33f feat(07-07): add localization keys for User Access Audit tab in English and French
- Add 17 audit.* keys and tab.userAccessAudit to Strings.resx (English)
- Add matching French translations with proper Unicode accented characters to Strings.fr.resx
2026-04-07 12:53:37 +02:00
Dev
df796ee956 feat(07-07): add UserAccessAuditTabItem to MainWindow and wire dialog factory
- Add UserAccessAuditTabItem to MainWindow.xaml TabControl before SettingsTabItem
- Wire UserAccessAuditView content and SitePickerDialog factory in MainWindow.xaml.cs
2026-04-07 12:53:04 +02:00
Dev
2ed8a0cb12 feat(07-07): add DI registrations for Phase 7 services and create UserAccessAuditView
- 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
2026-04-07 12:52:36 +02:00
Dev
c42140db1a docs(07-05): complete UserAccessAuditView plan
- 07-05-SUMMARY.md: view with people picker, summary banner, color-coded DataGrid
- STATE.md: progress updated to 85% (11/13), decisions recorded, session updated
- ROADMAP.md: phase 7 in progress with 6/8 summaries complete
2026-04-07 12:50:53 +02:00
Dev
975762dee4 feat(07-05): create UserAccessAuditView code-behind
- UserControl with UserAccessAuditViewModel constructor injection, sets DataContext
- Wires SearchResults.CollectionChanged to show/hide autocomplete ListBox
- OnSearchResultClicked handler invokes AddUserCommand for mouse-based user selection
2026-04-07 12:49:41 +02:00
Dev
bb9ba9d310 feat(07-05): create UserAccessAuditView XAML layout
- 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
2026-04-07 12:49:37 +02:00
Dev
72349d8415 docs(07-04): complete UserAccessAuditViewModel plan
- Add 07-04-SUMMARY.md with task commits and decisions
- Update STATE.md: progress 77%, session record, decisions
- Update ROADMAP.md: phase 7 plan count updated to 5/8 summaries
2026-04-07 12:45:14 +02:00
Dev
3de737ac3f feat(07-04): implement UserAccessAuditViewModel
- Extends FeatureViewModelBase with RunOperationAsync calling IUserAccessAuditService.AuditUsersAsync
- People picker with 300ms debounced Graph search via IGraphUserSearchService.SearchUsersAsync
- SelectedUsers ObservableCollection<GraphUserResult> with AddUserCommand/RemoveUserCommand
- Results ObservableCollection<UserAccessEntry> with CollectionViewSource grouping (by user/site) and FilterText predicate
- Summary banner properties: TotalAccessCount, SitesCount, HighPrivilegeCount (computed from Results)
- ExportCsvCommand/ExportHtmlCommand using UserAccessCsvExportService/UserAccessHtmlExportService
- Site selection with _hasLocalSiteOverride + OnGlobalSitesChanged pattern from PermissionsViewModel
- Dual constructors (DI + internal test constructor omitting export services)
- OnTenantSwitched resets all state (results, users, search, sites)
2026-04-07 12:44:02 +02:00
Dev
5c4a285473 docs(07-06): complete export services plan
- UserAccessCsvExportService and UserAccessHtmlExportService implemented
- SUMMARY.md created with task commits, decisions, self-check
- STATE.md updated: progress 69%, session, metrics, decisions
- ROADMAP.md updated: phase 7 showing 4/8 summaries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 12:42:00 +02:00
Dev
85712ad3ba docs(07-02): complete UserAccessAuditService plan
- Add 07-02-SUMMARY.md with implementation details
- Update STATE.md: progress 62%, decisions, session
- Update ROADMAP.md: phase 7 now 3/8 plans complete
2026-04-07 12:40:56 +02:00
Dev
3146a04ad8 feat(07-06): implement UserAccessHtmlExportService
- 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)
2026-04-07 12:40:51 +02:00
Dev
cc513777ec docs(07-03): complete GraphUserSearchService plan
- Add 07-03-SUMMARY.md with implementation details and decisions
- Update STATE.md: progress 54%, decisions, session, metrics
- Update ROADMAP.md: phase 07 now 2/8 summaries
2026-04-07 12:40:22 +02:00
Dev
44b238e07a feat(07-02): implement UserAccessAuditService
- 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#)
2026-04-07 12:39:57 +02:00
Dev
9f891aa512 feat(07-06): implement UserAccessCsvExportService
- 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
2026-04-07 12:39:35 +02:00
Dev
026b8294de feat(07-03): implement GraphUserSearchService for people-picker autocomplete
- Queries Graph /users with startsWith filter on displayName, mail, UPN
- Requires minimum 2 chars to prevent overly broad queries
- Sets ConsistencyLevel=eventual + Count=true (required for advanced filter)
- Escapes single quotes to prevent OData injection
- Returns up to maxResults (default 10) GraphUserResult records
2026-04-07 12:39:22 +02:00
Dev
7e6f3e7fc0 docs(07-01): complete data models and service interfaces plan
- UserAccessEntry, AccessType, IUserAccessAuditService, IGraphUserSearchService
- UACC-01, UACC-02 requirements marked complete
- STATE.md updated with position and decisions
- ROADMAP.md Phase 7 progress updated (1/8 plans)
2026-04-07 12:38:19 +02:00
Dev
1a6989a9bb feat(07-01): add IUserAccessAuditService and IGraphUserSearchService interfaces
- IUserAccessAuditService.AuditUsersAsync: scan sites and filter by user logins
- IGraphUserSearchService.SearchUsersAsync: Graph API people-picker autocomplete
- GraphUserResult record: DisplayName, UserPrincipalName, Mail
2026-04-07 12:37:26 +02:00
Dev
e08df0f658 feat(07-01): add UserAccessEntry model and AccessType enum
- UserAccessEntry record with 12 fields for user-centric audit results
- AccessType enum: Direct, Group, Inherited
- Pre-computed IsHighPrivilege and IsExternalUser fields for grid display
2026-04-07 12:37:00 +02:00
Dev
19e4c3852d docs(07): create phase plan - 8 plans across 5 waves
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:32:39 +02:00
Dev
91058bc2e4 docs(state): record phase 7 context session
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:22:02 +02:00
Dev
ab253ca80a docs(07): capture phase context for user access audit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:21:57 +02:00
Dev
e96ca3edfe test(06): complete UAT - 8/8 passed
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>
2026-04-07 12:01:33 +02:00
Dev
4846915c80 fix(site-list): fix parsing error and double-auth in SiteListService
- 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>
2026-04-07 11:00:54 +02:00
Dev
5666565ac1 test(06): complete UAT - 0 passed, 3 issues, 7 skipped
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>
2026-04-07 10:41:39 +02:00
Dev
52670bd262 docs(phase-06): complete phase verification and update state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:18:14 +02:00
Dev
9add2592b3 docs(06-05): complete GlobalSiteSelectionTests plan — phase 6 done
- SUMMARY.md created with test coverage details and decision rationale
- STATE.md updated: progress 100%, decisions recorded, session logged
- ROADMAP.md phase 6 marked Complete (5/5 plans with summaries)
2026-04-07 10:14:48 +02:00
Dev
80ef092a2e test(06-05): add GlobalSiteSelectionTests with 10 passing tests
- 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
2026-04-07 10:13:31 +02:00
Dev
da905b6ec0 docs(06-04): complete tab-vms global site consumption plan
- 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
2026-04-07 10:10:18 +02:00
Dev
0a91dd4ff3 feat(06-04): update TransferViewModel for global site consumption; confirm BulkMembers excluded
- 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
2026-04-07 10:08:52 +02:00
Dev
9a4365bd32 docs(06-03): complete toolbar UI, localization, and dialog factory wiring plan
- 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)
2026-04-07 10:08:51 +02:00
Dev
6a2e4d1d89 feat(06-04): update single-site tab VMs for global site consumption
- StorageViewModel: add OnGlobalSitesChanged, OnSiteUrlChanged, _hasLocalSiteOverride
- SearchViewModel: add OnGlobalSitesChanged, OnSiteUrlChanged, _hasLocalSiteOverride
- DuplicatesViewModel: add OnGlobalSitesChanged, OnSiteUrlChanged, _hasLocalSiteOverride
- FolderStructureViewModel: add OnGlobalSitesChanged, OnSiteUrlChanged, _hasLocalSiteOverride
- All four VMs pre-fill SiteUrl from first global site; local typing sets override flag
- Tenant switch resets _hasLocalSiteOverride in all four VMs
2026-04-07 10:08:19 +02:00
Dev
45eb531128 feat(06-03): add global site picker button and count label to toolbar
- Add Separator + Select Sites button (bound to OpenGlobalSitePickerCommand) to ToolBar
- Add TextBlock bound to GlobalSitesSelectedLabel for site count display
- Wire viewModel.OpenGlobalSitePickerDialog factory in MainWindow.xaml.cs using DI
- Add using SharepointToolbox.Core.Models for TenantProfile in code-behind
2026-04-07 10:07:35 +02:00
Dev
467a940c6f feat(06-03): localize GlobalSitesSelectedLabel in MainWindowViewModel
- Replace hardcoded EN strings with TranslationSource.Instance lookups
- Uses toolbar.globalSites.count (formatted) and toolbar.globalSites.none keys
- Follows same pattern as PermissionsViewModel.SitesSelectedLabel
2026-04-07 10:06:57 +02:00
Dev
1bf47b5c4e feat(06-04): update PermissionsViewModel for multi-site global consumption
- Add _hasLocalSiteOverride field to track local user selection
- Override OnGlobalSitesChanged to pre-populate SelectedSites from global sites
- Set _hasLocalSiteOverride=true when user picks sites via site picker dialog
- Reset _hasLocalSiteOverride=false on tenant switch (OnTenantSwitched)
2026-04-07 10:06:57 +02:00
Dev
185642f4af feat(06-03): add EN/FR localization keys for global site picker toolbar
- Add toolbar.selectSites, toolbar.selectSites.tooltip, toolbar.selectSites.tooltipDisabled
- Add toolbar.globalSites.count and toolbar.globalSites.none to both Strings.resx and Strings.fr.resx
2026-04-07 10:06:40 +02:00
Dev
a39c87d43e docs(06-01): complete GlobalSitesChangedMessage and FeatureViewModelBase plan
- 06-01-SUMMARY.md created with deviations and decisions documented
- STATE.md updated: progress 40%, decisions added, session recorded
- ROADMAP.md updated: phase 6 in-progress (2/5 summaries)
2026-04-07 10:05:16 +02:00
Dev
95bf9c2eed docs(06-02): complete MainWindowViewModel global site selection plan
- Add 06-02-SUMMARY.md with execution results and dependency graph
- Update STATE.md: progress 20%, decision logged, session recorded
- Update ROADMAP.md: phase 6 in progress (1/5 plans complete)
- Mark SITE-01 requirement complete in REQUIREMENTS.md
2026-04-07 10:04:36 +02:00
Dev
d4fe169bd8 feat(06-01): extend FeatureViewModelBase with GlobalSites support
- Add protected GlobalSites property (IReadOnlyList<SiteInfo>) initialized to Array.Empty
- Register GlobalSitesChangedMessage in OnActivated alongside TenantSwitchedMessage
- Add private OnGlobalSitesReceived to update GlobalSites and invoke virtual hook
- Add protected virtual OnGlobalSitesChanged for derived VMs to override
- [Rule 3 - Blocking] Fix MainWindowViewModel missing ExecuteOpenGlobalSitePicker and BroadcastGlobalSites stubs referenced in constructor (pre-existing partial state from earlier TODO commit)
2026-04-07 10:03:40 +02:00
Dev
a10f03edc8 feat(06-02): add global site selection state, command, and broadcast to MainWindowViewModel
- Add OpenGlobalSitePickerDialog factory property (dialog factory pattern)
- Add GlobalSelectedSites ObservableCollection<SiteInfo>
- Add GlobalSitesSelectedLabel computed property for toolbar display
- Add OpenGlobalSitePickerCommand (disabled when no profile selected)
- Broadcast GlobalSitesChangedMessage via WeakReferenceMessenger on collection change
- Clear GlobalSelectedSites on tenant switch (OnSelectedProfileChanged)
- Clear GlobalSelectedSites on session clear (ClearSessionAsync)
- Add using SharepointToolbox.Views.Dialogs for SitePickerDialog cast
2026-04-07 10:03:30 +02:00
Dev
7874fa8524 feat(06-01): create GlobalSitesChangedMessage
- New ValueChangedMessage<IReadOnlyList<SiteInfo>> following TenantSwitchedMessage pattern
- Carries snapshot of globally selected sites (IReadOnlyList — immutable by design)
2026-04-07 10:02:20 +02:00
Dev
6ae3629301 docs(06): create phase plan for global site selection (5 plans, 3 waves)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:57:15 +02:00
Dev
59efdfe3f0 docs: create milestone v1.1 roadmap (4 phases)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:41:49 +02:00
Dev
04a307b69c docs: define milestone v1.1 requirements (10 requirements)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:40:02 +02:00
Dev
81da0f6a99 docs: start milestone v1.1 Enhanced Reports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:38:28 +02:00
Dev
0fb35de80f docs: capture todo - Add global multi-site selection option
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:33:05 +02:00
Dev
724fdc550d chore: complete v1.0 milestone
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>
2026-04-07 09:19:03 +02:00
1077 changed files with 22382 additions and 1006 deletions

View File

@@ -1,5 +1,4 @@
#Wkf name: Release SharePoint Toolbox v2
name: Release zip package
on: on:
push: push:
@@ -19,12 +18,28 @@ jobs:
GITEA_REPO: ${{ gitea.repository }} GITEA_REPO: ${{ gitea.repository }}
GITEA_REF_NAME: ${{ gitea.ref_name }} GITEA_REF_NAME: ${{ gitea.ref_name }}
- name: Publish self-contained EXE
run: |
cd repo
dotnet publish SharepointToolbox/SharepointToolbox.csproj \
-c Release \
-p:PublishSingleFile=true \
-o publish
- name: Build zip - name: Build zip
run: | run: |
cd repo cd repo
VERSION="${{ gitea.ref_name }}" VERSION="${{ gitea.ref_name }}"
ZIP="SharePoint_ToolBox_${VERSION}.zip" ZIP="SharePoint_Toolbox_${VERSION}.zip"
zip -r "../${ZIP}" Sharepoint_ToolBox.ps1 lang/ examples/
mkdir -p package/examples
cp publish/SharepointToolbox.exe package/
cp SharepointToolbox/Resources/*.csv package/examples/
cd package
zip -r "../../${ZIP}" .
cd ../..
echo "ZIP=${ZIP}" >> "$GITHUB_ENV" echo "ZIP=${ZIP}" >> "$GITHUB_ENV"
echo "VERSION=${VERSION}" >> "$GITHUB_ENV" echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
@@ -34,7 +49,7 @@ jobs:
"${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases" \ "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases" \
-H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \ -H "Authorization: token ${{ secrets.RELEASE_TOKEN }}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"tag_name\":\"${{ env.VERSION }}\",\"name\":\"SharePoint ToolBox ${{ env.VERSION }}\",\"body\":\"1. Download and extract the archive\\n2. Launch Sharepoint_ToolBox.ps1 with PowerShell\\n\"}" \ -d "{\"tag_name\":\"${{ env.VERSION }}\",\"name\":\"SharePoint Toolbox ${{ env.VERSION }}\",\"body\":\"## Installation\\n\\n1. Download and extract the archive\\n2. Launch **SharepointToolbox.exe** (no .NET runtime required)\\n\\n## Included\\n\\n- SharepointToolbox.exe — self-contained desktop application\\n- examples/ — sample CSV templates for bulk operations\\n\"}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "RELEASE_ID=${RELEASE_ID}" >> "$GITHUB_ENV" echo "RELEASE_ID=${RELEASE_ID}" >> "$GITHUB_ENV"

View File

@@ -0,0 +1,155 @@
# Milestone Audit: SharePoint Toolbox v2 — v1 Release
**Audited:** 2026-04-07
**Milestone:** v1 (5 phases, 42 requirements)
**Verdict:** PASSED — all requirements satisfied, all phases integrated, build and tests green
---
## Phase Verification Summary
| Phase | Status | Score | Verification |
|-------|--------|-------|-------------|
| 01 — Foundation | PASSED | 11/11 | 01-VERIFICATION.md |
| 02 — Permissions | HUMAN_NEEDED | 7/7 automated | 02-VERIFICATION.md (2 human items pending) |
| 03 — Storage & File Ops | **MISSING** | No VERIFICATION.md | Summaries exist for all 8 plans; integration checker confirmed all wiring |
| 04 — Bulk Ops & Provisioning | HUMAN_NEEDED | 12/12 automated | 04-VERIFICATION.md (7 human items pending) |
| 05 — Distribution & Hardening | HUMAN_NEEDED | 6/6 automated | 05-VERIFICATION.md (2 human items pending) |
### Gap: Phase 03 Missing Verification
Phase 03 has no `03-VERIFICATION.md` file. All 8 plan summaries exist and confirm code was delivered. The integration checker independently verified:
- All 3 Phase 3 service interfaces (IStorageService, ISearchService, IDuplicatesService) registered in DI
- All 5 export services registered and wired to ViewModels
- All 4 Phase 3 tabs (Storage, Search, Duplicates + exports) wired in MainWindow
- 13 Phase 3 requirements (STOR-0105, SRCH-0104, DUPL-0103) covered
**Recommendation:** Run a retroactive phase verification for Phase 03 or accept integration checker evidence as sufficient.
---
## Requirements Coverage
All 42 v1 requirements are marked complete in REQUIREMENTS.md with phase traceability:
| Category | IDs | Count | Status |
|----------|-----|-------|--------|
| Foundation | FOUND-01 to FOUND-12 | 12 | All SATISFIED |
| Permissions | PERM-01 to PERM-07 | 7 | All SATISFIED |
| Storage | STOR-01 to STOR-05 | 5 | All SATISFIED |
| File Search | SRCH-01 to SRCH-04 | 4 | All SATISFIED |
| Duplicates | DUPL-01 to DUPL-03 | 3 | All SATISFIED |
| Templates | TMPL-01 to TMPL-04 | 4 | All SATISFIED |
| Folder Structure | FOLD-01 to FOLD-02 | 2 | All SATISFIED |
| Bulk Operations | BULK-01 to BULK-05 | 5 | All SATISFIED |
| **Total** | | **42** | **42/42 mapped and complete** |
**Orphaned requirements:** None
**Unmapped requirements:** None
---
## Cross-Phase Integration
Integration checker ran full verification. Results:
| Check | Status |
|-------|--------|
| DI wiring (all 5 phases) | PASS — all services registered in App.xaml.cs |
| MainWindow tabs (10 tabs) | PASS — all declared and wired from DI |
| FeatureViewModelBase inheritance (10 VMs) | PASS |
| SessionManager usage (9 ViewModels + SiteListService) | PASS |
| ExecuteQueryRetryHelper (9 CSOM services, 40+ call sites) | PASS |
| SharePointPaginationHelper (2 services using list enumeration) | PASS |
| TranslationSource localization (15 XAML files, 170 bindings) | PASS |
| TenantSwitchedMessage propagation | PASS |
| Export chain completeness (all features) | PASS |
| Build | PASS — 0 warnings, 0 errors |
| Tests | PASS — 134 passed, 22 skipped (live CSOM), 0 failed |
| EN/FR key parity | PASS — 199/199 keys |
**Orphaned code:** `FeatureTabBase.xaml` — Phase 1 placeholder, now superseded by full tab views. Harmless dead code.
---
## Tech Debt & Deferred Items
### From Phase Verifications
| Item | Source | Severity | Description |
|------|--------|----------|-------------|
| Hardcoded export button text | Phase 2 | Info | `PermissionsView.xaml` uses `Content="Export CSV"` / `"Export HTML"` instead of `rad.csv.perms` / `rad.html.perms` localization keys. French users see English button labels. |
| Missing Designer.cs property | Phase 2 | Info | `Strings.Designer.cs` lacks `tab_permissions` typed accessor. Runtime binding via `TranslationSource` works fine. |
| No invalid-row highlighting | Phase 4 | Warning | `BulkMembersView.xaml`, `BulkSitesView.xaml`, `FolderStructureView.xaml` show IsValid as text column but lack `RowStyle` + `DataTrigger` for visual red highlighting on invalid rows. |
| FeatureTabBase dead code | Phase 1→all | Info | `Views/Controls/FeatureTabBase.xaml` is no longer imported by any tab view after all phases replaced stubs. |
| Cancel test locale mismatch | Phase 3 (03-08) | Info | `FeatureViewModelBaseTests.CancelCommand_DuringOperation_SetsStatusMessageToCancelled` asserts `.Contains("cancel")` but app returns French string "Opération annulée". Pre-existing; deferred. |
### Deferred v2 Requirements
These are explicitly out of scope for v1 and tracked in REQUIREMENTS.md:
- UACC-01/02: User access audit across sites
- SIMP-01/02/03: Simplified plain-language permission reports
- VIZZ-01/02/03: Storage metrics graphs (pie/bar chart)
---
## Human Verification Backlog
11 items across 3 phases require human confirmation (runtime UI/locale checks that cannot be automated):
### Phase 2 (2 items)
1. Full Permissions tab UI visual checkpoint (layout, disabled states, French locale)
2. Export button localization decision (accept hardcoded English or bind to resx keys)
### Phase 4 (7 items)
1. Application launches with all 10 tabs visible
2. Bulk Members — Load Example populates DataGrid with 7 rows
3. Bulk Sites — semicolon CSV auto-detection works
4. Invalid row display in DataGrid (IsValid=False, Errors column)
5. Confirmation dialog appears before bulk operations
6. Transfer tab — two-step browse flow (SitePickerDialog → FolderBrowserDialog)
7. Templates tab — 5 capture checkboxes visible and checked by default
### Phase 5 (2 items)
1. Clean-machine EXE launch (no .NET runtime installed)
2. French locale runtime rendering (diacritics display correctly in all tabs)
---
## Build & Test Summary
| Metric | Value |
|--------|-------|
| Build | 0 errors, 0 warnings |
| Tests passed | 134 |
| Tests skipped | 22 (live CSOM — expected) |
| Tests failed | 0 |
| EN locale keys | 199 |
| FR locale keys | 199 |
| Published EXE | 200.9 MB self-contained |
| Phases complete | 5/5 |
| Requirements satisfied | 42/42 |
---
## Verdict
**PASSED** — The milestone has achieved its definition of done:
1. All 42 v1 requirements are implemented with real code and verified by phase-level checks
2. All cross-phase integration points are wired (DI, messaging, shared infrastructure)
3. Build compiles cleanly with zero warnings
4. 134 automated tests pass with zero failures
5. Self-contained 200.9 MB EXE produced successfully
6. Full EN/FR locale parity (199 keys each)
**Remaining actions before shipping:**
- [ ] Complete 11 human verification items (UI visual checks, clean-machine launch)
- [ ] Decide on Phase 03 retroactive verification (or accept integration check as sufficient)
- [ ] Address 3 Warning-level tech debt items (invalid-row highlighting in bulk DataGrids)
- [ ] Optionally clean up FeatureTabBase dead code and fix cancel test locale mismatch
---
*Audited: 2026-04-07*
*Auditor: Claude (milestone audit)*

23
.planning/MILESTONES.md Normal file
View File

@@ -0,0 +1,23 @@
# Milestones
## v1.0 MVP (Shipped: 2026-04-07)
**Phases completed:** 5 phases, 36 plans | 164 commits | 10,071 LOC (C# + XAML)
**Timeline:** 28 days (2026-03-10 → 2026-04-07)
**Tests:** 134 pass, 22 skip (live CSOM), 0 fail
**Key accomplishments:**
- Full C#/WPF rewrite of 6,400-line PowerShell tool into 10,071-line MVVM application
- Multi-tenant MSAL authentication with per-tenant token caching and instant switching
- SharePoint permissions scanner with multi-site support, CSV/HTML export, 5,000-item pagination
- Storage metrics, file search, and duplicate detection with configurable depth and exports
- Bulk operations (members, sites, file transfer) with per-item error reporting, retry, and cancellation
- Self-contained 200 MB EXE with full EN/FR localization (199 keys each)
**Archives:**
- [v1.0-ROADMAP.md](milestones/v1.0-ROADMAP.md)
- [v1.0-REQUIREMENTS.md](milestones/v1.0-REQUIREMENTS.md)
- [v1.0-MILESTONE-AUDIT.md](milestones/v1.0-MILESTONE-AUDIT.md)
---

View File

@@ -2,66 +2,82 @@
## What This Is ## What This Is
A full C#/WPF rewrite of an existing PowerShell-based SharePoint Online administration and auditing tool. The app lets IT administrators manage permissions, analyze storage, search files, detect duplicates, manage site templates, and perform bulk operations across SharePoint Online and Teams sites. It's a local desktop tool used by MSPs and IT teams managing multiple client tenants. A C#/WPF desktop application for IT administrators and MSPs to audit and manage SharePoint Online permissions, storage, files, and sites across multiple client tenants. Replaces a 6,400-line monolithic PowerShell script with a structured 10,071-line MVVM application shipping as a single self-contained EXE.
## Core Value ## Core Value
Administrators can audit and manage SharePoint/Teams permissions and storage across multiple client tenants from a single, reliable desktop application. Administrators can audit and manage SharePoint/Teams permissions and storage across multiple client tenants from a single, reliable desktop application.
## Current State
**Shipped:** v1.1 Enhanced Reports (2026-04-08)
**Status:** Feature-complete for v1.1; no active milestone
**v1.1 shipped features:**
- Global multi-site selection in toolbar (pick sites once, all tabs use them)
- User access audit tab with Graph API people-picker, direct/group/inherited access distinction
- Simplified permissions with plain-language labels, color-coded risk levels, detail-level toggle
- Storage visualization with LiveCharts2 pie/donut and bar charts by file type
Tech stack: C# / WPF / .NET 10 / PnP Framework / Microsoft Graph SDK / MSAL / Serilog / CommunityToolkit.Mvvm / LiveCharts2
Tests: 205 automated (xUnit), 22 skipped (require live SharePoint tenant)
Distribution: 200 MB self-contained EXE (win-x64)
## Requirements ## Requirements
### Validated ### Validated
(None yet — ship to validate) - Full C#/WPF rewrite of all existing PowerShell features — v1.0
- Multi-tenant authentication with cached sessions — v1.0
- Thorough error handling (per-item reporting, no silent failures) — v1.0
- Modular architecture (separate files per feature area, DI, MVVM) — v1.0
- Self-contained single EXE distribution — v1.0
### Active ### Shipped in v1.1
- [ ] Full C#/WPF rewrite of all existing PowerShell features - [x] Global multi-site selection in toolbar (SITE-01/02) — v1.1
- [ ] Multi-tenant authentication with cached sessions (switch between client tenants instantly) - [x] Export all SharePoint/Teams accesses a specific user has across selected sites (UACC-01/02) — v1.1
- [ ] Export all SharePoint/Teams accesses a specific user has across selected sites - [x] Simplified permissions reports (plain language, summary views) (SIMP-01/02/03) — v1.1
- [ ] Simplified permissions reports (plain language, summary views, reduced jargon for untrained users) - [x] Storage metrics graph by file type (pie/donut and bar chart, toggleable) (VIZZ-01/02/03) — v1.1
- [ ] Storage metrics graph by file type (pie/donut and bar chart, toggleable) in Storage Metrics tab
- [ ] Thorough error handling cleanup (eliminate silent failures, proper error reporting)
- [ ] Modular architecture (separate files per feature area)
- [ ] Self-contained single EXE distribution (no .NET runtime dependency)
### Out of Scope ### Out of Scope
- Cross-platform support (Mac/Linux) — Windows-only desktop tool, MAUI/Avalonia not justified - Cross-platform support (Mac/Linux) — WPF is Windows-only; not justified for current user base
- SQLite or database storage — JSON sufficient for config, profiles, and templates - SQLite or database storage — JSON sufficient for config, profiles, and templates
- Web-based UI — must remain a local desktop application - Web-based UI — must remain a local desktop application
- Cloud/SaaS deployment — local tool by design - Cloud/SaaS deployment — local tool by design
- Mobile support — desktop admin tool - Mobile support — desktop admin tool
- Real-time monitoring / alerts — requires background service, beyond scope
- Automated remediation (auto-revoke) — liability risk
- Content migration between tenants — separate product category
## Context ## Context
- **Existing codebase:** 6,400-line monolithic PowerShell script (`Sharepoint_ToolBox.ps1`) with WinForms UI - **v1.0 shipped** with full feature parity: permissions, storage, search, duplicates, bulk operations, templates, folder provisioning
- **Current features to port:** Permissions reports, storage metrics, site templates, file search, duplicate detection, bulk operations (transfer, site creation, member addition), folder structure creation, localization (EN/FR) - **v1.1 shipped** with enhanced reports: user access audit, simplified permissions, storage charts, global site selection
- **SharePoint integration:** Currently uses PnP.PowerShell module; C# rewrite will use PnP Framework / Microsoft Graph SDK - **Localization:** 220+ EN/FR keys, full parity verified
- **Authentication:** Currently interactive Azure AD OAuth via PnP; new version needs multi-tenant session caching - **Architecture:** 120+ C# files + 17 XAML files across Core/Infrastructure/Services/ViewModels/Views layers
- **Known issues in current app:** 38 silent catch blocks, 27 error suppressions, resource cleanup issues, UI freezes on large datasets, no operation cancellation
- **Localization:** English and French supported, key-based translation system
- **Report exports:** CSV and interactive HTML reports with embedded JS for sorting/filtering
## Constraints ## Constraints
- **Platform:** Windows desktop only — WPF requires Windows - **Platform:** Windows desktop only — WPF requires Windows
- **Distribution:** Self-contained EXE (~150MB) — no .NET runtime dependency for end users - **Distribution:** Self-contained EXE (~200 MB) — no .NET runtime dependency
- **Auth method:** Interactive browser-based Azure AD login (no client secrets or certificates stored) - **Auth method:** Interactive browser-based Azure AD login (no client secrets stored)
- **Data storage:** JSON files for profiles, settings, templates — same format as current app for migration - **Data storage:** JSON files for profiles, settings, templates
- **SharePoint API:** PnP Framework / Microsoft Graph SDK for C# (replaces PnP.PowerShell) - **SharePoint API:** PnP Framework / Microsoft Graph SDK
- **Local only:** No telemetry, no cloud services, no external dependencies at runtime - **Local only:** No telemetry, no cloud services, no external dependencies at runtime
## Key Decisions ## Key Decisions
| Decision | Rationale | Outcome | | Decision | Rationale | Outcome |
|----------|-----------|---------| |----------|-----------|---------|
| Rewrite to C#/WPF instead of improving PowerShell | Better async/await, proper OOP, richer UI, better tooling — worth the investment for long-term maintainability | — Pending | | Rewrite to C#/WPF instead of improving PowerShell | Better async/await, proper OOP, richer UI, better tooling | ✓ Good — 10k LOC structured app vs 6.4k monolithic script |
| WPF over WinForms | Modern data binding, MVVM pattern, richer styling for better UX | — Pending | | WPF over WinForms | Modern data binding, MVVM pattern, richer styling | ✓ Good — clean separation of concerns |
| Self-contained EXE | Users shouldn't need to install .NET runtime — simplifies distribution to clients | — Pending | | Self-contained EXE | Users shouldn't need to install .NET runtime | ✓ Good — 200 MB single file, zero dependencies |
| Keep JSON storage | Simple, human-readable, sufficient for config/profiles — no need for SQLite complexity | — Pending | | Keep JSON storage | Simple, human-readable, sufficient for config/profiles | ✓ Good — atomic write-then-replace pattern works well |
| Multi-tenant session caching | MSP workflow requires fast switching between client tenants without re-authenticating each time | — Pending | | Multi-tenant session caching | MSP workflow requires fast switching between tenants | ✓ Good — per-clientId MSAL PCA with MsalCacheHelper |
| Pie + bar chart toggle for storage | Gives users flexibility to view data in preferred format | — Pending | | BulkOperationRunner pattern | Continue-on-error with per-item results for all bulk ops | ✓ Good — consistent error handling across 4 bulk features |
| Wave 0 scaffold pattern | Models + interfaces + test stubs before implementation | ✓ Good — all phases had test targets from day 1 |
--- ---
*Last updated: 2026-04-02 after initialization* *Last updated: 2026-04-08 after v1.1 milestone shipped*

View File

@@ -0,0 +1,53 @@
# Retrospective
## Milestone: v1.0 — MVP
**Shipped:** 2026-04-07
**Phases:** 5 | **Plans:** 36 | **Commits:** 164 | **LOC:** 10,071
### What Was Built
- Complete C#/WPF rewrite replacing 6,400-line PowerShell monolith
- 10 feature tabs: Permissions, Storage, Search, Duplicates, Transfer, Bulk Members, Bulk Sites, Folder Structure, Templates, Settings
- Multi-tenant MSAL authentication with per-tenant token caching
- CSV and interactive HTML export across all scan features
- Bulk operations with continue-on-error, per-item reporting, retry, and cancellation
- Self-contained 200 MB EXE with full EN/FR localization (199 keys)
### What Worked
- **Wave 0 scaffold pattern**: Creating models, interfaces, and test stubs before implementation gave every phase testable targets from day 1
- **FeatureViewModelBase contract**: Establishing the async/cancel/progress pattern in Phase 1 meant Phases 2-4 had zero friction adding new features
- **BulkOperationRunner abstraction**: One shared helper gave consistent error semantics across 4 different bulk operations
- **Phase dependency ordering**: Foundation → Permissions → Storage → Bulk → Hardening prevented rework
- **Atomic commits per task**: Each plan produced clear, reviewable commit history
### What Was Inefficient
- **Phase 03 missing verification**: The only phase without a VERIFICATION.md — caught during milestone audit, but should have been produced during execution
- **Stale audit notes**: Phase 2 verification reported export buttons as hardcoded English, but they were actually already localized by the time of the audit — suggests verification reads code at a point-in-time snapshot that may not reflect later fixes
- **Cancel test locale mismatch**: The French locale test failure was flagged in Phase 3 plan 08 summary but deferred until post-milestone cleanup — should have been fixed inline
### Patterns Established
- Write-then-replace JSON persistence with SemaphoreSlim for thread safety
- TranslationSource singleton with PropertyChanged(string.Empty) for runtime culture switching
- ExecuteQueryRetryHelper for throttle-aware CSOM calls (429/503 detection)
- SharePointPaginationHelper with ListItemCollectionPosition for 5,000+ item lists
- CsvValidationService with auto-delimiter detection (comma/semicolon) and BOM handling
- DataGrid RowStyle DataTrigger for invalid-row visual highlighting
### Key Lessons
- Establish shared infrastructure patterns (auth, retry, pagination, progress) in Phase 1 — every subsequent phase benefits
- Test scaffolds (Wave 0) eliminate the "no tests until the end" anti-pattern
- Phase verifications should be mandatory during execution, not optional — catching Phase 03's gap at audit time is late
- Localization tests (key parity + diacritic spot-checks) are cheap and catch real bugs
---
## Cross-Milestone Trends
| Metric | v1.0 |
|--------|------|
| Phases | 5 |
| Plans | 36 |
| LOC | 10,071 |
| Tests | 134 pass / 22 skip |
| Timeline | 28 days |
| Commits | 164 |

View File

@@ -1,147 +1,36 @@
# Roadmap: SharePoint Toolbox v2 # Roadmap: SharePoint Toolbox v2
## Overview ## Milestones
A full C#/WPF rewrite of a 6,400-line PowerShell-based SharePoint Online administration tool. The -**v1.0 MVP** — Phases 1-5 (shipped 2026-04-07) — [archive](milestones/v1.0-ROADMAP.md)
project delivers a self-contained Windows desktop application that lets MSP administrators audit -**v1.1 Enhanced Reports** — Phases 6-9 (shipped 2026-04-08) — [archive](milestones/v1.1-ROADMAP.md)
and manage permissions, storage, and site provisioning across multiple client tenants from a single
tool. Foundation infrastructure (multi-tenant auth, async patterns, error handling, DI) must be
solid before any feature work begins — all 10 identified pitfalls in the existing codebase map
entirely to Phase 1. Subsequent phases deliver complete, verifiable feature areas in dependency
order: permissions first (validates auth and pagination), then storage and search (reuse those
patterns), then bulk and provisioning operations (highest write-risk features last), then
hardening and packaging.
## Phases ## Phases
**Phase Numbering:** <details>
- Integer phases (1, 2, 3): Planned milestone work <summary>✅ v1.0 MVP (Phases 1-5) — SHIPPED 2026-04-07</summary>
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
Decimal phases appear between their surrounding integers in numeric order. - [x] Phase 1: Foundation (8/8 plans) — completed 2026-04-02
- [x] Phase 2: Permissions (7/7 plans) — completed 2026-04-02
- [x] Phase 3: Storage and File Operations (8/8 plans) — completed 2026-04-02
- [x] Phase 4: Bulk Operations and Provisioning (10/10 plans) — completed 2026-04-03
- [x] Phase 5: Distribution and Hardening (3/3 plans) — completed 2026-04-03
- [x] **Phase 1: Foundation** - WPF shell, multi-tenant auth, DI, async patterns, error handling, logging, localization, JSON persistence (completed 2026-04-02) </details>
- [x] **Phase 2: Permissions** - Permissions scan (single and multi-site), CSV and HTML report export
- [x] **Phase 3: Storage and File Operations** - Storage metrics, file search, and duplicate detection (completed 2026-04-02)
- [x] **Phase 4: Bulk Operations and Provisioning** - Bulk member/site/transfer operations, site templates, folder structure provisioning (completed 2026-04-03)
- [x] **Phase 5: Distribution and Hardening** - Self-contained EXE packaging, end-to-end validation, FR locale completeness (completed 2026-04-03)
## Phase Details <details>
<summary>✅ v1.1 Enhanced Reports (Phases 6-9) — SHIPPED 2026-04-08</summary>
### Phase 1: Foundation - [x] Phase 6: Global Site Selection (5/5 plans) — completed 2026-04-07
**Goal**: The application shell runs, users can authenticate to multiple tenants and switch between them without re-logging in, all long-running operations are cancellable and report progress, all errors surface visibly, and the infrastructure patterns that prevent the existing app's 10 known pitfalls are in place before any feature work begins. - [x] Phase 7: User Access Audit (10/10 plans) — completed 2026-04-07
**Depends on**: Nothing (first phase) - [x] Phase 8: Simplified Permissions (6/6 plans) — completed 2026-04-07
**Requirements**: FOUND-01, FOUND-02, FOUND-03, FOUND-04, FOUND-05, FOUND-06, FOUND-07, FOUND-08, FOUND-09, FOUND-10, FOUND-12 - [x] Phase 9: Storage Visualization (4/4 plans) — completed 2026-04-07
**Success Criteria** (what must be TRUE):
1. User can create, rename, delete, and switch between tenant profiles via the UI — each profile stores tenant URL, client ID, and display name in a JSON file
2. User can authenticate to a tenant via interactive browser login and the session persists across tenant switches without re-entering credentials (MSAL token cache per tenant)
3. User can see real-time progress on any long-running operation and cancel it mid-execution with a button — the operation stops cleanly with no silent continuation
4. When any operation fails, the user sees an actionable error message in the UI — no operation fails silently or swallows an exception
5. UI language switches between English and French dynamically without restarting the application
**Plans**: 8 plans
Plans: </details>
- [ ] 01-01-PLAN.md — Solution scaffold: WPF project + xUnit test project with Generic Host entry point
- [ ] 01-02-PLAN.md — Core layer: models, messages, pagination helper, retry helper, LogPanelSink
- [ ] 01-03-PLAN.md — Persistence layer: ProfileRepository + SettingsRepository + services + unit tests
- [ ] 01-04-PLAN.md — Auth layer: MsalClientFactory + SessionManager + unit tests
- [ ] 01-05-PLAN.md — Localization + Serilog: TranslationSource, EN/FR resx, integration tests
- [ ] 01-06-PLAN.md — ViewModels + WPF shell: FeatureViewModelBase, MainWindow XAML, global exception handlers
- [ ] 01-07-PLAN.md — UI dialogs: ProfileManagementDialog + SettingsView wired into shell
- [ ] 01-08-PLAN.md — Checkpoint: full test suite + visual verification of running application
### Phase 2: Permissions
**Goal**: Users can scan SharePoint permissions on one or many sites and export the results as both a raw CSV and a sortable, filterable HTML report — with no silent failures on large libraries and full control over scan scope.
**Depends on**: Phase 1
**Requirements**: PERM-01, PERM-02, PERM-03, PERM-04, PERM-05, PERM-06, PERM-07
**Success Criteria** (what must be TRUE):
1. User can select one site or multiple sites and run a permissions scan that returns owners, members, guests, external users, and broken inheritance items
2. User can choose configurable scan depth and whether to include or exclude inherited permissions before running
3. User can export the permissions results to a CSV file with all raw permission data
4. User can export the permissions results to an interactive HTML report where rows are sortable, filterable, and groupable by user
5. Scanning a library with more than 5,000 items completes successfully — the tool paginates automatically and does not silently truncate or fail
**Plans**: 7 plans
Plans:
- [x] 02-01-PLAN.md — Wave 0: test scaffolds (PermissionsService, ViewModel, classification, CSV, HTML export tests) + PermissionEntryHelper
- [x] 02-02-PLAN.md — Core models + PermissionsService scan engine (PermissionEntry, ScanOptions, IPermissionsService, PermissionsService)
- [x] 02-03-PLAN.md — SiteListService: tenant admin site listing for multi-site picker (ISiteListService, SiteListService, SiteInfo)
- [x] 02-04-PLAN.md — Export services: CsvExportService (with row merging) + HtmlExportService (self-contained HTML)
- [x] 02-05-PLAN.md — Localization: 15 Phase 2 EN/FR keys in Strings.resx, Strings.fr.resx, Strings.Designer.cs
- [x] 02-06-PLAN.md — PermissionsViewModel + SitePickerDialog (XAML + code-behind)
- [x] 02-07-PLAN.md — DI wiring + PermissionsView XAML + MainWindow tab replacement + visual checkpoint
### Phase 3: Storage and File Operations
**Goal**: Users can view and export storage metrics per site and library, search for files across sites using multiple criteria, and detect duplicate files and folders — all with consistent export options and no silent failures on large datasets.
**Depends on**: Phase 2
**Requirements**: STOR-01, STOR-02, STOR-03, STOR-04, STOR-05, SRCH-01, SRCH-02, SRCH-03, SRCH-04, DUPL-01, DUPL-02, DUPL-03
**Success Criteria** (what must be TRUE):
1. User can view storage consumption per library and per site (with configurable folder depth), including total size, version size, item count, and last modified date
2. User can export storage metrics to CSV and to an interactive HTML with a collapsible tree view
3. User can search for files across sites using at least extension, name/regex, date range, creator, and editor as criteria — with a configurable result cap up to 50,000 items
4. User can export file search results to CSV and to an interactive sortable/filterable HTML
5. User can scan for duplicate files (by name, size, creation date, modification date) and duplicate folders (by name, subfolder count, file count) and export the results to an HTML with grouped display
**Plans**: 8 plans
Plans:
- [ ] 03-01-PLAN.md — Wave 0: test scaffolds + models (StorageNode, SearchResult, DuplicateGroup/Item, options) + interfaces (IStorageService, ISearchService, IDuplicatesService) + export stubs
- [ ] 03-02-PLAN.md — StorageService: CSOM StorageMetrics scan engine (recursive folder tree, library-level aggregation)
- [ ] 03-03-PLAN.md — Storage export services: StorageCsvExportService + StorageHtmlExportService (collapsible tree HTML)
- [ ] 03-04-PLAN.md — SearchService (KQL pagination, client-side Regex) + DuplicatesService (composite key grouping, CAML folder scan)
- [ ] 03-05-PLAN.md — Search and Duplicate export services: SearchCsvExportService + SearchHtmlExportService + DuplicatesHtmlExportService
- [ ] 03-06-PLAN.md — Localization: all Phase 3 EN/FR keys for Storage, File Search, and Duplicates tabs
- [ ] 03-07-PLAN.md — StorageViewModel + StorageView XAML + DI wiring (Storage tab replaces FeatureTabBase stub)
- [ ] 03-08-PLAN.md — SearchViewModel + SearchView + DuplicatesViewModel + DuplicatesView + DI wiring + visual checkpoint
### Phase 4: Bulk Operations and Provisioning
**Goal**: Users can execute bulk write operations (member additions, site creation, file transfer) with per-item error reporting and cancellation, capture site structures as reusable templates, apply templates to create new sites, and provision folder structures from CSV — all without silent partial failures.
**Depends on**: Phase 3
**Requirements**: BULK-01, BULK-02, BULK-03, BULK-04, BULK-05, TMPL-01, TMPL-02, TMPL-03, TMPL-04, FOLD-01, FOLD-02
**Success Criteria** (what must be TRUE):
1. User can transfer files and folders between sites with real-time progress tracking and can cancel mid-operation — transferred items are confirmed and failures are reported per-item
2. User can add members to groups in bulk from a CSV file — each row that fails is reported individually, not silently skipped
3. User can create multiple sites in bulk from a CSV file with per-site error reporting and mid-operation cancellation
4. User can capture an existing site's structure (libraries, folders, permission groups, logo, settings) as a named template stored in JSON, then apply that template to create a new Communication or Teams site
5. User can manage saved templates (create, rename, delete) and create folder structures on a target site from a CSV template
**Plans**: 10 plans
Plans:
- [ ] 04-01-PLAN.md — Dependencies + core models + interfaces + BulkOperationRunner + test scaffolds
- [ ] 04-02-PLAN.md — CsvValidationService + TemplateRepository with unit tests
- [ ] 04-03-PLAN.md — FileTransferService (CSOM MoveCopyUtil, conflict policies)
- [ ] 04-04-PLAN.md — BulkMemberService (Graph SDK batch + CSOM fallback)
- [ ] 04-05-PLAN.md — BulkSiteService (PnP Framework site creation)
- [ ] 04-06-PLAN.md — TemplateService + FolderStructureService
- [ ] 04-07-PLAN.md — Localization + shared dialogs + example CSV resources
- [ ] 04-08-PLAN.md — TransferViewModel + TransferView
- [ ] 04-09-PLAN.md — BulkMembersViewModel + BulkSitesViewModel + FolderStructureViewModel + Views
- [ ] 04-10-PLAN.md — TemplatesViewModel + TemplatesView + DI registration + MainWindow wiring + visual checkpoint
### Phase 5: Distribution and Hardening
**Goal**: The application ships as a single self-contained EXE that runs on a machine with no .NET runtime installed, all previously identified reliability constraints are verified end-to-end (5,000-item pagination, JSON corruption recovery, throttling retry, cancellation), and the French locale is complete and tested.
**Depends on**: Phase 4
**Requirements**: FOUND-11
**Success Criteria** (what must be TRUE):
1. Running the published EXE on a clean machine with no .NET runtime installed launches the application and all features function correctly
2. The application recovers gracefully when a SharePoint API call is throttled (429/503) — the user sees a retry progress message and the operation eventually completes or surfaces a clear failure
3. The French locale is complete for all UI strings — no English fallback text appears when the language is set to French
4. A scan against a library with more than 5,000 items returns complete, correct results with no silent truncation verified against a known dataset
**Plans**: 3 plans
Plans:
- [ ] 05-01-PLAN.md — Helper visibility changes + retry/pagination/locale unit tests
- [ ] 05-02-PLAN.md — FR diacritic corrections + self-contained publish configuration
- [ ] 05-03-PLAN.md — Full test suite verification + publish smoke test + human checkpoint
## Progress ## Progress
**Execution Order:** | Phase | Milestone | Plans | Status | Completed |
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 |-------|-----------|-------|--------|-----------|
| 1-5 | v1.0 | 36/36 | Shipped | 2026-04-07 |
| Phase | Plans Complete | Status | Completed | | 6-9 | v1.1 | 25/25 | Shipped | 2026-04-08 |
|-------|----------------|--------|-----------|
| 1. Foundation | 8/8 | Complete | 2026-04-02 |
| 2. Permissions | 7/7 | Complete | 2026-04-02 |
| 3. Storage and File Operations | 8/8 | Complete | 2026-04-02 |
| 4. Bulk Operations and Provisioning | 10/10 | Complete | 2026-04-03 |
| 5. Distribution and Hardening | 3/3 | Complete | 2026-04-03 |

View File

@@ -1,204 +1,131 @@
--- ---
gsd_state_version: 1.0 gsd_state_version: 1.0
milestone: v1.0 milestone: v1.1
milestone_name: milestone milestone_name: v1.1 Enhanced Reports
status: completed status: shipped
stopped_at: Completed 05-03-PLAN.md — Phase 5 and project fully complete stopped_at: Milestone archived
last_updated: "2026-04-07T06:51:37.208Z" last_updated: "2026-04-08T00:00:00Z"
last_activity: 2026-04-03Plan 05-03 complete — 134 tests pass, EXE verified, French locale approved by human last_activity: 2026-04-08v1.1 milestone archived and tagged
progress: progress:
total_phases: 5 total_phases: 4
completed_phases: 5 completed_phases: 4
total_plans: 36 total_plans: 25
completed_plans: 36 completed_plans: 25
percent: 100
--- ---
# Project State # Project State
## Project Reference ## Project Reference
See: .planning/PROJECT.md (updated 2026-04-02) See: .planning/PROJECT.md (updated 2026-04-07)
**Core value:** Administrators can audit and manage SharePoint/Teams permissions and storage across multiple client tenants from a single, reliable desktop application. **Core value:** Administrators can audit and manage SharePoint/Teams permissions and storage across multiple client tenants from a single, reliable desktop application.
**Current focus:** Phase 4 — Bulk Operations and Provisioning (not yet planned) **Current focus:** v1.1 Enhanced Reports — global site selection, user access audit, simplified permissions, storage visualization
## Current Position ## Current Position
Phase: 5 of 5 (Distribution and Hardening) — COMPLETE Phase: 9 — Storage Visualization
Plan: 3 of 3 in phase 05 — Integration gate passed, human smoke test approved Plan: 4 of 4
Status: ALL PHASES COMPLETE — application ready for distribution Status: Plan 09-04 complete — StorageViewModel chart unit tests
Last activity: 2026-04-03Plan 05-03 complete — 134 tests pass, EXE verified, French locale approved by human Last activity: 2026-04-07Completed 09-04 (StorageViewModel chart unit tests)
Progress: [██████████] 100% ```
v1.1 Progress: [██████████] 100%
## Phase 3 Wave Structure Phase 6 [x] → Phase 7 [x] → Phase 8 [x] → Phase 9 [x]
```
| Wave | Plans | Autonomous | Description |
|------|-------|------------|-------------|
| 0 | 03-01 | yes | Models, interfaces, export stubs, test scaffolds |
| 1 | 03-02 | yes | StorageService implementation |
| 2 | 03-03, 03-04, 03-06 | yes | Storage exports + Search/Duplicates services + Localization (parallel) |
| 3 | 03-05, 03-07 | yes | Search/Duplicate exports + StorageViewModel/View (parallel) |
| 4 | 03-08 | no (checkpoint) | SearchViewModel + DuplicatesViewModel + Views + visual checkpoint |
## Performance Metrics ## Performance Metrics
**Velocity:** | Metric | v1.0 | v1.1 (running) |
- Total plans completed: 0 |--------|------|----------------|
- Average duration: — | Phases | 5 | 4 planned |
- Total execution time: 0 hours | Plans | 36 | TBD |
| Commits | 164 | 0 |
**By Phase:** | Tests | 134 pass / 22 skip | — |
| Phase 06-global-site-selection P02 | 8 | 1 tasks | 1 files |
| Phase | Plans | Total | Avg/Plan | | Phase 06-global-site-selection P01 | 2 | 2 tasks | 3 files |
|-------|-------|-------|----------| | Phase 06-global-site-selection P03 | 2 | 3 tasks | 5 files |
| - | - | - | - | | Phase 06-global-site-selection P04 | 2 | 3 tasks | 6 files |
| Phase 06-global-site-selection P05 | 2 | 1 tasks | 1 files |
**Recent Trend:** | Phase 07-user-access-audit P01 | 5 | 2 tasks | 3 files |
- Last 5 plans: — | Phase 07-user-access-audit P03 | 2 | 1 tasks | 1 files |
- Trend: — | Phase 07-user-access-audit P02 | 1 | 1 tasks | 1 files |
| Phase 07-user-access-audit P06 | 2 | 2 tasks | 2 files |
*Updated after each plan completion* | Phase 07-user-access-audit P04 | 2 | 1 tasks | 1 files |
| Phase 01-foundation P01 | 4 | 2 tasks | 14 files | | Phase 07-user-access-audit P05 | 4 | 2 tasks | 2 files |
| Phase 01-foundation P02 | 1 | 2 tasks | 7 files | | Phase 07-user-access-audit P07 | 8 | 3 tasks | 7 files |
| Phase 01-foundation P03 | 8 | 2 tasks | 7 files | | Phase 07-user-access-audit P08 | 2 | 2 tasks | 4 files |
| Phase 01-foundation P05 | 4min | 2 tasks | 8 files | | Phase 07-user-access-audit P09 | 6 | 1 tasks | 1 files |
| Phase 01-foundation P04 | 4 | 2 tasks | 4 files | | Phase 07-user-access-audit P10 | 5 | 1 tasks | 1 files |
| Phase 01-foundation P06 | 5 | 2 tasks | 12 files | | Phase 08 P02 | 84 | 1 tasks | 1 files |
| Phase 01-foundation P07 | 3 | 2 tasks | 8 files | | Phase 08 P03 | 77 | 1 tasks | 2 files |
| Phase 01-foundation P08 | 5 | 1 tasks | 1 files | | Phase 08 P04 | 2 | 2 tasks | 2 files |
| Phase 01-foundation P08 | 15 | 2 tasks | 3 files | | Phase 08 P05 | 2 | 2 tasks | 4 files |
| Phase 02-permissions P05 | 1min | 1 tasks | 3 files | | Phase 08 P06 | 2 | 2 tasks | 3 files |
| Phase 02-permissions P03 | 1min | 1 tasks | 5 files | | Phase 09 P01 | 1 | 2 tasks | 3 files |
| Phase 02-permissions P01 | 5min | 2 tasks | 9 files | | Phase 09 P02 | 1 | 1 tasks | 1 files |
| Phase 02-permissions P02 | 7min | 2 tasks | 4 files | | Phase 09 P03 | 573 | 2 tasks | 5 files |
| Phase 02-permissions P04 | 1min | 2 tasks | 2 files | | Phase 09 P04 | 146 | 1 tasks | 2 files |
| Phase 02-permissions P06 | 4min | 2 tasks | 6 files |
| Phase 02-permissions P07 | 30min | 2 tasks | 6 files |
| Phase 03-storage P01 | 10min | 2 tasks | 22 files |
| Phase 03-storage P03 | 2min | 2 tasks | 2 files |
| Phase 03-storage P06 | 5min | 1 tasks | 3 files |
| Phase 03-storage P04 | 2min | 2 tasks | 2 files |
| Phase 03-storage P07 | 4min | 2 tasks | 10 files |
| Phase 03-storage P05 | 4min | 2 tasks | 3 files |
| Phase 03 P08 | 4min | 3 tasks | 9 files |
| Phase 04-bulk-operations-and-provisioning P01 | 7min | 2 tasks | 27 files |
| Phase 04-bulk-operations-and-provisioning P05 | 6min | 2 tasks | 3 files |
| Phase 04-bulk-operations-and-provisioning P03 | 7min | 2 tasks | 2 files |
| Phase 04-bulk-operations-and-provisioning P02 | 25 | 2 tasks | 4 files |
| Phase 04-bulk-operations-and-provisioning P04 | 7min | 2 tasks | 3 files |
| Phase 04-bulk-operations-and-provisioning P06 | 10min | 2 tasks | 4 files |
| Phase 04-bulk-operations-and-provisioning P07 | 15 | 2 tasks | 11 files |
| Phase 04-bulk-operations-and-provisioning P08 | 20min | 2 tasks | 6 files |
| Phase 04-bulk-operations-and-provisioning P10 | 25min | 2 tasks | 7 files |
| Phase 05-distribution-and-hardening P02 | 3min | 2 tasks | 2 files |
| Phase 05-distribution-and-hardening P01 | 2min | 2 tasks | 5 files |
| Phase 05-distribution-and-hardening P03 | 15min | 2 tasks | 0 files |
## Accumulated Context ## Accumulated Context
### Decisions ### Decisions
Decisions are logged in PROJECT.md Key Decisions table. Decisions are logged in PROJECT.md Key Decisions table.
Recent decisions affecting current work:
- Foundation: Use PnP.Framework 1.18.0 (not PnP.Core SDK) — PnP Provisioning Engine lives only in PnP.Framework **v1.1 architectural notes:**
- Foundation: Use MsalCacheHelper for per-tenant token cache serialization — scope IPublicClientApplication per ClientId - Global site selection (Phase 6) changes the toolbar; all tabs must bind to a shared `GlobalSiteSelectionViewModel` or equivalent. Use `WeakReferenceMessenger` for cross-tab site-changed notifications, consistent with v1.0 messenger usage.
- Foundation: Never set PublishTrimmed=true — PnP.Framework and MSAL use reflection; accept ~150-200 MB EXE - Per-tab override (SITE-02) means each `FeatureViewModelBase` subclass stores a nullable local site override; null means "use global".
- Foundation: Establish AsyncRelayCommand + IProgress<T> + CancellationToken patterns before any feature work — retrofitting is the most expensive WPF refactor - Storage Visualization (Phase 9) requires a WPF charting NuGet (LiveCharts2 recommended — actively maintained, WPF-native, self-contained friendly). Wire chart data binding to the existing storage scan result model.
- [Phase 01-foundation]: Upgraded MSAL from 4.83.1 to 4.83.3 — Extensions.Msal 4.83.3 requires MSAL >= 4.83.3; minor patch with no behavioral difference - Self-contained EXE constraint: charting library must not require runtime DLLs outside the publish output.
- [Phase 01-foundation]: Test project targets net10.0-windows with UseWPF=true — required to reference WPF main project; net10.0 is framework-incompatible - [Phase 06-02]: MainWindowViewModel uses Func<Window>? factory for SitePickerDialog and broadcasts GlobalSitesChangedMessage via WeakReferenceMessenger on collection change
- [Phase 01-foundation]: Solution uses .slnx format (new .NET 10 XML solution) — dotnet new sln creates .slnx by default in .NET 10 SDK - [Phase 06-01]: GlobalSitesChangedMessage uses IReadOnlyList<SiteInfo> (snapshot, not ObservableCollection) so receivers cannot mutate sender state
- [Phase 01-foundation]: TenantProfile is a plain mutable class (not record) — System.Text.Json requires settable properties; field names Name/TenantUrl/ClientId match JSON schema exactly - [Phase 06-01]: FeatureViewModelBase.OnGlobalSitesReceived (private) updates GlobalSites then calls OnGlobalSitesChanged (protected virtual) — separates storage from derived class hooks
- [Phase 01-foundation]: SharePointPaginationHelper uses [EnumeratorCancellation] on ct — required for correct WithCancellation() forwarding in async iterators - [Phase 06-03]: Added using SharepointToolbox.Core.Models to MainWindow.xaml.cs for TenantProfile in SitePickerDialog factory lambda
- [Phase 01-foundation]: Explicit System.IO using required in WPF project — WPF temp build project does not include System.IO in implicit usings; all persistence classes need explicit import - [Phase 06-03]: toolbar.selectSites.tooltipDisabled added to resources but not wired in XAML — WPF Button disabled tooltip requires style trigger (deferred)
- [Phase 01-foundation]: SettingsService validates only 'en' and 'fr' language codes — throws ArgumentException for unsupported codes - [Phase 06-global-site-selection]: PermissionsViewModel uses _hasLocalSiteOverride guard for SelectedSites; site picker sets flag, tenant switch resets it
- [Phase 01-foundation]: LoadAsync on corrupt JSON throws InvalidDataException (not silent empty) — explicit failure protects against silent data loss - [Phase 06-global-site-selection]: Single-site VMs use partial void OnSiteUrlChanged to detect local typing; clearing field reverts to global
- [Phase 01-foundation]: Strings.Designer.cs maintained manually — ResXFileCodeGenerator is VS-only, not run by dotnet build; only ResourceManager accessor needed - [Phase 06-global-site-selection]: BulkMembersViewModel confirmed excluded: no SiteUrl field, CSV-driven per-row site URLs
- [Phase 01-foundation]: EmbeddedResource uses Update not Include in SDK-style project — SDK auto-includes all .resx; Include causes NETSDK1022 duplicate error - [Phase 06-global-site-selection]: Test 8 asserts override-reset via next global sites message (not SiteUrl='' — OnSiteUrlChanged re-applies global immediately when cleared)
- [Phase 01-foundation]: MsalClientFactory stores MsalCacheHelper per clientId and exposes GetCacheHelper() — PnP creates its own internal PCA so tokenCacheCallback is the bridge for shared persistent cache - [Phase 06-global-site-selection]: Used reflection to set _hasLocalSiteOverride in PermissionsViewModel test — avoids needing a real SitePickerDialog
- [Phase 01-foundation]: SessionManager is the single holder of ClientContext instances — callers must not store returned contexts - [Phase 07-01]: UserAccessEntry is fully denormalized (one row = one user + one object + one permission) for direct DataGrid binding
- [Phase 01-foundation]: CacheDirectory is a constructor parameter (no-arg defaults to AppData) — enables test isolation without real filesystem writes - [Phase 07-01]: IsHighPrivilege and IsExternalUser pre-computed at scan time; GraphUserResult co-located with IGraphUserSearchService interface
- [Phase 01-foundation]: Interactive login test marked Skip in unit suite — browser/WAM MSAL flow cannot run in automated CI - [Phase 07-03]: Minimum 2-character query guard prevents overly broad Graph API requests
- [Phase 01-foundation]: ObservableRecipient lambda receivers need explicit cast to FeatureViewModelBase for virtual dispatch - [Phase 07-03]: OData single-quote escaping (replace apostrophe with two apostrophes) prevents injection in startsWith filter
- [Phase 01-foundation]: FeatureViewModelBase declared as abstract partial class — CommunityToolkit.Mvvm source generator requires partial keyword - [Phase 07-03]: ConsistencyLevel=eventual and Count=true both required for startsWith on Graph directory objects
- [Phase 01-foundation]: OpenFolderDialog (Microsoft.Win32) used in WPF instead of FolderBrowserDialog (System.Windows.Forms) - [Phase 07-user-access-audit]: TenantProfile.ClientId empty in service — session pre-authenticated at ViewModel level; SessionManager returns cached context by URL key
- [Phase 01-foundation]: LogPanel exposed via GetLogPanel() method — x:Name generates field in XAML partial class, property with same name causes CS0102 - [Phase 07-user-access-audit]: Bidirectional contains matching for user login — handles both plain email and full SharePoint claim formats
- [Phase 01-foundation]: ProfileManagementViewModel dialog factory pattern — ViewModel exposes Func<Window>? OpenProfileManagementDialog set by View layer; avoids Window/DI coupling in ViewModel - [Phase 07-user-access-audit]: UserAccessCsvExportService has two write modes: WriteAsync (per-user files to directory) and WriteSingleFileAsync (combined for SaveFileDialog)
- [Phase 01-foundation]: IServiceProvider injected into MainWindow constructor — resolves DI-registered ProfileManagementDialog and SettingsView at runtime - [Phase 07-user-access-audit]: HTML sortTable() scoped per group so sorting in by-user view keeps each user's rows together
- [Phase 01-foundation]: ProfileManagementDialog and SettingsView registered as Transient — fresh instance with fresh ViewModel per dialog open or tab init - [Phase 07-04]: CollectionViewSource bound at construction; ApplyGrouping() swaps PropertyGroupDescription between UserLogin/SiteUrl on IsGroupByUser toggle
- [Phase 01-foundation]: Solution file is .slnx (not .sln) — dotnet build/test commands must use SharepointToolbox.slnx - [Phase 07-04]: ExportCsvAsync uses WriteSingleFileAsync (combined file) not WriteAsync (per-user directory) to match SaveFileDialog single-path UX
- [Phase 01-foundation]: 45 tests total: 44 pass, 1 skip (interactive MSAL GetOrCreateContextAsync_CreatesContext — browser/WAM flow excluded from automated suite) - [Phase 07-05]: Autocomplete ListBox visibility managed via code-behind CollectionChanged — WPF DataTrigger cannot compare to non-zero Count without converter
- [Phase 02-permissions]: DeriveAdminUrl is internal static — enables direct unit testing of admin URL regex without live tenant - [Phase 07-05]: Simple ListBox autocomplete (not Popup) following plan's recommended simpler alternative — avoids Popup placement issues
- [Phase 02-permissions]: InternalsVisibleTo added to AssemblyInfo.cs — required for test project to access internal DeriveAdminUrl; plan omitted this assembly attribute - [Phase 07-user-access-audit]: Dialog factory wiring in MainWindow.xaml.cs by casting auditView.DataContext to UserAccessAuditViewModel — matches PermissionsView pattern
- [Phase 02-permissions]: Export service stubs created in Plan 02-01 so test project compiles before Plan 03 implementation - [Phase 07-user-access-audit]: UserAccessAuditView created inline (Rule 3) when 07-05 found missing — follows 07-05 spec with two-panel layout
- [Phase 02-permissions]: Principal.Email removed from CSOM load expression — Email only exists on User subtype, not Principal base class - [Phase 07-user-access-audit]: Used internal TestRunOperationAsync for ViewModel tests; Application.Current null in tests lets else branch run synchronously
- [Phase 02-permissions]: Folder is not a SecurableObject in CSOM — ListItem used for permission extraction — Required by CSOM type system; Folder inherits from ClientObject not SecurableObject - [Phase 07-user-access-audit]: WeakReferenceMessenger.Default.Reset() in test constructor prevents cross-test contamination from message registrations
- [Phase 02-permissions]: Principal.Email excluded from CSOM Include — email not needed for PermissionEntry — Principal base type has no Email property; only User subtype does; avoids CS1061 - [Phase 07-09]: Guest badge (orange pill) and warning icon (⚠) use DataTrigger-driven Visibility on DataGridTemplateColumn cells — collapsed by default, visible only when IsExternalUser/IsHighPrivilege=True
- [Phase 02-permissions]: CsvExportService uses UTF-8 with BOM for Excel compatibility; HtmlExportService uses UTF-8 without BOM - [Phase 07-10]: Extended CreateViewModel to 3-tuple (vm, auditMock, graphMock) so debounce test can verify SearchUsersAsync calls
- [Phase 02-permissions]: ISessionManager interface extracted from concrete SessionManager — required for Moq-based unit testing of PermissionsViewModel - [Phase 08]: ActiveItemsSource returns Results or SimplifiedResults based on IsSimplifiedMode -- View binds to single property
- [Phase 02-permissions]: PermissionsView code-behind wires Func<TenantProfile, SitePickerDialog> factory via DI — avoids Window coupling in ViewModel, keeps ViewModel testable - [Phase 08]: InvertBoolConverter in Core/Converters namespace for reuse; summary cards use WrapPanel; row color triggers only match SimplifiedPermissionEntry
- [Phase 02-permissions]: ISessionManager -> SessionManager DI registration was missing from App.xaml.cs — added in plan 02-07 (auto-detected Rule 3 blocker) - [Phase 08]: FR translations use XML entities for accented chars matching existing resx convention
- [Phase 02-permissions]: MainWindow.xaml uses x:Name on Permissions TabItem; MainWindow.xaml.cs sets Content at runtime from DI — same pattern as SettingsView - [Phase 09-01]: LiveChartsCore.SkiaSharpView.WPF 2.0.0-rc5.4 added as charting library; SkiaSharp backend for self-contained EXE compatibility
- [Phase 03-storage]: Storage display uses flat DataGrid with IndentLevel -> Margin IValueConverter (not WPF TreeView) — better UI virtualization for large sites - [Phase 09-01]: FileTypeMetric record uses Extension (with dot), TotalSizeBytes (long), FileCount (int), DisplayLabel (computed) matching existing model patterns
- [Phase 03-storage]: StorageNode.VersionSizeBytes is a derived property (TotalSizeBytes - FileStreamSizeBytes, Math.Max 0) — not stored separately - [Phase 09-01]: CollectFileTypeMetricsAsync omits StorageScanOptions since file-type scan covers all non-hidden libraries without folder depth filtering
- [Phase 03-storage]: SearchService uses KeywordQuery + SearchExecutor (Microsoft.SharePoint.Client.Search.Query) — transitive dep of PnP.Framework; no new NuGet package - [Phase 09-02]: Added System.IO using explicitly -- WPF project implicit usings do not include System.IO for Path.GetExtension
- [Phase 03-storage]: Search pagination: StartRow += 500, hard cap StartRow <= 50,000 (SharePoint Search boundary) = 50,000 max results - [Phase 09]: Used wrapper Grid elements with MultiDataTrigger for LiveCharts2 chart visibility -- more reliable than styling third-party controls directly
- [Phase 03-storage]: DuplicatesService uses CAML FSObjType=1 (not FileSystemObjectType) for folder queries — wrong name returns zero results silently
- [Phase 03-storage]: Duplicate detection uses composite key grouping (name+size+dates), no content hashing — matches PS reference and DUPL-01/02/03 requirements exactly
- [Phase 03-storage]: Phase 3 export services are separate classes from Phase 2 (StorageCsvExportService, SearchCsvExportService, etc.) — different schemas
- [Phase 03-storage]: StorageNode.VersionSizeBytes is a derived property (Math.Max(0, TotalSizeBytes - FileStreamSizeBytes)) — not stored separately
- [Phase 03-storage]: MakeKey composite key logic tested inline in DuplicatesServiceTests before Plan 03-04 creates the real class — avoids skipping all duplicate logic tests
- [Phase 03-storage]: Export service stubs return string.Empty until implemented — compile-only skeletons for Plans 03-03 and 03-05
- [Phase 03-storage 03-02]: StorageService.LastModified uses StorageMetrics.LastModified with fallback to Folder.TimeLastModified — StorageMetrics.LastModified may be DateTime.MinValue for empty libraries
- [Phase 03-storage 03-02]: System folder filter uses Forms/ and _-prefix heuristic — matches SharePoint standard hidden folder naming convention
- [Phase 03-storage]: Explicit System.IO using required in StorageCsvExportService and StorageHtmlExportService — WPF project does not include System.IO in implicit usings (established project pattern)
- [Phase 03-storage 03-04]: SearchService uses SelectProperties.Add per-item loop — StringCollection has no AddRange(string[]) overload in this SDK version
- [Phase 03-storage 03-04]: DuplicatesService.MakeKey internal static method matches inline test helper in DuplicatesServiceTests exactly — deliberate design to ensure test parity
- [Phase 03-storage 03-04]: DuplicatesService file mode re-implements pagination inline — avoids coupling between services with different result models (DuplicateItem vs SearchResult)
- [Phase 03-storage]: ClientContext.Url is read-only in CSOM — site URL override done via new TenantProfile with site URL for GetOrCreateContextAsync
- [Phase 03-storage]: IndentConverter/BytesConverter/InverseBoolConverter registered in App.xaml Application.Resources — accessible to all views without per-UserControl declaration
- [Phase 03-storage]: SearchCsvExportService uses UTF-8 BOM for Excel compatibility — consistent with Phase 2 CsvExportService pattern
- [Phase 03-storage]: DuplicatesHtmlExportService always uses badge-dup (red) for all groups — ok/diff distinction removed from final DUPL-03 spec
- [Phase 03]: SearchViewModel and DuplicatesViewModel use TenantProfile site URL override pattern — ctx.Url is read-only in CSOM (established pattern from StorageViewModel)
- [Phase 03]: DuplicateRow flat DTO wraps DuplicateItem with GroupName and GroupSize for DataGrid display
- [Phase 04-bulk-operations-and-provisioning]: ITemplateService uses ModelSiteTemplate alias — SiteTemplate is ambiguous between SharepointToolbox.Core.Models and Microsoft.SharePoint.Client; resolved with using alias
- [Phase 04-bulk-operations-and-provisioning]: ICsvValidationService and BulkResultCsvExportService require explicit System.IO using — WPF project does not include System.IO in implicit usings (established project pattern)
- [Phase 04-bulk-operations-and-provisioning]: BulkSiteService uses Core.Helpers.ExecuteQueryRetryHelper not Infrastructure.Auth — plan had wrong using; correct namespace is Core.Helpers (established pattern from Phase 2/3 services)
- [Phase 04-bulk-operations-and-provisioning]: Design-time MSBuild compile used for build verification — dotnet build WinFX BAML step fails in bash shell; C# compilation verified via dotnet msbuild -t:Compile -p:DesignTimeBuild=true with 0 errors; DLL-based test run confirms 4 pass / 3 skip
- [Phase 04-bulk-operations-and-provisioning]: CsvValidationService uses DetectDelimiter=true — handles both comma and semicolon CSV files without format-specific code paths
- [Phase 04-bulk-operations-and-provisioning]: TemplateRepository uses same atomic write pattern as SettingsRepository (tmp + File.Move + round-trip JSON validation)
- [Phase 04-bulk-operations-and-provisioning 04-04]: GraphClientFactory uses GetOrCreateAsync (async) — MsalClientFactory only exposes async method with SemaphoreSlim locking; plan had incorrect sync reference GetOrCreateClient
- [Phase 04-bulk-operations-and-provisioning 04-04]: AuthGraphClientFactory alias resolves CS0104 — Microsoft.Graph.GraphClientFactory conflicts with SharepointToolbox.Infrastructure.Auth.GraphClientFactory when both namespaces imported
- [Phase 04-bulk-operations-and-provisioning 04-04]: Microsoft.SharePoint.Client.Group? fully qualified in AddToClassicGroupAsync — Microsoft.Graph.Models.Group also in scope in BulkMemberService; explicit namespace resolves ambiguity
- [Phase 04-bulk-operations-and-provisioning]: TemplateService uses ModelSiteTemplate alias — consistent with ITemplateService; CSOM SiteTemplate and Core.Models.SiteTemplate are both in scope
- [Phase 04-bulk-operations-and-provisioning]: FolderStructureService.BuildUniquePaths sorts by slash count for parent-first ordering — ensures intermediate folders exist before children when using Folders.Add
- [Phase 04-bulk-operations-and-provisioning]: TemplateService.ApplyTemplateAsync creates new ClientContext for new site URL — adminCtx.Url points to admin site, new site needs separate context
- [Phase 04-bulk-operations-and-provisioning]: FolderBrowserDialog uses Core.Helpers.ExecuteQueryRetryHelper (not Infrastructure.Auth) — consistent with established project namespace pattern
- [Phase 04-bulk-operations-and-provisioning]: Example CSV files placed in Resources/ and registered as EmbeddedResource — accessible via Assembly.GetManifestResourceStream without file system dependency
- [Phase 04-bulk-operations-and-provisioning]: SitePickerDialog.SelectedUrls.FirstOrDefault() used for single-site transfer selection — avoids new dialog variant while reusing existing dialog
- [Phase 04-bulk-operations-and-provisioning]: EnumBoolConverter added for RadioButton-to-enum binding (TransferMode); ConverterParameter=EnumValueName pattern established for future ViewModels
- [Phase 04-bulk-operations-and-provisioning 04-09]: BulkMembersViewModel passes _currentProfile.ClientId to AddMembersAsync — IBulkMemberService interface requires clientId for Graph API authentication; plan code omitted this parameter
- [Phase 04-bulk-operations-and-provisioning 04-09]: Duplicate standalone converter files (EnumBoolConverter.cs etc.) removed — already defined in IndentConverter.cs which is the established project pattern for all converters
- [Phase 04-bulk-operations-and-provisioning]: TemplatesView uses RenameInputDialog (custom WPF Window) instead of Microsoft.VisualBasic.Interaction.InputBox — avoids additional framework dependency
- [Phase 04-bulk-operations-and-provisioning]: All value converters (EnumBoolConverter, StringToVisibilityConverter, ListToStringConverter) added to IndentConverter.cs — consistent co-location pattern
- [Phase 05-distribution-and-hardening]: PublishSingleFile PropertyGroup is conditional on '' == 'true' — regular dotnet build and dotnet test are unaffected
- [Phase 05-distribution-and-hardening]: IncludeNativeLibrariesForSelfExtract=true required — PnP.Framework has native binaries that must bundle into the EXE
- [Phase 05-distribution-and-hardening]: IsThrottleException only checks top-level Message (not InnerException) — documented via nested-throttle test asserting false
- [Phase 05-distribution-and-hardening]: FR diacritics already present in Strings.fr.resx — FrStrings_ContainExpectedDiacritics test passes immediately (no diacritic repair needed for those 5 keys)
### Pending Todos ### Pending Todos
None yet. 1. Add global multi-site selection option (ui) — `todos/pending/2026-04-07-add-global-multi-site-selection-option.md`**addressed by Phase 6**
### Blockers/Concerns ### Blockers/Concerns
- Phase 4 planning: PnP Provisioning Engine behavior for Teams-connected modern sites — edge cases need validation spike before planning None.
- Phase 5: User access export (v2 requirement UACC-01/02) depends on Phase 2 PermissionsService — confirm scope before Phase 5 planning
## Session Continuity ## Session Continuity
Last session: 2026-04-03T15:00:00.000Z Last session: 2026-04-07T13:40:30Z
Stopped at: Completed 05-03-PLAN.md — Phase 5 and project fully complete Stopped at: Completed 09-04-PLAN.md
Resume file: None Resume file: None

View File

@@ -0,0 +1,89 @@
---
status: awaiting_human_verify
trigger: "SitePickerDialog shows 'Must specify valid information for parsing in the string' error when trying to load sites after a successful tenant connection."
created: 2026-04-07T00:00:00Z
updated: 2026-04-07T00:00:00Z
---
## Current Focus
hypothesis: ROOT CAUSE CONFIRMED — two bugs in SiteListService.GetSitesAsync
test: code reading confirmed via PnP source
expecting: fixing both issues will resolve the error
next_action: apply fix to SiteListService.cs
## Symptoms
expected: After connecting to a SharePoint tenant (https://contoso.sharepoint.com format), clicking "Select Sites" opens SitePickerDialog and loads the list of tenant sites.
actual: SitePickerDialog opens but shows error "Must specify valid information for parsing in the string" instead of loading sites.
errors: "Must specify valid information for parsing in the string" — this is an ArgumentException thrown by CSOM when it tries to parse an empty string as a site URL cursor
reproduction: 1) Launch app 2) Add profile with valid tenant URL 3) Connect 4) Authenticate 5) Click Select Sites 6) Error appears in StatusText
started: First time testing this flow after Phase 6 wiring was added.
## Eliminated
- hypothesis: Error comes from PnP's AuthenticationManager.GetContextAsync URI parsing
evidence: GetContextAsync line 1090 does new Uri(siteUrl) which is valid for "https://contoso-admin.sharepoint.com"
timestamp: 2026-04-07
- hypothesis: Error from MSAL constructing auth URL with empty component
evidence: MSAL uses organizations authority or tenant-specific, both valid; no empty strings involved
timestamp: 2026-04-07
- hypothesis: UriFormatException from new Uri("") in our own code
evidence: No Uri.Parse or new Uri() calls in SiteListService or SessionManager
timestamp: 2026-04-07
## Evidence
- timestamp: 2026-04-07
checked: PnP Framework 1.18.0 GetContextAsync source (line 1090)
found: Calls new Uri(siteUrl) — valid for admin URL
implication: Error not from GetContextAsync itself
- timestamp: 2026-04-07
checked: PnP TenantExtensions.GetSiteCollections source
found: Uses GetSitePropertiesFromSharePointByFilters with StartIndex = null (for first page); OLD commented-out approach used GetSitePropertiesFromSharePoint(null, includeDetail) — note: null, not ""
implication: SiteListService passes "" which is wrong — should be null for first page
- timestamp: 2026-04-07
checked: Error message "Must specify valid information for parsing in the string"
found: This is ArgumentException thrown by Enum.Parse or string cursor parsing when given "" (empty string); CSOM's GetSitePropertiesFromSharePoint internally parses the startIndex string as a URL/cursor; passing "" triggers parse failure
implication: Direct cause of exception confirmed
- timestamp: 2026-04-07
checked: How PnP creates admin context from regular context
found: PnP uses clientContext.Clone(adminSiteUrl) — clones existing authenticated context to admin URL without triggering new auth flow
implication: SiteListService creates a SECOND AuthenticationManager and triggers second interactive login unnecessarily; should use Clone instead
## Resolution
root_cause: |
SiteListService.GetSitesAsync has two bugs:
BUG 1 (direct cause of error): Line 50 calls tenant.GetSitePropertiesFromSharePoint("", true)
with empty string "". CSOM expects null for the first page (no previous cursor), not "".
Passing "" causes CSOM to attempt parsing it as a URL cursor, throwing
ArgumentException: "Must specify valid information for parsing in the string."
BUG 2 (design problem): GetSitesAsync creates a separate TenantProfile for the admin URL
and calls SessionManager.GetOrCreateContextAsync(adminProfile) which creates a NEW
AuthenticationManager with interactive login. This triggers a SECOND browser auth flow
just to access the admin URL. The correct approach is to clone the existing authenticated
context to the admin URL using clientContext.Clone(adminUrl), which reuses the same tokens.
fix: |
1. Replace GetOrCreateContextAsync(adminProfile) with GetOrCreateContextAsync(profile) to
get the regular context, then clone it to the admin URL.
2. Replace GetSitePropertiesFromSharePointByFilters with proper pagination (StartIndex=null).
The admin URL context is obtained via: adminCtx = ctx.Clone(adminUrl)
The site listing uses: GetSitePropertiesFromSharePointByFilters with proper filter object.
verification: |
Build succeeds (0 errors). 144 tests pass, 0 failures.
Fix addresses both root causes:
1. No longer calls GetOrCreateContextAsync with admin profile — uses Clone() instead
2. Uses GetSitePropertiesFromSharePointByFilters (modern API) instead of GetSitePropertiesFromSharePoint("")
files_changed:
- SharepointToolbox/Services/SiteListService.cs

View File

@@ -0,0 +1,155 @@
# Milestone Audit: SharePoint Toolbox v2 — v1 Release
**Audited:** 2026-04-07
**Milestone:** v1 (5 phases, 42 requirements)
**Verdict:** PASSED — all requirements satisfied, all phases integrated, build and tests green
---
## Phase Verification Summary
| Phase | Status | Score | Verification |
|-------|--------|-------|-------------|
| 01 — Foundation | PASSED | 11/11 | 01-VERIFICATION.md |
| 02 — Permissions | HUMAN_NEEDED | 7/7 automated | 02-VERIFICATION.md (2 human items pending) |
| 03 — Storage & File Ops | **MISSING** | No VERIFICATION.md | Summaries exist for all 8 plans; integration checker confirmed all wiring |
| 04 — Bulk Ops & Provisioning | HUMAN_NEEDED | 12/12 automated | 04-VERIFICATION.md (7 human items pending) |
| 05 — Distribution & Hardening | HUMAN_NEEDED | 6/6 automated | 05-VERIFICATION.md (2 human items pending) |
### Gap: Phase 03 Missing Verification
Phase 03 has no `03-VERIFICATION.md` file. All 8 plan summaries exist and confirm code was delivered. The integration checker independently verified:
- All 3 Phase 3 service interfaces (IStorageService, ISearchService, IDuplicatesService) registered in DI
- All 5 export services registered and wired to ViewModels
- All 4 Phase 3 tabs (Storage, Search, Duplicates + exports) wired in MainWindow
- 13 Phase 3 requirements (STOR-0105, SRCH-0104, DUPL-0103) covered
**Recommendation:** Run a retroactive phase verification for Phase 03 or accept integration checker evidence as sufficient.
---
## Requirements Coverage
All 42 v1 requirements are marked complete in REQUIREMENTS.md with phase traceability:
| Category | IDs | Count | Status |
|----------|-----|-------|--------|
| Foundation | FOUND-01 to FOUND-12 | 12 | All SATISFIED |
| Permissions | PERM-01 to PERM-07 | 7 | All SATISFIED |
| Storage | STOR-01 to STOR-05 | 5 | All SATISFIED |
| File Search | SRCH-01 to SRCH-04 | 4 | All SATISFIED |
| Duplicates | DUPL-01 to DUPL-03 | 3 | All SATISFIED |
| Templates | TMPL-01 to TMPL-04 | 4 | All SATISFIED |
| Folder Structure | FOLD-01 to FOLD-02 | 2 | All SATISFIED |
| Bulk Operations | BULK-01 to BULK-05 | 5 | All SATISFIED |
| **Total** | | **42** | **42/42 mapped and complete** |
**Orphaned requirements:** None
**Unmapped requirements:** None
---
## Cross-Phase Integration
Integration checker ran full verification. Results:
| Check | Status |
|-------|--------|
| DI wiring (all 5 phases) | PASS — all services registered in App.xaml.cs |
| MainWindow tabs (10 tabs) | PASS — all declared and wired from DI |
| FeatureViewModelBase inheritance (10 VMs) | PASS |
| SessionManager usage (9 ViewModels + SiteListService) | PASS |
| ExecuteQueryRetryHelper (9 CSOM services, 40+ call sites) | PASS |
| SharePointPaginationHelper (2 services using list enumeration) | PASS |
| TranslationSource localization (15 XAML files, 170 bindings) | PASS |
| TenantSwitchedMessage propagation | PASS |
| Export chain completeness (all features) | PASS |
| Build | PASS — 0 warnings, 0 errors |
| Tests | PASS — 134 passed, 22 skipped (live CSOM), 0 failed |
| EN/FR key parity | PASS — 199/199 keys |
**Orphaned code:** `FeatureTabBase.xaml` — Phase 1 placeholder, now superseded by full tab views. Harmless dead code.
---
## Tech Debt & Deferred Items
### From Phase Verifications
| Item | Source | Severity | Description |
|------|--------|----------|-------------|
| Hardcoded export button text | Phase 2 | Info | `PermissionsView.xaml` uses `Content="Export CSV"` / `"Export HTML"` instead of `rad.csv.perms` / `rad.html.perms` localization keys. French users see English button labels. |
| Missing Designer.cs property | Phase 2 | Info | `Strings.Designer.cs` lacks `tab_permissions` typed accessor. Runtime binding via `TranslationSource` works fine. |
| No invalid-row highlighting | Phase 4 | Warning | `BulkMembersView.xaml`, `BulkSitesView.xaml`, `FolderStructureView.xaml` show IsValid as text column but lack `RowStyle` + `DataTrigger` for visual red highlighting on invalid rows. |
| FeatureTabBase dead code | Phase 1→all | Info | `Views/Controls/FeatureTabBase.xaml` is no longer imported by any tab view after all phases replaced stubs. |
| Cancel test locale mismatch | Phase 3 (03-08) | Info | `FeatureViewModelBaseTests.CancelCommand_DuringOperation_SetsStatusMessageToCancelled` asserts `.Contains("cancel")` but app returns French string "Opération annulée". Pre-existing; deferred. |
### Deferred v2 Requirements
These are explicitly out of scope for v1 and tracked in REQUIREMENTS.md:
- UACC-01/02: User access audit across sites
- SIMP-01/02/03: Simplified plain-language permission reports
- VIZZ-01/02/03: Storage metrics graphs (pie/bar chart)
---
## Human Verification Backlog
11 items across 3 phases require human confirmation (runtime UI/locale checks that cannot be automated):
### Phase 2 (2 items)
1. Full Permissions tab UI visual checkpoint (layout, disabled states, French locale)
2. Export button localization decision (accept hardcoded English or bind to resx keys)
### Phase 4 (7 items)
1. Application launches with all 10 tabs visible
2. Bulk Members — Load Example populates DataGrid with 7 rows
3. Bulk Sites — semicolon CSV auto-detection works
4. Invalid row display in DataGrid (IsValid=False, Errors column)
5. Confirmation dialog appears before bulk operations
6. Transfer tab — two-step browse flow (SitePickerDialog → FolderBrowserDialog)
7. Templates tab — 5 capture checkboxes visible and checked by default
### Phase 5 (2 items)
1. Clean-machine EXE launch (no .NET runtime installed)
2. French locale runtime rendering (diacritics display correctly in all tabs)
---
## Build & Test Summary
| Metric | Value |
|--------|-------|
| Build | 0 errors, 0 warnings |
| Tests passed | 134 |
| Tests skipped | 22 (live CSOM — expected) |
| Tests failed | 0 |
| EN locale keys | 199 |
| FR locale keys | 199 |
| Published EXE | 200.9 MB self-contained |
| Phases complete | 5/5 |
| Requirements satisfied | 42/42 |
---
## Verdict
**PASSED** — The milestone has achieved its definition of done:
1. All 42 v1 requirements are implemented with real code and verified by phase-level checks
2. All cross-phase integration points are wired (DI, messaging, shared infrastructure)
3. Build compiles cleanly with zero warnings
4. 134 automated tests pass with zero failures
5. Self-contained 200.9 MB EXE produced successfully
6. Full EN/FR locale parity (199 keys each)
**Remaining actions before shipping:**
- [ ] Complete 11 human verification items (UI visual checks, clean-machine launch)
- [ ] Decide on Phase 03 retroactive verification (or accept integration check as sufficient)
- [ ] Address 3 Warning-level tech debt items (invalid-row highlighting in bulk DataGrids)
- [ ] Optionally clean up FeatureTabBase dead code and fix cancel test locale mismatch
---
*Audited: 2026-04-07*
*Auditor: Claude (milestone audit)*

View File

@@ -1,3 +1,12 @@
# Requirements Archive: v1.0 MVP
**Archived:** 2026-04-07
**Status:** SHIPPED
For current requirements, see `.planning/REQUIREMENTS.md`.
---
# Requirements: SharePoint Toolbox v2 # Requirements: SharePoint Toolbox v2
**Defined:** 2026-04-02 **Defined:** 2026-04-02

View File

@@ -0,0 +1,147 @@
# Roadmap: SharePoint Toolbox v2
## Overview
A full C#/WPF rewrite of a 6,400-line PowerShell-based SharePoint Online administration tool. The
project delivers a self-contained Windows desktop application that lets MSP administrators audit
and manage permissions, storage, and site provisioning across multiple client tenants from a single
tool. Foundation infrastructure (multi-tenant auth, async patterns, error handling, DI) must be
solid before any feature work begins — all 10 identified pitfalls in the existing codebase map
entirely to Phase 1. Subsequent phases deliver complete, verifiable feature areas in dependency
order: permissions first (validates auth and pagination), then storage and search (reuse those
patterns), then bulk and provisioning operations (highest write-risk features last), then
hardening and packaging.
## Phases
**Phase Numbering:**
- Integer phases (1, 2, 3): Planned milestone work
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
Decimal phases appear between their surrounding integers in numeric order.
- [x] **Phase 1: Foundation** - WPF shell, multi-tenant auth, DI, async patterns, error handling, logging, localization, JSON persistence (completed 2026-04-02)
- [x] **Phase 2: Permissions** - Permissions scan (single and multi-site), CSV and HTML report export
- [x] **Phase 3: Storage and File Operations** - Storage metrics, file search, and duplicate detection (completed 2026-04-02)
- [x] **Phase 4: Bulk Operations and Provisioning** - Bulk member/site/transfer operations, site templates, folder structure provisioning (completed 2026-04-03)
- [x] **Phase 5: Distribution and Hardening** - Self-contained EXE packaging, end-to-end validation, FR locale completeness (completed 2026-04-03)
## Phase Details
### Phase 1: Foundation
**Goal**: The application shell runs, users can authenticate to multiple tenants and switch between them without re-logging in, all long-running operations are cancellable and report progress, all errors surface visibly, and the infrastructure patterns that prevent the existing app's 10 known pitfalls are in place before any feature work begins.
**Depends on**: Nothing (first phase)
**Requirements**: FOUND-01, FOUND-02, FOUND-03, FOUND-04, FOUND-05, FOUND-06, FOUND-07, FOUND-08, FOUND-09, FOUND-10, FOUND-12
**Success Criteria** (what must be TRUE):
1. User can create, rename, delete, and switch between tenant profiles via the UI — each profile stores tenant URL, client ID, and display name in a JSON file
2. User can authenticate to a tenant via interactive browser login and the session persists across tenant switches without re-entering credentials (MSAL token cache per tenant)
3. User can see real-time progress on any long-running operation and cancel it mid-execution with a button — the operation stops cleanly with no silent continuation
4. When any operation fails, the user sees an actionable error message in the UI — no operation fails silently or swallows an exception
5. UI language switches between English and French dynamically without restarting the application
**Plans**: 8 plans
Plans:
- [ ] 01-01-PLAN.md — Solution scaffold: WPF project + xUnit test project with Generic Host entry point
- [ ] 01-02-PLAN.md — Core layer: models, messages, pagination helper, retry helper, LogPanelSink
- [ ] 01-03-PLAN.md — Persistence layer: ProfileRepository + SettingsRepository + services + unit tests
- [ ] 01-04-PLAN.md — Auth layer: MsalClientFactory + SessionManager + unit tests
- [ ] 01-05-PLAN.md — Localization + Serilog: TranslationSource, EN/FR resx, integration tests
- [ ] 01-06-PLAN.md — ViewModels + WPF shell: FeatureViewModelBase, MainWindow XAML, global exception handlers
- [ ] 01-07-PLAN.md — UI dialogs: ProfileManagementDialog + SettingsView wired into shell
- [ ] 01-08-PLAN.md — Checkpoint: full test suite + visual verification of running application
### Phase 2: Permissions
**Goal**: Users can scan SharePoint permissions on one or many sites and export the results as both a raw CSV and a sortable, filterable HTML report — with no silent failures on large libraries and full control over scan scope.
**Depends on**: Phase 1
**Requirements**: PERM-01, PERM-02, PERM-03, PERM-04, PERM-05, PERM-06, PERM-07
**Success Criteria** (what must be TRUE):
1. User can select one site or multiple sites and run a permissions scan that returns owners, members, guests, external users, and broken inheritance items
2. User can choose configurable scan depth and whether to include or exclude inherited permissions before running
3. User can export the permissions results to a CSV file with all raw permission data
4. User can export the permissions results to an interactive HTML report where rows are sortable, filterable, and groupable by user
5. Scanning a library with more than 5,000 items completes successfully — the tool paginates automatically and does not silently truncate or fail
**Plans**: 7 plans
Plans:
- [x] 02-01-PLAN.md — Wave 0: test scaffolds (PermissionsService, ViewModel, classification, CSV, HTML export tests) + PermissionEntryHelper
- [x] 02-02-PLAN.md — Core models + PermissionsService scan engine (PermissionEntry, ScanOptions, IPermissionsService, PermissionsService)
- [x] 02-03-PLAN.md — SiteListService: tenant admin site listing for multi-site picker (ISiteListService, SiteListService, SiteInfo)
- [x] 02-04-PLAN.md — Export services: CsvExportService (with row merging) + HtmlExportService (self-contained HTML)
- [x] 02-05-PLAN.md — Localization: 15 Phase 2 EN/FR keys in Strings.resx, Strings.fr.resx, Strings.Designer.cs
- [x] 02-06-PLAN.md — PermissionsViewModel + SitePickerDialog (XAML + code-behind)
- [x] 02-07-PLAN.md — DI wiring + PermissionsView XAML + MainWindow tab replacement + visual checkpoint
### Phase 3: Storage and File Operations
**Goal**: Users can view and export storage metrics per site and library, search for files across sites using multiple criteria, and detect duplicate files and folders — all with consistent export options and no silent failures on large datasets.
**Depends on**: Phase 2
**Requirements**: STOR-01, STOR-02, STOR-03, STOR-04, STOR-05, SRCH-01, SRCH-02, SRCH-03, SRCH-04, DUPL-01, DUPL-02, DUPL-03
**Success Criteria** (what must be TRUE):
1. User can view storage consumption per library and per site (with configurable folder depth), including total size, version size, item count, and last modified date
2. User can export storage metrics to CSV and to an interactive HTML with a collapsible tree view
3. User can search for files across sites using at least extension, name/regex, date range, creator, and editor as criteria — with a configurable result cap up to 50,000 items
4. User can export file search results to CSV and to an interactive sortable/filterable HTML
5. User can scan for duplicate files (by name, size, creation date, modification date) and duplicate folders (by name, subfolder count, file count) and export the results to an HTML with grouped display
**Plans**: 8 plans
Plans:
- [ ] 03-01-PLAN.md — Wave 0: test scaffolds + models (StorageNode, SearchResult, DuplicateGroup/Item, options) + interfaces (IStorageService, ISearchService, IDuplicatesService) + export stubs
- [ ] 03-02-PLAN.md — StorageService: CSOM StorageMetrics scan engine (recursive folder tree, library-level aggregation)
- [ ] 03-03-PLAN.md — Storage export services: StorageCsvExportService + StorageHtmlExportService (collapsible tree HTML)
- [ ] 03-04-PLAN.md — SearchService (KQL pagination, client-side Regex) + DuplicatesService (composite key grouping, CAML folder scan)
- [ ] 03-05-PLAN.md — Search and Duplicate export services: SearchCsvExportService + SearchHtmlExportService + DuplicatesHtmlExportService
- [ ] 03-06-PLAN.md — Localization: all Phase 3 EN/FR keys for Storage, File Search, and Duplicates tabs
- [ ] 03-07-PLAN.md — StorageViewModel + StorageView XAML + DI wiring (Storage tab replaces FeatureTabBase stub)
- [ ] 03-08-PLAN.md — SearchViewModel + SearchView + DuplicatesViewModel + DuplicatesView + DI wiring + visual checkpoint
### Phase 4: Bulk Operations and Provisioning
**Goal**: Users can execute bulk write operations (member additions, site creation, file transfer) with per-item error reporting and cancellation, capture site structures as reusable templates, apply templates to create new sites, and provision folder structures from CSV — all without silent partial failures.
**Depends on**: Phase 3
**Requirements**: BULK-01, BULK-02, BULK-03, BULK-04, BULK-05, TMPL-01, TMPL-02, TMPL-03, TMPL-04, FOLD-01, FOLD-02
**Success Criteria** (what must be TRUE):
1. User can transfer files and folders between sites with real-time progress tracking and can cancel mid-operation — transferred items are confirmed and failures are reported per-item
2. User can add members to groups in bulk from a CSV file — each row that fails is reported individually, not silently skipped
3. User can create multiple sites in bulk from a CSV file with per-site error reporting and mid-operation cancellation
4. User can capture an existing site's structure (libraries, folders, permission groups, logo, settings) as a named template stored in JSON, then apply that template to create a new Communication or Teams site
5. User can manage saved templates (create, rename, delete) and create folder structures on a target site from a CSV template
**Plans**: 10 plans
Plans:
- [ ] 04-01-PLAN.md — Dependencies + core models + interfaces + BulkOperationRunner + test scaffolds
- [ ] 04-02-PLAN.md — CsvValidationService + TemplateRepository with unit tests
- [ ] 04-03-PLAN.md — FileTransferService (CSOM MoveCopyUtil, conflict policies)
- [ ] 04-04-PLAN.md — BulkMemberService (Graph SDK batch + CSOM fallback)
- [ ] 04-05-PLAN.md — BulkSiteService (PnP Framework site creation)
- [ ] 04-06-PLAN.md — TemplateService + FolderStructureService
- [ ] 04-07-PLAN.md — Localization + shared dialogs + example CSV resources
- [ ] 04-08-PLAN.md — TransferViewModel + TransferView
- [ ] 04-09-PLAN.md — BulkMembersViewModel + BulkSitesViewModel + FolderStructureViewModel + Views
- [ ] 04-10-PLAN.md — TemplatesViewModel + TemplatesView + DI registration + MainWindow wiring + visual checkpoint
### Phase 5: Distribution and Hardening
**Goal**: The application ships as a single self-contained EXE that runs on a machine with no .NET runtime installed, all previously identified reliability constraints are verified end-to-end (5,000-item pagination, JSON corruption recovery, throttling retry, cancellation), and the French locale is complete and tested.
**Depends on**: Phase 4
**Requirements**: FOUND-11
**Success Criteria** (what must be TRUE):
1. Running the published EXE on a clean machine with no .NET runtime installed launches the application and all features function correctly
2. The application recovers gracefully when a SharePoint API call is throttled (429/503) — the user sees a retry progress message and the operation eventually completes or surfaces a clear failure
3. The French locale is complete for all UI strings — no English fallback text appears when the language is set to French
4. A scan against a library with more than 5,000 items returns complete, correct results with no silent truncation verified against a known dataset
**Plans**: 3 plans
Plans:
- [ ] 05-01-PLAN.md — Helper visibility changes + retry/pagination/locale unit tests
- [ ] 05-02-PLAN.md — FR diacritic corrections + self-contained publish configuration
- [ ] 05-03-PLAN.md — Full test suite verification + publish smoke test + human checkpoint
## Progress
**Execution Order:**
Phases execute in numeric order: 1 → 2 → 3 → 4 → 5
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Foundation | 8/8 | Complete | 2026-04-02 |
| 2. Permissions | 7/7 | Complete | 2026-04-02 |
| 3. Storage and File Operations | 8/8 | Complete | 2026-04-02 |
| 4. Bulk Operations and Provisioning | 10/10 | Complete | 2026-04-03 |
| 5. Distribution and Hardening | 3/3 | Complete | 2026-04-03 |

View File

@@ -0,0 +1,57 @@
# Requirements Archive: SharePoint Toolbox v1.1 Enhanced Reports
**Defined:** 2026-04-07
**Completed:** 2026-04-08
**Coverage:** 10/10 requirements complete
## Requirements
### Global Site Selection
- [x] **SITE-01**: User can select one or multiple target sites from the toolbar and all feature tabs use that selection as default
- [x] **SITE-02**: User can override global site selection per-tab for single-site operations
- *Outcome: Initially implemented, later removed — per-tab selectors replaced by centralized global-only selection*
### User Access Audit
- [x] **UACC-01**: User can export all SharePoint/Teams accesses a specific user has across selected sites
- [x] **UACC-02**: Export includes direct assignments, group memberships, and inherited access
### Simplified Permissions
- [x] **SIMP-01**: User can toggle plain-language permission labels (e.g., "Can edit files" instead of "Contribute")
- [x] **SIMP-02**: Permissions report includes summary counts and color coding for untrained readers
- [x] **SIMP-03**: User can choose detail level (simple/detailed) for reports
### Storage Visualization
- [x] **VIZZ-01**: Storage Metrics tab includes a graph showing space by file type
- [x] **VIZZ-02**: User can toggle between pie/donut chart and bar chart views
- [x] **VIZZ-03**: Graph updates automatically when storage scan completes
## Traceability
| Requirement | Phase | Status | Notes |
|-------------|-------|--------|-------|
| SITE-01 | Phase 6 | Complete | |
| SITE-02 | Phase 6 | Complete | Per-tab override later removed in favor of global-only |
| UACC-01 | Phase 7 | Complete | |
| UACC-02 | Phase 7 | Complete | |
| SIMP-01 | Phase 8 | Complete | 11 standard SharePoint roles mapped |
| SIMP-02 | Phase 8 | Complete | 4 risk levels: High/Medium/Low/ReadOnly |
| SIMP-03 | Phase 8 | Complete | |
| VIZZ-01 | Phase 9 | Complete | LiveCharts2 SkiaSharp backend |
| VIZZ-02 | Phase 9 | Complete | |
| VIZZ-03 | Phase 9 | Complete | |
## Out of Scope
| Feature | Reason |
|---------|--------|
| Cross-platform (Mac/Linux) | WPF is Windows-only |
| Real-time monitoring / alerts | Requires background service |
| Automated remediation (auto-revoke) | Liability risk |
| Content migration between tenants | Separate product category |
---
*Archived: 2026-04-08*

View File

@@ -0,0 +1,81 @@
# v1.1 Enhanced Reports — Milestone Archive
**Goal:** Add user access audit, simplified permissions, storage visualization, and global multi-site selection
**Status:** Shipped 2026-04-08
**Timeline:** 2026-04-07 to 2026-04-08
## Stats
| Metric | Value |
|--------|-------|
| Phases | 4 (Phases 6-9) |
| Plans | 25 |
| Commits | 29 |
| C# LOC (total) | 10,484 |
| Tests | 205 pass / 22 skip |
| Requirements | 10/10 complete |
## Key Accomplishments
1. **Global Site Selection (Phase 6)** — Toolbar-level multi-site picker consumed by all feature tabs. Per-tab site selectors removed in favor of centralized selection. WeakReferenceMessenger broadcast pattern.
2. **User Access Audit (Phase 7)** — New feature tab: people-picker with Graph API autocomplete, audit every permission a specific user holds across selected sites, distinguish direct/group/inherited access, export to CSV/HTML. Claims prefix stripping for clean display.
3. **Simplified Permissions (Phase 8)** — Plain-language labels mapped from 11 standard SharePoint roles, color-coded risk levels (High/Medium/Low/ReadOnly), summary cards with counts, detail-level toggle (simple/detailed), simplified export overloads for both CSV and HTML.
4. **Storage Visualization (Phase 9)** — LiveCharts2 (SkiaSharp) integration for pie/donut and bar chart views of storage by file type. CamlQuery-based file enumeration to work around StorageMetrics API zeros. Custom single-slice tooltip. Per-library backfill for accurate folder-level metrics. Chart data included in HTML/CSV exports with summary stat cards.
5. **Post-phase Polish** — Removed per-tab site selectors from 8 tabs (centralized to global toolbar), fixed UserAccessAudit DataGrid binding (CollectionViewSource disconnect), added site-level summary totals to Storage tab and HTML reports, suppressed NU1701 NuGet warnings.
## Phases
### Phase 6: Global Site Selection (5 plans)
- GlobalSitesChangedMessage + FeatureViewModelBase extension
- MainWindowViewModel global selection state + command
- Toolbar UI, dialog wiring, and localization keys
- Tab VM updates for global site consumption
- Unit tests for global site selection flow
### Phase 7: User Access Audit (10 plans)
- UserAccessEntry model + service interfaces
- UserAccessAuditService implementation
- GraphUserSearchService implementation
- UserAccessAuditViewModel
- UserAccessAuditView XAML layout
- CSV + HTML export services
- Tab wiring, DI, localization
- Unit tests
- Gap closure: DataGrid visual indicators + ObjectType column
- Gap closure: Debounced search unit test
### Phase 8: Simplified Permissions (6 plans)
- RiskLevel enum, PermissionLevelMapping, SimplifiedPermissionEntry, PermissionSummary
- PermissionsViewModel simplified mode, detail toggle, summary computation
- PermissionsView XAML: toggles, summary panel, color-coded DataGrid
- HTML + CSV export simplified overloads
- Localization keys (EN/FR) + export command wiring
- Unit tests: mapping, summary, ViewModel toggle behavior
### Phase 9: Storage Visualization (4 plans)
- LiveCharts2 NuGet + FileTypeMetric model + IStorageService extension
- StorageService file-type enumeration implementation
- ViewModel chart properties + View XAML + localization
- Unit tests for chart ViewModel behavior
## Requirements Covered
| Requirement | Description | Status |
|-------------|-------------|--------|
| SITE-01 | Global multi-site selection from toolbar | Complete |
| SITE-02 | Per-tab override capability | Complete (later removed — centralized) |
| UACC-01 | Export all user accesses across sites | Complete |
| UACC-02 | Distinguish direct/group/inherited access | Complete |
| SIMP-01 | Plain-language permission labels | Complete |
| SIMP-02 | Summary counts with color coding | Complete |
| SIMP-03 | Detail-level selector | Complete |
| VIZZ-01 | Charting library integration | Complete |
| VIZZ-02 | Toggle pie/donut vs bar chart | Complete |
| VIZZ-03 | Auto-update chart on scan complete | Complete |
---
*Archived: 2026-04-08*

View File

@@ -0,0 +1,187 @@
---
phase: 06-global-site-selection
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- SharepointToolbox/Core/Messages/GlobalSitesChangedMessage.cs
- SharepointToolbox/ViewModels/FeatureViewModelBase.cs
autonomous: true
requirements:
- SITE-01
must_haves:
truths:
- "GlobalSitesChangedMessage exists and follows the same ValueChangedMessage pattern as TenantSwitchedMessage"
- "FeatureViewModelBase registers for GlobalSitesChangedMessage in OnActivated and exposes a protected GlobalSites property"
- "Derived tab VMs can override OnGlobalSitesChanged to react to global site selection changes"
- "Existing TenantSwitchedMessage registration still works (no regression)"
artifacts:
- path: "SharepointToolbox/Core/Messages/GlobalSitesChangedMessage.cs"
provides: "Messenger message for global site selection changes"
contains: "GlobalSitesChangedMessage"
- path: "SharepointToolbox/ViewModels/FeatureViewModelBase.cs"
provides: "Base class with GlobalSites property and OnGlobalSitesChanged virtual method"
contains: "GlobalSites"
key_links:
- from: "SharepointToolbox/ViewModels/FeatureViewModelBase.cs"
to: "SharepointToolbox/Core/Messages/GlobalSitesChangedMessage.cs"
via: "Messenger.Register<GlobalSitesChangedMessage> in OnActivated"
pattern: "Register<GlobalSitesChangedMessage>"
---
<objective>
Create the GlobalSitesChangedMessage and extend FeatureViewModelBase to receive and store global site selections. This establishes the messaging contract that all tab VMs and MainWindowViewModel depend on.
Purpose: Foundation contract — every other plan in this phase builds on this message class and base class extension.
Output: GlobalSitesChangedMessage.cs, updated FeatureViewModelBase.cs
</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/ROADMAP.md
@.planning/STATE.md
@.planning/phases/06-global-site-selection/06-CONTEXT.md
<interfaces>
<!-- Existing message pattern to follow exactly -->
From SharepointToolbox/Core/Messages/TenantSwitchedMessage.cs:
```csharp
using CommunityToolkit.Mvvm.Messaging.Messages;
using SharepointToolbox.Core.Models;
namespace SharepointToolbox.Core.Messages;
public sealed class TenantSwitchedMessage : ValueChangedMessage<TenantProfile>
{
public TenantSwitchedMessage(TenantProfile profile) : base(profile) { }
}
```
From SharepointToolbox/Core/Models/SiteInfo.cs:
```csharp
namespace SharepointToolbox.Core.Models;
public record SiteInfo(string Url, string Title);
```
From SharepointToolbox/ViewModels/FeatureViewModelBase.cs (OnActivated — extend this):
```csharp
protected override void OnActivated()
{
Messenger.Register<TenantSwitchedMessage>(this, (r, m) => ((FeatureViewModelBase)r).OnTenantSwitched(m.Value));
}
protected virtual void OnTenantSwitched(TenantProfile profile)
{
// Derived classes override to reset their state
}
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create GlobalSitesChangedMessage</name>
<files>SharepointToolbox/Core/Messages/GlobalSitesChangedMessage.cs</files>
<action>
Create a new message class following the exact same pattern as TenantSwitchedMessage.
File: `SharepointToolbox/Core/Messages/GlobalSitesChangedMessage.cs`
```csharp
using CommunityToolkit.Mvvm.Messaging.Messages;
using SharepointToolbox.Core.Models;
namespace SharepointToolbox.Core.Messages;
public sealed class GlobalSitesChangedMessage : ValueChangedMessage<IReadOnlyList<SiteInfo>>
{
public GlobalSitesChangedMessage(IReadOnlyList<SiteInfo> sites) : base(sites) { }
}
```
The value type is `IReadOnlyList<SiteInfo>` (not ObservableCollection) because the message carries a snapshot of the current selection — receivers should not mutate the sender's collection.
</action>
<verify>
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
</verify>
<done>GlobalSitesChangedMessage.cs exists in Core/Messages/, compiles without errors, follows the ValueChangedMessage pattern.</done>
</task>
<task type="auto">
<name>Task 2: Extend FeatureViewModelBase with GlobalSites support</name>
<files>SharepointToolbox/ViewModels/FeatureViewModelBase.cs</files>
<action>
Modify FeatureViewModelBase to register for GlobalSitesChangedMessage and store the global sites.
1. Add using directive: `using SharepointToolbox.Core.Models;` (SiteInfo is in Core.Models).
2. Add a protected property to store the global sites (after the existing fields, before RunCommand):
```csharp
/// <summary>
/// Sites selected in the global toolbar picker. Updated via GlobalSitesChangedMessage.
/// Derived VMs check this in RunOperationAsync before falling back to per-tab SiteUrl.
/// </summary>
protected IReadOnlyList<SiteInfo> GlobalSites { get; private set; } = Array.Empty<SiteInfo>();
```
3. In `OnActivated()`, add a second Messenger.Register call for GlobalSitesChangedMessage, right after the existing TenantSwitchedMessage registration:
```csharp
protected override void OnActivated()
{
Messenger.Register<TenantSwitchedMessage>(this, (r, m) => ((FeatureViewModelBase)r).OnTenantSwitched(m.Value));
Messenger.Register<GlobalSitesChangedMessage>(this, (r, m) => ((FeatureViewModelBase)r).OnGlobalSitesReceived(m.Value));
}
```
4. Add a private method that updates the property and calls the virtual hook:
```csharp
private void OnGlobalSitesReceived(IReadOnlyList<SiteInfo> sites)
{
GlobalSites = sites;
OnGlobalSitesChanged(sites);
}
```
5. Add a protected virtual method for derived classes to override:
```csharp
/// <summary>
/// Called when the global site selection changes. Override in derived VMs
/// to update UI state (e.g., pre-fill SiteUrl from first global site).
/// </summary>
protected virtual void OnGlobalSitesChanged(IReadOnlyList<SiteInfo> sites)
{
// Derived classes override to react to global site changes
}
```
Do NOT modify anything in the ExecuteAsync, RunCommand, CancelCommand, or OnTenantSwitched areas. Only add the new GlobalSites infrastructure.
</action>
<verify>
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 && dotnet test SharepointToolbox.Tests/SharepointToolbox.Tests.csproj --no-build 2>&1 | tail -5</automated>
</verify>
<done>FeatureViewModelBase compiles with GlobalSites property, OnGlobalSitesChanged virtual method, and GlobalSitesChangedMessage registration in OnActivated. All existing tests still pass (no regression).</done>
</task>
</tasks>
<verification>
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
- `dotnet test` shows no new failures (existing tests unaffected)
- GlobalSitesChangedMessage.cs exists in Core/Messages/
- FeatureViewModelBase.cs contains `GlobalSites` property and `OnGlobalSitesChanged` virtual method
- OnActivated registers for both TenantSwitchedMessage and GlobalSitesChangedMessage
</verification>
<success_criteria>
The messaging contract is established: GlobalSitesChangedMessage can be sent by any publisher and received by all FeatureViewModelBase subclasses. The protected GlobalSites property and virtual OnGlobalSitesChanged hook are available for tab VMs to override in plan 06-04.
</success_criteria>
<output>
After completion, create `.planning/phases/06-global-site-selection/06-01-SUMMARY.md`
</output>

Some files were not shown because too many files have changed in this diff Show More