Skip to main content

max / quasi

0.4.0: a picture carries its size and when it is wanted Picture gains intrinsic and loading, mirroring makeover-layout 0.22.0, and quasi-webview writes width/height so the browser holds the picture's place from first paint. loading="lazy" is emitted only when the description asks for it now, rather than for every picture including the one on screen. A minor because Picture gained public fields and Node is not non_exhaustive.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-14 21:16 UTC
Signed with PGP, not checked
Commit: f25fe7cceb22b1d5582e93998a5083025bb23738
Parent: a327a6d
10 files changed, +82 insertions, -47 deletions
M Cargo.lock +13 -19
@@ -2297,8 +2297,6 @@
2297 2297 [[package]]
2298 2298 name = "makeover-immediate"
2299 2299 version = "0.19.0"
2300 - source = "registry+https://github.com/rust-lang/crates.io-index"
2301 - checksum = "f6ab1b769d31a5866e84ee45356fae9487f2a889881c55c89e418da8f75342fc"
2302 2300 dependencies = [
2303 2301 "egui",
2304 2302 "egui_extras",
@@ -2307,15 +2305,11 @@
2307 2305
2308 2306 [[package]]
2309 2307 name = "makeover-layout"
2310 - version = "0.21.0"
2311 - source = "registry+https://github.com/rust-lang/crates.io-index"
2312 - checksum = "cd297f0832ba18e17409931a57d8c2272cbae5a5d57cf3370899b23af32f15e0"
2308 + version = "0.22.0"
2313 2309
2314 2310 [[package]]
2315 2311 name = "makeover-touch"
2316 2312 version = "0.13.0"
2317 - source = "registry+https://github.com/rust-lang/crates.io-index"
2318 - checksum = "694ed7bf31697afd8fcfe85d68d5dcba5d96f10ab13d8cbf52791d48d3acb75c"
2319 2313 dependencies = [
2320 2314 "makeover-geometry",
2321 2315 "makeover-layout",
@@ -2324,8 +2318,6 @@
2324 2318 [[package]]
2325 2319 name = "makeover-tui"
2326 2320 version = "0.19.0"
2327 - source = "registry+https://github.com/rust-lang/crates.io-index"
2328 - checksum = "35801286227b6e1340f52d7f299387325b01026c5074d63cde60f3390d27ff27"
2329 2321 dependencies = [
2330 2322 "makeover",
2331 2323 "makeover-layout",
@@ -2334,9 +2326,7 @@
2334 2326
2335 2327 [[package]]
2336 2328 name = "makeover-webview"
2337 - version = "0.37.0"
2338 - source = "registry+https://github.com/rust-lang/crates.io-index"
2339 - checksum = "678efbe9ea9634b446eff14e8b43c033a186195dd70004ef166c1684fa02aba9"
2329 + version = "0.38.0"
2340 2330 dependencies = [
2341 2331 "makeover-geometry",
2342 2332 "makeover-layout",
@@ -3029,7 +3019,7 @@
3029 3019
3030 3020 [[package]]
3031 3021 name = "quasi-axum"
3032 - version = "0.3.0"
3022 + version = "0.4.0"
3033 3023 dependencies = [
3034 3024 "axum",
3035 3025 "http",
@@ -3043,7 +3033,7 @@
3043 3033
3044 3034 [[package]]
3045 3035 name = "quasi-http"
3046 - version = "0.3.0"
3036 + version = "0.4.0"
3047 3037 dependencies = [
3048 3038 "form_urlencoded",
3049 3039 "http",
@@ -3052,7 +3042,7 @@
3052 3042
3053 3043 [[package]]
3054 3044 name = "quasi-immediate"
3055 - version = "0.3.0"
3045 + version = "0.4.0"
3056 3046 dependencies = [
3057 3047 "docengine",
3058 3048 "egui",
@@ -3062,7 +3052,7 @@
3062 3052
3063 3053 [[package]]
3064 3054 name = "quasi-router"
3065 - version = "0.3.0"
3055 + version = "0.4.0"
3066 3056 dependencies = [
3067 3057 "makeover-layout",
3068 3058 ]
@@ -3079,7 +3069,7 @@
3079 3069
3080 3070 [[package]]
3081 3071 name = "quasi-tauri"
3082 - version = "0.3.0"
3072 + version = "0.4.0"
3083 3073 dependencies = [
3084 3074 "http",
3085 3075 "quasi-http",
@@ -3090,7 +3080,7 @@
3090 3080
3091 3081 [[package]]
3092 3082 name = "quasi-tui"
3093 - version = "0.3.0"
3083 + version = "0.4.0"
3094 3084 dependencies = [
3095 3085 "docengine",
3096 3086 "makeover",
@@ -3102,7 +3092,7 @@
3102 3092
3103 3093 [[package]]
3104 3094 name = "quasi-webview"
3105 - version = "0.3.0"
3095 + version = "0.4.0"
3106 3096 dependencies = [
3107 3097 "docengine",
3108 3098 "makeover-layout",
@@ -5537,3 +5527,7 @@
5537 5527 [[patch.unused]]
5538 5528 name = "tagtree"
5539 5529 version = "0.4.0"
5530 +
5531 + [[patch.unused]]
5532 + name = "makeover-build"
5533 + version = "0.30.0"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-axum"
3 - version = "0.3.0"
3 + version = "0.4.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.3.0" }
17 - quasi-http = { path = "../quasi-http", version = "0.3.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.4.0" }
17 + quasi-http = { path = "../quasi-http", version = "0.4.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.3.0" }
23 + quasi-webview = { path = "../quasi-webview", version = "0.4.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-http"
3 - version = "0.3.0"
3 + version = "0.4.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.3.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.4.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.3.0"
3 + version = "0.4.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"
@@ -10,12 +10,12 @@
10 10 categories = ["gui"]
11 11
12 12 [dependencies]
13 - quasi-router = { path = "../quasi-router", version = "0.3.0" }
13 + quasi-router = { path = "../quasi-router", version = "0.4.0" }
14 14 # The node drawing, which is the makeover layer's and not this crate's. Every
15 15 # widget here that is not a container comes from it: a screen walk that painted
16 16 # its own meter would be the divergence the suite exists to end, one copy per
17 17 # renderer instead of one copy per app.
18 - makeover-immediate = "0.19.0"
18 + makeover-immediate = "0.20.0"
19 19 egui = { version = "0.35", default-features = false }
20 20 # Markdown to text for `Node::Rich`. The same crate the webview renderer takes,
21 21 # reading the same source: what differs is that it can emit markup and this
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-router"
3 - version = "0.3.0"
3 + version = "0.4.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
@@ -13,4 +13,4 @@
13 13 workspace = true
14 14
15 15 [dependencies]
16 - makeover-layout = "0.21.0"
16 + makeover-layout = "0.22.0"
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-tauri"
3 - version = "0.3.0"
3 + version = "0.4.0"
4 4 description = "The Tauri custom-protocol host adapter for quasi-router: a webview request in, a rendered description out"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -13,8 +13,8 @@
13 13 workspace = true
14 14
15 15 [dependencies]
16 - quasi-router = { path = "../quasi-router", version = "0.3.0" }
17 - quasi-http = { path = "../quasi-http", version = "0.3.0" }
16 + quasi-router = { path = "../quasi-router", version = "0.4.0" }
17 + quasi-http = { path = "../quasi-http", version = "0.4.0" }
18 18 http = "1.3.1"
19 19 # Default features off: this crate uses tauri's protocol registration and its
20 20 # blocking pool, and nothing else. An app brings the features it wants, and the
@@ -22,7 +22,7 @@
22 22 tauri = { version = "2.11.5", default-features = false }
23 23
24 24 [dev-dependencies]
25 - quasi-webview = { path = "../quasi-webview", version = "0.3.0" }
25 + quasi-webview = { path = "../quasi-webview", version = "0.4.0" }
26 26 # The doctest names a concrete `Runtime`, and `Wry` is behind a default feature
27 27 # this crate does not otherwise ask for. Only the example needs it: nothing in
28 28 # the test module builds a window, which is what keeps them runnable with no
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-tui"
3 - version = "0.3.0"
3 + version = "0.4.0"
4 4 description = "The terminal renderer for quasi: a screen description in, cells in a ratatui buffer 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.3.0" }
17 - makeover-layout = "0.21.0"
16 + quasi-router = { path = "../quasi-router", version = "0.4.0" }
17 + makeover-layout = "0.22.0"
18 18 # The depth palette, the theme bridge and the table. Everything else this crate
19 19 # draws is written here first and lifted upstream once a second consumer wants
20 20 # it, which is the order the suite has always moved in: the constrained consumer
21 21 # finds the shape, and the shared crate takes it after it is known rather than
22 22 # before.
23 - makeover-tui = { version = "0.19.0", features = ["theme"] }
23 + makeover-tui = { version = "0.20.0", features = ["theme"] }
24 24 ratatui = { version = "0.30", default-features = false }
25 25 # `Node::Rich` carries markdown source. A terminal has no markup to hand it to,
26 26 # so it takes the runs: `render_runs` is the words with the marks that were over
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "quasi-webview"
3 - version = "0.3.0"
3 + version = "0.4.0"
4 4 description = "The webview renderer for quasi: a screen description in, an htmx document out"
5 5 edition.workspace = true
6 6 rust-version.workspace = true
@@ -13,10 +13,10 @@
13 13 workspace = true
14 14
15 15 [dependencies]
16 - quasi-router = { path = "../quasi-router", version = "0.3.0" }
17 - quasi-http = { path = "../quasi-http", version = "0.3.0" }
18 - makeover-layout = "0.21.0"
19 - makeover-webview = "0.37.0"
16 + quasi-router = { path = "../quasi-router", version = "0.4.0" }
17 + quasi-http = { path = "../quasi-http", version = "0.4.0" }
18 + makeover-layout = "0.22.0"
19 + makeover-webview = "0.38.0"
20 20 # `Node::Rich` carries markdown source and this is what turns it into markup.
21 21 # Sanitising comes with it, which is why the node can carry what a user typed.
22 22 #
@@ -451,6 +451,14 @@
451 451 pub caption: Option<String>,
452 452 /// How it sits in the box it is given.
453 453 pub fit: layout::Fit,
454 + /// The picture's own dimensions, where the app knows them.
455 + ///
456 + /// `layout::Image::intrinsic` carries the argument. The short form: without
457 + /// it a renderer cannot hold the picture's place, so the picture takes no
458 + /// room until it arrives and then shoves the page down.
459 + pub intrinsic: Option<layout::Extent>,
460 + /// Whether the picture is needed with the screen, or can arrive later.
461 + pub loading: layout::Loading,
454 462 }
455 463
456 464 impl Picture {
@@ -461,9 +469,25 @@
461 469 alt: alt.into(),
462 470 caption: None,
463 471 fit: layout::Fit::Natural,
472 + intrinsic: None,
473 + loading: layout::Loading::Eager,
464 474 }
465 475 }
466 476
477 + /// The picture's own dimensions, so a renderer can hold its place.
478 + #[must_use]
479 + pub const fn intrinsic(mut self, width: u32, height: u32) -> Self {
480 + self.intrinsic = Some(layout::Extent::new(width, height));
481 + self
482 + }
483 +
484 + /// This picture is not on screen yet; it can arrive when it is near.
485 + #[must_use]
486 + pub const fn lazy(mut self) -> Self {
487 + self.loading = layout::Loading::Lazy;
488 + self
489 + }
490 +
467 491 /// A visible line under it.
468 492 #[must_use]
469 493 pub fn caption(mut self, caption: impl Into<String>) -> Self {
@@ -485,6 +509,8 @@
485 509 alt: &self.alt,
486 510 caption: self.caption.as_deref(),
487 511 fit: self.fit,
512 + intrinsic: self.intrinsic,
513 + loading: self.loading,
488 514 }
489 515 }
490 516 }
@@ -983,10 +983,25 @@
983 983 out.push_str("\" alt=\"");
984 984 out.push_str(&escape(&picture.alt));
985 985 out.push('"');
986 - // Lazy by default. Every measured consumer wrote it, nothing wanted
987 - // the other value, and a description that carried the choice would
988 - // be carrying a browser's loading policy in a host-neutral layer.
989 - out.push_str(" loading=\"lazy\"");
986 +
987 + // The picture's own dimensions, which is how the browser holds its
988 + // place. With `height: auto` set by makeover, these two attributes
989 + // give the box an aspect ratio before a byte arrives, so the space
990 + // is right at any width and nothing below moves when the image
991 + // lands. Omitted when the app does not know them -- an invented
992 + // size would reserve the wrong room, which is worse than none.
993 + if let Some(e) = picture.intrinsic {
994 + let _ = write!(out, " width=\"{}\" height=\"{}\"", e.width, e.height);
995 + }
996 +
997 + // 0.21.0 wrote `loading="lazy"` for every picture and that was
998 + // wrong for anything on screen at first paint: deferring what is
999 + // already needed saves nothing and lands its arrival later, so the
1000 + // page moves more rather than less. The description says which it
1001 + // is now, and eager is the default.
1002 + if matches!(picture.loading, layout::Loading::Lazy) {
1003 + out.push_str(" loading=\"lazy\"");
1004 + }
990 1005 fit_attr(picture.fit, out);
991 1006 out.push('>');
992 1007