Files
Sharepoint-Toolbox/.planning/phases/10-branding-data-foundation/10-01-SUMMARY.md
Dev 61d7ada945 docs(10-01): complete branding-data-foundation plan 01
- Add 10-01-SUMMARY.md with task commits, deviation doc, and dependency graph
- Update STATE.md: decisions logged, session updated
- Update ROADMAP.md: phase 10 In Progress (1/3 plans complete)
- Mark BRAND-01, BRAND-03 complete in REQUIREMENTS.md
2026-04-08 12:33:57 +02:00

7.7 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
10-branding-data-foundation 01 branding
logo
base64
json-persistence
wpf-imaging
magic-bytes
compression
LogoData record (Base64 + MimeType init properties) — shared model for all logo storage
BrandingSettings class with nullable MspLogo — MSP-level branding persistence model
TenantProfile.ClientLogo property — per-tenant client logo (additive, no breaking changes)
BrandingRepository — JSON persistence with write-then-replace safety using SemaphoreSlim
IBrandingService / BrandingService — magic byte validation, auto-compression, MSP logo CRUD
10-02 (branding UI ViewModel will consume IBrandingService)
11-report-branding (HTML export will use LogoData from BrandingSettings and TenantProfile)
Phase 13-14 (TenantProfile extended — profile serialization must stay compatible)
added patterns
BrandingRepository mirrors SettingsRepository exactly (SemaphoreSlim write-then-replace, JsonDocument validation)
LogoData as non-positional record with init properties (avoids System.Text.Json positional constructor pitfall)
BrandingService uses WPF PresentationCore (BitmapDecoder/TransformedBitmap/BitmapEncoder) for compression — no new NuGet package required
Magic byte detection (4 bytes PNG, 3 bytes JPEG) before extension check — format is determined by content, not filename
created modified
SharepointToolbox/Core/Models/LogoData.cs
SharepointToolbox/Core/Models/BrandingSettings.cs
SharepointToolbox/Infrastructure/Persistence/BrandingRepository.cs
SharepointToolbox/Services/IBrandingService.cs
SharepointToolbox/Services/BrandingService.cs
SharepointToolbox.Tests/Services/BrandingRepositoryTests.cs
SharepointToolbox.Tests/Services/BrandingServiceTests.cs
SharepointToolbox/Core/Models/TenantProfile.cs
Used WPF PresentationCore (BitmapDecoder/TransformedBitmap/JpegBitmapEncoder) for image compression instead of System.Drawing.Bitmap — System.Drawing.Common is not available without a new NuGet package on .NET 10, but WPF PresentationCore is already in the stack (net10.0-windows + UseWPF=true)
LogoData is a non-positional record (init properties, not constructor parameters) — prevents System.Text.Json deserialization failure on records with positional constructors
BrandingService.ImportLogoAsync is pure (no persistence) — caller decides where to store the LogoData; ViewModel in Phase 11 will call SaveMspLogoAsync or equivalent client logo save
Repository pattern: BrandingRepository is structural clone of SettingsRepository — same SemaphoreSlim(1,1) write lock, write-tmp-then-validate-then-move safety protocol
Magic byte validation: PNG checked with 4 bytes (0x89 0x50 0x4E 0x47), JPEG with 3 bytes (0xFF 0xD8 0xFF) — content-based not extension-based
Compression two-pass: 300x300 quality 75 first, 200x200 quality 50 if still over limit
Test pattern: IDisposable + Path.GetTempFileName() + Dispose cleanup of .tmp files — matches existing SettingsServiceTests
BRAND-01
BRAND-03
BRAND-06
4min 2026-04-08

Phase 10 Plan 01: Branding Data Foundation Summary

LogoData record + BrandingRepository (write-then-replace JSON) + BrandingService with PNG/JPEG magic byte validation and WPF-based auto-compression to 512 KB limit

Performance

  • Duration: ~4 min
  • Started: 2026-04-08T00:28:31Z
  • Completed: 2026-04-08T00:32:26Z
  • Tasks: 2
  • Files modified: 8 (7 created, 1 modified)

