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.
This commit is contained in:
31
docs/architecture/decisions/001-html-agnostic-core.md
Normal file
31
docs/architecture/decisions/001-html-agnostic-core.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-18
|
||||
---
|
||||
|
||||
# ADR-001: HTML-agnostic core
|
||||
|
||||
**Status**: Proposed
|
||||
|
||||
## Context
|
||||
|
||||
UJSX is a universal JSX IR that treats JSX as an intermediate representation. The core types (UNode, UElement, UniversalProps) are deliberately free of any platform-specific concepts.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **HTML-compatible props (className, onClick, etc.)**: Include DOM/HTML-specific prop names in core types. Rejected because it biases toward DOM rendering and forces non-DOM hosts to filter/translate irrelevant props.
|
||||
- **Separate "web" prop types**: Create a parallel type hierarchy for web/DOM props alongside the universal core. Rejected because it creates two type hierarchies and increases complexity for hosts that don't need HTML.
|
||||
|
||||
## Decision
|
||||
|
||||
UJSX core does NOT include HTML/DOM-specific concepts. No `onClick`, no `className`, no `style`, no `aria-*`. UniversalProps accepts any key-value pairs because different hosts need different prop shapes. The host decides what props mean.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- Clean separation. Each host defines its own prop semantics. Flowgraph hosts use `{ name, type, url }` not `{ className, id }`.
|
||||
- TypeBox schemas for host-specific props can validate independently. No "stripping" step needed.
|
||||
|
||||
### Negative
|
||||
- Consumers targeting HTML must map their own props (e.g., `class` → `className`). This is the host's responsibility.
|
||||
- No built-in event handling system — events are host-specific. The event system (`PubSubLike`, `EventEnvelope`) is generic.
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-18
|
||||
---
|
||||
|
||||
# ADR-002: TypeBox Module as type registry
|
||||
|
||||
**Status**: Proposed
|
||||
|
||||
## Context
|
||||
|
||||
UJSX needs a type system that serves three purposes: (1) runtime validation of UNode trees, (2) JSON Schema export for documentation and tooling, (3) TypeScript type inference for developers.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Separate registry class with `register()` methods**: Build a class-based registry that maps type names to schemas. Rejected because it separates the runtime schema from the TypeScript types and adds unnecessary indirection.
|
||||
- **Zod schemas**: Use Zod for runtime validation and type inference. Rejected because Zod doesn't have `Type.Module`-style composable schemas or `Value.Equal`/`Value.Hash`/`Value.Diff` primitives that UJSX needs for the reconciler.
|
||||
- **Derived TypeScript types (`Static<typeof UJSX>`)**: Derive all types from the TypeBox Module using `Static`. Rejected for the core union types because `ComponentFn` can't be serialized (functions aren't valid JSON Schema) and the inferred types from complex TypeBox unions were less readable than hand-written types.
|
||||
|
||||
## Decision
|
||||
|
||||
The `Type.Module` system from `@alkdev/typebox` IS the type registry. No separate registry class or map. The `UJSX` Module defines all core types (`UPrimitive`, `PropValue`, `UniversalProps`, `UElement`, `URoot`, `UNode`) in a single `Type.Module` call, and TypeScript types are defined separately for clean inference.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- Single source of truth for validation. `Value.Check(UJSX.Import("UElement"), node)` validates at runtime.
|
||||
- JSON Schema export for free: `Value.Convert(UJSX.Import("UElement"), node)` for serialization.
|
||||
- The planned reconciler can use `Value.Equal`, `Value.Hash`, `Value.Diff` for optimization (see reconciler Phase 4 research).
|
||||
|
||||
### Negative
|
||||
- TypeBox types and hand-written TypeScript types must be kept in sync manually. This is tractable (7 types) but requires discipline.
|
||||
- Consumers must depend on `@alkdev/typebox` to use schema validation, even if they only want the tree types. The tree types themselves are importable without TypeBox.
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
status: draft
|
||||
last_updated: 2026-05-18
|
||||
---
|
||||
|
||||
# ADR-003: Preact signals-core for reactivity
|
||||
|
||||
**Status**: Proposed
|
||||
|
||||
## Context
|
||||
|
||||
UJSX needs a reactive primitive for propagating changes through element trees. The reactive layer (ReactiveRoot, reactiveComponent, reactiveElement) needs signal, computed, effect, and batch operations.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Custom reactive implementation**: Write a signal/computed/effect system from scratch. Rejected because writing a correct implementation (cycle detection, batch scheduling, lazy evaluation) is non-trivial and Preact's implementation is battle-tested.
|
||||
- **Solid.js reactive primitives**: Use Solid's reactive system. Rejected because they're coupled to Solid's render cycle and not published as a standalone package.
|
||||
- **Vue reactivity**: Use Vue's reactive system. Rejected because it uses Proxy-based tracking which has different performance characteristics and isn't designed for tree-agnostic use.
|
||||
- **RxJS**: Use observables for reactivity. Rejected because it's an observable/stream model, not a reactive state model. Different paradigm, much larger API surface.
|
||||
|
||||
## Decision
|
||||
|
||||
Use `@preact/signals-core` as the reactive layer. Re-export its primitives (`signal`, `computed`, `effect`, `batch`) and types (`Signal`, `ReadonlySignal`) from the UJSX reactive module.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- Proven implementation with ~800 lines of battle-tested code, used in Preact and adopted by other frameworks.
|
||||
- Small dependency surface — `@preact/signals-core` has zero dependencies.
|
||||
- Batch scheduling is built-in, matching the reconciliation model planned for the reconciler.
|
||||
- Future consumers (flowgraph reactive host) can use the same signal/computed/effect primitives without a different reactive library.
|
||||
|
||||
### Negative
|
||||
- UJSX re-exports Preact signals, creating a coupling. If Preact signals changes its scheduling model, UJSX is affected.
|
||||
- The `effect` return value (dispose function) is currently discarded in reactiveComponent/reactiveElement — the dispose functions are no-ops. This is a known gap that the reconciler work addresses.
|
||||
Reference in New Issue
Block a user