Skip to main content

max / quasi

A member may settle itself under a guard, and MNW's chips do The settling rewrite covered a setting whose ARGUMENT carries the guarded setting -- `jumping ... { here when }`, `option ... { chosen when }` -- and missed the other place one turns up: a member whose own body settles it. MNW's analytics range selector is that, twice: `chip ... { latched when }`. Same answer, one production over. The member is built both ways and marked as two arms of itself, and the setting's guard becomes the arm the plan asks for. `Counters::swapping` is what carries that down: the setting reads it instead of refusing, and writes no fill of its own because the wrapper wrote one. Found by MNW rather than by the bench, which is the wrong way round, so the fixture is now there: a latched chip inside a loop, spliced into a region. That is the Select's problem stated on the other control -- exactly one chip differs and a residual holds one compiled loop body, so the difference has to be an arm inside that body.
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-08 21:09 UTC
Signed with PGP, not checked
Commit: b42a77aeb5b47a2c74c0ec5ccab7fc7b9815a39b
Parent: 226c1e4
2 files changed, +166 insertions, -0 deletions
@@ -51,6 +51,46 @@
51 51 .collect()
52 52 }
53 53
54 + declare! {
55 + /// A range selector: one chip per range, the current one held down.
56 + ///
57 + /// MNW's analytics screens are the site, twice. `latched` settles the tag
58 + /// its own statement produces, and it sits in a member's body rather than
59 + /// an argument's -- the other place a settling setting turns up, and the
60 + /// one that reaches through a loop.
61 + #[staged]
62 + pub(crate) shape ranges(chosen: &str) -> Vec<Node>;
63 +
64 + for range in RANGES {
65 + chip range.to_string() to get "/analytics?range={range}" navigating {
66 + latched when is_shown(range, chosen);
67 + }
68 + }
69 + }
70 +
71 + declare! {
72 + /// The region the chips are spliced into.
73 + ///
74 + /// A shape answering a run of members is derived through whatever holds
75 + /// them, never on its own: its twin answers `Staged<Vec<Node>>`, and the
76 + /// marks in that only mean anything once a container has said where the
77 + /// members landed.
78 + #[staged]
79 + pub(crate) shape range_bar(chosen: &str) -> Node;
80 +
81 + region "RANGES" as Pane {
82 + include each ranges(chosen);
83 + }
84 + }
85 +
86 + /// The ranges a selector offers.
87 + pub(crate) const RANGES: &[&str] = &["7d", "30d", "90d", "all"];
88 +
89 + /// Whether this chip is the one held down.
90 + pub(crate) fn is_shown(range: &str, chosen: &str) -> bool {
91 + range == chosen
92 + }
93 +
54 94 /// What the settings form was asked to draw.
55 95 ///
56 96 /// The disclosure is the fact a guarded field turns on: MNW's mail settings put
@@ -125,6 +165,24 @@
125 165 }
126 166
127 167 /// A guarded field is a branch, and the fields either side stay outside it.
168 + /// A settling setting inside a loop, which is one arm per pass.
169 + ///
170 + /// The Select's problem stated on the other control: exactly one chip
171 + /// differs and a residual holds one compiled loop body, so the difference
172 + /// has to be an arm inside that body rather than a branch beside it.
173 + #[test]
174 + fn a_latched_chip_is_an_arm_inside_the_loop() {
175 + let webview = Webview::new();
176 + let residual = quasi_webview::stage::derive(&Webview::new(), range_bar_staged);
177 + for chosen in ["7d", "all", "none of them"] {
178 + assert_eq!(
179 + webview.fragment(&range_bar(chosen)),
180 + range_bar_serve(&residual, chosen),
181 + "chosen {chosen}"
182 + );
183 + }
184 + }
185 +
128 186 #[test]
129 187 fn a_guarded_field_is_a_branch_in_the_form() {
130 188 let webview = Webview::new();
@@ -237,6 +237,13 @@
237 237 arms: u16,
238 238 /// The fill program under construction, innermost body last.
239 239 stack: Vec<Vec<Fill>>,
240 + /// The arm site of the member currently being built both ways, if any.
241 + ///
242 + /// Set while a member carrying a guarded settling setting is staged, and
243 + /// read by that setting: its guard becomes a read of this site's arm rather
244 + /// than a refusal, and it writes no fill of its own because the wrapper
245 + /// already wrote one.
246 + swapping: Option<u16>,
240 247 }
241 248
242 249 impl Counters {
@@ -407,6 +414,23 @@
407 414 body,
408 415 } => {
409 416 if let Some(guard) = guard {
417 + // The member around this one is being built both ways, and this
418 + // setting is what differs between them. Its guard becomes the
419 + // arm the plan asks for; the wrapper wrote the fill.
420 + if !placing(name)
421 + && let Some(site) = counters.swapping
422 + {
423 + return Ok(Item::Attribute {
424 + name: name.clone(),
425 + args: staged_args(name, args, counters)?,
426 + guard: Some(Guard {
427 + negated: false,
428 + predicate: Predicate::Truth(plan_read("swap", site)),
429 + span: guard.span,
430 + }),
431 + body: items(body, counters)?,
432 + });
433 + }
410 434 if !placing(name) {
411 435 return Err(syn::Error::new(
412 436 guard.span,
@@ -617,10 +641,94 @@
617 641 })],
618 642 }
619 643 }
644 + // A member whose own body settles it under a guard: `latched when x`
645 + // inside a chip, which is `chosen`'s case one production over. Built
646 + // both ways and marked as two arms of itself, exactly as a settling
647 + // setting on an argument is.
648 + Item::Emit(emission) if settling_guard(emission).is_some() => {
649 + let guard = settling_guard(emission).expect("just asked");
650 + let site = counters.arms;
651 + counters.arms += 1;
652 +
653 + // No scope of its own, for `Fill::Arms`'s reason: both arms' holes
654 + // are numbered at the level around them.
655 + let held = counters.swapping.replace(site);
656 + let staged = self::emission(emission, counters)?;
657 + counters.swapping = held;
658 + counters.wrote(Fill::Swap {
659 + guard: guard.clone(),
660 + });
661 +
662 + Item::Marked {
663 + site,
664 + varies: crate::ast::Varies::Arm {
665 + of: 2,
666 + taken: plan_arm(site, 2),
667 + },
668 + body: vec![Item::Emit(staged)],
669 + }
670 + }
620 671 Item::Emit(emission) => Item::Emit(self::emission(emission, counters)?),
621 672 })
622 673 }
623 674
675 + /// The one guarded settling setting in a member's own body, if it has one.
676 + ///
677 + /// A member may settle itself under a guard, which is what `latched when x`
678 + /// inside a chip is: what varies is the markup this statement produces rather
679 + /// than a run of members beside it, so the member becomes two arms of itself.
680 + ///
681 + /// Shallow on purpose. It reads the body this emission owns and no deeper: a
682 + /// guard further in belongs to whatever member IS further in, and that member
683 + /// is staged in its own right when the walk reaches it.
684 + fn settling_guard(emission: &Emission) -> Option<&Guard> {
685 + body_of(emission)?.iter().find_map(|item| match item {
686 + Item::Attribute {
687 + guard: Some(guard),
688 + name,
689 + ..
690 + } if !placing(name) => Some(guard),
691 + _ => None,
692 + })
693 + }
694 +
695 + /// The items a member's own body holds, for the emissions that have one.
696 + ///
697 + /// The three placement wrappers carry no body of their own and pass the
698 + /// question to what they place, which is the same reading every other pass over
699 + /// this grammar takes of them.
700 + fn body_of(emission: &Emission) -> Option<&[Item]> {
701 + Some(match emission {
702 + Emission::Simple { body, .. }
703 + | Emission::Chip { body, .. }
704 + | Emission::Screen { body, .. }
705 + | Emission::Row { body, .. }
706 + | Emission::Form { body, .. }
707 + | Emission::Field { body, .. }
708 + | Emission::Act { body, .. }
709 + | Emission::Offers { body, .. }
710 + | Emission::Column { body, .. }
711 + | Emission::Cell { body, .. }
712 + | Emission::Offering { body, .. }
713 + | Emission::Removes { body, .. }
714 + | Emission::Repeats { body, .. }
715 + | Emission::Region { body, .. }
716 + | Emission::Across { body, .. } => body,
717 + Emission::List(body) | Emission::Table(body) | Emission::Cells(body) => body,
718 + Emission::Framed { inner, .. }
719 + | Emission::Beside { inner, .. }
720 + | Emission::At { inner, .. } => {
721 + return body_of(inner);
722 + }
723 + Emission::Guarded { .. }
724 + | Emission::Given { .. }
725 + | Emission::Activate(_)
726 + | Emission::Include(_)
727 + | Emission::IncludeEach(_)
728 + | Emission::Link { .. } => return None,
729 + })
730 + }
731 +
624 732 /// One member's body, with its settling guard reading the plan's arm.
625 733 ///
626 734 /// Arm 0 is the setting made, which is [`Plan::swap`][swap]'s own reading and