The RAFScheduler per-render JSClosure leak (fixed in the jsclosure-per-frame-leak PR) is a pattern, not a one-off: `JSClosure.init` pins the closure in JavaScriptKit's static `sharedClosures` table until `release()`; dropping the Swift reference does nothing.
Remaining creation sites to audit for the same class:
- SwiflowRouter/Web/Link.swift:84 — closure created per what lifecycle? If per-render of a Link body, router apps leak per render.
- SwiflowStore/PersistentStore.swift:229/234/247 — `onSuccess`/`onError`/`onUpgrade` per IndexedDB request: if created per persisted write, apps with frequent `@Persisted` writes leak per write.
- SwiflowRouter/Web/BrowserNavigator.swift:88 — `startListening` replaces prior registration; verify `stopListening` calls `release()` rather than just dropping the reference.
- Cold-path sites (BackgroundRevalidation, DispatcherBridge, DevAPI, HMRBridge) are persistent-by-design singletons — verify and document, no fix expected.
Candidates for the fix pattern: reuse one persistent closure (RAFScheduler pattern) for recurring callbacks; `JSOneshotClosure` (Timing.after pattern) for fire-once callbacks, with explicit `release()` on every cancellation path.
Found while diagnosing Safari's 'this webpage was using significant memory' kill during GridBoard playback.
🤖 Generated with Claude Code
The RAFScheduler per-render JSClosure leak (fixed in the jsclosure-per-frame-leak PR) is a pattern, not a one-off: `JSClosure.init` pins the closure in JavaScriptKit's static `sharedClosures` table until `release()`; dropping the Swift reference does nothing.
Remaining creation sites to audit for the same class:
Candidates for the fix pattern: reuse one persistent closure (RAFScheduler pattern) for recurring callbacks; `JSOneshotClosure` (Timing.after pattern) for fire-once callbacks, with explicit `release()` on every cancellation path.
Found while diagnosing Safari's 'this webpage was using significant memory' kill during GridBoard playback.
🤖 Generated with Claude Code