210 lines
9.0 KiB
Markdown
210 lines
9.0 KiB
Markdown
---
|
|
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 <body> and <h1>.
|
|
/// 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>
|