From ac180405ba86be5dec9a8891015f716e741b59fb Mon Sep 17 00:00:00 2001 From: jnbdz <237008+jnbdz@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:43:09 -0400 Subject: [PATCH 1/2] Add the v0.1 design doc for the HTMT reference implementation. --- .../specs/2026-07-19-js-htf-design.md | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-19-js-htf-design.md diff --git a/docs/superpowers/specs/2026-07-19-js-htf-design.md b/docs/superpowers/specs/2026-07-19-js-htf-design.md new file mode 100644 index 0000000..86d79ce --- /dev/null +++ b/docs/superpowers/specs/2026-07-19-js-htf-design.md @@ -0,0 +1,115 @@ +# js-HTF v0.1 Design + +js-HTF is the JavaScript reference implementation of HTMT (HyperText Markup Templating): a templating engine that binds JSON data into HTML using `ht-*` attributes with JSONPath expressions, as documented in the [HTMT repository](https://github.com/ViewDescriptorProtocol/HTMT). + +## Goals + +- Implement the full documented HTMT attribute set: `ht-bind`, `ht-loop`, `ht-template`, `ht-attr-[name]`, `ht-show`, `ht-hide`, `ht-switch`, `ht-case`, `ht-class-[name]`, and every `ht-root-*` variant with the documented precedence rule (the local attribute wins over its `ht-root-*` twin). +- Browser-first: render live DOM in the browser; tests run in Node against happy-dom. The core never touches browser-only APIs beyond the DOM itself, so server-side rendering can be added later without redesign. +- Serve as the spec's conformance suite: every example in the HTMT docs becomes a test fixture. + +## Non-goals (v0.1) + +- No reactivity, state management, or event handling. Rendering is one-shot: `render(element, data)` produces the final DOM; re-rendering re-runs from a pristine copy. +- No arbitrary JavaScript in attribute expressions — only JSONPath plus the small comparison grammar below. +- No auto-initialization or DOM watching; that is htmx-HTF's role. +- No server-side (string) rendering target yet. + +## Relationship to HTMX and Alpine.js + +**HTMX** is complementary, not overlapping. HTMX transports HTML fragments and swaps them into the DOM; it has no built-in answer for JSON responses and delegates that to client-side templating. HTMT is that missing piece: HTMX moves the data, HTMT binds it. The htmx-HTF repository will provide the integration. + +**Alpine.js** overlaps on rendering directives (`x-text`/`x-for`/`x-show` map closely onto `ht-bind`/`ht-loop`/`ht-show`) but differs in intent: + +1. Alpine attributes contain arbitrary JavaScript evaluated at runtime; HTMT expressions are JSONPath plus a tiny comparison grammar — declarative, CSP-safe, and implementable in any language. +2. Alpine is a reactive mini-framework with component state and events; HTMT is a one-shot binding of server-provided JSON into markup. +3. HTMT is a language-neutral spec with js-HTF as its reference implementation — the same templates and data could be rendered by a JVM or Android implementation, which is the VDP cross-platform premise. + +Guarding this boundary is a design constraint: features that push js-HTF toward Alpine's territory (state, events, reactivity) are out of scope for the spec, not just deferred. + +## Technology choices + +- **Plain ESM JavaScript with JSDoc types.** No build step; the source is the artifact. Types are checked with `tsc --checkJs` (no emit). +- **json-p3** is the sole runtime dependency: an RFC 9535-compliant JSONPath engine, CSP-safe (no `eval`/`new Function`), usable in browser and Node. +- **Vitest + happy-dom** for tests; **typescript** as a dev-only type checker. + +## Public API + +```js +import { render } from 'js-htf'; + +render(element, data, options?); +``` + +- `element`: the DOM element whose subtree contains `ht-*` attributes. +- `data`: the JSON data object; it is the root context (`$`) at the top of the walk and the permanent target of `ht-root-*` expressions. +- `options.templates`: where `ht-template` definitions are collected from (default: the element's owner document). +- `options.debug`: when true, warnings (e.g. unmatched paths) are logged with the element and expression concerned. + +On first render, a pristine clone of the element's subtree is stored in a `WeakMap` keyed by the element. Subsequent `render` calls restore from the clone before rendering, making re-rendering with new data idempotent. + +`render` never half-commits: the walk runs on the pristine clone and the result is swapped in only if the whole walk succeeds. A thrown error leaves the previously rendered DOM intact. + +## Module layout + +``` +src/ + index.js public API (render), pristine-clone bookkeeping + walker.js depth-first DOM walk, runs the handler pipeline per element + context.js context stack: { root, current } — what "$" resolves against + path.js JSONPath resolution via json-p3 (single-value and array modes) + expr.js expression layer: , == , != + handlers/ + template.js ht-template registration;