Editable tag text in the table (tag-UI cleanup 2) #68
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/tag-text-edit"
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?
Task 2 of the tag-UI cleanup: the table let you change a tag's strength but not fix its text. Now each row can be edited in place. As with the strength slider, this fixes both the character editor and the settings authors, since
TagAuthoringis the shared component.What changed
PromptTagEditing.Rename: re-canonicalises the term (lowercase, spaces → underscores), updates the display name, and flags it freeform — it's now hand-edited, so unverified against the database (the same rule as the freeform box). Strength and polarity are kept. It no-ops on a blank name or one that would duplicate another authored tag (a tag can't appear twice).Tests & verification
Four new tests:
Renamecanonicalises + keeps strength/polarity + flags freeform, rejects a duplicate, and no-ops on blank; plus the edit-in-place and cancel flows through the component. Full suite green (899). Browser-verified on the settings authors: a typo'dlong_hai→ edit →long hairsaved and shown corrected, the editor closing on save.Last in the batch: D full-width tabs.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.9%
Kagura.Domain - 95.9%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.6%
n
Kagura.Kernel - 90%
Kagura.Server - 97.3%
Kagura.UI - 96.7%
Kagura.UseCases - 96.1%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Inline tag-text editing — the table could change a tag's strength but never its text until now! fufu~ And you made it a pure transform in
PromptTagEditing.Rename, just like every other operation in that file. The component stays a controlled surface; the parent owns the list. Consistency is love~ ♪Verdict: ✅ Looks good to me~
I read the full diff, then the full current contents of
PromptTagEditing.cs,TagAuthoring.razor, andPromptTag.cs, then cross-checked against the siblingAddFreeform/Addmethods and the CI coverage report for head SHA98ec7a6. Everything lines up~ Let me show you~Renameis a faithful sibling toAddFreeform. The canonicalisation is identical —trimmed.ToLowerInvariant().Replace(' ', '_'). The duplicate guard checkstags.Any(t => t.Tag == canonical)across all polarities, which matchesAdd'sFirstOrDefault(t => t.Tag == tag)— the invariant is "one tag text appears once in the list, period." Strength and polarity are preserved via thewithexpression.IsFreeform = trueis set because the tag is now hand-edited and unverified — the same rule as the freeform box. fufu~ correct~ ♡The no-op guards are right. Blank → returns
tagsunchanged. Duplicate → returnstagsunchanged. Both are consistent withAdd's no-op-on-duplicate philosophy. A user who tries to renameshort_haironto an existinglong_hairsimply gets nothing — no crash, no duplicate, no silent merge.The component wiring is clean.
BeginEditseeds_editDraftwithDisplayName(the friendly text), not the canonicalTag— so the user sees "Long Hair" in the field, and the system handles re-canonicalisation on save.@key="tag.Tag"means a rename (which changes the key) correctly destroys and recreates the row element — no stale DOM. TheIsEditingconditional hides the slider/move/remove while editing, keeping the row clean. Enter commits, Escape cancels, the buttons do the same. ♪Coverage confirms the new branches are exercised. From the forgejo-actions comment for
98ec7a6:PromptTagEditing— 100% line / 71.8% branch (up from 68.1% branch in #67's run — the threeRenamepaths are all covered: canonicalise, blank no-op, duplicate reject)TagAuthoring— 96.5% line / 70% branch (the edit-in-place and cancel flows are tested through the component; the keyboard-handler branches delegate to the same tested methods)The four new tests are well-targeted: the
Renameunit tests cover all three logic paths (canonicalise+freeform+keep, blank no-op, duplicate reject), and the component tests cover both the commit and cancel UI flows through the real Blazor render.💡 Little ideas (non-blocking)~
TagAuthoring.razor:170-176] —CancelEdit()(void) andCancelled()(returnsTask) do exactly the same thing: set_editing = null. They exist separately only because@onclickbinds toCancelEditandOnEditKey's switch returnsTask. You could collapse to justCancelled()(Blazor's@onclickacceptsFunc<Task>), eliminating the tiny DRY duplicate. Purely cosmetic~PromptTagEditing.cs:81] —Renamehas an untested-but-valid branch: whencanonical == tag(the user re-enters the same canonical text with a different display casing). It skips the duplicate guard and proceeds to the map, settingIsFreeform = true. This is arguably correct (the user "edited" it), but worth a one-line test to pin the behaviour if you want to be thorough. Non-blocking since the logic is straightforward~✅ What I liked~
Renameis a pure function, fully unit-testable, zero component coupling. The same design discipline asAdd,Remove,SetStrength,Move. The component is just plumbing; the brain lives inPromptTagEditing. fufu~ architecture~short_hairtolong_hairwhenlong_hairalready exists in the other prompt is correctly rejected, not silently merged.DisplayNameis kept verbatim,Tagis re-canonicalised. The user types "Long Hair" and sees "Long Hair", but the system storeslong_hair. The split between display and canonical is honoured throughout.long_hai→long hairtypo fix is the perfect test case — the exact scenario this feature was built for.Automated review by Jibril · 2026-07-11
CI/CD: ✅ passed (899 tests, 95.3% line / 85.1% branch) per forgejo-actions for head SHA
98ec7a6· Local checks: skipped (CI green and current)Both little ideas applied in
37ab369:CancelEdit/Cancelledcollapsed —@onclickbindsFunc<Task>fine, so the void twin was redundant. There's now a singleTask-returningCancelEdit; the button and the Escape key both call it.Renaming_to_the_same_canonical_text_updates_the_display_and_skips_the_duplicate_guardcoverscanonical == tag(display recasing skips the self-collision guard, updatesDisplayName, flags freeform, keeps strength).BlazorAdapter suite green (317).