max / goingson
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
3 files changed,
+123 insertions,
-75 deletions
| @@ -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.80.0" | |
| 4720 | + | version = "0.81.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.80.0" | |
| 4729 | + | version = "0.81.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.80.0" | |
| 4737 | + | version = "0.81.0" | |
| 4738 | 4738 | dependencies = [ | |
| 4739 | 4739 | "makeover-layout", | |
| 4740 | 4740 | ] | |
| 4741 | 4741 | ||
| 4742 | 4742 | [[package]] | |
| 4743 | 4743 | name = "quasi-tauri" | |
| 4744 | - | version = "0.80.0" | |
| 4744 | + | version = "0.81.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.80.0" | |
| 4770 | + | version = "0.81.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]] | |
| @@ -8495,15 +8495,15 @@ | |||
| 8495 | 8495 | ||
| 8496 | 8496 | [[patch.unused]] | |
| 8497 | 8497 | name = "quasi-axum" | |
| 8498 | - | version = "0.80.0" | |
| 8498 | + | version = "0.81.0" | |
| 8499 | 8499 | ||
| 8500 | 8500 | [[patch.unused]] | |
| 8501 | 8501 | name = "quasi-basics" | |
| 8502 | - | version = "0.80.0" | |
| 8502 | + | version = "0.81.0" | |
| 8503 | 8503 | ||
| 8504 | 8504 | [[patch.unused]] | |
| 8505 | 8505 | name = "quasi-immediate" | |
| 8506 | - | version = "0.80.0" | |
| 8506 | + | version = "0.81.0" | |
| 8507 | 8507 | ||
| 8508 | 8508 | [[patch.unused]] | |
| 8509 | 8509 | name = "quasi-store" |
| @@ -17,7 +17,8 @@ | |||
| 17 | 17 | //! This is the first screen that is mostly a *report* rather than a list of | |
| 18 | 18 | //! things, and it is the first one whose whole content is scoped by something | |
| 19 | 19 | //! other than an id: every route carries the week. Four things the description | |
| 20 | - | //! could not say turned up, recorded where each one bites. | |
| 20 | + | //! could not say turned up, recorded where each one bites. The third of them is | |
| 21 | + | //! closed: a place awaiting content is a region, ruled 2026-08-30. | |
| 21 | 22 | //! | |
| 22 | 23 | //! # The shape | |
| 23 | 24 | //! | |
| @@ -54,7 +55,7 @@ | |||
| 54 | 55 | }; | |
| 55 | 56 | use goingson_core::{LinkedTaskRef, Task, TaskId}; | |
| 56 | 57 | use makeover_layout::Tone; | |
| 57 | - | use quasi_router::screen::{Act, Field, Figure, Meter, Row, Tag}; | |
| 58 | + | use quasi_router::screen::{Act, Field, Figure, Row, Tag}; | |
| 58 | 59 | use quasi_router::{Action, Node, RegionKind, Response, RouteError, Router, Screen, Slot}; | |
| 59 | 60 | ||
| 60 | 61 | use crate::commands::{focus_blockers, gather_weekly_review}; | |
| @@ -335,61 +336,65 @@ | |||
| 335 | 336 | ||
| 336 | 337 | /// The week's priorities. | |
| 337 | 338 | /// | |
| 338 | - | /// # The third finding | |
| 339 | + | /// # The third finding, closed | |
| 339 | 340 | /// | |
| 340 | - | /// **A place awaiting content has no name in the vocabulary.** | |
| 341 | + | /// **A place awaiting content is a region, and `Slot` already had one.** | |
| 341 | 342 | /// | |
| 342 | 343 | /// `renderFocusSection` always draws three slots. A filled one holds a task; an | |
| 343 | - | /// empty one is a real described thing — it is focusable, it says "Press Enter | |
| 344 | - | /// or click a task to add focus", and it is where a chosen task lands. Two | |
| 345 | - | /// filled slots and one empty slot is not a list of two tasks, and describing | |
| 346 | - | /// it as one loses that there is room for a third. | |
| 344 | + | /// empty one is a real described thing: it is reachable, it is named, and it is | |
| 345 | + | /// where a chosen task lands. Two filled slots and one empty slot is not a list | |
| 346 | + | /// of two tasks, and describing it as one lost that there is room for a third. | |
| 347 | 347 | /// | |
| 348 | - | /// Every way of saying it inside the vocabulary is worse than not saying it. | |
| 349 | - | /// A row reading "Priority 3 - empty" is a row standing for nothing, which is | |
| 350 | - | /// the thing [`Node::StandIn`] exists to avoid doing per-item, and it would | |
| 351 | - | /// carry no action because the JS's slot is a drop target rather than a control. | |
| 348 | + | /// The port said the fact and dropped the furniture, as a `Meter` of how many | |
| 349 | + | /// of the three were taken. Ruled 2026-08-30 (quasicoherent `df57ed16`): three | |
| 350 | + | /// [`Slot`]s, one per priority, each [`named`](Slot::named). A region is exactly | |
| 351 | + | /// a named place, so this costs no vocabulary at all. It is heavy — three | |
| 352 | + | /// regions for three slots — but the weight sits in the description rather than | |
| 353 | + | /// in new words, which is the cheaper place to carry it. | |
| 352 | 354 | /// | |
| 353 | - | /// So the description says the fact and drops the furniture: a | |
| 354 | - | /// [`Meter`] of how many of the three are taken, then the chosen ones, then | |
| 355 | - | /// what could fill the rest. A renderer with three boxes to draw has the number | |
| 356 | - | /// it needs to draw them, and one without does not have to pretend. | |
| 355 | + | /// Rejected with it: extending [`Node::StandIn`] to stand for a *place* rather | |
| 356 | + | /// than a missing item. `StandIn` exists to stop a fake row appearing per | |
| 357 | + | /// absence, and a place that is empty and can be landed on carries an identity | |
| 358 | + | /// and a target; one member covering both would blur what `StandIn` is for. It | |
| 359 | + | /// still says what is inside an empty region, which is nothing, and that is the | |
| 360 | + | /// job it has. | |
| 357 | 361 | /// | |
| 358 | - | /// Filed against quasicoherent rather than closed here. It is the same shape as | |
| 359 | - | /// the row ruling of 2026-08-08 and probably ends the same way — a slot is a | |
| 360 | - | /// region, and `Slot` already exists — but a region per empty priority is a | |
| 361 | - | /// heavy answer to a light question and it wants a second consumer before | |
| 362 | - | /// anyone reaches for it. | |
| 362 | + | /// The meter goes rather than standing beside them. Three named regions carry | |
| 363 | + | /// the count they were a summary of, and a bar reading "1 of 3" next to three | |
| 364 | + | /// places one of which is full is the same fact drawn twice. | |
| 365 | + | /// | |
| 366 | + | /// # More focused than there are slots | |
| 367 | + | /// | |
| 368 | + | /// `tasks.is_focus` is one column and nothing enforces [`FOCUS_SLOTS`], so a | |
| 369 | + | /// fourth focused task is possible and must not vanish into a description that | |
| 370 | + | /// only ever draws three. The count of places is the greater of the two, so | |
| 371 | + | /// overflow reads as a fourth priority rather than as a task that stopped | |
| 372 | + | /// existing. | |
| 363 | 373 | fn focus(data: &WeeklyReviewData, waits: &FocusBlockers, week: NaiveDate) -> Vec<Node> { | |
| 364 | 374 | let taken = data.focused_tasks.len(); | |
| 365 | - | let mut out = vec![ | |
| 366 | - | Node::section("This Week's Focus"), | |
| 367 | - | Node::Meter( | |
| 368 | - | Meter::new( | |
| 369 | - | u32::try_from(taken).unwrap_or(u32::MAX), | |
| 370 | - | u32::try_from(FOCUS_SLOTS).unwrap_or(u32::MAX), | |
| 371 | - | ) | |
| 372 | - | // Neutral, not success: a full focus list is a week that has been | |
| 373 | - | // planned, and there is nothing good or bad about the number | |
| 374 | - | // itself. The subtask bar's Success would read as "well done for | |
| 375 | - | // picking three". | |
| 376 | - | .label("priorities"), | |
| 377 | - | ), | |
| 378 | - | ]; | |
| 375 | + | let mut out = vec![Node::section("This Week's Focus")]; | |
| 379 | 376 | ||
| 380 | - | if data.focused_tasks.is_empty() { | |
| 381 | - | out.push(Node::empty("No priorities picked for this week")); | |
| 382 | - | } else { | |
| 383 | - | out.push(Node::list(data.focused_tasks.iter().map(|task| { | |
| 384 | - | task_row(task).act(Act::new( | |
| 377 | + | for index in 0..FOCUS_SLOTS.max(taken) { | |
| 378 | + | let place = Slot::group(format!("weekly-focus-{}", index + 1)) | |
| 379 | + | .named(format!("Priority {}", index + 1)); | |
| 380 | + | out.push(Node::Region(match data.focused_tasks.get(index) { | |
| 381 | + | Some(task) => place.with(Node::list([task_row(task).act(Act::new( | |
| 385 | 382 | "Remove", | |
| 386 | 383 | in_week( | |
| 387 | 384 | Action::post(format!("/weekly-review/focus/{}", task.id)) | |
| 388 | 385 | .with("focus", "false"), | |
| 389 | 386 | week, | |
| 390 | 387 | ), | |
| 391 | - | )) | |
| 392 | - | }))); | |
| 388 | + | ))])), | |
| 389 | + | // Nothing about how a task gets here: the suggestions below carry | |
| 390 | + | // the only address that fills a place, and a browser's drop target | |
| 391 | + | // and a terminal's Enter are two renderers' answers to that one | |
| 392 | + | // fact rather than something to name once in words. | |
| 393 | + | None => place.with(Node::empty("Open")), | |
| 394 | + | })); | |
| 395 | + | } | |
| 396 | + | ||
| 397 | + | if taken > 0 { | |
| 393 | 398 | out.push(Node::act( | |
| 394 | 399 | "Clear all focus", | |
| 395 | 400 | in_week(Action::post("/weekly-review/focus/clear"), week), |
| @@ -143,10 +143,10 @@ | |||
| 143 | 143 | } | |
| 144 | 144 | ||
| 145 | 145 | #[tokio::test] | |
| 146 | - | async fn the_focus_says_how_many_slots_are_taken_and_draws_no_empty_ones() { | |
| 147 | - | // The third finding. Three slots always exist in the JS; the description | |
| 148 | - | // has no name for a place awaiting content, so it says the count and stops | |
| 149 | - | // rather than emitting rows that stand for nothing. | |
| 146 | + | async fn the_focus_draws_three_named_places_and_the_empty_ones_are_reachable() { | |
| 147 | + | // The third finding, closed 2026-08-30 (quasicoherent `df57ed16`): three | |
| 148 | + | // slots always exist in the JS, and a place awaiting content is a region. | |
| 149 | + | // The meter that stood in for them is gone with the finding. | |
| 150 | 150 | let state = state().await; | |
| 151 | 151 | let task = add(&state, "The one thing"); | |
| 152 | 152 | post( | |
| @@ -158,11 +158,53 @@ | |||
| 158 | 158 | let page = review(&state); | |
| 159 | 159 | ||
| 160 | 160 | assert!(page.contains("The one thing")); | |
| 161 | - | // One of three, as a bar rather than as two empty boxes. | |
| 162 | - | assert!(page.contains(r#"aria-valuenow="1""#)); | |
| 163 | - | assert!(page.contains(r#"aria-valuemax="3""#)); | |
| 164 | - | assert!(!page.contains("Priority #2")); | |
| 165 | - | assert!(!page.contains("Priority 2")); | |
| 161 | + | for slot in 1..=3 { | |
| 162 | + | assert!( | |
| 163 | + | page.contains(&format!(r#"id="weekly-focus-{slot}""#)), | |
| 164 | + | "priority {slot} is a place whether or not it is taken" | |
| 165 | + | ); | |
| 166 | + | assert!( | |
| 167 | + | page.contains(&format!(r#"aria-label="Priority {slot}""#)), | |
| 168 | + | "priority {slot} says which one it is" | |
| 169 | + | ); | |
| 170 | + | } | |
| 171 | + | // A `section` with a name, which is what makes an empty one reachable | |
| 172 | + | // rather than a nameless div nothing announces. | |
| 173 | + | assert!(page.contains(r#"<section id="weekly-focus-2""#)); | |
| 174 | + | // The one that is taken holds the task; the two nobody has filled say so | |
| 175 | + | // inside their own region rather than as a row standing for nothing. | |
| 176 | + | let second = page | |
| 177 | + | .split(r#"id="weekly-focus-2""#) | |
| 178 | + | .nth(1) | |
| 179 | + | .expect("the second place is drawn"); | |
| 180 | + | assert!(second[..second.find("weekly-focus-3").unwrap_or(second.len())].contains("Open")); | |
| 181 | + | // The meter the finding stood in for is gone with it. | |
| 182 | + | assert!(!page.contains("aria-valuemax")); | |
| 183 | + | } | |
| 184 | + | ||
| 185 | + | #[tokio::test] | |
| 186 | + | async fn a_fourth_focused_task_gets_a_fourth_place_rather_than_disappearing() { | |
| 187 | + | // `tasks.is_focus` is one column and nothing enforces the three, so the | |
| 188 | + | // count of places is the greater of the two. Three regions and four | |
| 189 | + | // focused tasks would be a task that stopped existing. | |
| 190 | + | let state = state().await; | |
| 191 | + | for index in 1..=4 { | |
| 192 | + | let task = add(&state, &format!("Priority task {index}")); | |
| 193 | + | post( | |
| 194 | + | &state, | |
| 195 | + | &format!("/weekly-review/focus/{}", task.id), | |
| 196 | + | Params::new().with("focus", "true"), | |
| 197 | + | ); | |
| 198 | + | } | |
| 199 | + | ||
| 200 | + | let page = review(&state); | |
| 201 | + | ||
| 202 | + | for index in 1..=4 { | |
| 203 | + | assert!(page.contains(&format!("Priority task {index}"))); | |
| 204 | + | } | |
| 205 | + | assert!(page.contains(r#"aria-label="Priority 4""#)); | |
| 206 | + | // And nothing offers a fifth place nobody is standing in. | |
| 207 | + | assert!(!page.contains(r#"aria-label="Priority 5""#)); | |
| 166 | 208 | } | |
| 167 | 209 | ||
| 168 | 210 | #[tokio::test] | |
| @@ -177,15 +219,16 @@ | |||
| 177 | 219 | Params::new().with("focus", "true").with("week", &week), | |
| 178 | 220 | )); | |
| 179 | 221 | assert!(focused.contains("Remove")); | |
| 180 | - | assert!(focused.contains(r#"aria-valuenow="1""#)); | |
| 222 | + | assert!(focused.contains(r#"aria-label="Priority 1""#)); | |
| 181 | 223 | ||
| 182 | 224 | let cleared = html(post( | |
| 183 | 225 | &state, | |
| 184 | 226 | &format!("/weekly-review/focus/{}", task.id), | |
| 185 | 227 | Params::new().with("focus", "false").with("week", &week), | |
| 186 | 228 | )); | |
| 187 | - | assert!(cleared.contains(r#"aria-valuenow="0""#)); | |
| 188 | - | assert!(cleared.contains("No priorities picked for this week")); | |
| 229 | + | // Every place is open again, and each still says which one it is. | |
| 230 | + | assert!(!cleared.contains("Remove")); | |
| 231 | + | assert!(cleared.contains(r#"aria-label="Priority 3""#)); | |
| 189 | 232 | } | |
| 190 | 233 | ||
| 191 | 234 | #[tokio::test] | |
| @@ -202,7 +245,7 @@ | |||
| 202 | 245 | ||
| 203 | 246 | let page = html(post(&state, "/weekly-review/focus/clear", Params::new())); | |
| 204 | 247 | ||
| 205 | - | assert!(page.contains(r#"aria-valuenow="0""#)); | |
| 248 | + | assert!(page.contains(r#"aria-label="Priority 1""#)); | |
| 206 | 249 | // Nothing is holding a slot, so nothing offers to give one up. The task | |
| 207 | 250 | // itself comes straight back under Suggested, which is the point of | |
| 208 | 251 | // clearing rather than a leak. |