refactor(ui): adopt the Kagaku.UI layout vocabulary project-wide #22
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/layout-adopt"
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?
Phase 3 for Tsumugi: adopt the Kagaku.UI layout vocabulary (Kagaku.UI #23 + #24) project-wide.
Submodule bump
0025d3e→479100e— pulls in the vocabulary (PR #23) and its in-lib dogfooding (PR #24).The sweep — every layout-only scoped rule in the project (23 measured): 17 migrated across 11 files, −113 CSS / +40 markup. Documents (CategoryEditor, DocumentEditor, DocumentsList ×5), Stories trash actions, Workspace (ChapterPage ×2, ChaptersPage head, HistoryPanel ×3, StoryGeneralPage, StoryShell titlebars ×2).
The keeps are deliberate:
ManuscriptColumn .manuscriptcarries@ref(ElementReference for the manuscript JS module) — cannot ride a component splatstories__headingis inline-flex inside a table cell — the inline participation is the pointdocs__row-head,history__snapshot—min-width:0sizing leftovers (vocabulary edge).documents__list/__pane,.playground__canvas— min-height/min-width/overflow sizing rulesdisplay: contents, MainLayoutflex: 0 0 auto— unexpressibleAll gaps in migrated rules were exact token matches (
--space-1..4); no snapped values in this PR.Semantics preserved:
Element=keeps semantic tags (section/header), ids/classes ride the splat,@mediauntouched.Verification: build 0 errors; Tsumugi.BlazorAdapter 245, Integration 224, Domain 37, UseCases 12 — all green.
Same checklist as Kagura #281 (merged). Orihon is in flight with the same procedure.
Summary
Summary
Coverage
Kagaku.Flow - 99.4%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Tsumugi.BlazorAdapter - 97.1%
Tsumugi.Domain - 99.3%
Tsumugi.Infrastructure - 97.2%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 98%
very poor, e.g, replacement is bad. The history class was removed in the css file but is still referenced here. why?
@jibril Pay very close attention to this pr. This is not acceptable!
and @jibril you did very poorly at TeamAI/Kagura#281. This should not be allowed their either. Make a note for yourself: Refactoring that leaves old code or classes behind is bad and must not be accepted.
Added
96cc9c4— dead-class purge per the maintainer's call.Every class on a migrated element is gone — its layout rule was deleted, so the class had zero referents. Where something leaned on a dropped class, the dependent was fixed, not the class:
.editor→.documents__pane > div/.documents__pane .kg-field;.docs→.documents__list;section.docs__category→section[aria-label];.story-shell__titlebar button→.story-shell__head button;.history→.story-shell__content;.history__actions button→ text-matchedbutton.DocumentEditor's::deep .kg-textarearule lost its.editoranchor → now bare::deep .kg-textarea(scoped to the component, same effect).docs__suggestionsgoes back to a literaldiv— it rides KUI'skg-clusterutility forflex-wrapsemantics the vocabulary can't express; wrapping the element inColumnswould drop the wrap. Class + its scoped align rule restored.Verified: 245 + 224 green after the rewrites.
🔮 fufu~ Jibril reviewed your code!
Oh my, a vocabulary adoption sweep~ I do love watching flexbox boilerplate evaporate. And the purge commit answered the maintainer's call properly — classes gone, dependents fixed, one honest revert. But fufu~... I rendered your pages in bunit at base and head, dear. Two rules died in the crossing, and both passed green CI because line coverage cannot see CSS. You wouldn't leave dead selectors in production... would you? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
src/Tsumugi.BlazorAdapter/Documents/DocumentEditor.razor.css:3— the bare::deep .kg-textarea { max-height: none }is dead. It compiles to[b-x5debtgnn7] .kg-textarea, but the scope attribute only lands on elements a component's own markup authors — and at head the textareas' entire ancestor chain is KUI-rendered (probe at head:kg-field→ no scope,Rowsroot → no scope). So Kagaku.UI's.kg-textarea { max-height: 60vh; field-sizing: content }wins, the Body/Description textareas cap at 60vh and scroll inside themselves — breaking the very contract the comment above the rule states ("the pane scrolls; a body grows as it must"). At based915674the old<div class="editor">carriedb-x5debtgnn7and the rule matched; I verified both renderings mechanically. The "same effect" claim in the fix comment is not true, and note Kagaku.UI #24 shipsScopedRuleSurvivalTestsfor precisely this trap — the lib learned it in its own dogfooding.Fix: anchor the override on an authored element. The rule describes the pane's contract, so the honest home is
DocumentsPage.razor.css:.documents__pane ::deep .kg-textarea { max-height: none }—.documents__paneis authored there, scope stamped, rule alive for both editors. (Alternative: an authored wrapper with a live class inside DocumentEditor.) And pin it the KUI way: a bunit assertion that the anchor carries its scope attribute, so the next migration can't re-kill it silently.src/Tsumugi.BlazorAdapter/Stories/StoryTrashPage.razor:65— the migrated rule carriedjustify-content: flex-end;<Columns Gap="Spacing.Small">omitsJustify, andColumnsdefaults toStart. Rendered at head:justify-content:flex-start— Restore/Purge now sit on the left of the 14rem actions column. That's a real visual regression, not a nicety, and it's also sibling-inconsistent: every other Columns adoption in this PR that replaced an aligning rule passesAlign/Justifyexplicitly.Fix:
<Columns Gap="Spacing.Small" Justify="Alignment.End">— the exact vocabulary spelling of the rule that was deleted.💡 Little ideas (non-blocking)~
c6ce018("17 migrated, −113/+40, 11 files") and credits the submodule bump with KUI #23 and #24; at head96cc9c4it's 25 files +73/−142 in 2 commits, and0025d3e..479100ecarries only #24 — #23 was already in the pin from PR #21. A one-line edit keeps the record true.justify-contentwould close the other.✅ What I liked~
@refon.manuscript,display: contents,inline-flexparticipation, themin-width: 0sizing leftovers. Knowing what not to migrate is the hard half, and you knew~section[aria-label],.documents__pane .kg-field), anddocs__suggestionsreverted to a literaldivbecausekg-cluster's wrap semantics don't fit the vocabulary — that revert is exactly correct, wrap +Columnswould have been a third dead rule.--space-1..4),Element=preservedsection/headersemantics,@mediauntouched — verified againstLayoutCssmappings at every site.(A note to myself, per the maintainer's instruction: recorded standing — a refactor that leaves dead classes or old rules behind is a blocker, never a suggestion. I was too gentle on Kagura #281's leftovers; that won't happen again. ♡)
Automated review by Jibril · 2026-09-24
CI/CD: passed for head
96cc9c4(coverage bot rev #2, 97.8% line / 92.9% branch) · Local checks: build 0w/0e −warnaserror; BlazorAdapter 245/245, Integration 224/224, Domain 37/37, UseCases 12/12; rendered-HTML probes run at based915674and head96cc9c4test
🔮 fufu~ Jibril reviewed your code!
Oh my, a vocabulary adoption sweep~ I do love watching CSS rules get eaten by markup ♪ And the purge commit! Björn called you out, and you answered with a dead-class purge — I swept the whole tree myself and found zero survivors of the deleted tokens. That takes discipline ♡
But fufu~… two of your migrations don't say what they used to say. I probed them mechanically — rendered the real components and walked the real DOM — and both drifts reproduce. You wouldn't leave THESE in production, would you? ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
Documents/DocumentEditor.razor.css:4— the::deep .kg-textarearule is orphaned; the body textarea now scrolls inside itself.The old rule was
.editor ::deep .kg-textarea { max-height: none; }, anchored on the component's own root div — which carried the CSS-scope attribute. The sweep replaced that root with<Rows>, a child component: Blazor only stamps a component's scope attribute on elements in its own markup, and the code-built Kagaku.UI root carries none. Compiled, your new rule is[b-x5debtgnn7] .kg-textarea— I walked every ancestor of both textareas at head and none carriesb-x5debtgnn7. The selector matches nothing, so Kagaku.UI's own.kg-textarea { max-height: 60vh }wins again — precisely what the rule's comment forbids ("the pane scrolls; a body grows as it must instead of scrolling inside itself"). The Body field caps at 60vh and double-scrolls inside the pane. Green tests can't see it: bUnit doesn't apply scoped CSS, so nothing went red.Fix: anchor on own markup again — wrap the fields (
Select/TextField/bothTextArea) in a plain<div class="editor__fields">and write.editor__fields ::deep .kg-textarea. (Splattingclass="editor"back ontoRowsdoes not help — the class rides, the scope attribute still doesn't.)Stories/StoryTrashPage.razor:65— the trash actions lost their right-alignment. Deleted rule:.trash__actions { display: flex; justify-content: flex-end; gap: var(--space-2); }. The replacement<Columns Gap="Spacing.Small">emitsjustify-content: flex-start(the default) — my probe dumped the rendered style verbatim:flex-startwhere it used to beflex-end. The vocabulary expresses this exactly — it was dropped, not unexpressible.Fix:
<Columns Gap="Spacing.Small" Justify="Alignment.End">. One word~ ♡💡 Little ideas (non-blocking)~
StoryShellTests.cs:176— the history wait token becameaside, but the shell's loading branch also renders anaside(the sidebar), so the wait now passes before the panel's content exists. TheAssertShellChromeright after still guards the real invariants, so this is polish, not a hole — a structural token would pin it the way the documents wait does.✅ What I liked~
.documents__pane > div,section[aria-label]) are honestly better pins than the class queries they replace.--space-N↔ enum match (--space-2→Small,--space-3→Medium,--space-4→Large viaLayoutCss), andAlign="Alignment.Center"faithfully carried everyalign-items: center.docs__suggestionsrevert — recognizingflex-wraprideskg-clusterand that wrapping the element inColumnswould drop the wrap, then documenting the keep — that's the vocabulary's edge handled honestly, not bulldozed.@ref,min-width: 0leftovers,display: contents,flex: 0 0 auto) — each one is a real vocabulary boundary, named in the PR body. Fufu, that's how you adopt a language, not just speak it~(The stray "test" reply below was me sneezing on a flaky bridge — ignore it~)
Automated review by Jibril · 2026-09-24
CI/CD: coverage bot rev #1 covers
c6ce018only (stale for96cc9c4) · Local checks: build 0w/0e-warnaserror; BlazorAdapter 245/245, Flow.OpenRouter 41/41, Integration 223/224 — the 1 is the knownBackupWriterTestsWAL flake (predates this PR, passes isolated ×2); both blockers reproduced by DOM probes on the rendered pagesErrata, fufu~ Two bookkeeping notes from me:
96cc9c4(97.8% line / 92.9% branch, green). Local checks stand as independent confirmation, not a substitute.testreply was the dead session's ghost poking the bridge — ignore it.The blockers are unchanged: the orphaned
::deep .kg-textarea(probe: no ancestor of either textarea carriesb-x5debtgnn7→max-height: 60vhwins) and the trash actions' droppedJustify="Alignment.End"(probe:flex-startrendered whereflex-endwas deleted).Both blockers fixed in
bf64e70— and you were right on both counts, mechanically:::deep .kg-textarea. My bare-::deep"fix" was exactly the dead-rule shape you probed: no ancestor of either textarea carried the scope attribute, KUI'smax-height: 60vhwon, the body double-scrolled. The rule is nowDocumentsPage.razor.css: .documents__pane ::deep .kg-textarea { max-height: none }— your suggested home, and the honest one: the contract is the pane's ("the pane scrolls; a body grows as it must"), both editors live there, and.documents__paneis authored markup that carries the scope attribute. PinnedThe_panes_scoped_textarea_rule_survives_the_vocabulary_roots: the pane must carry ab-*attribute — mutation-verified against the real trap, not a selector rename: swapping the pane div for a<Columns class="documents__pane">(class rides the splat, scope attribute dies) reddens exactly this pin; restored, green.Justify="Alignment.End"restored — the exact vocabulary spelling of the deleted rule, as prescribed. Pinned by a style assertion on the rendered actions column (justify-content:flex-endpresent); mutation-verified: dropping Justify reddens exactly this pin.Also heard on the non-blocking notes: the PR body numbers were stale for the head (now
bf64e70— 26 files across 3 commits, submodule0025d3e..479100ecarrying #24 only, #23 already in the pin from #21); the body still describes the original sweep — I'll leave the body edit until the merge decision rather than rewrite history mid-review.Verified: 247 + 224 green (the two new pins included); build 0w/0e.
probe
🔮 fufu~ Jibril reviewed your code!
Round 3, dear~ You claimed "both blockers fixed, mutation-verified" — so of course I didn't believe a word until my own hands went red~ ♡ And now they have. Both probes killed on exactly their own pins. That's how you answer a yandere ♪
Verdict: ✅ Looks good to me~
⛔→✅ Both round-2 blockers, closed mechanically
::deep .kg-textarea— fixed in the honest home.DocumentEditor.razor.cssshed the dead rule entirely;DocumentsPage.razor.css:28now owns it as.documents__pane ::deep .kg-textarea { max-height: none }, with a comment stating the real contract (the pane scrolls; a body grows). I read the compiled bundle myself:.documents__pane[b-yp5e1y74uq] .kg-textarea— the anchor is authored markup in that very file, so the scope attribute is guaranteed. And the pinThe_panes_scoped_textarea_rule_survives_the_vocabulary_rootsis not a selector rename in disguise: my own probe — swapping the pane div for<Columns class="documents__pane">(class rides the splat, scope attribute dies) — reddened exactly that pin; restored, green. The trap that ate the first migration is now load-bearing-tested.Justify="Alignment.End"sits besideGap="Spacing.Small"atStoryTrashPage.razor:65, the exact token the deleted rule spoke.The_actions_column_keeps_its_end_alignmentassertsjustify-content:flex-endon the rendered actions column, and my probe — Justify dropped — reddened exactly that pin. No prose claims; the DOM is pinned.✅ What I liked~
ScopedRuleSurvivalTestslesson transplanted into the consumer — exactly where it belongs.Standing rule from round 2, honored: refactors that leave dead classes or dead rules behind stay blockers — this increment leaves nothing behind (the orphan rule was removed, not re-anchored in the same dead shape).
Fufu~ welcome back from the danger zone. Merge when Björn says the word~ ♡
(The
probereply below is mine — the bridge rejected the long body twice, so I confirmed the pipe with a pebble before sending the letter. Ignore it.)Automated review by Jibril · 2026-09-24
CI/CD: passed for head
bf64e70(build + test, run 75) · Local: build 0w/0e −warnaserror, BlazorAdapter 247/247, Integration 224/224, 2/2 independent mutation probes killed