Skip to main content

max / makenotwork

The tag typeahead keeps what was typed across a sidebar swap quasicoherent a135f898. The sidebar rides along out of band on every filter change, so three letters of a tag plus a tick on an unrelated facet emptied the box. `discover_results.html` used to carry `hx-preserve` by hand and explained why in a comment; the port dropped the attribute and kept the comment, which is the regression. Said by the description now. The comment points at the member rather than at an attribute nothing writes, and the test asserts preservation again rather than only the id.
Author: Max Johnson <me@maxj.phd> · 2026-08-30 20:36 UTC
Signed with PGP, not checked
Commit: 6f9f8be8799afabb3b7e84ac15929c135ddcbfbe
Parent: b1d3513
5 files changed, +53 insertions, -35 deletions
M server/Cargo.lock +16 -16
@@ -2875,7 +2875,7 @@
2875 2875 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
2876 2876 dependencies = [
2877 2877 "libc",
2878 - "windows-sys 0.52.0",
2878 + "windows-sys 0.59.0",
2879 2879 ]
2880 2880
2881 2881 [[package]]
@@ -4811,7 +4811,7 @@
4811 4811 source = "registry+https://github.com/rust-lang/crates.io-index"
4812 4812 checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd"
4813 4813 dependencies = [
4814 - "windows-sys 0.61.2",
4814 + "windows-sys 0.59.0",
4815 4815 ]
4816 4816
4817 4817 [[package]]
@@ -5722,7 +5722,7 @@
5722 5722 source = "registry+https://github.com/rust-lang/crates.io-index"
5723 5723 checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
5724 5724 dependencies = [
5725 - "windows-sys 0.61.2",
5725 + "windows-sys 0.59.0",
5726 5726 ]
5727 5727
5728 5728 [[package]]
@@ -6636,7 +6636,7 @@
6636 6636
6637 6637 [[package]]
6638 6638 name = "quasi-axum"
6639 - version = "0.86.0"
6639 + version = "0.87.0"
6640 6640 dependencies = [
6641 6641 "axum",
6642 6642 "http 1.4.2",
@@ -6647,7 +6647,7 @@
6647 6647
6648 6648 [[package]]
6649 6649 name = "quasi-basics"
6650 - version = "0.86.0"
6650 + version = "0.87.0"
6651 6651 dependencies = [
6652 6652 "makeover-layout",
6653 6653 "quasi-router",
@@ -6655,7 +6655,7 @@
6655 6655
6656 6656 [[package]]
6657 6657 name = "quasi-http"
6658 - version = "0.86.0"
6658 + version = "0.87.0"
6659 6659 dependencies = [
6660 6660 "form_urlencoded",
6661 6661 "http 1.4.2",
@@ -6664,7 +6664,7 @@
6664 6664
6665 6665 [[package]]
6666 6666 name = "quasi-router"
6667 - version = "0.86.0"
6667 + version = "0.87.0"
6668 6668 dependencies = [
6669 6669 "makeover-layout",
6670 6670 ]
@@ -6687,7 +6687,7 @@
6687 6687
6688 6688 [[package]]
6689 6689 name = "quasi-webview"
6690 - version = "0.86.0"
6690 + version = "0.87.0"
6691 6691 dependencies = [
6692 6692 "docengine",
6693 6693 "makeover-layout",
@@ -6763,7 +6763,7 @@
6763 6763 "once_cell",
6764 6764 "socket2",
6765 6765 "tracing",
6766 - "windows-sys 0.52.0",
6766 + "windows-sys 0.59.0",
6767 6767 ]
6768 6768
6769 6769 [[package]]
@@ -7326,7 +7326,7 @@
7326 7326 "errno",
7327 7327 "libc",
7328 7328 "linux-raw-sys 0.4.15",
7329 - "windows-sys 0.52.0",
7329 + "windows-sys 0.59.0",
7330 7330 ]
7331 7331
7332 7332 [[package]]
@@ -7339,7 +7339,7 @@
7339 7339 "errno",
7340 7340 "libc",
7341 7341 "linux-raw-sys 0.12.1",
7342 - "windows-sys 0.52.0",
7342 + "windows-sys 0.59.0",
7343 7343 ]
7344 7344
7345 7345 [[package]]
@@ -7419,7 +7419,7 @@
7419 7419 "security-framework 3.7.0",
7420 7420 "security-framework-sys",
7421 7421 "webpki-root-certs",
7422 - "windows-sys 0.52.0",
7422 + "windows-sys 0.59.0",
7423 7423 ]
7424 7424
7425 7425 [[package]]
@@ -8545,7 +8545,7 @@
8545 8545 "getrandom 0.4.3",
8546 8546 "once_cell",
8547 8547 "rustix 1.1.4",
8548 - "windows-sys 0.52.0",
8548 + "windows-sys 0.59.0",
8549 8549 ]
8550 8550
8551 8551 [[package]]
@@ -10773,11 +10773,11 @@
10773 10773
10774 10774 [[patch.unused]]
10775 10775 name = "quasi-immediate"
10776 - version = "0.86.0"
10776 + version = "0.87.0"
10777 10777
10778 10778 [[patch.unused]]
10779 10779 name = "quasi-notifs"
10780 - version = "0.86.0"
10780 + version = "0.87.0"
10781 10781
10782 10782 [[patch.unused]]
10783 10783 name = "quasi-store"
@@ -10785,7 +10785,7 @@
10785 10785
10786 10786 [[patch.unused]]
10787 10787 name = "quasi-tauri"
10788 - version = "0.86.0"
10788 + version = "0.87.0"
10789 10789
10790 10790 [[patch.unused]]
10791 10791 name = "synckit-client"
@@ -163,7 +163,7 @@
163 163 # through quasi beside its Askama equivalent, so the cost of describing a screen
164 164 # here is measured rather than argued. Not load-bearing for any shipped route.
165 165 # See wiki look-wave-2, tier G.
166 - quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.86" }
166 + quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
167 167 # The description vocabulary quasi's screen types are built from. Pinned here
168 168 # rather than reached through quasi-router's re-export because a described
169 169 # screen names FieldKind and Tone directly; it has to track what quasi-router
@@ -179,13 +179,13 @@
179 179 # For the request head the per-viewer state factory reads. axum re-exports it,
180 180 # but the factory's signature is quasi-axum's and names `http::request::Parts`.
181 181 http = "1.3.1"
182 - quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.86" }
183 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.86" }
182 + quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
183 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
184 184 # The first-party widget set. The carousel was proved here against three pages
185 185 # and then moved there; what is left in `quasi/widgets/carousel.rs` is the
186 186 # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it
187 187 # sits above quasi-router rather than inside the description suite.
188 - quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.86" }
188 + quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
189 189
190 190 # Git source browser
191 191 gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] }
@@ -277,7 +277,7 @@
277 277 # Written from the crate rather than copied into the repo for the reason the
278 278 # constants exist: a stale copy in a static directory is a silently dead bulk
279 279 # bar, and the version has to be the one the emitter agrees with.
280 - quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.86" }
280 + quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.87" }
281 281
282 282 [dev-dependencies]
283 283 tower = { version = "0.5.3", features = ["util"] }
@@ -118,12 +118,23 @@
118 118 pub fn tag_box() -> String {
119 119 use quasi_axum::Serves as _;
120 120
121 - let mut field = Field::new(FieldKind::Text, FIELD, "Find a tag").suggesting(
122 - Consult::new(Action::get(SUGGEST_ROUTE))
123 - .after(WAIT)
124 - .at_least(FLOOR)
125 - .sending(FILTERS),
126 - );
121 + // What is in this box is the reader's: the server never sent it and cannot
122 + // send it again. This sidebar is swapped out of band on every filter
123 + // change, so without saying so, typing three letters of a tag and ticking
124 + // an unrelated facet emptied the box.
125 + //
126 + // `discover_results.html` carried `hx-preserve` by hand and said exactly
127 + // this in a comment; the port dropped the attribute and kept the comment,
128 + // which is the regression quasicoherent `a135f898` was filed for and this
129 + // is what closed it.
130 + let mut field = Field::new(FieldKind::Text, FIELD, "Find a tag")
131 + .keeping_value()
132 + .suggesting(
133 + Consult::new(Action::get(SUGGEST_ROUTE))
134 + .after(WAIT)
135 + .at_least(FLOOR)
136 + .sending(FILTERS),
137 + );
127 138 field.placeholder = Some("Find a tag".to_owned());
128 139
129 140 quasi_webview::Webview::new().fragment(&Node::field(field))
@@ -5,8 +5,10 @@
5 5
6 6 htmx restores focus after a swap to an element with the same id, so every
7 7 control in the sidebar carries a stable one and keyboard position survives.
8 - The typeahead input additionally uses hx-preserve, since its value is user
9 - state the server does not render.
8 + The typeahead input additionally keeps its value across the swap, since that
9 + value is the reader's and the server does not render it. It is described
10 + rather than written here: `Field::keeping_value` says the fact and the
11 + webview renderer emits the attribute (quasicoherent `a135f898`).
10 12
11 13 Guarded on `oob_sidebar`: the full page includes this same partial inside
12 14 #results-container, and rendering the sidebar there too would emit it twice
@@ -1196,16 +1196,21 @@
1196 1196 "ai tier radios need ids"
1197 1197 );
1198 1198 // The typeahead box is described (N8), and it is addressed by the field's
1199 - // own name, so focus restoration still has an id to find it back by.
1200 - //
1201 - // What it no longer carries is `hx-preserve`: nothing in the vocabulary
1202 - // says "this control's value is the reader's, keep it through a swap", so a
1203 - // sidebar swap while a tag name is half-typed now clears the box. Filed
1204 - // against quasicoherent rather than kept by leaving the box hand-written.
1199 + // own name, so focus restoration has an id to find it back by.
1205 1200 assert!(
1206 1201 resp.text.contains(r#"id="tag-search""#),
1207 1202 "the typeahead box needs an id for focus restoration"
1208 1203 );
1204 + // And it keeps what was typed. The sidebar rides along out of band on every
1205 + // filter change, so without this, three letters of a tag plus a tick on an
1206 + // unrelated facet emptied the box. Said by the description now
1207 + // (`Field::keeping_value`, quasicoherent `a135f898`) rather than by an
1208 + // attribute written into the template, which is where it was before the
1209 + // port dropped it.
1210 + assert!(
1211 + resp.text.contains("hx-preserve"),
1212 + "the typeahead box loses a half-typed tag on every facet tick"
1213 + );
1209 1214 }
1210 1215
1211 1216 // Tag spine: drill-down and typeahead.