Files
ujsx/docs/architecture
glm-5.1 23659233ca address architecture review findings and add review document
Fixes from architecture review (4 critical, 10 warnings):

Critical:
- Fix selectNode/setNode docs to accurately describe prop-key
  navigation behavior including array support and prop-key writes
- Document RenderContext/Density exported types in host-config
- Resolve ADR dual status ambiguity with clarifying note in README
  (frontmatter status = editorial, body Status = decision)
- Effect types already addressed in prior commit

Warnings addressed:
- Add Fragment re-export note to jsx-runtime section in
  build-distribution
- Document childCtx/transformCtx helper functions in transforms.md
- Document render() accepting non-root UNode in host-config
- Add Value.Hash re-entrancy constraint to reconciler.md
- Add true-passthrough constraint and h('root') special case
  to element-factory constraints
- Add _idCounter bundling caveat note

Review document added at docs/reviews/architecture-review-2026-05-18.md
with full findings, source verification table, and recommendations.
2026-05-18 15:36:38 +00:00
..

status, last_updated
status last_updated
draft 2026-05-18

@alkdev/ujsx Architecture

Universal JSX — runtime-agnostic reactive tree primitives with TypeBox schemas. UJSX treats JSX as an intermediate representation for multi-target rendering.

Why This Exists

UJSX fills a specific niche: generic tree construction and rendering where the same declarative template can target different hosts (markdown, graph structures, UI frameworks, workflow engines). It borrows the JSX mental model — nested elements with props and children — but strips away all platform-specific assumptions.

No onClick, no className, no style. The tree is a pure data structure (UNode) validated by TypeBox schemas, and the rendering contract is a HostConfig that decides what "create", "update", and "remove" mean for its target.

This makes UJSX useful for:

  • Workflow definitions — operations as elements, dependencies as parent-child structure, rendered to graphology DAGs or reactive execution engines
  • Structured document generation — markdown, HTML, or any text format via transform rules
  • Desktop UI — instanced glyphs, panels, layouts rendered to Three.js or similar (the spoke UI use case)

Core Principle

The tree is the truth. Hosts are interpreters. UJSX defines what a tree looks like (UNode, UElement, URoot), how it's constructed (h(), createComponent()), and how it can react to changes (signals). It does not dictate what the tree means — that's the host's job.

Current State

UJSX is functional but incomplete. The core primitives exist and are tested:

  • Schema — TypeBox Module (UJSX) defining UNode, UElement, URoot, UPrimitive, PropValue, UniversalProps; TypeScript types ComponentFn and UComponent; type guards isUElement, isURoot, isUPrimitive
  • Element factory — h(), createRoot(), createComponent(), Fragment, JSX runtime
  • Reactive layer — ReactiveRoot, reactiveComponent, reactiveElement, signal/computed/effect/batch
  • HostConfig — generic host interface with createRoot().render() for mount-only rendering
  • Transforms — TransformRegistry for bi-directional transforms
  • Events — PubSubLike and EventEnvelope for decoupled event emission
  • Pointers — ValuePointer, selectNode, setNode for tree navigation and targeted mutation

Known gaps (documented in architecture docs, planned for reconciler implementation):

  • unmount() is a stub — no fiber tree teardown, no instance removal, no signal disposal. See host-config.md and lifecycle.md.
  • render() is mount-only — no re-render, no diffing, no prepareUpdate/commitUpdate calls. See reconciler.md.
  • dispose functions are no-ops — signal subscriptions leak. See lifecycle.md.
  • No key field on UElement — positional matching only. See ADR-004.

Architecture Documents

