Skip to main content

max / goingson

Retire the predicate-and-reader pairs the `for`-over-Option idiom replaced Waves 10 and 11 wrote a `has_x` predicate beside a total `x` reader wherever a setting was conditional on an Option being full, because the idiom that says it directly had not been found yet. Eight pairs answered nothing but "is this Option full": sync's server and device rows, compose's From value, contexts' four stored values, problems' project badge, and task_list's project and milestone filters. Each is now one supplier handing back an `Option` and a `for` over it, which is what every screen written since wave 12 does. The emitted description is unchanged; the suite is the check. The twelve predicates that stay ask a real question about the thing rather than about an Option, or have several consumers. `has_error`/`error_for` is the good shape and is untouched. Closes 5b1bec18.
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 19:18 UTC
Signed with PGP, not checked
Commit: aa129c001b53065b97ef24017961af623555258d
Parent: e6256c3
5 files changed, +57 insertions, -80 deletions
@@ -266,14 +266,9 @@
266 266 }
267 267 }
268 268
269 - /// Whether an account has been chosen. R9: read whether or not it is placed.
270 - fn has_from(loaded: &Loaded) -> bool {
271 - loaded.from.is_some()
272 - }
273 -
274 - /// That account's id, or nothing.
275 - fn from_value(loaded: &Loaded) -> &str {
276 - loaded.from.as_deref().unwrap_or_default()
269 + /// The account the message is sent from, once one is chosen.
270 + fn from_value(loaded: &Loaded) -> Option<&str> {
271 + loaded.from.as_deref()
277 272 }
278 273
279 274 /// Whether the message carries nothing.
@@ -344,7 +339,9 @@
344 339
345 340 field Select FROM "From" {
346 341 options loaded.accounts.clone();
347 - value from_value(loaded) when has_from(loaded);
342 + for from in from_value(loaded).into_iter() {
343 + value from;
344 + }
348 345 writes field_route(loaded.id, FROM);
349 346 }
350 347
@@ -275,33 +275,26 @@
275 275 }
276 276 }
277 277
278 - /// The label a stored context holds.
279 - fn stored_label(editing: &Editing) -> String {
280 - editing
281 - .existing
282 - .map(|context| context.label.clone())
283 - .unwrap_or_default()
278 + /// The label a stored context holds, if the pane is editing one.
279 + fn stored_label(editing: &Editing) -> Option<String> {
280 + editing.existing.map(|context| context.label.clone())
284 281 }
285 282
286 283 /// The kind a stored context holds, as it is stored.
287 - fn stored_kind<'a>(editing: &Editing<'a>) -> &'a str {
288 - editing.existing.map_or("", |context| context.kind.as_str())
284 + fn stored_kind<'a>(editing: &Editing<'a>) -> Option<&'a str> {
285 + editing.existing.map(|context| context.kind.as_str())
289 286 }
290 287
291 288 /// The first day a stored context holds.
292 - fn stored_start(editing: &Editing) -> String {
289 + fn stored_start(editing: &Editing) -> Option<String> {
293 290 editing
294 291 .existing
295 292 .map(|context| context.starts_on.to_string())
296 - .unwrap_or_default()
297 293 }
298 294
299 295 /// The last day a stored context holds.
300 - fn stored_end(editing: &Editing) -> String {
301 - editing
302 - .existing
303 - .map(|context| context.ends_on.to_string())
304 - .unwrap_or_default()
296 + fn stored_end(editing: &Editing) -> Option<String> {
297 + editing.existing.map(|context| context.ends_on.to_string())
305 298 }
306 299
307 300 /// Whether a named field was refused.
@@ -358,7 +351,9 @@
358 351 field Text "label" "Label" {
359 352 required;
360 353 placeholder "Two weeks in Lisbon";
361 - value stored_label(editing) when is_edit(editing);
354 + for label in stored_label(editing).into_iter() {
355 + value label;
356 + }
362 357 error error_for(editing, "label") when has_error(editing, "label");
363 358 refilled typed(editing);
364 359 }
@@ -367,7 +362,9 @@
367 362 for kind in KINDS.iter().copied() {
368 363 option Choice::new(kind.as_str(), kind_label(kind));
369 364 }
370 - value stored_kind(editing) when is_edit(editing);
365 + for kind in stored_kind(editing).into_iter() {
366 + value kind;
367 + }
371 368 error error_for(editing, "kind") when has_error(editing, "kind");
372 369 refilled typed(editing);
373 370 }
@@ -375,7 +372,9 @@
375 372 field Date "starts_on" "First day" {
376 373 required;
377 374 hint "The first day inside it.";
378 - value stored_start(editing) when is_edit(editing);
375 + for start in stored_start(editing).into_iter() {
376 + value start;
377 + }
379 378 error error_for(editing, "starts_on") when has_error(editing, "starts_on");
380 379 refilled typed(editing);
381 380 }
@@ -383,7 +382,9 @@
383 382 field Date "ends_on" "Last day" {
384 383 required;
385 384 hint "Inclusive: off until the 17th means the 17th is off.";
386 - value stored_end(editing) when is_edit(editing);
385 + for end in stored_end(editing).into_iter() {
386 + value end;
387 + }
387 388 error error_for(editing, "ends_on") when has_error(editing, "ends_on");
388 389 refilled typed(editing);
389 390 }
@@ -273,15 +273,9 @@
273 273 listed.problem.painhours().to_string()
274 274 }
275 275
276 - /// Whether the problem belongs to a project. R9: read whether or not the badge
277 - /// is placed.
278 - fn has_project(listed: &Listed) -> bool {
279 - listed.project.is_some()
280 - }
281 -
282 - /// That project's name, or nothing.
283 - fn project_name(listed: &Listed) -> &str {
284 - listed.project.as_deref().unwrap_or_default()
276 + /// The project the problem belongs to, if it belongs to one.
277 + fn project_name(listed: &Listed) -> Option<&str> {
278 + listed.project.as_deref()
285 279 }
286 280
287 281 /// Where the score came from, and how old the report is.
@@ -380,7 +374,9 @@
380 374 &listed.problem.source,
381 375 list_action(listing.status, Some(&listed.problem.source))
382 376 );
383 - token Tag::badge(project_name(listed)).tone(Tone::Info) when has_project(listed);
377 + for project in project_name(listed).into_iter() {
378 + token Tag::badge(project).tone(Tone::Info);
379 + }
384 380 token Tag::badge(listed.problem.status.as_str())
385 381 .tone(status_tone(listed.problem.status))
386 382 when listed.problem.status.is_settled();
@@ -1063,32 +1063,17 @@
1063 1063 listing.view.priority.as_ref() == Some(priority)
1064 1064 }
1065 1065
1066 - /// The project the filter is on.
1067 - fn project_value(listing: &Listing) -> String {
1068 - listing
1069 - .view
1070 - .project
1071 - .map(|project| project.to_string())
1072 - .unwrap_or_default()
1066 + /// The project the filter is on, if it is on one.
1067 + fn project_value(listing: &Listing) -> Option<String> {
1068 + listing.view.project.map(|project| project.to_string())
1073 1069 }
1074 1070
1075 - /// Whether one is.
1076 - fn has_project(listing: &Listing) -> bool {
1077 - listing.view.project.is_some()
1078 - }
1079 -
1080 - /// The milestone the filter is on.
1081 - fn milestone_value(listing: &Listing) -> String {
1071 + /// The milestone the filter is on, if it is on one.
1072 + fn milestone_value(listing: &Listing) -> Option<String> {
1082 1073 listing
1083 1074 .view
1084 1075 .milestone
1085 1076 .map(|milestone| milestone.to_string())
1086 - .unwrap_or_default()
1087 - }
1088 -
1089 - /// Whether one is.
1090 - fn has_milestone(listing: &Listing) -> bool {
1091 - listing.view.milestone.is_some()
1092 1077 }
1093 1078
1094 1079 declare! {
@@ -1116,13 +1101,17 @@
1116 1101 field Select "project" "Project" unless listing.projects.is_empty() {
1117 1102 options listing.projects.clone();
1118 1103 consulting Consult::at_once(listing.view.clearing_project().list());
1119 - value project_value(listing) when has_project(listing);
1104 + for project in project_value(listing).into_iter() {
1105 + value project;
1106 + }
1120 1107 }
1121 1108
1122 1109 field Select "milestone" "Milestone" unless listing.milestones.is_empty() {
1123 1110 options listing.milestones.clone();
1124 1111 consulting Consult::at_once(listing.view.clearing_milestone().list());
1125 - value milestone_value(listing) when has_milestone(listing);
1112 + for milestone in milestone_value(listing).into_iter() {
1113 + value milestone;
1114 + }
1126 1115 }
1127 1116
1128 1117 for offered in PRIORITIES {
@@ -128,24 +128,14 @@
128 128 }
129 129 }
130 130
131 - /// Whether the server is named. R9: read whether or not the row is placed.
132 - fn has_server(state: &State) -> bool {
133 - !server(state).is_empty()
131 + /// The server this device syncs with, once it knows one.
132 + fn server(state: &State) -> Option<&str> {
133 + state.server_url.as_deref().filter(|url| !url.is_empty())
134 134 }
135 135
136 - /// That server, or nothing.
137 - fn server(state: &State) -> &str {
138 - state.server_url.as_deref().unwrap_or_default()
139 - }
140 -
141 - /// Whether this device has an id yet.
142 - fn has_device(state: &State) -> bool {
143 - !device(state).is_empty()
144 - }
145 -
146 - /// That id, or nothing.
147 - fn device(state: &State) -> &str {
148 - state.device_id.as_deref().unwrap_or_default()
136 + /// This device's id, once it has one.
137 + fn device(state: &State) -> Option<&str> {
138 + state.device_id.as_deref().filter(|id| !id.is_empty())
149 139 }
150 140
151 141 /// Signed in, or set up and not signed in.
@@ -210,14 +200,18 @@
210 200 row "Status" {
211 201 meta status_label(&state);
212 202 }
213 - row "Server" when has_server(&state) {
214 - meta server(&state);
203 + for server in server(&state).into_iter() {
204 + row "Server" {
205 + meta server;
206 + }
215 207 }
216 208 row "Encryption" {
217 209 meta encryption_label(&state);
218 210 }
219 - row "This device" when has_device(&state) {
220 - meta device(&state);
211 + for device in device(&state).into_iter() {
212 + row "This device" {
213 + meta device;
214 + }
221 215 }
222 216 row "Last sync" {
223 217 meta last_sync(&state);