Skip to main content

max / quasi

Grow the form for five more MNW panels, and take copying out of the modifier table Two productions across five files, which is the first wave where most files cost nothing: - a cell holds a run of leaves, so a node member in a cell body is `Cell::part` (git_blame's commit cell carries the short oid and a link to the commit's notes; feeds' name cell carries the creator under the name). A control reached by `include` is still `Cell::act`. - a `list { .. }` takes a guard, after the body rather than before it, because the body is what tells this `list` from the node member of the same name (user_projects draws its list only when there is something in it) `copying` was in the modifier table and is `Act::copying`, a setting on the control that sets the destination to `Action::local` itself. Exactly what happened to `filling` in wave 3, and collections' Copy link is the screen that would have failed against `Action`. The table now says what belongs on it: a word goes there only if `Action` has the method. `token` went into the node members and came back out. `Node::Token` looks like a member; `Row::token` and `Cell::token` are settings at four converted sites, and an ident inside a body is a member if it is in the member list and a setting if it is not. One name cannot mean both, so the ruling is written on `NODE_MEMBERS` and a screen wanting a tag on its own reaches `Node::token` through a supplier. quasi-router 0.101.8 closes three more gaps, all the shape every earlier one had: `Node::figure`, the accreting half of `Node::stats` and the fourth after `Table::column`, `Cells::cell` and `Field::options`; `Node::token`, the last node variant with no constructor; and `RegionKind::ceded`, beside `RegionKind::handover` and for its reason.
Author: Max Johnson <me@maxj.phd> · 2026-09-03 22:38 UTC
Signed with PGP, not checked
Commit: c4a24347ef4d94c51f8910cfb37c65ddd9903d1f
Parent: eafe6e4
4 files changed, +72 insertions, -11 deletions
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-router"
3 - version = "0.101.7"
3 + version = "0.101.8"
4 4 description = "Host-agnostic router: a request in, a renderer-agnostic description out"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -605,11 +605,12 @@
605 605 quote!(.act(#held)),
606 606 ))
607 607 }
608 - (Container::Cell, other) => Err(syn::Error::new(
609 - emission_span(other),
610 - "a cell holds controls and says what opening it does: write \
611 - `act <label> to <action>;`, `include <shape>;` or `activate to <action>;`",
612 - )),
608 + // A cell is a run of leaves, which is what `Cell::part` is for.
609 + // `git_blame`'s commit cell carries the short oid and, when the commit
610 + // has annotations, a second link to its notes; its code cell carries the
611 + // line. A control is still `.act`, so a supplier reached by `include`
612 + // stays a control and a node member is a part.
613 + (Container::Cell, other) => Ok((node(other)?, quote!(.part(#held)))),
613 614 (
614 615 Container::Node,
615 616 Emission::Offering {
@@ -22,9 +22,12 @@
22 22
23 23 /// The modifiers, and how many arguments each takes.
24 24 ///
25 - /// `filling` is deliberately not here. It is `Act::filling`, a setting on the
26 - /// control, and listing it as a modifier of the action made `media_picker`'s
27 - /// tiles fail against `Action` instead of reading as the setting they are.
25 + /// **Two words were here that are settings on the control rather than modifiers
26 + /// of the action, and both were caught by a screen rather than by reading.**
27 + /// `filling` is `Act::filling` and made `media_picker`'s tiles fail against
28 + /// `Action`; `copying` is `Act::copying`, which sets the destination to
29 + /// `Action::local` itself, and `collections`' Copy link is the site. A word
30 + /// belongs here only if `Action` has the method.
28 31 const MODIFIERS: &[(&str, usize)] = &[
29 32 ("navigating", 0),
30 33 ("awaiting", 0),
@@ -34,7 +37,6 @@
34 37 ("replacing_enclosing", 0),
35 38 ("saving", 1),
36 39 ("replacing", 1),
37 - ("copying", 1),
38 40 ("carrying", 2),
39 41 ];
40 42
@@ -51,6 +53,15 @@
51 53
52 54 /// The members that are one of `Node`'s own constructors, called by its name.
53 55 /// Growing this list is the whole of adding one.
56 + ///
57 + /// **A name that is a setting on any container cannot go here.** An ident inside
58 + /// a body is a member if it is in one of these two lists and a setting if it is
59 + /// not, and that is the whole of how the two are told apart, so one name cannot
60 + /// mean both. `token` is the measured case: `Node::Token` looks like a member,
61 + /// and `Row::token` and `Cell::token` are settings at four converted sites. A
62 + /// screen wanting a tag standing on its own reaches `Node::token` through a
63 + /// supplier, which is the remedy the deferred table already names, and
64 + /// `project_analytics`'s `chip` is that supplier.
54 65 const NODE_MEMBERS: &[&str] = &[
55 66 "text",
56 67 "page",
@@ -530,7 +541,18 @@
530 541 };
531 542 Ok(Self::Row { primary, body })
532 543 }
533 - "list" if input.peek(token::Brace) => Ok(Self::List(block(input)?)),
544 + // The guard goes after the body here rather than before it, because
545 + // the body is what tells this `list` from the node member of the
546 + // same name. `user_projects` draws its list only when there is
547 + // something in it.
548 + "list" if input.peek(token::Brace) => {
549 + let rows = block(input)?;
550 + let guard = guard(input)?;
551 + if guard.is_some() {
552 + input.parse::<Token![;]>()?;
553 + }
554 + Ok(guarded(guard, Self::List(rows)))
555 + }
534 556 "table" => Ok(Self::Table(block(input)?)),
535 557 "column" => {
536 558 let name: Arg = input.parse()?;
@@ -4193,6 +4193,17 @@
4193 4193 Self::Handover { name: name.into() }
4194 4194 }
4195 4195
4196 + /// A place the app fills itself, that no host is owed a fill for.
4197 + ///
4198 + /// [`Slot::ceded`]'s kind on its own, for the same reason [`handover`] has
4199 + /// one.
4200 + ///
4201 + /// [`handover`]: Self::handover
4202 + #[must_use]
4203 + pub fn ceded(name: impl Into<String>) -> Self {
4204 + Self::Ceded { name: name.into() }
4205 + }
4206 +
4196 4207 /// Borrow as the description layer's own type.
4197 4208 #[must_use]
4198 4209 pub fn as_layout(&self) -> layout::Region<'_> {
@@ -8835,6 +8846,33 @@
8835 8846 }
8836 8847 }
8837 8848
8849 + /// One more figure, beside [`stats`](Self::stats).
8850 + ///
8851 + /// `stats` takes the whole list, and every other container in this
8852 + /// vocabulary accretes: `Table::column`, `Cells::cell` and `Field::options`
8853 + /// were all added for that reason and this is the fourth. A caller building
8854 + /// figures one at a time, or conditionally, has nowhere to hold the list.
8855 + ///
8856 + /// Does nothing to a node that is not [`Stats`](Self::Stats), on
8857 + /// [`trust`](Self::trust)'s rule.
8858 + #[must_use]
8859 + pub fn figure(mut self, figure: Figure) -> Self {
8860 + if let Self::Stats { figures } = &mut self {
8861 + figures.push((figure, None));
8862 + }
8863 + self
8864 + }
8865 +
8866 + /// A tag standing on its own, rather than inside a row or a cell.
8867 + ///
8868 + /// [`Token`](Self::Token) was the last node variant with no constructor of
8869 + /// its own once [`literal`](Self::literal) landed. A chip that is a control
8870 + /// carries its action and its latched state on the tag.
8871 + #[must_use]
8872 + pub fn token(tag: Tag) -> Self {
8873 + Self::Token(tag)
8874 + }
8875 +
8838 8876 /// Time counting up from an instant.
8839 8877 #[must_use]
8840 8878 pub const fn since(at: std::time::SystemTime) -> Self {