---
phase: 11
title: HTML Export Branding + ViewModel Integration
status: ready-for-planning
created: 2026-04-08
---

# Phase 11 Context: HTML Export Branding + ViewModel Integration

## Decided Areas (from Phase 10 context + STATE.md)

These are locked — do not re-litigate during planning or execution.

| Decision | Value |
|---|---|
| Logo storage format | Base64 strings in JSON (not file paths) |
| MSP logo location | `BrandingSettings.MspLogo` → `branding.json` via `BrandingRepository` |
| Client logo location | `TenantProfile.ClientLogo` (per-tenant, in profile JSON) |
| Logo model | `LogoData { string Base64, string MimeType }` — shared by both MSP and client logos |
| SVG support | Rejected (XSS risk) — PNG/JPG only |
| Export service signature change | Optional `ReportBranding? branding = null` parameter on existing `BuildHtml` methods |
| No new interfaces | No `IHtmlExportService<T>` — keep concrete classes with optional branding param |
| Report header layout | `display: flex; gap: 16px` — MSP logo left, client logo right |
| Logo HTML format | `<img src="data:{MimeType};base64,{Base64}">` inline data-URI |
| No new NuGet packages | All capabilities provided by existing stack |

## Phase Goal

All five HTML reports display MSP and client logos in a consistent header, and administrators can manage logos from Settings and the profile dialog without touching the View layer.

## Success Criteria

1. Running any of the five HTML exports (Permissions, Storage, Search, Duplicates, User Access) produces an HTML file whose header contains the MSP logo `<img>` tag when an MSP logo is configured
2. When a client logo is configured for the active tenant, the same HTML export header contains both the MSP logo and the client logo side by side
3. When no logo is configured, the HTML export header contains no broken image placeholder and the report renders identically to the pre-branding output
4. SettingsViewModel exposes browse/clear commands for MSP logo; ProfileManagementViewModel exposes browse/clear commands for client logo — both commands are exercisable without opening any View
5. Auto-pulling the client logo from the tenant's Entra branding API stores the logo in the tenant profile and falls back silently when no Entra branding is configured

## Depends On

Phase 10 (completed) — provides `LogoData`, `BrandingSettings`, `BrandingRepository`, `IBrandingService`, `TenantProfile.ClientLogo`

## Requirements Mapped

- **BRAND-05**: Logos appear in HTML report headers
- **BRAND-04**: Auto-pull client logo from Entra branding API

## Code Context

### Phase 10 Infrastructure (already built)

| Asset | Path | Role |
|---|---|---|
| LogoData record | `Core/Models/LogoData.cs` | `{ string Base64, string MimeType }` |
| BrandingSettings model | `Core/Models/BrandingSettings.cs` | `{ LogoData? MspLogo }` |
| TenantProfile model | `Core/Models/TenantProfile.cs` | `{ LogoData? ClientLogo }` (per-tenant) |
| IBrandingService | `Services/IBrandingService.cs` | `ImportLogoAsync`, `SaveMspLogoAsync`, `ClearMspLogoAsync`, `GetMspLogoAsync` |
| BrandingService | `Services/BrandingService.cs` | Validates PNG/JPG via magic bytes, auto-compresses >512KB |
| BrandingRepository | `Infrastructure/Persistence/BrandingRepository.cs` | JSON persistence with SemaphoreSlim + atomic write |

### HTML Export Services (5 targets for branding injection)

