From 7eb670b04203005af8cfb05cefd7a64efe6eeb12 Mon Sep 17 00:00:00 2001 From: phillipc Date: Fri, 24 Apr 2026 22:01:01 +0200 Subject: [PATCH 1/3] docs: detail build.knockout vs build.reference deltas Document the concrete differences between the two builds in builds/: - 3to4.md: add Build deltas in detail subsection with provider, expression, and surface-API tables. Keep the existing Feature comparison table as an at-a-glance summary. - soul.md: expand the Stability and Migration section with the provider, equality, globals, function-rewrite, and surface-API deltas so agents do not need to read builds/{knockout,reference}/src/index.ts to answer this question. Adversarial review: verified each row against builds/knockout/src/index.ts and builds/reference/src/index.ts on this branch; no behavior change, docs only. --- tko.io/public/agents/soul.md | 21 ++++++++++++++ tko.io/src/content/docs/3to4.md | 50 +++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/tko.io/public/agents/soul.md b/tko.io/public/agents/soul.md index 4cc092b11..3f76fd2ce 100644 --- a/tko.io/public/agents/soul.md +++ b/tko.io/public/agents/soul.md @@ -83,6 +83,27 @@ script tag, verify your tests pass, ship. From there, adopting modern features (TSX, native providers, modular packages) is incremental and optional. +The two builds wire the same core differently. Concretely, comparing +`builds/knockout/src/index.ts` and `builds/reference/src/index.ts`: + +- **Providers.** Both ship `Component`, `DataBind`, `Virtual`, and + `Attribute`. `reference` additionally enables `Native`, + `AttributeMustache`, and `TextMustache`. `knockout` does not. +- **Equality.** `reference` sets `options.strictEquality = true`, so `==` + and `!=` in binding expressions evaluate as `===` / `!==`. `knockout` + leaves the legacy lax behavior on. +- **Globals in bindings.** `knockout` sets + `bindingGlobals: options.global`, exposing the global scope inside + binding expressions. `reference` does not. +- **Inline-function rewrite.** `knockout` registers + `functionRewrite` from `@tko/utils.functionrewrite` as a binding-string + preparser, so legacy inline `function (...) { ... }` expressions in + `data-bind` still work. `reference` does not. +- **Legacy surface.** `knockout` exposes a + `ko.expressionRewriting.preProcessBindings` shim for KO 3 plugins. + `reference` exposes `ko.jsx = { createElement, Fragment, render }` for + TSX. Neither has the other. + TKO aims for stability, not strict backwards compatibility at all costs. If a change improves correctness, security, or maintainability and the migration path is clear, it's worth making. But gratuitous breakage is diff --git a/tko.io/src/content/docs/3to4.md b/tko.io/src/content/docs/3to4.md index 024cd6c10..2f8b14870 100644 --- a/tko.io/src/content/docs/3to4.md +++ b/tko.io/src/content/docs/3to4.md @@ -12,9 +12,14 @@ TKO keeps the familiar Knockout programming model, but changes the packaging, bu ## Start with the right package - **`@tko/build.knockout`** - Recommended for most migrations. It is the compatibility-focused build and the closest match for a traditional Knockout application. + Recommended for most migrations. Closest to a traditional Knockout 3.x application: lax `==`/`!=` in expressions, globals visible inside bindings, the legacy `function (...) { ... }` rewrite for inline `data-bind` callbacks, and a `ko.expressionRewriting.preProcessBindings` shim for plugins that reach for it. - **`@tko/build.reference`** - Better for custom or modular setups where you want to compose providers and features more explicitly, and the build used by the TSX / `ko-*` docs path. + The modern, modular path used by the TSX / `ko-*` docs. Adds the native, text-mustache, and attribute-mustache providers; switches `==`/`!=` to strict equality (`options.strictEquality`); drops the legacy `functionRewrite` preparser and the `expressionRewriting` shim; and exposes `ko.jsx` for TSX rendering. + +If you have templates with literal `{{ ... }}` content you do **not** want +interpolated, or code that compares values across types with `==`, prefer +`@tko/build.knockout` until you can audit those call sites — `@tko/build.reference` +will interpolate the braces and tighten the comparisons. ## The biggest changes @@ -80,6 +85,47 @@ TKO is designed for stricter Content Security Policies and avoids the older `eva | CSP-friendly binding parser | No | Yes | Yes | | Legacy compatibility workarounds | More common | Reduced | Reduced | +### Build deltas in detail + +Both TKO builds share the same core (`@tko/builder`, observables, computeds, +the core/template/if/foreach/component bindings, and the punches filter +syntax). They differ in which providers are wired in, which options default +differently, and which surface APIs they expose. + +#### Providers (binding-resolution strategies) + +| Provider | What it recognizes | `@tko/build.knockout` | `@tko/build.reference` | +| --- | --- | --- | --- | +| `ComponentProvider` | `` components | Yes | Yes | +| `DataBindProvider` | `data-bind="..."` strings | Yes | Yes | +| `VirtualProvider` | `` comments | Yes | Yes | +| `AttributeProvider` | `ko-foo="expression"` string attributes | Yes | Yes | +| `NativeProvider` | `el.bindings = {...}` set in JS / TSX | No | Yes | +| `AttributeMustacheProvider` | `attr="prefix {{expr}} suffix"` interpolation | No | Yes | +| `TextMustacheProvider` | `{{ expr }}` in text nodes | No | Yes | + +#### Binding-expression behavior + +| Behavior | `@tko/build.knockout` | `@tko/build.reference` | +| --- | --- | --- | +| `==` / `!=` evaluated as `===` / `!==` (`options.strictEquality`) | Off | On | +| Bindings see globals (`options.bindingGlobals`) | On (`options.global`) | Off | +| Inline `function (...) { ... }` rewritten in `data-bind` (`functionRewrite` preparser) | Yes | No | +| `ko.expressionRewriting.preProcessBindings(s)` legacy stub | Yes | No | +| CSP-friendly parser (no `eval` / `new Function`) | Yes | Yes | + +#### Surface APIs + +| API | `@tko/build.knockout` | `@tko/build.reference` | +| --- | --- | --- | +| `ko.observable` / `ko.computed` / `ko.applyBindings` / components | Yes | Yes | +| `ko.expressionRewriting` (legacy KO 3 plugin shim) | Yes | No | +| `ko.jsx.createElement` / `Fragment` / `render(jsx)` | No | Yes | + +The reference build is what the TSX / `ko-*` examples in this docs set rely on, +because TSX needs `NativeProvider` (to read `el.bindings`) and the `ko.jsx` +render helper. + ## When not to migrate yet - Your templates rely heavily on legacy inline binding expressions you cannot easily change. From d42528a193a7e6a264a944a3c0a39b4cb4aeedd7 Mon Sep 17 00:00:00 2001 From: phillipc Date: Fri, 24 Apr 2026 22:05:13 +0200 Subject: [PATCH 2/3] docs: remove cautionary note about template interpolation and type comparisons --- tko.io/src/content/docs/3to4.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tko.io/src/content/docs/3to4.md b/tko.io/src/content/docs/3to4.md index 2f8b14870..f16fd36b5 100644 --- a/tko.io/src/content/docs/3to4.md +++ b/tko.io/src/content/docs/3to4.md @@ -16,11 +16,6 @@ TKO keeps the familiar Knockout programming model, but changes the packaging, bu - **`@tko/build.reference`** The modern, modular path used by the TSX / `ko-*` docs. Adds the native, text-mustache, and attribute-mustache providers; switches `==`/`!=` to strict equality (`options.strictEquality`); drops the legacy `functionRewrite` preparser and the `expressionRewriting` shim; and exposes `ko.jsx` for TSX rendering. -If you have templates with literal `{{ ... }}` content you do **not** want -interpolated, or code that compares values across types with `==`, prefer -`@tko/build.knockout` until you can audit those call sites — `@tko/build.reference` -will interpolate the braces and tighten the comparisons. - ## The biggest changes ### Packaging changed From 36b7b6f3bb1f05563658f57201aa5bfd6634315c Mon Sep 17 00:00:00 2001 From: phillipc Date: Tue, 5 May 2026 20:49:57 +0200 Subject: [PATCH 3/3] docs: capture foreach and provider-order build deltas Adversarial pass: self-review. Result: clean. --- tko.io/public/agents/soul.md | 13 +++++++++++-- tko.io/src/content/docs/3to4.md | 22 ++++++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tko.io/public/agents/soul.md b/tko.io/public/agents/soul.md index 3f76fd2ce..268444c59 100644 --- a/tko.io/public/agents/soul.md +++ b/tko.io/public/agents/soul.md @@ -88,7 +88,11 @@ The two builds wire the same core differently. Concretely, comparing - **Providers.** Both ship `Component`, `DataBind`, `Virtual`, and `Attribute`. `reference` additionally enables `Native`, - `AttributeMustache`, and `TextMustache`. `knockout` does not. + `AttributeMustache`, and `TextMustache`. `knockout` does not. Provider + order is resolution precedence and differs too: + - `knockout`: `Component` -> `DataBind` -> `Virtual` -> `Attribute` + - `reference`: `Component` -> `Native` -> `AttributeMustache` -> + `TextMustache` -> `DataBind` -> `Virtual` -> `Attribute` - **Equality.** `reference` sets `options.strictEquality = true`, so `==` and `!=` in binding expressions evaluate as `===` / `!==`. `knockout` leaves the legacy lax behavior on. @@ -98,7 +102,12 @@ The two builds wire the same core differently. Concretely, comparing - **Inline-function rewrite.** `knockout` registers `functionRewrite` from `@tko/utils.functionrewrite` as a binding-string preparser, so legacy inline `function (...) { ... }` expressions in - `data-bind` still work. `reference` does not. + `data-bind` still work. `reference` has no `functionRewrite` preparser. +- **`foreach` binding target.** `knockout` resolves + `data-bind="foreach: ..."` to `TemplateForEachBindingHandler` (legacy + template-engine path), while `reference` resolves it to modern + `ForEachBinding` from `@tko/binding.foreach`. Both resolve + `data-bind="each: ..."` to `ForEachBinding`. - **Legacy surface.** `knockout` exposes a `ko.expressionRewriting.preProcessBindings` shim for KO 3 plugins. `reference` exposes `ko.jsx = { createElement, Fragment, render }` for diff --git a/tko.io/src/content/docs/3to4.md b/tko.io/src/content/docs/3to4.md index f16fd36b5..d698f4161 100644 --- a/tko.io/src/content/docs/3to4.md +++ b/tko.io/src/content/docs/3to4.md @@ -14,7 +14,7 @@ TKO keeps the familiar Knockout programming model, but changes the packaging, bu - **`@tko/build.knockout`** Recommended for most migrations. Closest to a traditional Knockout 3.x application: lax `==`/`!=` in expressions, globals visible inside bindings, the legacy `function (...) { ... }` rewrite for inline `data-bind` callbacks, and a `ko.expressionRewriting.preProcessBindings` shim for plugins that reach for it. - **`@tko/build.reference`** - The modern, modular path used by the TSX / `ko-*` docs. Adds the native, text-mustache, and attribute-mustache providers; switches `==`/`!=` to strict equality (`options.strictEquality`); drops the legacy `functionRewrite` preparser and the `expressionRewriting` shim; and exposes `ko.jsx` for TSX rendering. + The modern, modular path used by the TSX / `ko-*` docs. Adds the native, text-mustache, and attribute-mustache providers; switches `==`/`!=` to strict equality (`options.strictEquality`); has no legacy `functionRewrite` preparser and no `expressionRewriting` shim; and exposes `ko.jsx` for TSX rendering. ## The biggest changes @@ -82,10 +82,10 @@ TKO is designed for stricter Content Security Policies and avoids the older `eva ### Build deltas in detail -Both TKO builds share the same core (`@tko/builder`, observables, computeds, -the core/template/if/foreach/component bindings, and the punches filter -syntax). They differ in which providers are wired in, which options default -differently, and which surface APIs they expose. +Both TKO builds share the same foundational packages (`@tko/builder`, +observables, computeds, core/template/if/component bindings, and the punches +filter syntax). They differ in provider order, which options default +differently, which `foreach` handler wins, and which surface APIs they expose. #### Providers (binding-resolution strategies) @@ -99,15 +99,21 @@ differently, and which surface APIs they expose. | `AttributeMustacheProvider` | `attr="prefix {{expr}} suffix"` interpolation | No | Yes | | `TextMustacheProvider` | `{{ expr }}` in text nodes | No | Yes | +Provider order is resolution precedence, and it differs: + +- `@tko/build.knockout`: `ComponentProvider` -> `DataBindProvider` -> `VirtualProvider` -> `AttributeProvider` +- `@tko/build.reference`: `ComponentProvider` -> `NativeProvider` -> `AttributeMustacheProvider` -> `TextMustacheProvider` -> `DataBindProvider` -> `VirtualProvider` -> `AttributeProvider` + #### Binding-expression behavior | Behavior | `@tko/build.knockout` | `@tko/build.reference` | | --- | --- | --- | -| `==` / `!=` evaluated as `===` / `!==` (`options.strictEquality`) | Off | On | -| Bindings see globals (`options.bindingGlobals`) | On (`options.global`) | Off | +| `==` / `!=` evaluated as `===` / `!==` (`options.strictEquality`) | No | Yes | +| Bindings see globals (`options.bindingGlobals`) | Yes (`options.global`) | No | | Inline `function (...) { ... }` rewritten in `data-bind` (`functionRewrite` preparser) | Yes | No | | `ko.expressionRewriting.preProcessBindings(s)` legacy stub | Yes | No | -| CSP-friendly parser (no `eval` / `new Function`) | Yes | Yes | +| `data-bind="foreach: ..."` resolves to | `TemplateForEachBindingHandler` (legacy template-engine path) | `ForEachBinding` (modern, from `@tko/binding.foreach`) | +| `data-bind="each: ..."` resolves to | `ForEachBinding` | `ForEachBinding` | #### Surface APIs