Commit1b039fe6Recorded17 Jul 2026Repositorysigil-web-client

sigil-web-client: v0.15.3 normalize '() content to '()

Message

web-client-normalize-children fell through '() to the (else (list value)) branch, producing (()): a phantom empty text child. That left an emptied element's old-records list non-null, so reconcile-children! never ran the empty->populated bulk clear, and foreign children of a reused element could survive a full replacement. Treat the empty content list like #f (no children). Adds a web-client-normalize-children test group (empty, #f, content list, single atom).

Changed
 package.sgl              |  2 +-
 src/sigil/web/client.sgl | 11 +++++++++--
 test/test-client.sgl     | 19 +++++++++++++++++++
 3 files changed, 29 insertions(+), 3 deletions(-)
Diff
package.sglmodified
@@ -14,7 +14,7 @@
14
15
(package
16
name: "sigil-web-client"
17
version: "0.15.2"
+17
version: "0.15.3"
18
sigil: "^0.17"
19
description: "Client-side WASM UI framework for Sigil: react-like browser app rendering"
20
url: "https://codeberg.org/sigil/sigil-web-client"
src/sigil/web/client.sglmodified
@@ -19,7 +19,10 @@
19
web-client-reset!
20
;; Exported for tests: the pure child-order diff used by the keyed
21
;; reconciler. Not part of the stable public API.
22
web-client-child-moves)
+22
web-client-child-moves
+23
;; Exported for tests: the SXML child-content normalizer. Not part of
+24
;; the stable public API.
+25
web-client-normalize-children)
26
27
(begin
28
(define web-client-roots '())
@@ -148,7 +151,11 @@
151
152
(define (web-client-normalize-children value)
153
(cond
151
((not value) '())
+154
;; #f and the empty content list both mean "no children". Without the
+155
;; null? case '() falls through to (else (list value)) => (()), a
+156
;; phantom empty text child that leaves the old-records list non-null,
+157
;; so reconcile-children! never runs the empty->populated bulk clear.
+158
((or (not value) (null? value)) '())
159
((and (web-client-sxml-element? value)
160
(or (eq? (web-client-sxml-tag value) '*TOP*)
161
(eq? (web-client-sxml-tag value) 'document)))
test/test-client.sglmodified
@@ -23,6 +23,25 @@
23
(test "clears retained root state"
24
(assert-true (web-client-reset!))))
25
+26
;; Normalizing element content into a flat child list. The empty-content case
+27
;; is load-bearing: '() must stay '() so an emptied element reconciles as
+28
;; genuinely empty (old-records null => the empty->populated bulk clear fires).
+29
;; Before the null? case, '() fell to (else (list value)) => (()), a phantom
+30
;; empty text child that suppressed the clear and leaked foreign children.
+31
(test-group "web-client-normalize-children"
+32
(test "empty content stays empty"
+33
(assert-equal '() (web-client-normalize-children '())))
+34
+35
(test "#f content is empty"
+36
(assert-equal '() (web-client-normalize-children #f)))
+37
+38
(test "a content list passes through unchanged"
+39
(assert-equal '((span "a") (span "b"))
+40
(web-client-normalize-children '((span "a") (span "b")))))
+41
+42
(test "a single atom is wrapped as one child"
+43
(assert-equal '("hello") (web-client-normalize-children "hello"))))
+44
45
;; The pure child-order diff behind the keyed reconciler. Node handles are
46
;; plain integers here, exactly as the DOM bridge represents them. Each op is
47
;; (node . ref): insert node before ref, ref #f = append at the end.