Repository navigation
Fonts wiring (TXT1-C Phase B): @font-face, font map, font manifest in the digest, web @font-face and assets - #29
Conversation
…c (quoted "sans-serif") names a family, never the pinned generic, as Chrome's stated reference P2 shows. The pinned capture's map gains Lato (vendor/fonts/Lato 400 and 700, T036) and four requests (Lato lists, a quoted generic); wire.test.ts compares Dragon's rewrite with every P2 visit, and its unmapped-family examples move off Lato, which is now mapped
…nditions=dragon-internal scripts/capture-font-data.ts; --check byte-identical, the other four captures unchanged)
…l and collected (at-rules.ts, stylesheet.ts); the fonts configuration key with DRAGON_FONT_MAP_INVALID; projectFonts and the font manifest in the digest only when the project has fonts; font-family keyed by resolution kind (featureOf and usedKeys take the font context; Ahem keeps font-family:Ahem); unmapped families refused for every target as DRAGON_FONT_UNMAPPED_FAMILY (not also UNSUPPORTED_VALUE); @font-face and pinned-face issues with their own codes; the variable-font fence at style resolution; the web output rewritten through the map with its @font-face prelude and GeneratedAsset font files; four planted faults; fonts-wiring.test.ts. Retargeted: android-target's font test maps serif to the platform (serif alone is now unmapped); ua.test's web-emitter import pin gains the fonts rewrite imports (still no lowering, layout or matching)
… Regular and Bold and pins sans-serif to Dragon Sans (T036 constraint); the font files enter the snapshot as assets, and the report records the map and asset hashes
…un north-star:check; twice, second run unchanged; per-target error totals unchanged: web 185, ios 219, android 690)
…families, the recommended Dragon Sans and Dragon Mono, the web @font-face prelude and assets, the digest, the resolution-kind support keys, the codes); createProject's fonts key and the ready output's assets; §7.1 the pinned-generic stated reference
…ed families after substitution; the variable-font fence runs on the faces Chrome selects for the text's UA weight and style (userAgentTextFonts: h1 to h6 bold, address italic), so UA-bold text on a variable face is fenced at its wght 700 instance; validateFontMap refuses a pinned entry with keys beyond mode, family and faces. Tests in fonts-wiring.test.ts
…rows from the profile it enforces, so it holds whatever rows the committed web profile has
…keeps the refusal (a style rule: Chrome ignores it; a conditional rule: refused with it)
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR adds a substantial cross-cutting font capability, including new public configuration and artifact fields, @font-face parsing, font asset emission, digest changes, and altered handling of existing font-family declarations. The scope and unresolved semantic concern warrant human review. Not approved because:
No code changes detected at Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
ebfcd2f to
05d94e1
Compare
… with the project's font context checkSubstitution called featureOf without the FamilyKeyContext, so a substituted font-family was keyed font-family:<family-list> and an enforced profile refused a pinned sans-serif. checkComputed now takes the same context usedKeys gets. Test in fonts-wiring.test.ts (fails before: DRAGON_UNSUPPORTED_VALUE).
…raws the text with checkCaseFonts fenced the best face group of every declared or pinned family in the list, so a later variable family refused text that an earlier static family draws in full. renderedFaces takes the primary font (the first bundled family's first face whose unicode-range holds U+0020) and, per code point, the first face down the list whose unicode-range and cmap hold it; entries without bundled faces are passed over, so a later family still counts for any character an earlier one might lack. Tabs and segment breaks count as U+0020 (css-text-3 §4), so a face that maps a control character is not counted for it. Tests in fonts-wiring.test.ts (both fail before).
…*/ one included, leaves the prelude empty css-tree 3.2.1 skips every comment before an at-rule prelude (skipSC), so the prelude is null and acceptFontFace accepts the rule; the reported refusal does not reproduce. This test keeps that true if the parser changes, and pins that a real prelude with a trailing /*! */ comment is still refused.
…ubtree, as checkFonts does Chrome never draws that text, so it selects no face for it. The var() font-family check stays per declaration, like checkFamilies, and still runs there. Audit of the other per-case font and text walks: checkFonts and checkComputed's bidi, position and UA-font checks already skip hidden subtrees; checkSubstitution is per declaration like checkValues; usedKeys keys hidden content through its display-none contexts by design. Test in fonts-wiring.test.ts (fails before).
Conflicts: stylesheet.ts keeps master's preprocessed input, U+0000 report and escape canonicalization with the branch's fontFaces collector; values.ts familyValue keeps master's serializeString and decodeName, and tests the lone generic keyword on its decoded, ASCII-folded name (asciiLower).
…-folded, as the stylesheet parser reads property names parseFontFace compared the descriptor name as written, so f\6fnt-family was an unknown descriptor (Chrome reads it as font-family, css-syntax-3 §4.3.7). It now decodes the name (css/escapes.ts decodeName); fonts/css-tokens.ts re-exports css/escapes.ts asciiLower, so the fonts module and the stylesheet parser fold names with one definition (A-Z only). Tests in fonts-wiring.test.ts: escaped and upper-case at-rule, descriptor, format() and keyword names (the descriptor case fails before), and escaped or upper-case generic and declared family names keep their font-family keys.
| if (v.kind === 'family') return serializeString(v.value); | ||
| return v.kind === 'other' && v.type === 'family-list' ? v.text : null; |
There was a problem hiding this comment.
🟡 Medium css/values.ts:107
Quoted generic families such as font-family: "ui-serif" are pinned or replaced as the mapped generic ui-serif, changing both the selected font and emitted CSS. familyListText sends the single-family value through familySupport without preserving that it was quoted; exclude generic names from resolution when the value is a family, while continuing to resolve bare generic lists.
- if (v.kind === 'family') return serializeString(v.value);
+ if (v.kind === 'family') {
+ const name = asciiLower(v.value);
+ return (GENERIC_FAMILY_KEYWORDS as readonly string[]).includes(name) ? null : serializeString(v.value);
+ }🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/dragon/src/css/values.ts around lines 107-108:
Quoted generic families such as `font-family: "ui-serif"` are pinned or replaced as the mapped generic `ui-serif`, changing both the selected font and emitted CSS. `familyListText` sends the single-family value through `familySupport` without preserving that it was quoted; exclude generic names from resolution when the value is a `family`, while continuing to resolve bare generic lists.
There was a problem hiding this comment.
Chrome 145 treats "ui-serif" and ui-serif as the same family: ui-*, emoji and fangsong are family names to its parser whether quoted or not, and both serialize as ui-serif (new P2 case quoted-ui-serif). So resolving a quoted "ui-serif" like the bare one matches Chrome, and returning null would not. The quoting distinction applies only to the parser generics (sans-serif, serif, monospace, system-ui, -webkit-body, …), where a quoted name was already treated as a family. Checking that class turned up a real hole, fixed in db4673b: a family named like a generic in another case ("SANS-SERIF", "-webkit-body") was written bare in the web output, and bare it re-parses as the generic. Dragon now quotes those names. The Chrome font reference had the same hole and is fixed too, with Chrome-backed P2 cases for quoted, escaped and case-variant generics (a33056f).
Generated by Claude Code
…itten quoted, so it never becomes the generic Chrome 145 (probed): "SANS-SERIF" is a family, and Chrome's own serialization writes it bare, which re-parses as the generic sans-serif; "-webkit-body" likewise. rewriteFamilyList and the web emitter's family name now quote a name that ASCII case-folds to a parser generic (font-map.ts outputFamilyName; web-css.ts uses it in place of its own list). Quoted "ui-serif" and bare ui-serif stay the same family, as in Chrome, where both serialize ui-serif. The stated reference (capture-font-reference.ts) had the same hole when it wrote a rewritten list back: it now quotes such a bare name. P2 gains quoted-monospace, escaped-generic, quoted-escaped-generic, quoted-upper-then-mono (fails the reference without the fix: Chrome drew Inter, not Noto Sans Mono) and quoted-ui-serif; a case whose Chrome serialization is lossy carries its authored list. Tests: fonts-wiring.test.ts (quoted names, escaped generics, var() and the no-fonts emitter; fails before), wire.test.ts P2 compares Dragon's rewrite of the authored list where Chrome's text is lossy (Chrome's serialization cannot be the input there), and ua.test.ts's web emitter import pin gains outputFamilyName.
node --conditions=dragon-internal scripts/capture-font-reference.ts, then --check (byte-identical).
Conflict in project.ts: master's profileText (profile snapshots only) is kept beside the branch's analyze, which takes the fonts configuration key; the digest, moved after parsing on this branch, hashes profileText(profileFor(...)) of the project's snapshot. Every profile read the branch adds (checkValues, the contextual check, checkComputed's substitution check, resolved support queries) reads options.supportProfiles or record.profiles, the snapshot.
…ce, as DRAGON_FONT_UNMAPPED_FAMILY Keying the substituted font-family with the font context (67c6a3f) made checkSubstitution also refuse it as DRAGON_UNSUPPORTED_VALUE on font-family:<unmapped>, beside checkCaseFonts' DRAGON_FONT_UNMAPPED_FAMILY; the Tailwind sweep (#35) caught it on font-sans, font-serif and font-mono. It now skips that key, as checkValues leaves a direct unmapped family to checkFamilies. Test in fonts-wiring.test.ts (fails before).
pnpm run tw:sweep, twice (the second run changed nothing). font-sans, font-serif and font-mono now refuse with DRAGON_FONT_UNMAPPED_FAMILY on every target (the sweep has no font map), in place of DRAGON_UNSUPPORTED_VALUE on the legacy font-family:<family-list> key; nothing else changed.
…e scratch pass checks font families - An @font-face that declares Ahem (in any case: FontFaceCache folds it) names the authored face on web, while ios and android draw the bundled Ahem. Text in font-family Ahem is then DRAGON_UNSUPPORTED_FONT on each configured native target, and web keys it font-family:<declared>, not the Ahem rows proven with the bundled face. - A font map entry keyed Ahem, or pinning a family named Ahem, is DRAGON_FONT_MAP_INVALID. Native lowering treats no other family specially: every other family is already refused on native. - The scratch pass over the rules inside an unsupported at-rule now runs checkFamilies too, so an unmapped family there is a related entry of the at-rule error. It already ran every other per-declaration check of the main pass (checkValues, and checkCases: computed, native font, case font and contextual checks). Tests in fonts-wiring.test.ts on web, ios and android (each fails before).
#37, showcase group) into MQ-a: pnpm run parity:capture and pnpm run profile:rows to the fixed point (a third run changes nothing); pnpm run parity:dpr-capture; pnpm run layout:vectors; pnpm run layout:dpr-vectors; pnpm run layout:break-vectors; pnpm run parity:break-capture; pnpm run parity:pixel-capture; pnpm run native:gen; pnpm run north-star:check; pnpm run tw:sweep. Generated conflicts were taken from origin/master and regenerated, never hand-merged.
Part-1 code takes master's reviewed version. Conflicts: - packages/parity/emitted/**, profiles/*.ts, north-star-check.json: master's files; regenerated in the next commits. - scripts/capture-font-reference.ts: keeps the import of the shared packages/parity/src/font-reference.ts; master's change to the in-page rewrite (quote a bare family that ASCII case-folds to a parser generic, PARSER_GENERICS) is ported into font-reference.ts applyFontReference, so the script and the fonts fixtures run the same rewrite. - packages/parity/src/fixtures.ts: master's showcase group, then fonts.
…puts
- font-reference.ts pinnedGenerics: a named family entry may pin under its own name as Chrome's FontFaceCache folds it
(foldFamily, fonts/selection.ts), not only byte-equal; any other family name still throws.
- pipeline.ts inlineFontAssets (webCssOf): every url("fonts/...") of dragon.css must name a web output asset and every
asset must be named, else it throws; before, a url of another shape was left as written and an unnamed asset passed.
- fonts-run.ts readFontCapture (committedFontAuthored): a committed expected-fonts capture is checked before use (its case,
direction, viewport, Chrome version, well-formed nodes, and the exact bytes parity:capture writes); before, the JSON was cast.
Tests (fonts-fixtures.test.ts): pinnedGenerics folding and rename refusal; inlineFontAssets both failure directions;
readFontCapture on a wrong case, non-canonical text, a malformed node, no nodes and null.
…capture, profile:rows, parity:capture, parity:dpr-capture, layout:vectors, layout:dpr-vectors, native:gen, north-star:check, tw:sweep, wpt:run -- --target web, wpt:update-expectations -- --target web Changed: - packages/dragon/src/profiles/web.ts: six web rows, font-family:<pinned|declared|platform> at text-in-block ltr and rtl (<platform> caveat); no ios or android row changes. - packages/parity/emitted/**, packages/parity/expected-fonts/emitted/**: header digests only (the digest covers the web profile). - examples/music-player/dragon/north-star-check.json: the Lato declaration's two web DRAGON_UNSUPPORTED_VALUE rows become two DRAGON_UNPROVEN_CONTEXT rows (text-in-flex-item row and column); per-target error totals unchanged. No change from grammar:gen, the captures (expected, expected-dpr, expected-fonts captures), the vectors, native:gen, tw:sweep or the WPT web expectations.
Conflicts: faults.ts keeps both sets of appended plants (the four fonts plants, then blockifySkipped and inlineFlexToBlock); the Tailwind snapshot takes master's side here and is regenerated in the next commit.
…it, #37) into wm0-horizontal-tb Only packages/parity/out/lanes.json conflicted; it takes master's side here and is rebuilt by the regeneration and device run that follow.
…o pr/v1a-engine-values; lanes.json and corpus-dpr.json taken from master and regenerated after
… parity:lanes -- --run-host --run-device) after merging origin/master (#29, #37, #40): 388 cases; frames, applied, lines and layout-vectors pass on ios and android; device-pixels ios 135, android 182, equal to origin/master's; out/device-failures-{ios,android}.json byte-identical to master's; parity:glyph-b3 passes, fringe 0
…ring, since f799419) into size-ar Source conflicts resolved by hand: computed-checks.ts imports exactLayoutRatio beside master's FamilyKeyContext; values.ts keeps ratioValue and exactLayoutRatio before master's font-context featureOf; faults.ts keeps v1b's two calc faults and master's four font faults. The Tailwind sweep snapshot takes master's side and is regenerated by tw:sweep in the next commit.
…) into pr/v1b-compiler-values. faults.ts keeps both sides' faults (fonts and blockification, then V1's sumOrderSwapped and dropExplicitZeroPercent); fixtures.ts keeps master's order with values last (values.test.ts); the glyph-clearance pins in pixel-reference.test.ts are master's plus the values-* cases' own increments (edge +12/+12 at DPR 2, +10/+10 at 3, +11/+9 at 2.625, as in 2c7e72a). Generated conflicts (profiles, emitted CSS, pixel manifest, bottom pins, lanes.json, corpus-dpr.json, the Tailwind snapshot, north-star-check.json) taken from master and regenerated after
…) (regen sequence: grammar:gen, parity:capture, profile:rows, parity:capture, layout:vectors, parity:dpr-capture, layout:dpr-vectors, layout:break-vectors, parity:break-capture, parity:pixel-capture, native:gen, parity:lanes -- --run-host, node --conditions=dragon-internal examples/music-player/tools/check.ts (pnpm run north-star:check fails on master since #39: fixture-reader imports collectFontFaces from the internal entry), wpt:run --target web, wpt:update-expectations --target web, parity:glyph-b3 -- --write-bottom-pins): profiles regain the values rows, emitted CSS digests, pixel manifest and bottom pins gain the values cases, corpus-dpr.json digests; north star supported declarations 159 -> 165 on web and ios (54.6% -> 56.7%)
What changed
@font-face: accepted at the top level of a stylesheet and collected. The configuration takes an optionalfontsmap: Dragon Sans forsans-serif, Dragon Mono formonospace, orplatform.font-family:font-family:Ahem.DRAGON_FONT_UNMAPPED_FAMILYon every target, including aftervar()substitution."sans-serif"names a family and is never pinned, matching Chrome (thefont-map.tsfix).local(), unresolved asset, unreadable file, unsupported descriptor, variable font refused) and one warning (descriptor not applied).dragon.cssstarts with the@font-facerules;assets;pinned.json(recaptured) and in the north-star check;Native targets still refuse any font but Ahem. Real fonts on device are TXT1a-1/2, which follow inline formatting.
What passed
pnpm typecheck.--check: byte-identical.north-star:check, run twice: totals unchanged.Changed tests
android-target:serifis mapped toplatform, becauseserifalone is now an unmapped family.ua: the import pin.seams: font-face is accepted; the byte pin is intact.wire: the unmapped examples no longer use Lato, which is now mapped.🤖 Generated with Claude Code
Note
Add font wiring to Dragon compiler:
@font-face, font map, digest manifest, and web assetsfontsFontMap,@font-facerules are accepted at the top level of stylesheets, and their sources are resolved from snapshot assets during compilation.DRAGON_FONT_UNMAPPED_FAMILYdiagnostic, and per-case checks cover variable-font refusals, Ahem conflicts, and invalid family lists.@font-facerules, and returns bundled font assets on the readyArtifactState. The font manifest contributes to the digest.@font-faceis the only at-rule accepted at the top level (others are still refused); genericDRAGON_UNSUPPORTED_VALUEis no longer emitted for unmapped font-family values, which now reportDRAGON_FONT_UNMAPPED_FAMILYinstead; ready artifacts gain a requiredassetscollection; a lone unquoted generic family likesans-serifis now parsed as a family-list value rather than a family value.Macroscope summarized 397f7d6.