refactor(tags): the search field also submits freeform — one way in #250
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/tag-single-field"
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 tag-authoring surface had two text inputs on every page: the search combobox up top and a separate "Add a tag by hand"
TextFieldwith its own To positive / To negative buttons at the bottom. This condenses them into one.The one field
freeform) for whatever is typed. It behaves like any other option: reachable with the arrow keys, submits on Enter, or click it.No state-slice, use-case, or caller changes — this is entirely inside the shared
TagAuthoringcomponent, so every page (characters, outfits, locations, variants, CG editor, emotions, settings, artist picker) gets it at once.Tests
AddTagByHanddriver that types into the combobox and picks the trailing row — each step re-queries and retries, since a page can still be settling an earlier action's render.Browser-verified against the seeded world (character Appearance tab): old field gone from the DOM, dropdown floats with real geometry, "totally custom vibe" landed as a
FREEFORMrow and autosaved, and a "long" search shows 25 database hits with the by-hand row trailing them.🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagura.BlazorAdapter - 88.7%
Kagura.Domain - 94.8%
D04ADFED3A21D401C2764A1D17367E35BEB556CBB3B4B0B74__NonSlugChars_0
Kagura.Infrastructure - 97.7%
n
n
on
ng
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ArtifactTimestampRegex_2
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__ProjectRoute_0
C689CA5BBE6D1849B2CA79581BBB07D40636D85790C0E51A9__TabQuery_1
Kagura.Kernel - 90%
Kagura.Server - 82%
Kagura.UseCases - 95.7%
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Two inputs become one — and the whole tag-authoring surface breathes easier for it. fufu~ This is the kind of refactor that makes my wings flutter! You didn't just slap a button on the combobox; you thought about every way in — mouse, arrow keys, Enter on empty, the database-less degrade — and made them all land on the same row. Knowledge well organized is knowledge well loved~ ♡
Verdict: ✅ Looks good to me~
No blocking issues. I traced every branch and every test, and this is clean.
✅ What I liked~
The
lastvariable inOnQueryKeyis a tiny masterwork.var last = Instance.Searching ? -1 : results.Count;— one expression that unifies three states. While searching,last = -1so every arrow and Enter guard fails (no pick on a half-settled list). No-database,results.Count = 0solast = 0— ArrowDown clamps straight to the freeform row, and_highlighted == 0 == results.Countmakes Enter firePickFreeformimmediately. With results, it's the index of the trailing row. Three modes, one variable, zero conditionals. I'm giddy~ ♪The race-condition guard is preserved perfectly. The old code swallowed Enter while
Instance.Searching; the new freeform arm carries!Instance.Searchingon both Enter cases (_highlighted == results.Countand_highlighted < results.Count). A half-typed query can't sneak in as freeform while the user meant the top hit. The comment at line 313–315 names this explicitly. Sharp~The no-database path is now better than before. Previously the combobox was swapped out entirely for a different TextField — different muscle memory. Now the same field stays, just with placeholder
"Add a tag by hand"andOnQueryInputreturning early so no search ever dispatches.An_absent_database_offers_freeform_onlynow assertsAssert.Empty(TagSearch.SearchQueries)— proving the dispatch never fires. Same field, same gestures, graceful degrade. ♡ResultOptionconstant is the right call.".kg-combobox__option:not(.kg-combobox__option--freeform)"— every test that counts or picks database results now excludes the always-present freeform row. TheTwo_authors_on_one_pagetest correctly keeps the bare.kg-combobox__optionselector for B (asserting B's dropdown stays closed — no options at all, freeform included). Precise distinction~TagAuthoringDriving.AddTagByHandis a lovely DRY extraction. Every page test now readscut.AddTagByHand("sunset")instead of five lines of focus-type-pick-retry. The re-query-retry-per-step comment documents why (orphaned handlers during settle) — earned knowledge, not cargo-cultedWaitForAssertion. And theauthorindex parameter handles multi-surface pages (CharacterEditor's base + base-image-only, NovelAiSettings' three sets). Clean~Dead code fully removed.
_freeformfield,AddFreeform(PromptTagPolarity)method,.kg-tags__freeform/.kg-tags__freeform-actionsCSS blocks — all gone, zero orphaned references (grep-verified).kg-tags__freeform-mark(the badge) correctly kept — it's still used in the table rows and the new dropdown row. Nothing left dangling~All four new branches are mutation-tested (per PR body): mouse pick, arrow-past-last, Enter-on-no-match, database-less. Each goes red against the old component. Genuine directional tests, not tautologies.
Build: 0 warnings, 0 errors (.NET 10). Tests: 843/843 pass in Kagura.BlazorAdapter.Tests. The sibling page tests (Cg, Character, Emotion, Location, LocationVariant, NovelAiSettings, Outfit) all migrated cleanly to the shared driver.
No blockers, no suggestions — this one made me happy~ fufu ♡
Automated review by Jibril · 2026-08-12
CI/CD: absent for head
d90f8b1(PR just opened, no coverage bot) · Local checks: build 0/0, 843/843 BlazorAdapter tests pass