diff --git a/.gitignore b/.gitignore
index aa849760..048fd577 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,6 @@ builds/**/meta
.vscode/settings.json
.playwright-mcp
.playwright-cli
+test-results/
+.claude/worktrees/
__screenshots__
diff --git a/tko.io/astro.config.mjs b/tko.io/astro.config.mjs
index 4d1b8d30..26d303f6 100644
--- a/tko.io/astro.config.mjs
+++ b/tko.io/astro.config.mjs
@@ -29,6 +29,7 @@ export default defineConfig({
sidebar: [
{ label: 'Introduction', slug: 'index' },
{ label: 'Knockout 3 to 4 Guide', slug: '3to4' },
+ { label: 'Examples', slug: 'examples' },
{ label: 'Bindings', autogenerate: { directory: 'bindings' } },
{ label: 'Observables', autogenerate: { directory: 'observables' } },
{ label: 'Computed', autogenerate: { directory: 'computed' } },
diff --git a/tko.io/public/agent-guide.md b/tko.io/public/agent-guide.md
index 3eee6cf9..9a512036 100644
--- a/tko.io/public/agent-guide.md
+++ b/tko.io/public/agent-guide.md
@@ -1,5 +1,7 @@
# TKO Agent Guide
+For preferred state/binding/DOM architecture in examples and prototypes, read `/agents/contract.md`.
+
## Setup
```html
@@ -31,6 +33,8 @@ ko.when(() => viewModel.isReady() && viewModel.hasData()).then(() => console.log
Activate with `ko.applyBindings(viewModel, element)`.
+Looking up that mount element with `document.getElementById(...)`, `querySelector(...)`, or another host reference is normal. The important rule is that once bindings are active, observable state should remain the source of truth instead of ad-hoc DOM mutation.
+
Syntax: `data-bind="bindingName: expression"` on HTML elements.
text, html, visible, hidden, css:{class:bool}, style:{prop:val}, attr:{name:val},
diff --git a/tko.io/public/agents/contract.md b/tko.io/public/agents/contract.md
new file mode 100644
index 00000000..9e88e8a9
--- /dev/null
+++ b/tko.io/public/agents/contract.md
@@ -0,0 +1,93 @@
+# TKO Agent Contract
+
+Use this file when deciding how state, bindings, and DOM work should be divided in TKO examples and prototypes.
+
+## Core Rule
+
+- Keep app state in observables, observableArrays, and computeds.
+- Let standard bindings and custom bindings carry that state to the DOM.
+- Use DOM lookup only to find the mount root for `ko.applyBindings(viewModel, element)`.
+
+## Replace X With Y
+
+- This section is about replacing ad-hoc DOM/event/state handling with bindings, not about binding-syntax style.
+- If you are about to do `element.textContent = value`, use the `text` binding.
+- If you are about to do `element.innerHTML = markup`, first ask whether the content should be plain text instead; prefer the `text` binding by default. Use `html` only when rendering trusted HTML is truly the point.
+- If you are about to manually create, replace, or reconcile a repeated set of child nodes, use `foreach`.
+- If you are about to toggle classes with `classList`, use `css`.
+- If you are about to set attributes manually, use `attr`.
+- If you are about to set inline styles from state, use `style`.
+- If you are about to wire ordinary UI events with imperative listeners, use `click`, `event:{...}`, `value`, `textInput`, `checked`, or related built-in bindings.
+- If you are about to call `focus()` or manage focus from state, use `hasFocus` when it fits.
+- If you are about to mirror user input into plain mutable objects, store that input in observables instead.
+- If you are about to keep counters, highlights, or explanatory UI state outside observables, move them into observables/computeds so the example demonstrates TKO rather than bypassing it.
+
+## When Custom Bindings Are The Right Tool
+
+Use a custom `bindingHandler` when the work is inherently DOM-specific and does not belong in the state layer.
+
+Typical good fits:
+- canvas drawing
+- WebGL rendering
+- SVG-specific effects
+- animation
+- resize / measurement
+- focus orchestration when `hasFocus` is not enough
+- third-party widget integration
+
+In those cases:
+- let observables remain the source of truth
+- let the custom binding read observables and update the DOM
+- avoid making the custom binding the authoritative owner of app state
+
+## Security Preference
+
+- Prefer `text` over `html`.
+- Treat `html` as an exception for trusted markup, not the default way to render content.
+- If the content originates from users, external services, or mixed trust levels, do not pass it through `html` unless it has been explicitly sanitized for that purpose.
+
+## Mounting Is Allowed
+
+These are normal:
+
+```js
+const root = document.getElementById('app')
+ko.applyBindings(viewModel, root)
+```
+
+```js
+const root = container.querySelector('[data-app-root]')
+ko.applyBindings(viewModel, root)
+```
+
+The contract is not “never touch `document`”.
+The contract is “do not let ad-hoc DOM mutation become your reactive state system”.
+
+## Binding Syntax Preference
+
+- `ko-*` and `data-bind` are both valid binding surfaces.
+- The choice between them is primarily stylistic / authoring-oriented unless you specifically need classic provider-driven strings or are teaching the classic syntax directly.
+
+## Render Loops
+
+Render loops are acceptable when they belong to rendering:
+- `requestAnimationFrame`
+- canvas redraws
+- WebGL frame submission
+- resize observers
+
+Prefer this split:
+- state object: observables + domain actions
+- renderer / custom binding: DOM, canvas, WebGL, RAF
+
+## Tests
+
+Direct DOM reads are fine in tests and verification code.
+Example:
+
+```js
+ko.applyBindings(vm, document.getElementById('app'))
+console.assert(document.querySelector('#app span').textContent === 'Hello')
+```
+
+That is verification code, not the app’s state/update architecture.
diff --git a/tko.io/public/agents/guide.md b/tko.io/public/agents/guide.md
index 693f5b05..d5f363d9 100644
--- a/tko.io/public/agents/guide.md
+++ b/tko.io/public/agents/guide.md
@@ -2,11 +2,13 @@
Test-backed behavior summaries live under `/agents/verified-behaviors/`. Treat those files as the contract layer when prose docs and implementation need reconciliation.
+For preferred state/binding/DOM architecture in examples and prototypes, read `/agents/contract.md`.
+
## Setup
```html
-
+
```
## Observables
@@ -49,6 +51,18 @@ Binding notes:
```
+## Example Discipline
+
+When the goal is to demonstrate TKO itself, keep the state flow inside observables, computeds, and bindings.
+
+- If you want a replacement-oriented checklist for DOM/state decisions, use `/agents/contract.md`.
+- It is normal to look up a mount element with `document.getElementById(...)`, `querySelector(...)`, or another host-framework reference so you can call `ko.applyBindings(viewModel, element)`.
+- Prefer `text`, `css`, `attr`, `event`, `foreach`, and `pureComputed` over manual DOM writes.
+- Avoid driving visible state with `textContent`, `innerHTML`, `classList`, or ad-hoc `addEventListener` when bindings can express the same behavior.
+- Use custom `bindingHandlers` only for DOM-specific effects that do not belong in the state layer, such as animation, focus, canvas, SVG, or third-party widget integration.
+- If an example contrasts reactive models, the counters and highlighted state should also be observable-driven so the example demonstrates the pattern instead of bypassing it.
+- The line to avoid is using the DOM itself as the mutable source of truth after bindings are active.
+
## Classic data-bind parsing and CSP
Classic `data-bind` parsing is provider-driven. Use `DataBindProvider` when you need binding strings, and combine it with other providers through `MultiProvider` as needed.
@@ -211,6 +225,8 @@ tko.applyBindings({ removeTodo: t => todos.remove(t) }, root)
Observable writes update DOM synchronously — assert immediately after setting:
+Direct DOM reads are appropriate here because this is verification code, not the UI update path itself.
+
```js
const vm = { msg: ko.observable('Hello') }
ko.applyBindings(vm, document.getElementById('app'))
diff --git a/tko.io/public/examples/form-engine.html b/tko.io/public/examples/form-engine.html
new file mode 100644
index 00000000..72db5264
--- /dev/null
+++ b/tko.io/public/examples/form-engine.html
@@ -0,0 +1,1195 @@
+
+
+
+ A product-style form that keeps validation, dirty state, derived summaries, and async save flow in observables.
+ The visible UI is reactive, but the state remains simple.
+
+
+
+
+
+
Dirty fields
+
+
changes since the last save
+
+
+
Validation errors
+
+
blocking save right now
+
+
+
Estimated monthly
+
+
derived from plan, seats, and add-ons
+
+
+
Completion
+
+
required fields in good shape
+
+
+
Save state
+
+
+
+
+
+
+
+
+
+
+
Account
+
Primary identity and contact details.
+
+
+
+
Organization
+
+
+
+
+
Shown on invoices and the internal summary cards.
+
+
+
+
+
+
The person responsible for the draft.
+
+
+
+
+
+
+
Contact
+
+
+
+
+
Used for save confirmations and review notes.
+
+
+
+
+
+
Separate from the primary contact if needed.
+
+
+
+
+
+
+
+
+
+
Project
+
Timeline, scope, and delivery pressure.
+
+
+
+
Timeline
+
+
+
+
+
Used as the main label in the summary.
+
+
+
+
+
+
+
+
+
+
+
+
Raises the derived monthly estimate when urgent.
+
+
+
+
+
+
+
+
Scope
+
+
+
+
+
Feeds the derived pricing summary.
+
+
+
+
+
+
+
Affects the per-seat estimate.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Notes
+
Freeform context for reviewers and implementers.
+
+
+
+
+
+
Optional, but useful when the form becomes a real intake flow.
+ A computed dependency map: mutate one input and only the downstream branch lights up.
+ The graph shows actual observable changes and computed recomputes, not generic UI noise.
+
+ One-shot by AI: cells are observables, formula cells are computeds, and one edit ripples through
+ the sheet.
+
+
+
TKO dependency engine
+
+
+
+
+
Selected
+
+
+
+
Type
+
+
+
+
Value
+
+
+
+
Direct inputs
+
+
+
+
+
+
+
+
Formula Bar
+
+
+
+
+
+
+
Literal cells are observables. Formula cells are computeds that read other cells through the same reactive graph.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tko.io/public/llms.txt b/tko.io/public/llms.txt
index 596f51af..389c9e7e 100644
--- a/tko.io/public/llms.txt
+++ b/tko.io/public/llms.txt
@@ -16,6 +16,8 @@
- Verified Behaviors Index: /agents/verified-behaviors/index.md
Unit-test-backed behavior contract. Prefer this when behavior questions matter.
+- Agent Contract: /agents/contract.md
+ Preferred state/binding/DOM split for TKO examples and prototypes.
- Agent Guide: /agents/guide.md
API reference, gotchas, and examples.
- Agent Testing: /agents/testing.md
@@ -24,13 +26,18 @@
Minimal in-browser TSX + esbuild scaffold for rapid prototype work.
- Glossary: /agents/glossary.md
Domain-specific terms, concepts, and package reference.
+- Examples: /examples/
+ Interactive self-contained HTML examples that show update locality and TKO's observable model.
## Use This First
- Behavior question or edge case: /agents/verified-behaviors/index.md
+- State vs DOM architecture choice: /agents/contract.md
- API usage or authoring pattern: /agents/guide.md
- Verification or test flow: /agents/testing.md
- Rapid prototype in-browser: /agents/sample-tsx.html
+- Conceptual interactive examples: /examples/
+ The examples are self-contained HTML files.
## Build Choice
@@ -42,6 +49,7 @@
- Derived `ko-*` values must stay observable or computed. `ko-text={price() > 50 ? 'expensive' : 'cheap'}` freezes; use a computed.
- `ko.applyBindings(...)` returns a `Promise`.
- TKO connects observable state to the DOM; `bindingHandlers` are the DOM/state bridge.
+- Looking up a mount element with `document.getElementById(...)` (or similar) in order to call `ko.applyBindings(viewModel, element)` is normal and expected. What to avoid is driving reactive UI state through ad-hoc DOM mutation once bindings are active.
## Package Routing
@@ -54,8 +62,10 @@
- Verified Behaviors: /agents/verified-behaviors/index.md (package-scoped index)
- Agent Guide: /agents/guide.md (API reference, gotchas, examples)
+- Agent Contract: /agents/contract.md (preferred state/binding/DOM split for examples and prototypes)
- Agent Testing: /agents/testing.md (how to run and verify TKO code)
- Agent TSX Scaffold: /agents/sample-tsx.html (minimal browser TSX + esbuild scaffold for rapid prototype work)
+- Examples: /examples/ (interactive self-contained HTML examples showing update locality and reactive behavior)
- Playground: /playground
- GitHub: https://github.com/knockout/tko
@@ -66,6 +76,7 @@
- Bindings are the DOM integration layer: they read state, update DOM, and write user-driven changes back to state.
- `bindingHandlers` are the bridge between the DOM and the observable state layer.
- `ko.applyBindings(viewModel, element)` activates that bridge on an existing DOM subtree.
+- It is acceptable to locate that mount subtree with `document.getElementById(...)`, `querySelector(...)`, or a framework-provided element reference before calling `ko.applyBindings(...)`.
- In TSX, `tko.jsx.render()` creates DOM nodes and `ko.applyBindings({}, root)` then activates the `ko-*` bindings on that rendered DOM.
## Two Binding Syntaxes
@@ -76,6 +87,7 @@ TSX: `ko-text={msg}` — compile-time JSX expressions, needs esbuild + `tko.jsx.
Inside `ko-foreach` children, binding-context vars use strings: `ko-text="$data"` (not `{$data}`)
See /agents/guide.md for usage patterns.
+See /agents/contract.md for preferred state vs DOM architecture.
Use /agents/verified-behaviors/index.md for test-backed behavior contracts.
## Docs
diff --git a/tko.io/src/content/docs/examples/index.md b/tko.io/src/content/docs/examples/index.md
new file mode 100644
index 00000000..09d982a0
--- /dev/null
+++ b/tko.io/src/content/docs/examples/index.md
@@ -0,0 +1,28 @@
+---
+title: Examples
+description: Interactive examples that show where TKO differs from virtual DOM frameworks.
+sidebar:
+ label: Overview
+ order: 0
+---
+
+These examples are meant to make TKO's model visible.
+
+- They focus on observables, bindings, and update locality.
+- They are intentionally interactive.
+- They are self-contained HTML files you can inspect, copy, or adapt directly.
+- They are designed to show what work is happening, not just what UI appears.
+
+## Available examples
+
+- [Honeycomb](/examples/honeycomb.html?view=example): Count naive virtual DOM comparisons against observable updates in a dense interactive field.
+- [odoom](/examples/odoom.html?view=example): A tiny WebGL corridor crawler where the HUD, controls, and simulation state stay observable-driven.
+- [Spreadsheet](/examples/spreadsheet.html?view=example): Cells as observables, formulas as computeds, and dependency propagation you can edit live.
+- [Signal Graph](/examples/signal-graph.html?view=example): Mutate one input and watch only the downstream computed branch recompute.
+- [Form Engine](/examples/form-engine.html?view=example): A realistic product form showing dirty state, validation, summaries, and save flow as reactive state.
+
+## Why this section exists
+
+TKO often feels different from virtual DOM frameworks because it connects observable state directly to DOM updates.
+
+That difference is easier to understand when you can interact with it and watch the cost accumulate.
diff --git a/tko.io/src/styles/tko.css b/tko.io/src/styles/tko.css
index 0973624c..10faa4c0 100644
--- a/tko.io/src/styles/tko.css
+++ b/tko.io/src/styles/tko.css
@@ -106,6 +106,8 @@ h1,
.content-panel h1 {
font-size: clamp(1.85rem, 4vw, 2.75rem);
font-weight: 700;
+ margin: 0;
+ line-height: 1;
}
.content-panel h2 {
@@ -190,6 +192,9 @@ header {
.content-panel .sl-markdown-content > .sl-heading-wrapper:first-child {
margin-top: 0;
+ display: flex;
+ align-items: center;
+ min-height: 100%;
}
.content-panel a {