Skip to content

v2.4.0: feat(intent-element): un-privatize + publish @hyperfixi/intent-element

Choose a tag to compare

@codetalcott codetalcott released this 21 May 00:32
· 1053 commits to main since this release

[2.4.0] - 2026-05-20

Two parallel arcs landed since v2.3.1: upstream _hyperscript 0.9.90 parity (9 phases — commands, comparators, collection ops, event modifiers, plugin system, 3 new public plugin packages, i18n) and htmx v4 compatibility (reactive hx-live, SSE/WebSocket streaming, per-element localized attribute names, the size-busting hyperfixi-hx-v4.js bundle).

Added

Core language (upstream _hyperscript 0.9.90 compat)

  • 9 new commands: focus, blur, empty, open, close, select, clear, reset, breakpoint — covers DOM focus management, form state, window control, and debugger integration.
  • 4 comparator expressions: X starts with Y, X ends with Y, X is between A and B, and the postfix ... ignoring case modifier for case-insensitive string comparison. between is inclusive and auto-orders bounds.
  • 5 collection infix operators: collection where <pred>, collection sorted by <key>, collection mapped to <expr>, string split by <sep>, array joined by <sep>. where/sorted by/mapped to bind it per element for inline predicates/keys.
  • Event modifiers: on first <event> alias for .once, plus synthetic on resize for HTMLElements backed by ResizeObserver (browser standard resize is window-only).

Plugin system

  • HyperfixiPlugin public API: installPlugin(plugin) on Runtime, ParserExtensionRegistry singleton with snapshot/restore for test isolation. Five plugin seams: registerFeature, registerNodeEvaluator, registerGlobalWriteHook, registerGlobalReadHook, and HyperfixiPluginContext.runtime. $name globals canonicalized to bare-key storage across setVariableValue and both identifier evaluators.

