Files
zeekay 259ecc1411 fix(font): the console asked for Zen and was sent Geist
The kit moved to Zen — @hanzogui/font-zen names the families and @hanzo/font
ships the bytes — but only half of this console followed. src/gui.ts took the
stacks from the kit, so every rule asked for "Zen" and "Zen Mono", while
src/font.ts still fetched Geist from /fonts/geist/1.7.2 and registered those
bytes under the name the kit had just changed. A face installed under a name
the file inside it does not carry is the failure this suite exists to catch,
and font.spec.ts was left asserting Geist on both sides of it.

Delivery now reads the same package the kit points at: the self-hosted build
and the suite both take their bytes from @hanzo/font rather than from `geist`,
and the licence emitted beside them is that package's, which carries Vercel's
notice as well as ours because Zen is drawn from Geist. offline.spec.ts checks
for both holders now, since dropping the upstream line is the same defect as
dropping the licence.

The CDN does not yet publish /fonts/zen/1.9.1. Until it does, ship this with
FONT_ORIGIN= so the bundle serves the two files from the app's own origin.
2026-08-28 08:45:05 -07:00

54 lines
2.0 KiB
TypeScript

// Our CDN, answered locally.
//
// The console fetches its two faces from cdn.hanzo.ai. That is a real origin
// this machine may or may not be able to reach, and a suite whose result
// depends on which is a suite that reports on the network. So the request is
// still MADE — it still has to pass `font-src`, it is still cross-origin, and
// the tests that count cross-origin requests still see it — and it is answered
// here from the `@hanzo/font` package at the version the bundle pins.
//
// Same reasoning as the issuer in session.ts: keep the run offline without
// relaxing anything.
import { readFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
import type { Page } from '@playwright/test'
import { cdn, dir, faces } from '../src/font'
const root = dirname(dirname(createRequire(import.meta.url).resolve('@hanzo/font/css')))
/** The bytes the CDN is expected to be serving, keyed by the path they live at. */
const bytes = new Map(
faces.map((f) => [`${dir}/${f.file}`, readFileSync(join(root, 'dist', 'fonts', f.cut, f.from))]),
)
/**
* Serve the faces the way the CDN serves them.
*
* `access-control-allow-origin: *` is not decoration: a cross-origin face is
* always fetched in CORS mode, so a font host without that header serves
* nothing usable however correct its CSP entry is. cdn.hanzo.ai returns it,
* which is why it is a font host and not merely a static one.
*
* Anything else under that host is a 404, so a test can prove the console asks
* it for the two files and for nothing else.
*/
export async function serveFonts(page: Page) {
await page.route(`${cdn}/**`, (route) => {
const body = bytes.get(new URL(route.request().url()).pathname)
if (!body) return route.fulfill({ status: 404, body: 'not found' })
return route.fulfill({
status: 200,
headers: {
'content-type': 'font/woff2',
'access-control-allow-origin': '*',
'cache-control': 'public, max-age=31536000, immutable',
},
body,
})
})
}