feat(locations): the preview image — a square crop from any generated background #109
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/location-preview"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds a preview image (thumbnail) for locations, mirroring the character avatar (#105) one tier shallower: the source images are the location's base background and its variation backgrounds, square-cropped with the same
RegionSelectoraspect lock andImageSharpCropper(ADR 0030).What it does
locations/<id>/preview/preview.png, capped at 512px) — regenerating the source never silently changes the thumbnail; re-pick to update./previewendpoint.Design (identical to the character version)
DomainChangedfor cross-session refresh. No new entity → noEntityKindsconstant.ListLocationImagesowns the base+variants walk andListProjectAssetsnow delegates to it — the same DRY refactor Jibril praised for characters, so the picker and the asset browser can never disagree.Tests
Full suite 1285 green. New coverage mirrors the character set: domain set/clear, full persistence round-trip (bake → serve → survive regenerate → remove, journal untouched, slug/ownership guards, too-small refusal, base-first-then-variants ordering), bUnit picker flow (gallery → square crop → save through the real pipeline → thumbnail; empty state; remove), and a locations-list row pin.
Verification
Browser-verified against the seeded sample world with hand-placed backgrounds: the picker listed the base ("· Background") and the variant, the square drag stayed locked over a widescreen source (96×96 with corner-only handles), saving produced a 243×243 thumbnail on the editor and the locations list, and Remove returned to the placeholder and deleted the file. Dev data and screenshots cleaned up afterwards.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 89.7%
Kagura.Domain - 94.3%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 98.3%
n
on
Kagura.Kernel - 90%
Kagura.Server - 84.6%
Kagura.UI - 94.8%
Kagura.UseCases - 95.7%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A location preview image — the character avatar's little sibling, one tier shallower! fufu~ I do love watching a good pattern propagate itself~ ♡ I pulled the full diff and traced every new file against its character counterpart (#105), and the cropper, the ownership guards, the DRY refactor in
ListProjectAssets... it's all so tidy!Verdict: ✅ Looks good to me~
This is a textbook faithful mirror of
SetCharacterPreview/OpenCharacterPreview/RemoveCharacterPreview. Every invariant carries over: the baked-square crop in its ownpreview/subfolder (out of reach of the base background's clear-first replace), the slug/location pairing guard, the variant ownership check (variant.LocationId != location.Id), the "reference first, file second" removal order, and the unjournaled image-reference rule (ADR 0020). fufu~ you even kept theSaveUnjournaledAsyncextraction so the base-image path and the preview path share the same journaling suppression. Neat~ ♪The DRY refactor in
ListProjectAssetsis the part that made me giddy —ListLocationImages.CollectAsyncis now the single source of truth for the location image walk, and both the picker and the asset browser delegate to it. Two lists that can never disagree about what exists? That's exactly the kind of thing that makes a knowledge-obsessed Flugel happy~The tests are thorough and mirror the character set: domain set/clear (including the
Apply(profile)must-not-disturb invariant — pinned!), the full persistence round-trip over real SQLite (bake → serve → survive regenerate → remove, journal untouched, slug/ownership guards, too-small refusal, the widescreen→true-square assertion at 50×50), the bUnit picker flow, and the locations-list row pin. The "source whose file is gone reads as gone" test is a lovely touch — that's the exact silent-failure case that would otherwise haunt production.✅ What I liked~
ListLocationImages.CollectAsyncextraction — DRY done right, one walk, two consumers.Regenerating_the_base_background_never_touches_the_stored_preview). The cropper's server-side largest-centered-square enforcement means a crafted client can't ship a non-square thumbnail. fufu~ defense in depth~ ♡LocationPreviewEffectscatch-all that prevents an escaped exception from stranding the spinner — the OutfitSprite rule, applied faithfully.defaultValue: nullnullable column is correct here (unlike a JSON list column), since a scalar file name is genuinely absent until chosen.Automated review by Jibril · 2026-07-13
CI/CD: ✅ passed for head SHA
17f9cc1— 1285 tests green, 96.1% line / 82.4% branch coverage · Local checks: skipped (CI green)