Skip to main content

max / makeover-layout

0.39.0: an option carries what picking it means `Choice::detail`, the line under the label. Measured off the MNW server's un-ported markup: six option lists want it and four had already folded it into the label with `<strong>X</strong>: y` or a parenthetical. One member and not two. The site that asked carries a name, a price and a description, and the price loses on the count: the tree's other three priced tier lists are not option lists at all, so a price member would have one consumer and would mean growing a money type this crate does not have. Where it is drawn is `unavailable`'s question met a third time and takes the same answer, which is what says one member is the right size.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_0136sbU8F6i9WrcvA3wn4Lgk
Author: Max Johnson <me@maxj.phd> · 2026-08-29 15:39 UTC
Signed with PGP, not checked
Commit: 0856a240b7039805bae122881ca373205fa8589f
Parent: 058ff0e
2 files changed, +129 insertions, -1 deletion
M Cargo.toml +1 -1
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makeover-layout"
3 - version = "0.38.0"
3 + version = "0.39.0"
4 4 edition = "2024"
5 5 # One copy of this vocabulary per dependency graph, enforced by cargo rather
6 6 # than by remembering. Two versions of a description layer in one build means
M src/lib.rs +128
@@ -382,6 +382,37 @@
382 382 //! that one page: audiofiles' library browser and goingson's filters are the
383 383 //! same shape.
384 384 //!
385 + //! 0.39.0 gives an *option* its second line. [`Choice::detail`] is the sentence
386 + //! that says what picking one means, and it is one member and not two. Measured
387 + //! 2026-08-29 (`5e21dcfc`), off the MNW server's un-ported markup.
388 + //!
389 + //! - **Six sites, and four of them had already folded it into the label.**
390 + //! `<strong>Public</strong>: Anyone can see this repository` in git settings,
391 + //! the same shape in the project-basics AI tier and the cart's currency
392 + //! conversion, `Mislabeled (wrong AI tier or category)` in the report modal.
393 + //! The item-type and item-pricing wizards give it its own span. The described
394 + //! screens fold it in miniature: `Every 15 minutes (recommended)`. One fact,
395 + //! six spellings, and the folding is what says the member was missing.
396 + //! - **It is not a price**, and the count is what decided that rather than
397 + //! taste. The site that asked carries a name, a price and a description; the
398 + //! tree's other three priced tier lists are not option lists at all — each
399 + //! card carries its own submit, so each is a region with a heading, a fact
400 + //! and an act and is sayable today. A price member would have one consumer
401 + //! and would mean growing a money type this crate does not have.
402 + //! - **Where it is drawn is [`unavailable`](Choice::unavailable)'s question met
403 + //! a third time**, and it takes the same answer: its own element in a radio
404 + //! group, run into the option's own text in a `<select>`, a row of its own on
405 + //! a terminal. A rule the crate had already paid for twice is the cheapest
406 + //! evidence that one member is the right size.
407 + //! - **This does not reopen 0.35.0.** That release ruled [`Candidate`] a type
408 + //! of its own rather than a `Choice` with a second line, and it stays one:
409 + //! what separates them is that an option is picked out of a set the user can
410 + //! see whole and a candidate is offered out of one nobody can see. The
411 + //! argument 0.35.0 made against a member here — that it would land on every
412 + //! option list including the ones with nowhere to draw it — is answered by
413 + //! the two sentences above: it lands as `None` on the ones that do not ask,
414 + //! and the one host with nowhere to draw it already had a rule.
415 + //!
385 416 //! 0.38.0 describes the theme picker. [`FieldKind::Theme`], [`ThemeChoice`],
386 417 //! [`ThemeVariant`], [`Contrast`], [`Field::themes`] and [`Field::follows`] are
387 418 //! one screen's control named as furniture. Ruled by Max 2026-08-28
@@ -3922,6 +3953,62 @@
3922 3953 ///
3923 3954 /// Added 0.28.0, from audiofiles' instrument mode selector (`e761833e`).
3924 3955 pub unavailable: Option<&'a str>,
3956 + /// The line under the label that says what picking this means.
3957 + ///
3958 + /// `5e21dcfc`, measured 2026-08-29. A choice between three plans is a
3959 + /// choice nobody can make from three names, and until this existed the
3960 + /// description had nowhere to put the sentence that made it makeable. What
3961 + /// the corpus did instead is the tell: four of the six measured sites fold
3962 + /// it into the label — `<strong>Public</strong>: Anyone can see this
3963 + /// repository` in MNW's git settings, the same shape in its project-basics
3964 + /// AI tier and its cart's currency conversion, and `Mislabeled (wrong AI
3965 + /// tier or category)` in its report modal. The described screens do it too,
3966 + /// in miniature: `Every 15 minutes (recommended)`, `Reference samples in
3967 + /// place (loose-files mode)`. One fact, six spellings, no member.
3968 + ///
3969 + /// # Where it goes is the host's, and the rule already exists
3970 + ///
3971 + /// This is [`unavailable`](Self::unavailable)'s question met a third time
3972 + /// and it takes the same answer, which is the strongest evidence one member
3973 + /// is right rather than two. A radio group has room and gives the line its
3974 + /// own element beside the label. A `<select>`'s option takes no elements,
3975 + /// no second line and no title a keyboard reaches, so the line runs into
3976 + /// the option's own text — exactly as a precondition does, and as a theme's
3977 + /// contrast badge does in brackets. A terminal has rows and puts it on one
3978 + /// under the option.
3979 + ///
3980 + /// # Not a price, and that is a measurement rather than a preference
3981 + ///
3982 + /// The site that asked for this is MNW's fee calculator, whose tier cards
3983 + /// carry a name, a price *and* a description, so a second member for the
3984 + /// price was on the table. It loses on the count: the tree's other three
3985 + /// priced tier lists — `project.html`, `project_paywall.html`,
3986 + /// `index.html` — are not option lists at all. Each card carries its own
3987 + /// submit, which makes it a region with a heading, a fact and an act, and
3988 + /// it is sayable already. So a price member would have exactly one
3989 + /// consumer, and it would mean this crate growing a money type it does not
3990 + /// have: [`Unit`] is a time axis, and every amount in the described tree is
3991 + /// text.
3992 + ///
3993 + /// The price therefore leads the line: `$24/mo. 2GB/file, 100GB total.
3994 + /// Fits audio, plugins, binaries.` What would reopen it is a **second**
3995 + /// priced option list, not a judgement about how that reads.
3996 + ///
3997 + /// # What it is not
3998 + ///
3999 + /// Not [`unavailable`](Self::unavailable), which says the option cannot be
4000 + /// picked. This says what it means to pick it, and the two are drawn
4001 + /// together on an option that carries both: the description that says a
4002 + /// tier is out of stock *and* what the tier is has said two things.
4003 + ///
4004 + /// Not [`Field::hint`], which is standing help for the whole question, and
4005 + /// not markup. One line of plain text, for [`Candidate::detail`]'s reason:
4006 + /// an option list is a place a renderer lays out, and a description that
4007 + /// put a block in one would be handing every host a layout problem for the
4008 + /// benefit of one.
4009 + ///
4010 + /// Added 0.39.0.
4011 + pub detail: Option<&'a str>,
3925 4012 }
3926 4013
3927 4014 impl<'a> Choice<'a> {
@@ -3943,6 +4030,7 @@
3943 4030 value,
3944 4031 label,
3945 4032 unavailable: None,
4033 + detail: None,
3946 4034 }
3947 4035 }
3948 4036
@@ -3956,6 +4044,17 @@
3956 4044 self
3957 4045 }
3958 4046
4047 + /// The same option, with the line that says what picking it means.
4048 + ///
4049 + /// Builder-shaped for [`unless`](Self::unless)'s reason, and it is the
4050 + /// commoner of the two: six measured sites want this and one wants a
4051 + /// precondition. See [`detail`](Self::detail).
4052 + #[must_use]
4053 + pub const fn detailing(mut self, detail: &'a str) -> Self {
4054 + self.detail = Some(detail);
4055 + self
4056 + }
4057 +
3959 4058 /// Whether the option can be picked right now.
3960 4059 ///
3961 4060 /// The predicate a renderer branches on, so that "unavailable" is read as
@@ -6896,6 +6995,35 @@
6896 6995 assert_eq!(multi.label, "Multi-sample");
6897 6996 }
6898 6997
6998 + #[test]
6999 + fn an_option_can_say_what_picking_it_means_and_why_it_cannot_be_picked() {
7000 + // `5e21dcfc`. Two different sentences about one option, and an option
7001 + // that has both has said two things: what the tier is, and that it is
7002 + // not available yet. Folding them would be the label-folding this
7003 + // member exists to end.
7004 + let tier = Choice::new("24", "Small Files")
7005 + .detailing("$24/mo. 2GB/file, 100GB total. Fits audio, plugins, binaries.")
7006 + .unless("Sold out while the founder window is open.");
7007 +
7008 + assert_eq!(
7009 + tier.detail,
7010 + Some("$24/mo. 2GB/file, 100GB total. Fits audio, plugins, binaries.")
7011 + );
7012 + assert_eq!(
7013 + tier.unavailable,
7014 + Some("Sold out while the founder window is open.")
7015 + );
7016 + assert!(!tier.available());
7017 +
7018 + // Neither is implied by the other, which is what keeps a renderer from
7019 + // reading a detail as a reason: an ordinary option with a second line
7020 + // is still pickable.
7021 + let plain = Choice::new("free", "Free").detailing("No charge. Available to everyone.");
7022 + assert!(plain.available());
7023 + assert_eq!(plain.detail, Some("No charge. Available to everyone."));
7024 + assert_eq!(Choice::new("free", "Free").detail, None);
7025 + }
7026 +
6899 7027 #[test]
6900 7028 fn a_range_carries_both_ends_and_a_validated_number_need_not() {
6901 7029 // The distinction the kind exists for, asserted rather than only