Skip to main content

max / audiofiles

Take Field::writes quasi 0.76.0 renamed Field::changes. Mechanical across the 47 call sites, no control's behaviour touched. Three local helpers were already named `writes` and returned the route a control posts to, so the rename read as `.writes(writes(knob))`. They say what they return now: `knob_route`, `setting_route`, `decision_route`.
Author: Max Johnson <me@maxj.phd> · 2026-08-28 22:01 UTC
Signed with PGP, not checked
Commit: 32538ffad5122dd6e2b5c97bb7f26c4587c6ee66
Parent: 7fc85cb
15 files changed, +94 insertions, -94 deletions
M Cargo.lock +30 -30
@@ -4256,7 +4256,7 @@
4256 4256
4257 4257 [[package]]
4258 4258 name = "quasi-immediate"
4259 - version = "0.75.0"
4259 + version = "0.76.0"
4260 4260 dependencies = [
4261 4261 "docengine",
4262 4262 "egui",
@@ -4267,7 +4267,7 @@
4267 4267
4268 4268 [[package]]
4269 4269 name = "quasi-router"
4270 - version = "0.75.0"
4270 + version = "0.76.0"
4271 4271 dependencies = [
4272 4272 "makeover-layout",
4273 4273 ]
@@ -7556,34 +7556,6 @@
7556 7556 "winnow 1.0.4",
7557 7557 ]
7558 7558
7559 - [[patch.unused]]
7560 - name = "quasi-axum"
7561 - version = "0.75.0"
7562 -
7563 - [[patch.unused]]
7564 - name = "quasi-basics"
7565 - version = "0.75.0"
7566 -
7567 - [[patch.unused]]
7568 - name = "quasi-http"
7569 - version = "0.75.0"
7570 -
7571 - [[patch.unused]]
7572 - name = "quasi-notifs"
7573 - version = "0.75.0"
7574 -
7575 - [[patch.unused]]
7576 - name = "quasi-store"
7577 - version = "0.1.0"
7578 -
7579 - [[patch.unused]]
7580 - name = "quasi-tauri"
7581 - version = "0.75.0"
7582 -
7583 - [[patch.unused]]
7584 - name = "quasi-webview"
7585 - version = "0.75.0"
7586 -
7587 7559 [[patch.unused]]
7588 7560 name = "kberg"
7589 7561 version = "0.1.0"
@@ -7599,3 +7571,31 @@
7599 7571 [[patch.unused]]
7600 7572 name = "quasi-type"
7601 7573 version = "0.1.2"
7574 +
7575 + [[patch.unused]]
7576 + name = "quasi-axum"
7577 + version = "0.76.0"
7578 +
7579 + [[patch.unused]]
7580 + name = "quasi-basics"
7581 + version = "0.76.0"
7582 +
7583 + [[patch.unused]]
7584 + name = "quasi-http"
7585 + version = "0.76.0"
7586 +
7587 + [[patch.unused]]
7588 + name = "quasi-notifs"
7589 + version = "0.76.0"
7590 +
7591 + [[patch.unused]]
7592 + name = "quasi-store"
7593 + version = "0.1.0"
7594 +
7595 + [[patch.unused]]
7596 + name = "quasi-tauri"
7597 + version = "0.76.0"
7598 +
7599 + [[patch.unused]]
7600 + name = "quasi-webview"
7601 + version = "0.76.0"
M Cargo.toml +2 -2
@@ -26,8 +26,8 @@
26 26 # The described screens, which are audiofiles-browser's screens. By git URL with
27 27 # a version requirement, per the tree's rule for cross-repo deps. They were
28 28 # behind a `quasi` feature until 2026-08-25, when the last flip landed.
29 - quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.75" }
30 - quasi-immediate = { git = "https://makenot.work/git/max/quasi.git", version = "0.75" }
29 + quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.76" }
30 + quasi-immediate = { git = "https://makenot.work/git/max/quasi.git", version = "0.76" }
31 31 egui = { version = "0.35", default-features = false, features = ["default_fonts"] }
32 32 egui_extras = { version = "0.35", default-features = false }
33 33 eframe = { version = "0.35", default-features = false, features = ["default_fonts", "glow"] }
@@ -170,7 +170,7 @@
170 170 .with(Node::Field(Box::new(
171 171 Field::new(FieldKind::Checkbox, "enabled", "Enable library mirror")
172 172 .value(if mirror.enabled { "on" } else { "" })
173 - .changes(Action::post("/settings/advanced/mirror/enabled")),
173 + .writes(Action::post("/settings/advanced/mirror/enabled")),
174 174 )))
175 175 // Said before it is enabled rather than after, which is the shipped
176 176 // section's own reasoning: the reader should know where the tree will
@@ -406,7 +406,7 @@
406 406 Field::new(FieldKind::Text, PATTERN, "Pattern")
407 407 .value(pattern)
408 408 .hint("{name}_{bpm}")
409 - .changes(Action::post("/bulk/rename/preview")),
409 + .writes(Action::post("/bulk/rename/preview")),
410 410 ],
411 411 submit: "Rename".to_owned(),
412 412 action: Action::post("/bulk/rename"),
@@ -69,7 +69,7 @@
69 69 //!
70 70 //! The shipped thresholds and the layer weights both commit through
71 71 //! `widgets::settled`, so a drag across a slider is one write rather than one
72 - //! per frame. [`Field::changes`] is the only thing the vocabulary has and it
72 + //! per frame. [`Field::writes`] is the only thing the vocabulary has and it
73 73 //! means every change. Three sliders here take it, so a drag is now a write per
74 74 //! step: correct, and chattier than the shipped screen against a store that is
75 75 //! local.
@@ -114,7 +114,7 @@
114 114 //!
115 115 //! [`Act::confirm`]: quasi_router::Act::confirm
116 116 //! [`Classifier::testable`]: super::Classifier::testable
117 - //! [`Field::changes`]: quasi_router::Field::changes
117 + //! [`Field::writes`]: quasi_router::Field::writes
118 118 //! [`Field::repeats`]: quasi_router::Field::repeats
119 119 //! [`Outcome::File`]: quasi_router::Outcome::File
120 120 //! [`Repeat`]: quasi_router::Repeat
@@ -664,7 +664,7 @@
664 664 /// Whether a checkbox came back ticked.
665 665 ///
666 666 /// A renderer sends the name with a value when the box is on and sends nothing
667 - /// when it is off, which is the shape every `Field::changes` checkbox in this
667 + /// when it is off, which is the shape every `Field::writes` checkbox in this
668 668 /// app already answers with.
669 669 fn ticked(request: &Request, name: &str) -> bool {
670 670 !request.payload.get(name).unwrap_or_default().is_empty()
@@ -811,7 +811,7 @@
811 811 fn editor(body: Slot, state: &Panels<'_>, draft: &super::Authoring) -> Slot {
812 812 let mut name = Field::new(FieldKind::Text, "name", "Name")
813 813 .value(draft.name.clone())
814 - .changes(Action::post("/classifier/draft/name"));
814 + .writes(Action::post("/classifier/draft/name"));
815 815 name.required = true;
816 816 name.placeholder = Some("Kick drums".to_owned());
817 817 // Gated on a refusal rather than on emptiness: a freshly opened editor
@@ -825,7 +825,7 @@
825 825 .with(Node::Field(Box::new(
826 826 Field::new(FieldKind::Checkbox, "enabled", "Enabled")
827 827 .value(if draft.enabled { "on" } else { "" })
828 - .changes(Action::post("/classifier/draft/enabled")),
828 + .writes(Action::post("/classifier/draft/enabled")),
829 829 )))
830 830 .with(Node::Field(Box::new(
831 831 Field::radio(
@@ -837,7 +837,7 @@
837 837 ],
838 838 )
839 839 .value(if draft.all { "all" } else { "any" })
840 - .changes(Action::post("/classifier/draft/match")),
840 + .writes(Action::post("/classifier/draft/match")),
841 841 )))
842 842 .with(Node::section("When"));
843 843
@@ -903,7 +903,7 @@
903 903 .collect(),
904 904 )
905 905 .value(condition.field.clone())
906 - .changes(Action::post(format!(
906 + .writes(Action::post(format!(
907 907 "/classifier/draft/conditions/{at}/set/field"
908 908 )));
909 909
@@ -918,7 +918,7 @@
918 918 .collect(),
919 919 )
920 920 .value(condition.op.clone())
921 - .changes(Action::post(format!(
921 + .writes(Action::post(format!(
922 922 "/classifier/draft/conditions/{at}/set/op"
923 923 )));
924 924
@@ -937,7 +937,7 @@
937 937 region = region.with(Node::Field(Box::new(
938 938 Field::new(FieldKind::Text, "value", "Value")
939 939 .value(condition.value.clone())
940 - .changes(Action::post(format!(
940 + .writes(Action::post(format!(
941 941 "/classifier/draft/conditions/{at}/set/value"
942 942 ))),
943 943 )));
@@ -967,7 +967,7 @@
967 967 ],
968 968 )
969 969 .value(action.kind.clone())
970 - .changes(Action::post(format!(
970 + .writes(Action::post(format!(
971 971 "/classifier/draft/actions/{at}/set/kind"
972 972 )));
973 973
@@ -977,7 +977,7 @@
977 977 if action.kind != "stop" {
978 978 let mut tag = Field::new(FieldKind::Text, "tag", "Tag")
979 979 .value(action.tag.clone())
980 - .changes(Action::post(format!(
980 + .writes(Action::post(format!(
981 981 "/classifier/draft/actions/{at}/set/tag"
982 982 )));
983 983 tag.placeholder = Some("instrument.drum.kick".to_owned());
@@ -1142,7 +1142,7 @@
1142 1142 body.with(Node::Field(Box::new(
1143 1143 Field::new(FieldKind::Text, "tag", "Tag to configure")
1144 1144 .value(state.classifier.policy_typing())
1145 - .changes(Action::post("/classifier/policies/typing")),
1145 + .writes(Action::post("/classifier/policies/typing")),
1146 1146 )))
1147 1147 .with(Node::Act(Act::new(
1148 1148 "Add threshold",
@@ -1159,7 +1159,7 @@
1159 1159 Field::range(name, label, "0", "1")
1160 1160 .value(format!("{value:.2}"))
1161 1161 .step("0.01")
1162 - .changes(Action::post(at.to_owned()).with(name, format!("{value:.2}")))
1162 + .writes(Action::post(at.to_owned()).with(name, format!("{value:.2}")))
1163 1163 }
1164 1164
1165 1165 /// Clustering: the cold-start grouping.
@@ -1176,7 +1176,7 @@
1176 1176 Field::range("cluster_k", "Groups", "2", "24")
1177 1177 .value(state.classifier.cluster_k().to_string())
1178 1178 .step("1")
1179 - .changes(Action::post("/classifier/clusters/k")),
1179 + .writes(Action::post("/classifier/clusters/k")),
1180 1180 )))
1181 1181 .with(Node::Act(unless(
1182 1182 Act::new("Find clusters", Action::post("/classifier/clusters/find")),
@@ -1198,7 +1198,7 @@
1198 1198 .with(Node::Field(Box::new(
1199 1199 Field::new(FieldKind::Text, "name", "Name this group")
1200 1200 .value(pile.name.clone())
1201 - .changes(Action::post(format!("/classifier/clusters/{at}/name"))),
1201 + .writes(Action::post(format!("/classifier/clusters/{at}/name"))),
1202 1202 )))
1203 1203 // The representative can be gone: deleting a sample must not take
1204 1204 // the pile or its name with it.
@@ -1265,7 +1265,7 @@
1265 1265 .with(Node::Field(Box::new(
1266 1266 Field::new(FieldKind::Text, "tag", "Tag")
1267 1267 .value(folder.tag.clone())
1268 - .changes(Action::post(format!("/classifier/folders/{at}/tag"))),
1268 + .writes(Action::post(format!("/classifier/folders/{at}/tag"))),
1269 1269 )))
1270 1270 .with(Node::Act(unless(
1271 1271 Act::new(
@@ -1283,7 +1283,7 @@
1283 1283 fn export_name(current: &str) -> Field {
1284 1284 let mut field = Field::new(FieldKind::Text, "export_name", "Name")
1285 1285 .value(current.to_owned())
1286 - .changes(Action::post("/classifier/sharing/name"));
1286 + .writes(Action::post("/classifier/sharing/name"));
1287 1287 field.placeholder = Some("My classifier".to_owned());
1288 1288 field
1289 1289 }
@@ -1307,7 +1307,7 @@
1307 1307 ] {
1308 1308 let mut field = Field::new(FieldKind::Checkbox, name, label)
1309 1309 .value(if part.wanted { "on" } else { "" })
1310 - .changes(Action::post(format!("/classifier/sharing/include/{name}")));
1310 + .writes(Action::post(format!("/classifier/sharing/include/{name}")));
1311 1311 // Said rather than silently contributing nothing: a part that is ticked
1312 1312 // and does not exist is why `worth_writing` asks about both.
1313 1313 if !part.available {
@@ -1367,7 +1367,7 @@
1367 1367 .with(Node::Field(Box::new(
1368 1368 Field::new(FieldKind::Checkbox, "on", "Use when auto-tagging")
1369 1369 .value(if layer.enabled { "on" } else { "" })
1370 - .changes(Action::post(format!(
1370 + .writes(Action::post(format!(
1371 1371 "/classifier/layers/{}/enabled",
1372 1372 layer.id
1373 1373 ))),
@@ -1377,7 +1377,7 @@
1377 1377 .value(format!("{:.2}", layer.weight))
1378 1378 .hint("Your own tags always count 1.0; lower means weaker.")
1379 1379 .step("0.01")
1380 - .changes(Action::post(format!(
1380 + .writes(Action::post(format!(
1381 1381 "/classifier/layers/{}/weight",
1382 1382 layer.id
1383 1383 ))),
@@ -70,7 +70,7 @@
70 70 //! `Field`'s two message slots are help and error with nothing between, and this
71 71 //! is its third measured site after the export screen's re-encoding warning.
72 72 //!
73 - //! The workaround is the rename preview's: `Field::changes` answers a fragment
73 + //! The workaround is the rename preview's: `Field::writes` answers a fragment
74 74 //! into a region beside the field. It costs a route and a region for what wants
75 75 //! to be a member.
76 76 //!
@@ -315,7 +315,7 @@
315 315 .value("0")
316 316 // The clipping consequence, which the field cannot carry
317 317 // itself. See the module header, `5672cad4`.
318 - .changes(Action::post("/edit/gain/preview")),
318 + .writes(Action::post("/edit/gain/preview")),
319 319 ],
320 320 submit: "Apply gain".to_owned(),
321 321 action: Action::post("/edit/gain"),
@@ -352,7 +352,7 @@
352 352
353 353 /// What the gain about to be applied would do to the peak.
354 354 ///
355 - /// Its own node so `Field::changes` can answer it as a fragment while the
355 + /// Its own node so `Field::writes` can answer it as a fragment while the
356 356 /// control is being moved, which is the rename preview's arrangement.
357 357 fn clips(peak: Option<f64>, gain: f64) -> Node {
358 358 let Some(peak) = peak else {
@@ -446,7 +446,7 @@
446 446 /// What happens to the edited sample, and what happened to the last one.
447 447 fn result(body: Slot, sample: &Editing) -> Slot {
448 448 let mut field =
449 - Field::radio(RESULT, "Result", result_modes()).changes(Action::post("/edit/result"));
449 + Field::radio(RESULT, "Result", result_modes()).writes(Action::post("/edit/result"));
450 450 if let Some(chosen) = &sample.result {
451 451 field = field.value(chosen.clone());
452 452 }
@@ -189,7 +189,7 @@
189 189 fn destination(_state: &Panels<'_>, _request: Request) -> Result<Response, RouteError> {
190 190 Ok(Response::locate(Locating::folder(
191 191 "Export Destination",
192 - writes(Setting::Destination),
192 + setting_route(Setting::Destination),
193 193 Setting::Destination.as_str(),
194 194 )))
195 195 }
@@ -304,7 +304,7 @@
304 304 "Include metadata (.audiofiles.json)",
305 305 )
306 306 .value(if settings.sidecar { "on" } else { "" })
307 - .changes(writes(Setting::Sidecar)),
307 + .writes(setting_route(Setting::Sidecar)),
308 308 )));
309 309
310 310 if settings.flatten {
@@ -563,7 +563,7 @@
563 563 }));
564 564
565 565 let mut field = Field::select(Setting::DeviceProfile.as_str(), "Device profile", options)
566 - .changes(writes(Setting::DeviceProfile));
566 + .writes(setting_route(Setting::DeviceProfile));
567 567 field.value = Some(chosen.unwrap_or_default().to_owned());
568 568 Node::Field(Box::new(field))
569 569 }
@@ -681,7 +681,7 @@
681 681 .collect();
682 682 Field::radio(setting.as_str(), label, options)
683 683 .value(chosen)
684 - .changes(writes(setting))
684 + .writes(setting_route(setting))
685 685 }
686 686
687 687 /// How to name the output files.
@@ -697,7 +697,7 @@
697 697 )
698 698 .value(pattern.unwrap_or_default())
699 699 .hint("Tokens: {name} {bpm} {key} {class} {duration} {n} {nn} {nnn} {ext}")
700 - .changes(writes(Setting::NamingPattern)),
700 + .writes(setting_route(Setting::NamingPattern)),
701 701 ))
702 702 }
703 703
@@ -732,7 +732,7 @@
732 732 }
733 733
734 734 /// The address a control changing this setting calls.
735 - fn writes(setting: Setting) -> Action {
735 + fn setting_route(setting: Setting) -> Action {
736 736 Action::post(format!("/export/set/{}", setting.as_str()))
737 737 }
738 738
@@ -49,7 +49,7 @@
49 49 //! asked.
50 50 //! - **The debounce.** `requery_now` defers the query to a drag's settle point,
51 51 //! which is renderer policy the way the footer's fade timer was, and is
52 - //! already `Field::changes`'s own behaviour: a described field answers when it
52 + //! already `Field::writes`'s own behaviour: a described field answers when it
53 53 //! settles (`audiofiles@a3e59e3`).
54 54 //! - **The per-section collapse.** `widgets::filter_section` is a disclosure per
55 55 //! axis. [`Field::extended`](quasi_router::Field::extended) says a field lives
@@ -522,7 +522,7 @@
522 522 ..Field::new(FieldKind::Text, "tag", "Require a tag")
523 523 }
524 524 .value(state.filters.typing())
525 - .changes(Action::post("/filters/tags/typing")),
525 + .writes(Action::post("/filters/tags/typing")),
526 526 ],
527 527 });
528 528
@@ -272,7 +272,7 @@
272 272 .collect(),
273 273 )
274 274 .value(forging.how.as_str())
275 - .changes(Action::post("/forge/slice")),
275 + .writes(Action::post("/forge/slice")),
276 276 )));
277 277
278 278 // Only the parameters the chosen method reads, which is the shipped
@@ -361,7 +361,7 @@
361 361 .map(|device| Choice::new(device.name.clone(), describe(device)))
362 362 .collect(),
363 363 )
364 - .changes(Action::post("/forge/device"));
364 + .writes(Action::post("/forge/device"));
365 365 // The instruction is the picker's ghost text rather than a disabled button's
366 366 // job, which is the call the shipped screen already made: a select with
367 367 // nothing chosen reads as an empty box, and the greyed control beside it is
@@ -398,7 +398,7 @@
398 398 .unit("dBFS")
399 399 .step("1")
400 400 .value(format!("{:.0}", forging.threshold_db))
401 - .changes(writes(Knob::Threshold)),
401 + .writes(knob_route(Knob::Threshold)),
402 402 )))
403 403 .with(Node::Act(live(
404 404 forging,
@@ -423,7 +423,7 @@
423 423 let _ = forging;
424 424 let name = field.name.clone();
425 425 Node::Field(Box::new(
426 - field.changes(Action::post(format!("/forge/set/{name}"))),
426 + field.writes(Action::post(format!("/forge/set/{name}"))),
427 427 ))
428 428 }
429 429
@@ -446,12 +446,12 @@
446 446 Node::Field(Box::new(
447 447 Field::radio(knob.as_str(), label, options)
448 448 .value(chosen)
449 - .changes(writes(knob)),
449 + .writes(knob_route(knob)),
450 450 ))
451 451 }
452 452
453 453 /// The address a control changing this number calls.
454 - fn writes(knob: Knob) -> Action {
454 + fn knob_route(knob: Knob) -> Action {
455 455 Action::post(format!("/forge/set/{}", knob.as_str()))
456 456 }
457 457
@@ -924,7 +924,7 @@
924 924 .required()
925 925 .hint("e.g. Drum Kits")
926 926 .value(vault_name)
927 - .changes(writes(Decision::VaultName));
927 + .writes(decision_route(Decision::VaultName));
928 928 // The state the shipped screen used to leave unexplained: Import
929 929 // disabled itself and said why only to a pointer that hovered it.
930 930 if vault_name.trim().is_empty() {
@@ -948,7 +948,7 @@
948 948 .map(|(at, vault)| Choice::new(at.to_string(), vault.name.clone()))
949 949 .collect();
950 950 let mut field = Field::select(Decision::MergeVault.as_str(), "Merge into", options)
951 - .changes(writes(Decision::MergeVault));
951 + .writes(decision_route(Decision::MergeVault));
952 952 field.value = Some(merging_into.to_string());
953 953 body = body.with(Node::Field(Box::new(field)));
954 954 }
@@ -1011,7 +1011,7 @@
1011 1011 Node::Field(Box::new(
1012 1012 Field::radio(Decision::Strategy.as_str(), "Import strategy", options)
1013 1013 .value(strategy.as_str())
1014 - .changes(writes(Decision::Strategy)),
1014 + .writes(decision_route(Decision::Strategy)),
1015 1015 ))
1016 1016 }
1017 1017
@@ -1116,7 +1116,7 @@
1116 1116 .with(Node::Field(Box::new(
1117 1117 Field::new(FieldKind::Text, TAGS, "Tags")
1118 1118 .value(&folder.typed)
1119 - .changes(Action::post(format!("/import/folders/{at}/tags"))),
1119 + .writes(Action::post(format!("/import/folders/{at}/tags"))),
1120 1120 )));
1121 1121 if !folder.invalid.is_empty() {
1122 1122 group = group.with(Node::banner(
@@ -1151,7 +1151,7 @@
1151 1151 body = body.with(Node::Field(Box::new(
1152 1152 Field::new(FieldKind::Checkbox, measure.as_str(), measure.label())
1153 1153 .value(if measure.read(&measures) { "on" } else { "" })
1154 - .changes(Action::post(format!(
1154 + .writes(Action::post(format!(
1155 1155 "/import/measure/{}",
1156 1156 measure.as_str()
1157 1157 ))),
@@ -1339,7 +1339,7 @@
1339 1339 .collect(),
1340 1340 )
1341 1341 .value(order.as_str())
1342 - .changes(Action::post("/import/review/order")),
1342 + .writes(Action::post("/import/review/order")),
1343 1343 )));
1344 1344
1345 1345 if items.is_empty() {
@@ -1581,7 +1581,7 @@
1581 1581 }
1582 1582
1583 1583 /// The address a control changing this answers calls.
1584 - fn writes(decision: Decision) -> Action {
1584 + fn decision_route(decision: Decision) -> Action {
1585 1585 Action::post(format!("/import/set/{}", decision.as_str()))
1586 1586 }
1587 1587
@@ -209,7 +209,7 @@
209 209 } else {
210 210 ""
211 211 })
212 - .changes(Action::post(format!("/settings/config/{name}"))),
212 + .writes(Action::post(format!("/settings/config/{name}"))),
213 213 )));
214 214 }
215 215
@@ -335,7 +335,7 @@
335 335 })
336 336 .collect();
337 337 let mut field = Field::theme(ConfigKey::Theme.as_str(), "Theme", themes)
338 - .changes(Action::post("/settings/config/theme"));
338 + .writes(Action::post("/settings/config/theme"));
339 339 field.value = chosen;
340 340 Node::Field(Box::new(field))
341 341 }
@@ -368,7 +368,7 @@
368 368
369 369 /// A boolean setting as a checkbox that writes when it changes.
370 370 ///
371 - /// `Field::changes` rather than a form: this screen has no submit and never
371 + /// `Field::writes` rather than a form: this screen has no submit and never
372 372 /// should, which is the shape `14612ed8` was recounted for. Thirteen of
373 373 /// goingson's nineteen change-sites were standalone controls, and every control
374 374 /// here is one.
@@ -377,7 +377,7 @@
377 377 Ok(Node::Field(Box::new(
378 378 Field::new(FieldKind::Checkbox, key.as_str(), label)
379 379 .value(if on { "on" } else { "" })
380 - .changes(Action::post(format!("/settings/config/{}", key.as_str()))),
380 + .writes(Action::post(format!("/settings/config/{}", key.as_str()))),
381 381 )))
382 382 }
383 383
@@ -396,7 +396,7 @@
396 396 )
397 397 .value(current)
398 398 .hint("Between 20 and 32 pixels.")
399 - .changes(Action::post(format!(
399 + .writes(Action::post(format!(
400 400 "/settings/config/{}",
401 401 ConfigKey::RowHeight.as_str()
402 402 ))))
@@ -496,7 +496,7 @@
496 496 // where Create New disabled itself and said nothing about why.
497 497 let mut name = Field::new(FieldKind::Text, "create_name", "Name")
498 498 .value(draft.name.clone())
499 - .changes(Action::post("/settings/storage/draft/name"));
499 + .writes(Action::post("/settings/storage/draft/name"));
500 500 name.required = true;
501 501 if draft.folder.is_some() && draft.name.trim().is_empty() {
502 502 name = name.error("A library needs a name.");
@@ -532,7 +532,7 @@
532 532 "copy"
533 533 })
534 534 .hint(STYLE_HINT)
535 - .changes(Action::post("/settings/storage/draft/style"));
535 + .writes(Action::post("/settings/storage/draft/style"));
536 536 body = body.with(Node::Field(Box::new(style)));
537 537
538 538 if draft.reference_in_place {
@@ -69,7 +69,7 @@
69 69 //! **A price cannot follow a slider.** The shipped picker is a logarithmic
70 70 //! slider with a live quote beside it, recomputed per drag frame from
71 71 //! `AppPricing::quote_cents` — a *server* pricing model the app happens to hold
72 - //! a copy of. A described field carries a value and `Field::changes` fires a
72 + //! a copy of. A described field carries a value and `Field::writes` fires a
73 73 //! route when it changes, so following the drag means a request per step. The
74 74 //! port therefore quotes what is **committed**, not what is under the thumb, and
75 75 //! the finding is the general one: **nothing describes a display derived from a
@@ -433,7 +433,7 @@
433 433 .with(Node::Field(Box::new(
434 434 Field::new(FieldKind::Checkbox, "auto", "Sync on a schedule")
435 435 .value(if status.auto_sync_enabled { "on" } else { "" })
436 - .changes(Action::post("/sync/auto")),
436 + .writes(Action::post("/sync/auto")),
437 437 )))
438 438 // A label at last: the "Auto-sync" heading above names the checkbox, so
439 439 // as a bare strip this control asked its question without ever saying
@@ -448,7 +448,7 @@
448 448 .collect(),
449 449 )
450 450 .value(status.sync_interval_minutes.to_string())
451 - .changes(Action::post("/sync/interval")),
451 + .writes(Action::post("/sync/interval")),
452 452 )))
453 453 .with(Node::section("Audio file sync"))
454 454 .with(subscription(sync))
@@ -11161,7 +11161,7 @@
11161 11161 classifying(&classifier, request).expect("answered");
11162 11162 classifying(&classifier, Request::post("/classifier/rules/r1/enabled")).expect("answered");
11163 11163 // Nothing sent is a box that came back unticked, which is what every
11164 - // `Field::changes` checkbox in this app answers with.
11164 + // `Field::writes` checkbox in this app answers with.
11165 11165 assert_eq!(
11166 11166 classifier.asked(),
11167 11167 ["enable_rule r1 true", "enable_rule r1 false"]
@@ -11244,7 +11244,7 @@
11244 11244 let has_tag = |classifier: &FakeClassifier| {
11245 11245 deep_fields(&classified(classifier)).iter().any(|field| {
11246 11246 field.name == "tag"
11247 - && field.changes.as_ref().and_then(quasi_router::Action::route)
11247 + && field.writes.as_ref().and_then(quasi_router::Action::route)
11248 11248 == Some("/classifier/draft/actions/0/set/tag")
11249 11249 })
11250 11250 };
@@ -42,7 +42,7 @@
42 42 //!
43 43 //! # THE FINDING: a field cannot say that firing it is expensive
44 44 //!
45 - //! [`Field::changes`](quasi_router::Field::changes) names an address to call
45 + //! [`Field::writes`](quasi_router::Field::writes) names an address to call
46 46 //! when a value changes and says nothing about how often. The shipped search box
47 47 //! cannot afford per-keystroke, and its comment is explicit: "each keystroke
48 48 //! would otherwise run a blocking DB query + re-sort on the GUI thread". So it
@@ -509,7 +509,7 @@
509 509 // no pixel -- and it is the difference between a row that
510 510 // happens to look right and one that says what it means.
511 511 .width(Width::Fill)
512 - .changes(Action::post("/search")),
512 + .writes(Action::post("/search")),
513 513 )),
514 514 Priority::Essential,
515 515 )
@@ -533,7 +533,7 @@
533 533 } else {
534 534 "folder"
535 535 })
536 - .changes(Action::post("/search/scope")),
536 + .writes(Action::post("/search/scope")),
537 537 )),
538 538 Priority::Secondary,
539 539 );