Files
ujsx/docs/architecture/README.md
glm-5.1 09f32f0c64 add architecture docs synced to current source and sdd process
Phase 1 of SDD process: syncing docs/architecture/ to reflect the
existing source code. Eight component documents describe WHAT and WHY
(not HOW) for each module: schema, element factory, reactive layer,
host config, transforms, events, pointers, and build distribution.
Three ADRs capture key decisions (HTML-agnostic core, TypeBox Module
as type registry, Preact signals-core for reactivity). Each doc
documents known reconciler gaps and references the research in
docs/research/reconciler/.

Also adds docs/sdd_process.md (process reference shared across
alkdev projects) matching the taskgraph_ts pattern.
2026-05-18 15:00:33 +00:00

7.0 KiB

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 (to be addressed by the reconciler work documented in docs/research/reconciler/):

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

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
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

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 research in docs/research/reconciler/ documents a phased plan to close the current gaps:

Phase Description Status
0 key field on UElement Research complete
1 Reactive → Host bridge (fiber tree, signal-driven updates) Research complete
2 Key-based children reconciliation (LIS algorithm) Research complete
3 Unmount & dispose support Research complete
4 TypeBox value optimization layer Research complete
5 Flowgraph HostConfig implementations Research complete

Research docs are in docs/research/reconciler/. Architecture docs for the reconciler will be created during the architecture phase of the SDD process, informed by this research.

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/