From 90a2eaaf7aa6b7b5f671088cc7e2205cb0d7ea5c Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 2 Apr 2026 00:17:02 +0000 Subject: [PATCH] Fix infinite scroll and all sx-trigger/sx-get element binding Root cause: process-elements during WASM boot-init marks elements as processed but process-one silently fails (effect functions don't execute in WASM boot context). Deferred process-elements then skips them. Fixes: - boot-init: defer process-elements via set-timeout 0 - hydrate-island: defer process-elements via set-timeout 0 - process-elements: move mark-processed! after process-one so failed boot-context calls don't poison the flag - observe-intersection: native JS platform function (K.registerNative) to avoid bytecode callback issue with IntersectionObserver - Remove SX observe-intersection from boot-helpers.sx (was overriding the working native version) Co-Authored-By: Claude Opus 4.6 (1M context) --- hosts/ocaml/browser/sx-platform.js | 16 ++++++++++++++++ web/boot.sx | 4 ++-- web/lib/boot-helpers.sx | 11 ----------- web/orchestration.sx | 8 ++++---- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/hosts/ocaml/browser/sx-platform.js b/hosts/ocaml/browser/sx-platform.js index d8867161..eb94a850 100644 --- a/hosts/ocaml/browser/sx-platform.js +++ b/hosts/ocaml/browser/sx-platform.js @@ -166,6 +166,22 @@ document.cookie = args[0] + "=" + encodeURIComponent(args[1] || "") + ";path=/;max-age=31536000;SameSite=Lax"; }); + // IntersectionObserver — native JS to avoid bytecode callback issues + K.registerNative("observe-intersection", function(args) { + var el = args[0], callback = args[1], once = args[2], delay = args[3]; + var obs = new IntersectionObserver(function(entries) { + for (var i = 0; i < entries.length; i++) { + if (entries[i].isIntersecting) { + var d = (delay && delay !== null) ? delay : 0; + setTimeout(function() { K.callFn(callback, []); }, d); + if (once) obs.unobserve(el); + } + } + }); + obs.observe(el); + return obs; + }); + // ================================================================ // Load SX web libraries and adapters // ================================================================ diff --git a/web/boot.sx b/web/boot.sx index 9b24e00b..acca9267 100644 --- a/web/boot.sx +++ b/web/boot.sx @@ -321,7 +321,7 @@ (dom-set-text-content el "") (dom-append el body-dom) (dom-set-data el "sx-disposers" disposers) - (process-elements el) + (set-timeout (fn () (process-elements el)) 0) (log-info (str "hydrated island: " @@ -430,7 +430,7 @@ (sx-hydrate-elements nil) (sx-hydrate-islands nil) (run-post-render-hooks) - (process-elements nil) + (set-timeout (fn () (process-elements nil)) 0) (dom-listen (dom-window) "popstate" (fn (e) (handle-popstate 0))) (dom-set-attr (host-get (dom-document) "documentElement") diff --git a/web/lib/boot-helpers.sx b/web/lib/boot-helpers.sx index 211f5a1d..5f363fc1 100644 --- a/web/lib/boot-helpers.sx +++ b/web/lib/boot-helpers.sx @@ -430,17 +430,6 @@ (host-callback thunk)) (thunk)))) -(define - observe-intersection - (fn - (el callback once? delay) - (let - ((cb (host-callback (fn (entries) (for-each (fn (entry) (when (host-get entry "isIntersecting") (if delay (set-timeout (fn () (callback entry)) delay) (callback entry)) (when once? (host-call observer "unobserve" el)))) (host-call entries "forEach" (host-callback (fn (e) e)))))))) - (let - ((observer (host-new "IntersectionObserver" (host-callback (fn (entries) (let ((arr-len (host-get entries "length"))) (let loop ((i 0)) (when (< i arr-len) (let ((entry (host-call entries "item" i))) (when (and entry (host-get entry "isIntersecting")) (if delay (set-timeout (fn () (callback entry)) delay) (callback entry)) (when once? (host-call observer "unobserve" el)))) (loop (+ i 1)))))))))) - (host-call observer "observe" el) - observer)))) - (define event-source-connect (fn diff --git a/web/orchestration.sx b/web/orchestration.sx index eb3b380b..ba2572d2 100644 --- a/web/orchestration.sx +++ b/web/orchestration.sx @@ -428,7 +428,7 @@ (observe-intersection el (fn () (execute-request el nil nil)) - false + true (get mods "delay")) (= kind "load") (set-timeout @@ -438,7 +438,7 @@ (observe-intersection el (fn () (execute-request el nil nil)) - true + false (get mods "delay")) (= kind "event") (bind-event el (get trigger "event") mods verbInfo)))) @@ -1484,8 +1484,8 @@ (el) (when (not (is-processed? el "verb")) - (mark-processed! el "verb") - (process-one el))) + (process-one el) + (mark-processed! el "verb"))) els)) (process-boosted root) (process-sse root)