designStyleExtractor now also extracts chip/pill/badge/tag, stat/metric cells, and
tab components — by class-substring selector plus a shape fallback (small + fully
rounded + short text) so hashed/utility class names (Tailwind, CSS-modules) are
still caught. It also emits a "transparent" sentinel for fully-transparent
(rgba(...,0)) grounds instead of collapsing them to #000000, so a transparent
chip/tab/stat on a light-ground site no longer reads as solid black.
fontMetadataExtractor now flags icon fonts (isIcon) by glyph coverage: a font is an
icon font only when it BOTH lacks a real Latin alphabet (<26 of A-Za-z) AND is
mostly (>50%) Private-Use-Area glyphs. The Latin gate matters — some text fonts pack
thousands of PUA glyphs yet are plainly text (Apple SF Pro is ~81% PUA but ships a
full alphabet; Descript's Booton ~50%); flagging by PUA ratio alone would strip a
brand's real typeface. Measured icon fonts: "hushly" 63% PUA / 7 letters, Font
Awesome 95% / 0 letters. Names alone can't identify icon fonts ("hushly",
"swiper-icons"), hence the glyph-based test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Five fixes from Copilot's inline review + Miguel's note on PR #987:
1. inferWeightFromSubfamily — only matched concatenated forms
("extralight", "semibold"). Spaced ("Extra Light") and
hyphenated ("Extra-Light") variants fell through to the 400
default, misreporting 200-weight fonts as 400. Now normalizes
`[\s-]+` out of the subfamily before matching.
2. meta.tool — was hardcoded to "fontkit@2.0.4" but
`packages/cli/package.json` allows ^2.0.4, so the manifest
string would drift on every dep bump. Now records just
"fontkit"; the version moves with the dep and can be discovered
from package.json at debug-time if needed.
3. FontFileMetadata.rawFamily — docstring said "nameID 16 preferred,
then nameID 1" but the code also derives from PostScript via
deriveFamilyFromPostscript when both name-table fields are
missing. Doc now reflects the actual three-step precedence.
4. FontFileMetadata.weight — docstring said "100-900" but the code
emits 0 (when identified: false) and 950 (when
canonicalizeFamily picks ExtraBlack/UltraBlack). Doc now
documents both edge values explicitly.
5. sharp ^0.34.5 — bumped from ^0.34.0 on this PR but font
extraction doesn't use sharp; the bump is needed by the contact
sheet code in PR #988. Reverted on #987; will re-bump on #988
where it's actually consumed.
Also adds vitest coverage:
- 34 tests in fontMetadataExtractor.test.ts
- Covers inferWeightFromSubfamily for concatenated, spaced, and
hyphenated forms (including composite styles like "Bold Italic"
and case-insensitivity)
- Covers canonicalizeFamily for unchanged families, stripped
weight tokens, preserved width modifiers, and the 950 emit
- Integration tests for extractFontMetadata (non-existent dir,
empty dir) verifying the meta.tool / generatedAt shape
Exported `inferWeightFromSubfamily` and `canonicalizeFamily` for
testing. Pure functions, internal helpers, but exporting is the
clean way to pin their behavior against regressions.