Accomplishments

  • LogoData record, BrandingSettings model, and TenantProfile.ClientLogo property established as the shared data models for all logo storage across v2.2
  • BrandingRepository persists BrandingSettings to branding.json with write-then-replace safety (SemaphoreSlim + tmp file + JsonDocument validation before move)
  • BrandingService validates PNG/JPEG via magic bytes, rejects all other formats with descriptive error message mentioning PNG and JPG, auto-compresses files over 512 KB using WPF imaging in two passes

Task Commits

Each task was committed atomically:

  1. Task 1: Create logo models, BrandingRepository, and repository tests - 2280f12 (feat)
  2. Task 2: Create BrandingService with validation, compression, and tests - 1303866 (feat)

Files Created/Modified

  • SharepointToolbox/Core/Models/LogoData.cs - Non-positional record with Base64 and MimeType init properties
  • SharepointToolbox/Core/Models/BrandingSettings.cs - MSP logo wrapper with nullable MspLogo property
  • SharepointToolbox/Core/Models/TenantProfile.cs - Extended with nullable ClientLogo property (additive only)
  • SharepointToolbox/Infrastructure/Persistence/BrandingRepository.cs - JSON persistence mirroring SettingsRepository pattern
  • SharepointToolbox/Services/IBrandingService.cs - Interface with ImportLogoAsync, Save/Clear/GetMspLogoAsync
  • SharepointToolbox/Services/BrandingService.cs - Magic byte validation, WPF-based compression, MSP logo CRUD
  • SharepointToolbox.Tests/Services/BrandingRepositoryTests.cs - 5 tests: defaults, round-trip, dir creation, TenantProfile serialization
  • SharepointToolbox.Tests/Services/BrandingServiceTests.cs - 9 tests: PNG/JPEG acceptance, BMP rejection, empty file, no-compression, compression, CRUD

Decisions Made

  • Used WPF PresentationCore imaging (BitmapDecoder, TransformedBitmap, JpegBitmapEncoder) for compression — System.Drawing.Common is not available without a new NuGet package on .NET 10 and is not in the existing stack
  • ImportLogoAsync is kept pure (no persistence side-effects) — caller decides where to store the returned LogoData, enabling reuse for both MSP logo and per-tenant client logo paths

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Used WPF PresentationCore instead of System.Drawing.Bitmap for compression

  • Found during: Task 2 (BrandingService implementation)
  • Issue: Plan specified System.Drawing.Bitmap and ImageCodecInfo, but System.Drawing.Common is not in the project's package list and is not available on .NET 10 without an explicit NuGet package reference. Adding it would violate the v2.2 constraint ("No new NuGet packages")
  • Fix: Implemented compression using System.Windows.Media.Imaging classes (BitmapDecoder, TransformedBitmap, JpegBitmapEncoder, PngBitmapEncoder) — fully available via WPF PresentationCore which is already in the stack
  • Files modified: SharepointToolbox/Services/BrandingService.cs
  • Verification: All 9 BrandingServiceTests pass including the compression test (400x400 random-pixel PNG over 512 KB compressed to under 512 KB)
  • Committed in: 1303866 (Task 2 commit)

Total deviations: 1 auto-fixed (Rule 1 — implementation approach) Impact on plan: No scope change. Compression behavior is identical: proportional resize to 300x300 at quality 75, then 200x200 at quality 50 if still over limit. WPF APIs provide the same capability without a new dependency.

Issues Encountered

None — build and all tests passed first time after implementation.

User Setup Required

None - no external service configuration required.

Next Phase Readiness

  • All logo storage models and infrastructure are ready for Phase 10 Plan 02 (branding UI ViewModel)
  • BrandingService.ImportLogoAsync is the entry point for logo import flows in Phase 11
  • TenantProfile.ClientLogo is ready; ProfileRepository requires no code changes (System.Text.Json handles the new nullable property automatically)
  • 14 total Branding tests passing; 10 ProfileService tests confirm no regression from TenantProfile extension

Phase: 10-branding-data-foundation Completed: 2026-04-08