Skip to main content

max / goingson

Take quasi 0.88.0: the filters ask rather than write quasicoherent aeb44860. Six controls said Field::writes for something that writes nothing. The email folder and label selects, the task list's project and milestone selects, and the timer's two minute boxes all narrow or recompute what is on screen and put nothing in the database; the member that says that is consults, which asks a route about a value and stores nothing. The four selects take Consult::at_once, because a select is at its next value or its last one and there is nothing to wait out. The two number boxes take the typing wait, since they are typed into. Field::consults now hears them: the webview picked its event from the control in 0.88.0, so a select's question is hung on `input` rather than on a `keyup` it never raises.
Author: Max Johnson <me@maxj.phd> · 2026-08-30 21:03 UTC
Signed with PGP, not checked
Commit: 4de92700d4b0f2a584d6b5f93c08b6b78b21bedb
Parent: 461ba3a
5 files changed, +60 insertions, -52 deletions
M Cargo.lock +24 -24
@@ -1387,7 +1387,7 @@
1387 1387 "libc",
1388 1388 "option-ext",
1389 1389 "redox_users",
1390 - "windows-sys 0.61.2",
1390 + "windows-sys 0.60.2",
1391 1391 ]
1392 1392
1393 1393 [[package]]
@@ -1628,7 +1628,7 @@
1628 1628 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
1629 1629 dependencies = [
1630 1630 "libc",
1631 - "windows-sys 0.61.2",
1631 + "windows-sys 0.60.2",
1632 1632 ]
1633 1633
1634 1634 [[package]]
@@ -3650,7 +3650,7 @@
3650 3650 "png 0.18.1",
3651 3651 "serde",
3652 3652 "thiserror 2.0.20",
3653 - "windows-sys 0.61.2",
3653 + "windows-sys 0.60.2",
3654 3654 ]
3655 3655
3656 3656 [[package]]
@@ -3783,7 +3783,7 @@
3783 3783 source = "registry+https://github.com/rust-lang/crates.io-index"
3784 3784 checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
3785 3785 dependencies = [
3786 - "windows-sys 0.61.2",
3786 + "windows-sys 0.60.2",
3787 3787 ]
3788 3788
3789 3789 [[package]]
@@ -4717,7 +4717,7 @@
4717 4717
4718 4718 [[package]]
4719 4719 name = "quasi-http"
4720 - version = "0.87.0"
4720 + version = "0.88.0"
4721 4721 dependencies = [
4722 4722 "form_urlencoded",
4723 4723 "http",
@@ -4726,7 +4726,7 @@
4726 4726
4727 4727 [[package]]
4728 4728 name = "quasi-notifs"
4729 - version = "0.87.0"
4729 + version = "0.88.0"
4730 4730 dependencies = [
4731 4731 "quasi-router",
4732 4732 "synckit-config",
@@ -4734,14 +4734,14 @@
4734 4734
4735 4735 [[package]]
4736 4736 name = "quasi-router"
4737 - version = "0.87.0"
4737 + version = "0.88.0"
4738 4738 dependencies = [
4739 4739 "makeover-layout",
4740 4740 ]
4741 4741
4742 4742 [[package]]
4743 4743 name = "quasi-tauri"
4744 - version = "0.87.0"
4744 + version = "0.88.0"
4745 4745 dependencies = [
4746 4746 "http",
4747 4747 "quasi-http",
@@ -4767,7 +4767,7 @@
4767 4767
4768 4768 [[package]]
4769 4769 name = "quasi-webview"
4770 - version = "0.87.0"
4770 + version = "0.88.0"
4771 4771 dependencies = [
4772 4772 "docengine",
4773 4773 "makeover-layout",
@@ -5206,7 +5206,7 @@
5206 5206 "errno",
5207 5207 "libc",
5208 5208 "linux-raw-sys",
5209 - "windows-sys 0.61.2",
5209 + "windows-sys 0.60.2",
5210 5210 ]
5211 5211
5212 5212 [[package]]
@@ -5263,7 +5263,7 @@
5263 5263 "security-framework",
5264 5264 "security-framework-sys",
5265 5265 "webpki-root-certs",
5266 - "windows-sys 0.61.2",
5266 + "windows-sys 0.60.2",
5267 5267 ]
5268 5268
5269 5269 [[package]]
@@ -5803,7 +5803,7 @@
5803 5803 checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
5804 5804 dependencies = [
5805 5805 "libc",
5806 - "windows-sys 0.61.2",
5806 + "windows-sys 0.60.2",
5807 5807 ]
5808 5808
5809 5809 [[package]]
@@ -6563,7 +6563,7 @@
6563 6563 "getrandom 0.4.3",
6564 6564 "once_cell",
6565 6565 "rustix",
6566 - "windows-sys 0.61.2",
6566 + "windows-sys 0.60.2",
6567 6567 ]
6568 6568
6569 6569 [[package]]
@@ -6585,7 +6585,7 @@
6585 6585 "parking_lot",
6586 6586 "rustix",
6587 6587 "signal-hook",
6588 - "windows-sys 0.61.2",
6588 + "windows-sys 0.60.2",
6589 6589 ]
6590 6590
6591 6591 [[package]]
@@ -7067,7 +7067,7 @@
7067 7067 "png 0.18.1",
7068 7068 "serde",
7069 7069 "thiserror 2.0.20",
7070 - "windows-sys 0.61.2",
7070 + "windows-sys 0.60.2",
7071 7071 ]
7072 7072
7073 7073 [[package]]
@@ -7102,7 +7102,7 @@
7102 7102 dependencies = [
7103 7103 "memoffset",
7104 7104 "tempfile",
7105 - "windows-sys 0.61.2",
7105 + "windows-sys 0.60.2",
7106 7106 ]
7107 7107
7108 7108 [[package]]
@@ -7632,7 +7632,7 @@
7632 7632 source = "registry+https://github.com/rust-lang/crates.io-index"
7633 7633 checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
7634 7634 dependencies = [
7635 - "windows-sys 0.61.2",
7635 + "windows-sys 0.60.2",
7636 7636 ]
7637 7637
7638 7638 [[package]]
@@ -8489,22 +8489,22 @@
8489 8489 "winnow 1.0.4",
8490 8490 ]
8491 8491
8492 - [[patch.unused]]
8493 - name = "ops-status"
8494 - version = "0.1.0"
8495 -
8496 8492 [[patch.unused]]
8497 8493 name = "quasi-axum"
8498 - version = "0.87.0"
8494 + version = "0.88.0"
8499 8495
8500 8496 [[patch.unused]]
8501 8497 name = "quasi-basics"
8502 - version = "0.87.0"
8498 + version = "0.88.0"
8503 8499
8504 8500 [[patch.unused]]
8505 8501 name = "quasi-immediate"
8506 - version = "0.87.0"
8502 + version = "0.88.0"
8507 8503
8508 8504 [[patch.unused]]
8509 8505 name = "quasi-store"
8510 8506 version = "0.1.0"
8507 +
8508 + [[patch.unused]]
8509 + name = "ops-status"
8510 + version = "0.1.0"
@@ -36,7 +36,7 @@
36 36 makeover-geometry = "0.7"
37 37 # Only to write its selection and clock scripts into the frontend. Those two are
38 38 # the whole of `frontend/js/` since the swap.
39 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
39 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.88" }
40 40
41 41 [dependencies]
42 42 goingson-core = { workspace = true }
@@ -54,10 +54,10 @@
54 54 # dependency's source even with the feature off -- so a public clone could not
55 55 # build at all against a URL it could not reach. quasi went public that day and
56 56 # the URL moved here with it.
57 - quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
58 - quasi-http = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
59 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
60 - quasi-tauri = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
57 + quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.88" }
58 + quasi-http = { git = "https://makenot.work/git/max/quasi.git", version = "0.88" }
59 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.88" }
60 + quasi-tauri = { git = "https://makenot.work/git/max/quasi.git", version = "0.88" }
61 61
62 62 # The notification declaration, and it is NOT behind the `quasi` feature: the
63 63 # watcher is desktop app behaviour rather than a described screen, and the
@@ -74,7 +74,7 @@
74 74 # before it has ported its screens cannot take it. `notifications::Notifier` is
75 75 # the eight lines meanwhile, and it goes when the flip makes quasi-router
76 76 # unconditional here.
77 - quasi-notifs = { git = "https://makenot.work/git/max/quasi.git", version = "0.87", features = ["synckit", "describe"] }
77 + quasi-notifs = { git = "https://makenot.work/git/max/quasi.git", version = "0.88", features = ["synckit", "describe"] }
78 78
79 79 # Tauri
80 80 tauri = { workspace = true, features = ["image-png", "devtools"] }
@@ -68,7 +68,7 @@
68 68 BodyFormat, Email, EmailId, EmailSource, EmailThread, Validate as _, date_utils, email_compose,
69 69 event_from_email, task_from_email,
70 70 };
71 - use quasi_router::screen::{Act, Choice, Field, Figure, Rest, Row, Tag};
71 + use quasi_router::screen::{Act, Choice, Consult, Field, Figure, Rest, Row, Tag};
72 72 use quasi_router::{Action, Node, RegionKind, Response, RouteError, Router, Screen, Slot};
73 73
74 74 use crate::commands::get_snooze_options;
@@ -492,10 +492,15 @@
492 492
493 493 /// The two filters, and the archive switch.
494 494 ///
495 - /// Fields with a [`Field::writes`] rather than a strip of options: the folder
495 + /// Fields with a [`Field::consults`] rather than a strip of options: the folder
496 496 /// set is whatever the server has and can be any length, and a strip is a shape
497 497 /// for a handful. The JS reaches the same conclusion by using a `<select>`, and
498 - /// `14612ed8` is what lets a control write without a form around it.
498 + /// `14612ed8` is what lets a control call a route without a form around it.
499 + ///
500 + /// A question and not a write (`aeb44860`): narrowing the list puts nothing in
501 + /// the database, and the member that says so is the one that means "ask a route
502 + /// about this value". [`Consult::at_once`] because a select is at its next
503 + /// value or its last one and has nothing to wait out.
499 504 ///
500 505 /// Each carries the *other* filters in its action and supplies its own value,
501 506 /// which is what keeps picking a label from resetting the folder.
@@ -527,13 +532,13 @@
527 532 if !folders.is_empty() {
528 533 let mut options = vec![Choice::new("", "All folders")];
529 534 options.extend(folders.iter().map(|folder| Choice::new(folder, folder)));
530 - let mut field = Field::select("folder", "Folder", options).writes(
535 + let mut field = Field::select("folder", "Folder", options).consulting(Consult::at_once(
531 536 View {
532 537 folder: None,
533 538 ..base.clone()
534 539 }
535 540 .list(),
536 - );
541 + ));
537 542 if let Some(folder) = &view.folder {
538 543 field = field.value(folder);
539 544 }
@@ -543,13 +548,13 @@
543 548 if !labels.is_empty() {
544 549 let mut options = vec![Choice::new("", "All labels")];
545 550 options.extend(labels.iter().map(|label| Choice::new(label, label)));
546 - let mut field = Field::select("label", "Label", options).writes(
551 + let mut field = Field::select("label", "Label", options).consulting(Consult::at_once(
547 552 View {
548 553 label: None,
549 554 ..base.clone()
550 555 }
551 556 .list(),
552 - );
557 + ));
553 558 if let Some(label) = &view.label {
554 559 field = field.value(label);
555 560 }
@@ -155,7 +155,9 @@
155 155 TaskStatus,
156 156 };
157 157 use makeover_layout::{Sort, Tone, Width};
158 - use quasi_router::screen::{Act, Cell, Cells, Choice, Column, Field, Figure, Meter, Rest, Tag};
158 + use quasi_router::screen::{
159 + Act, Cell, Cells, Choice, Column, Consult, Field, Figure, Meter, Rest, Tag,
160 + };
159 161 use quasi_router::{Action, Node, RegionKind, Response, RouteError, Router, Screen, Slot};
160 162
161 163 use crate::state::{AppState, DESKTOP_USER_ID};
@@ -998,14 +1000,14 @@
998 1000 // Clearing the project clears the milestone with it: a milestone id
999 1001 // that outlived its project filters to a project the view no longer
1000 1002 // names, and the control that would clear it is not on screen.
1001 - let mut field = Field::select("project", "Project", options).writes(
1003 + let mut field = Field::select("project", "Project", options).consulting(Consult::at_once(
1002 1004 View {
1003 1005 project: None,
1004 1006 milestone: None,
1005 1007 ..base.clone()
1006 1008 }
1007 1009 .list(),
1008 - );
1010 + ));
1009 1011 if let Some(project) = view.project {
1010 1012 field = field.value(project.to_string());
1011 1013 }
@@ -1024,13 +1026,14 @@
1024 1026 .iter()
1025 1027 .map(|milestone| Choice::new(milestone.id.to_string(), &milestone.name)),
1026 1028 );
1027 - let mut field = Field::select("milestone", "Milestone", options).writes(
1028 - View {
1029 - milestone: None,
1030 - ..base.clone()
1031 - }
1032 - .list(),
1033 - );
1029 + let mut field =
1030 + Field::select("milestone", "Milestone", options).consulting(Consult::at_once(
1031 + View {
1032 + milestone: None,
1033 + ..base.clone()
1034 + }
1035 + .list(),
1036 + ));
1034 1037 if let Some(milestone) = view.milestone {
1035 1038 field = field.value(milestone.to_string());
1036 1039 }
@@ -177,7 +177,7 @@
177 177 use chrono::TimeZone as _;
178 178 use goingson_core::{Task, TaskFilterQuery, TaskId, TaskStatus, TimeSession, TimeSessionMode};
179 179 use makeover_layout::Tone;
180 - use quasi_router::screen::{Act, Field, Figure, Meter, Row, Tag};
180 + use quasi_router::screen::{Act, Consult, Field, Figure, Meter, Row, Tag};
181 181 use quasi_router::{
182 182 Action, Chrome, Node, RegionKind, Response, Role, RouteError, Router, Screen, Slot,
183 183 };
@@ -533,16 +533,16 @@
533 533 /// field sends its value under its own name and an address carrying it too
534 534 /// would be answering with the value the user just replaced.
535 535 fn modes(view: View) -> Slot {
536 - let work = bounded("work", "Minutes of work", view.work, 1, 240).writes(
536 + let work = bounded("work", "Minutes of work", view.work, 1, 240).consulting(Consult::new(
537 537 Action::get("/timer")
538 538 .carrying("break", view.rest.to_string())
539 539 .carrying("days", view.days.to_string()),
540 - );
541 - let rest = bounded("break", "Minutes of break", view.rest, 1, 60).writes(
540 + ));
541 + let rest = bounded("break", "Minutes of break", view.rest, 1, 60).consulting(Consult::new(
542 542 Action::get("/timer")
543 543 .carrying("work", view.work.to_string())
544 544 .carrying("days", view.days.to_string()),
545 - );
545 + ));
546 546
547 547 Slot::group("timer-modes")
548 548 .with(Node::section("Track"))