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).
57 lines
2.5 KiB
Markdown
57 lines
2.5 KiB
Markdown
---
|
|
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 |