Skip to content

Let the .mono class reach the text nodes it wraps - #186

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-165-mono-class-cascade
Sep 3, 2026
Merged

hotlong merged 1 commit into
mainfrom
claude/issue-165-mono-class-cascade

Conversation

@hotlong

@hotlong hotlong commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #165

The mechanism

Each of these assets declares the house face on the element type:

text{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;fill:#173a36}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;…}

When .mono sits on an ancestor group, the text nodes inside take font-family from the type selector matching them directly, which outranks the value merely inherited from the group. The monospace face was declared, inherited in principle, and never applied. font-size on the same class did land — no competing text{font-size} rule exists — which is why the blocks looked deliberately sized but wrongly faced.

Same mechanism as #140, applied to font-family instead of fill. #167 moved colour onto the text nodes and left .mono on the group, so it did not make this redundant.

The fix shape, and why this one

One rule per asset, next to the class it repairs:

.mono text{font-family:inherit}

The ruling allowed either making the class specific enough to win or moving it onto the text nodes (#133's shape). This is the first, and it was chosen because it closes the class rather than patching the instances:

  • Any group carrying .mono now passes the face down — including ones added later. The alternative edits 46 text nodes and leaves the next .mono group to fail the same way.
  • (0,1,1) beats the type selector's (0,0,1), and inherit takes the mono stack from the group, so the face is still declared exactly once per file — no duplicated font stack to drift.
  • It touches exactly one property, so it cannot disturb the fill values Blog SVGs: give dark-card text an explicit fill so it stops rendering dark-on-dark #167 just moved, nor font-size.
  • Where .mono is already on a text node (run-record-en.svg uses both forms), the descendant selector does not match it and nothing changes.

Diff is 7 lines across 7 files. No wording, geometry, colours or font sizes changed.

Scope: 4 of the 7 files came from the sweep

The issue named three. Sweeping all 102 files under content/blog/**/*.svg for the same shape found four more — ai-wrote-your-app-dare-to-merge/cover{,-en}.svg and give-your-agent-rules-for-governable-apps/cover{,-en}.svg. None of the seven is among the five files #166 holds. Defeated declarations found in the rest of the corpus, including one inside #166's files, are recorded in #180 rather than fixed here — every one of them is a colour or size change.

Before / after: computed font-family per affected text node

Measured in Chromium at native size, all 102 blog SVGs loaded, 1,944 text nodes read.

Honoured before: 0 of 46. Honoured after: 46 of 46.

File Nodes computed BEFORE computed AFTER max bbox width growth
ai-ontology-open-protocol/one-definition-four-surfaces-en.svg 8 -apple-system ui-monospace +84.3px
ai-ontology-open-protocol/one-definition-four-surfaces.svg 8 -apple-system ui-monospace +84.3px
ai-wrote-your-app-dare-to-merge/cover-en.svg 6 -apple-system ui-monospace +57.5px
ai-wrote-your-app-dare-to-merge/cover.svg 6 -apple-system ui-monospace +47.3px
give-your-agent-rules-for-governable-apps/cover-en.svg 8 -apple-system ui-monospace +62.5px
give-your-agent-rules-for-governable-apps/cover.svg 8 -apple-system ui-monospace +32.2px
objectos-automation-engine/run-record-en.svg 2 -apple-system ui-monospace +205.9px

Monospace is wider, so containment was checked rather than assumed: each of the 46 nodes was compared against the card rect behind it. 46 fit, 0 overflow, tightest slack 78.1px (discount: Field.percent(...),). No x/y needed revisiting, so no geometry moved.

No collateral change

#167 moved every colour in this corpus, so drift here would be easy to miss. Both font-family and fill were re-measured on every node not targeted by the fix, plus font-size, font-weight, text-anchor and letter-spacing:

Population Nodes Drift
Non-target nodes in the 7 touched files 100 0
All nodes in the other 95 files 1,798 0
All six properties, all 1,944 nodes, excluding the 46 intended font-family changes 1,944 0

The assets were looked at

All 7 re-rendered at native size in Chromium and inspected, not just measured. The code blocks now read as source; in run-record-en.svg the evidence strip finally matches the mono labels beside it that always worked (sys_automation_run, status: paused), which is what made the inconsistency visible in the first place. The Simplified Chinese assets were checked specifically: Latin runs render mono, CJK falls back as it must, and every line still sits inside its card.

Gates

Run through the shared verify lock after the final commit, at 47786ad; exit codes captured before any pipe.

Gate Exit Its own verdict line
pnpm content:lint 0 ✓ content lint passed (334 files, 44 glossary terms checked)
pnpm check 0 Result (134 files): 0 errors, 0 warnings, 0 hints
pnpm build 0 [build] 866 page(s) built in 62.75s · [build] Complete!
pnpm seo:smoke 0 SEO smoke test passed (865 HTML pages checked)

os-verify-lock: VERDICT command-exit 0 · held the lock 80s · waited 73s. git status --porcelain empty after the run — the gen-zh-hant step inside build regenerated nothing.

🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

A house `text{font-family:…}` type selector matches every text node
directly, so it outranks the monospace face *inherited* from an
ancestor group carrying `class="mono"`. The face was declared,
inherited in principle, and never applied: code blocks, AGENTS.md
samples and log lines all rendered in the sans-serif body face.
(`font-size` on the same class did land, because no competing
`text{font-size}` rule exists — which is why the blocks looked
deliberately sized but wrongly faced.)

Add one rule per asset, `.mono text{font-family:inherit}`, which is
specific enough (0,1,1) to beat the type selector and re-establishes
the inheritance the author intended. It closes the class rather than
patching the instances: any group carrying `.mono` now passes the
face down, including ones added later. It touches exactly one
property, so it cannot drift the `fill` values #167 just moved, and
it does not restate the font stack — the face stays declared once
per file.

Measured in Chromium at native size across all 102 blog SVGs
(1,944 text nodes): 0 of 46 affected nodes honoured the declared
mono face before, 46 of 46 after, with 0 drift on font-family,
font-size, fill, font-weight, text-anchor and letter-spacing for
every other node. Monospace is wider, so containment was checked
too: all 46 still fit their cards, tightest slack 78.1px, so no
geometry needed revisiting.

Four of the seven files came from the sweep, not the report.

Fixes #165

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr

hotlong commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — reviewed against the branch, and looked at.

Union I at 43ed2fd (main 3d22291 + #182 + #186 + #187 + #188) is green on all five gates through the shared lock — content:lint 0, content:lint --published 0, check 0, build 0 ([build] 867 page(s) built), seo:smoke 0 (SEO smoke test passed (866 HTML pages checked)), git status --porcelain 0 lines. #186 lands on main 82edbd2 (#182 landed first; the two surfaces are disjoint).

The mechanism, confirmed independently. Every one of the seven files has at least one <g class="mono"> with <text> children:

ai-ontology-open-protocol/one-definition-four-surfaces{,-en}.svg   <g class="mono"> ×1
ai-wrote-your-app-dare-to-merge/cover{,-en}.svg                    <g class="mono"> ×1, <text class="mono"> ×1
give-your-agent-rules-for-governable-apps/cover{,-en}.svg          <g class="mono"> ×2, <text class="mono"> ×2
objectos-automation-engine/run-record-en.svg                       <g class="mono"> ×1, <text class="mono"> ×7

That mix is the proof. font-family on a <g> reaches its <text> children by inheritance, and inheritance loses to any rule that matches the element directly — so the house text{font-family:…} won, while a <text class="mono"> (0,1,0 beats 0,0,1) was fine all along. give-your-agent-rules/cover-en.svg shows both failure and non-failure in one composition, which is as clean a demonstration as this cascade offers.

Rendered before and after at native size in Chromium and compared side by side, seven files after, four before:

  • one-definition-four-surfaces-en.svg — the schema block (ObjectSchema.create({, name: 'crm_opportunity',, the nested amount: / discount: pair) was proportional; the nesting read as ragged because the leading spaces were sans-width. Now mono, and the two nested lines align.
  • run-record-en.svg — nodeId · nodeType · status · startedAt · durationMs · input · output and the second log line were sans; now mono. The stage boxes (record-after-update, approval · screen · wait, completed / failed) came along with them.
  • give-your-agent-rules/cover-en.svg — both rule lists (# AGENTS.md, - 2-space indent, - model as ObjectStack objects …) were sans while refund.read → by caller's rights two boxes down was already mono. After: uniform.
  • ai-wrote-your-app-dare-to-merge/cover-en.svg — the metadata diff (+ object: support_refund … + flow: refund > 500 → approval) was sans; now mono, and the +/- gutter lines up.

Nothing else moved: no geometry, no colour, no font size, no wording. Seven files, seven insertions, seven deletions — one appended declaration per file, on the line that already held .mono.

.mono text{font-family:inherit} is the right shape for this, not !important and not a rewrite of the type selector: it restores inheritance for exactly the elements that were meant to inherit, leaves text{fill:…} doing its job, and stays inert in the files where .mono sits on the <text> directly.

This is the fourth distinct way the house text{…} type selector has defeated an authored declaration in this corpus — after the shadowed fill attribute (#125/#133), unclassed text on a dark ground (#140), and authored colours silently replaced by the default (#156). Same selector, four different properties and four different declaration styles. Worth carrying into whatever eventually owns the shared SVG boilerplate.

Landing on main.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blog SVGs: a .mono class on a group element never reaches its text — code blocks render in the sans-serif house face

2 participants