--- phase: 17-group-expansion-html-reports plan: "02" subsystem: html-export tags: [html-export, group-expansion, permissions-viewmodel, tdd] dependency_graph: requires: ["17-01"] provides: ["expandable-group-pills-html", "group-resolver-wired-to-export"] affects: ["HtmlExportService", "PermissionsViewModel"] tech_stack: added: [] patterns: ["optional-parameter-backward-compat", "pre-render-resolution", "inline-js-toggle"] key_files: created: [] modified: - SharepointToolbox/Services/Export/HtmlExportService.cs - SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs - SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs decisions: - "groupMembers added as optional last parameter to both BuildHtml overloads and WriteAsync methods — null produces byte-identical output to pre-Phase 17" - "Group pill expansion uses name-based lookup in groupMembers dict (not login) — matches how SharePoint groups are identified in Users field" - "ISharePointGroupResolver injected as optional last parameter in PermissionsViewModel main constructor — DI works without explicit resolver if not registered" - "Resolution failure logged as Warning and export proceeds without expansion — never blocks user export" metrics: duration_seconds: 204 completed_date: "2026-04-09" tasks_completed: 2 files_modified: 3 --- # Phase 17 Plan 02: Group Expansion Wire-up Summary Expandable SharePoint group pills in HTML reports with toggleGroup JS and PermissionsViewModel pre-render resolution via ISharePointGroupResolver. ## Tasks Completed | Task | Description | Commit | Status | |------|-------------|--------|--------| | 1 (RED) | Failing tests for group pill expansion | c35ee76 | Done | | 1 (GREEN) | HtmlExportService: groupMembers param + expandable pills + toggleGroup JS | 07ed6e2 | Done | | 2 | PermissionsViewModel: ISharePointGroupResolver injection + ExportHtmlAsync wiring | aab3aee | Done | ## What Was Built Both `BuildHtml` overloads in `HtmlExportService` now accept an optional `IReadOnlyDictionary>? groupMembers` parameter. When provided: - SharePoint group pills render as `` with a down-arrow indicator - A hidden `` sub-row is injected immediately after the parent row, containing resolved member display names and logins - Empty member lists (resolution failed) render `members unavailable` - `toggleGroup(id)` JS function is added to the inline script block - `filterTable()` updated to skip `data-group` sub-rows - CSS `.group-expandable` adds cursor pointer and hover opacity `PermissionsViewModel` now injects `ISharePointGroupResolver?` as an optional last constructor parameter. In `ExportHtmlAsync`, before calling `WriteAsync`, it: 1. Collects all distinct group names from `Results` where `PrincipalType == "SharePointGroup"` 2. Calls `_groupResolver.ResolveGroupsAsync(ctx, clientId, groupNames, ct)` if resolver is non-null and groups were found 3. Passes the resulting `groupMembers` dict to both `WriteAsync` call sites (standard and simplified paths) 4. On resolution failure, logs a warning and exports without expansion (graceful degradation) ## Deviations from Plan None — plan executed exactly as written. ## Verification - `dotnet build` — 0 errors, 0 warnings - `dotnet test --filter "FullyQualifiedName~HtmlExportServiceTests"` — 32 tests pass (includes 6 new group expansion tests) - `dotnet test` — 320 passed, 0 failed, 28 skipped (full suite green) ## Self-Check: PASSED Files verified: - SharepointToolbox/Services/Export/HtmlExportService.cs — contains `toggleGroup`, `groupMembers`, `group-expandable` - SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs — contains `_groupResolver`, `ResolveGroupsAsync` - SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs — contains `grpmem` test patterns Commits verified: - c35ee76 — test(17-02): RED phase - 07ed6e2 — feat(17-02): HtmlExportService implementation - aab3aee — feat(17-02): PermissionsViewModel wiring