max / quasi
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
3 files changed,
+77 insertions,
-16 deletions
| @@ -6314,10 +6314,6 @@ | |||
| 6314 | 6314 | "winnow 1.0.4", | |
| 6315 | 6315 | ] | |
| 6316 | 6316 | ||
| 6317 | - | [[patch.unused]] | |
| 6318 | - | name = "synckit-client" | |
| 6319 | - | version = "0.10.0" | |
| 6320 | - | ||
| 6321 | 6317 | [[patch.unused]] | |
| 6322 | 6318 | name = "kberg" | |
| 6323 | 6319 | version = "0.1.0" | |
| @@ -6337,3 +6333,7 @@ | |||
| 6337 | 6333 | [[patch.unused]] | |
| 6338 | 6334 | name = "quasi-type" | |
| 6339 | 6335 | version = "0.1.3" | |
| 6336 | + | ||
| 6337 | + | [[patch.unused]] | |
| 6338 | + | name = "synckit-client" | |
| 6339 | + | version = "0.10.0" |
| @@ -22,11 +22,54 @@ | |||
| 22 | 22 | #![allow(dead_code)] | |
| 23 | 23 | ||
| 24 | 24 | use quasi_declare::declare; | |
| 25 | + | use quasi_router::Action; | |
| 26 | + | use quasi_router::screen::{Jump, Rest}; | |
| 25 | 27 | ||
| 26 | 28 | /// What the panel read. | |
| 27 | 29 | pub(crate) struct Feed { | |
| 28 | 30 | pub heading: String, | |
| 29 | 31 | pub items: Vec<String>, | |
| 32 | + | /// The pages the strip offers, which is the description's own window. | |
| 33 | + | pub pages: Vec<usize>, | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | impl Feed { | |
| 37 | + | /// Whether this is the page the reader is on. | |
| 38 | + | /// | |
| 39 | + | /// A fixed answer rather than a field, because what the strip needs is one | |
| 40 | + | /// jump differing from its siblings and which one is not interesting here. | |
| 41 | + | pub(crate) fn here(&self, page: usize) -> bool { | |
| 42 | + | page == self.pages.first().copied().unwrap_or_default() | |
| 43 | + | } | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | declare! { | |
| 47 | + | /// A table of the feed's items, in a shape of its own. | |
| 48 | + | /// | |
| 49 | + | /// The third level MNW's feed has and this fixture did not: the loop is | |
| 50 | + | /// not in the spliced shape, it is in a shape that spliced shape INCLUDES, | |
| 51 | + | /// under a guard. So the branch is numbered in one scope and the loop | |
| 52 | + | /// inside it in another, and the marks reach the region through two | |
| 53 | + | /// boundaries rather than one. | |
| 54 | + | #[staged] | |
| 55 | + | pub(crate) shape rows(feed: &Feed) -> Node; | |
| 56 | + | ||
| 57 | + | list { | |
| 58 | + | for item in feed.items.iter() { | |
| 59 | + | row "{item}"; | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // A described pager, which is a loop inside an argument's body rather | |
| 63 | + | // than inside a container's. MNW's feed has one here, and it is the | |
| 64 | + | // last structural feature this fixture was missing. | |
| 65 | + | more Rest::page(0, 10).of(feed.items.len()) { | |
| 66 | + | for &page in feed.pages.iter() { | |
| 67 | + | jumping Jump::new(page, Action::get("/feed?page={page}").navigating()) { | |
| 68 | + | here when feed.here(page); | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | } | |
| 72 | + | } | |
| 30 | 73 | } | |
| 31 | 74 | ||
| 32 | 75 | declare! { | |
| @@ -43,9 +86,9 @@ | |||
| 43 | 86 | ||
| 44 | 87 | empty "Nothing here yet." when feed.items.is_empty(); | |
| 45 | 88 | ||
| 46 | - | for item in feed.items.iter() { | |
| 47 | - | text "{item}"; | |
| 48 | - | } | |
| 89 | + | // A guarded include of a shape that loops, which is the feed's shape and | |
| 90 | + | // the one that was never exercised. | |
| 91 | + | include rows(feed) unless feed.items.is_empty(); | |
| 49 | 92 | } | |
| 50 | 93 | ||
| 51 | 94 | declare! { | |
| @@ -74,6 +117,7 @@ | |||
| 74 | 117 | Feed { | |
| 75 | 118 | heading: heading.to_owned(), | |
| 76 | 119 | items: items.iter().map(|item| (*item).to_owned()).collect(), | |
| 120 | + | pages: vec![1, 2, 3], | |
| 77 | 121 | } | |
| 78 | 122 | } | |
| 79 | 123 | ||
| @@ -113,8 +157,10 @@ | |||
| 113 | 157 | let residual = residual(); | |
| 114 | 158 | let (mut branches, mut loops) = (0, 0); | |
| 115 | 159 | count(residual.ops(), &mut branches, &mut loops); | |
| 116 | - | assert_eq!(branches, 2, "{:#?}", residual.ops()); | |
| 117 | - | assert_eq!(loops, 1, "{:#?}", residual.ops()); | |
| 160 | + | // Two guards in the spliced shape, one loop over the rows the shape it | |
| 161 | + | // includes draws, and one over the pager's jumps. | |
| 162 | + | assert_eq!(branches, 3, "{:#?}", residual.ops()); | |
| 163 | + | assert_eq!(loops, 2, "{:#?}", residual.ops()); | |
| 118 | 164 | ||
| 119 | 165 | // And the caller's own members are outside all of it. | |
| 120 | 166 | let settled: String = residual |
| @@ -410,9 +410,10 @@ | |||
| 410 | 410 | let mut arms = Vec::with_capacity(of.saturating_sub(1)); | |
| 411 | 411 | for arm in 1..of { | |
| 412 | 412 | let (drawn, covers) = render(&plan.clone().with_arm_at(cover.scope, cover.id, arm)); | |
| 413 | - | let Some(here) = covers | |
| 413 | + | let Some((at, here)) = covers | |
| 414 | 414 | .iter() | |
| 415 | - | .find(|other| other.scope == cover.scope && other.id == cover.id) | |
| 415 | + | .enumerate() | |
| 416 | + | .find(|(_, other)| other.scope == cover.scope && other.id == cover.id) | |
| 416 | 417 | else { | |
| 417 | 418 | panic!( | |
| 418 | 419 | "dispatch {:x}/{} drew arm {arm} nowhere, so the screen has an arm \ | |
| @@ -427,7 +428,7 @@ | |||
| 427 | 428 | cover.scope, | |
| 428 | 429 | cover.id | |
| 429 | 430 | ); | |
| 430 | - | let inside = spans_within(&covers, here); | |
| 431 | + | let inside = spans_within(&covers, at); | |
| 431 | 432 | arms.push(build(&drawn, here.at, here.to, &inside, &mut 0)); | |
| 432 | 433 | } | |
| 433 | 434 | arms | |
| @@ -438,16 +439,30 @@ | |||
| 438 | 439 | /// A nested dispatch inside an arm is left alone: it is one more site with its | |
| 439 | 440 | /// own arms, and it is compiled when the walk reaches it in the render it was | |
| 440 | 441 | /// drawn in. | |
| 441 | - | fn spans_within(covers: &[Cover], outer: &Cover) -> Vec<Span> { | |
| 442 | + | fn spans_within(covers: &[Cover], at: usize) -> Vec<Span> { | |
| 443 | + | let outer = &covers[at]; | |
| 442 | 444 | covers | |
| 443 | 445 | .iter() | |
| 444 | - | .filter(|cover| { | |
| 445 | - | !std::ptr::eq(*cover, outer) | |
| 446 | + | .enumerate() | |
| 447 | + | .filter(|(index, cover)| { | |
| 448 | + | // Recorded EARLIER than the arm, which is the same rule `tree` uses | |
| 449 | + | // and the reason it is needed here. A container adds its marks | |
| 450 | + | // innermost first, so a cover recorded after this one encloses it | |
| 451 | + | // rather than sitting inside it -- and range alone cannot tell the | |
| 452 | + | // two apart, because a loop running one pass covers exactly the | |
| 453 | + | // bytes of the single member that pass drew. | |
| 454 | + | // | |
| 455 | + | // MNW's feed is the site: a pager's jump is one member, the loop | |
| 456 | + | // over jumps runs once while staging, and the `here` setting makes | |
| 457 | + | // an arm of that same member. Adopting the loop as the arm's child | |
| 458 | + | // put a `Loop` inside an `Arms`, which the fill program has no | |
| 459 | + | // instruction for. | |
| 460 | + | *index < at | |
| 446 | 461 | && cover.at >= outer.at | |
| 447 | 462 | && cover.to <= outer.to | |
| 448 | 463 | && (cover.scope, cover.id) != (outer.scope, outer.id) | |
| 449 | 464 | }) | |
| 450 | - | .map(|cover| Span { | |
| 465 | + | .map(|(_, cover)| Span { | |
| 451 | 466 | at: cover.at, | |
| 452 | 467 | to: cover.to, | |
| 453 | 468 | varies: cover.varies, |