max / quasi-type
7 files changed,
+150 insertions,
-40 deletions
| @@ -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" |
| @@ -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( |