From 87d2baa35e125ffd2980375dc3eaea97f4c22c82 Mon Sep 17 00:00:00 2001 From: phillipc Date: Thu, 16 Jul 2026 19:27:39 +0200 Subject: [PATCH 1/2] docs: update descendantsComplete behavior and migration guidance for TKO --- tko.io/src/content/docs/3to4.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tko.io/src/content/docs/3to4.md b/tko.io/src/content/docs/3to4.md index 024cd6c10..51e35fe98 100644 --- a/tko.io/src/content/docs/3to4.md +++ b/tko.io/src/content/docs/3to4.md @@ -54,6 +54,30 @@ TKO is designed for stricter Content Security Policies and avoids the older `eva `ko.applyBindings` returns a `Promise`. If you chain bootstrap work after binding or need to wait for async bindings to finish, account for that in your startup flow. +### `descendantsComplete` no longer waits for async descendants + +In Knockout 3, `descendantsComplete` fired only after every descendant — including content rendered asynchronously by a nested `if`, `template`, `foreach`, or component — had finished binding. Knockout tracked this with an ancestor-linked async-completion context and, on control-flow bindings, a `completeOn: 'render'` option. + +TKO drops that ancestor-tracking machinery in favor of a simpler per-node model. As a result, `descendantsComplete` is decided **once**, synchronously, when the node itself binds, and waits only for async bindings **on that same node** — not for subtrees that appear later. If a descendant `if`/`template` renders its content after the fact (for example when its condition flips to `true`), the `descendantsComplete` callback is not re-triggered and will not fire. + +**Why:** the async-completion context was cross-cutting, hard to reason about, and a common source of disposal and ordering bugs. The per-node lifecycle is smaller and predictable, at the cost of this one edge case. + +**Migration:** use `childrenComplete` instead, which is notified every time a node's descendants are (re-)bound and therefore still fires when async content renders: + +```html + +
+
+
+``` + +```html + +
+
+
+``` + ## What is still familiar - `ko.observable`, `ko.observableArray`, and `ko.computed` From 3b3eb793cd4d701bcaa69920e12a97feb184c43c Mon Sep 17 00:00:00 2001 From: phillipc Date: Thu, 16 Jul 2026 19:33:48 +0200 Subject: [PATCH 2/2] Update tko.io/src/content/docs/3to4.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tko.io/src/content/docs/3to4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tko.io/src/content/docs/3to4.md b/tko.io/src/content/docs/3to4.md index 51e35fe98..6bd8752cc 100644 --- a/tko.io/src/content/docs/3to4.md +++ b/tko.io/src/content/docs/3to4.md @@ -58,7 +58,7 @@ TKO is designed for stricter Content Security Policies and avoids the older `eva In Knockout 3, `descendantsComplete` fired only after every descendant — including content rendered asynchronously by a nested `if`, `template`, `foreach`, or component — had finished binding. Knockout tracked this with an ancestor-linked async-completion context and, on control-flow bindings, a `completeOn: 'render'` option. -TKO drops that ancestor-tracking machinery in favor of a simpler per-node model. As a result, `descendantsComplete` is decided **once**, synchronously, when the node itself binds, and waits only for async bindings **on that same node** — not for subtrees that appear later. If a descendant `if`/`template` renders its content after the fact (for example when its condition flips to `true`), the `descendantsComplete` callback is not re-triggered and will not fire. +TKO drops that ancestor-tracking machinery in favor of a simpler per-node model. As a result, `descendantsComplete` is decided **once**, synchronously, when the node itself binds, and waits only for async bindings **on that same node** — not for subtrees that appear later. If a descendant `if`/`template` renders its content after the fact (for example when its condition flips to `true`), the `descendantsComplete` callback is not re-triggered. **Why:** the async-completion context was cross-cutting, hard to reason about, and a common source of disposal and ordering bugs. The per-node lifecycle is smaller and predictable, at the cost of this one edge case.