Skip to content

Make the UI font configurable - #6

Merged
cmer merged 1 commit into
mainfrom
feat/configurable-ui-fonts
Aug 12, 2026
Merged

Make the UI font configurable#6
cmer merged 1 commit into
mainfrom
feat/configurable-ui-fonts

Conversation

@cmer

@cmer cmer commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Adds a font picker at the bottom of the sidebar, next to the theme toggle, and stops inlining fonts in the stylesheet.

What you get

Six choices: Public Sans (the new default), Barlow, General Sans, Inter, Manrope, and System (the OS UI font, no webfont downloaded). Hosts set the default everyone starts on:

Flightdeck.configure do |config|
  config.ui_font = "inter"
end

Anything not offered raises in the initializer rather than falling back silently. Each user's own pick is remembered in localStorage and overrides the default. The mono face (IBM Plex Mono) stays fixed — the job tables depend on its tabular figures.

How it works

The face is a data-font stamp on <html>, rendered server-side so the first paint is already correct. Only the [data-font] blocks in input.css name a family; every other rule reads --font-ui. Switching is therefore one attribute write — no reload, no stylesheet swap, no font-loading JS.

Fonts are no longer base64 data: URIs. rake assets:build copies each vendored woff2 into the manifest under a digest name and emits @font-face rules with relative url()s, which resolve against the stylesheet's own URL and so stay correct under any mount path. A browser downloads only the face it renders in, and the stylesheet drops 118KB → 31KB despite offering four more families. AssetsController needed no change — the manifest whitelist still guarantees a request can never name a file that was not built.

Inter, Manrope and Public Sans are served by Google as variable fonts, so those ship as a single file declaring the weight range it covers rather than three static instances.

Notes for review

  • General Sans licensing. It is not on Google Fonts; it comes from Fontshare under the ITF Free Font Licence rather than the OFL covering the other five. The FFL clearly permits embedding in websites and apps, but redistributing the binaries inside a published gem is a case it does not spell out, and the licence page is JS-rendered so I could not retrieve the text to check. FONT-LICENSES.md records what each face is under. Worth confirming before release — dropping the face is a LABELS entry, a CSS block and a rebuild.
  • The picker is transparent, not opacity: 0. Zero opacity reads as "not displayed" to WebDriver and makes the control undriveable from system tests.
  • A user who overrode the default sees one frame of it on load, since the stored pick applies when the Stimulus controller connects. Same tradeoff the theme toggle already makes; avoiding it means a blocking inline script in <head>.
  • No version bump herelib/flightdeck/version.rb is owned by bin/release. This is intended to ship as 1.1.0, so release with bin/release 1.1.0 after merge.

Testing

  • test/ui_fonts_test.rb fails if a face is missing from the registry, the stylesheet, or the vendored files — the three places that cannot see each other.
  • System tests assert via document.fonts.check() that every offered face really loads, so a corrupt or unroutable woff2 fails loudly instead of falling back to system-ui. Also covers that the mono face is unaffected and that a pick survives navigation.
  • assets:check now fails if a face is vendored but never built.
  • Full suite green in both host modes (277 × 2), 13 system tests, rubocop clean. Screenshots regenerated.

🤖 Generated with Claude Code

Adds a font picker at the bottom of the sidebar, next to the theme
toggle, offering Public Sans (the new default), Barlow, General Sans,
Inter, Manrope, and the OS UI font. Hosts set the default everyone
starts on with `config.ui_font`; each user's own pick is remembered in
localStorage and overrides it.

The face is a `data-font` stamp on <html>, rendered server-side so the
first paint is already correct. Only the `[data-font]` blocks in
input.css name a family — every other rule reads --font-ui — so
switching is one attribute write, with no reload and no stylesheet
swap.

Fonts are no longer inlined as base64 data: URIs. `rake assets:build`
now copies each vendored woff2 into the manifest under a digest name
and emits @font-face rules with relative url()s, which resolve against
the stylesheet's own URL and so survive any mount path. A browser
downloads only the face it renders in, and the stylesheet drops from
118KB to 31KB despite offering four more families.

Inter, Manrope and Public Sans are served by Google as variable fonts,
so those ship as a single file declaring the weight range it covers
rather than three static instances. General Sans is not on Google
Fonts and comes from Fontshare; FONT-LICENSES.md records what each
face is licensed under.

test/ui_fonts_test.rb fails if a face is missing from the registry,
the stylesheet or the vendored files, and the system tests assert via
document.fonts.check that every offered face really loads rather than
silently falling back to system-ui.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cmer
cmer merged commit c2e63fb into main Aug 12, 2026
9 checks passed
@cmer
cmer deleted the feat/configurable-ui-fonts branch August 12, 2026 17:44
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