Template ↔ DOM interop

Vertex has one rendering system — Vertex.template (Mustache). There's no virtual DOM and no reconciler to reconcile against, so the only real interop question left is how a template's full re-render interacts with DOM you (or a third-party widget) manage by hand. Three patterns — which ones work and why.

Pattern 1 — Template as a leaf, driven only through .set()/.update() SAFE

Plain HTML controls sit outside the template's mount element. A V$(...).on(event, selector, fn) delegated handler reads the control and calls tmpl.set() or tmpl.update() — it never reaches into the template's own rendered markup. Because the controls' DOM and the template's mount element are disjoint, the template is free to blow away and rebuild its own innerHTML on every change without touching anything the surrounding page owns.

Data flows controls → template via .set()/.update(). Data flows template → controls via the template's on('change', …) event (fired whenever a data-bind input changes). Fully bidirectional, zero shared DOM.

Progress 0%
Active track
Pattern 2 — Side-by-side, shared JS state SAFE

The imperative controls and the template live in completely separate DOM regions. A plain JS object acts as the shared store. When either side mutates it, the other is notified via a tiny event emitter — no framework coupling at all. The controls call store.set(patch) directly via VQuery event handlers; the template's store.subscribe(...) callback calls tmpl.update(store).

Imperative controls (VQuery)
BPM 128
Key
Label
Template display (reads same store)
Pattern 3 — Foreign DOM (or a nested template) mounted inside another template's element UNSAFE — do not use

Template._render() always does this._el.innerHTML = html — a full replace, every .set()/.update() call. If a template renders a <div id="sub"> and something else — a hand-built widget, a third-party script, or even a second new Vertex.template({ el: '#sub', … }) — attaches DOM inside that div, the next re-render of the parent destroys it all. Any setInterval/addEventListener the child set up keeps running against a now-detached node — nothing ever tears it down. That's a silent leak, not just a visual glitch.

The template protects its own data-bind inputs from this — a focused text input keeps its live DOM node and cursor position across a re-render, and a range input skips re-rendering entirely while it's being dragged (see the source) — but that carve-out is scoped to its own bound inputs. It has no idea foreign DOM is sitting inside the element it's about to overwrite.

Foreign widgets mounted so far: 0