--- 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