max / goingson
4 files changed,
+174 insertions,
-33 deletions
| @@ -3444,9 +3444,9 @@ | |||
| 3444 | 3444 | ||
| 3445 | 3445 | [[package]] | |
| 3446 | 3446 | name = "makeover-build" | |
| 3447 | - | version = "0.52.0" | |
| 3447 | + | version = "0.53.0" | |
| 3448 | 3448 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3449 | - | checksum = "7b10f15fe8101177872c65b4bf2dc521a98975a42cee7b1ddfb2646fdcbfaaa9" | |
| 3449 | + | checksum = "82a170c530883774fc44d41e8e785260ee1b44cc26d9bfaa4d77154176af794c" | |
| 3450 | 3450 | dependencies = [ | |
| 3451 | 3451 | "makeover", | |
| 3452 | 3452 | "makeover-geometry", | |
| @@ -3498,9 +3498,9 @@ | |||
| 3498 | 3498 | ||
| 3499 | 3499 | [[package]] | |
| 3500 | 3500 | name = "makeover-webview" | |
| 3501 | - | version = "0.59.1" | |
| 3501 | + | version = "0.60.0" | |
| 3502 | 3502 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3503 | - | checksum = "59714ea075f5c266616199eb8345808f85092be04278471ffde546bdb0f43cb6" | |
| 3503 | + | checksum = "203b3a259e249ed32ffd9d48d75a6c12e4aa173095607cc2c852f2c1f3fe9c0e" | |
| 3504 | 3504 | dependencies = [ | |
| 3505 | 3505 | "makeover-geometry", | |
| 3506 | 3506 | "makeover-layout", | |
| @@ -4716,7 +4716,7 @@ | |||
| 4716 | 4716 | ||
| 4717 | 4717 | [[package]] | |
| 4718 | 4718 | name = "quasi-http" | |
| 4719 | - | version = "0.63.0" | |
| 4719 | + | version = "0.64.0" | |
| 4720 | 4720 | dependencies = [ | |
| 4721 | 4721 | "form_urlencoded", | |
| 4722 | 4722 | "http", | |
| @@ -4725,7 +4725,7 @@ | |||
| 4725 | 4725 | ||
| 4726 | 4726 | [[package]] | |
| 4727 | 4727 | name = "quasi-notifs" | |
| 4728 | - | version = "0.63.0" | |
| 4728 | + | version = "0.64.0" | |
| 4729 | 4729 | dependencies = [ | |
| 4730 | 4730 | "quasi-router", | |
| 4731 | 4731 | "synckit-config", | |
| @@ -4733,14 +4733,14 @@ | |||
| 4733 | 4733 | ||
| 4734 | 4734 | [[package]] | |
| 4735 | 4735 | name = "quasi-router" | |
| 4736 | - | version = "0.63.0" | |
| 4736 | + | version = "0.64.0" | |
| 4737 | 4737 | dependencies = [ | |
| 4738 | 4738 | "makeover-layout", | |
| 4739 | 4739 | ] | |
| 4740 | 4740 | ||
| 4741 | 4741 | [[package]] | |
| 4742 | 4742 | name = "quasi-tauri" | |
| 4743 | - | version = "0.63.0" | |
| 4743 | + | version = "0.64.0" | |
| 4744 | 4744 | dependencies = [ | |
| 4745 | 4745 | "http", | |
| 4746 | 4746 | "quasi-http", | |
| @@ -4766,7 +4766,7 @@ | |||
| 4766 | 4766 | ||
| 4767 | 4767 | [[package]] | |
| 4768 | 4768 | name = "quasi-webview" | |
| 4769 | - | version = "0.63.0" | |
| 4769 | + | version = "0.64.0" | |
| 4770 | 4770 | dependencies = [ | |
| 4771 | 4771 | "docengine", | |
| 4772 | 4772 | "makeover-layout", | |
| @@ -8504,21 +8504,21 @@ | |||
| 8504 | 8504 | ] | |
| 8505 | 8505 | ||
| 8506 | 8506 | [[patch.unused]] | |
| 8507 | - | name = "quasi-axum" | |
| 8508 | - | version = "0.63.0" | |
| 8509 | - | ||
| 8510 | - | [[patch.unused]] | |
| 8511 | - | name = "quasi-basics" | |
| 8512 | - | version = "0.63.0" | |
| 8513 | - | ||
| 8514 | - | [[patch.unused]] | |
| 8515 | - | name = "quasi-immediate" | |
| 8516 | - | version = "0.63.0" | |
| 8517 | - | ||
| 8518 | - | [[patch.unused]] | |
| 8519 | - | name = "quasi-store" | |
| 8507 | + | name = "ops-status" | |
| 8520 | 8508 | version = "0.1.0" | |
| 8521 | 8509 | ||
| 8522 | 8510 | [[patch.unused]] | |
| 8523 | - | name = "ops-status" | |
| 8511 | + | name = "quasi-axum" | |
| 8512 | + | version = "0.64.0" | |
| 8513 | + | ||
| 8514 | + | [[patch.unused]] | |
| 8515 | + | name = "quasi-basics" | |
| 8516 | + | version = "0.64.0" | |
| 8517 | + | ||
| 8518 | + | [[patch.unused]] | |
| 8519 | + | name = "quasi-immediate" | |
| 8520 | + | version = "0.64.0" | |
| 8521 | + | ||
| 8522 | + | [[patch.unused]] | |
| 8523 | + | name = "quasi-store" | |
| 8524 | 8524 | version = "0.1.0" |
| @@ -18,7 +18,7 @@ | |||
| 18 | 18 | # Materialises all three generated files: themes/, geometry.css, layout.css, | |
| 19 | 19 | # and holds both drift checks this build.rs used to carry itself: touch density | |
| 20 | 20 | # as of 0.17, breakpoints as of 0.18. | |
| 21 | - | makeover-build = "0.52.0" | |
| 21 | + | makeover-build = "0.53.0" | |
| 22 | 22 | # Cuts the two house faces into frontend/fonts/. A git dependency because | |
| 23 | 23 | # quasi-type is `publish = false`, which is also why makeover-build cannot do | |
| 24 | 24 | # this and only emits the CSS that fetches the result. The rev is the one | |
| @@ -28,7 +28,7 @@ | |||
| 28 | 28 | # The table CSS is generated here too: the columns are this app's, so the | |
| 29 | 29 | # shared helper cannot know them, but the tracks and the narrowing rules come | |
| 30 | 30 | # from the description rather than from hand-written nth-child cuts. | |
| 31 | - | makeover-webview = "0.59.1" | |
| 31 | + | makeover-webview = "0.60.0" | |
| 32 | 32 | makeover-layout = "0.35.0" | |
| 33 | 33 | # Width. Direct rather than through makeover-webview, because the narrow table | |
| 34 | 34 | # pass keys off SizeClass::Compact and a boundary reached transitively is a | |
| @@ -36,7 +36,7 @@ | |||
| 36 | 36 | makeover-geometry = "0.7" | |
| 37 | 37 | # Only to write its selection and clock scripts into the frontend. Those two are | |
| 38 | 38 | # the whole of `frontend/js/` since the swap. | |
| 39 | - | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.63" } | |
| 39 | + | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.64" } | |
| 40 | 40 | ||
| 41 | 41 | [dependencies] | |
| 42 | 42 | goingson-core = { workspace = true } | |
| @@ -54,10 +54,10 @@ | |||
| 54 | 54 | # dependency's source even with the feature off -- so a public clone could not | |
| 55 | 55 | # build at all against a URL it could not reach. quasi went public that day and | |
| 56 | 56 | # the URL moved here with it. | |
| 57 | - | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.63" } | |
| 58 | - | quasi-http = { git = "https://makenot.work/git/max/quasi.git", version = "0.63" } | |
| 59 | - | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.63" } | |
| 60 | - | quasi-tauri = { git = "https://makenot.work/git/max/quasi.git", version = "0.63" } | |
| 57 | + | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.64" } | |
| 58 | + | quasi-http = { git = "https://makenot.work/git/max/quasi.git", version = "0.64" } | |
| 59 | + | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.64" } | |
| 60 | + | quasi-tauri = { git = "https://makenot.work/git/max/quasi.git", version = "0.64" } | |
| 61 | 61 | ||
| 62 | 62 | # The notification declaration, and it is NOT behind the `quasi` feature: the | |
| 63 | 63 | # watcher is desktop app behaviour rather than a described screen, and the | |
| @@ -74,7 +74,7 @@ | |||
| 74 | 74 | # before it has ported its screens cannot take it. `notifications::Notifier` is | |
| 75 | 75 | # the eight lines meanwhile, and it goes when the flip makes quasi-router | |
| 76 | 76 | # unconditional here. | |
| 77 | - | quasi-notifs = { git = "https://makenot.work/git/max/quasi.git", version = "0.63", features = ["synckit", "describe"] } | |
| 77 | + | quasi-notifs = { git = "https://makenot.work/git/max/quasi.git", version = "0.64", features = ["synckit", "describe"] } | |
| 78 | 78 | ||
| 79 | 79 | # Tauri | |
| 80 | 80 | tauri = { workspace = true, features = ["image-png", "devtools"] } | |
| @@ -139,7 +139,7 @@ | |||
| 139 | 139 | # The forms emitter and the description it renders. Runtime deps, not build: | |
| 140 | 140 | # form markup depends on the value, the error and the options at the moment a | |
| 141 | 141 | # modal opens, so it cannot be materialised the way the stylesheet is. | |
| 142 | - | makeover-webview = "0.59.1" | |
| 142 | + | makeover-webview = "0.60.0" | |
| 143 | 143 | makeover-layout = "0.35.0" | |
| 144 | 144 | # Browser opening | |
| 145 | 145 | open = { workspace = true } |
| @@ -603,7 +603,7 @@ | |||
| 603 | 603 | // silently stops counting, a band showing the time the screen was served, | |
| 604 | 604 | // an export pasted into the page, or a picker that writes nothing. | |
| 605 | 605 | // | |
| 606 | - | // A loop rather than four calls, because the shell links every one of these | |
| 606 | + | // A loop rather than a call each, because the shell links every one of these | |
| 607 | 607 | // by default: a script the renderer starts shipping and this file forgets | |
| 608 | 608 | // is a 404 per document with nothing in the log. `quasi-download.js` was | |
| 609 | 609 | // exactly that until 2026-08-22. | |
| @@ -614,6 +614,7 @@ | |||
| 614 | 614 | ("quasi-fill.js", quasi_webview::FILL_JS), | |
| 615 | 615 | ("quasi-reveal.js", quasi_webview::REVEAL_JS), | |
| 616 | 616 | ("quasi-repeat.js", quasi_webview::REPEAT_JS), | |
| 617 | + | ("quasi-awaiting.js", quasi_webview::AWAITING_JS), | |
| 617 | 618 | ] { | |
| 618 | 619 | fs::write(frontend.join("js").join(name), source) | |
| 619 | 620 | .unwrap_or_else(|error| panic!("write js/{name}: {error}")); |
| @@ -1,0 +1,140 @@ | |||
| 1 | + | // The half of a wait that only the browser can see. | |
| 2 | + | // | |
| 3 | + | // The node emitter has written `data-awaiting` since the vocabulary gained | |
| 4 | + | // `Awaiting`: `determinate` with a `data-awaiting-amount` beside it, or | |
| 5 | + | // `indeterminate` alone. `makeover-webview` 0.60.0 draws both. Neither of them | |
| 6 | + | // knows when a wait is actually running, and that is what this file supplies. | |
| 7 | + | // | |
| 8 | + | // Three things, and nothing else: | |
| 9 | + | // | |
| 10 | + | // 1. `aria-busy` on the element that made the request, for as long as the | |
| 11 | + | // request is in flight. That is what the design system keys the mark off, | |
| 12 | + | // and it is a standard attribute rather than a class of ours, so it is | |
| 13 | + | // announced as well as drawn. | |
| 14 | + | // 2. `--awaiting-share`, for a control whose wait has a measured size, from | |
| 15 | + | // bytes actually observed arriving. Never from elapsed time: see below. | |
| 16 | + | // 3. Nothing at all for a control that is not marked as awaiting, which is | |
| 17 | + | // nearly all of them. | |
| 18 | + | // | |
| 19 | + | // # Why the attribute alone was not enough | |
| 20 | + | // | |
| 21 | + | // `data-awaiting` is a fact about the control -- pressing this waits -- and it | |
| 22 | + | // is true when the page is painted and stays true. Styling it directly would | |
| 23 | + | // put a blinking mark on every awaiting control on the screen from the moment | |
| 24 | + | // the document loaded. The running-or-not half is true only between two events, | |
| 25 | + | // which makes it the binder's, which is this. | |
| 26 | + | // | |
| 27 | + | // # What the share may not be | |
| 28 | + | // | |
| 29 | + | // Rule 1 of wiki `loading-and-progress-standard`, and `Awaiting`'s own docs | |
| 30 | + | // before it: what is done over what there is, and never a remaining time, an | |
| 31 | + | // arrival time, or a rate extrapolated forward. So the share is set from | |
| 32 | + | // `event.loaded` -- bytes the browser says have landed -- and from nothing | |
| 33 | + | // else. A control with no readable progress keeps a share of zero and draws an | |
| 34 | + | // empty trough, which is the honest picture: the size is known and the delivery | |
| 35 | + | // is not. | |
| 36 | + | // | |
| 37 | + | // The one thing this file must never grow is a timer that advances the share. | |
| 38 | + | // A bar walking forward on a stalled transfer is the confidently-wrong drawing | |
| 39 | + | // the rule exists to forbid, and it is the easiest thing in the world to add. | |
| 40 | + | // | |
| 41 | + | // # Degrading | |
| 42 | + | // | |
| 43 | + | // A page that does not serve this file draws no mark and no bar, and every | |
| 44 | + | // control still locks itself through `hx-disable` exactly as before. That is | |
| 45 | + | // the direction the rest of this renderer degrades in: less decoration, never | |
| 46 | + | // less behaviour. | |
| 47 | + | (() => { | |
| 48 | + | "use strict"; | |
| 49 | + | ||
| 50 | + | /** The fact about the control: this call waits. */ | |
| 51 | + | const AWAITING = "data-awaiting"; | |
| 52 | + | /** The measured payload size, when the description carried one. */ | |
| 53 | + | const AMOUNT = "data-awaiting-amount"; | |
| 54 | + | /** How much of it has landed, as a share from 0 to 1. */ | |
| 55 | + | const SHARE = "--awaiting-share"; | |
| 56 | + | ||
| 57 | + | /** | |
| 58 | + | * The element a request belongs to, or null when it is not our business. | |
| 59 | + | * | |
| 60 | + | * htmx names the element on the event detail. An event for something that | |
| 61 | + | * never described a wait is left alone rather than given one: the emitter | |
| 62 | + | * decides which calls are worth marking, and a renderer that marked every | |
| 63 | + | * request would be overruling it. | |
| 64 | + | */ | |
| 65 | + | const marked = (event) => { | |
| 66 | + | const element = event.detail?.elt; | |
| 67 | + | if (!element || typeof element.hasAttribute !== "function") { | |
| 68 | + | return null; | |
| 69 | + | } | |
| 70 | + | return element.hasAttribute(AWAITING) ? element : null; | |
| 71 | + | }; | |
| 72 | + | ||
| 73 | + | /** Say a wait has started, with nothing delivered yet. */ | |
| 74 | + | const begin = (event) => { | |
| 75 | + | const element = marked(event); | |
| 76 | + | if (!element) { | |
| 77 | + | return; | |
| 78 | + | } | |
| 79 | + | element.setAttribute("aria-busy", "true"); | |
| 80 | + | element.style.removeProperty(SHARE); | |
| 81 | + | }; | |
| 82 | + | ||
| 83 | + | /** | |
| 84 | + | * Say the wait is over. | |
| 85 | + | * | |
| 86 | + | * The share is cleared with it. A control pressed twice would otherwise | |
| 87 | + | * start its second wait wearing the first one's bar, which is a number | |
| 88 | + | * about a payload that has already arrived. | |
| 89 | + | */ | |
| 90 | + | const end = (event) => { | |
| 91 | + | const element = marked(event); | |
| 92 | + | if (!element) { | |
| 93 | + | return; | |
| 94 | + | } | |
| 95 | + | element.removeAttribute("aria-busy"); | |
| 96 | + | element.style.removeProperty(SHARE); | |
| 97 | + | }; | |
| 98 | + | ||
| 99 | + | /** | |
| 100 | + | * What has landed, over what the description said there was. | |
| 101 | + | * | |
| 102 | + | * `event.loaded` when the browser is counting, against the described | |
| 103 | + | * amount rather than against `event.total`. The two usually agree, and | |
| 104 | + | * where they do not the description is the one the bar was drawn for: a | |
| 105 | + | * server that gzips a response reports a total the screen never described. | |
| 106 | + | * | |
| 107 | + | * A response with no readable length leaves the share alone, so the trough | |
| 108 | + | * stays empty rather than jumping to something invented. | |
| 109 | + | */ | |
| 110 | + | const progressed = (event) => { | |
| 111 | + | const element = marked(event); | |
| 112 | + | if (!element || element.getAttribute(AWAITING) !== "determinate") { | |
| 113 | + | return; | |
| 114 | + | } | |
| 115 | + | const amount = Number(element.getAttribute(AMOUNT)); | |
| 116 | + | const loaded = Number(event.detail?.loaded); | |
| 117 | + | if (!Number.isFinite(amount) || amount <= 0 || !Number.isFinite(loaded)) { | |
| 118 | + | return; | |
| 119 | + | } | |
| 120 | + | element.style.setProperty(SHARE, String(Math.min(loaded / amount, 1))); | |
| 121 | + | }; | |
| 122 | + | ||
| 123 | + | // htmx 4 spells every event `phase:action`; 2.x spelled these | |
| 124 | + | // `htmx:beforeRequest`, `htmx:afterRequest` and `htmx:xhr:progress`. | |
| 125 | + | // | |
| 126 | + | // Delegated on the document rather than bound per control, for the reason | |
| 127 | + | // the reveal and repeat scripts give: a swap brings new controls and takes | |
| 128 | + | // old ones away, and a listener on the document covers both without a | |
| 129 | + | // re-scan. | |
| 130 | + | document.addEventListener("htmx:before:request", begin); | |
| 131 | + | document.addEventListener("htmx:after:request", end); | |
| 132 | + | document.addEventListener("htmx:xhr:progress", progressed); | |
| 133 | + | ||
| 134 | + | // A request that is aborted or errors out never reaches `after:request` in | |
| 135 | + | // every htmx version, and a control left wearing `aria-busy` blinks for the | |
| 136 | + | // rest of the page's life. Cheap insurance, and the same handler. | |
| 137 | + | document.addEventListener("htmx:abort", end); | |
| 138 | + | document.addEventListener("htmx:response:error", end); | |
| 139 | + | document.addEventListener("htmx:send:error", end); | |
| 140 | + | })(); |