---
phase: 11-html-export-branding
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - SharepointToolbox/Core/Models/ReportBranding.cs
  - SharepointToolbox/Services/Export/BrandingHtmlHelper.cs
  - SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs
autonomous: true
requirements:
  - BRAND-05

must_haves:
  truths:
    - "BrandingHtmlHelper.BuildBrandingHeader returns a div with two img tags when both MSP and client logos are provided"
    - "BrandingHtmlHelper.BuildBrandingHeader returns a div with one img tag when only MSP or only client logo is provided"
    - "BrandingHtmlHelper.BuildBrandingHeader returns empty string when branding is null or both logos are null"
    - "ReportBranding record bundles MspLogo and ClientLogo as nullable LogoData properties"
  artifacts:
    - path: "SharepointToolbox/Core/Models/ReportBranding.cs"
      provides: "Immutable DTO bundling MSP and client logos for export pipeline"
      contains: "record ReportBranding"
    - path: "SharepointToolbox/Services/Export/BrandingHtmlHelper.cs"
      provides: "Static helper generating branding header HTML fragment"
      contains: "BuildBrandingHeader"
    - path: "SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs"
      provides: "Unit tests covering all 4 branding states"
      min_lines: 50
  key_links:
    - from: "SharepointToolbox/Services/Export/BrandingHtmlHelper.cs"
      to: "SharepointToolbox/Core/Models/ReportBranding.cs"
      via: "parameter type"
      pattern: "ReportBranding\\?"
    - from: "SharepointToolbox/Services/Export/BrandingHtmlHelper.cs"
      to: "SharepointToolbox/Core/Models/LogoData.cs"
      via: "property access"
      pattern: "MimeType.*Base64"
---

<objective>
Create the ReportBranding model and BrandingHtmlHelper static class that all HTML exporters will call to render the branding header.

Purpose: Centralizes the branding header HTML generation so all 5 exporters share identical markup. This is the foundation artifact that Plan 02 depends on.

Output: ReportBranding record, BrandingHtmlHelper static class, and comprehensive unit tests covering all logo combination states.
</objective>

<execution_context>
@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/dev/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/11-html-export-branding/11-CONTEXT.md
@.planning/phases/11-html-export-branding/11-RESEARCH.md

<interfaces>
<!-- Phase 10 infrastructure this plan depends on -->

From SharepointToolbox/Core/Models/LogoData.cs:
```csharp
namespace SharepointToolbox.Core.Models;

public record LogoData
{
    public string Base64 { get; init; } = string.Empty;
    public string MimeType { get; init; } = string.Empty;
}
```

From SharepointToolbox/Core/Models/BrandingSettings.cs:
```csharp
namespace SharepointToolbox.Core.Models;

public class BrandingSettings
{
    public LogoData? MspLogo { get; set; }
}
```

