Skip to content

feat: extract typography (font families + heading/body styles) - #17

Open
AnandSundar wants to merge 5 commits into
tight-studio:mainfrom
AnandSundar:feat/typography-extraction
Open

feat: extract typography (font families + heading/body styles)#17
AnandSundar wants to merge 5 commits into
tight-studio:mainfrom
AnandSundar:feat/typography-extraction

Conversation

@AnandSundar

@AnandSundar AnandSundar commented Jun 21, 2026

Copy link
Copy Markdown

Closes #15

Adds font detection and typography analysis to OpenBrand. The extraction now returns a non-optional typography field with font families (with source classification) and heading/body typographic styles.

What's new

extractTypography() in src/scraper.ts — collects CSS from inline <style> blocks plus allowlisted external stylesheets (Google Fonts, Typekit, same-origin), parses it with a brace-counting CSS extractor, and returns:

  • fonts: FontAsset[] with family, source (google-fonts / typekit / self-hosted / system), optional weights, optional url
  • styles: h1, h2, body with fontFamily, fontSize, fontWeight

API route/api/extract returns typography on the standard response. Legacy cache entries (pre-typography) are normalized to { fonts: [], styles: {} } on cache hits so the shape is always complete.

UI — new TypographyDisplay component renders fonts with color-coded source badges and live heading/body samples in the actual fonts. Wired into BrandResults between ColorPalette and BackdropGallery.

MCP — tool description for extract_brand_assets updated to mention typography.

Design notes

  • CSS scope (KTD3): inline <style>, Google Fonts (fonts.googleapis.com, fonts.gstatic.com, regardless of origin), Typekit (use.typekit.net, regardless of origin), and same-origin first-party stylesheets. Other third-party CSS is skipped to keep the request surface narrow.
  • Generic family filter (R14): sans-serif, serif, monospace, system-ui, cursive, fantasy, ui-*, emoji, math, fangsong, inherit, initial, unset, revert, revert-layer, none.
  • Dedup (R15): fonts are grouped case-insensitively; weights are merged across @font-face rules for the same family; url is omitted when multiple URLs exist (representative URL would be misleading).
  • Fetch safety (R9, KTD7): external CSS fetched in parallel via Promise.allSettled with a 5-second timeout and the same USER_AGENT used for page fetches. Individual failures are silently tolerated.
  • Parser (KTD3): brace-counting CSS extractor — strips /* */ comments first, walks the string tracking {/} depth. Skips @-rules (e.g. @media) at the top level to keep the rule list flat. (Wrapped @media heading overrides are not in scope for v1.)

Files changed

File Change
src/types.ts New types: FontSource, FontAsset, HeadingStyle, BodyStyle, TypographyAsset. typography made non-optional on BrandExtractionResult.
src/index.ts Forward-export extractTypography + new types.
src/scraper.ts extractTypography() + parser helpers (splitTopLevelBlocks, parseDeclarations, firstNonGenericFamily, etc.). parseHtml now returns typography.
app/api/extract/route.ts Wire typography into response; normalize legacy cache entries.
components/typography-display.tsx New TypographyDisplay component.
components/brand-results.tsx Mount TypographyDisplay between ColorPalette and BackdropGallery.
integration_test/typography-shape.test.ts New: 15 tests covering shape, generic filter, dedup, URL classification, heading/body extraction, inline override, and comment handling.
mcp/src/index.ts Tool description updated to mention typography.
SKILL.md, CHANGELOG.md Docs updated; 0.3.0 entry added.

Testing

  • 15/15 new typography tests pass
  • All pre-existing asset-shapes.test.ts tests still pass (4/4)
  • One pre-existing api-keys.test.ts failure unrelated to this change (Supabase env not configured in this sandbox)

Implements U1 and U2 from the typography extraction plan:

- Add FontSource, FontAsset, HeadingStyle, BodyStyle, TypographyAsset types (src/types.ts)
- Make typography non-optional on BrandExtractionResult
- Re-export extractTypography and new types from src/index.ts
- Implement extractTypography($, html, baseUrl) in src/scraper.ts:

  * Collects CSS from inline <style> + allowlisted external stylesheets (Google Fonts, Typekit, same-origin) via parallel fetch with 5s timeout
  * Parses with brace-counting CSS extractor (strips /* */ comments, splits top-level blocks)
  * Builds FontAsset[] from @font-face rules, classifies source by URL (google-fonts/typekit/self-hosted/system), filters generic family names, dedupes case-insensitively with weights-merge
  * Extracts h1/h2/body styles from CSS rules + inline style override
Implements U3 from the typography extraction plan:

- Add extractTypography call to parseHtml; include typography in return type
- Add typography to the explicit result construction in the API route
- Normalize legacy cache entries (pre-typography) so cache hits always return the full shape with typography: { fonts: [], styles: {} } when the field is missing
Implements U4 from the typography extraction plan:

- Create components/typography-display.tsx with:

  * Font list with family, weights, url (if single), and color-coded source badge (Google Fonts / Typekit / Self-hosted / System)
  * Heading (h1, h2) and body samples rendered in the actual font with computed font-size/weight
  * Empty case: returns null when both fonts and styles are empty (preserves the existing rendering when a site has none)
- Wire TypographyDisplay into brand-results.tsx between ColorPalette and BackdropGallery
Implements U5 from the typography extraction plan:

Create integration_test/typography-shape.test.ts with 15 tests covering:

  - Live-site shape validation against vercel.com (fonts, styles)
  - Generic CSS family names (sans-serif, serif, monospace, etc.) are filtered out
  - Case-insensitive family dedup merges Inter/inter with weight accumulation
  - URL-based source classification (google-fonts / typekit / self-hosted / system)
  - Heading and body style extraction from CSS rules + inline style override
  - @font-face rules inside CSS comments are ignored
- Fix splitTopLevelBlocks so each top-level block includes its selector (was previously only capturing { body }, missing @font-face / h1 / body)
Implements U6 from the typography extraction plan:

- Update MCP tool description for extract_brand_assets to mention typography
- Update SKILL.md frontmatter description, npm example, asset-sources table, and response-format JSON sample to include typography
- Add CHANGELOG.md entry for 0.3.0 documenting the new typography field, scope of CSS sources, generic-name filter, weights-merge dedup, and 5-second fetch timeout
@RonanCamus

Copy link
Copy Markdown

This is great

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.

Feature Request: Add Font Detection and Typography Analysis

2 participants