Skip to main content

max / makenotwork

Ignore every generated quasi script, not five of eight server/build.rs writes all eight out of quasi-webview, but .gitignore named only five and quasi-reveal.js, quasi-repeat.js and quasi-awaiting.js were tracked. A checked-in copy of a crate's emitted source is exactly the drift the rule stated in that block exists to prevent. The tracked copies matched what the crate emits today, so nothing is lost by untracking them. write_if_changed writes when the file is absent, so a fresh checkout regenerates all eight and build.rs panics if a write fails. goingson had the same split the other way round (one ignored, seven tracked) and is fixed in the same pass.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-27 20:48 UTC
Signed with PGP, not checked
Commit: 176a69665479ca9ebb883a946842967f43d424c7
Parent: ac15dae
4 files changed, +8 insertions, -469 deletions
M .gitignore +8 -2
@@ -8,13 +8,19 @@
8 8 multithreaded/static/dist/
9 9 multithreaded/frontend/node_modules/
10 10
11 - # Generated stylesheets (makeover-build emits these from makeover-geometry and
12 - # makeover-webview; the crates are the source, a checked-in copy would drift)
11 + # Generated stylesheets and scripts (makeover-build and quasi-webview emit
12 + # these; the crates are the source, a checked-in copy would drift). Every
13 + # script quasi-webview ships is named here, not some of them: three of the
14 + # eight were tracked until 2026-08-27, which is the drift this rule exists to
15 + # prevent sitting in the tree waiting to happen.
13 16 server/static/quasi-selection.js
14 17 server/static/quasi-clock.js
15 18 server/static/quasi-download.js
16 19 server/static/quasi-fill.js
17 20 server/static/quasi-copy.js
21 + server/static/quasi-reveal.js
22 + server/static/quasi-repeat.js
23 + server/static/quasi-awaiting.js
18 24 server/static/geometry.css
19 25 server/static/timing.css
20 26 server/static/layout.css
@@ -1,140 +1,0 @@
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 - })();
@@ -1,204 +1,0 @@
1 - // A question answered N times, with the reader adding and removing the slots.
2 - //
3 - // `60d1753c`, ruled 2026-08-25: a repeating group enters the vocabulary and
4 - // submits once. This is the browser's half of the third hard part -- the reader
5 - // creates and destroys slots without a round trip on every one. The bytes are
6 - // already in the document: the emitter writes a `template` holding one blank
7 - // slot, and adding is cloning it.
8 - //
9 - // A round trip instead would re-render a form the reader is midway through,
10 - // which is `a135f898`, and it would ask a route for an empty box.
11 - //
12 - // It reads four attributes the node emitter writes and nothing else:
13 - // `data-repeat` names the question, `data-repeat-label` is what its slots are
14 - // called, and `data-repeat-least` / `data-repeat-most` are the floor and the
15 - // ceiling. All four are ordinary escaped attributes, so nothing here is a
16 - // program built out of app text -- which is why this is a script rather than
17 - // one of the emitted _hyperscript programs, whose one rule is that no program
18 - // is written from text a user typed.
19 - //
20 - // A page that does not serve this file shows every slot the description
21 - // offered, fillable and submittable, with two controls that do nothing.
22 - (() => {
23 - "use strict";
24 -
25 - /** The question a fieldset repeats. */
26 - const NAME = "data-repeat";
27 - /** What one slot of it is called. */
28 - const LABEL = "data-repeat-label";
29 - /** The fewest slots that may stand. */
30 - const LEAST = "data-repeat-least";
31 - /** The most that may stand. Absent means no ceiling. */
32 - const MOST = "data-repeat-most";
33 - /** The box the slots sit in. */
34 - const SLOTS = "data-repeat-slots";
35 - /** One slot, and where in the order it currently sits. */
36 - const AT = "data-repeat-at";
37 - /** The control that adds one. */
38 - const ADD = "data-repeat-add";
39 - /** The control that takes one away. */
40 - const REMOVE = "data-repeat-remove";
41 -
42 - /** The attributes a rename has to follow the index through. */
43 - const NAMED = ["name", "id", "for", "aria-describedby", "aria-labelledby"];
44 -
45 - /** The group a control inside one belongs to. */
46 - const groupOf = (element) => element.closest(`[${NAME}]`);
47 -
48 - /** Where a group keeps its slots. */
49 - const boxOf = (group) => group.querySelector(`[${SLOTS}]`);
50 -
51 - /** The slots standing in a group, in document order. */
52 - const slotsOf = (group) => [
53 - ...(boxOf(group)?.querySelectorAll(`:scope > [${AT}]`) ?? []),
54 - ];
55 -
56 - /** A number written in an attribute, or a fallback when it is not there. */
57 - const count = (group, attribute, fallback) => {
58 - const written = group.getAttribute(attribute);
59 - if (written === null) {
60 - return fallback;
61 - }
62 - const value = Number.parseInt(written, 10);
63 - return Number.isNaN(value) ? fallback : value;
64 - };
65 -
66 - /**
67 - * Move one slot to a place in the order.
68 - *
69 - * The index is in the slot's own attribute, in every name it submits under
70 - * and in the ordinal a person reads. All three are rewritten together, or a
71 - * removed slot leaves the ones after it answering under names nobody asked
72 - * for.
73 - *
74 - * The rename is a replacement of `question[old]` wherever it appears, which
75 - * covers the derived ids beside the name itself -- `question[0]-hint`,
76 - * `question[0]-error` -- without this having to know what the field
77 - * emitter derives.
78 - */
79 - const renumber = (slot, question, label, to) => {
80 - const from = slot.getAttribute(AT);
81 - slot.setAttribute(AT, String(to));
82 - if (from === null) {
83 - return;
84 - }
85 - const was = `${question}[${from}]`;
86 - const now = `${question}[${to}]`;
87 - if (was !== now) {
88 - for (const element of [slot, ...slot.querySelectorAll("*")]) {
89 - for (const attribute of NAMED) {
90 - const value = element.getAttribute(attribute);
91 - if (value !== null && value.includes(was)) {
92 - element.setAttribute(attribute, value.split(was).join(now));
93 - }
94 - }
95 - }
96 - }
97 - // The visible ordinal, which `Repeat::ordinal` wrote as "<label> <n>"
98 - // counting from one. Rewritten from the group's own label rather than
99 - // by editing whatever text is in there, so a slot cannot end up named
100 - // after the one it replaced.
101 - if (label !== null) {
102 - const named = slot.querySelector("label[for]");
103 - if (named !== null) {
104 - named.textContent = `${label} ${to + 1}`;
105 - }
106 - }
107 - };
108 -
109 - /**
110 - * Put a group's slots and its two controls back in agreement.
111 - *
112 - * Called after every change and on arrival, so a group the reader has not
113 - * touched is in the same state as one they have.
114 - */
115 - const settle = (group) => {
116 - const question = group.getAttribute(NAME) ?? "";
117 - const label = group.getAttribute(LABEL);
118 - const slots = slotsOf(group);
119 - const least = count(group, LEAST, 0);
120 - const most = count(group, MOST, Number.POSITIVE_INFINITY);
121 - slots.forEach((slot, at) => renumber(slot, question, label, at));
122 - for (const control of group.querySelectorAll(`[${REMOVE}]`)) {
123 - control.disabled = slots.length <= least;
124 - }
125 - for (const control of group.querySelectorAll(`[${ADD}]`)) {
126 - control.disabled = slots.length >= most;
127 - }
128 - };
129 -
130 - /** Every group in the document, settled. */
131 - const settleAll = () => {
132 - for (const group of document.querySelectorAll(`[${NAME}]`)) {
133 - settle(group);
134 - }
135 - };
136 -
137 - /** Clone the blank slot onto the end, if the ceiling allows another. */
138 - const add = (group) => {
139 - const blank = group.querySelector("template");
140 - const box = boxOf(group);
141 - if (blank === null || box === null) {
142 - return;
143 - }
144 - const most = count(group, MOST, Number.POSITIVE_INFINITY);
145 - if (slotsOf(group).length >= most) {
146 - return;
147 - }
148 - const made = blank.content.cloneNode(true);
149 - const slot = made.firstElementChild;
150 - box.append(made);
151 - settle(group);
152 - // The reader pressed add because they have something to type, so the
153 - // caret goes where they meant it to. Nothing else in this file moves
154 - // focus: a settle after a swap must not steal it.
155 - slot?.querySelector("input, select, textarea")?.focus();
156 - };
157 -
158 - /** Take one slot out, if the floor allows one fewer. */
159 - const remove = (group, slot) => {
160 - const least = count(group, LEAST, 0);
161 - if (slotsOf(group).length <= least) {
162 - return;
163 - }
164 - slot.remove();
165 - settle(group);
166 - };
167 -
168 - // Delegated, so a group that arrives in a swap needs no wiring of its own.
169 - document.addEventListener("click", (event) => {
170 - const target = event.target;
171 - if (!(target instanceof Element)) {
172 - return;
173 - }
174 - const adder = target.closest(`[${ADD}]`);
175 - if (adder !== null) {
176 - const group = groupOf(adder);
177 - if (group !== null) {
178 - event.preventDefault();
179 - add(group);
180 - }
181 - return;
182 - }
183 - const remover = target.closest(`[${REMOVE}]`);
184 - if (remover === null) {
185 - return;
186 - }
187 - const group = groupOf(remover);
188 - const slot = remover.closest(`[${AT}]`);
189 - if (group !== null && slot !== null) {
190 - event.preventDefault();
191 - remove(group, slot);
192 - }
193 - });
194 -
195 - // A group that arrives mid-page settles like one that was parsed with the
196 - // document. `htmx:after:settle` is htmx 4's name for it; 2.x spelled the
197 - // same event `htmx:afterSettle`.
198 - document.addEventListener("htmx:after:settle", settleAll);
199 - if (document.readyState === "loading") {
200 - document.addEventListener("DOMContentLoaded", settleAll);
201 - } else {
202 - settleAll();
203 - }
204 - })();
@@ -1,123 +1,0 @@
1 - // A region that is out only while a control holds a value.
2 - //
3 - // `079a011e`, ruled 2026-08-25: the condition lives on the region. A region
4 - // names the control it watches and the value that brings it out, and every
5 - // renderer answers it from what it already has. This is the browser's half, and
6 - // it makes no request: the bytes are in the document, and a round trip to
7 - // reveal a section of a form the reader is midway through would re-render boxes
8 - // holding values they have not committed yet.
9 - //
10 - // It reads three attributes the node emitter writes and nothing else:
11 - // `data-reveal` names the control, `data-reveal-when` says whether the region
12 - // wants anything, nothing or one of a set of values, and `data-reveal-values`
13 - // carries that set as JSON. All three are ordinary escaped attributes, so
14 - // nothing here is a program built out of app text -- which is why the behaviour
15 - // is a script rather than one of the emitted _hyperscript programs, whose one
16 - // rule is that no program is written from text a user typed.
17 - //
18 - // A page that does not serve this file shows every conditional region, which is
19 - // the direction everything else in this renderer degrades in: more content
20 - // rather than less. The six hand-written toggles this replaces fail the other
21 - // way, since their markup starts hidden.
22 - (() => {
23 - "use strict";
24 -
25 - /** The control a region watches. */
26 - const CONTROL = "data-reveal";
27 - /** What that control has to hold: `any`, `none` or `value`. */
28 - const WHEN = "data-reveal-when";
29 - /** The values `value` accepts, as a JSON array. */
30 - const VALUES = "data-reveal-values";
31 -
32 - /**
33 - * What the named control is holding now, or null when it holds nothing.
34 - *
35 - * By `name` rather than by `id`, because `name` is what the description
36 - * carries: it is what a submit sends the value under and what a region
37 - * names. An id is scoped per form instance by the emitter and is therefore
38 - * not the string the description wrote.
39 - *
40 - * Searched from the region outward: the nearest enclosing form first, then
41 - * the document, which is `fill.js`'s rule and is here for the same reason.
42 - * A screen showing the same form twice -- an edit modal over a list --
43 - * otherwise reads the first copy in the document whichever one the reader
44 - * is in.
45 - *
46 - * A tick is there by presence, the way a form submits one, so an unticked
47 - * box and an empty box are one answer. That is the convention the router
48 - * states and the other two renderers hold.
49 - */
50 - const held = (region, name) => {
51 - const selector = `[name="${CSS.escape(name)}"]`;
52 - const scope = region.closest("form") ?? document;
53 - const controls = scope.querySelectorAll(selector);
54 - let value = null;
55 - for (const control of controls) {
56 - if (control.type === "checkbox" || control.type === "radio") {
57 - if (control.checked) {
58 - value = control.value === "" ? "on" : control.value;
59 - }
60 - continue;
61 - }
62 - value = control.value ?? null;
63 - }
64 - return value === "" ? null : value;
65 - };
66 -
67 - /** Whether a region asking for this is satisfied by what is held. */
68 - const satisfied = (region, value) => {
69 - const when = region.getAttribute(WHEN);
70 - if (when === "any") {
71 - return value !== null;
72 - }
73 - if (when === "none") {
74 - return value === null;
75 - }
76 - if (value === null) {
77 - return false;
78 - }
79 - let wanted;
80 - try {
81 - wanted = JSON.parse(region.getAttribute(VALUES) ?? "[]");
82 - } catch {
83 - // An attribute this file did not write. Nothing is revealed on a
84 - // condition nobody can read, which is the same answer a region
85 - // watching a control that is not on the screen gets.
86 - return false;
87 - }
88 - return Array.isArray(wanted) && wanted.includes(value);
89 - };
90 -
91 - /**
92 - * Put every conditional region where its condition says it belongs.
93 - *
94 - * A walk over the document rather than over a list captured when the page
95 - * loaded, for the reason the clock gives: a swap brings new regions and
96 - * takes old ones away, and both are found by the next pass.
97 - */
98 - const settle = () => {
99 - for (const region of document.querySelectorAll(`[${CONTROL}]`)) {
100 - const value = held(region, region.getAttribute(CONTROL) ?? "");
101 - region.hidden = !satisfied(region, value);
102 - }
103 - };
104 -
105 - // Delegated, and on both events a control settles under: `change` is what a
106 - // checkbox and a select fire, `input` is what a box being typed into fires,
107 - // and a region may watch either.
108 - document.addEventListener("change", settle);
109 - document.addEventListener("input", settle);
110 -
111 - // A region that arrives mid-page should not wait for the reader to touch
112 - // something before it decides whether it applies. htmx's event and the
113 - // initial parse both land here.
114 - //
115 - // `htmx:after:settle` is htmx 4's name for it; 2.x spelled the same event
116 - // `htmx:afterSettle`, and 4 renamed every event to phase:action.
117 - document.addEventListener("htmx:after:settle", settle);
118 - if (document.readyState === "loading") {
119 - document.addEventListener("DOMContentLoaded", settle);
120 - } else {
121 - settle();
122 - }
123 - })();