Skip to main content

max / alloy

Measure the font coverage instead of assuming it docs/FONTS.md, new. The audit the design docs kept deferring: every non-ASCII codepoint Alloy's own source and templates emit, every one the widget stack can reach, and both sets differenced against the cmap tables of the fonts in the built image rather than against the package list. The answer is that there is no gap. IosevkaTerm Nerd Font Mono covers all 18 codepoints Alloy emits and all 723 the widget stack can reach, sextants and octants included, so nothing in a terminal falls back to anything. Atkinson's 722 misses are not gaps either: box drawing never routes to a proportional font, and the rule that keeps that true is now written down rather than assumed. Two findings the measurement turned up on its way past: Departure lacks five of the eighteen, and four of those five are prompt furniture, so a mockup set in Departure loses its starship separators silently. It has no braille either, so it cannot render a Canvas. Both are recorded in STACK.md next to the coverage note already there. ICONOGRAPHY.md described the functional tier as Nerd Font glyphs reached through severity_glyph(Urgency) and pane_marker. Neither accessor exists in alloy_tui 5.0.0, and neither it nor Alloy emits a single Private Use Area codepoint anywhere: what the widgets actually draw is the geometric shapes block. Corrected to say what is there, with the choice between building the accessor and retiring the framing left to the icon-set work. Six of the fourteen families in the fontconfig chain are not in the image. No rendering consequence, since <prefer> skips what is absent, so it is filed as GO 6aeeb122 rather than fixed here.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-06 19:55 UTC
Signed with PGP, not checked
Commit: aa211284b9048daf98a804703e482f7b0af1e561
Parent: 7e26c29
3 files changed, +168 insertions, -1 deletion
@@ -10,7 +10,11 @@
10 10 - **Rendering:** treated as text, a cell wide. Inherits the surrounding text's ramp position and weight. Never accent-colored unless paired with information (an error glyph beside an error message uses `accent-error`).
11 11 - **Size:** the cell. There is no standalone sizing scale in a terminal.
12 12
13 - This tier does the overwhelming majority of Alloy's iconography now. It is uncomplicated, and in a terminal it is the only icon form that renders inline. `alloy_tui` widgets reach for these glyphs by semantic name (a `severity_glyph(Urgency)`, a `pane_marker`) so the choices stay centralized.
13 + This tier does the overwhelming majority of Alloy's iconography now. It is uncomplicated, and in a terminal it is the only icon form that renders inline.
14 +
15 + **As written, though, this tier is aspirational rather than shipped.** Measured 2026-08-06 ([FONTS.md](FONTS.md)): nothing in Alloy's own source or in `alloy_tui 5.0.0` emits a single Private Use Area codepoint, and the `severity_glyph(Urgency)` / `pane_marker` accessors this paragraph used to describe as the centralizing mechanism do not exist. What the widgets actually draw is the geometric-shapes block: `▶` and `▸` for selection, `▾` for disclosure, the block elements for gauges. The Nerd Font glyphs in the terminal font are reached only by third-party tools Alloy configures (starship, yazi, bottom, helix), never by Alloy itself.
16 +
17 + Two readings are open and this file does not settle them: either the semantic-name accessor is work not yet done, or the geometric-shapes set is the real functional tier and the Nerd Font framing above should be retired. Deciding that belongs with the icon-set work, not here.
14 18
15 19 ## Hero tier: isometric line illustrations (reserved, mostly deferred)
16 20
@@ -523,12 +523,16 @@
523 523
524 524 Optimized for readability. A single fontconfig at `~/.config/fontconfig/fonts.conf` routes every app that asks for a generic family to Alloy's picks. Config at [`etc/skel/.config/fontconfig/`](../etc/skel/.config/fontconfig/).
525 525
526 + This section holds the reasoning behind each pick. The measurement lives in [FONTS.md](FONTS.md): which codepoints Alloy's surfaces actually emit, what the shipped fonts cover, and which links in the fallback chain exist. The headline from the 2026-08-06 audit is that there is no coverage gap in any cell-grid surface, and that six of the fourteen families named in the chain are not in the image.
527 +
526 528 **Departure Mono, Alloy's brand mark, is not in the fontconfig chain.** It stays reserved for headers and brand elements and is invoked by name where wanted. Nobody reads code or long text in Departure.
527 529
528 530 **It is also not in the image, which is the more useful fact.** Audited 2026-08-05. The font layer installs IosevkaTerm Nerd and Atkinson Hyperlegible and nothing else, and the only copy in the repo is `docs/mockups/fonts/DepartureMonoNerdFontMono-Regular.otf`, a design-time asset. So Departure is a font for things rendered *outside* the running system: mockups, the README and social card, a swaylock background plate, a first-boot splash image. Anything that names the family at runtime gets a silent fontconfig substitution, which is the same defect class as the emoji alias that named a font the image never installed. Naming it in a theme is therefore not a way to get it.
529 531
530 532 **Measured coverage, for whoever reaches for it in a mockup:** 11,468 glyphs across 169 declared languages. Latin, Cyrillic and Greek, the box-drawing range, and the Nerd Font private-use icons. It does not cover CJK, Arabic, Hebrew, Devanagari, Thai, Bengali or Tamil, and it does not cover Vietnamese, which is the one worth knowing: Vietnamese is Latin-script and looks like it should work, so a string with stacked diacritics falls back mid-word rather than failing visibly. For a brand mark set in English that is an acceptable limit rather than a gap, and it is the reason the font stays out of the generic-family chain where a page of arbitrary text would hit it.
531 533
534 + **One limit does bite where Departure is legitimately used.** Of the eighteen non-ASCII codepoints Alloy's own surfaces emit, Departure lacks five: `⏎` `␣` `▸` `◂` `✘`. Four are starship and helix prompt furniture, which is exactly what a terminal mockup renders, so a mockup set in Departure drops its prompt separators without saying so. It also carries no braille, sextants or octants, so no Departure render can show a `Canvas` or a sparkline. Mock a running surface in Iosevka; keep Departure for headers and plates. Detail in [FONTS.md](FONTS.md).
535 +
532 536 ### Monospace: **IosevkaTerm Nerd Font Mono**
533 537
534 538 The Term variant of Iosevka drops ligatures, which is safe for TUIs (yazi, bottom, helix status columns) that count characters. The Nerd Font bundle carries the glyphs starship, yazi, and bottom need for prompt icons and file-type indicators. Iosevka's narrow proportions give more columns per line than JetBrains Mono or Fira Code, real screen-real-estate value in a tiling stack.
A docs/FONTS.md +159
@@ -1,0 +1,159 @@
1 + # Fonts: what is shipped, what is covered, what catches the gaps
2 +
3 + Companion to [STACK.md](STACK.md), which holds the reasoning behind each pick, and to
4 + [ICONOGRAPHY.md](ICONOGRAPHY.md), which holds the icon tiers. This file holds the
5 + measurement: which codepoints Alloy's own surfaces put on a screen, which of them the
6 + shipped fonts cover, and what the fallback chain does with the rest.
7 +
8 + Measured 2026-08-06 against the image built from `Containerfile` at `eeeb768`, read out
9 + of `podman image mount localhost/alloy:local` rather than out of the package list. A
10 + package list says what was requested; the mounted rootfs says what is there.
11 +
12 + ## Method, so the numbers can be re-derived
13 +
14 + Two enumerations and a set difference.
15 +
16 + - **What a font covers** is its `cmap` table: the Unicode subtable (format 4 or 12), with
17 + every codepoint whose glyph id is non-zero. Not the `name` table's language claims,
18 + which count languages and not glyphs.
19 + - **What Alloy emits** is every non-ASCII codepoint in its own source and templates, with
20 + Rust comments and `#[cfg(test)]` modules cut out first. A comment is never rendered and
21 + a test fixture is never shipped, and both are full of characters that would otherwise
22 + read as coverage requirements. Sources: `crates/`, `etc/`, `usr/`, `templates/`,
23 + `schemas/`, `tools/`, `scripts/`.
24 + - **What the widget stack can emit** is the same scan over `alloy_tui 5.0.0`,
25 + `ratatui-core 0.1.2`, `ratatui-widgets 0.3.2` and `makeover 2.4.1`, the versions in
26 + `Cargo.lock`. This is a ceiling and not a usage count: it includes symbol sets for
27 + widgets Alloy does not draw.
28 +
29 + ## What Alloy's own surfaces emit
30 +
31 + Eighteen distinct non-ASCII codepoints, in total, across the console, the installer TUI
32 + and every shipped config template.
33 +
34 + | Codepoint | Glyph | Where |
35 + |---|---|---|
36 + | U+00B7 | `·` | helix config, whitespace render |
37 + | U+00BB | `»` | starship prompt character |
38 + | U+2014 | `—` | 97 sites, string literals across the console |
39 + | U+2022 | `•` | installer, list bullets |
40 + | U+2026 | `…` | truncation, six sites |
41 + | U+2191 U+2192 U+2193 U+2195 | `↑ → ↓ ↕` | starship git-status, helix whitespace |
42 + | U+23CE | `⏎` | helix, newline render |
43 + | U+2423 | `␣` | helix, space render |
44 + | U+2502 | `│` | helix, indent guide |
45 + | U+2588 U+258F U+2591 | `█ ▏ ░` | installer progress bar, helix indent guide |
46 + | U+25B8 U+25C2 | `▸ ◂` | starship segment separators |
47 + | U+2718 | `✘` | starship, error state |
48 +
49 + Latin-1 punctuation, arrows, four block-element characters and one dingbat. No Private
50 + Use Area: **Alloy itself emits no Nerd Font icons.** The PUA reasoning in
51 + `etc/skel/.config/fontconfig/fonts.conf` and in STACK.md is entirely about third-party
52 + consumers (starship, yazi, bottom, helix), which is worth knowing before anyone treats
53 + the `Mono` family constraint as an Alloy-internal requirement.
54 +
55 + ## What the widget stack can reach
56 +
57 + 723 distinct non-ASCII codepoints, once tests are excluded. As ranges:
58 +
59 + - **U+2022**, the bullet `Canvas` uses as its default marker.
60 + - **U+2190-U+2193**, the four arrows, for scrollbar ends.
61 + - **U+2500-U+2570** (113) box drawing: `Block` ships plain, thick, double, rounded and
62 + dashed at three densities, so nearly the whole block is reachable.
63 + - **U+2574-U+259F** (44) the half-lines and the block elements, for gauges, bars and the
64 + bevel `alloy_tui` draws.
65 + - **U+25B2 U+25B6 U+25B8 U+25BA U+25BC U+25BE U+25C4**, triangles, for selection and
66 + scroll markers.
67 + - **U+2800-U+28FF** (256) the full braille range, which `Canvas` uses as a sub-cell pixel
68 + grid.
69 + - **U+1CD00-U+1CDE5** (230) and **U+1CEA0-U+1CEAB**, Symbols for Legacy Computing:
70 + ratatui's sextant and octant marker sets.
71 + - **U+1FB00-U+1FB3B** (60), **U+1FB82**, **U+1FB85**, **U+1FBE6-U+1FBE7**, Symbols for
72 + Legacy Computing Supplement, same purpose.
73 +
74 + The last three ranges are the interesting ones: they are recent Unicode, and a font that
75 + covers box drawing convincingly can still have nothing there.
76 +
77 + ## Coverage against the shipped fonts
78 +
79 + | Font | Codepoints | Missing from Alloy's set | Missing from the widget ceiling |
80 + |---|---|---|---|
81 + | IosevkaTerm Nerd Font Mono | 17,827 | **0** | **0** |
82 + | Atkinson Hyperlegible | 342 | 13 | 722 |
83 + | Departure Mono NF Mono (not in the image) | 11,468 | 5 | 557 |
84 +
85 + **The monospace answer is that there is no gap.** IosevkaTerm Nerd Font Mono covers
86 + every codepoint Alloy emits and every codepoint the widget stack could emit, sextants and
87 + octants included. Nothing in a terminal, the console, the installer or the greeter falls
88 + back to anything, so the chain below is insurance rather than a working part.
89 +
90 + **Atkinson's gaps are not gaps**, because nothing routes box drawing to a sans font.
91 + Atkinson serves the `sans-serif` alias and `gtk-font-name`, which is proportional UI text
92 + in GTK dialogs and the portal file chooser. The 722 it lacks are box drawing, blocks,
93 + braille and legacy-computing, none of which any Pango surface asks for. The rule worth
94 + writing down: **cell-grid surfaces get the mono family and the symbol sets with it;
95 + proportional surfaces get Atkinson and never emit a symbol set.** A future surface that
96 + breaks that rule is where this becomes a real gap.
97 +
98 + **Departure's gaps only matter outside the running system.** It is not in the image
99 + (STACK.md records the 2026-08-05 audit), so it can only appear in a mockup, the README,
100 + the social card or a splash plate. Of the eighteen codepoints Alloy emits it lacks five:
101 + `⏎` (U+23CE), `␣` (U+2423), `▸` (U+25B8), `◂` (U+25C2), `✘` (U+2718). Four of those five
102 + are starship and helix prompt furniture, which is exactly what a terminal mockup renders,
103 + so a mockup set in Departure loses its prompt separators silently. It also has no
104 + braille, no sextants and no octants, so no Departure render can show a `Canvas` or a
105 + sparkline. Use Iosevka for any mockup of a running surface, and keep Departure for
106 + headers and brand plates, which is what STACK.md already asks for on other grounds.
107 +
108 + ## The fallback chain, in order, and which links exist
109 +
110 + From `etc/skel/.config/fontconfig/fonts.conf`. Each `<prefer>` list is tried in order and
111 + fontconfig skips a family the system does not have.
112 +
113 + | Alias | Order | In the image? |
114 + |---|---|---|
115 + | `monospace` | IosevkaTerm Nerd Font Mono | yes, 81 faces under `iosevkaterm-nerd` |
116 + | | Iosevka Term | no |
117 + | | Iosevka Nerd Font | no |
118 + | | Iosevka | no |
119 + | | DejaVu Sans Mono | no |
120 + | `sans-serif` | Atkinson Hyperlegible | yes |
121 + | | Inter | no |
122 + | | Cantarell | yes, `abattis-cantarell-vf-fonts` |
123 + | | DejaVu Sans | no |
124 + | `serif` | Source Serif 4 | no |
125 + | | Source Serif Pro | no |
126 + | | Liberation Serif | yes |
127 + | | DejaVu Serif | no |
128 + | `emoji` | (deliberately absent) | no emoji font is installed |
129 +
130 + **Six of the fourteen named families are not in the image.** They cost nothing at
131 + runtime, because `<prefer>` skips what is absent, and they are not the emoji-alias defect
132 + (that alias changed where emoji resolved to; these do not change anything). They are a
133 + reading hazard rather than a rendering one: the list looks like a depth of fallback that
134 + is not there. The monospace chain in particular has exactly one live entry, so if
135 + IosevkaTerm ever failed to install, resolution would drop past four dead names straight
136 + into Fedora's own generic rules. Whether the dead names get deleted or annotated is GO
137 + task `6aeeb122`.
138 +
139 + **What actually catches a gap is the layer below this file**, and it is worth naming
140 + because the chain above suggests otherwise:
141 +
142 + 1. `/etc/fonts/conf.d/45-latin.conf` and `60-latin.conf`, Fedora's generic-family
143 + ordering, which is what a request resolves through once the `<prefer>` list is
144 + exhausted.
145 + 2. `59-adwaita-mono-fonts.conf`, `59-liberation-*.conf`, the URW base-35 set: the real
146 + substitutes present on disk for mono, sans and serif.
147 + 3. The Noto set for everything non-Latin: `NotoSans[wght].ttf` plus 24 per-script
148 + variable faces, `NotoSansCJK-VF.ttc` and `NotoSansMonoCJK-VF.ttc`. This is the layer
149 + the 2026-07-29 coverage fix added, and it is what stops an arbitrary web page from
150 + rendering as rows of missing glyphs.
151 + 4. Nothing, for emoji. That is the decided position, not a gap.
152 +
153 + ## Re-running this
154 +
155 + The scripts are not in the repo: the measurement is a one-off against a built image, and
156 + a checked-in tool that nobody runs drifts into a lie. Re-derive it by mounting the image
157 + and reading `cmap` tables directly. The three numbers worth re-checking after a font bump
158 + are the IosevkaTerm coverage count (17,827 at Nerd Fonts v3.4.0), the size of Alloy's own
159 + emitted set (18), and whether either "missing from the widget ceiling" cell is still 0.