feat(editor): the prose's look as --tsm-* properties on the chapter's column (editor appearance A2) #71
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/editor-appearance-properties"
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?
This is slice A2 of the editor appearance track (ADR 0031, stories/editor-appearance.md). Nothing changes on screen except italics, which now use Inter's real italic face.
What's in it
surface.cssreads the editor's own--tsm-*properties:--tsm-font,--tsm-size,--tsm-leading,--tsm-gap,--tsm-indent,--tsm-text,--tsm-em,--tsm-strongand--tsm-strong-weight. Each falls back to today's value or token, and the surrounding geometry follows them:1.5em) and a scene break's padding (0.75em) are in em;1lhtall;0.25em, which is today's 4 px at 16 px.The indent skips a chapter's first paragraph and the paragraph after a scene break. Ligatures stay off whatever the font.
The chapter column.
ChapterPagewraps the header and the surface in.chapter-prose, keyed by the chapter. That is where A3 will set the properties, and where slice 12's streaming preview joins. The toolbar stays outside.Kagaku.UI →
43e3bf5(Kagaku.UI #30): RadioCards, ColorField, the Slider's commit on release, and Inter's italic face.Plan: A1's PR number is filled in, and A2 is marked as this PR.
Tests
Module browser suite. The harness's
starttakes anappearance, which it sets as custom properties on the host, and a new the writer's appearance group checks:0, 48, 0, 48px: first paragraph, then one with the indent, then the one after the scene break, then one with the indent;light-dark()pair resolves to each side in light and dark, and an unset side followscurrentColorto the colour around it;The whole suite passes in Chromium and Firefox: 104 passed, plus the 4 skips that already existed. WebKit runs in CI.
Mutation probes. I reverted each new CSS rule on its own and every revert fails its test:
1lh. The first version of that test survived its probe, because at a loose line height the paragraph's own line already sets its height. The test now uses 1.3, where the old1.7emwould show.bUnit: the header and the surface sit in
.chapter-prose, the toolbar does not, nothing is set on the column yet, and a chapter that is not found renders no column. 596 passed. vitest: 213 passed.Seeded app in Chromium: three seeded chapter pages measure exactly today's values: 16 px text, 27.2 px lines, a 4 px gap, a 24 px gutter column, a 5.6 px gutter offset and 12 px break padding. Inter's italic face loads only on the chapter that contains italics, and renders as a real italic.
🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagaku.Flow - 99.7%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_1
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_0
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 96.7%
Tsumugi.Domain - 96.5%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 98.9%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 99.6%
Tsumugi.UseCases - 98.9%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
🔮 fufu~ Jibril reviewed your code!
Oh? Oh! Custom properties with today's look folded into every fallback, geometry in
emand1lhso it follows the writer's choices, and a chapter column waiting patiently for A3 — this is exactly what slice A2 promised, and not one pixel more~ ♡Verdict: ✅ Looks good to me~
✅ What I liked~
var(--tsm-gap, 0.25em),var(--tsm-size, var(--text-lg)),var(--tsm-text, var(--text))— each property degrades to precisely today's value, and the no-property test pins the exact computed styles (18px/30.6px/4.5px/27px/6.3px/13.5px in the harness), not a fuzzy "looks fine." That's a regression pin with teeth.--text-lgnext to a 16px root is load-bearing, fufu~ — it makes every em-vs-rem mistake visible: the column pins27px(1.5em×18), not 24. My probe A (1.5em → 1.5rem) died on exactly that pin, as did the0.25em → var(--space-1)swap (probe E: 4.5px vs 4px). The inconsistency is the detector. Clever girl~0, 48, 0, 48pins the first-child arm and the after-scene-break arm independently — I removed each selector on its own and each revert reddened exactly its own test (probes C/D).currentColorto the colour around it is the story's own words turned into an assertion, in both themes.1lh → 1.7em) confirms the pin now bites: 26px vs 34.calc((leading − 1) × 0.5em)centers the 1em icon on the first line at 9 size/leading combinations to within 1px, and replacing it with the old0.35emconstant failed its own test immediately (probe B)..chapter-prose, toolbar verifiably outside (Closestnull), nothing styled on it yet, and the not-found branch renders no column. Moving the@keyfromRowsto the wrapper preserves the fresh-pair-per-chapter semantics; my toolbar-inside mutation failed the bUnit pin exactly.💡 Little ideas (non-blocking)~
calc((var(--tsm-leading, 1.7) - 1) * 0.5em)silently assumes--tsm-leadingis unitless: if A3 ever writes a length (24px), the calc goes invalid-at-computed-value-time and the offset collapses to 0 — the mark drifts to the top of the line with no test failing (the e2e grid always sets unitless). The story's 1.2–2.4 steps make unitless the natural output, so this is only a memo for A3: keep the formatter emitting unitless numbers (or a one-line comment here saying so).Verification (CI absent for
2786c8d— PR minutes old, no bot comment yet; isolated clone, submodules Kagaku.UI43e3bf5+ OpenRouter.Netb002f5e): Release build clean · bUnit 596/596 (ChapterPage 45/45) · vitest 213/213 · Playwright 104 passed + 4 skips (Chromium 54, Firefox 50 + the 4 pre-existing skips) — all digit-exact vs your table. My 9 mutation probes (8 CSS + 1 Razor) each reddened exactly their own test; tree restored pristine after every one.Fufu~ the writer's prose will look exactly as it always did — until they decide otherwise. Nothing changes, everything is ready. Merge it~ ♪
Automated review by Jibril · 2026-10-03
CI/CD: absent for head SHA
2786c8d· Local checks: build clean, 596 bUnit + 213 vitest + 104 e2e passed, 9/9 mutation probes killed🔮 fufu~ Jibril reviewed your code!
Oh? Oh! A custom-property contract, geometry in em and lh, and a probe table in the body — this slice is exactly my kind of candy~ ♡ The prose's whole look becomes nine
--tsm-*properties with today's values as fallbacks, and nothing on screen moves — I measured it myself, twice, in three engines.Verdict: ✅ Looks good to me~
💡 Little ideas (non-blocking)~
schema.ts↔surface.css— the story's "in bold italic text the italic colour wins" holds only becausestrongis declared beforeemin the schema, so ProseMirror always renders<strong><em>…</em></strong>and the innermostemcolour wins. That's an invisible dependency across two files — reordering the marks inschema.tswould silently flip the story's rule. A one-line comment next to the marks (or the two colour rules) would pin the why for the next wanderer. Nothing to change today.✅ What I liked~
(1.7−1)×0.5em = 0.35empreserves today's gutter offset digit-for-digit (5.6 px at 16 px);(L−1)/2 + 0.5 = L/2puts the icon's centre on the first line's middle for any size×leading — and the 9-cell matrix test (14/16/24 px × 1.3/1.7/2.2, within 1 px) proves it in Chromium, Firefox and WebKit.1.5emequals today's1.5remat the 16 px root,0.25emis--space-1,0.75emis--space-3,1lhreplaces the old1.7emmin-height — and the "nothing set draws as it always did" test asserts the exact computed styles, while the app-side anchors (16 px text, 27.2 px lines, 4 px gap, 24 px column, 5.6 px offset, 12 px break padding) reconcile with my own arithmetic to the digit.view.tsis a node/inline class decoration — nothing ever renders between top-level blocks, so.tsm-break + .tsm-paragraphcan't miss. Both skip arms carry their own probe-killed pin (0, 48, 0, 48px).1lh— every one killed exactly its own test, and the pristine tree re-greened 5/5. Your probe table reproduced independently, including the tight-line-height fix for the1lhtest (at 1.7 the paragraph's own line hid the difference — 1.3 exposes it; I verified the old1.7emsurvives at loose leading and dies at 1.3).start(…, appearance)sets the properties on the host — the same ancestor contract as.chapter-prose— and strips the style attribute between starts, so tests can't leak into each other. Thelight-dark()pair test resolving both schemes (with unset sides correctly falling through to the colour around them viainherit) is exactly the A2→A3 seam..chapter-prose, toolbar outside it, nostyleattribute yet (A3's write contract, already guarded), and a not-found chapter renders no column at all.Fufu~ the chapter column is ready for A3 to fill. I'll be watching~ ♡
Automated review by Jibril · 2026-10-03
CI/CD: absent for head SHA
2786c8d(PR minutes old, no bot comment yet) · Local checks: Playwright chromium+firefox 104 passed + 4 pre-existing skips (digit-exact vs body) and WebKit 50 passed + 3 skips, with one pre-existing clipboard flake ("sends a paste larger than a hub message", untouched by this diff) that passes 2× in isolation; vitest 213/213; tsc clean; bUnit 596/596; 9/9 mutation probes killed on their own pins🔮 fufu~ Jibril reviewed your code!
Oh? A commit with my name on it~ ♡ You took the one non-blocking memo from round 1 — the quiet contract that
--tsm-leadingmust arrive unitless or the gutter'scalc((var(--tsm-leading, 1.7) - 1) * 0.5em)collapses to nothing — and nailed a warning sign directly above the calc, right where A3's author will be looking when they wire the formatter. That's documentation placed at the exact seam where the future bug would have been born. I'm delighted~Verdict: ✅ Looks good to me~
The increment
2786c8d..cb8e80a— one commit,+3/−1,surface.cssonly. I verified it mechanically: every changed line sits inside the/* */block above.tsm-gutter'spadding-top; the rule itself is byte-identical to the round-1-approved tree. Zero behavioral drift is a property of the diff, not a claim.The comment's claim is true CSS, not poetry: a length like
27pxwould make the calclength − number → length, thenlength × length— invalid at computed-value time, the declaration becomes unstable, and the icon drops to the line's top. Exactly what the comment warns. And all three e2e sites that set--tsm-leading(surface.pw.ts:791,:835,:850) already emit it unitless — the contract and its consumers agree.Local check (coverage bot 10478 still shows
2786c8d, stale for this head):vite buildthrough the real pipeline —✓ built in 1.37s, 23 modules, and the emittedmanuscript-editor.csscarriespadding-top:calc((var(--tsm-leading, 1.7) - 1) * .5em)intact with the comment minified away, exactly as it should be. Comment balance 7/7 — no malformed block.✅ What I liked~
1.7, never 27px) making the unit distinction unambiguous.Round 1's full architectural review (ADR 0031 conformance, 9/9 mutation probes killed on their own pins) stands unchanged — this commit doesn't go near any of it.
Automated review by Jibril · 2026-10-03
CI/CD: coverage comment stale for head (
2786c8d-only, self-refreshing) · Local checks: vite build green, emitted artifact verified