Commite33f8560Recorded16 Jul 2026Repositorysigil-web-client

web-client: simplify reconcile-children

Message

Use map for the target node list and fold the empty->populated branch into the general moves path: first populate is now the same prune+moves flow with a preceding bulk clear (dropping possible pre-existing static markup), removing the duplicated append loop.

Changed
 src/sigil/web/client.sgl | 64 +++++++++++++++++++++++++++++-----------------------------------
 1 file changed, 29 insertions(+), 35 deletions(-)
Diff
src/sigil/web/client.sglmodified
@@ -465,41 +465,35 @@
465
;; dropped nodes, then insert-before only the nodes whose position
466
;; actually changed. The unchanged common case makes zero DOM calls.
467
(define (web-client-reconcile-children! parent old-records ordered)
468
(let ((target (let collect ((rest ordered) (acc '()))
469
(if (null? rest)
470
(reverse acc)
471
(collect (cdr rest)
472
(cons (web-client-record-node (car rest)) acc))))))
473
(cond
474
;; populated -> empty: one bulk clear beats N removals.
475
((null? target)
476
(when (pair? old-records)
477
(wasm-dom-replace-children parent)))
478
;; empty -> populated (first populate of this parent): clear any
479
;; pre-existing content (e.g. static placeholder markup in a root
480
;; that was never rendered into) and append in order, exactly like
481
;; the old path did on initial render.
482
((null? old-records)
483
(wasm-dom-replace-children parent)
484
(for-each
485
(lambda (node) (wasm-dom-append-child parent node))
486
target))
487
(else
488
;; Drop old top-level nodes not carried into the new set, keeping
489
;; the still-attached nodes in their current DOM order.
490
(let ((current (let prune ((rest old-records) (acc '()))
491
(if (null? rest)
492
(reverse acc)
493
(let ((node (web-client-record-node (car rest))))
494
(if (memv node target)
495
(prune (cdr rest) (cons node acc))
496
(begin
497
(wasm-dom-remove-child parent node)
498
(prune (cdr rest) acc))))))))
499
(for-each
500
(lambda (op)
501
(wasm-dom-insert-before parent (car op) (cdr op)))
502
(web-client-child-moves current target)))))))
+468
(let ((target (map web-client-record-node ordered)))
+469
(if (null? target)
+470
;; populated -> empty: one bulk clear beats N removals. (When both
+471
;; sides are empty nothing is touched, so a childless element costs
+472
;; no bridge call per render.)
+473
(when (pair? old-records)
+474
(wasm-dom-replace-children parent))
+475
(begin
+476
;; empty -> populated (first populate of this parent): clear any
+477
;; pre-existing content first (e.g. static placeholder markup in
+478
;; a root that was never rendered into), like the old path did on
+479
;; initial render. The moves below then append every node.
+480
(when (null? old-records)
+481
(wasm-dom-replace-children parent))
+482
;; Drop old top-level nodes not carried into the new set, keeping
+483
;; the still-attached nodes in their current DOM order.
+484
(let ((current (let prune ((rest old-records) (acc '()))
+485
(if (null? rest)
+486
(reverse acc)
+487
(let ((node (web-client-record-node (car rest))))
+488
(if (memv node target)
+489
(prune (cdr rest) (cons node acc))
+490
(begin
+491
(wasm-dom-remove-child parent node)
+492
(prune (cdr rest) acc))))))))
+493
(for-each
+494
(lambda (op)
+495
(wasm-dom-insert-before parent (car op) (cdr op)))
+496
(web-client-child-moves current target)))))))
497
498
(define (web-client-patch-children parent old-records new-values)
499
(let loop ((rest (web-client-normalize-children new-values))