---
phase: 17-group-expansion-html-reports
plan: 02
type: execute
wave: 2
depends_on: ["17-01"]
files_modified:
- SharepointToolbox/Services/Export/HtmlExportService.cs
- SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs
- SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
autonomous: true
requirements: [RPT-01, RPT-02]
must_haves:
truths:
- "SharePoint group pills in the HTML report are clickable and expand to show group members"
- "When group members are resolved, clicking the pill reveals member names inline"
- "When group resolution fails, the pill expands to show 'members unavailable' label"
- "When no groupMembers dict is passed, HTML output is identical to pre-Phase 17 output"
- "toggleGroup() JS function exists in HtmlExportService inline JS"
- "PermissionsViewModel calls ISharePointGroupResolver before HTML export and passes results to BuildHtml"
artifacts:
- path: "SharepointToolbox/Services/Export/HtmlExportService.cs"
provides: "Expandable group pill rendering + toggleGroup JS"
contains: "toggleGroup"
- path: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs"
provides: "Group resolution orchestration before export"
contains: "_groupResolver"
- path: "SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs"
provides: "Tests for group pill expansion and backward compatibility"
contains: "grpmem"
key_links:
- from: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs"
to: "ISharePointGroupResolver"
via: "constructor injection + call in ExportHtmlAsync"
pattern: "_groupResolver.ResolveGroupsAsync"
- from: "SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs"
to: "HtmlExportService.BuildHtml"
via: "passing groupMembers dict"
pattern: "groupMembers"
- from: "SharepointToolbox/Services/Export/HtmlExportService.cs"
to: "toggleGroup JS"
via: "inline script block"
pattern: "function toggleGroup"
---
Wire group member expansion into HtmlExportService rendering and PermissionsViewModel export flow.
Purpose: This is the user-visible feature — SharePoint group pills become clickable in HTML reports, expanding to show resolved members or a "members unavailable" fallback.
Output: Modified HtmlExportService (both overloads), modified PermissionsViewModel, new HTML export tests.
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/17-group-expansion-html-reports/17-RESEARCH.md
@.planning/phases/17-group-expansion-html-reports/17-01-SUMMARY.md
From SharepointToolbox/Core/Models/ResolvedMember.cs:
```csharp
public record ResolvedMember(string DisplayName, string Login);
```
From SharepointToolbox/Services/ISharePointGroupResolver.cs:
```csharp
public interface ISharePointGroupResolver
{
Task>> ResolveGroupsAsync(
ClientContext ctx, string clientId,
IReadOnlyList groupNames, CancellationToken ct);
}
```
From SharepointToolbox/Services/Export/HtmlExportService.cs:
```csharp
public class HtmlExportService
{
// Overload 1 — standard PermissionEntry
public string BuildHtml(IReadOnlyList entries, ReportBranding? branding = null)
public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct, ReportBranding? branding = null)
// Overload 2 — simplified
public string BuildHtml(IReadOnlyList entries, ReportBranding? branding = null)
public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct, ReportBranding? branding = null)
}
```
From SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs:
```csharp
public partial class PermissionsViewModel : FeatureViewModelBase
{
private readonly HtmlExportService? _htmlExportService;
private readonly ISessionManager _sessionManager;
private readonly IBrandingService? _brandingService;
private TenantProfile? _currentProfile;
public PermissionsViewModel(
IPermissionsService permissionsService,
ISiteListService siteListService,
ISessionManager sessionManager,
CsvExportService csvExportService,
HtmlExportService htmlExportService,
IBrandingService brandingService,
ILogger logger)
// Test constructor (no export services)
internal PermissionsViewModel(
IPermissionsService permissionsService,
ISiteListService siteListService,
ISessionManager sessionManager,
ILogger logger,
IBrandingService? brandingService = null)
private async Task ExportHtmlAsync()
{
// Currently calls: _htmlExportService.WriteAsync(entries, path, ct, branding)
}
}
```
From SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs (toggleGroup JS to copy):
```javascript
function toggleGroup(id) {
var rows = document.querySelectorAll('tr[data-group="' + id + '"]');
rows.forEach(function(r) { r.style.display = r.style.display === 'none' ? '' : 'none'; });
}
```
Task 1: Extend HtmlExportService with groupMembers parameter and expandable group pills
SharepointToolbox/Services/Export/HtmlExportService.cs,
SharepointToolbox.Tests/Services/Export/HtmlExportServiceTests.cs
- RPT-01-a: BuildHtml with no groupMembers (null/omitted) produces output identical to pre-Phase 17
- RPT-01-b: BuildHtml with groupMembers containing a group name renders clickable pill with onclick="toggleGroup('grpmem0')" and class "group-expandable"
- RPT-01-c: BuildHtml with resolved members renders hidden sub-row (data-group="grpmem0", display:none) containing member display names
- RPT-01-d: BuildHtml with empty member list (resolution failed) renders sub-row with "members unavailable" italic label
- RPT-01-e: BuildHtml output contains "function toggleGroup" in inline JS
- RPT-01-f: Simplified BuildHtml overload also accepts groupMembers and renders expandable pills identically
1. Add `groupMembers` optional parameter to BOTH `BuildHtml` overloads and BOTH `WriteAsync` methods:
```csharp
public string BuildHtml(IReadOnlyList entries,
ReportBranding? branding = null,
IReadOnlyDictionary>? groupMembers = null)
public async Task WriteAsync(IReadOnlyList entries, string filePath,
CancellationToken ct, ReportBranding? branding = null,
IReadOnlyDictionary>? groupMembers = null)
```
Same for the `SimplifiedPermissionEntry` overloads. `WriteAsync` passes `groupMembers` through to `BuildHtml`.
2. Add CSS for `.group-expandable` in the inline `