Skip to content

feat(webui): ship the wordmark's own font with the client - #29

Merged
modacker merged 3 commits into
mainfrom
feat/webui-brand-wordmark-font
Nov 11, 2026
Merged

modacker merged 3 commits into
mainfrom
feat/webui-brand-wordmark-font

Conversation

@modacker

@modacker modacker commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

What

Ship the wordmark's own font with the client, instead of naming a family that only exists on some machines.

The mark used to render in "Century Gothic", "Arial Rounded MT Bold", "Segoe UI", sans-serif. "Century Gothic" is a Microsoft font, so a Mac without Office fell through to Arial Rounded MT Bold, a Linux box fell through to whatever its fontconfig had, and a Windows box got Century Gothic. The same mark came out in a different face on every platform. That is what issue #27 reported: the recorded baseline for one platform stopped matching the other two, with no WebUI source involved.

The mark now uses Nunito, carried by the client.

Why this face

  • It is a rounded sans, so the mark keeps the round terminals it had on macOS. The face it replaces could not be carried: Arial Rounded MT Bold and Century Gothic are Monotype fonts the client may not redistribute, and this repository publishes the client and its npm package.
  • It is licensed under the SIL Open Font License 1.1. The licence text ships beside the file.
  • It is carried as one variable file (weight axis 200–1000), so the two weights the mark uses — 700 for the wordmark, 600 for its lower line — cost one 39 KB file rather than two.

What changed

  • packages/webui/src/client/assets/fonts/brand/Nunito-latin.woff2, with OFL.txt beside it.
  • packages/webui/src/client/styles/brand-font.css — two @font-face rules (600 and 700) over the same variable file. The family is named "MiniMax Wordmark" rather than "Nunito" on purpose: a name no host happens to have installed is a name only this file can answer, so a machine that already has Nunito cannot answer first with a different version of it. font-display: block keeps the mark from being painted in a fallback face first — the mark is the one thing on that page that must not flicker between two shapes.
  • packages/webui/src/client/styles/index.css — one import, after tokens.css.
  • packages/webui/src/client/features/workspace/ui/workspace.css — the wordmark's stack now reads "MiniMax Wordmark", "Century Gothic", "Arial Rounded MT Bold", "Segoe UI", sans-serif. The faces that came before stay behind it: if the font file does not make it into an artifact, the mark is still drawn, just not in its own face.
  • packages/webui/test/unit/webui-w0-css-structure.test.ts — two guards. The @font-face URLs must live under /assets/fonts/brand/ and the files must exist (nothing else in the gate checks that a URL in a stylesheet resolves, and a missing font is a blank mark rather than a failure). The wordmark's stack must name the bundled face first and keep the old faces behind it.
  • packages/webui/test/gates/fixtures/path-metadata-f00.json — the new stylesheet's path entry, classified by the frozen baseline classifier (scripts/lib/webui-dependency-rules.mjs at f00b1268c28ad15054a8882620c3fb08beb86d58, its sha256 verified against the fixture's sourceSha256) and recorded in declaredDelta.
  • scripts/build-webui.mjs, scripts/package-webui-npm.mjs — the brand font directory travels with the built client and with the npm package. These two files sit outside packages/webui; this copy rule is the one thing about the font that has to live at the repository root.
  • release/public-source.json — regenerated.
  • packages/webui/test/browser/harness.mjs — the pixel harness now waits for the bundled face before it measures the client, and asserts that the face came back loaded. font-display: block means the mark is not painted at all until the face is ready, so a screenshot taken inside that window records a blank mark; without the wait the gate could record a blank mark and then hold it up as the truth. The wait is an assertion, not a hope: a build where the font file did not make it into the artifact fails the suite instead.
  • packages/webui/test/browser/responsive-baselines.spec.mjs-snapshots/ and packages/webui/test/browser/visual-baselines.spec.mjs-snapshots/ — the twelve recorded baselines re-recorded on the CI runners (see below).

Baseline impact

Four recorded groups change, and only inside the wordmark: workspace-panel-at-1440, workspace-panel-at-768, workspace-panel-at-1024 and embedded-browser-panel. The mark is drawn only on the launcher tab, so every other recorded view is unaffected.

Measured on one machine by rendering the tree before and after this change and comparing the frames byte for byte: of the 22 rendered baseline groups, 18 are byte-identical, and the four above differ only inside the wordmark's own row — 7,039 / 3,667 / 1,823 / 7,039 pixels, at most 1.31% of a frame.

The recorded baselines therefore have to be re-recorded on each platform (4 groups × 3 platforms = 12 images). That has to happen on the CI runners: a frame recorded on one machine does not match another, because the three platforms rasterize the same font file their own way, and those differences are orders of magnitude above the 8-pixel budget this gate allows.

Re-recorded. The twelve images are the ones CI itself produced for run 42946296900, taken from that run's artifacts and written back per platform. A frame recorded on any other machine is that machine's rasterizer's idea of the same picture, and the runner that compares it would fail it.

