Skip to main content

max / quasi-type

Grow the set to the whole arrow and triangle runs, and bump it to v2 Measured 2026-08-17 against Alloy's widget ceiling (alloy docs/FONTS.md) while retiring IosevkaTerm from the image, which is where a gap the old base had been hiding turned up: the set drew three of the four arrows and two of the six triangles, and Plex drew the rest so nothing showed. Atkinson draws none of them, so `←` in a ratatui scrollbar and `▶` in a scroll marker would arrive from a fallback face - one glyph in a row of four at another face's weight, on another face's baseline. Five entries, no new recipes: `←` is the arrow recipe with a direction, and the four triangles are the caret's proportions rotated. `▶` and `►` come out identical, and that is deliberate - Unicode separates TRIANGLE from POINTER by intent rather than by shape, and a house set that drew them differently would be inventing a distinction no consumer makes. The set version goes to 2. A face's identity is (base, set version), so a face carrying a grown set is a different face and says so in its own name table; the tests read the version off the manifest now rather than writing `v1` out. The proof sheet grew a row that sets each run beside its own siblings, since what these have to be is one drawing at two sizes rather than six drawings.
Author: Max Johnson <me@maxj.phd> · 2026-08-17 15:21 UTC
Signed with PGP, not checked
Commit: 6d076b7f85268381f6cd34dd0d28e84c574929cc
Parent: d6994c0
7 files changed, +150 insertions, -40 deletions
M Cargo.lock +24 -24
@@ -526,30 +526,6 @@
526 526 "simd-adler32",
527 527 ]
528 528
529 - [[patch.unused]]
530 - name = "kberg"
531 - version = "0.1.0"
532 -
533 - [[patch.unused]]
534 - name = "ops-status"
535 - version = "0.1.0"
536 -
537 - [[patch.unused]]
538 - name = "painhours"
539 - version = "0.1.0"
540 -
541 - [[patch.unused]]
542 - name = "tagtree"
543 - version = "0.4.0"
544 -
545 - [[patch.unused]]
546 - name = "synckit-client"
547 - version = "0.8.0"
548 -
549 - [[patch.unused]]
550 - name = "synckit-config"
551 - version = "0.2.0"
552 -
553 529 [[patch.unused]]
554 530 name = "quasi-axum"
555 531 version = "0.18.0"
@@ -582,6 +558,30 @@
582 558 name = "quasi-webview"
583 559 version = "0.18.0"
584 560
561 + [[patch.unused]]
562 + name = "synckit-client"
563 + version = "0.8.0"
564 +
565 + [[patch.unused]]
566 + name = "synckit-config"
567 + version = "0.2.0"
568 +
585 569 [[patch.unused]]
586 570 name = "docengine"
587 571 version = "0.7.0"
572 +
573 + [[patch.unused]]
574 + name = "kberg"
575 + version = "0.1.0"
576 +
577 + [[patch.unused]]
578 + name = "ops-status"
579 + version = "0.1.0"
580 +
581 + [[patch.unused]]
582 + name = "painhours"
583 + version = "0.1.0"
584 +
585 + [[patch.unused]]
586 + name = "tagtree"
587 + version = "0.4.0"
@@ -58,7 +58,10 @@
58 58 # the set. Nothing downstream needs touching.
59 59
60 60 [set]
61 - version = 1
61 + # Bumped to 2 on 2026-08-17: `←` and four more triangles. A face's identity is
62 + # (base, set version), so a face carrying a set that grew is a different face
63 + # and says so in its own name table.
64 + version = 2
62 65 name = "quasi house glyph set"
63 66
64 67 # ---------------------------------------------------------------------------
@@ -91,6 +94,65 @@
91 94 depth = 0.866
92 95 anchor = "band-center"
93 96
97 + # The rest of the triangle run, on the same footing as `←` below: nothing in
98 + # Alloy emits them, and ratatui's scroll and selection markers reach for them,
99 + # so a face drawing two of the run and leaving four to a fallback is a row of
100 + # markers at two weights. Same recipe as the carets above and the same
101 + # proportions, rotated; `▾` takes the small separators'.
102 + #
103 + # `▶` and `▸` differ in size and `▶` and `►` do not differ at all here. Unicode
104 + # separates TRIANGLE from POINTER by intent rather than by shape, and a house set
105 + # that drew them differently would be inventing a distinction no consumer makes.
106 + # ---------------------------------------------------------------------------
107 +
108 + [[glyph]]
109 + codepoint = 0x25B6
110 + name = "uni25B6"
111 + role = "scroll marker, right"
112 + source = "alloy widget ceiling, measured 2026-08-17"
113 + purpose = "coverage"
114 + shape = "triangle"
115 + direction = "right"
116 + span = { band = 0.75, weight = 0.62 }
117 + depth = 0.866
118 + anchor = "band-center"
119 +
120 + [[glyph]]
121 + codepoint = 0x25C4
122 + name = "uni25C4"
123 + role = "scroll marker, left"
124 + source = "alloy widget ceiling, measured 2026-08-17"
125 + purpose = "coverage"
126 + shape = "triangle"
127 + direction = "left"
128 + span = { band = 0.75, weight = 0.62 }
129 + depth = 0.866
130 + anchor = "band-center"
131 +
132 + [[glyph]]
133 + codepoint = 0x25BA
134 + name = "uni25BA"
135 + role = "scroll marker, right pointer"
136 + source = "alloy widget ceiling, measured 2026-08-17"
137 + purpose = "coverage"
138 + shape = "triangle"
139 + direction = "right"
140 + span = { band = 0.75, weight = 0.62 }
141 + depth = 0.866
142 + anchor = "band-center"
143 +
144 + [[glyph]]
145 + codepoint = 0x25BE
146 + name = "uni25BE"
147 + role = "scroll marker, down small"
148 + source = "alloy widget ceiling, measured 2026-08-17"
149 + purpose = "coverage"
150 + shape = "triangle"
151 + direction = "down"
152 + span = { band = 0.50, weight = 0.50 }
153 + depth = 0.75
154 + anchor = "band-center"
155 +
94 156 # ---------------------------------------------------------------------------
95 157 # Starship segment separators and error state.
96 158 # ---------------------------------------------------------------------------
@@ -185,15 +247,35 @@
185 247 stroke = 1.0
186 248
187 249 # ---------------------------------------------------------------------------
188 - # The arrows. Alloy's own surfaces emit these four (docs/FONTS.md, measured
250 + # The arrows. Alloy's own surfaces emit four of these (docs/FONTS.md, measured
189 251 # against the built image), and a base can ship a full text face without one:
190 252 # Plex Mono draws all four, Atkinson Hyperlegible Mono draws none.
191 253 #
192 - # One recipe, four entries. The head is the same drawing as the sort caret two
254 + # One recipe, five entries. The head is the same drawing as the sort caret two
193 255 # sections up, which is the point of a house set: a triangle in a status line
194 256 # and a triangle on an arrow should not be two people's triangles.
257 + #
258 + # `←` is here on a different footing from the other four, and it is worth saying
259 + # which: nothing in Alloy emits it, but ratatui draws scrollbar ends with the
260 + # whole U+2190-U+2193 run, so a face carrying three of the four leaves the
261 + # fourth to a fallback — one arrow in a row of four at another face's weight and
262 + # another face's baseline. Measured 2026-08-17 against Alloy's widget ceiling
263 + # while adopting Atkinson; the old base drew all four and hid it.
195 264 # ---------------------------------------------------------------------------
196 265
266 + [[glyph]]
267 + codepoint = 0x2190
268 + name = "uni2190"
269 + role = "leftwards arrow"
270 + source = "alloy widget ceiling, measured 2026-08-17"
271 + purpose = "coverage"
272 + shape = "arrow"
273 + direction = "left"
274 + length = 0.92
275 + head_span = 0.42
276 + head_depth = 0.9
277 + stroke = 1.0
278 +
197 279 [[glyph]]
198 280 codepoint = 0x2191
199 281 name = "uni2191"
M src/manifest.rs +5 -2
@@ -325,10 +325,13 @@
325 325 .iter()
326 326 .filter(|glyph| !glyph.shape.is_cell_furniture())
327 327 .count();
328 - assert_eq!(authored, 11, "seven marks and four arrows");
328 + assert_eq!(
329 + authored, 16,
330 + "the seven marks, five arrows and four scroll markers"
331 + );
329 332 assert_eq!(
330 333 manifest.glyphs.len(),
331 - 171,
334 + 176,
332 335 "plus both generated blocks, 160 of them"
333 336 );
334 337 }
@@ -95,6 +95,13 @@
95 95 present(vec![
96 96 0x25B2, 0x25BC, 0x25B8, 0x25C2, 0x2718, 0x2423, 0x23CE, 0x2191, 0x2193, 0x2192, 0x2195,
97 97 ]),
98 + // The runs, each set beside its own siblings: four arrows that have to
99 + // read as one set, and six triangles that have to read as two sizes of
100 + // one drawing rather than as six drawings.
101 + present(vec![
102 + 0x2190, 0x2192, 0x2191, 0x2193, 0x2195, 0x0020, 0x25B2, 0x25BC, 0x25B6, 0x25C4, 0x25BA,
103 + 0x25BE, 0x25B8, 0x25C2,
104 + ]),
98 105 present(vec![
99 106 0x0078, 0x00D7, 0x2718, 0x002B, 0x25B2, 0x003D, 0x2423, 0x0061, 0x0062, 0x0063, 0x23CE,
100 107 ]),
@@ -58,7 +58,7 @@
58 58 let id = Identity {
59 59 family: &slot.family,
60 60 style: &base.faces[0].style,
61 - version: &format!("1.{}", base.version),
61 + version: &format!("{}.{}", manifest.set.version, base.version),
62 62 set_version: manifest.set.version,
63 63 base,
64 64 };
@@ -39,6 +39,13 @@
39 39 }
40 40 }
41 41
42 + /// The set version the shipped manifest carries, which the face states in its
43 + /// own name table: a face's identity is (base, set version), so the two travel
44 + /// together and neither is written out here.
45 + fn set_version() -> u32 {
46 + Manifest::parse(HOUSE_SET).unwrap().set.version
47 + }
48 +
42 49 /// The style the pin declares, which is the base's own default instance and is
43 50 /// not always `Regular`: Atkinson Mono's is `ExtraLight`.
44 51 ///
@@ -64,13 +71,14 @@
64 71 let slot = pins.slot("quasi-mono").unwrap();
65 72 let base = pins.base(&slot.base).unwrap();
66 73 let manifest = Manifest::parse(HOUSE_SET).unwrap();
74 + let set_version = manifest.set.version;
67 75 let specs = manifest.glyphs;
68 76 let refs: Vec<&GlyphSpec> = specs.iter().collect();
69 77 let id = Identity {
70 78 family: &slot.family,
71 79 style: &style,
72 - version: &format!("1.{}", base.version),
73 - set_version: 1,
80 + version: &format!("{set_version}.{}", base.version),
81 + set_version,
74 82 base,
75 83 };
76 84 let built = compose::build(&face.bytes, &refs, &id).expect("the face cuts");
@@ -99,9 +107,18 @@
99 107 .collect::<Vec<_>>()
100 108 );
101 109 // Alloy's eighteen, the two carets, and the whole house set, deduplicated.
102 - // The number moved when the set grew a cell tier; what it is checking is
103 - // that the floor is the union of the two and not one of them.
104 - assert_eq!(result.required.len(), 176);
110 + // Counted rather than written out: the number has moved twice already, once
111 + // when the set grew a cell tier and once when it grew the rest of the arrow
112 + // and triangle runs. What it is checking is that the floor is the union of
113 + // the two and not one of them.
114 + let union: std::collections::BTreeSet<u32> = coverage::ALLOY_SURFACE
115 + .iter()
116 + .chain(coverage::SORT_CARETS.iter())
117 + .chain(house.iter())
118 + .copied()
119 + .collect();
120 + assert_eq!(result.required.len(), union.len());
121 + assert!(result.required.len() > house.len());
105 122 assert!(
106 123 coverage::ALLOY_SURFACE
107 124 .iter()
@@ -238,14 +255,14 @@
238 255 let unique_id = unique_id.expect("a unique id");
239 256
240 257 assert!(
241 - description.contains("house glyph set v1"),
258 + description.contains(&format!("house glyph set v{}", set_version())),
242 259 "the description names the set version: {description}"
243 260 );
244 261 // The unique id identifies one exact build, so it names both halves rather
245 262 // than the packed `{set}.{base}` version, which cannot be split without
246 263 // already knowing where the base's version starts.
247 264 assert!(
248 - unique_id.contains("house glyph set v1")
265 + unique_id.contains(&format!("house glyph set v{}", set_version()))
249 266 && unique_id.contains("Atkinson Hyperlegible Mono 2.001"),
250 267 "the unique id names both halves of the composition: {unique_id}"
251 268 );
@@ -49,13 +49,14 @@
49 49
50 50 fn cut_as(style: &str) -> Option<Result<(Vec<u8>, Vec<GlyphSpec>), String>> {
51 51 let (face, base) = base_face()?;
52 - let specs = Manifest::parse(HOUSE_SET).unwrap().glyphs;
52 + let manifest = Manifest::parse(HOUSE_SET).unwrap();
53 + let specs = manifest.glyphs;
53 54 let refs: Vec<&GlyphSpec> = specs.iter().collect();
54 55 let id = Identity {
55 56 family: FAMILY,
56 57 style,
57 - version: "1.2.001",
58 - set_version: 1,
58 + version: "2.2.001",
59 + set_version: manifest.set.version,
59 60 base,
60 61 };
61 62 Some(