diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 8b336a0..b4c6dbd 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -111,7 +111,7 @@ Plans: | 1-5 | v1.0 | 36/36 | Shipped | 2026-04-07 | | 6-9 | v1.1 | 25/25 | Shipped | 2026-04-08 | | 10. Branding Data Foundation | v2.2 | 3/3 | Complete | 2026-04-08 | -| 11. HTML Export Branding + ViewModel Integration | 4/4 | Complete | 2026-04-08 | — | +| 11. HTML Export Branding + ViewModel Integration | 4/4 | Complete | 2026-04-08 | — | | 12. Branding UI Views | v2.2 | 0/? | Not started | — | | 13. User Directory ViewModel | v2.2 | 0/? | Not started | — | | 14. User Directory View | v2.2 | 0/? | Not started | — | diff --git a/.planning/STATE.md b/.planning/STATE.md index 51a9571..f77b2d8 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,7 +4,7 @@ milestone: v2.2 milestone_name: Report Branding & User Directory status: completed stopped_at: Completed 11-03-PLAN.md — ViewModel branding wiring -last_updated: "2026-04-08T12:51:43.354Z" +last_updated: "2026-04-08T12:56:08.172Z" last_activity: 2026-04-08 — Phase 11 planning completed progress: total_phases: 5 diff --git a/.planning/phases/11-html-export-branding/11-VERIFICATION.md b/.planning/phases/11-html-export-branding/11-VERIFICATION.md new file mode 100644 index 0000000..83c4cbd --- /dev/null +++ b/.planning/phases/11-html-export-branding/11-VERIFICATION.md @@ -0,0 +1,149 @@ +--- +phase: 11-html-export-branding +verified: 2026-04-08T00:00:00Z +status: passed +score: 5/5 must-haves verified +re_verification: false +--- + +# Phase 11: HTML Export Branding + ViewModel Integration — Verification Report + +**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. +**Verified:** 2026-04-08 +**Status:** PASSED +**Re-verification:** No — initial verification + +--- + +## Goal Achievement + +### Observable Truths (from ROADMAP.md Success Criteria) + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | Running any of the five HTML exports produces an HTML file whose header contains the MSP logo `` tag when an MSP logo is configured | VERIFIED | All 5 export services call `BrandingHtmlHelper.BuildBrandingHeader(branding)` between `` and `

