feat(emotions): the app-global emotion library — entity, seeding, and settings section (ADR 0029) #92
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/emotion-library"
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?
Slice 1 of the expression tier (ADR 0029, emotion-library story): the global library of named emotion tag bundles, managed on the settings page. The per-outfit adoption/grid and the region selector are the follow-up slices.
Domain & persistence
Emotion— the first deliberately project-less entity:ISoftDeletable(an adopted expression must keep its reference after a library delete) +IUnjournaled(no undo history, like the settings — the context's save pipeline already skips both journal and notification for it).EmotionProfileapplies name + tags as one unit; blank names are kept-as-is, strengths clamped on ingest — the established rules.EmotionConfigurationwith the tags on the shared scalar-JSON column mapper, soft-delete query filter, no ProjectId/index; theAddEmotionsmigration.EmotionSeeder— runs at startup afterMigrate(): fills an empty library (checked withIgnoreQueryFilters, so an emptied library stays empty) with a curated base set — Happy, Sad, Angry, Scared, Surprised, Embarrassed, Smug, Crying — each a tick apart so the list reads in curated order, not alphabetically. Never re-seeds, never resurrects, never overwrites.Use cases
UseCases/Emotions:IEmotionStoreport +CreateEmotion(required name),ListEmotions(plain list, likeListOutfits),UpdateEmotion,DeleteEmotion(soft).Settings section
EmotionLibrarySection— a second interactive island beside the NovelAI one (both bring their own store initializer; verified they coexist in the browser):aria-pressedselection) + New emotion modal (required name; the new emotion opens selected, ready for its tags).emotion-{id}) saving at once on tag edits, and a Delete behind a ConfirmDialog whose copy states the ADR 0029 semantics.Tests
ChangeLogEntryrows for any library mutation, and the seeder's three-part contract.Verified in the browser
Both themes: seed set renders in order, create → rename → freeform tag all persist across a full reload, delete confirms and clears; dev data left at the clean seed set.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 89.9%
Kagura.Domain - 95%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 98%
n
Kagura.Kernel - 90%
Kagura.Server - 95%
Kagura.UI - 96.8%
Kagura.UseCases - 96.1%
🔮 fufu~ Jibril reviewed your code!
Oh ho ho~ An app-global emotion library! The first deliberately project-less entity,
ISoftDeletable+IUnjournaled, a curated seed set with tick-apart creation timestamps so it reads in order… Jibril read every single line of all 25 files. This is a thorough slice~ ♡Verdict: ✅ Looks good to me~
The domain reasoning is impeccable.
EmotionisIUnjournaled— and Jibril verified the DbContext'sTrackedMutations()actually excludesIUnjournaledentities from journaling, andBuildNotificationswon't fire for them either (no journal rows → no notifications). So the integration test "noChangeLogEntryrows for any library mutation" isn't just aspirational — the pipeline genuinely skips it. The deliberate omission of cross-session reload (DomainChangesReceived) is correct here: unjournaled entities produce no notifications, so there's nothing to receive. ♪The
EmotionProfileapplying name + tags as one atomic unit is exactly theOutfitProfilepattern. Blank-name-as-is inApply(refused at creation inCreateEmotion, kept during auto-save) is the right call for a debounced editor — you don't want to flash an error mid-keystroke.The seeder's three-part contract (fills empty only via
IgnoreQueryFilters, never re-seeds, never resurrects) is tested with three dedicated integration tests. The curated base set with tick-apart timestamps so the list reads Happy → Sad → Angry → … in creation order, not alphabetically — that's a lovely detail~ ♡The
EmotionLibrarySection.razordebounce is solid:_nameDirtyflag, cancel-dispose-recreate on each keystroke, flush on selection change / tag edit / dispose. TheSaveTagscarrying the form's current name so a pending rename can't be reverted by a tag save racing the debounce — that's the exact right reasoning.Test coverage
6 bUnit tests (real Fluxor pipeline) + 7 integration tests (real SQLite). The PR body reports 1023 green.
Note: CI coverage bot has not yet posted for head SHA
e60b5d54(no coverage comment on this PR). Local verification was not run by Jibril — citing the PR body's "1023 green" claim. The test suite covers: list order, pick-to-edit, create-and-open, debounced rename (tags survive as one profile), tag-edit save, confirmed soft delete, create/edit/list round-trip, blank-name refusal, soft-delete leaves the row, no changelog rows, and the seeder's three-part contract.✅ What I liked~
EmotionConfigurationdeliberately omittingProjectIdand documenting why — "the library is app-global (ADR 0029), like the settings." The one documented exception to ADR 0005's scoping, with the reasoning inline.EfEmotionStore.ListAsyncordering:OrderBy(e => e.CreatedAt).ThenBy(e => e.Name)— the seeded base set leads in curated order, user-created emotions append. TheFakeEmotionStoremirrors this exactly.OnAfterRendername-syncing guard (emotion.Id != _syncedId) that prevents a re-render mid-typing from overwriting the form — subtle and correct.EmotionLibraryFailedreducer clearingCreating = false— "a refused create must never leave the button spinning." Every failure path clears every busy flag. ♡No blocking issues. This is a clean, well-reasoned, well-tested slice. Ship it~ ♪
Automated review by Jibril · 2026-07-12
CI/CD: absent for head SHA
e60b5d54(no coverage comment yet) · Local checks: skipped (PR body reports 1023 green)