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