Skip to main content

max / makeover-layout

A facet value carries an identifier as well as a label Choice's split, and a tree needs it for a reason a flat facet does not: two leaves under different parents are legitimately both called "Ambient", so the path is the only thing telling them apart -- and it is what nearest-ancestor-wins resolves over, so an app carrying only labels could not compute the Standing it hands back. FacetValue::of covers the flat case where the two are the same string. Caught while writing the webview emitter, which had nothing to key a value's route on.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-18 20:27 UTC
Signed with PGP, not checked
Commit: 4269eea73a525d3ced1ba91bff6eec96d432d5ab
Parent: 6d778c5
1 file changed, +35 insertions, -9 deletions
M src/lib.rs +35 -9
@@ -375,8 +375,12 @@
375 375 //! making one gesture do browsing and filtering together is what lets the
376 376 //! second mechanism go. [`Standing`] has four members rather than a bool for
377 377 //! the tree's sake — a value in force because an ancestor is, is not a value
378 - //! somebody picked. Deliberately wider than that one page: audiofiles' library
379 - //! browser and goingson's filters are the same shape.
378 + //! somebody picked. [`FacetValue`] splits an identifier from a label for
379 + //! [`Choice`]'s reason and one of its own: two leaves under different parents
380 + //! are legitimately both called "Ambient", and the path is what tells them
381 + //! apart and what nearest-ancestor-wins resolves over. Deliberately wider than
382 + //! that one page: audiofiles' library browser and goingson's filters are the
383 + //! same shape.
380 384 //!
381 385 //! # Reach, focus and the focus ring
382 386 //!
@@ -4200,7 +4204,19 @@
4200 4204 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4201 4205 #[non_exhaustive]
4202 4206 pub struct FacetValue<'a> {
4207 + /// What identifies it, and what a host keys its route on.
4208 + ///
4209 + /// [`Choice::value`]'s split, and a tree is why it is not optional: two
4210 + /// leaves under different parents are legitimately both called "Ambient",
4211 + /// and the path is the only thing telling them apart. It is also what
4212 + /// nearest-ancestor-wins resolves over, so an app that carried only labels
4213 + /// could not compute the [`standing`](Self::standing) it hands back here.
4214 + pub value: &'a str,
4203 4215 /// What it is called, as the user reads it.
4216 + ///
4217 + /// The leaf's own name rather than its path: a facet drawn as an indented
4218 + /// tree repeats every ancestor on every line otherwise, and one drawn as a
4219 + /// breadcrumb has the ancestors already.
4204 4220 pub label: &'a str,
4205 4221 /// How many members of the set carry it.
4206 4222 ///
@@ -4232,8 +4248,9 @@
4232 4248 impl<'a> FacetValue<'a> {
4233 4249 /// An unpicked value at the root of the facet.
4234 4250 #[must_use]
4235 - pub const fn new(label: &'a str) -> Self {
4251 + pub const fn new(value: &'a str, label: &'a str) -> Self {
4236 4252 Self {
4253 + value,
4237 4254 label,
4238 4255 count: None,
4239 4256 standing: Standing::Open,
@@ -4242,6 +4259,15 @@
4242 4259 }
4243 4260 }
4244 4261
4262 + /// A value whose identifier is also what the user reads.
4263 + ///
4264 + /// [`Choice::of`]'s convenience, and it is the flat case: a type or a tier
4265 + /// is its own name, and only a tree needs a path that is not one.
4266 + #[must_use]
4267 + pub const fn of(value: &'a str) -> Self {
4268 + Self::new(value, value)
4269 + }
4270 +
4245 4271 /// How many members carry it, when that was measured.
4246 4272 #[must_use]
4247 4273 pub const fn counted(mut self, count: u64) -> Self {
@@ -4390,10 +4416,10 @@
4390 4416 #[test]
4391 4417 fn a_facet_is_engaged_by_a_decision_and_not_by_an_inherited_value() {
4392 4418 let inherited = [
4393 - FacetValue::new("music")
4419 + FacetValue::of("music")
4394 4420 .standing(Standing::Taken)
4395 4421 .at(0, true),
4396 - FacetValue::new("synths")
4422 + FacetValue::new("music/synths", "synths")
4397 4423 .standing(Standing::Inherited)
4398 4424 .at(1, false),
4399 4425 ];
@@ -4403,8 +4429,8 @@
4403 4429 assert_eq!(facet.reach(), 1);
4404 4430
4405 4431 let untouched = [
4406 - FacetValue::new("music").at(0, true),
4407 - FacetValue::new("synths")
4432 + FacetValue::of("music").at(0, true),
4433 + FacetValue::new("music/synths", "synths")
4408 4434 .standing(Standing::Inherited)
4409 4435 .at(1, false),
4410 4436 ];
@@ -4423,8 +4449,8 @@
4423 4449 fn a_count_is_absent_rather_than_zero_when_it_was_not_measured() {
4424 4450 // Awaiting::amount's rule in a second place: a written zero reads as
4425 4451 // "none of them", which is a different claim from "not counted".
4426 - assert_eq!(FacetValue::new("Ambient").count, None);
4427 - assert_eq!(FacetValue::new("Ambient").counted(0).count, Some(0));
4452 + assert_eq!(FacetValue::of("Ambient").count, None);
4453 + assert_eq!(FacetValue::of("Ambient").counted(0).count, Some(0));
4428 4454 }
4429 4455
4430 4456 #[test]