From 31ed8b20f48700b889986da2fb0dedecc8d082e7 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 25 Mar 2026 15:50:12 +0000 Subject: [PATCH] Remove click buffer, add CSS cursor:pointer for island controls The click buffer (capture + stopPropagation + replay) caused more harm than good: synchronous XHR blocks the main thread during kernel load, so there's no window where clicks can be captured. The buffer was eating clicks after hydration due to property name mismatches. Replace with pure CSS: buttons/links/[role=button] inside islands get cursor:pointer from SSR. No JS needed, works immediately. Co-Authored-By: Claude Opus 4.6 (1M context) --- shared/static/wasm/sx/boot.sx | 22 +--------------------- shared/sx/templates/shell.sx | 5 ++--- web/boot.sx | 22 +--------------------- 3 files changed, 4 insertions(+), 45 deletions(-) diff --git a/shared/static/wasm/sx/boot.sx b/shared/static/wasm/sx/boot.sx index a7cbbdeb..154c3b76 100644 --- a/shared/static/wasm/sx/boot.sx +++ b/shared/static/wasm/sx/boot.sx @@ -492,27 +492,7 @@ (process-elements nil) ;; Wire up popstate for back/forward navigation (dom-listen (dom-window) "popstate" - (fn (e) (handle-popstate 0))) - ;; Replay buffered clicks from hydration gap - (let ((queued (host-get (dom-window) "_sxQ"))) - (when queued - (let ((arr (host-call (host-global "Array") "from" queued))) - (let ((n (host-get arr "length"))) - (when (> n 0) - (log-info (str "replaying " n " buffered click(s)")) - (let loop ((i 0)) - (when (< i n) - (let ((entry (host-call arr "at" i))) - (when entry - (let ((target (host-get entry "t"))) - (when (and target (host-get target "isConnected")) - (dom-remove-class target "sx-pending") - (host-call target "click"))))) - (loop (+ i 1))))))) - (host-set! (dom-window) "_sxQ" nil) - ;; Clear any remaining pending indicators - (for-each (fn (el) (dom-remove-class el "sx-pending")) - (dom-query-all (dom-body) ".sx-pending"))))))) + (fn (e) (handle-popstate 0)))))) diff --git a/shared/sx/templates/shell.sx b/shared/sx/templates/shell.sx index e7e92e2f..bb12899f 100644 --- a/shared/sx/templates/shell.sx +++ b/shared/sx/templates/shell.sx @@ -76,9 +76,8 @@ details.group{overflow:hidden}details.group>summary{list-style:none}details.grou (body :class "bg-stone-50 text-stone-900" ;; Server-rendered HTML — visible immediately before JS loads (div :id "sx-root" (raw! (or body-html ""))) - ;; Event buffer — captures clicks during hydration gap, replayed after boot - (style (raw! "@keyframes sx-pending-pulse{0%,100%{opacity:1}50%{opacity:.5}}.sx-pending{animation:sx-pending-pulse .8s ease-in-out infinite}")) - (script (raw! "document.addEventListener('click',function(e){var i=e.target.closest('[data-sx-island]');if(i&&!i['_sxBoundisland-hydrated']){e.stopPropagation();var t=e.target.closest('button,a,[role=button]')||e.target;t.classList.add('sx-pending');(window._sxQ=window._sxQ||[]).push({t:t,ts:Date.now()})}},true)")) + ;; Island interactive elements — cursor pointer from SSR, no JS needed + (style (raw! "[data-sx-island] button,[data-sx-island] a,[data-sx-island] [role=button]{cursor:pointer}")) (script :type "text/sx" :data-components true :data-hash component-hash (raw! (or component-defs ""))) (when init-sx diff --git a/web/boot.sx b/web/boot.sx index a7cbbdeb..154c3b76 100644 --- a/web/boot.sx +++ b/web/boot.sx @@ -492,27 +492,7 @@ (process-elements nil) ;; Wire up popstate for back/forward navigation (dom-listen (dom-window) "popstate" - (fn (e) (handle-popstate 0))) - ;; Replay buffered clicks from hydration gap - (let ((queued (host-get (dom-window) "_sxQ"))) - (when queued - (let ((arr (host-call (host-global "Array") "from" queued))) - (let ((n (host-get arr "length"))) - (when (> n 0) - (log-info (str "replaying " n " buffered click(s)")) - (let loop ((i 0)) - (when (< i n) - (let ((entry (host-call arr "at" i))) - (when entry - (let ((target (host-get entry "t"))) - (when (and target (host-get target "isConnected")) - (dom-remove-class target "sx-pending") - (host-call target "click"))))) - (loop (+ i 1))))))) - (host-set! (dom-window) "_sxQ" nil) - ;; Clear any remaining pending indicators - (for-each (fn (el) (dom-remove-class el "sx-pending")) - (dom-query-all (dom-body) ".sx-pending"))))))) + (fn (e) (handle-popstate 0))))))