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:
2026-05-18 15:15:13 +00:00
parent 09f32f0c64
commit da82b52b27
10 changed files with 631 additions and 34 deletions

View File

@@ -125,11 +125,11 @@ Calling `render()` a second time replaces `this.renderDisposer` without disposin
### No connection to HostConfig reconciler
`ReactiveRoot.render()` emits events, but nothing consumes those events to call `HostConfig.prepareUpdate`/`commitUpdate`. The signal layer and the host layer are two separate islands. The reconciler research ([01-reactive-host-bridge.md](../../research/reconciler/01-reactive-host-bridge.md)) proposes a fiber-based bridge: `ReactiveRoot` signal changes trigger a reconciliation pass that diffs props and calls `HostConfig` update methods.
`ReactiveRoot.render()` emits events, but nothing consumes those events to call `HostConfig.prepareUpdate`/`commitUpdate`. The signal layer and the host layer are two separate islands. The reconciler architecture ([reconciler.md](reconciler.md)) proposes a fiber-based bridge: `ReactiveRoot` signal changes trigger a reconciliation pass that diffs props and calls `HostConfig` update methods.
### No auto-dispose on unmount
`ReactiveRoot` has no `unmount()` or `destroy()` method. Effects created by `subscribe()` and `render()` are never automatically torn down. The reconciler research ([03-unmount-dispose-support.md](../../research/reconciler/03-unmount-dispose-support.md)) addresses this.
`ReactiveRoot` has no `unmount()` or `destroy()` method. Effects created by `subscribe()` and `render()` are never automatically torn down. The lifecycle management architecture ([lifecycle.md](lifecycle.md)) addresses this, with `ReactiveRoot.dispose()` tracking and cleaning up all subscriber and render effect disposers.
## Constraints
@@ -143,13 +143,15 @@ Calling `render()` a second time replaces `this.renderDisposer` without disposin
1. **Should `ReactiveNode.dispose` be implemented using `effect` cleanup or stored-disposer patterns?** The current `computed` signals have no public dispose API in `@preact/signals-core`. Disposal requires either switching to an effect-based approach (where each `computed` is tracked by an `effect` that can be disposed) or maintaining an explicit disposer list.
2. **Should `ReactiveRoot` track all subscription disposers?** Adding an internal `Set<() => void>` for active subscribers would allow `ReactiveRoot` to clean up on unmount. This creates a lifecycle coupling — `ReactiveRoot` would need a `destroy()` method.
3. **How should `ReactiveRoot` connect to the reconciler?** Options: (a) `ReactiveRoot` emits events that a reconciler subscribes to, (b) `createReactiveRoot(host, container)` bridges both layers, (c) consumer code wires them manually. See [01-reactive-host-bridge.md](../../research/reconciler/01-reactive-host-bridge.md) for analysis.
3. **How should `ReactiveRoot` connect to the reconciler?** Options: (a) `ReactiveRoot` emits events that a reconciler subscribes to, (b) `createReactiveRoot(host, container)` bridges both layers, (c) consumer code wires them manually. See [reconciler.md](reconciler.md) for the fiber-based bridge approach.
4. **Should `render()` support multiple concurrent subscribers?** The current overwriting design suggests single-subscriber usage. If multiple hosts need to render the same reactive tree, they should each call `subscribe()` directly rather than `render()`.
## References
- Source: `src/core/reactive.ts`
- ADR-003: `docs/architecture/decisions/003-preact-signals-for-reactivity.md`
- Reconciler architecture: [reconciler.md](reconciler.md)
- Lifecycle management: [lifecycle.md](lifecycle.md)
- Reactive → Host bridge research: `docs/research/reconciler/01-reactive-host-bridge.md`
- Unmount & dispose research: `docs/research/reconciler/03-unmount-dispose-support.md`
- Preact signals-core: `@preact/signals-core`