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.
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) definingUNode,UElement,URoot,UPrimitive,PropValue,UniversalProps; TypeScript typesComponentFnandUComponent; type guardsisUElement,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 —
TransformRegistryfor bi-directional transforms - Events —
PubSubLikeandEventEnvelopefor decoupled event emission - Pointers —
ValuePointer,selectNode,setNodefor 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 disposalrender()is mount-only — no re-render, no diffing, noprepareUpdate/commitUpdatecallsdisposefunctions are no-ops — signal subscriptions leak- No
keyfield onUElement— 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/