Skip to main content

max / quasi

Let a meter stand on its own `Node::Meter` had no spelling, because `meter` is a setting on `Row` and on `Cell` and one name cannot mean both. goingson's task drawer wants one twice -- subtask completion, and time against estimate -- and MNW's `crossover_meter` was a supplier that existed only because this did not. The member is `proportion`, named for `RowPart::Proportion`, which is where `Row::meter` already puts one. Its body accretes onto the `Meter` the way `badge`'s accretes onto a `Tag`.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01MptwXZ8k65v19rFmdGAyki
Author: Max Johnson <me@maxj.phd> · 2026-09-04 20:00 UTC
Signed with PGP, not checked
Commit: 0c993e4947188beead38faa3f69d3a5ba3195910
Parent: 23c219d
2 files changed, +30 insertions, -0 deletions
@@ -54,6 +54,8 @@
54 54 Tag,
55 55 /// An axis: its members are `at`, each a placement and the row on it.
56 56 Timeline,
57 + /// One meter: its body says what it is like, and it emits nothing.
58 + Meter,
57 59 }
58 60
59 61 pub fn declaration(declaration: &Declaration) -> Result<TokenStream> {
@@ -598,6 +600,10 @@
598 600 emission_span(other),
599 601 "a tag holds nothing: its body says what it is like",
600 602 )),
603 + (Container::Meter, other) => Err(syn::Error::new(
604 + emission_span(other),
605 + "a meter holds nothing: its body says what it is like",
606 + )),
601 607 (Container::Run, Emission::Beside { priority, inner }) => Ok((
602 608 node(inner)?,
603 609 quote!(.beside(#held, ::quasi_router::layout::Priority::#priority)),
@@ -869,6 +875,20 @@
869 875 )?;
870 876 Ok(quote!(::quasi_router::Node::token(#tag)))
871 877 }
878 + Emission::Simple { member, args, body } if member == "proportion" => {
879 + // A meter standing on its own. `Node::Meter` holds a `Meter`, so
880 + // the body sets on the meter the way `badge`'s sets on a `Tag`, and
881 + // the member is named for the row part rather than for the type:
882 + // `meter` is a setting on `Row` and on `Cell`, so it cannot also be
883 + // a member.
884 + let args = args.iter().map(self::arg).collect::<Result<Vec<_>>>()?;
885 + let meter = accrete(
886 + body,
887 + Container::Meter,
888 + &quote!(::quasi_router::screen::Meter::new(#(#args),*)),
889 + )?;
890 + Ok(quote!(::quasi_router::Node::Meter(#meter)))
891 + }
872 892 Emission::Simple { member, args, body } if member == "underway" => {
873 893 // The one member whose name is not its constructor's. `Node::pending`
874 894 // is the third `StandIn` state, beside `empty` and `failed`, and
@@ -101,6 +101,15 @@
101 101 /// member under `Container::Timeline`. It is a `Node` either way, so nothing is
102 102 /// wrapped afterwards.
103 103 ///
104 + /// `proportion` is the seventh, and the second renamed rather than reshaped. It
105 + /// is `Node::Meter`, which holds a `Meter` builder the way `badge` holds a
106 + /// `Tag`, so the body accretes onto the meter and the emitter wraps the
107 + /// variant. It cannot be spelled `meter` because `Row::meter` and `Cell::meter`
108 + /// are settings; `Row` puts one in `RowPart::Proportion`, which is where the
109 + /// name comes from. goingson's task drawer is the site, twice -- subtask
110 + /// completion and time against estimate -- and MNW's `crossover_meter` is the
111 + /// supplier that existed only because this did not.
112 + ///
104 113 /// `underway` is the sixth, and the only one renamed rather than reshaped. It
105 114 /// is `Node::pending`, the third of the three `StandIn` states beside `empty`
106 115 /// and `failed`, and it cannot be spelled `pending` because `Slot::pending` is
@@ -133,6 +142,7 @@
133 142 "badge",
134 143 "timeline",
135 144 "underway",
145 + "proportion",
136 146 ];
137 147
138 148 /// The arrangements a screen may be laid out in.