What the re-recorded frames show:

  • The change is confined to the mark. Against the twelve previous baselines, the new images differ by 1,617 to 9,239 pixels. Every differing pixel lies in rows 311–393 of a 900-pixel-tall frame, and the rightmost differing pixel of any of the twelve is column 1280 of a 1434-pixel-wide frame — none of them touches the edge. The mark's coloured pixels are still there (857 to 6,102 of them, depending on the platform and the width), so nothing was recorded as a blank mark.
  • The mark no longer differs between platforms. Measured over the mark's own box — columns 930–1310, rows 290–400 of the 1440 shot, 41,800 pixels — the three platforms used to disagree on 19.41% (macOS vs Linux), 23.54% (macOS vs Windows) and 19.36% (Linux vs Windows) of those pixels, because each machine fell through to a different installed font. They now disagree on 6.86%, 5.41% and 7.65%, and the letterforms in the three frames are the same shape: the remainder is anti-aliasing, not a different face.

Checks

  • Local gate, all nine stages — typecheck, dependency, presentation, motion, overlays, feedback, build, boundary, source: pass.
  • Vitest: 302 files, 4525 tests: pass.
  • The bundled file renders byte-identically to the copy Google Fonts serves for the same family and weights — verified by rendering both and comparing frames, and by re-rendering the wordmark with only one of the two weight rules to confirm the variable file is instanced at 600 and at 700 rather than at one weight for both.
  • The twelve new baselines were checked against the twelve old ones pixel by pixel: the differences are confined to the mark, the mark is still drawn, and the three platforms now draw the same letterforms.
  • The pixel harness's new wait was measured, not assumed: the same four shots were taken with and without it and came out identical to the pixel — 0 differing pixels in each of the four.
  • CI, all three runners: pass. The branch's first run failed exactly the four recorded groups on each platform, which is what this change predicts; the twelve baselines were re-recorded from that run's artifacts, and the next run of the same code passes on macOS, Linux and Windows. One caveat worth naming: that second run also had a failure on the Windows runner, in native-runtime.spec.mjs — the spec's own teardown hook missing a 30-second deadline (the native host did not finish closing) while the test itself passed. That spec does not call the helper this change touches, and it passed when the same commit was re-run.

Not included

No expected value changed and no budget was relaxed. The pixel budget stays at 8, the recorded baselines are re-recorded rather than re-tuned, and the fonts the mark used before are kept behind the bundled face.

probe added 3 commits November 11, 2026 10:28
The mark used to name "Century Gothic", a Microsoft font, so a Mac without
Office fell through to Arial Rounded MT Bold, a Linux box fell through to
whatever its fontconfig had, and a Windows box got Century Gothic. The same
mark came out in a different face on every platform, which is what issue
#27 reported: one platform's recorded baseline stopped matching the other
two without a line of WebUI source changing.

The mark now uses Nunito, carried by the client. It is a rounded sans under
the SIL Open Font License 1.1, so it is free to redistribute, and it keeps
the round terminals the mark had on macOS — the face it replaces could not
be carried, because Arial Rounded MT Bold and Century Gothic are Monotype
fonts this repository may not ship.

It travels as one variable file (weight axis 200-1000) that covers both
weights the mark uses: 700 for the wordmark, 600 for its lower line. The
family is named "MiniMax Wordmark" rather than "Nunito" on purpose — a name
no host happens to have installed is a name only this file can answer, so a
machine that already has Nunito cannot answer first with a different version
of it. The faces that came before stay behind it in the stack, so a build
where the font file did not make it into the artifact still draws the mark.
The mark now draws with the font the client carries, so every platform
renders the same shapes and the recorded baselines had to be taken again.
They are taken from what CI itself produced for run 42946296900: a
baseline recorded on any other machine is that machine's rasteriser's
idea of the same picture, and the runner that compares it would fail it.

What the new baselines show is the point of the change. The mark's own
box diverged between platforms by 19% to 24% of its pixels before,
because each machine fell through to a different installed font. It now
diverges by 5% to 8%, and that remainder is anti-aliasing rather than
different letterforms.

Four shots per platform are affected: the workspace panel at 1440,
1024 and 768, and the embedded browser panel, which is where the mark
is drawn.
The mark's font is a web font now, and it is declared with
font-display: block so the mark is never painted in a fallback face
first. That has a consequence for the pixel gate: until the face is
ready the mark is not painted at all, so a screenshot taken inside that
window records a blank mark.

The harness already waited for the two fonts the browser is pinned to.
It now waits for the mark's face too, and the wait is an assertion
rather than a hope: all three faces have to come back loaded, so a
build where the font file did not make it into the artifact fails the
suite instead of quietly recording a blank mark.

Waiting for a face that is already there changes nothing on screen: the
same four pixel shots were taken with and without this wait and came
out identical to the pixel.
@modacker
modacker merged commit 503d69f into main Nov 11, 2026
5 of 6 checks passed
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.

1 participant