Skip to main content

max / quasi

0.41.0: a control in a table cell is reached by stepping into the row `27f2331e`. Thirty table rows across the MNW templates carry a control in a cell, in 25 files, and a terminal could draw every one of them and reach none. The reason is layout: `makeover_tui::table` decides column widths, drops the columns a narrow window has no room for and answers no coordinates back, so nothing in quasi-tui knows where in a row a button ended up, and a focus order is a list of places the caret can stand. Max ruled it on 2026-08-18: **the row is one focus stop, and a key enters it and cycles the controls inside**. No coordinates, and no new public surface on a published makeover-tui. Per-cell rects stay available underneath this later without changing what a user does. The open half of the ruling was which key, and it is Right and Left. Enter is taken -- `Cells::activate` already means "this row opens", and every measured table with a control in a cell also opens its rows -- and Right and Left are the keys a grid is walked with everywhere else. They mean this before they mean the region they move: the caret standing on a row with controls in it beats the screen-wide search that arm falls back to, so a table inside a carousel steps between frames from any stop but that one. The smaller cost of the two, because the frames are reachable from everywhere else and the row's controls are reachable from nowhere else. Escape steps out before it dismisses or goes back, and the short circuit is what keeps one press from doing two of the three. `Spot::Row` gains `inside`, populated only for a table row: a list row's run is walked straight after the row, so its controls are stops of their own and there is nothing to step into. `View` gains the eighth thing a browser was quietly providing, `inside: Option<usize>`, held beside `focus` rather than folded into it so that nothing counting reachable things has to know a table is different. A control the caret has stepped onto draws with the focus style inside the row's own highlight, which is what the two-step has instead of a ring around a rect. `focus.rs` and `draw_table` now decide a row's reachability with one function (`row_reachable`) and pick the lit part from one list (`inside`), because that pair has drifted before: the walk and the count disagreeing shifts every stop below the table by one. A row that neither opens nor ticks is a stop now when its cells carry a control, which is the third case and the one this task was filed for. Not reachable inside a cell: a disabled control, as everywhere else, and an `Act::asks` that wants boxes a cell has no room to draw -- a stop on that would fire it with nothing gathered. quasi-webview and quasi-immediate need nothing. The browser builds the tab order from the document, and egui's cell controls are real widgets it focuses itself.
Author: Max Johnson <me@maxj.phd> · 2026-08-20 16:09 UTC
Signed with PGP, not checked
Commit: 6442b03ba4b4507df055ce42402e1296d743bfb1
Parent: cb028ce
18 files changed, +618 insertions, -93 deletions
M Cargo.lock +23 -23
@@ -3404,11 +3404,11 @@
3404 3404
3405 3405 [[package]]
3406 3406 name = "quasi"
3407 - version = "0.40.0"
3407 + version = "0.41.0"
3408 3408
3409 3409 [[package]]
3410 3410 name = "quasi-axum"
3411 - version = "0.40.0"
3411 + version = "0.41.0"
3412 3412 dependencies = [
3413 3413 "axum",
3414 3414 "http",
@@ -3422,7 +3422,7 @@
3422 3422
3423 3423 [[package]]
3424 3424 name = "quasi-basics"
3425 - version = "0.40.0"
3425 + version = "0.41.0"
3426 3426 dependencies = [
3427 3427 "makeover-layout",
3428 3428 "quasi-http",
@@ -3432,7 +3432,7 @@
3432 3432
3433 3433 [[package]]
3434 3434 name = "quasi-bench"
3435 - version = "0.40.0"
3435 + version = "0.41.0"
3436 3436 dependencies = [
3437 3437 "dhat",
3438 3438 "makeover",
@@ -3447,7 +3447,7 @@
3447 3447
3448 3448 [[package]]
3449 3449 name = "quasi-http"
3450 - version = "0.40.0"
3450 + version = "0.41.0"
3451 3451 dependencies = [
3452 3452 "form_urlencoded",
3453 3453 "http",
@@ -3456,7 +3456,7 @@
3456 3456
3457 3457 [[package]]
3458 3458 name = "quasi-immediate"
3459 - version = "0.40.0"
3459 + version = "0.41.0"
3460 3460 dependencies = [
3461 3461 "docengine",
3462 3462 "egui",
@@ -3466,7 +3466,7 @@
3466 3466
3467 3467 [[package]]
3468 3468 name = "quasi-notifs"
3469 - version = "0.40.0"
3469 + version = "0.41.0"
3470 3470 dependencies = [
3471 3471 "quasi-router",
3472 3472 "synckit-config",
@@ -3474,7 +3474,7 @@
3474 3474
3475 3475 [[package]]
3476 3476 name = "quasi-router"
3477 - version = "0.40.0"
3477 + version = "0.41.0"
3478 3478 dependencies = [
3479 3479 "makeover-layout",
3480 3480 ]
@@ -3491,7 +3491,7 @@
3491 3491
3492 3492 [[package]]
3493 3493 name = "quasi-tauri"
3494 - version = "0.40.0"
3494 + version = "0.41.0"
3495 3495 dependencies = [
3496 3496 "http",
3497 3497 "quasi-http",
@@ -3504,7 +3504,7 @@
3504 3504
3505 3505 [[package]]
3506 3506 name = "quasi-tui"
3507 - version = "0.40.0"
3507 + version = "0.41.0"
3508 3508 dependencies = [
3509 3509 "docengine",
3510 3510 "makeover",
@@ -3516,7 +3516,7 @@
3516 3516
3517 3517 [[package]]
3518 3518 name = "quasi-webview"
3519 - version = "0.40.0"
3519 + version = "0.41.0"
3520 3520 dependencies = [
3521 3521 "docengine",
3522 3522 "makeover-layout",
@@ -6187,18 +6187,6 @@
6187 6187 "winnow 1.0.4",
6188 6188 ]
6189 6189
6190 - [[patch.unused]]
6191 - name = "makeover-build"
6192 - version = "0.47.0"
6193 -
6194 - [[patch.unused]]
6195 - name = "quasi-type"
6196 - version = "0.1.0"
6197 -
6198 - [[patch.unused]]
6199 - name = "synckit-client"
6200 - version = "0.8.0"
6201 -
6202 6190 [[patch.unused]]
6203 6191 name = "kberg"
6204 6192 version = "0.1.0"
@@ -6214,3 +6202,15 @@
6214 6202 [[patch.unused]]
6215 6203 name = "tagtree"
6216 6204 version = "0.4.0"
6205 +
6206 + [[patch.unused]]
6207 + name = "quasi-type"
6208 + version = "0.1.0"
6209 +
6210 + [[patch.unused]]
6211 + name = "synckit-client"
6212 + version = "0.8.0"
6213 +
6214 + [[patch.unused]]
6215 + name = "makeover-build"
6216 + version = "0.47.0"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-axum"
3 - version = "0.40.0"
3 + version = "0.41.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.40.0" }
17 - quasi-http = { path = "../quasi-http", version = "0.40.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.41.0" }
17 + quasi-http = { path = "../quasi-http", version = "0.41.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.40.0" }
23 + quasi-webview = { path = "../quasi-webview", version = "0.41.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.40.0"
3 + version = "0.41.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.40.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.41.0" }
17 17 makeover-layout = "0.31.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.40.0" }
24 - quasi-http = { path = "../quasi-http", version = "0.40.0" }
23 + quasi-webview = { path = "../quasi-webview", version = "0.41.0" }
24 + quasi-http = { path = "../quasi-http", version = "0.41.0" }
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-bench"
3 - version = "0.40.0"
3 + version = "0.41.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,13 +22,13 @@
22 22 count = ["dep:dhat"]
23 23
24 24 [dependencies]
25 - quasi-router = { path = "../quasi-router", version = "0.40.0" }
26 - quasi-webview = { path = "../quasi-webview", version = "0.40.0" }
27 - quasi-tui = { path = "../quasi-tui", version = "0.40.0" }
25 + quasi-router = { path = "../quasi-router", version = "0.41.0" }
26 + quasi-webview = { path = "../quasi-webview", version = "0.41.0" }
27 + quasi-tui = { path = "../quasi-tui", version = "0.41.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.40.0" }
31 + quasi-http = { path = "../quasi-http", version = "0.41.0" }
32 32 makeover-layout = "0.31.0"
33 33 makeover-tui = { version = "0.30.0", features = ["theme"] }
34 34 # Only to load a bundled theme file. `makeover_tui::Theme` is `#[non_exhaustive]`,
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-http"
3 - version = "0.40.0"
3 + version = "0.41.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.40.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.41.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.40.0"
3 + version = "0.41.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,7 +12,7 @@
12 12 categories = ["gui"]
13 13
14 14 [dependencies]
15 - quasi-router = { path = "../quasi-router", version = "0.40.0" }
15 + quasi-router = { path = "../quasi-router", version = "0.41.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
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-notifs"
3 - version = "0.40.0"
3 + version = "0.41.0"
4 4 description = "Declared notification kinds and the registry that holds them: what a notification says and what it is for, apart from how it reaches a person"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -24,5 +24,5 @@
24 24 describe = ["dep:quasi-router"]
25 25
26 26 [dependencies]
27 - quasi-router = { path = "../quasi-router", version = "0.40.0", optional = true }
27 + quasi-router = { path = "../quasi-router", version = "0.41.0", optional = true }
28 28 synckit-config = { git = "https://makenot.work/git/max/synckit.git", version = "0.2", optional = true }
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-router"
3 - version = "0.40.0"
3 + version = "0.41.0"
4 4 description = "Host-agnostic router: a request in, a renderer-agnostic description out"
5 5 edition.workspace = true
6 6 rust-version.workspace = true