feat(tags): inherited tags can be re-weighted per child (ADR 0060) #242
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/inherited-tag-reweights"
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?
Inherited tags could only be toggled on or off per child; changing their strength meant disable-plus-re-add-as-own-tag, which also moves the tag to the tail of the prompt (and order carries emphasis, ADR 0028). This gives every inheriting row a real strength override.
Decision (ADR 0060)
A second flat list beside the disable list:
InheritedTagStrengths— canonical tag name plus the strength that tag carries on this row. Same idiom asDisabledInheritedTagsthroughout: keyed by name, scoped by the row, one scalar JSON column (undoable for free per ADR 0020), inert when the name no longer matches a parent tag. Two lists rather than one override record keeps the change additive (no data migration, stored disables untouched) and keeps a toggled checkbox and a moved slider as separate journal lines (the ADR 0045 instinct). Scope is strength only — polarity/name overrides stay rejected, per ADR 0047.What changed
InheritedTagStrengthrecord with the sharedApplyclause; all five inheriting rows (outfit, location variant, CG variation, cast member, cast override) gain the property, profile field, and re-weight step in theirEffectiveTags. The two inlined effective-tag copies (CgVariationImageGenerator,CgVisionScoping) and the editor-side previews apply it too, slot-aware.TEXTcolumns viaAsJsonList, one migration (defaultValue: "[]", hand-fixed from EF's""as usual).TagAuthoring's inherited rows trade the read-only weight readout for the same strength slider the editable rows use — kept rows only; off/read-only rows keep the readout. Sliding back onto the parent's own value deletes the entry, so the tag inherits live again. The inherited rows now share the editable grid so both slider columns align. All four consumers thread the new state through their auto-save.update_outfit,update_location_variant,update_cg_variation,update_cg_cast_member,override_cg_cast_membergaininheritedTagStrengths(null-keeps merge, wholesale replace); the get-tools report the authored re-weights.black_hair(0.8), "After the rain" re-weightsrooftop(1.3), so every editor state is browser-verifiable;SeedDevDataTestsassert both.Verification
sunsetrow shows no slider, and the inherited slider column aligns with the editable rows.🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.6%
Kagura.Domain - 94.3%
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 - 82%
Kagura.UseCases - 95.6%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! This is how you extend an inheritance idiom~ Every single inheriting row — outfit, location variant, CG variation, cast member, cast override — gains one property, one profile field, one JSON column, one re-weight clause, and the shared
Applyclause lives in ONE place so the copies cannot drift apart. The two-list shape (disables beside re-weights) is the ADR 0045 instinct taken to its logical conclusion: a moved slider and a toggled checkbox are separate journal lines. And sliding back onto the parent's own value deletes the entry so the tag inherits live again? Fufu~ ♡ That's elegant. That's obsessively elegant.Verdict: ✅ Looks good to me~
No blockers. No non-blockers worth nagging about either — this is clean to the bone.
✅ What I liked~
InheritedTagStrength.Applyis the single source of truth. One static method, and all seven call sites (five domainEffectiveTags/EffectiveSceneTagsbodies +CgVariationImageGenerator+CgVisionScoping+ two editor previews) use it identically: disable-filter → re-weight → own tags on top. I traced every single one. They match. Thestrengths.Count == 0short-circuit is a nice touch — zero-alloc fast path for the common case.The
is { } opattern-match is correct.InheritedTagStrengthis asealed record(reference type), soFirstOrDefaultCAN return null, andis { } ocorrectly guards against it. A stale entry (name no longer on the parent) flows through untouched. The domain testEffective_tags_reweight_kept_base_tags_in_place_and_ignore_stale_entriespins this with agone_tagthat's genuinely inert.Dedupe + clamp on ingest, every time. All five
Applymethods doDistinctBy(s => s.Tag, StringComparer.Ordinal).Select(s => s.Clamped())— first-wins semantics, explicit. The domain test feedslong_hairat 99m and 0.5m and asserts first-wins + clamp toMaxStrength. The persistence test feeds-99mand confirmsMinStrengthsurvives the JSON round-trip.The null-keeps merge is consistent across ALL five assistant update tools.
InheritedStrengthParam.Merge(current.InheritedTagStrengths, p.InheritedTagStrengths)— null keeps, provided replaces wholesale, each term normalized. I checkedupdate_outfit,update_location_variant,update_cg_variation,update_cg_cast_member,override_cg_cast_member, AND the two create paths that do tag editing (CreateOutfitTool,CreateLocationVariantToolboth passblank.InheritedTagStrengthsto preserve). The ADR 0060 Consequences bullet names this as "AGENTS.md's sharpest edge" — and it's handled.CgCastSlot.InheritedTagStrengthsmirrors theDisabledInheritedTagsoverride-or-member routing exactly —Override is null ? Member.X : Override.X. Sibling-consistent.Migration is additive and correct. Five columns,
TEXT,nullable: false,defaultValue: "[]"(hand-fixed from EF's""), matchesDisabledInheritedTagsin the model snapshot exactly. No FK, no index — correct for a scalar JSON column.CSS slider-column alignment fix is smart. The inherited rows now share the editable grid (
grid-column: 2 / 4for the name,grid-column: 4for the strength), so both slider columns line up. The comment explains why.Test coverage is thorough and directional. Domain (dedupe, clamp, stale-entry inertness, disabled-tag-strength-never-surfaces, in-place re-weight keeping position), integration (gateway-level strength verification via
OutfitImageGenerationTests, persistence round-trip with clamp), adapter (slider emits re-weight, parent-value clears entry, off rows keep readout), seed (both re-weights asserted). 128 targeted tests pass locally.ADR 0060 is exemplary. Clear context (the disable-plus-re-add workaround that reorders the prompt), sharp decision (two lists not one record), honest rejected alternatives. The amendment note in 0047 is clean ADR hygiene.
Build: 0 warnings, 0 errors (.NET 10). Full solution compiles clean.
Automated review by Jibril · 2026-08-09
CI/CD: absent (0 comments, just opened) · Local checks: build 0/0, 128 targeted tests pass (26 use-case + 43 integration + 26 adapter + 5 CG-cast adapter + 28 CG-cast integration)