From 04ff03f5d4ea06439e7fe45547dcc482a0560b6c Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 7 Mar 2026 02:11:09 +0000 Subject: [PATCH] Live-read all DOM attributes: forms and preloads too bindBoostForm re-reads method/action at submit time. bind-preload-for re-reads verb-info and headers at preload time. No closed-over stale values anywhere in the event binding system. Co-Authored-By: Claude Opus 4.6 --- shared/static/scripts/sx-browser.js | 21 +++++++++++---------- shared/sx/ref/bootstrap_js.py | 9 ++++++--- shared/sx/ref/orchestration.sx | 23 ++++++++++++----------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 2e95189..35d6982 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-07T02:03:55Z"; + var SX_VERSION = "2026-03-07T02:10:28Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -2185,14 +2185,12 @@ return postSwap(target); })) : NIL); var bindPreloadFor = function(el) { return (function() { var preloadAttr = domGetAttr(el, "sx-preload"); return (isSxTruthy(preloadAttr) ? (function() { - var info = getVerbInfo(el); - return (isSxTruthy(info) ? (function() { - var url = get(info, "url"); - var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash); var events = (isSxTruthy((preloadAttr == "mousedown")) ? ["mousedown", "touchstart"] : ["mouseover"]); var debounceMs = (isSxTruthy((preloadAttr == "mousedown")) ? 0 : 100); - return bindPreload(el, events, debounceMs, function() { return doPreload(url, headers); }); -})() : NIL); + return bindPreload(el, events, debounceMs, function() { return (function() { + var info = getVerbInfo(el); + return (isSxTruthy(info) ? doPreload(get(info, "url"), buildRequestHeaders(el, loadedComponentNames(), _cssHash)) : NIL); +})(); }); })() : NIL); })(); }; @@ -3460,11 +3458,14 @@ callExpr.push(dictGet(kwargs, k)); } } }); } - function bindBoostForm(form, method, action) { + function bindBoostForm(form, _method, _action) { form.addEventListener("submit", function(e) { e.preventDefault(); - executeRequest(form, { method: method, url: action }).then(function() { - try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {} + // Re-read from element at submit time + var liveMethod = (form.getAttribute("method") || _method || "GET").toUpperCase(); + var liveAction = form.getAttribute("action") || _action || location.href; + executeRequest(form, { method: liveMethod, url: liveAction }).then(function() { + try { history.pushState({ sxUrl: liveAction, scrollY: window.scrollY }, "", liveAction); } catch (err) {} }); }); } diff --git a/shared/sx/ref/bootstrap_js.py b/shared/sx/ref/bootstrap_js.py index 1dd9822..f2d62be 100644 --- a/shared/sx/ref/bootstrap_js.py +++ b/shared/sx/ref/bootstrap_js.py @@ -2731,11 +2731,14 @@ PLATFORM_ORCHESTRATION_JS = """ }); } - function bindBoostForm(form, method, action) { + function bindBoostForm(form, _method, _action) { form.addEventListener("submit", function(e) { e.preventDefault(); - executeRequest(form, { method: method, url: action }).then(function() { - try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {} + // Re-read from element at submit time + var liveMethod = (form.getAttribute("method") || _method || "GET").toUpperCase(); + var liveAction = form.getAttribute("action") || _action || location.href; + executeRequest(form, { method: liveMethod, url: liveAction }).then(function() { + try { history.pushState({ sxUrl: liveAction, scrollY: window.scrollY }, "", liveAction); } catch (err) {} }); }); } diff --git a/shared/sx/ref/orchestration.sx b/shared/sx/ref/orchestration.sx index 823e7a5..b1b9efe 100644 --- a/shared/sx/ref/orchestration.sx +++ b/shared/sx/ref/orchestration.sx @@ -780,17 +780,18 @@ ;; Bind preload event listeners based on sx-preload attribute (let ((preload-attr (dom-get-attr el "sx-preload"))) (when preload-attr - (let ((info (get-verb-info el))) - (when info - (let ((url (get info "url")) - (headers (build-request-headers el - (loaded-component-names) _css-hash)) - (events (if (= preload-attr "mousedown") - (list "mousedown" "touchstart") - (list "mouseover"))) - (debounce-ms (if (= preload-attr "mousedown") 0 100))) - (bind-preload el events debounce-ms - (fn () (do-preload url headers)))))))))) + (let ((events (if (= preload-attr "mousedown") + (list "mousedown" "touchstart") + (list "mouseover"))) + (debounce-ms (if (= preload-attr "mousedown") 0 100))) + ;; Re-read verb info and headers at preload time, not bind time + (bind-preload el events debounce-ms + (fn () + (let ((info (get-verb-info el))) + (when info + (do-preload (get info "url") + (build-request-headers el + (loaded-component-names) _css-hash))))))))))) (define do-preload