Skip to main content

max / quasi

Give a list row and a table row one settings table in declare! Wave 1d. Container::Cells and Container::Row accreted onto the same type after the collapse, so the form was carrying two settings tables for one thing. A row now takes act, offers, include, activate, beside and cell in one place, and the error message says so once instead of twice. That is the grammar admitting what the type already knew: a row places its content by key, and the two spellings are the two kinds of key. `beside <part>` names a role of the default column set, `cell at <column>` names a declared one. fn row and fn cells were the same builder differing in whether a primary was named, which is an argument rather than a second function. `cells { .. }` is now `row(None, body)`. The list and table surface spellings are untouched, and every guard placement keeps its reason: list and table take their guard after the body, column before it, because git_repos shows a visibility column to the owner and the same guard decides the cell below. Node::List stays a distinct node, and that is a decision rather than an omission. The model is unified; the presentation is not, and it should not be. A feed of items is a list to a screen reader, and emitting role="table" over it would be a worse document, not a more consistent one. RowLook already carries that difference in the webview. Reversible if Max wants the nodes collapsed too. Production lines 1527 -> 1520. Total lines grew by five, which is documentation. emit.rs is still far over its 1000-line budget; that is 322ab65f, not this lane. Whole workspace suite green.
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 01:25 UTC
Signed with PGP, not checked
Commit: d1f2a1874eec5baeac9155ea044f514f13544825
Parent: 2c6ff89
1 file changed, +48 insertions, -43 deletions
@@ -40,8 +40,6 @@
40 40 Table,
41 41 /// One column: its body says how it narrows, and it emits nothing.
42 42 Column,
43 - /// One row of a table: its members are its cells, by position.
44 - Cells,
45 43 /// One cell: its body says what opening it does.
46 44 Cell,
47 45 /// One node member: its body says what it is told afterwards.
@@ -330,7 +328,7 @@
330 328 }
331 329 },
332 330 "Row" => match only {
333 - Emission::Row { primary, body } => row(primary, body)?,
331 + Emission::Row { primary, body } => row(Some(primary), body)?,
334 332 other => {
335 333 return Err(syn::Error::new(
336 334 emission_span(other),
@@ -650,7 +648,7 @@
650 648 "a document holds regions: write `region <name> as <kind> { .. }` \
651 649 or `include <shape>;`",
652 650 )),
653 - (Container::Cells | Container::Cell | Container::Row, Emission::Activate(action)) => {
651 + (Container::Cell | Container::Row, Emission::Activate(action)) => {
654 652 Ok((self::action(action)?, quote!(.activate(#held))))
655 653 }
656 654 (Container::Row, Emission::Act { .. }) => Ok((act(emission)?, quote!(.act(#held)))),
@@ -681,11 +679,36 @@
681 679 let part = variant(priority, "RowPart")?;
682 680 Ok((node(inner)?, quote!(.part(#part, #held))))
683 681 }
682 + // A cell names its column instead of taking a role, and that is the
683 + // whole of what a table row spells differently. One container since the
684 + // 2026-09-05 collapse, so this sits beside `beside` rather than in a
685 + // second settings table.
686 + (
687 + Container::Row,
688 + Emission::Cell {
689 + column,
690 + value,
691 + body,
692 + },
693 + ) => {
694 + let built = cell(value, body)?;
695 + Ok(match column {
696 + Some(column) => {
697 + let column = arg(column)?;
698 + (built, quote!(.at(#column, #held)))
699 + }
700 + None => (built, quote!(.cell(#held))),
701 + })
702 + }
703 + // One message, because there is one container. A row places its content
704 + // by key and the two spellings are the two kinds of key: `beside <part>`
705 + // names a role of the default column set, `cell at <column>` names a
706 + // declared column.
684 707 (Container::Row, other) => Err(syn::Error::new(
685 708 emission_span(other),
686 - "a row holds controls, places its parts and says what opening it does: write \
687 - `act <label> to <action>;`, `include <shape>;`, `beside <part> <emission>` \
688 - or `activate to <action>;`",
709 + "a row holds controls, places its content and says what opening it does: write \
710 + `act <label> to <action>;`, `include <shape>;`, `beside <part> <emission>`, \
711 + `cell [at <column>] <value>;` or `activate to <action>;`",
689 712 )),
690 713 (Container::Image, other) => Err(syn::Error::new(
691 714 emission_span(other),
@@ -743,27 +766,6 @@
743 766 "a table holds columns and rows: write `column <name>;`, `cells { .. }` \
744 767 or `include <shape>;`",
745 768 )),
746 - (
747 - Container::Cells,
748 - Emission::Cell {
749 - column,
750 - value,
751 - body,
752 - },
753 - ) => {
754 - let built = cell(value, body)?;
755 - Ok(match column {
756 - Some(column) => {
757 - let column = arg(column)?;
758 - (built, quote!(.at(#column, #held)))
759 - }
760 - None => (built, quote!(.cell(#held))),
761 - })
762 - }
763 - (Container::Cells, other) => Err(syn::Error::new(
764 - emission_span(other),
765 - "a row of a table holds cells: write `cell <value>;`",
766 - )),
767 769 (Container::Cell, Emission::Act { .. }) => Ok((act(emission)?, quote!(.act(#held)))),
768 770 // A control another shape built, as a row takes one. `item_files`'s
769 771 // acts column holds a Download written here and a Delete that is
@@ -1572,7 +1574,7 @@
1572 1574 /// for the same reason, and this is the list saying it the same way.
1573 1575 fn list_row(emission: &Emission) -> Result<TokenStream> {
1574 1576 match emission {
1575 - Emission::Row { primary, body } => row(primary, body),
1577 + Emission::Row { primary, body } => row(Some(primary), body),
1576 1578 Emission::Include(supplier) => {
1577 1579 let supplier = hole(supplier)?;
1578 1580 Ok(quote!(::std::convert::Into::into(#supplier)))
@@ -1610,13 +1612,9 @@
1610 1612 )
1611 1613 }
1612 1614
1613 - /// One row of a table, by position.
1615 + /// One row of a table, by position. `row` with no primary.
1614 1616 fn cells(body: &[Item]) -> Result<TokenStream> {
1615 - accrete(
1616 - body,
1617 - Container::Cells,
1618 - &quote!(<::quasi_router::screen::Cells as ::std::default::Default>::default()),
1619 - )
1617 + row(None, body)
1620 1618 }
1621 1619
1622 1620 /// One cell: what it says, what it holds, and what opening it does.
@@ -1629,14 +1627,21 @@
1629 1627 )
1630 1628 }
1631 1629
1632 - /// One row of a list: what it says, and what it offers.
1633 - fn row(primary: &Arg, body: &[Item]) -> Result<TokenStream> {
1634 - let primary = arg(primary)?;
1635 - accrete(
1636 - body,
1637 - Container::Row,
1638 - &quote!(::quasi_router::Row::new(#primary)),
1639 - )
1630 + /// One row: what it says, what it holds, and what it offers.
1631 + ///
1632 + /// One function since the 2026-09-05 collapse. `row "x" { .. }` and `cells {
1633 + /// .. }` were two builders onto what is now one type, differing only in whether
1634 + /// a primary was named: a list row opens with its primary text and a table row
1635 + /// opens empty and fills by key. That is an argument, not a second function.
1636 + fn row(primary: Option<&Arg>, body: &[Item]) -> Result<TokenStream> {
1637 + let base = match primary {
1638 + Some(primary) => {
1639 + let primary = arg(primary)?;
1640 + quote!(::quasi_router::Row::new(#primary))
1641 + }
1642 + None => quote!(<::quasi_router::Row as ::std::default::Default>::default()),
1643 + };
1644 + accrete(body, Container::Row, &base)
1640 1645 }
1641 1646
1642 1647 /// One control: what it is called, what it does, and what it is like.