max / quasi
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
5 files changed,
+83 insertions,
-14 deletions
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | [package] | |
| 2 | 2 | name = "quasi-router" | |
| 3 | - | version = "0.101.9" | |
| 3 | + | version = "0.101.10" | |
| 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 |
| @@ -164,7 +164,10 @@ | |||
| 164 | 164 | /// `screen <arrangement> <arg> { .. }` | |
| 165 | 165 | Screen { | |
| 166 | 166 | arrangement: Ident, | |
| 167 | - | title: Arg, | |
| 167 | + | /// What the arrangement is told. Every one takes a title; `list_detail` | |
| 168 | + | /// takes whether it is tabbed as well, which is why this is a list | |
| 169 | + | /// rather than the one title it was until wave 9. | |
| 170 | + | args: Vec<Arg>, | |
| 168 | 171 | body: Vec<Item>, | |
| 169 | 172 | }, | |
| 170 | 173 | /// `row <arg> { .. }` |
| @@ -262,14 +262,14 @@ | |||
| 262 | 262 | "Screen" => match only { | |
| 263 | 263 | Emission::Screen { | |
| 264 | 264 | arrangement, | |
| 265 | - | title, | |
| 265 | + | args, | |
| 266 | 266 | body, | |
| 267 | 267 | } => { | |
| 268 | - | let title = arg(title)?; | |
| 268 | + | let args = args.iter().map(self::arg).collect::<Result<Vec<_>>>()?; | |
| 269 | 269 | accrete( | |
| 270 | 270 | body, | |
| 271 | 271 | Container::Screen, | |
| 272 | - | "e!(::quasi_router::Screen::#arrangement(#title)), | |
| 272 | + | "e!(::quasi_router::Screen::#arrangement(#(#args),*)), | |
| 273 | 273 | )? | |
| 274 | 274 | } | |
| 275 | 275 | other => { | |
| @@ -523,6 +523,30 @@ | |||
| 523 | 523 | )?; | |
| 524 | 524 | Ok((run, quote!(.across(#held)))) | |
| 525 | 525 | } | |
| 526 | + | // A question standing in a region rather than in a form. A region that | |
| 527 | + | // consults gathers the dials it contains and sends them itself, so | |
| 528 | + | // there is no form to put them in and `Node::field` is what the | |
| 529 | + | // vocabulary has for it. `pricing`'s calculator is the site: five dials | |
| 530 | + | // and no submit button anywhere on the page. | |
| 531 | + | // | |
| 532 | + | // Narrow on purpose. Everywhere else a field is still refused with the | |
| 533 | + | // message that names the two legal homes, because a question in a cell | |
| 534 | + | // or a row is a question nothing will ever read. | |
| 535 | + | ( | |
| 536 | + | Container::Slot, | |
| 537 | + | Emission::Field { | |
| 538 | + | kind, | |
| 539 | + | name, | |
| 540 | + | label, | |
| 541 | + | body, | |
| 542 | + | }, | |
| 543 | + | ) => { | |
| 544 | + | let built = field(kind, name, label, body)?; | |
| 545 | + | Ok(( | |
| 546 | + | quote!(::quasi_router::Node::field(#built)), | |
| 547 | + | quote!(.with(#held)), | |
| 548 | + | )) | |
| 549 | + | } | |
| 526 | 550 | (Container::Slot, other) => Ok((node(other)?, quote!(.with(#held)))), | |
| 527 | 551 | (Container::Screen, Emission::Region { name, kind, body }) => { | |
| 528 | 552 | Ok((slot(name, kind, body)?, quote!(.with(#held)))) |
| @@ -76,6 +76,7 @@ | |||
| 76 | 76 | /// it. | |
| 77 | 77 | const NODE_MEMBERS: &[&str] = &[ | |
| 78 | 78 | "text", | |
| 79 | + | "toned", | |
| 79 | 80 | "page", | |
| 80 | 81 | "section", | |
| 81 | 82 | "subsection", | |
| @@ -549,22 +550,35 @@ | |||
| 549 | 550 | "a screen is laid out `single`, `list_detail` or `sidebar_content`", | |
| 550 | 551 | )); | |
| 551 | 552 | } | |
| 552 | - | let title: Arg = input.parse()?; | |
| 553 | + | // Every arrangement takes a title, and `list_detail` takes | |
| 554 | + | // whether it is tabbed as well. Read to the body rather than | |
| 555 | + | // taking exactly one, so the grammar says what the constructor | |
| 556 | + | // says. `pricing` is the site: the first converted screen that | |
| 557 | + | // is not `single`. | |
| 558 | + | let mut args = Vec::new(); | |
| 559 | + | while !input.peek(token::Brace) { | |
| 560 | + | args.push(input.parse()?); | |
| 561 | + | } | |
| 553 | 562 | Ok(Self::Screen { | |
| 554 | 563 | arrangement, | |
| 555 | - | title, | |
| 564 | + | args, | |
| 556 | 565 | body: block(input)?, | |
| 557 | 566 | }) | |
| 558 | 567 | } | |
| 559 | 568 | "row" => { | |
| 560 | 569 | let primary: Arg = input.parse()?; | |
| 570 | + | // Before the body, where `column`, `cell`, `act`, `offering` | |
| 571 | + | // and `form` already put theirs. `pricing`'s footer is the | |
| 572 | + | // site: Changelog is linked only while a published changelog | |
| 573 | + | // project exists, and the route 404s otherwise. | |
| 574 | + | let guard = guard(input)?; | |
| 561 | 575 | let body = if input.peek(token::Brace) { | |
| 562 | 576 | block(input)? | |
| 563 | 577 | } else { | |
| 564 | 578 | input.parse::<Token![;]>()?; | |
| 565 | 579 | Vec::new() | |
| 566 | 580 | }; | |
| 567 | - | Ok(Self::Row { primary, body }) | |
| 581 | + | Ok(guarded(guard, Self::Row { primary, body })) | |
| 568 | 582 | } | |
| 569 | 583 | // The guard goes after the body here rather than before it, because | |
| 570 | 584 | // the body is what tells this `list` from the node member of the | |
| @@ -667,18 +681,27 @@ | |||
| 667 | 681 | let kind: Ident = input.parse()?; | |
| 668 | 682 | let name: Arg = input.parse()?; | |
| 669 | 683 | let label: Arg = input.parse()?; | |
| 684 | + | // Before the body, where every other guarded member puts it. | |
| 685 | + | // `pricing` is the site: the founder-or-list question is asked | |
| 686 | + | // only while there are two rates to choose between, and with | |
| 687 | + | // the window shut the page is what it was before the question | |
| 688 | + | // existed. | |
| 689 | + | let guard = guard(input)?; | |
| 670 | 690 | let body = if input.peek(token::Brace) { | |
| 671 | 691 | block(input)? | |
| 672 | 692 | } else { | |
| 673 | 693 | input.parse::<Token![;]>()?; | |
| 674 | 694 | Vec::new() | |
| 675 | 695 | }; | |
| 676 | - | Ok(Self::Field { | |
| 677 | - | kind, | |
| 678 | - | name, | |
| 679 | - | label, | |
| 680 | - | body, | |
| 681 | - | }) | |
| 696 | + | Ok(guarded( | |
| 697 | + | guard, | |
| 698 | + | Self::Field { | |
| 699 | + | kind, | |
| 700 | + | name, | |
| 701 | + | label, | |
| 702 | + | body, | |
| 703 | + | }, | |
| 704 | + | )) | |
| 682 | 705 | } | |
| 683 | 706 | name if NODE_MEMBERS.contains(&name) => { | |
| 684 | 707 | let mut args = Vec::new(); |
| @@ -8680,6 +8680,25 @@ | |||
| 8680 | 8680 | } | |
| 8681 | 8681 | } | |
| 8682 | 8682 | ||
| 8683 | + | /// The same prose, saying something about itself. | |
| 8684 | + | /// | |
| 8685 | + | /// [`text`](Self::text) is this at [`layout::Tone::Neutral`], which is | |
| 8686 | + | /// ordinary content, and it was the only constructor `Text` had: a line | |
| 8687 | + | /// that is a warning wrote the variant out by hand. **21 sites in the tree | |
| 8688 | + | /// do**, nine of them in MNW's commit view alone, which makes this the most | |
| 8689 | + | /// repeated instance of the gap `Node::literal` and `Node::token` closed | |
| 8690 | + | /// before it. | |
| 8691 | + | /// | |
| 8692 | + | /// Not a notice. A notice is a thing that happened and carries a way out; | |
| 8693 | + | /// this is a sentence in the reading that is warning-coloured, which is | |
| 8694 | + | /// what a diff's deletion count and an unverifiable signature both are. | |
| 8695 | + | pub fn toned(text: impl Into<String>, tone: layout::Tone) -> Self { | |
| 8696 | + | Self::Text { | |
| 8697 | + | text: text.into(), | |
| 8698 | + | tone, | |
| 8699 | + | } | |
| 8700 | + | } | |
| 8701 | + | ||
| 8683 | 8702 | /// Machine text in a line of reading. | |
| 8684 | 8703 | /// | |
| 8685 | 8704 | /// One unclassified run, inline, no language: a ref path, a fingerprint, a |