---
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<string, IReadOnlyList<ResolvedMember>>? groupMembers` parameter. When provided:

- SharePoint group pills render as `<span class="user-pill group-expandable" onclick="toggleGroup('grpmem0')">` with a down-arrow indicator
- A hidden `<tr data-group="grpmem0" style="display:none">` sub-row is injected immediately after the parent row, containing resolved member display names and logins
- Empty member lists (resolution failed) render `<em style="color:#888">members unavailable</em>`
- `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
