Skip to main content

max / quasi

Close the collapse: one row type, one grammar, no alias Wave 3. `pub type Cells = Row` is gone, and deleting it was not the rename it looked like. `declare!` read the shape's return type as a token to pick the body form -- `-> Cells` for `cells { .. }`, `-> Row` for `row ".." { .. }` -- and emitted it verbatim into the generated signature. So the alias was load-bearing and the grammar had two spellings for one type. The return type was also redundant: the body already says which form it is, and the token had to agree with it, so a table row spelled `-> Row` was an error about the return type when nothing was wrong with it. One arm now takes both bodies, every shape returns `Row`, and the 16 `-> Cells` sites across the three app trees moved with it. `Of::TableRows` went the same way. It and `Of::Rows` named the same element type, so the enum that states "the element type is fixed by the container" was being read as a distinction it had stopped making. The rest is prose that argued from two containers: members documented as "on the other container", counterparts with nothing to be a counterpart to, and link definitions pointing through the alias. Three `Cells` survive and mean three different things: `Emission::Cells`, the `cells { .. }` body; `Of::Cells`, a row's cells; `Want::Cells`, terminal character cells. `Part` is gone entirely. Also four clippy findings that predate this work and were failing `--all-targets -D warnings` in quasi: a single-variant wildcard, a needless `for_each`, a missing `#[must_use]`, and a `HashMap` impl not generic over its hasher. The fourth was only reachable once the first two stopped the build. 27 test blocks green, clippy clean with -D warnings.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
Author: Max Johnson <me@maxj.phd> · 2026-09-06 17:05 UTC
Signed with PGP, not checked
Commit: ba358235b980266314621a84718ea64042270482
Parent: e458491
20 files changed, +130 insertions, -178 deletions
@@ -237,7 +237,7 @@
237 237 /// `table { column ..; cells { .. } }`
238 238 ///
239 239 /// Columns and rows accrete rather than arriving as two lists, which is
240 - /// what `Table::column` and `Cells::cell` were added to `quasi-router` for.
240 + /// what `Table::column` and `Row::cell` were added to `quasi-router` for.
241 241 /// The form has no expression to hold a list in, and a production that
242 242 /// admitted one would be the door this grammar exists to keep shut.
243 243 Table(Vec<Item>),
@@ -246,14 +246,14 @@
246 246 /// `cells { cell ..; }` -- one row of a table.
247 247 ///
248 248 /// A row's cells are all positional or all named; mixing the two is what
249 - /// `Cells::at`'s own docs warn against.
249 + /// `Row::at`'s own docs warn against.
250 250 Cells(Vec<Item>),
251 251 /// `cell [ "at" <column> ] <value> ( "{" .. "}" | ";" )`
252 252 ///
253 253 /// Positional without the column, named with it. `item_sales` demanded the
254 254 /// named form: its columns are module consts and its cells are a function
255 255 /// away, so a row written by position would be lined up against headings
256 - /// nobody reading the row can see. Naming is `Cells::at`, and the two are
256 + /// nobody reading the row can see. Naming is `Row::at`, and the two are
257 257 /// not mixed in one row -- that warning is `at`'s own.
258 258 Cell {
259 259 column: Option<Arg>,
@@ -133,7 +133,7 @@
133 133 /// `Vec<Act>`: a menu, which is the controls a row holds back.
134 134 ///
135 135 /// [`Nodes`](Self::Nodes)' twin and for its reason. `Row::menu` and
136 - /// `Cells::menu` take the whole list, so a menu that is built conditionally
136 + /// `Row::menu` takes the whole list, so a menu that is built conditionally
137 137 /// has nowhere to accrete, and audiofiles' file list has two of them --
138 138 /// what a row offers and what a chosen set does. Every member is an `act`,
139 139 /// because a menu is nothing but controls.
@@ -231,7 +231,7 @@
231 231 let shaped = match shaped {
232 232 Shaped::Nodes => return nodes(items),
233 233 Shaped::Acts => return acts(items),
234 - single => single,
234 + single @ Shaped::Single { .. } => single,
235 235 };
236 236 let Shaped::Single {
237 237 name: shaped,
@@ -327,21 +327,20 @@
327 327 ));
328 328 }
329 329 },
330 + // One arm, because there is one row type since the 2026-09-05 collapse.
331 + // The body says which spelling it is -- `row "primary" { .. }` names
332 + // roles of the default column set, `cells { .. }` names declared columns
333 + // -- and the return type was a second, redundant way to say the same
334 + // thing. It was worse than redundant: it had to agree with the body, so
335 + // a table row spelled `-> Row` was an error about the return type when
336 + // nothing was wrong with it.
330 337 "Row" => match only {
331 338 Emission::Row { primary, body } => row(Some(primary), body)?,
332 - other => {
333 - return Err(syn::Error::new(
334 - emission_span(other),
335 - "a `-> Row` shape is its single `row` member",
336 - ));
337 - }
338 - },
339 - "Cells" => match only {
340 339 Emission::Cells(body) => cells(body)?,
341 340 other => {
342 341 return Err(syn::Error::new(
343 342 emission_span(other),
344 - "a `-> Cells` shape is its single `cells` member",
343 + "a `-> Row` shape is its single `row` or `cells` member",
345 344 ));
346 345 }
347 346 },
@@ -180,7 +180,9 @@
180 180 fn walk_emission(emission: &Emission, found: &mut BTreeMap<String, proc_macro2::Span>) {
181 181 match emission {
182 182 Emission::Simple { args, .. } | Emission::Screen { args, .. } => {
183 - args.iter().for_each(|arg| walk_arg(arg, found));
183 + for arg in args {
184 + walk_arg(arg, found);
185 + }
184 186 }
185 187 Emission::Cell { value, .. } => walk_arg(value, found),
186 188 Emission::Row { primary, .. } => walk_arg(primary, found),
@@ -41,7 +41,7 @@
41 41 pub struct RowAt {
42 42 /// The row's own value, when the description gave it one.
43 43 ///
44 - /// `Row::value` and `Cells::value`, which is the identifier the description
44 + /// `Row::value`, which is the identifier the description
45 45 /// already uses for a row: it is what a tick travels under and what
46 46 /// survives a reorder. `None` on a row that carries none, where the index
47 47 /// is all there is.
@@ -147,7 +147,7 @@
147 147 /// `ticked` is the values the view holds ticked, which is **half** of what
148 148 /// [`Anchor::Selection`] resolves against -- the runtime owns that set and this
149 149 /// module does not, so it is passed in rather than reached for. The other half
150 - /// is `Cells::chosen`, a selection already in force, which the description
150 + /// is `Row::chosen`, a selection already in force, which the description
151 151 /// carries and which therefore arrived with the row.
152 152 ///
153 153 /// `None` means the anchor named nothing on this pass: a region or control that
@@ -266,17 +266,15 @@
266 266 /// rather than in the document. [`Screen::discovery`] is declined outright,
267 267 /// since nothing crawls a desktop window.
268 268 ///
269 - /// [`Row::address`] and [`Cells::address`] are declined too, and this says
270 - /// so rather than leaving it to be inferred: a member read by one renderer
271 - /// and silently ignored by two is how [`Cells::current`]'s drift began. An
269 + /// [`Row::address`] is declined too, and this says so rather than leaving
270 + /// it to be inferred: a member read by one renderer and silently ignored by
271 + /// two is how the row types drifted apart in the first place. An
272 272 /// address is where a document holds a row, which is a fact about having a
273 273 /// document and an address bar; a window has neither. The identity this
274 274 /// host does answer with is [`Row::value`], which `row_at` uses to say
275 275 /// which row the pointer is over.
276 276 ///
277 277 /// [`Row::address`]: quasi_router::Row::address
278 - /// [`Cells::address`]: quasi_router::Cells::address
279 - /// [`Cells::current`]: quasi_router::Cells::current
280 278 /// [`Row::value`]: quasi_router::Row::value
281 279 ///
282 280 /// `None` is the ordinary frame. egui redraws continuously, so most frames