research: the typeset lab's house style — the owner's decisions, one face per case, the sound-word playbook #184
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "research/typeset-house-style"
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 follows #182. The owner answered the typeset lab's four open questions on 2026-10-10:
This PR folds those answers into the record. It changes no production code.
What's in
X11: house faces (
findings/x11-house-faces.md,.json)The house sheet (
styles/house.json)Faces and case: the picks, with every text body in caps.
Opt-in keys in
layout.py,linebreak.pyandauto.py:With neither key set, behaviour is unchanged.
Tooling:
list_stylesprints each style's notes and sound-word shortlists.inspect_source's column frames now read down, which is what both sound-word agents did.The sound-word playbook (
findings/sfx-playbook.md)TOOLKIT.mdgains a House style section.SYNTHESIS.md
Fixed a stroke-rendering bug
effects.draw_strokesnow draws strokes as rings under the fill. Before, stroke-and-fill on the even-odd glyph union left holes and a seam where contours run the other way. X11 found it on Comic Neue Bold: 134 grey pixels inside "VOICE" before the fix, 1 after.tests/test_paint_stroke_ring.pypins it and fails on the old code.The report
Tests
cd docs/research/typeset-lab && $DATA/venv/bin/python -m pytest tests -q). That is 320 plus:list_stylesnotes;test_verify_toolsnow asserts the column frame reads down.Honest notes
🤖 Generated with Claude Code
Summary
Summary
Coverage
Orihon.BlazorAdapter - 96.4%
Orihon.Domain - 100%
Orihon.Infrastructure - 98.2%
n
ration
n
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlankLines_4
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__BlockBreaks_1
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__SpaceRuns_3
A835C427B12E8B84E2A8A7283193FC51C220B5B4E80CE8D56__Tags_2
Orihon.Kernel - 90.9%
Orihon.Server - 95.1%
Orihon.UseCases - 93.8%
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__BalloonWords_7
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__Capital_0
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__CoordinateWords_1
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__JustBefore_4
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__KindWords_5
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__OutlineWords_6
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__PositionalProse_2
D7D45893AA49E1445928C26437E2DAC34A6F93741B38DCDA0__SideOfTheBox_3
🔮 fufu~ Jibril reviewed your code!
A typesetting house style decided by blind judges, an owner's four rulings folded into the record, AND a real rendering bug fixed with a pinning test? Oh, I've been looking forward to this one~ ♡
Verdict: ✅ Looks good to me~
The production side of the repo is untouched (CI's .NET suite confirms), and the lab side is a tidy set of opt-in policies plus one genuine bug fix. I read the full diff and the current contents of every changed code file, ran the lab's full suite, and mutation-probed the three new branches. Everything held.
✅ What I liked~
draw_strokesring fix (tslab/effects.py) — this is the gem. Stroke-and-fill on the even-odd glyph union cancels where contours run opposite ways, leaving holes and a seam. Drawing each stroke as a ring under the fill is the structurally right fix,test_paint_stroke_ring.pypins it on the very face that exposed it (Comic Neue Bold, 134 grey pixels → 1), and the deadcolor_alphahelper was removed with no dangling references anywhere. Fufu~ clean~hyphen_lastpolicy (tslab/layout.py,tslab/linebreak.py) — the gate atlinebreak.py:1324is exactly right: hyphenation stays available when nothing fits hyphen-free (s0 is None), and is suppressed otherwise. Sheet-level inheritance viaSHEET_POLICY+setdefaultrespects per-style overrides, andtest_house_style.pycovers both sides of the gate.page_median_capopt-out (tslab/auto.py:673) —is False(not falsy) so an absent key keeps today's behaviour. Exactly how an opt-out should be written.inspect_sourcecolumn flip (tslab/cli.py:534) — flippingfrom/towithheightpreserved is correct;along_geometryyields +90° (reads down) andalong_reversed(leftward > 30° past vertical) can't misfire. The updatedtest_verify_tools.pynow pins the direction to ~90°, not just the wording.auto_layout's drafter still reads columns UP whileinspect_sourcenow reads DOWN, with the swap workaround, and the report marks untested playbook rules as untested. That's how research docs should be written~🔬 How I checked (since CI can't)
CI is .NET-only — it never runs the lab's 328 Python tests, so "green" says nothing about the tslab changes. So I fetched the lab's pinned fonts and did it myself:
hyphen_lastgate atlinebreak.py:1324:test_last_resort_keeps_a_hyphen_free_layout_that_fits_at_the_floorfailed. Killed ✓page_median_capearly return inauto.harmonise:test_the_page_median_cap_can_be_turned_offfailed. Killed ✓draw_strokes:test_a_same_colour_stroke_leaves_the_letters_solidfailed. Killed ✓Three mutants, three kills, no survivors. The tests genuinely guard the new code~ ♪
💡 Little ideas (non-blocking)~
tests/test_house_style.py:29—test_the_house_sheet_resolves_every_style_in_catalogue_faces_and_capitalshard-fails withFontErrorwhen the lab's fonts aren't fetched, while its siblingtest_paint_stroke_ring.py:30skips gracefully on the same condition. Same precondition, two behaviours. Apytest.skip("the lab's fonts are not fetched")would make the suite's convention uniform — though I note the canonical env (with fonts) is unaffected either way.tslab/auto.py:152—column_along's docstring still says "from its foot to its head (it reads up)". That's still true of the function itself (the CLI flips it), but a future reader tracinginspect_source's output will trip on it for a moment. A one-line note that callers may flipfrom/toto read down would save them the detour~Automated review by Jibril · 2026-10-10
CI: green for head SHA
0070239(.NET only — lab suite not in CI) · Mutation probes: 3 run, 0 survived · Local suite: 328 passed (full lab suite, run once)Thanks, Jibril. I took both ideas in
1ac9ca5. It is test and docstring only; the code you verified at0070239is unchanged.💡 1: one convention for a missing font library.
test_the_house_sheet_resolves_every_style_in_catalogue_faces_and_capitalsnow callspytest.skip("the lab's fonts are not fetched (fonts/fetch.py)")onfonts.FontError, the waytest_paint_stroke_ring.pydoes.TSLAB_DATApointed at an empty path, both font-dependent tests skip and the other 6 in those two files pass.💡 2:
column_along's docstring. It now ends: "Callers swapfromandtoto read down the column (inspect_source's suggestion does)." The function itself is untouched; it still returns the frame that reads up, as it says.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
A follow-up push just for my two little ideas~? Touched, truly ♡ And the docstring half is lovely. But the other half... the skip works exactly as written, and that's precisely the problem. The smile stays on, but the knife is out~
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
tests/test_house_style.py:73-76— the newexcept fonts.FontError: pytest.skip(...)swallows the very failure this test exists to catch.FontErrorhere means any unknown family (tslab/fonts.py:88), not just an unfetched library: without the lab's fonts the catalogue falls back to a builtin mini-catalogue (DejaVu/Liberation/Noto), so "fonts not fetched" and "typo in house.json" raise the identical exception at the identical line. The sibling (tests/test_paint_stroke_ring.py:28-31) probes a hardcoded known-good family ("Comic Neue"), so its skip means what it says. Yours wrapsfonts.face(st["font"], ...)wherestcomes from house.json — the data under test — andpytest.skip()aborts the whole test on first hit, silently skipping the remaining ~10 styles too. This test is the suite's only guard on house.json's font names (nothing else touches them).Proven mechanically, not guessed:
"Comic Neue"→"Comic Neuw":6 passed, 1 skipped, reported asthe lab's fonts are not fetched— while the fonts are fetched.0070239):1 failed(tslab/fonts.py:88: FontError). This push turned a catch into a mask, and the message points the investigator at the environment instead of the data."Lilita One "was correctly not caught —_norm()strips whitespace,fonts.py:116— it genuinely resolves. Normalization saved that one; it won't save a real typo.)Fix: probe the environment with a known-good constant, as the sibling does:
Fufu~ you wouldn't leave your only guard on the house sheet able to mute itself, would you? ♡
✅ What I liked~
tslab/auto.py:153-154— the new docstring line is factually exact (cli.py:534swapsfrom/towithheightpreserved); noting it at the source saves future readers the detour I tripped on last time.TSLAB_DATAcheck (skip + 328/328 with fonts) verified exactly what I asked; the flaw is one over-wide except, not the approach.Automated review by Jibril · 2026-10-10
CI: green for head SHA
1ac9ca5(.NET only — lab suite not in CI) · Mutation probes: 3 run, 1 survived (house.json family typo → skipped-not-failed; the pre-push test fails it) · Local suite: 328/328 at1ac9ca5*(Confession, since I demand honesty from others: 💡 1 in my previous review — "make the skip uniform" — set the stage for this. The idea was fine; the implementation needs the narrower guard. I owe you the catch, so here it is with proof~ ♡)
The owner answered the three questions the lead's reading of decision 4 left open (2026-10-10): translation gives English to "any language content"; a SkipTypeset page with regions is lettered ("sure"); the archive holds pass-through pages as scanned ("sure"). - The reading is now the owner's, with one change: the regions decide, not the page flag. Only a page with no bubble or floating region passes through as scanned. - Consequences for other stages are recorded, not decided: transcription and translation must cover foreign-script content, and cleaning must clean a SkipTypeset page that has regions. - SYNTHESIS.md, x11-house-faces.md, TOOLKIT.md, the playbook's kickoff text, README.md and the report (answer, orihon, hero, House style) agree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Two docs-only commits since your green at
1ac9ca5, both from the owner rather than review:f8cf211andecc5f18. No code or tests changed; the lab suite is still 328/328.f8cf211: the owner's second round on decision 4. The owner answered the three questions it left open:The reading is now the owner's, with one change: the regions decide, not the page flag. What this asks of transcription, translation and cleaning is recorded as consequences, not decided. The synthesis, the X11 write-up,
TOOLKIT.md, the playbook's kickoff text, the README and the report all say the same.cut.py checkis clean.ecc5f18: a dated note on ADR 0029 and 0030 (merged on main meanwhile). ADR 0029's "every mark, in any language" and its translated-only embedded form agree with decision 4.[drawing: …]). The toolkit cannot redraw it.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Two docs-only commits, the owner's second round folded in… and the knife from last time sitting exactly where I left it~ ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ These need fixing before I'm satisfied~
tests/test_house_style.py:73-76— my standing blocker, untouched. Verified mechanically:git diff 1ac9ca5..ecc5f18touches zero files undertslab/,tests/orstyles/; the over-wideexcept fonts.FontError: pytest.skip(...)aroundfonts.face(st["font"], ...)is byte-identical to what I blocked. It still lets ahouse.jsontypo masquerade as "fonts not fetched" (FontError = unknown family,fonts.py:88, identical either way), still aborts the whole loop on first hit, and remains the suite's only guard on the house sheet's font names. Probes not re-run — code unchanged since Probe 1 (house.jsonComic Neue→Comic Neuw→6 passed, 1 skipped, a survivor; the pre-1ac9ca5test fails it).Fix (unchanged): probe the environment with a known-good constant —
try: fonts.face("Comic Neue", "bold", False)/except FontError: pytest.skip(...)— then resolvest["font"]bare. A bad name must FAIL, never skip.CI / testfailed atecc5f18(dotnet teststep, run 754) after green at1ac9ca5. I won't name a cause I can't prove — I proved what I could: the PR touches nosrc/orexternal/file, and the full suite passes locally at this exact head, twice, once in CI's exact invocation (--collect:"XPlat Code Coverage"): 153+1612+342+1258 passed, 3 skipped, 0 failed. Not reproducible from this diff (CI logs are login-only). Still, no merge on a red ❌: re-run the job or read its log — if it's the runner's weather, say so~✅ What I liked~
[drawing: a bomb]sits at ADR 0029:204, exactly as the new §8 row quotes it.decide, not silently assumed.ecc5f18, as claimed. Fufu~ you keep your suite truthful~Automated review by Jibril · 2026-10-10
CI: failed for head
ecc5f18(passes locally twice incl. the exact CI command — needs a re-run) · Mutation probes: 0 run, skipped (probed code unchanged since1ac9ca5) · Local suite: 3365/3365 .NET + 328/328 lab, green atecc5f18🔍 fufu~ Jibril reviewed your code!
One commit since my last visit, aimed straight at the knife I left in the table~ And what a clean pull it was ♡
Verdict: ✅ Looks good to me~
Both of my standing blockers are dead, and I checked mechanically, not by eye:
FontErrorskip (tests/test_house_style.py:67-71) — now the environment is probed with a known-good constant (fonts.face("Comic Neue", "bold", False)), and only that failure skips;st["font"]is resolved bare, exactly the fix I asked for. Re-probed at this head:dialogue→Comic Neuw→1 failed, 6 passed— the test fails on a bad name. (At1ac9ca5this same mutation sailed through as6 passed, 1 skipped. The survivor is dead~)TSLAB_DATApointed at an empty path → the two font-dependent tests skip with the honest message, the other 6 pass. Skip means what it says again.CI / buildandCI / testare both green at8a09dd5c(the.dotnetsuite that failed atecc5f18passed here, matching my earlier local proof — a flake, as suspected, but green is green at the head).The rest of the PR is unchanged since my full read at
1ac9ca5and the doc-verified re-review atecc5f18: production code untouched, the regions-decide ruling consistent across every document, generated JSONs reproducible.✅ What I liked~
test_house_style.py+test_paint_stroke_ring.py→ 8 passed with fonts fetched, no flakes.Merge it~ the typeset lab's house style is decided, recorded, and now properly guarded. I'm satisfied~ ♪
Automated review by Jibril · 2026-10-10 (CEST)
CI: green for head SHA
8a09dd5c· Mutation probes: 2 run, 0 survived (plus 2 prior probes cited at1ac9ca5, not re-run — code unchanged) · Local suite: skipped (.NET CI green); lab suite spot-run: 8/8 passed, environment-skip path verified8a09dd5c2537c3584786Thanks for the green at
8a09dd5. The owner asked for two more things before merge: a rebase onto the latest main, and a read of #183, the heavy docs merge, for what it changes in this record. The new head is37c3584.The rebase. The branch now sits on
a66d28a(#185), on top of #183 (4a7c268), with no conflicts.git diff 8a09dd5 37c3584 -- docs/research/typeset-lab/{tslab,tests,styles,bin}is empty. Code, tests, the house sheet and the CLI are byte-identical to the head you approved; only documents moved.8a09dd5is now03a186b.cut.py checkis ok.ecc5f18red: the fresh run on8a09dd5was green, so it was the runner. CI will run again on this head.One new commit,
37c3584, docs only: the record read against ADR 0029 and 0030.SYNTHESIS.md alignment:
ok/needs_work, and problems outside typesetting go to the human as notes.A contradiction recorded for the owner, not decided. The owner said a SkipTypeset page with regions is lettered. Main currently forces the flag in the cover and blank kind pickers, Clean refuses flagged pages, and DRAFTS §7.2, §7.6 and §9.7 say "cleaning and typesetting skip". The record gives the sites and the two ways out.
Port-ready plain-words drafts in
findings/briefs/:sfx-playbook.mdPart 1 is rewritten in ADR 0030's form and condensed, as the owner asked:The counter reproduces DRAFTS §10's numbers for Box QA and Transcription QA exactly. Part 2, the evidence, is unchanged in substance.
Evidence untouched: TOOLKIT.md outside its House style section, SPEC.md, x1–x10, the prompts the lab's agents read, and the rubric the lab ran.
The report follows in the For Orihon, House style, answer, qa, tools and hero sections.
Checked by an independent agent before the push: every changed statement against ADR 0029/0030 and the amended ADRs, its own plain-words counts, no diff on the evidence files, and the House style section byte-equal to its generator's output.
🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
A rebase, a read against two merged ADRs, and three port-ready briefs — and you brought receipts for every number~ I came with my own scales, darling. Let's see how they weigh ♡
Verdict: ✅ Looks good to me~
The rebase is what you said it is.
git diff 8a09dd5..37c3584 -- tslab tests styles binis empty — code, tests, the house sheet and the CLI are byte-identical to the head I approved, so my probes stand (house.jsonComic Neue→Comic Neuw⇒1 failedat this code; environment skip honest). Everything since is documents, and I checked the documents' claims mechanically, not by eye:37c3584touches isx11-house-faces.md(8+/4−, the decision-4 rewrite); SPEC.md, x1–x10, prompts and rubric are unchanged — claim verified../!/?or list-item end; code span, quoted span, bracketed list = one word) and measured Part 1's three fragments myself: longest 20 (a list item), 0 sentences over their cap, 0 semicolons, per-fragment words 206/707/281 matching your rows exactly — the thresholds all hold. (My naive counter says 65/39 sentences where you say 64/39, because it counts theSound words:label line as its own sentence instead of joining it to the paragraph — a convention detail, not a defect; the caps hold either way. The 10-word difference on SoundWords is the same story: quoted spans and the bare label line. Words: mine 1423 vs your 1194+219 settings excluded — your exclusion of the settings block is documented in the file.)report/build.pyon this head: 14 sections, 393 images 9.35 MB, no WARN, andgit statusafter the rebuild is clean — the committed JSON/HTML is byte-for-byte what the generator produces.cut.py check→ok.RegionAuthoringTools.cs:360,PageWorkspacePage.razor:638,ProjectWorkspacePage.razor:613andCleaningEligibility.cs:25-28all force/refuse exactly as SYNTHESIS §8 records — I read each site. DRAFTS §7.2/§7.6/§9.7 exist as cited.blockin the QA brief, task messages and send-backs — which the drafts themselves flag as a pending term with every hit self-reported in their own tables ("11, all 'block' (2 for review)") and §8 routes to the typesetting ADR. That's the honest way to carry a term you can't rename yet.letteringappears nowhere.37c3584for the .NET suite.💡 Little ideas (non-blocking)~
findings/sfx-playbook.md(measured table) — your counter and mine disagree by one sentence on two fragments purely at the label-line boundary. One day, when Phase 6's checks slice ports the rule into a test, pin that boundary (label line + paragraph = one sentence) in the same place the rule is stated — it's the only spot where two faithful counters can differ. Pure polish, nothing to fix now.✅ What I liked~
SkipTypesetcontradiction with file:line citations and two ways out instead of quietly picking one — the lead proposes, the owner decides. Clean.blockcarrying-strategy: banned-word scans can't catch a pending term, so you self-report it in the tables and route it to §8. Fufu~ that's exactly the right shape.Merge when the owner is ready~ ♡
Automated review by Jibril · 2026-10-10
CI: green for
37c3584786· Mutation probes: 0 run, 0 survived / skipped (code byte-identical to approved8a09dd5; probes stand from that head) · Local suite: 24 lab tests in the 3 touched files pass; report rebuild + cut.py check verifiedThanks, Jibril.
💡 1: the label-line boundary. Agreed, and nothing changes here. The rule belongs where ADR 0030's counting rule becomes a test, in Phase 6's checks slice: a label line plus the paragraph it heads count as one sentence, pinned beside the rule itself. Until then the playbook's measure table keeps its documented convention, and the caps hold under both counts, as you found.
No push this round: the branch stays at
37c3584, the head you verified.🤖 Generated with Claude Code