ui: the console is set in a typeface a browser has

It shipped in Times New Roman. Two independent causes, both silent, both found
by looking at the running console rather than by any check it passes:

The kit's default config names its families `System` and `Heading`. Those are
React Native names — that platform resolves them and this one does not — and the
kit writes the name straight into `--f-family`, which every component reads. A
family no browser knows is not an error; it takes the default, which is a serif.
So every control, heading and cell the kit owns was serif. The families are now
the web's own stack, given at createGui, so the kit emits it and nothing has to
be overridden downstream.

`--hz-font-sans` was declared in the theme bridge, which is on `.shell` and
`.gate`. `body` is outside both, so `font-family: var(--hz-font-sans)` there
substituted nothing, the declaration was invalid, and the document root fell
back to the same serif. A typeface answers to no theme — one stack serves dark
and light — so it belongs with the type scale at :root, which is where it is.

The invariant is in the kit suite, and it is mutation-proven: put either cause
back and it is the test that fails. It names the two stacks the console declares
and refuses every other resolved family, so the next family that means nothing
here fails the same way rather than needing its own case. 13 green.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-01 15:06:10 -07:00
co-authored by Hanzo Dev
parent e835a29007
commit 9f1ea316b9
3 changed files with 62 additions and 5 deletions
+27
View File
@@ -21,6 +21,10 @@
// - A custom property is substituted where it is DECLARED. The theme bridge
// sat at `:root`, which the kit's `:root .t_dark` can never reach, so a
// control took a white ground and white text and disappeared into itself.
// - The kit's default families are `System` and `Heading`, which are React
// Native names no browser resolves, and the sans stack was declared in
// the theme bridge that `body` is outside of. Both fall back to the
// default serif, so the console shipped set in Times New Roman.
//
// Each is silent, each is a lie about what the screen says, and each has an
// assertion below. They are written as invariants rather than as three
@@ -140,6 +144,29 @@ test.describe('the kit reaches the screens', () => {
).toBe(true)
})
test("the type is the console's typeface, not the browser's default", async ({ page }) => {
await signIn(page)
await page.goto('/cases')
await expect(page.locator('nav.rail')).toBeVisible()
// A family the browser cannot resolve is not an error: it silently takes
// the default, which is a serif, and the screen still renders. So the
// assertion names the two stacks this console declares and refuses
// everything else — a kit family that means nothing here (`System`), and a
// variable substituted in a scope that does not hold it, both land outside
// them. It reads the resolved family off elements the kit owns and elements
// it does not, because the two are dressed on opposite sides of the seam.
const families = await page
.locator('body, nav.rail, h1, h2, button, input, select, td, .grid .id, code')
.evaluateAll((els) =>
[...new Set(els.map((el) => getComputedStyle(el).fontFamily))].sort(),
)
expect(families.length, 'nothing was measured').toBeGreaterThan(0)
const stray = families.filter((f) => !/^ui-sans-serif,|^ui-monospace,/.test(f))
expect(stray, `type is set in ${stray.join(' / ')}`).toEqual([])
})
test('nothing is invisible against itself', async ({ page }) => {
await signIn(page)
await page.goto('/relationships')
+9 -4
View File
@@ -22,9 +22,16 @@
* attribute rather than a style.
*/
/* Values that are the console's own and answer to no theme: the four state
steps, the type scale, and the two fixed dimensions of the frame. */
/* Values that are the console's own and answer to no theme: the typefaces, the
four state steps, the type scale, and the two fixed dimensions of the frame.
A typeface is not a theme — the same two stacks serve dark and light — so
they are declared here, where every element can reach them. They were in the
theme bridge below, which is on .shell and .gate; body is outside both, so
`font-family: var(--hz-font-sans)` there substituted nothing, the declaration
was invalid, and the document fell back to the browser's default serif. */
:root {
--hz-font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
--hz-font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
--hz-text-xs: 11px;
--hz-text-sm: 13px;
--hz-text-lg: 16px;
@@ -77,8 +84,6 @@
--hz-sidebar: var(--color1, oklch(0.175 0 0));
--hz-sidebar-accent: var(--color3, oklch(0.24 0 0));
--hz-radius: var(--radius-4, 8px);
--hz-font-sans: var(--font-body, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
--hz-font-mono: var(--font-mono, ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace);
--ink: var(--hz-foreground);
--ink-2: var(--hz-muted-foreground);
+26 -1
View File
@@ -27,7 +27,32 @@
import { getDefaultGuiConfig } from '@hanzogui/config-default'
import { createGui } from '@hanzogui/core'
export const config = createGui(getDefaultGuiConfig('web'))
/**
* The typeface, spelled the way the web spells it.
*
* The kit's default config names its families `System` and `Heading`. Those are
* React Native family names: on that platform the OS resolves them, and on this
* one nothing does. The kit emits the name verbatim as `--f-family`, every
* component reads `font-family: var(--f-family)`, and a family no browser knows
* falls back to the default serif — so the whole console rendered in Times New
* Roman while the build, the typecheck, the CSP run and the render tests all
* passed. One stack, given to both roles, is what `System` was asking for.
*
* It is the same list as `--hz-font-sans` in app.css, which dresses the plain
* elements the kit does not own; the two are the console's one typeface stated
* on each side of the seam.
*/
const sans = 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
const base = getDefaultGuiConfig('web')
export const config = createGui({
...base,
fonts: {
body: { ...base.fonts.body, family: sans },
heading: { ...base.fonts.heading, family: sans },
},
})
export default config
/**