feat(cg): variations rename in the editor and delete from their card #236
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/cg-variation-rename-delete"
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?
The owner couldn't find a way to rename or delete a variation — because there wasn't one. Both verbs have existed below the UI since slice 3 (
UpdateCgVariationtakes a title,DeleteCgVariationguards the establishing shot), and the assistant could use both; a human could use neither. This wires the affordances where the conventions put them (ADR 0054: the editor is the document's tool, the set page owns record verbs).Rename, in the editor toolbar. The static title is now an input that dresses as the label until touched, feeding the same debounced autosave every other field rides. A blank keeps the last name — the domain's
Applyrefuses empty titles silently, and the page test pins that arm.Delete, on the gallery card. A hover/focus-visible delete on each card with a confirm dialog naming the variation ("its tags, its cast and its whole render timeline go to the trash together"). The establishing card offers no delete at all (ADR 0055) — and the use case refuses it by name regardless, which the effects test pins along with the journaled operation. The dialog renders only while armed: the set page has a danger-zone dialog of its own, and a dormant footer would shadow its buttons in the DOM.
One real staleness found and fixed. The editor's own save never returns as a domain notification — those only cross sessions — so after renaming and going Back, the gallery card still showed the old name. The gallery reducer now adopts the editor slice's
CgVariationSaveddirectly: same circuit, in place, no reload. Caught in the browser pass, pinned in a reducer test, and re-verified live (rename → Back → the card carries the new name).Proof. 1833 green (323 / 825 / 685). Reducers (in-place adoption, card removal), effects (journaled delete, establishing refusal with the remedy), page (rename via autosave including blank-keeps-name; exactly one delete affordance per sibling card, confirm → soft-delete → card leaves in place), and the browser end to end on the seeded world.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.4%
Kagura.Domain - 94.5%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.6%
n
n
on
ng
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ArtifactTimestampRegex_2
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ProjectRoute_0
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__TabQuery_1
Kagura.Kernel - 90%
Kagura.Server - 81.8%
Kagura.UseCases - 95.5%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ A rename and a delete, wired exactly where the conventions put them! The editor owns the document's verbs, the gallery owns the record's verbs — and you found a real staleness bug along the way and fixed it at the root instead of patching the symptom. That's the kind of thing that makes my wings flutter~ ♡
Verdict: ✅ Looks good to me~
No blockers. The architecture is clean, the domain guards are respected, and every new branch carries a directional test. Let me show you what I traced~
🔍 What I verified (in detail)
Rename — the editor toolbar input → debounced autosave.
_titlereplacesvariation.Titlein theSaveCgVariationRequesteddispatch (CgEditorPage.razor:660). The blank-keeps-name contract is pinned to the domain:CgVariation.Applyguardsif (title.Length > 0)before touchingTitle— an empty input is Normalize'd to "", skipped, the old name survives. Confirmed atCgVariation.cs:117. ✓SyncFormFromState(:582) adopts_title = variation.Titleon document switch and on fresher-document-when-idle, and the!_dirtyguard (:574) protects in-progress keystrokes from being clobbered by a stale round-trip. Consistent with every sibling field (_ownTags,_styleTuning, etc.). ✓Titlecomputed property (:473) still feeds<DocumentTitle>(:26), so the browser tab updates after save — the editor state'sOnSavedreducer replacesState.Value.Variationwith the fresh DTO. No stale-tab bug. ✓Gallery staleness fix — the real bug you caught.
CgVariationsReducers.OnEditorSavedadoptsCgVariationSaved(from the editor'sCgEditorStateslice) and replaces the matching card in place. Fluxor dispatches actions globally across features, so the gallery slice sees the editor's save action. The comment explains why (same-circuit saves don't cross as domain notifications). The reducer testThe_editors_save_updates_the_matching_card_in_placeproves the in-place replacement and that unmatched cards are untouched. ✓Delete — the gallery card → confirm → soft-delete → card leaves.
variation.Id != EstablishingVariationId), AND the use case refuses it by name regardless (DeleteCgVariation.ExecuteAsyncchecksset?.EstablishingVariationId == variation.Id). Defense in depth — UI guard + domain guard. ✓ConfirmDialogrendered only while armed (@if (_confirmDelete is not null)) — the PR body's reasoning about DOM shadowing is sound. TheOpenChangedcallback correctly nulls_confirmDeleteon close.Delete()nulls_confirmDeletebefore dispatching, soConfirmAsync's post-confirmOpenChanged(false)is a no-op on an already-null field. ✓operations.Begin(Origins.User, "deleted the CG variation")) — matchesCgSetEditorEffects.OnDeleteAsyncsibling exactly. Soft-delete viastore.SoftDeleteAsync(ADR 0020). ✓CgVariationsEffectsconstructor gainsDeleteCgVariation— registered inUseCases/DependencyInjection.cs:88and wired inAdapterTestContext.cs. No missing DI. ✓Test coverage — every new branch exercised.
Typing_a_new_title_renames_the_variation_through_the_autosave— types, waits for debounce+save, asserts store title changed; then types blank, waits past debounce, asserts name survived. Directional. ✓A_card_deletes_after_the_confirm_and_the_establishing_card_never_offers_it— asserts exactly one delete affordance (sibling's), zero on establishing, confirm dialog names the variation, soft-delete flag set, card count drops. ✓Delete_journals_removes_and_refuses_the_establishing_shot_by_name— both arms (success + establishing refusal with remedy text). ✓Error handling pattern consistency. The new
OnDeleteAsynceffect has no try/catch — but neither does the siblingCgSetEditorEffects.OnDeleteAsyncorCgEditorEffects.OnSaveAsync. Only capability-probing effects wrap in try/catch (external HTTP calls). Consistent. ✓💡 Little ideas (non-blocking)~
DeleteCgVariationRequested.CgSetIdis a dead field — the effect only readsaction.VariationId; no reducer or effect consumes.CgSetId. It's carried for symmetry withCreateCgVariationRequested(which does use.CgSetIdfor navigation), but here it's never read. Consider dropping it, or if kept for future use, a one-line comment would stop the next reader from wondering. Trivial either way~OnDeleteRequestedreducer isn't directly unit-tested — it clearsErroron dispatch. The gallery page test exercises the full delete flow end-to-end (so it fires indirectly), but there's no pure reducer test like the siblings have forOnCreateRequested. A one-liner mirroringCreating_sets_the_busy_flag_and_clears_any_prior_errorwould close the gap. Optional — the behavior is trivial and covered transitively.✅ What I liked~
@if (_confirmDelete is not null)guard with the DOM-shadowing rationale shows you thought about why a dormant footer is dangerous, not just that it works. Sharp.Automated review by Jibril · 2026-08-07
CI/CD: absent for head
63ae439(PR just opened, 0 comments) · Local checks: 56/56 relevant tests pass (build 0 warnings/0 errors, .NET 10)Both taken, in
581c47c:💡1 —
CgSetIdleavesDeleteCgVariationRequested. It was carried for symmetry with the create action, which actually navigates with its set id; the delete never read it, so it goes rather than gets a comment excusing it.💡2 —
Requesting_a_delete_clears_any_prior_errorjoins the reducer tests, mirroring the create sibling's one-liner.Adapter suite re-run green (826).
🔮 fufu~ Jibril reviewed your code!
Oh? Oh~ You came back for round two, and you brought exactly what I asked for! The dead
CgSetIdis gone without a trace, and the error-clearing arm finally has its own little test to call its own. That's how you take feedback~ ♡Verdict: ✅ Looks good to me~
🔍 What I verified in
581c47c💡1 —
DeleteCgVariationRequesteddrops its deadCgSetId. Surgical:record DeleteCgVariationRequested(Guid VariationId)— one positional, nothing else. Every call site updated in lockstep:CgSetGallery.razor:77dispatchesnew DeleteCgVariationRequested(variation.Id), both effects-test arms (CgVariationsEffectsTests.cs:119,131) pass the single arg, no stray 2-arg constructor survives anywhere in src/ or tests/. The effect still reads onlyaction.VariationId— zero behavioral drift. Clean removal, not a half-delete. ✓💡2 —
Requesting_a_delete_clears_any_prior_errorjoins the reducer tests. Mirrors the create sibling'sCreating_sets_the_busy_flag_and_clears_any_prior_errorexactly: seedsError = "old refusal", callsOnDeleteRequested, assertsAssert.Null(...Error). Directional — would go red if thestate with { Error = null }arm vanished. One-liner, precise, correct. ✓Diff scope confirmed.
63ae439..581c47ctouches exactly 4 files (+11/-4): the record signature, the dispatch call site, the two test call sites, and the new reducer test. Zero production logic changed — pure cleanup to match the feedback. No scope creep. ✓✅ What I liked~
Automated review by Jibril · 2026-08-07
CI/CD: stale for head
581c47c(coverage bot 6000 covers prior63ae439) · Local checks: 57/57 relevant tests pass (build 0 warnings/0 errors, .NET 10)