Fix DOM-preserving hydration: text node mismatch + conditional markers
Two issues with the initial hydration implementation:
1. Text node mismatch: SSR merges adjacent text into one node
("0 / 16") but client renders three separate children. When the
cursor ran out, new nodes were created but dom-append was
unconditionally skipped. Fix: only skip append when the child
already has a parent (existing SSR node). New nodes (nil parent)
get appended even during hydration.
2. Conditional markers: dispatch-render-form for if/when/cond in
island scope was injecting comment markers during hydration,
corrupting the DOM. Fix: skip the reactive conditional machinery
during hydration — just evaluate and render the active branch
normally, walking the cursor. Reactivity for conditionals
activates after the first user-triggered re-render.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -375,7 +375,11 @@
|
|||||||
(fn () (render-to-dom arg env new-ns)))
|
(fn () (render-to-dom arg env new-ns)))
|
||||||
(spread? child)
|
(spread? child)
|
||||||
nil
|
nil
|
||||||
:else (when (not (hydrating?)) (dom-append el child)))))
|
:else (when
|
||||||
|
(or
|
||||||
|
(not (hydrating?))
|
||||||
|
(nil? (dom-parent child)))
|
||||||
|
(dom-append el child)))))
|
||||||
(assoc state "i" (inc (get state "i"))))))))
|
(assoc state "i" (inc (get state "i"))))))))
|
||||||
(dict "i" 0 "skip" false)
|
(dict "i" 0 "skip" false)
|
||||||
args)
|
args)
|
||||||
@@ -477,9 +481,18 @@
|
|||||||
((args :as list) (env :as dict) (ns :as string))
|
((args :as list) (env :as dict) (ns :as string))
|
||||||
(if
|
(if
|
||||||
(hydrating?)
|
(hydrating?)
|
||||||
(do
|
(let
|
||||||
(for-each (fn (x) (render-to-dom x env ns)) args)
|
((frag (create-fragment)))
|
||||||
(create-fragment))
|
(for-each
|
||||||
|
(fn
|
||||||
|
(x)
|
||||||
|
(let
|
||||||
|
((result (render-to-dom x env ns)))
|
||||||
|
(when
|
||||||
|
(and (not (spread? result)) (nil? (dom-parent result)))
|
||||||
|
(dom-append frag result))))
|
||||||
|
args)
|
||||||
|
frag)
|
||||||
(let
|
(let
|
||||||
((frag (create-fragment)))
|
((frag (create-fragment)))
|
||||||
(for-each
|
(for-each
|
||||||
@@ -553,7 +566,7 @@
|
|||||||
(cond
|
(cond
|
||||||
(= name "if")
|
(= name "if")
|
||||||
(if
|
(if
|
||||||
(island-scope?)
|
(and (island-scope?) (not (hydrating?)))
|
||||||
(let
|
(let
|
||||||
((marker (create-comment "r-if"))
|
((marker (create-comment "r-if"))
|
||||||
(current-nodes (list))
|
(current-nodes (list))
|
||||||
@@ -617,7 +630,7 @@
|
|||||||
(create-fragment)))))
|
(create-fragment)))))
|
||||||
(= name "when")
|
(= name "when")
|
||||||
(if
|
(if
|
||||||
(island-scope?)
|
(and (island-scope?) (not (hydrating?)))
|
||||||
(let
|
(let
|
||||||
((marker (create-comment "r-when"))
|
((marker (create-comment "r-when"))
|
||||||
(current-nodes (list))
|
(current-nodes (list))
|
||||||
@@ -701,7 +714,7 @@
|
|||||||
frag)))
|
frag)))
|
||||||
(= name "cond")
|
(= name "cond")
|
||||||
(if
|
(if
|
||||||
(island-scope?)
|
(and (island-scope?) (not (hydrating?)))
|
||||||
(let
|
(let
|
||||||
((marker (create-comment "r-cond"))
|
((marker (create-comment "r-cond"))
|
||||||
(current-nodes (list))
|
(current-nodes (list))
|
||||||
@@ -1080,7 +1093,9 @@
|
|||||||
(c)
|
(c)
|
||||||
(let
|
(let
|
||||||
((result (render-to-dom c env ns)))
|
((result (render-to-dom c env ns)))
|
||||||
(when (not (hydrating?)) (dom-append el result))))
|
(when
|
||||||
|
(or (not (hydrating?)) (nil? (dom-parent result)))
|
||||||
|
(dom-append el result))))
|
||||||
children)
|
children)
|
||||||
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
||||||
(define
|
(define
|
||||||
@@ -1135,7 +1150,9 @@
|
|||||||
(c)
|
(c)
|
||||||
(let
|
(let
|
||||||
((result (render-to-dom c env ns)))
|
((result (render-to-dom c env ns)))
|
||||||
(when (not (hydrating?)) (dom-append el result))))
|
(when
|
||||||
|
(or (not (hydrating?)) (nil? (dom-parent result)))
|
||||||
|
(dom-append el result))))
|
||||||
children)
|
children)
|
||||||
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
||||||
(define
|
(define
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -375,7 +375,11 @@
|
|||||||
(fn () (render-to-dom arg env new-ns)))
|
(fn () (render-to-dom arg env new-ns)))
|
||||||
(spread? child)
|
(spread? child)
|
||||||
nil
|
nil
|
||||||
:else (when (not (hydrating?)) (dom-append el child)))))
|
:else (when
|
||||||
|
(or
|
||||||
|
(not (hydrating?))
|
||||||
|
(nil? (dom-parent child)))
|
||||||
|
(dom-append el child)))))
|
||||||
(assoc state "i" (inc (get state "i"))))))))
|
(assoc state "i" (inc (get state "i"))))))))
|
||||||
(dict "i" 0 "skip" false)
|
(dict "i" 0 "skip" false)
|
||||||
args)
|
args)
|
||||||
@@ -477,9 +481,18 @@
|
|||||||
((args :as list) (env :as dict) (ns :as string))
|
((args :as list) (env :as dict) (ns :as string))
|
||||||
(if
|
(if
|
||||||
(hydrating?)
|
(hydrating?)
|
||||||
(do
|
(let
|
||||||
(for-each (fn (x) (render-to-dom x env ns)) args)
|
((frag (create-fragment)))
|
||||||
(create-fragment))
|
(for-each
|
||||||
|
(fn
|
||||||
|
(x)
|
||||||
|
(let
|
||||||
|
((result (render-to-dom x env ns)))
|
||||||
|
(when
|
||||||
|
(and (not (spread? result)) (nil? (dom-parent result)))
|
||||||
|
(dom-append frag result))))
|
||||||
|
args)
|
||||||
|
frag)
|
||||||
(let
|
(let
|
||||||
((frag (create-fragment)))
|
((frag (create-fragment)))
|
||||||
(for-each
|
(for-each
|
||||||
@@ -553,7 +566,7 @@
|
|||||||
(cond
|
(cond
|
||||||
(= name "if")
|
(= name "if")
|
||||||
(if
|
(if
|
||||||
(island-scope?)
|
(and (island-scope?) (not (hydrating?)))
|
||||||
(let
|
(let
|
||||||
((marker (create-comment "r-if"))
|
((marker (create-comment "r-if"))
|
||||||
(current-nodes (list))
|
(current-nodes (list))
|
||||||
@@ -617,7 +630,7 @@
|
|||||||
(create-fragment)))))
|
(create-fragment)))))
|
||||||
(= name "when")
|
(= name "when")
|
||||||
(if
|
(if
|
||||||
(island-scope?)
|
(and (island-scope?) (not (hydrating?)))
|
||||||
(let
|
(let
|
||||||
((marker (create-comment "r-when"))
|
((marker (create-comment "r-when"))
|
||||||
(current-nodes (list))
|
(current-nodes (list))
|
||||||
@@ -701,7 +714,7 @@
|
|||||||
frag)))
|
frag)))
|
||||||
(= name "cond")
|
(= name "cond")
|
||||||
(if
|
(if
|
||||||
(island-scope?)
|
(and (island-scope?) (not (hydrating?)))
|
||||||
(let
|
(let
|
||||||
((marker (create-comment "r-cond"))
|
((marker (create-comment "r-cond"))
|
||||||
(current-nodes (list))
|
(current-nodes (list))
|
||||||
@@ -1080,7 +1093,9 @@
|
|||||||
(c)
|
(c)
|
||||||
(let
|
(let
|
||||||
((result (render-to-dom c env ns)))
|
((result (render-to-dom c env ns)))
|
||||||
(when (not (hydrating?)) (dom-append el result))))
|
(when
|
||||||
|
(or (not (hydrating?)) (nil? (dom-parent result)))
|
||||||
|
(dom-append el result))))
|
||||||
children)
|
children)
|
||||||
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
||||||
(define
|
(define
|
||||||
@@ -1135,7 +1150,9 @@
|
|||||||
(c)
|
(c)
|
||||||
(let
|
(let
|
||||||
((result (render-to-dom c env ns)))
|
((result (render-to-dom c env ns)))
|
||||||
(when (not (hydrating?)) (dom-append el result))))
|
(when
|
||||||
|
(or (not (hydrating?)) (nil? (dom-parent result)))
|
||||||
|
(dom-append el result))))
|
||||||
children)
|
children)
|
||||||
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
(do (when (hydrating?) (hydrate-exit-element)) el)))))
|
||||||
(define
|
(define
|
||||||
|
|||||||
Reference in New Issue
Block a user