From SharepointToolbox/Core/Models/TenantProfile.cs (relevant property):
```csharp
public LogoData? ClientLogo { get; set; }
```
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Create ReportBranding record and BrandingHtmlHelper with tests</name>
  <files>
    SharepointToolbox/Core/Models/ReportBranding.cs,
    SharepointToolbox/Services/Export/BrandingHtmlHelper.cs,
    SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs
  </files>
  <behavior>
    - Test 1: BuildBrandingHeader with null ReportBranding returns empty string
    - Test 2: BuildBrandingHeader with both logos null returns empty string
    - Test 3: BuildBrandingHeader with only MspLogo returns HTML with one img tag containing MSP base64 data-URI, no second img
    - Test 4: BuildBrandingHeader with only ClientLogo returns HTML with one img tag containing client base64 data-URI, no flex spacer div
    - Test 5: BuildBrandingHeader with both logos returns HTML with two img tags and a flex spacer div between them
    - Test 6: All generated img tags use inline data-URI format: src="data:{MimeType};base64,{Base64}"
    - Test 7: All generated img tags have max-height:60px and max-width:200px styles
    - Test 8: The outer div uses display:flex;gap:16px;align-items:center styling
  </behavior>
  <action>
    1. Create `SharepointToolbox/Core/Models/ReportBranding.cs`:
       ```csharp
       namespace SharepointToolbox.Core.Models;

       /// <summary>
       /// Bundles MSP and client logos for passing to export services.
       /// Export services receive this as a simple DTO — they don't know
       /// about IBrandingService or ProfileService.
       /// </summary>
       public record ReportBranding(LogoData? MspLogo, LogoData? ClientLogo);
       ```
       This is a positional record (OK because it is never deserialized from JSON — it is always constructed in code).

    2. Create `SharepointToolbox/Services/Export/BrandingHtmlHelper.cs`:
       ```csharp
       using System.Text;
       using SharepointToolbox.Core.Models;

       namespace SharepointToolbox.Services.Export;

       /// <summary>
       /// Generates the branding header HTML fragment for HTML reports.
       /// Called by each HTML export service between &lt;body&gt; and &lt;h1&gt;.
       /// Returns empty string when no logos are configured (no broken images).
       /// </summary>
       internal static class BrandingHtmlHelper
       {
           public static string BuildBrandingHeader(ReportBranding? branding)
           {
               if (branding is null) return string.Empty;

               var msp = branding.MspLogo;
               var client = branding.ClientLogo;

               if (msp is null && client is null) return string.Empty;

               var sb = new StringBuilder();
               sb.AppendLine("<div style=\"display:flex;gap:16px;align-items:center;padding:12px 24px 0;\">");

               if (msp is not null)
                   sb.AppendLine($"  <img src=\"data:{msp.MimeType};base64,{msp.Base64}\" alt=\"\" style=\"max-height:60px;max-width:200px;object-fit:contain;\">");

               if (msp is not null && client is not null)
                   sb.AppendLine("  <div style=\"flex:1\"></div>");

               if (client is not null)
                   sb.AppendLine($"  <img src=\"data:{client.MimeType};base64,{client.Base64}\" alt=\"\" style=\"max-height:60px;max-width:200px;object-fit:contain;\">");

               sb.AppendLine("</div>");
               return sb.ToString();
           }
       }
       ```
       Key decisions per CONTEXT.md locked decisions:
       - `display:flex;gap:16px` layout (MSP left, client right)
       - `<img src="data:{MimeType};base64,{Base64}">` inline data-URI format
       - `max-height:60px` keeps logos reasonable
       - Returns empty string (not null) when no branding — callers need no null checks
       - `alt=""` (decorative image — not essential content)
       - Class is `internal` — only used within Services.Export namespace. Tests access via `InternalsVisibleTo`.

    3. Create `SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs`:
       Write tests FIRST (RED phase), then verify the implementation makes them GREEN.
       Use `[Trait("Category", "Unit")]` per project convention.
       Create helper method `MakeLogo(string mime = "image/png", string base64 = "dGVzdA==")` to build test LogoData instances.
       Tests must assert exact HTML structure: data-URI format, style attributes, flex spacer presence/absence.

    4. Verify the test project has `InternalsVisibleTo` for the helper class. Check `SharepointToolbox.csproj` or `AssemblyInfo.cs` for `[assembly: InternalsVisibleTo("SharepointToolbox.Tests")]`. If missing, add `<InternalsVisibleTo Include="SharepointToolbox.Tests" />` inside an `<ItemGroup>` in `SharepointToolbox.csproj`.
  </action>
  <verify>
    <automated>dotnet build --no-restore -warnaserror && dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~BrandingHtmlHelperTests" --no-build -q</automated>
  </verify>
  <done>ReportBranding record exists in Core/Models. BrandingHtmlHelper generates correct HTML for all 4 states (null branding, both null, single logo, both logos). All tests pass. Build succeeds with no warnings.</done>
</task>

</tasks>

<verification>
```bash
dotnet build --no-restore -warnaserror
dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~BrandingHtmlHelper" --no-build -q
```
Both commands must pass with zero failures.
</verification>

<success_criteria>
- ReportBranding record exists at Core/Models/ReportBranding.cs as a positional record with two nullable LogoData params
- BrandingHtmlHelper.BuildBrandingHeader handles all 4 states correctly (null branding, both null, single, both)
- Generated HTML uses data-URI format, flex layout, 60px max-height per locked decisions
- No broken image tags when logos are missing
- Tests cover all states with assertions on HTML structure
- Build passes with zero warnings
</success_criteria>

<output>
After completion, create `.planning/phases/11-html-export-branding/11-01-SUMMARY.md`
</output>
