Skip to main content

max / quasi

Say when a selection is live, and what a press on it meant quasi 0.78.0. `Cells::current` was the only thing that lit a row, so an app whose list carries a real multi-selection could not draw one: audiofiles' `Cmd+A` over five hundred samples looked exactly like a click on one. And nothing could say a press was ctrl-held, so the only multi-select it had left was the keyboard. Picking eleven kicks out of four hundred by hand -- close to what that app is for -- could not be done at all. `1894e95d`. Two members, and the split between them is the point. `Cells::chosen` and `Row::chosen` say the row is part of a selection already in force. The third state, and three-valued for the reason `selected` is: a ctrl-click lands on an *unchosen* row, so a renderer has to know that row can join a set before anything is in one. It is never a checkbox -- a box is the affordance for staging a set, and this set is not staged -- and the app owns the set rather than the renderer holding a copy that can disagree. `Choosing { Only, Also, Through }` is what a press meant, said by the renderer because the idiom is the host's, travelling under `Node::CHOOSING`. It looks like input state, which every ruling here has refused to carry, and the difference is that it is not the gesture but its *meaning*: a description carrying "ctrl was held" would be naming a keyboard, and this names an intention a touch host can honour with none. The test it passes and a modifier would not is that a terminal can implement it. The three renderers: - egui highlights a chosen row through the same `Body::selected` predicate `current` already used, and reads command/shift off the frame. Shift wins when both are held, which `Choosing` rules on rather than leaving to each renderer. - The terminal marks chosen rows in the gutter it already draws ticks in, and binds Enter to `Only` and Space to `Also`. `Through` gets no key: `Key` carries no modifiers, and widening it is a change to every host driving that renderer. Stated rather than invented around. - The webview emits `row-chosen`/`table-row-chosen` and `aria-selected`, which is the one place ARIA's own names line up with the vocabulary's distinction, and computes the modifier at click time through a `js:` `hx-vals`. `Anchor::Selection` resolves against a live selection now and not only against staged ticks, which is what it was written for: audiofiles was the measured consumer and could not take it. ALSO CARRIED, and not mine: an uncommitted `Outcome::Started` / `Slot::consults` change and the makeover-layout 0.39 cascade were in this working tree from a parallel session, interleaved with this in `screen.rs`. They cannot be separated by file, so they land here. Split them out if the history matters.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_0153yKAYUmoC9bZz2eV6cA88
Author: Max Johnson <me@maxj.phd> · 2026-08-29 15:56 UTC
Signed with PGP, not checked
Commit: 45be08830f6790379b90edda91bfc0c58c4b3cb4
Parent: 571d964
32 files changed, +2169 insertions, -233 deletions
M Cargo.lock +29 -29
@@ -2605,9 +2605,9 @@
2605 2605
2606 2606 [[package]]
2607 2607 name = "makeover-immediate"
2608 - version = "0.39.0"
2608 + version = "0.40.0"
2609 2609 source = "registry+https://github.com/rust-lang/crates.io-index"
2610 - checksum = "628f2e478a4bd00a305cc7b43ee1bdc229ccd8dd9c7077ba7d7cc1f85ba6994c"
2610 + checksum = "ebf95b374f071aac2094c22d1d90ee565ae920adb9950195c072352ee4da2f01"
2611 2611 dependencies = [
2612 2612 "egui",
2613 2613 "egui_extras",
@@ -2617,9 +2617,9 @@
2617 2617
2618 2618 [[package]]
2619 2619 name = "makeover-layout"
2620 - version = "0.38.0"
2620 + version = "0.39.0"
2621 2621 source = "registry+https://github.com/rust-lang/crates.io-index"
2622 - checksum = "73ad4a03ed87470b838bd84be586739682f0a1210db1d7ad4a0146cb9ef7734b"
2622 + checksum = "253ab4630d39ec39233020b81024773da52e93b15544217034ce6ca083ba9227"
2623 2623
2624 2624 [[package]]
2625 2625 name = "makeover-timing"
@@ -2632,9 +2632,9 @@
2632 2632
2633 2633 [[package]]
2634 2634 name = "makeover-touch"
2635 - version = "0.28.0"
2635 + version = "0.29.0"
2636 2636 source = "registry+https://github.com/rust-lang/crates.io-index"
2637 - checksum = "10693ddd1a7c5aad0289bcee2bb02c25cef10e292820a60a2813d21411217a83"
2637 + checksum = "e49a886415501f073a2e24e21c07970ac8be6e0549ca0a2986bce5062b9fa8b1"
2638 2638 dependencies = [
2639 2639 "makeover-geometry",
2640 2640 "makeover-layout",
@@ -2642,9 +2642,9 @@
2642 2642
2643 2643 [[package]]
2644 2644 name = "makeover-tui"
2645 - version = "0.38.0"
2645 + version = "0.39.0"
2646 2646 source = "registry+https://github.com/rust-lang/crates.io-index"
2647 - checksum = "0b2702bf3d12d4f978607a6209d5ebc9786e46adb13b2ee2c704881b391ec1bd"
2647 + checksum = "b5fd2983f561c0a1698508a35e12a16330b0706469dc48dcb2742ea5708e6a16"
2648 2648 dependencies = [
2649 2649 "makeover",
2650 2650 "makeover-layout",
@@ -2654,9 +2654,9 @@
2654 2654
2655 2655 [[package]]
2656 2656 name = "makeover-webview"
2657 - version = "0.65.0"
2657 + version = "0.66.0"
2658 2658 source = "registry+https://github.com/rust-lang/crates.io-index"
2659 - checksum = "899e27bcbcf2d8de779c0404ca5e1e354d9ff0fa9d834f1855c5202d0271c80f"
2659 + checksum = "38d8db2ca46e821e19050b3d8bae17cf542b694f028210eb2ac9738faed127c2"
2660 2660 dependencies = [
2661 2661 "makeover-geometry",
2662 2662 "makeover-layout",
@@ -3425,11 +3425,11 @@
3425 3425
3426 3426 [[package]]
3427 3427 name = "quasi"
3428 - version = "0.77.0"
3428 + version = "0.78.0"
3429 3429
3430 3430 [[package]]
3431 3431 name = "quasi-axum"
3432 - version = "0.77.0"
3432 + version = "0.78.0"
3433 3433 dependencies = [
3434 3434 "axum",
3435 3435 "http",
@@ -3443,7 +3443,7 @@
3443 3443
3444 3444 [[package]]
3445 3445 name = "quasi-basics"
3446 - version = "0.77.0"
3446 + version = "0.78.0"
3447 3447 dependencies = [
3448 3448 "makeover-layout",
3449 3449 "quasi-http",
@@ -3453,7 +3453,7 @@
3453 3453
3454 3454 [[package]]
3455 3455 name = "quasi-bench"
3456 - version = "0.77.0"
3456 + version = "0.78.0"
3457 3457 dependencies = [
3458 3458 "dhat",
3459 3459 "makeover",
@@ -3468,7 +3468,7 @@
3468 3468
3469 3469 [[package]]
3470 3470 name = "quasi-http"
3471 - version = "0.77.0"
3471 + version = "0.78.0"
3472 3472 dependencies = [
3473 3473 "form_urlencoded",
3474 3474 "http",
@@ -3477,7 +3477,7 @@
3477 3477
3478 3478 [[package]]
3479 3479 name = "quasi-immediate"
3480 - version = "0.77.0"
3480 + version = "0.78.0"
3481 3481 dependencies = [
3482 3482 "docengine",
3483 3483 "egui",
@@ -3488,7 +3488,7 @@
3488 3488
3489 3489 [[package]]
3490 3490 name = "quasi-notifs"
3491 - version = "0.77.0"
3491 + version = "0.78.0"
3492 3492 dependencies = [
3493 3493 "quasi-router",
3494 3494 "synckit-config",
@@ -3496,7 +3496,7 @@
3496 3496
3497 3497 [[package]]
3498 3498 name = "quasi-router"
3499 - version = "0.77.0"
3499 + version = "0.78.0"
3500 3500 dependencies = [
3501 3501 "makeover-layout",
3502 3502 ]
@@ -3513,7 +3513,7 @@
3513 3513
3514 3514 [[package]]
3515 3515 name = "quasi-tauri"
3516 - version = "0.77.0"
3516 + version = "0.78.0"
3517 3517 dependencies = [
3518 3518 "http",
3519 3519 "quasi-http",
@@ -3526,7 +3526,7 @@
3526 3526
3527 3527 [[package]]
3528 3528 name = "quasi-tui"
3529 - version = "0.77.0"
3529 + version = "0.78.0"
3530 3530 dependencies = [
3531 3531 "docengine",
3532 3532 "makeover",
@@ -3539,7 +3539,7 @@
3539 3539
3540 3540 [[package]]
3541 3541 name = "quasi-webview"
3542 - version = "0.77.0"
3542 + version = "0.78.0"
3543 3543 dependencies = [
3544 3544 "docengine",
3545 3545 "makeover-layout",
@@ -6211,6 +6211,14 @@
6211 6211 "winnow 1.0.4",
6212 6212 ]
6213 6213
6214 + [[patch.unused]]
6215 + name = "synckit-client"
6216 + version = "0.10.0"
6217 +
6218 + [[patch.unused]]
6219 + name = "quasi-type"
6220 + version = "0.1.2"
6221 +
6214 6222 [[patch.unused]]
6215 6223 name = "kberg"
6216 6224 version = "0.1.0"
@@ -6226,11 +6234,3 @@
6226 6234 [[patch.unused]]
6227 6235 name = "tagtree"
6228 6236 version = "0.4.1"
6229 -
6230 - [[patch.unused]]
6231 - name = "synckit-client"
6232 - version = "0.10.0"
6233 -
6234 - [[patch.unused]]
6235 - name = "quasi-type"
6236 - version = "0.1.2"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-axum"
3 - version = "0.77.0"
3 + version = "0.78.0"
4 4 description = "The axum host adapter for quasi-router: an HTTP request in, a rendered description out"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -13,14 +13,14 @@
13 13 workspace = true
14 14
15 15 [dependencies]
16 - quasi-router = { path = "../quasi-router", version = "0.77.0" }
17 - quasi-http = { path = "../quasi-http", version = "0.77.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.78.0" }
17 + quasi-http = { path = "../quasi-http", version = "0.78.0" }
18 18 axum = "0.8.8"
19 19 http = "1.3.1"
20 20 tokio = { version = "1.50.0", features = ["rt"] }
21 21
22 22 [dev-dependencies]
23 - quasi-webview = { path = "../quasi-webview", version = "0.77.0" }
23 + quasi-webview = { path = "../quasi-webview", version = "0.78.0" }
24 24 tokio = { version = "1.50.0", features = ["macros", "rt-multi-thread"] }
25 25 tower = { version = "0.5.3", features = ["util"] }
26 26 http-body-util = "0.1.3"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-basics"
3 - version = "0.77.0"
3 + version = "0.78.0"
4 4 description = "The first-party widget set: named assemblies of primitives, shared across our apps"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -13,12 +13,12 @@
13 13 workspace = true
14 14
15 15 [dependencies]
16 - quasi-router = { path = "../quasi-router", version = "0.77.0" }
17 - makeover-layout = "0.38.0"
16 + quasi-router = { path = "../quasi-router", version = "0.78.0" }
17 + makeover-layout = "0.39.0"
18 18
19 19 [dev-dependencies]
20 20 # A widget's guarantees are claims about what a renderer draws, so they are
21 21 # tested against a real one rather than by walking the tree the assembly just
22 22 # built. The webview is the renderer that recognises names today.
23 - quasi-webview = { path = "../quasi-webview", version = "0.77.0" }
24 - quasi-http = { path = "../quasi-http", version = "0.77.0" }
23 + quasi-webview = { path = "../quasi-webview", version = "0.78.0" }
24 + quasi-http = { path = "../quasi-http", version = "0.78.0" }
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-bench"
3 - version = "0.77.0"
3 + version = "0.78.0"
4 4 description = "What a described screen costs to render, in time and in allocations"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -22,15 +22,15 @@
22 22 count = ["dep:dhat"]
23 23
24 24 [dependencies]
25 - quasi-router = { path = "../quasi-router", version = "0.77.0" }
26 - quasi-webview = { path = "../quasi-webview", version = "0.77.0" }
27 - quasi-tui = { path = "../quasi-tui", version = "0.77.0" }
25 + quasi-router = { path = "../quasi-router", version = "0.78.0" }
26 + quasi-webview = { path = "../quasi-webview", version = "0.78.0" }
27 + quasi-tui = { path = "../quasi-tui", version = "0.78.0" }
28 28 # `Serves` is the trait carrying `screen` and `fragment`, which is what the
29 29 # webview is measured through. Taken directly rather than through quasi-webview
30 30 # because a bench calling a trait method should name the trait it calls.
31 - quasi-http = { path = "../quasi-http", version = "0.77.0" }
32 - makeover-layout = "0.38.0"
33 - makeover-tui = { version = "0.38.0", features = ["theme"] }
31 + quasi-http = { path = "../quasi-http", version = "0.78.0" }
32 + makeover-layout = "0.39.0"
33 + makeover-tui = { version = "0.39.0", features = ["theme"] }
34 34 # Only to load a bundled theme file. `makeover_tui::Theme` is `#[non_exhaustive]`,
35 35 # so `Theme::from_theme` is the one way to get one, the same reason quasi-tui's
36 36 # tests take this.
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-http"
3 - version = "0.77.0"
3 + version = "0.78.0"
4 4 description = "The http-shaped seam quasi's webview host adapters share: decoding in, a rendered description out"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -13,6 +13,6 @@
13 13 workspace = true
14 14
15 15 [dependencies]
16 - quasi-router = { path = "../quasi-router", version = "0.77.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.78.0" }
17 17 http = "1.3.1"
18 18 form_urlencoded = "1.2.2"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-immediate"
3 - version = "0.77.0"
3 + version = "0.78.0"
4 4 edition = "2024"
5 5 description = "The immediate-mode renderer for quasi-router: a described screen in, an egui frame out. Immediate mode is the constraint that matters, not the library."
6 6 license = "MIT"
@@ -12,12 +12,12 @@
12 12 categories = ["gui"]
13 13
14 14 [dependencies]
15 - quasi-router = { path = "../quasi-router", version = "0.77.0" }
15 + quasi-router = { path = "../quasi-router", version = "0.78.0" }
16 16 # The node drawing, which is the makeover layer's and not this crate's. Every
17 17 # widget here that is not a container comes from it: a screen walk that painted
18 18 # its own meter would be the divergence the suite exists to end, one copy per
19 19 # renderer instead of one copy per app.
20 - makeover-immediate = "0.39.0"
20 + makeover-immediate = "0.40.0"
21 21 # The one place a duration is named rather than numbered. `Intent::Dismiss` is
22 22 # how long a transient notice lives, and `notice_lifetime` is the seam: the
23 23 # description says a toast goes away on its own and declines to say when, so