Skip to main content

max / makenotwork

Take quasi 0.42, whose emitter writes htmx 4 quasi `2246072d`, ruled 2026-08-18. The described screens now emit htmx 4's markup: `hx-swap="outerMorph"` instead of the idiomorph `morph`, `hx-disable` instead of `hx-disabled-elt`, `hx-headers:inherited` on the body, and no `htmx-config` meta tag because 4 swaps every status but 204 and 304 on its own. Three things follow here and nothing else does. The shell no longer links `idiomorph-ext.min.js` or writes `hx-ext="morph"`, which changes nothing a reader can see: no template asks for a morph swap, across 586 `hx-swap` uses. The ssh-keys pane's guard assertions read `hx-disable`. And the head test asserts what the head links rather than what it used to. What this deliberately does not do is swap `static/htmx.min.js`, which is still 2.0.4. The site's 940 hand-written `hx-` attributes move with that bundle and the migration is `6b87e5ff`. Until then the described screens and the templates target different htmx versions, which is the state the ruling sequences for; exposure is bounded by `QUASI_SCREENS`, off unless a host sets it. 2282 lib tests green.
Author: Max Johnson <me@maxj.phd> · 2026-08-20 16:30 UTC
Signed with PGP, not checked
Commit: 423cec9c303ec343c4758825d154eef73ad73555
Parent: 1833f9a
4 files changed, +50 insertions, -46 deletions
M server/Cargo.lock +29 -29
@@ -2865,7 +2865,7 @@
2865 2865 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
2866 2866 dependencies = [
2867 2867 "libc",
2868 - "windows-sys 0.59.0",
2868 + "windows-sys 0.52.0",
2869 2869 ]
2870 2870
2871 2871 [[package]]
@@ -4801,7 +4801,7 @@
4801 4801 source = "registry+https://github.com/rust-lang/crates.io-index"
4802 4802 checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd"
4803 4803 dependencies = [
4804 - "windows-sys 0.59.0",
4804 + "windows-sys 0.61.2",
4805 4805 ]
4806 4806
4807 4807 [[package]]
@@ -5694,7 +5694,7 @@
5694 5694 source = "registry+https://github.com/rust-lang/crates.io-index"
5695 5695 checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
5696 5696 dependencies = [
5697 - "windows-sys 0.59.0",
5697 + "windows-sys 0.61.2",
5698 5698 ]
5699 5699
5700 5700 [[package]]
@@ -6608,7 +6608,7 @@
6608 6608
6609 6609 [[package]]
6610 6610 name = "quasi-axum"
6611 - version = "0.41.0"
6611 + version = "0.42.0"
6612 6612 dependencies = [
6613 6613 "axum",
6614 6614 "http 1.4.2",
@@ -6619,7 +6619,7 @@
6619 6619
6620 6620 [[package]]
6621 6621 name = "quasi-basics"
6622 - version = "0.41.0"
6622 + version = "0.42.0"
6623 6623 dependencies = [
6624 6624 "makeover-layout",
6625 6625 "quasi-router",
@@ -6627,7 +6627,7 @@
6627 6627
6628 6628 [[package]]
6629 6629 name = "quasi-http"
6630 - version = "0.41.0"
6630 + version = "0.42.0"
6631 6631 dependencies = [
6632 6632 "form_urlencoded",
6633 6633 "http 1.4.2",
@@ -6636,7 +6636,7 @@
6636 6636
6637 6637 [[package]]
6638 6638 name = "quasi-router"
6639 - version = "0.41.0"
6639 + version = "0.42.0"
6640 6640 dependencies = [
6641 6641 "makeover-layout",
6642 6642 ]
@@ -6659,7 +6659,7 @@
6659 6659
6660 6660 [[package]]
6661 6661 name = "quasi-webview"
6662 - version = "0.41.0"
6662 + version = "0.42.0"
6663 6663 dependencies = [
6664 6664 "docengine",
6665 6665 "makeover-layout",
@@ -6735,7 +6735,7 @@
6735 6735 "once_cell",
6736 6736 "socket2",
6737 6737 "tracing",
6738 - "windows-sys 0.59.0",
6738 + "windows-sys 0.52.0",
6739 6739 ]
6740 6740
6741 6741 [[package]]
@@ -7298,7 +7298,7 @@
7298 7298 "errno",
7299 7299 "libc",
7300 7300 "linux-raw-sys 0.4.15",
7301 - "windows-sys 0.59.0",
7301 + "windows-sys 0.52.0",
7302 7302 ]
7303 7303
7304 7304 [[package]]
@@ -7311,7 +7311,7 @@
7311 7311 "errno",
7312 7312 "libc",
7313 7313 "linux-raw-sys 0.12.1",
7314 - "windows-sys 0.59.0",
7314 + "windows-sys 0.52.0",
7315 7315 ]
7316 7316
7317 7317 [[package]]
@@ -7391,7 +7391,7 @@
7391 7391 "security-framework 3.7.0",
7392 7392 "security-framework-sys",
7393 7393 "webpki-root-certs",
7394 - "windows-sys 0.59.0",
7394 + "windows-sys 0.52.0",
7395 7395 ]
7396 7396
7397 7397 [[package]]
@@ -8517,7 +8517,7 @@
8517 8517 "getrandom 0.4.3",
8518 8518 "once_cell",
8519 8519 "rustix 1.1.4",
8520 - "windows-sys 0.59.0",
8520 + "windows-sys 0.52.0",
8521 8521 ]
8522 8522
8523 8523 [[package]]
@@ -10731,22 +10731,6 @@
10731 10731 "pkg-config",
10732 10732 ]
10733 10733
10734 - [[patch.unused]]
10735 - name = "quasi-immediate"
10736 - version = "0.41.0"
10737 -
10738 - [[patch.unused]]
10739 - name = "quasi-notifs"
10740 - version = "0.41.0"
10741 -
10742 - [[patch.unused]]
10743 - name = "quasi-store"
10744 - version = "0.1.0"
10745 -
10746 - [[patch.unused]]
10747 - name = "quasi-tauri"
10748 - version = "0.41.0"
10749 -
10750 10734 [[patch.unused]]
10751 10735 name = "synckit-client"
10752 10736 version = "0.8.0"
@@ -10774,3 +10758,19 @@
10774 10758 [[patch.unused]]
10775 10759 name = "painhours"
10776 10760 version = "0.1.0"
10761 +
10762 + [[patch.unused]]
10763 + name = "quasi-immediate"
10764 + version = "0.42.0"
10765 +
10766 + [[patch.unused]]
10767 + name = "quasi-notifs"
10768 + version = "0.42.0"
10769 +
10770 + [[patch.unused]]
10771 + name = "quasi-store"
10772 + version = "0.1.0"
10773 +
10774 + [[patch.unused]]
10775 + name = "quasi-tauri"
10776 + version = "0.42.0"
@@ -157,7 +157,7 @@
157 157 # through quasi beside its Askama equivalent, so the cost of describing a screen
158 158 # here is measured rather than argued. Not load-bearing for any shipped route.
159 159 # See wiki look-wave-2, tier G.
160 - quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.41" }
160 + quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.42" }
161 161 # The description vocabulary quasi's screen types are built from. Pinned here
162 162 # rather than reached through quasi-router's re-export because a described
163 163 # screen names FieldKind and Tone directly; it has to track what quasi-router
@@ -166,13 +166,13 @@
166 166 # For the request head the per-viewer state factory reads. axum re-exports it,
167 167 # but the factory's signature is quasi-axum's and names `http::request::Parts`.
168 168 http = "1.3.1"
169 - quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.41" }
170 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.41" }
169 + quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.42" }
170 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.42" }
171 171 # The first-party widget set. The carousel was proved here against three pages
172 172 # and then moved there; what is left in `quasi/widgets/carousel.rs` is the
173 173 # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it
174 174 # sits above quasi-router rather than inside the description suite.
175 - quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.41" }
175 + quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.42" }
176 176
177 177 # Git source browser
178 178 gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] }
@@ -168,8 +168,7 @@
168 168 // `{% block body_attrs %}` straight after. Neither side puts a
169 169 // separator in, so this one has to carry it.
170 170 let attrs = body_attrs();
171 - assert!(attrs.starts_with(' '));
172 - assert!(attrs.contains("hx-ext=\"morph\""));
171 + assert!(attrs.is_empty() || attrs.starts_with(' '));
173 172 // No class of its own, or a page's class attribute would be the second
174 173 // on the tag and the browser would drop it.
175 174 assert!(!attrs.contains("class="));
@@ -235,16 +234,21 @@
235 234 }
236 235
237 236 #[test]
238 - fn morph_is_loaded_wherever_a_swap_can_ask_for_it() {
239 - // The shell writes `hx-ext="morph"` onto every page's body, so the
240 - // extension has to be served. It is: `static/idiomorph-ext.min.js`.
241 - assert!(head().contains("idiomorph-ext.min.js"));
237 + fn the_transport_the_head_links_is_served_from_here() {
238 + // The shell links one script for the transport now. Morphing is a swap
239 + // style in htmx 4 rather than something an extension supplies, so the
240 + // idiomorph registration and the extension script both left the head
241 + // when quasi-webview moved to 4 (`2246072d`, ruled 2026-08-18).
242 + //
243 + // What this site serves is still htmx 2.0.4, and the described screens
244 + // are already emitting 4's markup: `outerMorph`, `hx-disable`,
245 + // `hx-headers:inherited`. Swapping the bundle is the site's own
246 + // migration and is `6b87e5ff`, because the 940 hand-written `hx-`
247 + // attributes in the templates move with it.
248 + assert!(head().contains("htmx.min.js"));
242 249 assert!(
243 - std::path::Path::new(concat!(
244 - env!("CARGO_MANIFEST_DIR"),
245 - "/static/idiomorph-ext.min.js"
246 - ))
247 - .exists()
250 + std::path::Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/static/htmx.min.js"))
251 + .exists()
248 252 );
249 253 }
250 254 }
@@ -506,7 +506,7 @@
506 506 // whole of the fix, so asserting the count catches a fourth form added
507 507 // without the mark as well as a regression in the emitter.
508 508 assert_eq!(
509 - html.matches(r#"hx-disabled-elt="find button[type='submit']""#)
509 + html.matches(r#"hx-disable="find button[type='submit']""#)
510 510 .count(),
511 511 3,
512 512 "every form guards its submit: {html}"
@@ -514,7 +514,7 @@
514 514
515 515 // The delete is an act, and an act is a button already.
516 516 assert!(
517 - html.contains(r#"hx-disabled-elt="this""#),
517 + html.contains(r#"hx-disable="this""#),
518 518 "the remove control locks itself: {html}"
519 519 );
520 520