- 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
131 lines
7.7 KiB
Markdown
131 lines
7.7 KiB
Markdown
---
|
|
phase: 10-branding-data-foundation
|
|
plan: 01
|
|
subsystem: branding
|
|
tags: [logo, base64, json-persistence, wpf-imaging, magic-bytes, compression]
|
|
|
|
requires: []
|
|
|
|
provides:
|
|
- 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
|
|
|
|
affects:
|
|
- 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)
|
|
|
|
tech-stack:
|
|
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
|
|
|
|
key-files:
|
|
created:
|
|
- 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
|
|
modified:
|
|
- SharepointToolbox/Core/Models/TenantProfile.cs
|
|
|
|
key-decisions:
|
|
- "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"
|
|
|
|
patterns-established:
|
|
- "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"
|
|
|
|
requirements-completed:
|
|
- BRAND-01
|
|
- BRAND-03
|
|
- BRAND-06
|
|
|
|
duration: 4min
|
|
completed: 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*
|