Skip to main content

max / goingson

Describe the week's three priority slots as three regions The port said the fact and dropped the furniture: a Meter of how many of the three were taken, because the vocabulary had no name for a place awaiting content. It does, and it always did. Three groups, one per priority, each named, so an empty one is a labelled section a reader can reach rather than a number standing in for it. The meter goes rather than standing beside them: three named places carry the count they were a summary of. Nothing enforces the three in storage, so the count of places is the greater of three and what is focused, and a fourth focused task reads as a fourth priority instead of vanishing. Closes the third finding of the weekly-review port, ruled in quasicoherent df57ed16.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
Author: Max Johnson <me@maxj.phd> · 2026-08-30 15:35 UTC
Signed with PGP, not checked
Commit: ea3bd6fceaf76b79f2c5dc8f3cf1d34714647f95
Parent: f8ece02
3 files changed, +123 insertions, -75 deletions
M Cargo.lock +20 -20
@@ -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.