decompose reconciler roadmap into 20 implementation tasks across 5 phases

Tasks follow the architecture spec phases:
- Phase 0: key field on UElement (2 tasks + review)
- Phase 1: reactive-host bridge / fiber tree (4 tasks + review)
- Phase 2: key-based children reconciliation (3 tasks + review)
- Phase 3: unmount & dispose support (4 tasks)
- Phase 4: TypeBox value optimizations (4 tasks)

Validated with taskgraph CLI: no cycles, 15 parallel generations,
3 high-risk tasks identified (signal-driven-updates, commit-mutations,
fiber-disposal).
This commit is contained in:
2026-05-18 16:26:52 +00:00
parent 8cd4091afc
commit c9c32a6aa6
20 changed files with 993 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
---
id: reactiveroot-dispose
name: Implement ReactiveRoot.dispose()
status: pending
depends_on: [review-reconciler]
created: 2026-05-18T16:22:57.292895316Z
modified: 2026-05-18T16:22:57.292895758Z
scope: narrow
risk: medium
impact: component
level: implementation
---
# Description
Implement real disposal in `ReactiveRoot` to close the signal subscription leak gap. Currently, `ReactiveRoot` has no `dispose()` method, `subscribe()` return values are not tracked, and `render()` overwrites the previous disposer without disposing it.
`ReactiveRoot.dispose()`:
1. Calls the render effect disposer and clears it
2. Iterates all tracked subscriber disposers and calls each one
3. Clears internal tracking state
`subscribe()` is updated to track disposer returns: each `effect()` created by `subscribe()` has its disposer stored in an internal list. The returned unsubscribe function both calls the disposer and removes it from tracking.
Both `dispose()` and the returned unsubscribe functions are idempotent.
Also implement real `dispose` on `ReactiveNode` — currently both `reactiveComponent` and `reactiveElement` return `dispose: () => {}`. With proper disposal tracking, these should return actual cleanup functions.
## Acceptance Criteria
- [ ] `ReactiveRoot.dispose()` method implemented
- [ ] `dispose()` calls render effect disposer and clears `renderDisposer`
- [ ] `dispose()` iterates all tracked subscriber disposers and calls each
- [ ] `dispose()` clears internal tracking state after running
- [ ] `subscribe()` tracks disposer returns in an internal `Set<() => void>`
- [ ] `subscribe()` returned unsubscribe function calls disposer AND removes from tracking
- [ ] `dispose()` and unsubscribe functions are idempotent (safe to call twice)
- [ ] `reactiveComponent` and `reactiveElement` return real dispose functions (not no-ops)
- [ ] `ReactiveNode.dispose` cleans up the underlying computed subscription
- [ ] Existing tests pass
- [ ] New test: `ReactiveRoot.dispose()` prevents future effect fires
- [ ] New test: `subscribe()` unsubscribe removes from tracking
- [ ] New test: double `dispose()` is safe
- [ ] New test: `reactiveComponent.dispose()` prevents future computed evaluations
## References
- docs/architecture/reactive-layer.md — Known Gaps: all dispose functions are no-ops, subscribe return value thrown away, render overwrites previous disposer, no auto-dispose on unmount
- docs/architecture/lifecycle.md — ReactiveRoot Disposal section
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion