Drafted by Claude (agent) on behalf of @brianmhunt.
Summary
Applying the foreach binding to a template built by tko.jsx.render(...) renders zero items and emits no warning. The same ko-foreach markup parsed from innerHTML renders correctly, and the reactive-children form ({ko.computed(() => arr().map(...))}) works. The silent 0-render is an easy multi-hour trap.
Environment
@tko/build.reference 4.1.0 (https://tko.io/lib/tko.js), browser, no build step (esbuild / Bun transpile with jsxFactory: 'tko.jsx.createElement', jsxFragment: 'tko.jsx.Fragment').
Repro
Paste-run on any page that loads tko.js:
const tko = globalThis.tko, ko = tko;
// A) foreach + JSX-built template → 0 li (unexpected, no warning)
const a = ko.observableArray(['x', 'y', 'z']);
const { node: nodeA } = tko.jsx.render(
tko.jsx.createElement('ul', { 'ko-foreach': 'a' },
tko.jsx.createElement('li', { 'ko-text': '$data' })));
document.body.appendChild(nodeA);
ko.applyBindings({ a }, nodeA);
console.log('A jsx foreach:', nodeA.querySelectorAll('li').length); // 0
// B) identical ko-foreach markup via innerHTML → 3 li (works)
const b = ko.observableArray(['x', 'y', 'z']);
const ul = document.createElement('ul');
ul.setAttribute('ko-foreach', 'b');
ul.innerHTML = '<li ko-text="$data"></li>';
document.body.appendChild(ul);
ko.applyBindings({ b }, ul);
console.log('B innerHTML foreach:', ul.querySelectorAll('li').length); // 3
// C) reactive children (JSX-native list pattern) → 3 li (works, live)
const c = ko.observableArray(['x', 'y', 'z']);
const { node: nodeC } = tko.jsx.render(
tko.jsx.createElement('ul', {}, ko.computed(() => c().map(v => tko.jsx.createElement('li', {}, v)))));
document.body.appendChild(nodeC);
console.log('C reactive children:', nodeC.querySelectorAll('li').length); // 3
Expected
Either (A) behaves like (B)/(C), or foreach warns that its template is a JSX-built node it can't clone — rather than silently rendering nothing.
Why it bites
agents/guide.md presents
<ul ko-foreach={items}><li ko-text="$data" /></ul>
as a JSX example, which implies (A) is supported. But the verified behaviors for @tko/utils.jsx describe reactive children / observable-array diffs as the JSX list path — not foreach + child template. So the guide example steers JSX users straight into the silent-failure path. (Cost me a long detour before reactive children resolved it cleanly.)
Suggested fixes (cheapest first)
- Docs — replace/relabel the
ko-foreach + template JSX example in agents/guide.md with the reactive-children form, or explicitly mark foreach + template as data-bind/HTML-only.
- Dev-mode warning — have
foreach (and template bindings) warn when handed a JSX-origin template it renders empty / can't clone. TKO already throws on double-binding non-JSX nodes (@tko/utils.jsx verified behaviors) — same diagnostic spirit.
- (Optional, larger) make
foreach clone JSX-built templates (carry the native-provider accessors into the clones) so the guide example works as written.
Drafted by Claude (agent) on behalf of @brianmhunt.
Summary
Applying the
foreachbinding to a template built bytko.jsx.render(...)renders zero items and emits no warning. The sameko-foreachmarkup parsed frominnerHTMLrenders correctly, and the reactive-children form ({ko.computed(() => arr().map(...))}) works. The silent 0-render is an easy multi-hour trap.Environment
@tko/build.reference4.1.0 (https://tko.io/lib/tko.js), browser, no build step (esbuild / Bun transpile withjsxFactory: 'tko.jsx.createElement',jsxFragment: 'tko.jsx.Fragment').Repro
Paste-run on any page that loads
tko.js:Expected
Either (A) behaves like (B)/(C), or
foreachwarns that its template is a JSX-built node it can't clone — rather than silently rendering nothing.Why it bites
agents/guide.mdpresentsas a JSX example, which implies (A) is supported. But the verified behaviors for
@tko/utils.jsxdescribe reactive children / observable-array diffs as the JSX list path — notforeach+ child template. So the guide example steers JSX users straight into the silent-failure path. (Cost me a long detour before reactive children resolved it cleanly.)Suggested fixes (cheapest first)
ko-foreach+ template JSX example inagents/guide.mdwith the reactive-children form, or explicitly markforeach+ template asdata-bind/HTML-only.foreach(and template bindings) warn when handed a JSX-origin template it renders empty / can't clone. TKO already throws on double-binding non-JSX nodes (@tko/utils.jsxverified behaviors) — same diagnostic spirit.foreachclone JSX-built templates (carry the native-provider accessors into the clones) so the guide example works as written.