You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-`fn` may return an optional cleanup that runs when the scope is disposed
23
-
-Used to group effects and control their lifetime
21
+
-`fn` receives no arguments and may return an optional cleanup that runs when the scope is disposed
22
+
-Used to group effects and control their shared lifetime
23
+
-`options.root = true` (`ScopeOptions`) — suppresses parent-owner registration; the returned `dispose` is the sole teardown mechanism. Use for scopes whose lifecycle is controlled externally (e.g. a web component's `disconnectedCallback`)
- Runs `fn` without registering cleanups in the current owner (nulls `activeOwner`)
43
-
- Use in `connectedCallback` and similar DOM lifecycle hooks where the DOM — not the reactive graph — owns the element's lifetime
43
+
- For creating a scope with an external lifecycle authority, prefer `createScope(fn, { root: true })` — it is equivalent to `unown(() => createScope(fn))` but more readable
44
+
- Use `unown` directly when detaching non-scope computations from the current owner
Copy file name to clipboardExpand all lines: .claude/skills/cause-effect-dev/references/internal-types.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,7 +29,7 @@ Two independent global pointers are maintained by `src/graph.ts`:
29
29
- Set to the currently-running Effect or Scope
30
30
- Any cleanup registered while `activeOwner` is set is attached to that owner
31
31
- Nulled by `unown(fn)` — cleanups registered inside `fn` are not owned by the current context
32
-
-Use `unown` in `connectedCallback` for nodes whose lifecycle is managed by the DOM, not by the reactive graph
32
+
-For web component `connectedCallback`, prefer `createScope(fn, { root: true })` over `unown(() => createScope(fn))` — both suppress parent registration, but `{ root: true }` is clearer at the call site
-`fn` receives no arguments and may return an optional cleanup
25
25
- Use to group effects and control their shared lifetime
26
+
-`options.root = true` (`ScopeOptions`) — suppresses parent-owner registration; the returned `dispose` is the sole teardown mechanism. Use when an external authority (e.g. a web component's `disconnectedCallback`) manages the scope's lifetime
26
27
27
28
```typescript
28
29
const dispose =createScope(() => {
@@ -67,16 +68,18 @@ createEffect(() => {
67
68
68
69
**`unown(fn)`**
69
70
- Runs `fn` without registering cleanups in the current owner
70
-
-Use in `connectedCallback` and similar DOM lifecycle methods where the DOM —
71
-
not the reactive graph — manages the element's lifetime
71
+
-For DOM-managed lifecycles involving a scope, prefer `createScope(fn, { root: true })` — it is equivalent to `unown(() => createScope(fn))` but more readable
72
+
- Use `unown` directly when detaching a single computation (e.g. `createEffect`) rather than a full scope
72
73
73
74
```typescript
74
75
connectedCallback() {
75
-
// cleanup is tied to disconnectedCallback, not to a reactive owner
-**Must be created inside an owner** (`createScope` or another effect) — throws `RequiredOwnerError` otherwise
96
96
- Runs immediately on creation, then re-runs on dependency changes
97
97
- Returns a `Cleanup` function; calling it disposes the effect and all its children
98
-
- Use `unown` inside`connectedCallback`/ `disconnectedCallback` when the DOM manages the element's lifetime
98
+
- Use `createScope(fn, { root: true })` in`connectedCallback`for DOM-managed lifetimes — the returned `dispose` goes in `disconnectedCallback`; the scope is never silently disposed by a re-running outer effect
Copy file name to clipboardExpand all lines: .claude/skills/cause-effect/workflows/debug.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,7 +31,7 @@ Read the specific section in references/non-obvious-behaviors.md or references/e
31
31
If the symptom does not match a known gotcha, read references/api-facts.md to confirm:
32
32
- Signal generics use `T extends {}` (no `null` or `undefined`)
33
33
-`createEffect` is inside an owner (`createScope` or another effect)
34
-
-`unown`is used only for DOM-owned lifecycles, not to bypass ownership generally
34
+
-For DOM-owned lifecycles involving a scope: use `createScope(fn, { root: true })`, not `unown(() => createScope(fn))`. `unown`is still correct for non-scope cases (e.g. detaching a single `createEffect`)
35
35
-`batch` is used if multiple state writes should coalesce
Copy file name to clipboardExpand all lines: .claude/skills/cause-effect/workflows/use-api.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -13,7 +13,7 @@ Read references/signal-types.md. Match the task to the appropriate signal(s) usi
13
13
14
14
Before writing any `createEffect` call, confirm there is an active owner in scope:
15
15
- Top-level effects must be wrapped in `createScope`
16
-
- Effects in Web Component lifecycle methods that use DOM-managed lifetime should use `unown`
16
+
- Effects in Web Component lifecycle methods that use DOM-managed lifetime: use `createScope(fn, { root: true })` — the returned `dispose` goes in `disconnectedCallback`. This suppresses parent-owner registration so the scope is never disposed by a re-running outer effect
17
17
18
18
See references/api-facts.md → `<core_functions>` for the rules.
Copy file name to clipboardExpand all lines: ARCHITECTURE.md
+5-2Lines changed: 5 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -170,9 +170,11 @@ Cleanup functions are stored on the `cleanup` field of owner nodes. The field is
170
170
171
171
`registerCleanup()` promotes from `null` → function → array as needed. `runCleanup()` executes all registered cleanups and resets the field to `null`.
172
172
173
-
### `createScope(fn)`
173
+
### `createScope(fn, options?)`
174
174
175
-
Creates an ownership scope without an effect. The scope becomes `activeOwner` during `fn` execution. Returns a `dispose` function. If the scope is created inside another owner, its disposal is automatically registered on the parent.
175
+
Creates an ownership scope without an effect. The scope becomes `activeOwner` during `fn` execution. Returns a `dispose` function. Unless `options.root` is `true`, the scope's disposal is automatically registered on the parent owner (if any).
176
+
177
+
`{ root: true }` (via `ScopeOptions`) suppresses that registration, making the returned `dispose` the sole mechanism for tearing down the scope. This is the correct pattern for any owner with an external lifecycle authority (e.g. a web component whose `disconnectedCallback` is the only teardown point) — without it, a scope created inside a re-runnable effect would be disposed on the next effect re-run.
176
178
177
179
## Signal Types
178
180
@@ -358,3 +360,4 @@ The first-subscriber path is the key to `watched` lifecycle propagation: when an
358
360
|`isEqual` placement | Implementation in `graph.ts`; public preset exported as `DEEP_EQUALITY` from `graph.ts`|`util.ts` (blocked by circular import: `errors.ts` → `util.ts`); keep in `list.ts`|`isEqual` needs `CircularDependencyError`, which lives in `errors.ts`; `errors.ts` already imports `util.ts`, so `util.ts` cannot import back. `graph.ts` already imports `CircularDependencyError` and is the correct home for all equality constants. |
359
361
|`isEqual` public export | Deprecated alias re-exported from `index.ts` pointing to the implementation in `graph.ts`| Remove immediately | No known downstream consumers, but it was part of the public API — a deprecation cycle is the correct path to removal. |
360
362
| Cycle detection in `isEqual` / `DEEP_EQUALITY`| No cycle detection — plain recursion, no `WeakSet`| (a) Keep `WeakSet` per call; (b) import `fast-deep-equal` or `dequal`|`WeakSet` allocation on every `List.set()` / `Store.set()` call is unnecessary overhead for the common case (plain JSON-like signal values). Circular signal data is a user bug; a stack overflow is an acceptable outcome. Importing an external package for a 20-line function contradicts the zero-dependency policy and bundle-size constraints. `DEEP_EQUALITY` has never shipped; deprecated `isEqual` has no known consumers — no major version required. |
363
+
|`createScope` root option |`ScopeOptions { root?: boolean }` as second argument to `createScope`| Standalone `createRoot(fn)` export | One-line implementation difference; extending the existing function avoids adding a new export. `ScopeOptions` follows the `*Options` pattern used by every other creation function in the library (`SignalOptions`, `ListOptions`, `SensorOptions`, etc.). Positional boolean (`createScope(fn, true)`) was rejected: readable only with IDE hover support; an options object is self-documenting in code review. |
Copy file name to clipboardExpand all lines: CHANGELOG.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,13 @@
1
1
# Changelog
2
2
3
+
## 1.3.1
4
+
5
+
### Added
6
+
-`createScope` now accepts an optional `ScopeOptions` second argument; `{ root: true }` creates a root scope that is not registered on the current parent owner – the returned `dispose` is the sole teardown mechanism. Export new `ScopeOptions` type.
7
+
8
+
### Changed
9
+
- Improved type inference for `createList` and `createCollection` when providing a custom `createItem` factory (e.g. `createStore`). The generic type of the returned item signal is now properly inferred without requiring type assertions.
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -29,4 +29,4 @@ These are easy to get wrong and not derivable from reading the source without pr
29
29
30
30
**`Slot` is a valid property descriptor** with `get`, `set`, `configurable`, and `enumerable` — pass directly to `Object.defineProperty()`. `slot.replace(nextSignal)` swaps the backing signal. Slot has no `update()` — it is a forwarding layer, not a mutable cell.
31
31
32
-
**`unown()` is the correct fix for DOM-owned component lifecycles.**`createScope` inside `connectedCallback` registers its `dispose` on the parent effect's cleanup list — disposing the component on the next parent re-run. Wrap with `unown(() => createScope(...))`to leave `disconnectedCallback` as the sole lifecycle authority.
32
+
**`createScope(fn, { root: true })` is the correct fix for DOM-owned component lifecycles.**`createScope` inside `connectedCallback` registers its `dispose` on the parent effect's cleanup list — disposing the component on the next parent re-run. Pass `{ root: true }` (via `ScopeOptions`) to suppress that registration and leave `disconnectedCallback` as the sole lifecycle authority. `unown(() => createScope(...))`achieves the same effect but is the legacy pattern; `{ root: true }` is preferred because the intent is explicit at the call site. `unown()` remains useful for detaching arbitrary computations from the current owner when no new scope is needed.
0 commit comments