| Service | Path | `BuildHtml` Signature | Header Location |
|---|---|---|---|
| HtmlExportService | `Services/Export/HtmlExportService.cs` | `BuildHtml(IReadOnlyList<PermissionEntry>)` | `<h1>SharePoint Permissions Report</h1>` at line 76 |
| HtmlExportService (simplified) | Same file | `BuildHtml(IReadOnlyList<SimplifiedPermissionEntry>)` (2nd overload) | Similar pattern |
| SearchHtmlExportService | `Services/Export/SearchHtmlExportService.cs` | `BuildHtml(IReadOnlyList<SearchResult>)` | `<h1>File Search Results</h1>` at line 46 |
| StorageHtmlExportService | `Services/Export/StorageHtmlExportService.cs` | `BuildHtml(IReadOnlyList<StorageNode>)` | `<h1>SharePoint Storage Metrics</h1>` at line 51 |
| DuplicatesHtmlExportService | `Services/Export/DuplicatesHtmlExportService.cs` | `BuildHtml(IReadOnlyList<DuplicateGroup>)` | `<h1>Duplicate Detection Report</h1>` at line 55 |
| UserAccessHtmlExportService | `Services/Export/UserAccessHtmlExportService.cs` | `BuildHtml(IReadOnlyList<UserAccessEntry>)` | `<h1>User Access Audit Report</h1>` at line 91 |

### WriteAsync Signatures (7 overloads across 5 services)

```csharp
// HtmlExportService.cs
WriteAsync(IReadOnlyList<PermissionEntry>, string filePath, CancellationToken)
WriteAsync(IReadOnlyList<SimplifiedPermissionEntry>, string filePath, CancellationToken)

// SearchHtmlExportService.cs
WriteAsync(IReadOnlyList<SearchResult>, string filePath, CancellationToken)

// StorageHtmlExportService.cs
WriteAsync(IReadOnlyList<StorageNode>, string filePath, CancellationToken)
WriteAsync(IReadOnlyList<StorageNode>, IReadOnlyList<FileTypeMetric>, string filePath, CancellationToken)

// DuplicatesHtmlExportService.cs
WriteAsync(IReadOnlyList<DuplicateGroup>, string filePath, CancellationToken)

// UserAccessHtmlExportService.cs
WriteAsync(IReadOnlyList<UserAccessEntry>, string filePath, CancellationToken)
```

### ViewModels That Trigger Exports (5 targets)

| ViewModel | Path | Export Call Pattern |
|---|---|---|
| PermissionsViewModel | `ViewModels/Tabs/PermissionsViewModel.cs` | `_htmlExportService.WriteAsync(Results/SimplifiedResults, ...)` |
| SearchViewModel | `ViewModels/Tabs/SearchViewModel.cs` | `_htmlExportService.WriteAsync(Results, ...)` |
| StorageViewModel | `ViewModels/Tabs/StorageViewModel.cs` | `_htmlExportService.WriteAsync(Results, FileTypeMetrics, ...)` |
| DuplicatesViewModel | `ViewModels/Tabs/DuplicatesViewModel.cs` | `_htmlExportService.WriteAsync(_lastGroups, ...)` |
| UserAccessAuditViewModel | `ViewModels/Tabs/UserAccessAuditViewModel.cs` | `_htmlExportService.WriteAsync(Results, ...)` |

### Logo Management ViewModels (2 targets)

| ViewModel | Path | Current State |
|---|---|---|
| SettingsViewModel | `ViewModels/Tabs/SettingsViewModel.cs` | Has language + data folder; needs MSP logo browse/clear commands |
| ProfileManagementViewModel | `ViewModels/ProfileManagementViewModel.cs` | Has CRUD profiles; needs client logo browse/clear/auto-pull commands |

### DI Registration

`App.xaml.cs` — All export services registered as `Transient`, branding services registered as `Singleton`.

### HTML Generation Pattern

All 5 HTML exporters use StringBuilder with inline HTML/CSS/JS. No template files. Each builds a self-contained single-file report. The branding header must be injected between `<body>` and the existing `<h1>` tag in each exporter.

## Deferred Ideas (out of scope for Phase 11)

- Logo preview in Settings UI (Phase 12)
- Live thumbnail preview after import (Phase 12)
- "Pull from Entra" button in profile dialog UI (Phase 12)
- User directory browse mode (Phase 13-14)