New plugin packages

  • @hyperfixi/speech: Voice I/O via Web Speech API — speak "text" (with optional rate/pitch/voice/volume), ask "question", answer with "value". Idempotent installation; no-ops cleanly when the Web Speech API or window.prompt() is unavailable.
  • @hyperfixi/reactivity: Reactive signals — live { ... } blocks, bind expressions (now also accepts explicit property via 's or . syntax), when X changes, ^name reactive reads. Microtask-flushed scheduler with cycle guard and auto-stop on element disconnect.
  • @hyperfixi/components: Custom Element registration from <template component="tag"> / <script type="text/hyperscript-template" component="tag">. v2 shipped this release — reactive ^var rendering (via @hyperfixi/reactivity), #if / #for directives, slot substitution, ${expr} interpolation. Init scripts (_= on the <template>) run on each stamp.

Internationalization (i18n)

  • Phase 1 commands translated across all 23 COMPLETE_LANGUAGES (7 new schemas in @lokascript/semantic: empty, open, close, select, clear, reset, breakpoint).
  • Phase 2 comparators (starts with, ends with, between, ignoring case) translated across all 23 languages (with starts with/ends with/between backfills in bn, th, vi).
  • Phase 3 collection ops (sorted by, mapped to, split by, joined by) translated across all 23 languages (where predated this release).
  • New packages/i18n/src/schema-alignment.test.ts — guards drift: every CommandSchema must have an en.ts entry, Phase 1/2/3 operators must exist across all complete dictionaries.

htmx v4 integration

  • hx-live reactive expressions: When @hyperfixi/reactivity is installed, the htmx-compat layer recognizes htmx v4's hx-live attribute and translates it to a live ... end block. The expression body is hyperscript (not JavaScript like upstream htmx v4), so it gets fine-grained dependency tracking + inherits multilingual support. If reactivity isn't installed, the element is skipped with a clear console error.
  • sse-connect / sse-swap: Long-lived EventSource per element. Bounded exponential reconnect backoff (1s → 2s → 4s … capped at 30s, 5 retries). Auto-close on DOM removal via MutationObserver. Lifecycle events: htmx:sseOpen, htmx:sseMessage, htmx:sseError, htmx:sseClose. Multiple named events per connection. Swap targets resolve closest/find/next/previous.
  • ws-connect / ws-send: Bidirectional WebSocket per element. JSON envelope routing ({ target, swap?, data }) drives surgical updates through the existing hx-target/hx-swap machinery; raw messages dispatch as htmx:wsMessage. Outbound sends queue while connecting, flush on htmx:wsOpen. Same reconnect backoff as SSE.
  • Localized htmx attribute names (Phases 8–10): Per-element lang= resolution. Authors can write hx-obtener / hx-objetivo / sse-conectar (Spanish), hx-取得 / hx-ターゲット (Japanese), hx-احصل / hx-هدف (Arabic), etc. — the orchestrator translates them to canonical English before they hit the existing processor. 8 priority vocab modules ship with the bundle (en, es, fr, ja, zh, ar, ko, de). Missing-vocab languages log a one-time warning per language and fall back to English. eventNameOf / selectorFor / nameOf hooks are namespaced (hx, sse, ws) so hx-on:* localizes correctly.
  • htmx event lifecycle: New CustomEvents htmx:configuring (cancelable, mutate e.detail.config), htmx:beforeRequest (cancelable), htmx:afterSettle, htmx:error, plus the SSE/WS lifecycle events listed above.

Bundles & build tooling

  • hyperfixi-hx-v4.js (~257 KB gz): Full runtime + @hyperfixi/reactivity (auto-installed) + htmx-compat + SSE/WS support, all in a single script tag. The "batteries-included" choice for projects using htmx v4 reactive/streaming features. The slim hyperfixi-hx.js (~13 KB gz) does NOT ship reactivity/SSE/WS — choose hx-v4 when you reach for hx-live / sse-connect / ws-connect, hx otherwise.
  • Vite plugin: htmx v4 surface detection — scans HTML for hx-live / sse-* / ws-* attributes and auto-routes to hyperfixi-hx-v4.js; otherwise emits a minimal handcrafted bundle as before.
  • getDefaultRuntime() exposed for forcing lazy runtime construction (useful for bundles that defer wiring until first DOM event).

Fixed

  • Runtime: it/its/result identifier cache no longer returns stale values across loop iterations — required for Phase 3 collection operators that rebind it per element (16 ms TTL cache had masked all Phase 3 tests before fix).
  • Runtime: method-call this binding preserved on member callees — items mapped to it.toUpperCase() no longer throws "called on null or undefined". Uses .apply(thisArg, args) for member expressions.
  • Parser: is not binary operator now routes to notEquals.evaluate — pre-existing gap where tokens parsed but runtime dispatch threw Unknown binary operator: is not.
  • Runtime: execute() now mutates the caller's ExecutionContext in place when injecting the ExpressionRegistry, rather than shallow-cloning. Commands writing to context.result / context.it (e.g. the new answer command) now propagate those writes back to the caller — matching the long-standing context.locals.set(...) / context.globals.set(...) mutation pattern in the same function. ExecutionContext.registry is now mutable in the type signature (the runtime is the only legitimate writer; the readonly modifier was a defensive holdover from an earlier consolidation arc).
  • Runtime: or / and short-circuit now returns the operand value (truthy/falsy), not a coerced boolean — matches upstream _hyperscript semantics.
  • Runtime: put / set / toggle accept plain property names as write targets (not just the X.Y chains).
  • Runtime: evalHyperScript() defaults to shared globals when none provided.
  • Runtime: scoped notifyLocalRead() now fires when reading scoped locals (was missing — broke reactive bind to local vars).
  • Math: binary-arithmetic operands accept DOM elements (coerced through the consolidated "is this convertible" path).
  • Parser: set the X.Y to Z dotted property chains (e.g. set the event.detail.result to ...) now use the full expression parser rather than a fixed 2-token lookahead — handles arbitrary-depth chains and of expressions uniformly.
  • Parser: pick first 3 of arr and other keyword-led command syntaxes (commands whose argument grammar starts with a keyword) now route through dedicated parsers and preserve trailing args. Added to COMPOUND_COMMANDS with a dedicated parseXxxCommand per case.
  • Browser bundle: ExpressionRegistry threaded through the command-execution path (processCommandadapter.executeparseInput). Without this, commands like tell/send/toggle whose parseInput() evaluates AST nodes failed in the example gallery with "Expression X not in ExecutionContext.registry".
  • htmx-compat: hx-on:* registers real event listeners directly from the processor (the translator never sees them, so it can stay declarative-only). Refresh observer wired correctly; localized hx-on:* prefix resolved via the namespaced i18n hooks.
  • i18n: Reactive blocks (live, when X changes) route around parseStatement to fix when/unless + SOV-language live block parsing. Suppressed spurious "then" injection inside live blocks.
  • patterns-reference: HTML patterns marked non-translatable (they're DOM markup, not source code); cleaned orphan languages from the database seed.
  • Semantic: registry singleton shared across tsup entries (multi-entry split was forking the registry, breaking cross-entry pattern lookups).

Changed (internal)

  • Evaluator consolidation: Retired the BaseExpressionEvaluator class hierarchy and 4 helper modules (~2,700 LOC removed) in favor of the single canonical evaluator at parser/runtime.ts:evaluateAST with the ExpressionRegistry on ExecutionContext. Both consolidation arcs (α + β) closed.
  • Domain DSLs: Extracted a shared internal config package (workspace-only) consumed by all 8 domain DSLs.