cmux NEXT: Inter, bundled, is the UI font everywhere, with its calculated tracking

Merged
TE
10d ago
+1940-462

Leo asked for Inter as the UI font with its calculated tracking and the fonts bundled in. None of it was on feat-cmux-next or main: --font-ui was never defined, pages hard-coded system-ui / -apple-system, and native chrome drew SF. This makes Inter, bundled, the UI font of cmux NEXT on both sides, with per-size tracking, and adds a check that keeps it that way.

Leo-ask: ask-52da478628
Leo-ask: ask-00b6f86024

Leo's words (ask-00b6f86024, as written): "WE ALREADY FUCKING HAD THE FUCKING FONTS WE SAID INTER WITH SOME AMOUNT AND WE FUCKING BUUNDLED SOME SHIT IN WHERE THE FUCK IS EVERYBODY WHERE THE FUCK IS THE FUCKING CI AND EFFICIENCY AND OPTIMIZATION"

What changes

  • Bundled font. Inter 4.1 from the official release (Inter-4.1.zip, sha256 9883fdd4…6b11e), unmodified: InterVariable.ttf (CoreText), InterVariable.woff2 (pages) and the OFL license in CmuxNextDesign/Resources/Fonts/, with each file's sha256 in FONTS.md. The italic isn't bundled, to keep the app lean.

  • One source. schemas/type-scale/type-scale.json gets font (family Inter, the TTF's own family Inter Variable, the files, the system fallback) and tracking, Inter's dynamic metrics: a + b * e^(c * size) em with a = -0.0223, b = 0.185, c = -0.1745. gen-type-scale.py writes each step's tracking into the JSON (4 decimals), so the table is never typed by hand:

    stepsizetracking (em)
    caption110.0048
    detail120.0005
    body13-0.0032
    field14-0.0062
    title15-0.0088
    heading20-0.0167

    The other recorded value is the 2026-10-19 study's flat -0.011em against SF Pro. Leo can switch to it by setting a = -0.011, b = 0 (with a Leo-order: trailer, see below).

  • Web. The generator writes --font-ui ("Inter", then the system stack) and --text-<step>--letter-spacing into desktop.css, and Tailwind's text-* utilities carry the letter-spacing. Every cmux web view installs WebTheme.userScripts: the theme bootstrap, then BundledFonts.pageScript, which declares Inter's @font-face from a data: URL (every page CSP already allows data: fonts). Every webviews stylesheet that named the system font now uses var(--font-ui), and every rule sized by a type-scale step also sets that step's letter-spacing (rules with a monospace family are skipped). The chat's "system" font preset and the Markdown viewer's default body font follow --font-ui. A font the person picked (#22762's settings: chat, code, Markdown and terminal fonts) still wins. The gallery frame declares the same face.

  • Native. CheckedFont draws the default design in Inter (BundledFonts.uiFont, with its wght axis set from the AppKit weight). If the font is missing it falls back to the system font. ChromeTextLayer and LabelText add the tracking as kern at each font's own size (em * size points; 0 for any face other than Inter). The theme store's launch init registers the font and builds the page script off the main thread.

  • Leo's bans. scripts/cmux-next/check-leo-bans.py, with the pin schemas/type-scale/leo-bans.json, fails when:

    • the UI family, the tracking constants or the tracking table drift from the pin
    • --font-ui doesn't start with Inter
    • the Swift TypeScale family or tracking, or the CheckedFont path, isn't the bundled Inter
    • the bundled TTF's family isn't Inter Variable
    • any webviews stylesheet names system-ui / -apple-system / BlinkMacSystemFont
    • the pin changes in a range where no commit touching it has a Leo-order: <Leo's words> trailer

    It runs in prepush-gate.py on every push, where CMUX_NEXT_CHECKS_SKIP_REASON doesn't skip it, and as its own step, "Leo's bans (Inter UI font and tracking)", in the cmux-next checks job. tests/test_leo_bans.py covers each case and is registered in tests/test-execution.toml (linux-guard) and in the checks job's script tests.

Verification

Test-first: 9251507fc143 commits tests/test_leo_bans.py before the check exists. At that commit scripts/cmux-next/check-leo-bans.py doesn't exist, so every case gets exit 2 and fails, and the pass case can't pass. The head runs it in CI: python3 tests/test_leo_bans.py (checks job, "Script tests").

Observed / Not run

Observed:

  • python3 -I scripts/cmux-next/check-leo-bans.py: ok on the head. At the base it reports the missing pin (and, before the stylesheet changes, 68 system-font findings).
  • python3 -I scripts/cmux-next/check-leo-bans.py --rev HEAD --base <base>: ok, because the pin-adding commit carries Leo-order:.
  • python3 -I scripts/cmux-next/gen-type-scale.py --check: clean.
  • python3 scripts/cmux-next/prepush-gate.py: no new violations.
  • scripts/web-lint.sh (Oxfmt, Oxlint, type check and style checks on the changed webviews files): passed.
  • cmux-ci compile-check --ref <sha> --app on the fleet passed at 2c9f292920d8 and again at the head, e7023990fb76.
  • PR CI at the head e7023990fb76: success (glaeda-gh wait pr). That covers tests/test_leo_bans.py and the new "Leo's bans" step in the cmux-next checks job.
  • Independent review subagent: SHIP at 2c9f292920d8 and again at e7023990fb76. Its should-fix items are fixed in e7023990fb76: .kern is set only for Inter, so other faces keep their own kerning, and an Inter the person already installed counts as available.

Not run here (CI runs them, since this Mac takes no test runs):

  • The red run of tests/test_leo_bans.py at 9251507fc143. Green ran in CI at the head.
  • The webviews bun tests I updated: agent-pane-chat-display (the chat's measure font is now "Inter", system-ui, sans-serif) and internal-pages-app-font (the body's font stack now starts with Inter).
  • The Swift suites.
  • I didn't watch Inter render in a live app. The tracking values come from the formula, not from a visual check.
  • Known costs, left for follow-ups:
    • The page script inlines the 352 KB WOFF2 as base64 (about 470 KB of script per web view). Serving it from each page scheme would cut that.
    • Native text set outside ChromeTextLayer and LabelText (SwiftUI Text, NSTextField) gets Inter without the tracking.
    • The native font sits at optical size 14 at every size, while the web picks the optical size per size.

Capture

Web before/after (gallery, merge-base vs this branch): https://cmux-lawrences-mac-mini.tail137216.ts.net:18796/compare/cmux-inter-ui-font/

No capture: cmux-ci capture <head> --state scene:main-showcase --pr 24100 can't build a DEV app at any current feat-cmux-next head. The fleet's web step stops on a base red: "the agent pane imports webviews/src/agent-session/acpmux/inspector/ContextSection.css but build-agent-pane-web.sh does not add it to the page's styles". That comes from #23789, and build-agent-pane-web.sh at base ca686c9badab still doesn't list the file (fleet build jobs e40b2086… and c8bb618a…). The web before/after above shows the font and tracking on every gallery state. The native chrome change shows up once the base builds again.

Changelog

  • Changed: the UI font is Inter, bundled with the app, with tracking tuned per text size, in native chrome and every web page.

Agent session: cosmic-gray

🤖 Generated with Claude Code