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
This commit is contained in:
@@ -82,6 +82,7 @@ export type UElement = {
|
||||
type: string;
|
||||
props: UniversalProps;
|
||||
children: UNode[];
|
||||
key?: string; // TODO: Not yet implemented. See ADR-004 and reconciler.md
|
||||
};
|
||||
export type URoot = {
|
||||
type: "root";
|
||||
@@ -135,28 +136,30 @@ The `isUElement` guard excludes `URoot` by checking `type !== "root"`. Without t
|
||||
|
||||
`UElement` currently has no `key` field. The reconciler needs an identity mechanism to match old children to new children across re-renders. Without `key`, reconciliation is positional-only — the Nth child of the old tree maps to the Nth child of the new tree, which breaks when children are reordered, inserted, or removed.
|
||||
|
||||
The reconciler research (`docs/research/reconciler/00-KEY-FIELD-DESIGN.md`) proposes adding `key?: string` to `UElement` as a first-class field. `h()` would extract `key` from props and promote it to the element level, so component functions never receive it. `URoot` does not get `key` — roots are unique per `createRoot()` call and never need reconciliation identity.
|
||||
The reconciler architecture (see [reconciler.md](reconciler.md) and [ADR-004](decisions/004-key-as-first-class-field.md)) specifies adding `key?: string` to `UElement` as a first-class field. `h()` extracts `key` from props and promotes it to the element level, so component functions never receive it. `URoot` does not get `key` — roots are unique per `createRoot()` call and never need reconciliation identity.
|
||||
|
||||
**Status**: Research complete, not yet implemented.
|
||||
**Status**: Architecture specified, not yet implemented.
|
||||
|
||||
## Constraints
|
||||
|
||||
- **Props are not fully serializable** — `PropValue` includes functions. Hosts that need serialization must strip function-valued props at their boundary. This is by design: event handlers and component references are first-class prop values.
|
||||
- **UniversalProps is open** — `additionalProperties` allows any key. This prevents UJSX from being a prop gatekeeper and lets hosts define their own contracts without extending UJSX's schema.
|
||||
- **TypeScript types are authoritative for type inference** — the TypeBox Module is for runtime validation and JSON Schema export only. Do not use `Static<typeof UJSX>` as the source of truth for TypeScript types; the hand-written types include `ComponentFn` and function-typed `PropValue` entries that the schema cannot express cleanly.
|
||||
- **No `key` on `UElement`** — see Known Gaps above. Positional reconciliation only until `key` is added.
|
||||
- **`key` not yet on `UElement`** — `key?: string` is planned (ADR-004) but not yet implemented. Until then, reconciliation is positional-only.
|
||||
- **No `key` on `URoot`** — roots are identified by `props.id`, not a `key` field. This is by design; roots are never children of another element.
|
||||
- **Type guards are mutually exclusive** — `isUElement`, `isURoot`, and `isUPrimitive` partition the `UNode` space. Every `UNode` matches exactly one guard.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Should `key` accept numbers?** React coerces number keys to strings. The current proposal enforces `string` only — simpler, no implicit coercion. Users can wrap in `String()`. See [00-KEY-FIELD-DESIGN.md](../../research/reconciler/00-KEY-FIELD-DESIGN.md).
|
||||
1. **Should `key` accept numbers?** React coerces number keys to strings. The current proposal enforces `string` only — simpler, no implicit coercion. Users can wrap in `String()`. See [ADR-004](decisions/004-key-as-first-class-field.md) and research: [00-KEY-FIELD-DESIGN.md](../../research/reconciler/00-KEY-FIELD-DESIGN.md).
|
||||
2. **Should `UPrimitive` include `undefined`?** Currently `null` represents an explicitly empty value. `undefined` means "absent" and should not appear as a tree node. This is consistent with how JSX treats `undefined` children (rendered to nothing), but some hosts might benefit from an explicit "missing" sentinel. No current use case justifies this.
|
||||
3. **Should `UniversalProps` constrain value types per-host?** The open schema allows any `PropValue` for any key. A host that wants stricter prop contracts (e.g., `onClick` must be a function, `className` must be a string) must validate at its own boundary. A future host-typed props system could be layered on top without changing the base schema.
|
||||
|
||||
## References
|
||||
|
||||
- Source: `src/core/schema.ts`
|
||||
- Key field design: `docs/research/reconciler/00-KEY-FIELD-DESIGN.md`
|
||||
- Key field ADR: [decisions/004-key-as-first-class-field.md](decisions/004-key-as-first-class-field.md)
|
||||
- Reconciler architecture: [reconciler.md](reconciler.md)
|
||||
- Key field design research: `docs/research/reconciler/00-KEY-FIELD-DESIGN.md`
|
||||
- TypeBox Module as type registry: `docs/architecture/decisions/002-typebox-module-as-registry.md`
|
||||
- HTML-agnostic core: `docs/architecture/decisions/001-html-agnostic-core.md`
|
||||
Reference in New Issue
Block a user