Skip to main content

max / quasi

A band sits at the end it was said at Ruled by Max 2026-08-23 (quasicoherent 3725bacf), picking (b) of two: where a band appears in a screen's slots is which end of the screen it belongs to, so a footer is a band declared after the body rather than a member the vocabulary had to grow. What the ruling settled was a disagreement rather than a silence. quasi-webview emits slots in declaration order and always has; quasi-immediate and quasi-tui both hoisted every band to the top, so one description drew two different screens depending on who was drawing it, and the renderer that honoured order was the one with live screens in front of users. egui reserves the bottom with a bottom-up layout, which is the host's own arithmetic answering "how tall is this band" exactly, rather than a reserved guess. The terminal asks `height` per trailing band and cuts the rows out of the body before arranging it, or a pane that fills its area would leave the footer nowhere to go. Both walk trailing bands in declaration order, so the band said last is the one at the bottom -- reversed in egui, where the first widget in a bottom-up layout is the lowest. The terminal's focus walk moves with the drawing: leading bands, body, trailing bands. The two walks disagreeing is how a caret lights a control that is not where it looks. Seven tests. A screen of nothing but bands is unchanged, which is every description written before today.
Author: Max Johnson <me@maxj.phd> · 2026-08-23 13:30 UTC
Signed with PGP, not checked
Commit: 439aa5517463e8449e70545554458e76ae941e15
Parent: 9804cc1
5 files changed, +265 insertions, -28 deletions
M Cargo.lock +4 -4
@@ -6197,10 +6197,6 @@
6197 6197 "winnow 1.0.4",
6198 6198 ]
6199 6199
6200 - [[patch.unused]]
6201 - name = "synckit-client"
6202 - version = "0.8.1"
6203 -
6204 6200 [[patch.unused]]
6205 6201 name = "quasi-type"
6206 6202 version = "0.1.0"
@@ -6220,3 +6216,7 @@
6220 6216 [[patch.unused]]
6221 6217 name = "tagtree"
6222 6218 version = "0.4.1"
6219 +
6220 + [[patch.unused]]
6221 + name = "synckit-client"
6222 + version = "0.8.1"
@@ -28,24 +28,73 @@
28 28 use crate::{Pass, node};
29 29
30 30 /// Lay a screen's regions out and draw them.
31 + ///
32 + /// Bands take the height they need and get out of the way; which way they get
33 + /// out of is where they were said. Ruled by Max 2026-08-23 (quasicoherent
34 + /// `3725bacf`), picking (b) of two: a band declared before the body sits above
35 + /// it and one declared after it sits below, so a footer is a band at the end
36 + /// rather than a member the vocabulary had to grow.
37 + ///
38 + /// What the ruling settled was a disagreement, not a silence. quasi-webview
39 + /// emits `screen.slots` in declaration order and always has; this renderer and
40 + /// quasi-tui both hoisted every band to the top, so the same description drew
41 + /// two different screens depending on who was drawing it. Order was already
42 + /// load-bearing for the one renderer with live screens.
31 43 pub(crate) fn screen_regions(pass: &mut Pass<'_>, ui: &mut Ui, screen: &Screen) {
32 - // Bands stack at the top, full width, in the order they were said. A band is
33 - // an arrangement rather than a type -- a page header, a toolbar -- so it
34 - // takes the height it needs and gets out of the way.
35 - for slot in screen
36 - .slots
37 - .iter()
38 - .filter(|slot| matches!(slot.kind, RegionKind::Band))
39 - {
44 + let (leading, body, trailing) = split(screen);
45 +
46 + for slot in &leading {
40 47 node::region(pass, ui, slot);
41 48 }
42 49
43 - let body: Vec<&Slot> = screen
50 + // The trailing bands first, from the bottom up, so the body is arranged in
51 + // what is left rather than in all of it. egui gives no way to ask a region
52 + // how tall it will be without drawing it, and a reserved guess would be the
53 + // authored number the whole layer exists to refuse; a bottom-up layout is
54 + // the host's own arithmetic answering the same question exactly.
55 + //
56 + // Reversed, because the first widget in a bottom-up layout is the lowest
57 + // one: the band said last is the band at the bottom.
58 + if trailing.is_empty() {
59 + arranged(pass, ui, screen, &body);
60 + } else {
61 + ui.with_layout(egui::Layout::bottom_up(egui::Align::Min), |ui| {
62 + for slot in trailing.iter().rev() {
63 + node::region(pass, ui, slot);
64 + }
65 + ui.with_layout(egui::Layout::top_down(egui::Align::Min), |ui| {
66 + arranged(pass, ui, screen, &body);
67 + });
68 + });
69 + }
70 + }
71 +
72 + /// A screen's slots as the bands above the body, the body, and the bands below.
73 + ///
74 + /// The split is at the first region that is not a band. A screen of nothing but
75 + /// bands is all leading, which keeps the shape every description written before
76 + /// the ruling already had.
77 + fn split(screen: &Screen) -> (Vec<&Slot>, Vec<&Slot>, Vec<&Slot>) {
78 + let first_body = screen
44 79 .slots
45 80 .iter()
46 - .filter(|slot| !matches!(slot.kind, RegionKind::Band))
47 - .collect();
81 + .position(|slot| !matches!(slot.kind, RegionKind::Band));
82 + let Some(at) = first_body else {
83 + return (screen.slots.iter().collect(), Vec::new(), Vec::new());
84 + };
85 + let is_band = |slot: &&Slot| matches!(slot.kind, RegionKind::Band);
86 + (
87 + screen.slots[..at].iter().collect(),
88 + screen.slots[at..]
89 + .iter()
90 + .filter(|slot| !is_band(slot))
91 + .collect(),
92 + screen.slots[at..].iter().filter(is_band).collect(),
93 + )
94 + }
48 95
96 + /// The body regions, laid out the way the screen said.
97 + fn arranged(pass: &mut Pass<'_>, ui: &mut Ui, screen: &Screen, body: &[&Slot]) {
49 98 match screen.arrangement {
50 99 Arrangement::SidebarContent { share } => {
51 100 let sidebar = body
@@ -72,7 +121,7 @@
72 121 // rather than refused: the regions are real and the reader can
73 122 // still use them.
74 123 None => {
75 - for slot in &body {
124 + for slot in body {
76 125 scrolled(ui, slot, pass);
77 126 }
78 127 }
@@ -1393,6 +1393,72 @@
1393 1393 }
1394 1394 }
1395 1395
1396 + /// A screen with a band, a pane and a band, said in that order.
1397 + fn topped_and_tailed() -> Screen {
1398 + Screen::sidebar_content("Test")
1399 + .with(Slot::new("bar", RegionKind::Band).with(Node::text("the toolbar")))
1400 + .with(Slot::new("main", RegionKind::Pane).with(Node::text("the content")))
1401 + .with(Slot::new("foot", RegionKind::Band).with(Node::text("the status")))
1402 + }
1403 +
1404 + #[test]
1405 + fn a_band_said_after_the_body_is_drawn_under_it() {
1406 + // Ruled by Max 2026-08-23 (quasicoherent 3725bacf): where a band was said
1407 + // is which end it belongs to. Before it this renderer hoisted every band,
1408 + // so a footer drew above the content it was the footer of while the same
1409 + // description put it underneath in a webview.
1410 + let mut host = Host::new();
1411 + let screen = topped_and_tailed();
1412 + host.settle(&screen);
1413 +
1414 + let bar = host.find("the toolbar");
1415 + let content = host.find("the content");
1416 + let foot = host.find("the status");
1417 +
1418 + assert!(
1419 + bar.y < content.y,
1420 + "the toolbar left the top: {bar:?} {content:?}"
1421 + );
1422 + assert!(
1423 + content.y < foot.y,
1424 + "the footer is above what it is the footer of: {content:?} {foot:?}"
1425 + );
1426 + }
1427 +
1428 + #[test]
1429 + fn the_band_said_last_is_the_one_at_the_bottom() {
1430 + // Two trailing bands, which is audiofiles' shell: a migration strip above
1431 + // the status band. A bottom-up layout puts the first widget lowest, so the
1432 + // order they are drawn in is the reverse of the order they were said in,
1433 + // and getting that wrong is invisible until there are two.
1434 + let screen = Screen::sidebar_content("Test")
1435 + .with(Slot::new("main", RegionKind::Pane).with(Node::text("the content")))
1436 + .with(Slot::new("strip", RegionKind::Band).with(Node::text("the strip")))
1437 + .with(Slot::new("foot", RegionKind::Band).with(Node::text("the status")));
1438 + let mut host = Host::new();
1439 + host.settle(&screen);
1440 +
1441 + let strip = host.find("the strip");
1442 + let foot = host.find("the status");
1443 + assert!(
1444 + strip.y < foot.y,
1445 + "the bands are upside down: {strip:?} {foot:?}"
1446 + );
1447 + }
1448 +
1449 + #[test]
1450 + fn a_screen_of_nothing_but_bands_is_unchanged() {
1451 + // The shape every description written before the ruling had. With no body
1452 + // to be after, every band is a leading one.
1453 + let screen = Screen::sidebar_content("Test")
1454 + .with(Slot::new("one", RegionKind::Band).with(Node::text("first")))
1455 + .with(Slot::new("two", RegionKind::Band).with(Node::text("second")));
1456 + let mut host = Host::new();
1457 + host.settle(&screen);
1458 +
1459 + assert!(host.find("first").y < host.find("second").y);
1460 + }
1461 +
1396 1462 /// A band whose members were said to share one row.
1397 1463 fn run_of(fallback: layout::Fallback, members: [(&str, layout::Priority); 3]) -> Screen {
1398 1464 let mut band = Slot::new("bar", RegionKind::Band).across(fallback);
@@ -73,18 +73,35 @@
73 73 let area = measured(screen.measure, area);
74 74 let mut rest = area;
75 75
76 - // Bands stack at the top, full width, in the order they were said. A band
77 - // is an arrangement rather than a type -- a page header, a toolbar -- so it
78 - // takes the rows it needs and gets out of the way.
79 - for slot in screen
80 - .slots
81 - .iter()
82 - .filter(|slot| matches!(slot.kind, RegionKind::Band))
83 - {
76 + // Bands take the rows they need and get out of the way; which way they get
77 + // out of is where they were said. Ruled by Max 2026-08-23 (quasicoherent
78 + // `3725bacf`), picking (b) of two: a band before the body sits above it and
79 + // a band after it sits below, so a footer is a band at the end rather than
80 + // a member the vocabulary had to grow. quasi-webview has emitted slots in
81 + // declaration order all along; this renderer and quasi-immediate hoisted,
82 + // so the ruling settled a disagreement rather than a silence.
83 + let (leading, trailing) = bands(screen);
84 + for slot in &leading {
84 85 let used = draw(pass, slot, rest, buf);
85 86 rest = below(rest, used);
86 87 }
87 88
89 + // The bottom is reserved before the body is laid out, or a pane that fills
90 + // its area would leave the footer nowhere to go. Reserved by asking each
91 + // trailing band how tall it is, which this renderer can do exactly --
92 + // `height` is the same arithmetic the scroll already trusts -- so nothing
93 + // here is a guess and no number is authored.
94 + let reserved = trailing
95 + .iter()
96 + .map(|slot| height(pass.tui, slot, rest.width))
97 + .sum::<u16>()
98 + .min(rest.height);
99 + let mut feet = below(rest, rest.height - reserved);
100 + rest = Rect {
101 + height: rest.height - reserved,
102 + ..rest
103 + };
104 +
88 105 let body = body_slots(screen);
89 106
90 107 match screen.arrangement {
@@ -124,6 +141,13 @@
124 141 }
125 142 }
126 143
144 + // The trailing bands, in the room kept for them, in the order they were
145 + // said: the band said last is the one at the bottom.
146 + for slot in &trailing {
147 + let used = draw(pass, slot, feet, buf);
148 + feet = below(feet, used);
149 + }
150 +
127 151 // Modals last and over everything, which is what a modal is. Centred in
128 152 // half the width, because `Depth::Overlay` says it sits above the page and
129 153 // says nothing about how much of it to cover.
@@ -188,6 +212,26 @@
188 212 }
189 213 }
190 214
215 + /// A screen's bands, as the ones above the body and the ones below it.
216 + ///
217 + /// The split is at the first region that is not a band or a modal. A screen of
218 + /// nothing but bands is all leading, which is the shape every description
219 + /// written before the ruling already had.
220 + fn bands(screen: &Screen) -> (Vec<&Slot>, Vec<&Slot>) {
221 + let is_band = |slot: &&Slot| matches!(slot.kind, RegionKind::Band);
222 + let first_body = screen
223 + .slots
224 + .iter()
225 + .position(|slot| !matches!(slot.kind, RegionKind::Band | RegionKind::Modal));
226 + let Some(at) = first_body else {
227 + return (screen.slots.iter().filter(is_band).collect(), Vec::new());
228 + };
229 + (
230 + screen.slots[..at].iter().filter(is_band).collect(),
231 + screen.slots[at..].iter().filter(is_band).collect(),
232 + )
233 + }
234 +
191 235 /// Every region the user can see, in the order it is drawn.
192 236 ///
193 237 /// What the focus walk reads. A region that is not drawn holds nothing
@@ -209,11 +253,11 @@
209 253 return modals;
210 254 }
211 255
212 - screen
213 - .slots
214 - .iter()
215 - .filter(|slot| matches!(slot.kind, RegionKind::Band))
256 + let (leading, trailing) = bands(screen);
257 + leading
258 + .into_iter()
216 259 .chain(body_slots(screen))
260 + .chain(trailing)
217 261 .collect()
218 262 }
219 263
@@ -103,6 +103,84 @@
103 103 rows(&buf)
104 104 }
105 105
106 + /// The row a piece of text was drawn on.
107 + fn row_of(rows: &[String], text: &str) -> Option<usize> {
108 + rows.iter().position(|row| row.contains(text))
109 + }
110 +
111 + #[test]
112 + fn a_band_said_after_the_body_is_drawn_under_it() {
113 + // Ruled by Max 2026-08-23 (quasicoherent 3725bacf): where a band was said
114 + // is which end it belongs to. This renderer hoisted every band before it,
115 + // so a status band drew above the content it belonged under while the same
116 + // description put it underneath in a webview.
117 + let screen = Screen::sidebar_content("Test")
118 + .with(Slot::new("bar", RegionKind::Band).with(Node::text("the toolbar")))
119 + .with(Slot::new("main", RegionKind::Pane).with(Node::text("the content")))
120 + .with(Slot::new("foot", RegionKind::Band).with(Node::text("the status")));
121 + let out = shown(&screen, 40, 12);
122 +
123 + let bar = row_of(&out, "the toolbar").expect("no toolbar drawn");
124 + let content = row_of(&out, "the content").expect("no content drawn");
125 + let foot = row_of(&out, "the status").expect("no status drawn");
126 + assert!(bar < content, "the toolbar left the top: {out:?}");
127 + assert!(
128 + content < foot,
129 + "the footer is above what it is the footer of: {out:?}"
130 + );
131 + }
132 +
133 + #[test]
134 + fn the_room_a_trailing_band_needs_is_kept_out_of_the_body() {
135 + // The failure a reservation prevents: a pane that fills its area leaves a
136 + // footer nowhere to go, and the band drawn into no rows is a band nobody
137 + // sees. The pane here holds more lines than the screen has.
138 + let mut pane = Slot::new("main", RegionKind::Pane);
139 + for line in 0..30 {
140 + pane = pane.with(Node::text(format!("line {line}")));
141 + }
142 + let screen = Screen::sidebar_content("Test")
143 + .with(pane)
144 + .with(Slot::new("foot", RegionKind::Band).with(Node::text("the status")));
145 + let out = shown(&screen, 40, 10);
146 +
147 + assert!(
148 + row_of(&out, "the status").is_some(),
149 + "the body took the footer's rows: {out:?}"
150 + );
151 + }
152 +
153 + #[test]
154 + fn the_band_said_last_is_the_one_at_the_bottom() {
155 + // Two trailing bands, which is audiofiles' shell: a migration strip above
156 + // the status band.
157 + let screen = Screen::sidebar_content("Test")
158 + .with(Slot::new("main", RegionKind::Pane).with(Node::text("the content")))
159 + .with(Slot::new("strip", RegionKind::Band).with(Node::text("the strip")))
160 + .with(Slot::new("foot", RegionKind::Band).with(Node::text("the status")));
161 + let out = shown(&screen, 40, 12);
162 +
163 + let strip = row_of(&out, "the strip").expect("no strip drawn");
164 + let foot = row_of(&out, "the status").expect("no status drawn");
165 + assert!(strip < foot, "the bands are upside down: {out:?}");
166 + }
167 +
168 + #[test]
169 + fn the_caret_reaches_a_trailing_band_in_the_order_it_is_drawn() {
170 + // The focus walk and the drawing have to agree about where a band is, or
171 + // the caret lights the footer before the content it sits under.
172 + let screen = Screen::sidebar_content("Test")
173 + .with(Slot::new("bar", RegionKind::Band).with(Node::text("the toolbar")))
174 + .with(Slot::new("main", RegionKind::Pane).with(Node::text("the content")))
175 + .with(Slot::new("foot", RegionKind::Band).with(Node::text("the status")));
176 +
177 + let order: Vec<&str> = crate::region::reachable(&screen)
178 + .iter()
179 + .map(|slot| slot.id.as_str())
180 + .collect();
181 + assert_eq!(order, ["bar", "main", "foot"]);
182 + }
183 +
106 184 #[test]
107 185 fn a_row_draws_its_run_in_the_order_the_description_says_it() {
108 186 // The property the containment migration bought this renderer: a terminal