Document Content
schema.md TypeBox Module, UNode/UElement/URoot/UPrimitive types, type guards
element-factory.md h(), createRoot(), createComponent(), Fragment, JSX runtime
reactive-layer.md ReactiveRoot, reactiveComponent, reactiveElement, signals, disposal gaps
host-config.md HostConfig interface, createRoot(), mount-only rendering, reconciler gap
reconciler.md Fiber tree, reconciliation algorithm, update scheduling, TypeBox optimizations
lifecycle.md Mount, update, unmount/dispose lifecycle, signal cleanup, partial tree teardown
transforms.md TransformRegistry, TransformRule, TransformContext, bi-directional transforms
events.md EventEnvelope, PubSubLike, UjsxEventMap
pointers.md ValuePointer, selectNode, setNode, tree navigation
build-distribution.md Package structure, exports map, dependencies, runtime targets

Design Decisions

ADR Decision
001 HTML-agnostic core — no DOM-specific props
002 TypeBox Module IS the type registry
003 Preact signals-core for reactivity
004 key as a first-class field on UElement — not a prop
005 Signal-driven updates for props, reconciliation for structure

Consumer Context

UJSX is designed as a library consumed by other projects, not an end-user application. Understanding these consumers shapes the API design:

Flowgraph (@alkdev/flowgraph)

Uses UJSX as a direct dependency. Workflow templates are UNode trees. Renders them to:

  • graphology DAG — structural analysis, cycle detection, topological sort via a HostConfig
  • Reactive execution engine — runtime workflow execution with signal-based status propagation

See docs/research/reconciler/05-flowgraph-host-configs.md for the planned integration.

Desktop UI (Spoke HUD)

Uses UJSX to define instanced glyph layouts, panels, and adaptive-density content. Renders via a Three.js HostConfig. Signal-driven property updates flow through prepareUpdate/commitUpdate to GPU buffers.

OpenCode Plugin (future)

An OpenCode plugin that provides UJSX-based template operations. Would use TransformRegistry for bi-directional markdown↔UJSX conversion.

Reconciler Roadmap

The reconciler bridges the reactive layer to the host layer, enabling signal-driven updates and key-based children reconciliation. Architecture docs define the WHAT and WHY; research docs contain the detailed implementation plans.

Phase Description Architecture Research
0 key field on UElement ADR-004, schema.md 00-KEY-FIELD-DESIGN.md
1 Reactive → Host bridge (fiber tree, signal-driven updates) reconciler.md, ADR-005 01-reactive-host-bridge.md
2 Key-based children reconciliation (LIS algorithm) reconciler.md 02-key-based-children-reconciliation.md
3 Unmount & dispose support lifecycle.md 03-unmount-dispose-support.md
4 TypeBox value optimization layer reconciler.md (TypeBox Optimization Layer section) 04-typebox-optimization-layer.md
5 Flowgraph HostConfig implementations Downstream consumer (@alkdev/flowgraph), not ujsx 05-flowgraph-host-configs.md

Document Lifecycle

Architecture documents use YAML frontmatter with status and last_updated fields:

---
status: draft | stable | deprecated
last_updated: YYYY-MM-DD
---
Status Meaning Transitions
draft Under active development. Content may change significantly. Implementation should not start until the document reaches stable. → stable when implementation is complete and API contract is verified by tests.
stable API contracts are locked. Changes require a review cycle and may warrant an ADR if they affect documented decisions. → deprecated when superseded. → draft if a fundamental redesign is needed (rare).
deprecated Superseded by another document. Kept for reference. Links should point to the replacement. Removed when no longer referenced.

ADR documents use a separate Status field in their body: Proposed, Accepted, Deprecated, or Superseded. ADRs never revert from Accepted. Note that ADR frontmatter (status: draft) refers to the document's editorial status (is the writing complete?), while the body Status refers to the decision's status (is this decision finalized?). A Proposed decision can have a draft document; an Accepted decision has a stable document.

References

  • UJSX research index: docs/research/README.md
  • Reconciler research: docs/research/reconciler/
  • SDD process: docs/sdd_process.md
  • Preact signals-core: @preact/signals-core
  • TypeBox: @alkdev/typebox
  • Taskgraph_ts architecture pattern: /workspace/@alkdev/taskgraph_ts/docs/architecture/