` (7 injection points across 5 files) | +| 2 | When a client logo is configured, the HTML export header contains both logos side by side | VERIFIED | `BrandingHtmlHelper.BuildBrandingHeader` emits both `` tags with a flex spacer when both logos are non-null; ViewModels assemble `ReportBranding(mspLogo, clientLogo)` from `IBrandingService.GetMspLogoAsync()` and `_currentProfile?.ClientLogo` | +| 3 | When no logo is configured, the HTML export header contains no broken image placeholder | VERIFIED | `BuildBrandingHeader` returns `string.Empty` when branding is null or both logos are null; all 5 services use optional `ReportBranding? branding = null` preserving identical pre-branding output | +| 4 | SettingsViewModel exposes browse/clear commands for MSP logo; ProfileManagementViewModel exposes browse/clear commands for client logo — both exercisable without a View | VERIFIED | `SettingsViewModel.BrowseMspLogoCommand` and `ClearMspLogoCommand` exist as `IAsyncRelayCommand`; `ProfileManagementViewModel` exposes `BrowseClientLogoCommand`, `ClearClientLogoCommand`, `AutoPullClientLogoCommand`; both backed by unit tests | +| 5 | Auto-pulling the client logo from Entra branding API stores it in the tenant profile and falls back silently when no Entra branding is configured | VERIFIED | `AutoPullClientLogoAsync` calls `squareLogo` endpoint, pipes bytes to `ImportLogoFromBytesAsync`, calls `_profileService.UpdateProfileAsync`; catches `ODataError` with `ResponseStatusCode == 404` and sets informational `ValidationMessage` with no rethrow | + +**Score:** 5/5 truths verified + +--- + +## Required Artifacts + +### Plan 01 — ReportBranding Model + BrandingHtmlHelper + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `SharepointToolbox/Core/Models/ReportBranding.cs` | Immutable DTO with MspLogo and ClientLogo | VERIFIED | Positional record `ReportBranding(LogoData? MspLogo, LogoData? ClientLogo)` — 8 lines, substantive | +| `SharepointToolbox/Services/Export/BrandingHtmlHelper.cs` | Static helper generating branding header HTML | VERIFIED | Internal static class with `BuildBrandingHeader`, flex layout, data-URI format, empty-string fallback | +| `SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs` | Unit tests covering all 4 branding states | VERIFIED | 105 lines, 8 `[Fact]` tests covering null branding, both-null, single logo, both logos | + +### Plan 02 — Branding Parameter in All 5 Export Services + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `SharepointToolbox/Services/Export/HtmlExportService.cs` | Optional branding param on BuildHtml + WriteAsync | VERIFIED | 4 signatures carry `ReportBranding? branding = null`; 2 injection points | +| `SharepointToolbox/Services/Export/SearchHtmlExportService.cs` | Optional branding param | VERIFIED | BuildHtml + WriteAsync both carry param; injection confirmed | +| `SharepointToolbox/Services/Export/StorageHtmlExportService.cs` | Optional branding param (both overloads) | VERIFIED | 3 signatures with param; 2 injection points | +| `SharepointToolbox/Services/Export/DuplicatesHtmlExportService.cs` | Optional branding param | VERIFIED | BuildHtml + WriteAsync carry param; injection confirmed | +| `SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs` | Optional branding param | VERIFIED | BuildHtml + WriteAsync carry param; injection confirmed | + +### Plan 03 — IBrandingService Wired into Export ViewModels + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs` | IBrandingService injection + branding in ExportHtmlAsync | VERIFIED | `IBrandingService? _brandingService` field; DI and test constructors present; 2 `WriteAsync` calls pass `branding` | +| `SharepointToolbox/ViewModels/Tabs/SearchViewModel.cs` | IBrandingService injection | VERIFIED | Non-nullable `IBrandingService _brandingService`; single constructor; `WriteAsync` passes `branding` | +| `SharepointToolbox/ViewModels/Tabs/StorageViewModel.cs` | IBrandingService injection | VERIFIED | Nullable field; DI + test constructors; `WriteAsync` passes `branding` | +| `SharepointToolbox/ViewModels/Tabs/DuplicatesViewModel.cs` | IBrandingService injection | VERIFIED | Non-nullable field; single constructor; `WriteAsync` passes `branding` | +| `SharepointToolbox/ViewModels/Tabs/UserAccessAuditViewModel.cs` | IBrandingService injection | VERIFIED | Nullable field; DI + test constructors; `WriteAsync` passes `branding` | + +### Plan 04 — Logo Management Commands + Service Extensions + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `SharepointToolbox/Services/ProfileService.cs` | UpdateProfileAsync | VERIFIED | `UpdateProfileAsync` at line 55, find-by-name-replace-save pattern | +| `SharepointToolbox/Services/IBrandingService.cs` | ImportLogoFromBytesAsync declaration | VERIFIED | `Task ImportLogoFromBytesAsync(byte[] bytes);` at line 8 | +| `SharepointToolbox/Services/BrandingService.cs` | ImportLogoFromBytesAsync implementation | VERIFIED | Implemented at line 40; `ImportLogoAsync` delegates to it at line 33 | +| `SharepointToolbox/ViewModels/Tabs/SettingsViewModel.cs` | BrowseMspLogoCommand + ClearMspLogoCommand | VERIFIED | Both `IAsyncRelayCommand` fields at lines 50-51; IBrandingService injected via constructor | +| `SharepointToolbox/ViewModels/ProfileManagementViewModel.cs` | BrowseClientLogoCommand + ClearClientLogoCommand + AutoPullClientLogoCommand | VERIFIED | All three at lines 40-42; 404 catch at line 235 | +| `SharepointToolbox.Tests/ViewModels/SettingsViewModelLogoTests.cs` | Tests for MSP logo commands | VERIFIED | 72 lines (min 40 required); tests confirm command existence and ClearMspLogo path | +| `SharepointToolbox.Tests/ViewModels/ProfileManagementViewModelLogoTests.cs` | Tests for client logo commands and auto-pull | VERIFIED | 118 lines (min 60 required); 7 tests including 404 handling | + +--- + +## Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| `BrandingHtmlHelper.cs` | `ReportBranding.cs` | parameter type | VERIFIED | `BuildBrandingHeader(ReportBranding? branding)` — type referenced directly | +| `BrandingHtmlHelper.cs` | `LogoData.cs` | property access | VERIFIED | `msp.MimeType`, `msp.Base64`, `client.MimeType`, `client.Base64` | +| `HtmlExportService.cs` | `BrandingHtmlHelper.cs` | static method call | VERIFIED | `BrandingHtmlHelper.BuildBrandingHeader` at lines 76 and 232 | +| `SearchHtmlExportService.cs` | `BrandingHtmlHelper.cs` | static method call | VERIFIED | `BrandingHtmlHelper.BuildBrandingHeader` at line 47 | +| `StorageHtmlExportService.cs` | `BrandingHtmlHelper.cs` | static method call | VERIFIED | `BrandingHtmlHelper.BuildBrandingHeader` at lines 52 and 152 | +| `DuplicatesHtmlExportService.cs` | `BrandingHtmlHelper.cs` | static method call | VERIFIED | `BrandingHtmlHelper.BuildBrandingHeader` at line 56 | +| `UserAccessHtmlExportService.cs` | `BrandingHtmlHelper.cs` | static method call | VERIFIED | `BrandingHtmlHelper.BuildBrandingHeader` at line 91 | +| `PermissionsViewModel.cs` | `IBrandingService.cs` | constructor injection | VERIFIED | `IBrandingService? _brandingService` field; DI constructor at line 132 | +| `PermissionsViewModel.cs` | `HtmlExportService.cs` | WriteAsync with branding | VERIFIED | `WriteAsync(..., branding)` at lines 330 and 332 | +| `SettingsViewModel.cs` | `IBrandingService.cs` | constructor injection | VERIFIED | `IBrandingService _brandingService` field at line 14; constructor at line 53 | +| `ProfileManagementViewModel.cs` | `ProfileService.cs` | UpdateProfileAsync call | VERIFIED | `_profileService.UpdateProfileAsync` at lines 175, 191, 232 | +| `ProfileManagementViewModel.cs` | `Microsoft.Graph` | Organization.Branding.SquareLogo | VERIFIED | `graphClient.Organization[orgId].Branding.Localizations["default"].SquareLogo.GetAsync()` at lines 217-218 | + +--- + +## Requirements Coverage + +| Requirement | Source Plan | Description | Status | Evidence | +|-------------|------------|-------------|--------|----------| +| BRAND-05 | 11-01, 11-02, 11-03 | All five HTML report types display MSP and client logos in a consistent header | SATISFIED | `BrandingHtmlHelper` generates flex-layout data-URI header; all 5 exporters inject it; all 5 ViewModels assemble and pass `ReportBranding` to `WriteAsync` | +| BRAND-04 | 11-04 | User can auto-pull client logo from tenant's Entra branding API | SATISFIED | `AutoPullClientLogoCommand` implemented in `ProfileManagementViewModel`; calls squareLogo endpoint; persists via `UpdateProfileAsync`; handles 404 gracefully | + +**Note on REQUIREMENTS.md checkbox:** `BRAND-04` shows `[ ]` (unchecked) in REQUIREMENTS.md and "Pending" in the traceability table. The implementation in the codebase is complete (see `AutoPullClientLogoAsync` and related commands). This is a documentation tracking artifact that needs updating — the requirement itself is satisfied by the implementation. + +--- + +## Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| `HtmlExportService.cs` | 88, 257 | `placeholder="Filter permissions..."` | Info | HTML `` placeholder attribute in a filter UI element — this is valid HTML, not a code stub | + +No blockers or warnings found. The only `placeholder` matches are HTML form attribute strings in the legitimate permissions filter input, not code stubs. + +--- + +## Human Verification Required + +### 1. Visual Logo Layout in Browser + +**Test:** Configure an MSP logo and a client logo in the application. Run any HTML export. Open the resulting HTML file in a browser. +**Expected:** The header shows the MSP logo left-aligned and the client logo right-aligned in a flex row with 16px gap; both logos are max 60px tall and max 200px wide; no broken image icons appear. +**Why human:** CSS rendering and visual layout cannot be verified by grep. + +### 2. No-Logo Regression + +**Test:** Clear both logos. Run any HTML export. Open the HTML file. +**Expected:** The report body appears identical to a pre-branding export — no blank space where the header would be, no empty `
`. +**Why human:** Visual comparison of rendered output requires a browser. + +### 3. Auto-Pull from Entra Branding (Live Tenant) + +**Test:** In the profile dialog, select a tenant with Entra branding configured. Click "Pull from Entra". Verify the logo appears after Phase 12 adds the preview control. +**Expected:** The tenant's squareLogo is imported, stored in the profile, and `ValidationMessage` reads "Client logo pulled from Entra branding." +**Why human:** Requires a live Graph API call to a real tenant. The 404 fallback path is tested by unit tests, but the success path requires a real tenant credential. + +--- + +## Gaps Summary + +No gaps. All five success criteria are satisfied, all must-have artifacts exist with substantive implementations, all key links are wired end-to-end. + +The single documentation artifact to note: `REQUIREMENTS.md` still shows BRAND-04 as `[ ]` and "Pending" in the traceability table. The code fully implements the requirement; the tracking document was not updated during plan 04 execution. This does not affect goal achievement. + +--- + +_Verified: 2026-04-08_ +_Verifier: Claude (gsd-verifier)_