Files
Sharepoint-Toolbox/.planning/phases/11-html-export-branding/11-01-PLAN.md
2026-04-08 14:23:01 +02:00

9.0 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
11-html-export-branding 01 execute 1
SharepointToolbox/Core/Models/ReportBranding.cs
SharepointToolbox/Services/Export/BrandingHtmlHelper.cs
SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs
true
BRAND-05
truths artifacts key_links
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
path provides contains
SharepointToolbox/Core/Models/ReportBranding.cs Immutable DTO bundling MSP and client logos for export pipeline record ReportBranding
path provides contains
SharepointToolbox/Services/Export/BrandingHtmlHelper.cs Static helper generating branding header HTML fragment BuildBrandingHeader
path provides min_lines
SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs Unit tests covering all 4 branding states 50
from to via pattern
SharepointToolbox/Services/Export/BrandingHtmlHelper.cs SharepointToolbox/Core/Models/ReportBranding.cs parameter type ReportBranding?
from to via pattern
SharepointToolbox/Services/Export/BrandingHtmlHelper.cs SharepointToolbox/Core/Models/LogoData.cs property access MimeType.*Base64
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.

<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>

@.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

From SharepointToolbox/Core/Models/LogoData.cs:

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:

namespace SharepointToolbox.Core.Models;

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

From SharepointToolbox/Core/Models/TenantProfile.cs (relevant property):

public LogoData? ClientLogo { get; set; }
Task 1: Create ReportBranding record and BrandingHtmlHelper with tests SharepointToolbox/Core/Models/ReportBranding.cs, SharepointToolbox/Services/Export/BrandingHtmlHelper.cs, SharepointToolbox.Tests/Services/Export/BrandingHtmlHelperTests.cs - 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 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`.
dotnet build --no-restore -warnaserror && dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~BrandingHtmlHelperTests" --no-build -q 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. ```bash dotnet build --no-restore -warnaserror dotnet test SharepointToolbox.Tests --filter "FullyQualifiedName~BrandingHtmlHelper" --no-build -q ``` Both commands must pass with zero failures.

<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>
After completion, create `.planning/phases/11-html-export-branding/11-01-SUMMARY.md`