Skip to content

feat(core): the renderer's font pipeline is importable from @hyperframes/core/fonts/embed - #5041

Merged
miguel-heygen merged 6 commits into
mainfrom
feat/core-fonts-embed
Oct 5, 2026
Merged

miguel-heygen merged 6 commits into
mainfrom
feat/core-fonts-embed

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

What changes for a person

A preview that wants to show exactly the fonts a render will use can now call the renderer's own font pipeline, from @hyperframes/core, without installing the producer:

import {
  normalizeSystemFontPrimaryFamilies,
  injectDeterministicFontFaces,
} from "@hyperframes/core/fonts/embed";

const html = await injectDeterministicFontFaces(normalizeSystemFontPrimaryFamilies(source));

That is the same pair, in the same order, the producer runs when it compiles a render (htmlCompiler.ts, compileForRender). Until now it lived only in @hyperframes/producer, so the HyperFrames desktop app's preview had to install the whole renderer to match it.

Install footprint

Clean installs on Linux from the release tarball (pnpm pack), du -sm node_modules:

Package Size package.json files
@hyperframes/[email protected] (today's only home of the pipeline) 203 MB 122
@hyperframes/[email protected] (published) 74 MB 78
@hyperframes/core with this PR 76 MB 80

The desktop app already depends on @hyperframes/core, so the pipeline costs it about 2 MB (wawoff2 and its one dependency, plus the embedded font data) instead of 203 MB.

The move

One owner, no copy left behind:

  • packages/producer/src/services/{deterministicFonts,fontCompression,authoredGoogleFonts,fontData.generated}.ts and their 15 test files move to packages/core/src/fonts/ (git renames).
  • The font-data generator and its build step move to packages/core/scripts/; core's build runs build:fonts the way the producer's did. wawoff2 and the @fontsource/* packages move with them. The @fontsource/* packages are only read by the generator, so they become core devDependencies; 11 of them were runtime dependencies of the producer.
  • Two node-only subpaths, declared in package-subpaths.json with environments: ["bun", "node"]: @hyperframes/core/fonts/embed (the pipeline) and @hyperframes/core/fonts/embedded-data (the font data the distributed planner hashes). Core's root entry does not pull in either; the pipeline reads the disk cache and uses node:fs, node:os and node:crypto.
  • The producer imports both from core and keeps re-exporting the same names from @hyperframes/producer, so its public API is unchanged. Its own bundle still contains the pipeline.
  • The CLI's Studio server and the Studio dev server load the pipeline from core's source instead of the producer's.

Behaviour

Unchanged. The one new option is logger on injectDeterministicFontFaces: core has no logger of its own, so the messages the pipeline used to send to the producer's logger go to the caller's logger when given, and otherwise to stderr in the same [WARN] / [INFO] format the producer's console logger writes (never stdout). The producer passes its own logger, so its output is the same as before.

Tests

  • The 15 moved test files ran under bun test in the producer; core runs vitest. They now import from vitest and run in a Node environment (@vitest-environment node; core's default is jsdom). Two bun-only matchers became vitest ones (toBeString, toStartWith). The fail-closed test now captures the default logger's console.warn.
  • "shares one elapsed-time budget across multiple font resources" failed once in three full runs under vitest's parallel workers: it timed a 100 ms budget with real clocks, and a millisecond of drift between the abort timer and Date.now() let a second attempt start. It now runs with Date and setTimeout faked, so the budget is read from the test's clock. 16 files, 216 tests pass in 5 of 5 full runs on this code.
  • Producer unit lane (bun and vitest): pass. Core, producer, CLI and Studio typechecks: clean. verify:packed-manifests after a full workspace build: 157 packed exports resolved, clean consumer install and Node execution pass.
  • A clean install of the packed core tarball imports @hyperframes/core/fonts/embed, runs both functions on a page that asks for Inter, and embeds Inter's face; the logger option receives the pipeline's message.

.fallowrc.jsonc: the moved files bring their existing duplication and one inherited complexity finding with them, which fallow reads as new because the paths changed. They are listed under duplicates.ignore and health.ignore with a "moved from" note, as the earlier parsers move did; parseFontFamilyValue keeps its complexity with an inline ignore.

No visible change

The only Studio change is two lines in packages/studio/vite.adapter.ts: the dev server loads the same font function from its new home in core. Preview and render output are unchanged.

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 2040 (base branch 2040), smooth 1542 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

@miguel-heygen
miguel-heygen marked this pull request as ready for review October 5, 2026 04:24

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at aa731214.

The move keeps behavior the same

  • fontData.generated.ts is the same blob as before the move (d9a3789). authoredGoogleFonts.ts is unchanged, and fontCompression.ts changes only a comment.
  • deterministicFonts.ts changes four things:
    • relative imports in place of the two @hyperframes/core/... ones;
    • a fontToDataUri re-export for htmlCompiler;
    • a fallow-ignore comment;
    • defaultLogger replaced by options.log. That is the caller's logger, or a stderr default with the same [WARN]/[INFO] format. The producer passes its own defaultLogger, so its output is unchanged.
  • The export set only grows (FontLogger, fontToDataUri). producer/src/index.ts keeps its export list identical, and producer's exports field is unchanged. The @fontsource/* dependencies it drops were never imported at runtime.

Packed core: I packed core and its workspace deps and installed them clean in a temp dir: 76 MB, matching the body. Both @hyperframes/core/fonts/embed functions import and run on an Inter page, and the output carries the marker, an Inter @font-face, and the "Injected…" message routed to logger. fonts/embedded-data loads 42 entries. Core's root entry doesn't expose the pipeline, and nothing in core's browser entry, studio/src, player or sdk imports the new subpaths. check:package-subpaths passes.

Images and stale paths: Dockerfile.test and the Cloud Run Dockerfile build core, which runs build:fonts (a skip, since the generated file is committed), before producer. So dropping producer's generator step is safe. No code still points at the old producer font paths.

Tests:

  • Core src/fonts: 217 pass (16 files).
  • Producer bun unit lane: 644/644.
  • Producer vitest: 861/862. The one failure is the audioPadTrim loudness integration test locally, unrelated to fonts and green in CI.
  • Typechecks are clean in core, producer, cli and studio.
  • It merges into current main with no conflicts. CI: 89 pass, 5 skipped.

Reuse / simplicity: these are pure renames with no copy left behind in producer, and core had no logger type to reuse. One option: fonts/embedded-data exists so the planner can hash the font map, and exporting a fingerprint from fonts/embed would avoid a second public subpath. Not blocking.

Nits (non-blocking)

  • deterministicFonts.ts:1649-1651: the Options for {@link injectDeterministicFontFaces} doc comment now sits above FontLogger, and the new logger? field has no doc.
  • deterministicFonts.ts:1110: the unwritable-cache-dir warning still goes straight to stderr rather than the caller's logger.
  • fontCompression.test.ts reads its TTF from a docs catalog item, so renaming that item would break core's tests. A copy under core's own fixtures would decouple them.
  • .github/workflows/windows-render.yml:195,466 comments still name packages/producer/node_modules/@fontsource/*.

— Rames

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit b3d2b82 Oct 5, 2026
94 checks passed
@miguel-heygen
miguel-heygen deleted the feat/core-fonts-embed branch October 5, 2026 05:12
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.

2 participants