diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 9465cbd..2e95189 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-07T01:41:53Z"; + var SX_VERSION = "2026-03-07T02:03:55Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -1984,13 +1984,13 @@ return postSwap(target); }); })(); } return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() { - var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(verbInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay"))); + var liveInfo = sxOr(getVerbInfo(el), verbInfo); + var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(liveInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay"))); var clientRouted = false; if (isSxTruthy(isGetLink)) { - logInfo((String("sx:route trying ") + String(get(verbInfo, "url")))); - clientRouted = tryClientRoute(urlPathname(get(verbInfo, "url")), domGetAttr(el, "sx-target")); + clientRouted = tryClientRoute(urlPathname(get(liveInfo, "url")), domGetAttr(el, "sx-target")); } - return (isSxTruthy(clientRouted) ? (browserPushState(get(verbInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(verbInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, get(mods, "delay")))) : executeRequest(el, verbInfo, NIL)))); + return (isSxTruthy(clientRouted) ? (browserPushState(get(liveInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(liveInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "delay")))) : executeRequest(el, NIL, NIL)))); })()) : NIL); })(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL); })(); }; @@ -3449,11 +3449,13 @@ callExpr.push(dictGet(kwargs, k)); } } // --- Boost bindings --- - function bindBoostLink(el, href) { + function bindBoostLink(el, _href) { el.addEventListener("click", function(e) { e.preventDefault(); - executeRequest(el, { method: "GET", url: href }).then(function() { - try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {} + // Re-read href from element at click time (not closed-over value) + var liveHref = el.getAttribute("href") || _href; + executeRequest(el, { method: "GET", url: liveHref }).then(function() { + try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {} }); }); } @@ -3469,10 +3471,12 @@ callExpr.push(dictGet(kwargs, k)); } } // --- Client-side route bindings --- - function bindClientRouteClick(link, href, fallbackFn) { + function bindClientRouteClick(link, _href, fallbackFn) { link.addEventListener("click", function(e) { e.preventDefault(); - var pathname = urlPathname(href); + // Re-read href from element at click time (not closed-over value) + var liveHref = link.getAttribute("href") || _href; + var pathname = urlPathname(liveHref); // Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel var boostEl = link.closest("[sx-boost]"); var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null; @@ -3480,12 +3484,12 @@ callExpr.push(dictGet(kwargs, k)); } } targetSel = link.getAttribute("sx-target") || "#main-panel"; } if (tryClientRoute(pathname, targetSel)) { - try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {} + try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {} if (typeof window !== "undefined") window.scrollTo(0, 0); } else { logInfo("sx:route server " + pathname); - executeRequest(link, { method: "GET", url: href }).then(function() { - try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {} + executeRequest(link, { method: "GET", url: liveHref }).then(function() { + try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {} }); } }); diff --git a/shared/sx/ref/bootstrap_js.py b/shared/sx/ref/bootstrap_js.py index a66a707..1dd9822 100644 --- a/shared/sx/ref/bootstrap_js.py +++ b/shared/sx/ref/bootstrap_js.py @@ -2720,11 +2720,13 @@ PLATFORM_ORCHESTRATION_JS = """ // --- Boost bindings --- - function bindBoostLink(el, href) { + function bindBoostLink(el, _href) { el.addEventListener("click", function(e) { e.preventDefault(); - executeRequest(el, { method: "GET", url: href }).then(function() { - try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {} + // Re-read href from element at click time (not closed-over value) + var liveHref = el.getAttribute("href") || _href; + executeRequest(el, { method: "GET", url: liveHref }).then(function() { + try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {} }); }); } @@ -2740,10 +2742,12 @@ PLATFORM_ORCHESTRATION_JS = """ // --- Client-side route bindings --- - function bindClientRouteClick(link, href, fallbackFn) { + function bindClientRouteClick(link, _href, fallbackFn) { link.addEventListener("click", function(e) { e.preventDefault(); - var pathname = urlPathname(href); + // Re-read href from element at click time (not closed-over value) + var liveHref = link.getAttribute("href") || _href; + var pathname = urlPathname(liveHref); // Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel var boostEl = link.closest("[sx-boost]"); var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null; @@ -2751,12 +2755,12 @@ PLATFORM_ORCHESTRATION_JS = """ targetSel = link.getAttribute("sx-target") || "#main-panel"; } if (tryClientRoute(pathname, targetSel)) { - try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {} + try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {} if (typeof window !== "undefined") window.scrollTo(0, 0); } else { logInfo("sx:route server " + pathname); - executeRequest(link, { method: "GET", url: href }).then(function() { - try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {} + executeRequest(link, { method: "GET", url: liveHref }).then(function() { + try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {} }); } }); diff --git a/shared/sx/ref/orchestration.sx b/shared/sx/ref/orchestration.sx index 81abd7d..823e7a5 100644 --- a/shared/sx/ref/orchestration.sx +++ b/shared/sx/ref/orchestration.sx @@ -388,33 +388,33 @@ (dom-has-attr? el "href"))) (prevent-default e)) - ;; For GET clicks on links, try client-side routing first - (let ((is-get-link (and (= event-name "click") - (= (get verbInfo "method") "GET") + ;; Re-read verb info from element at click time (not closed-over) + (let ((live-info (or (get-verb-info el) verbInfo)) + (is-get-link (and (= event-name "click") + (= (get live-info "method") "GET") (dom-has-attr? el "href") (not (get mods "delay")))) (client-routed false)) (when is-get-link - (log-info (str "sx:route trying " (get verbInfo "url"))) (set! client-routed (try-client-route - (url-pathname (get verbInfo "url")) + (url-pathname (get live-info "url")) (dom-get-attr el "sx-target")))) (if client-routed (do - (browser-push-state (get verbInfo "url")) + (browser-push-state (get live-info "url")) (browser-scroll-to 0 0)) (do (when is-get-link - (log-info (str "sx:route server fetch " (get verbInfo "url")))) + (log-info (str "sx:route server fetch " (get live-info "url")))) (if (get mods "delay") (do (clear-timeout timer) (set! timer (set-timeout - (fn () (execute-request el verbInfo nil)) + (fn () (execute-request el nil nil)) (get mods "delay")))) - (execute-request el verbInfo nil)))))))) + (execute-request el nil nil)))))))) (if (get mods "once") (dict "once" true) nil))))))