feat(theme): pick terminal and UI fonts from the installed families (#196) - #201
Merged
Conversation
…196) The font pickers offered two bundled presets and a free-text box. A family typed into that box was accepted whether or not it existed, and an unresolvable name silently falls through to a proportional default — cells measured from a serif face, letters painted in it. That is what #196 looks like, and nothing in the UI said the font had not been found. A new `list_system_fonts` command enumerates installed families with `fontdb`, which scans the OS font directories in pure Rust and so needs no fontconfig, CoreText or DirectWrite linkage. Families are deduplicated across faces and reported under their English name, which is what CSS matches on. The picker now lists those families, previews each in its own face, and filters the terminal one to `monospaced` faces with a "show all fonts" escape hatch — some fonts misreport fixed pitch, and Nerd Fonts' non-Mono variants report proportional. Picking one stores `'Family', monospace`, so the generic tail is there even when the family later fails to resolve. The free-text box stays, and now warns when the family it holds is not installed. Bundled webfonts (JetBrains Mono, Source Code Pro) ship inside the app rather than being installed, so they stay pinned as presets and are never reported missing.
kipavy
added a commit
that referenced
this pull request
Sep 1, 2026
…203) The font picker added in #201 came with its own trigger, rows, dividers, footer actions and search box, so the one dropdown next to the appearance settings looked unlike every other dropdown in the app: a smaller trigger whose chevron swapped glyphs instead of rotating, rows tinted with accent-12% instead of carrying an accent label, and its own radius, paddings and shadow. It is now assembled from PickerTrigger, PickerSurface, PickerOption, PickerDivider and PickerFooterAction, the same parts as the encoding, key, identity and folder pickers. That also gives it the surface's behaviour, which the in-flow absolute menu could not have: the list is a portalled float that flips above the trigger when the theme panel is scrolled near the bottom, and a bottom sheet on Android. PickerTrigger and PickerOption gain an optional labelFont so a row can preview itself in the face it names, and PickerOption's icon becomes optional for rows whose label is the whole content. The in-surface filter box, until now copied between the distro icon picker and the host command field, is extracted as PickerSearch and used by all three. A nested fixed-height scroller inside PickerSurface fought the surface's own 320px cap — two scrollbars, and a row clipped mid-glyph. The surface is the only scroller now, with the filter pinned sticky to its top.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows #199. Together they close #196.
Why
The font pickers offered two bundled presets and a free-text box. A family typed into that box was accepted whether or not it existed on the machine, and an unresolvable family silently falls through to the engine's proportional default — cells measured from a serif face, letters painted in it. That is exactly what #196 looks like, and nothing in the UI ever said the font had not been found.
#199 makes that failure degrade to a monospace face instead of a proportional one. This PR stops it happening in the first place.
What
Backend. A new
list_system_fontscommand enumerates installed families withfontdb. It scans the OS font directories in pure Rust, so it needs no fontconfig, CoreText or DirectWrite linkage and stays out of the cross-compile images' way. Faces are deduplicated into one row per family, reported under the Englishnamerecord because that is what CSS matches on, and flagged monospace if any face in the family claims fixed pitch.Frontend. The picker keeps the bundled presets pinned at the top, then lists the installed families with a search box, each row previewed in its own face. The terminal picker filters to monospaced families, with a Show all fonts toggle — some fonts misreport fixed pitch, and Nerd Fonts' non-
Monovariants legitimately report proportional. Picking a family stores'Family', monospace(orsans-seriffor the UI picker), so the generic tail from #199 is present from the start. The free-text box stays as an escape hatch and now warns when the family it holds is not installed.Bundled webfonts (JetBrains Mono, Source Code Pro) ship inside the app rather than being installed, so
fontdbcannot see them — they stay pinned as presets and are never reported missing.Strings added to all four locales (en/fr/ru/zh); the locale diffs are additive only.
Verification
Driven live in the headless container with the Meslo Nerd Fonts installed:
DejaVu Sans Monoand the Meslo Mono variants;DejaVu Sans/Serifand the non-MonoMeslo variants hiddenMesloLGS Nerd Font Mono'MesloLGS Nerd Font Mono', monospaceMesloLGS NFThat last case is the likely shape of the original report: powerlevel10k's installer ships the family as
MesloLGS NF, not the Nerd Fonts v3 nameMesloLGS Nerd Font Mono.tsc --noEmitclean.--libgreen: 349 passed, 0 failed. (A fresh worktree fails 3commands::pluginstests untilpnpm build:pluginshas run — unrelated to this change.)cargo fmt --checkclean.Notes for review
monospacedflag comes frompost.isFixedPitchand PANOSE, which is why the escape hatch exists rather than a hard filter.