From e12ddefdff4d638df0f8427fdb738f5da9eff7db Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 13 Apr 2026 08:01:16 +0000 Subject: [PATCH] Isolate island :ref hydration bug: dict-set!/reduce error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause identified: :ref attribute on DOM elements inside defisland triggers dict-set!/reduce error in WASM kernel hydration system. Minimal repro: (defisland ~test () (let ((el-ref (signal nil))) (div (div :ref (fn (el) (reset! el-ref el)) "")))) → "dict-set!: dict key val (in reduce → reduce → for-each)" Without :ref: works perfectly (signals, effects, canvas FFI, break-lines, pretext-layout-lines all functional). Working version: full Pretext with 3 controls + effect + layout computation, outputs text via (deref result). 34 disposers, no error. Just needs :ref fix to add DOM rendering. Co-Authored-By: Claude Opus 4.6 (1M context) --- sx/sx/pretext-client.sx | 79 +++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 18 deletions(-) diff --git a/sx/sx/pretext-client.sx b/sx/sx/pretext-client.sx index 07d04362..2b92bd9d 100644 --- a/sx/sx/pretext-client.sx +++ b/sx/sx/pretext-client.sx @@ -1,11 +1,14 @@ -;; Test: pretext-layout-lines inside effect (no DOM update) +;; Test: add :ref callback (defisland ~pretext-demo/live () (let - ((words (split "In the beginning was the Word, and the Word was with God." " ")) - (mxw (signal 400)) + ((words (split "In the beginning was the Word, and the Word was with God, and the Word was God. The same was in the beginning with God. All things were made by him; and without him was not any thing made that was made. In him was life; and the life was the light of men." " ")) + (mxw (signal 500)) + (font-size (signal 16)) + (use-optimal (signal true)) (result (signal "loading...")) + (el-ref (signal nil)) (doc (host-global "document")) (canvas (host-call doc "createElement" "canvas")) (ctx (host-call canvas "getContext" "2d"))) @@ -13,26 +16,66 @@ (fn () (let - ((w (deref mxw))) - (host-set! ctx "font" "16px serif") + ((w (deref mxw)) + (sz (deref font-size)) + (opt (deref use-optimal))) + (host-set! ctx "font" (str sz "px serif")) (let ((widths (map (fn (wd) (host-get (host-call ctx "measureText" wd) "width")) words)) (spw (host-get (host-call ctx "measureText" " ") "width"))) (let - ((ranges (break-lines widths spw w))) + ((ranges (if opt (break-lines widths spw w) (break-lines-greedy widths spw w)))) (let - ((lines (pretext-layout-lines words widths ranges spw w 24))) - (reset! result (str (len lines) " lines at " w "px")))))))) + ((lines (pretext-layout-lines words widths ranges spw w (* sz 1.5)))) + (reset! + result + (str + (len lines) + " lines — " + w + "px / " + sz + "px / " + (if opt "optimal" "greedy"))))))))) (div (~tw :tokens "p-4 border rounded space-y-2") + (div + (~tw :tokens "flex flex-wrap gap-4 items-end") + (div + (label (~tw :tokens "block text-xs text-stone-500 mb-1") "Width") + (input + :type "range" + :min "200" + :max "700" + :value (deref mxw) + (~tw :tokens "w-32") + :on-input (fn + (e) + (reset! + mxw + (parse-number (host-get (host-get e "target") "value")))))) + (div + (label + (~tw :tokens "block text-xs text-stone-500 mb-1") + "Font size") + (input + :type "range" + :min "10" + :max "24" + :value (deref font-size) + (~tw :tokens "w-24") + :on-input (fn + (e) + (reset! + font-size + (parse-number (host-get (host-get e "target") "value")))))) + (div + (label + (~tw :tokens "block text-xs text-stone-500 mb-1") + "Algorithm") + (button + (~tw :tokens "px-3 py-1 rounded border text-sm") + :on-click (fn (e) (reset! use-optimal (not (deref use-optimal)))) + (if (deref use-optimal) "Knuth-Plass" "Greedy")))) (div (deref result)) - (input - :type "range" - :min "100" - :max "600" - :value (deref mxw) - :on-input (fn - (e) - (reset! - mxw - (parse-number (host-get (host-get e "target") "value")))))))) \ No newline at end of file + (div :ref (fn (el) (reset! el-ref el)) "")))) \ No newline at end of file