Skip to main content

max / audiofiles

Take the unit off the label and put it on the value makeover-layout 0.33.0's `Field::unit`, decided by Max 2026-08-21. Four of this app's described controls carried their unit in parentheses at the end of the label because there was nowhere else to put it; they carry it as a unit now. The ADSR times take `s` through `envelope_field`, the forge threshold `dBFS` on both its described and its shipped copy, and the storage cap `GiB`. egui draws each inside the slider beside the readout, which is where these controls had it before they were described. Two label sites in `ui/classifier.rs` read "Duration (s)" and "Attack (s)" and are deliberately unchanged: they are option labels in the rule builder's field picker, naming which attribute a condition tests. They matched the text of this gap and are not sites of it -- what a `Field::label` says about the question is a different thing from what an option says about an attribute.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-22 00:30 UTC
Signed with PGP, not checked
Commit: b2f671996ab039cc0a8a20cfc528eb4ca969f6f0
Parent: e69fe55
7 files changed, +76 insertions, -45 deletions
M Cargo.lock +32 -32
@@ -3073,9 +3073,9 @@
3073 3073
3074 3074 [[package]]
3075 3075 name = "makeover-immediate"
3076 - version = "0.31.0"
3076 + version = "0.32.0"
3077 3077 source = "registry+https://github.com/rust-lang/crates.io-index"
3078 - checksum = "26c7990eecc3f9e21a39e0025500c67c9e5c54b69f9d4c6da630684fb20fe616"
3078 + checksum = "437b1e68f0d1ce824f458e623fc901e81f961b97f167c58c9400453f976fc175"
3079 3079 dependencies = [
3080 3080 "egui",
3081 3081 "egui_extras",
@@ -3084,9 +3084,9 @@
3084 3084
3085 3085 [[package]]
3086 3086 name = "makeover-layout"
3087 - version = "0.32.0"
3087 + version = "0.33.0"
3088 3088 source = "registry+https://github.com/rust-lang/crates.io-index"
3089 - checksum = "35c4934f537cbfb57946c0654ec579c874f80ea7465385bc65f6512c4550fefb"
3089 + checksum = "7ba9bae28e348ef6df04ddf04222703b5ea680ac2c978f0dce8679e2acbdc5a0"
3090 3090
3091 3091 [[package]]
3092 3092 name = "maplit"
@@ -4245,7 +4245,7 @@
4245 4245
4246 4246 [[package]]
4247 4247 name = "quasi-immediate"
4248 - version = "0.47.0"
4248 + version = "0.48.0"
4249 4249 dependencies = [
4250 4250 "docengine",
4251 4251 "egui",
@@ -4255,7 +4255,7 @@
4255 4255
4256 4256 [[package]]
4257 4257 name = "quasi-router"
4258 - version = "0.47.0"
4258 + version = "0.48.0"
4259 4259 dependencies = [
4260 4260 "makeover-layout",
4261 4261 ]
@@ -7545,9 +7545,33 @@
7545 7545 ]
7546 7546
7547 7547 [[patch.unused]]
7548 - name = "quasi-type"
7548 + name = "quasi-axum"
7549 + version = "0.48.0"
7550 +
7551 + [[patch.unused]]
7552 + name = "quasi-basics"
7553 + version = "0.48.0"
7554 +
7555 + [[patch.unused]]
7556 + name = "quasi-http"
7557 + version = "0.48.0"
7558 +
7559 + [[patch.unused]]
7560 + name = "quasi-notifs"
7561 + version = "0.48.0"
7562 +
7563 + [[patch.unused]]
7564 + name = "quasi-store"
7549 7565 version = "0.1.0"
7550 7566
7567 + [[patch.unused]]
7568 + name = "quasi-tauri"
7569 + version = "0.48.0"
7570 +
7571 + [[patch.unused]]
7572 + name = "quasi-webview"
7573 + version = "0.48.0"
7574 +
7551 7575 [[patch.unused]]
7552 7576 name = "kberg"
7553 7577 version = "0.1.0"
@@ -7561,29 +7585,5 @@
7561 7585 version = "0.1.0"
7562 7586
7563 7587 [[patch.unused]]
7564 - name = "quasi-axum"
7565 - version = "0.47.0"
7566 -
7567 - [[patch.unused]]
7568 - name = "quasi-basics"
7569 - version = "0.47.0"
7570 -
7571 - [[patch.unused]]
7572 - name = "quasi-http"
7573 - version = "0.47.0"
7574 -
7575 - [[patch.unused]]
7576 - name = "quasi-notifs"
7577 - version = "0.47.0"
7578 -
7579 - [[patch.unused]]
7580 - name = "quasi-store"
7588 + name = "quasi-type"
7581 7589 version = "0.1.0"
7582 -
7583 - [[patch.unused]]
7584 - name = "quasi-tauri"
7585 - version = "0.47.0"
7586 -
7587 - [[patch.unused]]
7588 - name = "quasi-webview"
7589 - version = "0.47.0"
M Cargo.toml +4 -4
@@ -17,12 +17,12 @@
17 17 # to compile against an API added in a later one. The two move together --
18 18 # makeover-immediate re-exports nothing, so the `Column` the app describes and
19 19 # the `Column` the renderer matches on have to be the same type.
20 - makeover-layout = "0.32.0"
21 - makeover-immediate = "0.31.0"
20 + makeover-layout = "0.33.0"
21 + makeover-immediate = "0.32.0"
22 22 # The described screens, behind audiofiles-browser's `quasi` feature. By git URL
23 23 # with a version requirement, per the tree's rule for cross-repo deps.
24 - quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.47" }
25 - quasi-immediate = { git = "https://makenot.work/git/max/quasi.git", version = "0.47" }
24 + quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.48" }
25 + quasi-immediate = { git = "https://makenot.work/git/max/quasi.git", version = "0.48" }
26 26 egui = { version = "0.35", default-features = false, features = ["default_fonts"] }
27 27 egui_extras = { version = "0.35", default-features = false }
28 28 eframe = { version = "0.35", default-features = false, features = ["default_fonts", "glow"] }
@@ -382,7 +382,8 @@
382 382
383 383 group
384 384 .with(Node::Field(Box::new(
385 - Field::range(Knob::Threshold.as_str(), "Threshold (dBFS)", "-96", "-20")
385 + Field::range(Knob::Threshold.as_str(), "Threshold", "-96", "-20")
386 + .unit("dBFS")
386 387 .step("1")
387 388 .value(format!("{:.0}", forging.threshold_db))
388 389 .changes(writes(Knob::Threshold)),
@@ -716,7 +716,8 @@
716 716 let field = Field {
717 717 min: Some(gib_count(pricing.min_bytes).to_string()),
718 718 max: Some(gib_count(pricing.max_bytes).to_string()),
719 - ..Field::new(FieldKind::Number, CAP, "Storage cap (GiB)")
719 + unit: Some("GiB".to_owned()),
720 + ..Field::new(FieldKind::Number, CAP, "Storage cap")
720 721 }
721 722 .value(gib_count(proposed).to_string())
722 723 .required()
@@ -7624,6 +7624,29 @@
7624 7624 assert_eq!(several.asked(), ["trim"]);
7625 7625 }
7626 7626
7627 + #[test]
7628 + fn a_measured_control_names_its_unit_rather_than_hiding_it_in_the_label() {
7629 + // makeover-layout 0.33.0, decided by Max 2026-08-21. The label is the
7630 + // question's name and the unit is a fact about the value, so a reader of
7631 + // this description gets `-96` and `dBFS` as two answers rather than one
7632 + // string it would have to parse the second out of.
7633 + // The batch trim appears once more than one sample is picked, which is what
7634 + // carries the threshold.
7635 + let forge = FakeForge::with(Forging {
7636 + chosen: 3,
7637 + ..forging()
7638 + });
7639 + let response = forged(&forge, Request::get("/forge")).unwrap();
7640 +
7641 + let threshold = deep_fields(&screen_of(&response))
7642 + .into_iter()
7643 + .find(|field| field.name == "threshold")
7644 + .expect("the threshold is described");
7645 + assert_eq!(threshold.label, "Threshold");
7646 + assert_eq!(threshold.unit.as_deref(), Some("dBFS"));
7647 + assert!(threshold.kind.measurable());
7648 + }
7649 +
7627 7650 #[test]
7628 7651 fn one_write_route_serves_five_controls_across_two_sections() {
7629 7652 let forge = FakeForge::with(Forging {
@@ -394,7 +394,8 @@
394 394 curve: makeover_layout::Curve::Linear {
395 395 step: Some(TRIM_THRESHOLD_STEP),
396 396 },
397 - ..makeover_layout::Field::range("trim_threshold", "Threshold (dBFS)", "-96", "-20")
397 + unit: Some("dBFS"),
398 + ..makeover_layout::Field::range("trim_threshold", "Threshold", "-96", "-20")
398 399 };
399 400 let mut text = format!("{:.0}", state.forge.trim_threshold_db);
400 401 widgets::field(
@@ -641,6 +641,10 @@
641 641 let described = makeover_layout::Field {
642 642 hint: Some(hint),
643 643 curve: makeover_layout::Curve::Logarithmic { step: Some(step) },
644 + // Seconds, on the value rather than in the label. egui draws it as the
645 + // slider's suffix, beside the readout, which is where these controls had
646 + // it before they were described.
647 + unit: Some("s"),
644 648 ..makeover_layout::Field::range(name, label, &min_text, &max_text)
645 649 };
646 650 let mut text = format!("{value:.3}");
@@ -712,9 +716,10 @@
712 716 // short one: a 5 ms attack and a 50 ms attack are audibly different
713 717 // instruments, and a linear track puts both inside its first one percent.
714 718 //
715 - // The second is open (makeover-layout `32215e21`), so the unit is in the
716 - // label. That is the convention three fields in this app already arrived at
717 - // independently, and it is what the gap exists to ratify or replace.
719 + // The second is closed too, as of makeover-layout 0.33.0: `Field::unit`,
720 + // decided by Max 2026-08-21 against the eight sites that had each put the
721 + // unit in parentheses at the end of the label. These three carry `s` on the
722 + // value, and egui draws it inside the slider beside the readout.
718 723 //
719 724 // Two per row, each keeping its own label above its own bar: the
720 725 // classifier's paired thresholds, which is the shape this app uses whenever
@@ -723,7 +728,7 @@
723 728 envelope_field(
724 729 ui,
725 730 "attack",
726 - "Attack (s)",
731 + "Attack",
727 732 "Time to reach full volume after key press.",
728 733 TIME_STEP,
729 734 0.001,
@@ -733,7 +738,7 @@
733 738 envelope_field(
734 739 ui,
735 740 "decay",
736 - "Decay (s)",
741 + "Decay",
737 742 "Time to fall from peak to the sustain level.",
738 743 TIME_STEP,
739 744 0.001,
@@ -767,7 +772,7 @@
767 772 envelope_field(
768 773 ui,
769 774 "release",
770 - "Release (s)",
775 + "Release",
771 776 "Time to fade to silence after key release.",
772 777 TIME_STEP,
773 778 0.001,