Files
ujsx/docs/architecture
glm-5.1 da82b52b27 add reconciler architecture docs and update existing docs with cross-references
Phase 2: transitioning reconciler research into architecture documents.

New docs:
- reconciler.md: fiber tree, reconciliation algorithm (signal-driven
  props + key-based children), update scheduling, commit order,
  TypeBox optimization layer, file structure, consumer impact
- lifecycle.md: mount/update/dispose phases, fiber tree disposal,
  partial tree removal, ReactiveRoot.dispose(), finalizeInstance,
  idempotent disposal, computed vs effect cleanup
- ADR-004: key as first-class field on UElement (not a prop)
- ADR-005: signal-driven updates for props, reconciliation for
  structure (hybrid approach, not full tree diffing)

Updated docs:
- README.md: add reconciler.md, lifecycle.md, ADRs 004/005 to
  index; update reconciler roadmap with architecture doc links
- schema.md: add key?: string to UElement type with TODO comment;
  update known gaps to reference ADR-004 and reconciler.md;
  rephrase key constraint as temporary
- element-factory.md: update key extraction gap to reference
  ADR-004 and reconciler.md
- host-config.md: reference reconciler.md and lifecycle.md
  for the reconciler bridge and disposal gaps
- reactive-layer.md: reference reconciler.md and lifecycle.md
  for the signal-host bridge and disposal gaps
- events.md: reference lifecycle.md for unmount/dispose gap
2026-05-18 15:15:13 +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 factoryh(), createRoot(), createComponent(), Fragment, JSX runtime
  • Reactive layerReactiveRoot, reactiveComponent, reactiveElement, signal/computed/effect/batch
  • HostConfig — generic host interface with createRoot().render() for mount-only rendering
  • TransformsTransformRegistry for bi-directional transforms
  • EventsPubSubLike and EventEnvelope for decoupled event emission
  • PointersValuePointer, 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.

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/