@pyreon/reactivity 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # @pyreon/reactivity
2
2
 
3
- Signal-based fine-grained reactivity primitives for the Pyreon framework.
3
+ Standalone fine-grained reactivity primitives signals, computeds, effects, stores, resources, scopes.
4
+
5
+ `@pyreon/reactivity` is the foundation layer every other Pyreon package builds on, but it has zero framework dependencies and works on its own in Node, Bun, edge workers, or any JavaScript environment without DOM or JSX. Subscribers are tracked via a `Set<() => void>`; batches use a pointer swap for zero-allocation grouping. Two-tier batch flush (computed recompute → effect run) prevents stale reads in diamond-shaped dependency graphs.
4
6
 
5
7
  ## Install
6
8
 
@@ -8,70 +10,173 @@ Signal-based fine-grained reactivity primitives for the Pyreon framework.
8
10
  bun add @pyreon/reactivity
9
11
  ```
10
12
 
11
- ## Quick Start
13
+ ## Quick start
12
14
 
13
15
  ```ts
14
- import { signal, computed, effect, batch } from '@pyreon/reactivity'
16
+ import {
17
+ signal, computed, effect, batch, onCleanup, watch, untrack,
18
+ createStore, createResource, effectScope,
19
+ } from '@pyreon/reactivity'
15
20
 
16
21
  const count = signal(0)
17
22
  const doubled = computed(() => count() * 2)
18
23
 
19
- effect(() => {
24
+ const dispose = effect(() => {
20
25
  console.log('doubled:', doubled())
26
+ onCleanup(() => console.log('cleaning up'))
21
27
  })
22
28
 
29
+ batch(() => {
30
+ count.set(1)
31
+ count.set(2) // subscribers fire once, with doubled = 4
32
+ })
33
+
34
+ watch(() => count(), (next, prev) => console.log(`${prev} → ${next}`))
35
+
36
+ const store = createStore({ todos: [{ text: 'Learn Pyreon', done: false }] })
37
+ store.todos[0].done = true // fine-grained update, no immer
38
+
39
+ dispose()
40
+ ```
41
+
42
+ ## The signal contract
43
+
44
+ ```ts
45
+ const x = signal(0)
46
+ x() // read (subscribes if inside a tracked scope)
47
+ x.set(1) // write
48
+ x.update(n => n + 1)
49
+ x.peek() // read without subscribing
50
+ ```
51
+
52
+ Signals are **callable functions**, not `.value` getters (Vue) and not `[state, setState]` tuples (React). Calling the signal as a function is the read; `signal(5)` does NOT set the value — it reads and discards the argument. Dev mode warns; the `@pyreon/lint` rule `signal-write-as-call` flags it statically.
53
+
54
+ Optional `name` for debugging: `signal(0, { name: 'count' })` — the `@pyreon/vite-plugin` injects names automatically in dev.
55
+
56
+ ## Computed
57
+
58
+ ```ts
59
+ const doubled = computed(() => count() * 2)
60
+ const sameRef = computed(() => obj(), { equals: (a, b) => a.id === b.id })
61
+ ```
62
+
63
+ Lazy, memoized, auto-tracking. Recomputes only when a dependency changes AND a subscriber actually reads it. Pass a custom `equals` to dedupe by structural identity instead of `Object.is`.
64
+
65
+ ## Effects
66
+
67
+ ```ts
68
+ const dispose = effect(() => {
69
+ console.log(count())
70
+ onCleanup(() => console.log('before next run / on dispose'))
71
+ })
72
+ dispose()
73
+ ```
74
+
75
+ `effect()` re-runs on tracked-signal change; the returned function disposes. Returning a cleanup function from the effect body is supported; `onCleanup(fn)` is the explicit form. `renderEffect()` is a lighter DOM-targeted variant that does NOT support `onCleanup` and does NOT register with `EffectScope` — used internally by `@pyreon/runtime-dom`.
76
+
77
+ `watch(source, callback)` is the explicit-source variant: `source` is evaluated for tracking, `callback(next, prev)` runs on change, and returning a cleanup function is honored.
78
+
79
+ ## Batching
80
+
81
+ ```ts
23
82
  batch(() => {
24
83
  count.set(1)
25
84
  count.set(2)
85
+ }) // subscribers notified ONCE with count=2
86
+ ```
87
+
88
+ `batch()` defers subscriber notifications until the end of the callback. `nextTick(): Promise<void>` resolves after the current flush — useful for awaiting DOM updates in tests.
89
+
90
+ ## Stores
91
+
92
+ ```ts
93
+ const store = createStore({ count: 0, todos: [{ text: 'a', done: false }] })
94
+ store.count++ // notifies
95
+ store.todos[0].done = true // deep — notifies
96
+
97
+ const shallow = shallowReactive({ user: { name: 'a' } })
98
+ shallow.user = { name: 'b' } // notifies
99
+ shallow.user.name = 'c' // does NOT notify (shallow)
100
+
101
+ const raw = markRaw(thirdPartyClassInstance) // skip proxy
102
+ ```
103
+
104
+ `createStore` returns a deeply-reactive proxy. `shallowReactive` proxies only the top level. `markRaw` opts an object out of proxying — useful for class instances, DOM nodes, third-party objects. `reconcile(target, source)` patches an existing store to match `source` without remounting.
105
+
106
+ **Caveat:** `Map`, `Set`, `WeakMap`, `WeakSet`, `Date`, `RegExp`, `Promise`, `Error` are returned RAW. Mutating them does not notify; assign a new instance to trigger updates.
107
+
108
+ ## Resources
109
+
110
+ ```ts
111
+ const user = createResource(() => userId(), async (id) => {
112
+ const r = await fetch(`/api/users/${id}`)
113
+ return r.json()
26
114
  })
27
- // logs "doubled: 4" once
115
+
116
+ user.data() // T | undefined
117
+ user.loading() // boolean
118
+ user.error() // Error | undefined
119
+ user.refetch()
120
+ ```
121
+
122
+ `createResource(source, fetcher)` re-runs the fetcher whenever `source` changes; stale responses are dropped via an internal request-id guard. Resources created **outside** an `EffectScope` must be `dispose()`-d explicitly to avoid leaks.
123
+
124
+ ## EffectScope
125
+
126
+ ```ts
127
+ const scope = effectScope()
128
+ scope.runInScope(() => {
129
+ effect(() => console.log(count()))
130
+ onScopeDispose(() => console.log('scope ended'))
131
+ })
132
+ scope.stop() // disposes every effect inside
28
133
  ```
29
134
 
30
- ## API
135
+ Groups effects for bulk disposal — used internally by `@pyreon/runtime-dom`'s mount pipeline. `getCurrentScope()` returns the active scope; `setCurrentScope(scope)` is the escape hatch for advanced cross-tree integrations.
31
136
 
32
- ### Signals
137
+ Internal arrays (`_effects`, `_updateHooks`) are lazy-allocated — scopes with no effects cost only the object itself.
33
138
 
34
- - **`signal<T>(initial: T, options?): Signal<T>`** -- Callable getter with `.set(value)` and `.update(fn)` methods. Pass `{ name }` for debug labels (auto-injected by `@pyreon/vite-plugin` in dev mode).
35
- - **`computed<T>(fn, options?): Computed<T>`** -- Derived signal that recomputes lazily when dependencies change.
36
- - **`cell<T>(initial: T): Cell<T>`** -- Lightweight reactive cell.
139
+ ## Selectors
37
140
 
38
- ### Effects
141
+ ```ts
142
+ const selected = signal<string | null>(null)
143
+ const isSelected = createSelector(() => selected())
39
144
 
40
- - **`effect(fn): Effect`** -- Runs `fn` and re-runs it whenever its tracked dependencies change.
41
- - **`onCleanup(fn)`** -- Registers a cleanup function inside an effect. Runs before re-execution and on disposal.
42
- - **`renderEffect(fn): Effect`** -- Like `effect`, but scheduled for render timing.
43
- - **`watch(source, callback, options?): WatchOptions`** -- Watches a reactive source and calls back on change.
44
- - **`setErrorHandler(handler)`** -- Sets a global error handler for effect errors.
145
+ <For each={items} by={i => i.id}>
146
+ {(item) => <li class={() => isSelected(item.id) ? 'active' : ''}>{item.name}</li>}
147
+ </For>
148
+ ```
45
149
 
46
- ### Batching
150
+ `createSelector(source)` returns a function that, when called with a key, only notifies subscribers when the key transitions in or out of the selected state. O(1) instead of N effect runs on selection change.
47
151
 
48
- - **`batch(fn)`** -- Groups multiple signal writes; subscribers notified once at the end.
49
- - **`nextTick(): Promise<void>`** -- Resolves after the current batch of updates flushes.
152
+ ## Cell minimal alternative to signal
50
153
 
51
- ### Tracking
154
+ ```ts
155
+ import { cell } from '@pyreon/reactivity'
156
+ const c = cell(0)
157
+ c.get(); c.set(1); c.subscribe(listener)
158
+ ```
52
159
 
53
- - **`runUntracked(fn)`** -- Runs `fn` without tracking any signal reads.
54
- - **`untrack(fn)`** -- Alias for `runUntracked`.
160
+ `cell()` is a class-based primitive with a single-listener fast path and one allocation per cell. It is **not** callable and **does not** participate in effect tracking use it only for cross-cutting state where the signal-tracking overhead would be wasteful.
55
161
 
56
- ### Scopes
162
+ ## Debugging
57
163
 
58
- - **`effectScope(): EffectScope`** -- Creates a scope that collects effects for bulk disposal. Internal arrays (`_effects`, `_updateHooks`) are lazy-allocated on first use -- scopes with no effects cost only the object itself.
59
- - **`getCurrentScope(): EffectScope | null`** -- Returns the active effect scope, or `null` if none.
60
- - **`onScopeDispose(fn)`** -- Register a callback to run when the current scope stops (Vue 3 parity).
61
- - **`setCurrentScope(scope)`** -- Manually sets the current effect scope.
164
+ ```ts
165
+ import { setErrorHandler, inspectSignal, onSignalUpdate, why, getReactiveTrace } from '@pyreon/reactivity'
166
+
167
+ setErrorHandler((err, source) => reportToSentry(err, { tag: source }))
62
168
 
63
- ### Selectors and Resources
169
+ const count = signal(0, { name: 'count' })
170
+ onSignalUpdate(count, (next, prev) => console.log('count', prev, '→', next))
171
+ inspectSignal(count) // { name, value, subscribers: number }
172
+ why(count) // print dependency graph for this signal
173
+ ```
64
174
 
65
- - **`createSelector(source)`** -- Creates an efficient selector for keyed comparisons.
66
- - **`createResource(fetcher): Resource<T>`** -- Wraps an async data source in a reactive resource.
175
+ `activate/deactivate/getReactiveGraph/getReactiveFires` form the **opt-in** bridge consumed by the Pyreon devtools zero cost until activated, gated by `process.env.NODE_ENV !== 'production'`, tree-shaken in production.
67
176
 
68
- ### Stores
177
+ ## Documentation
69
178
 
70
- - **`createStore(initial)`** -- Creates a deeply reactive store object.
71
- - **`isStore(value): boolean`** -- Checks whether a value is a reactive store.
72
- - **`reconcile(target, source)`** -- Efficiently patches a store to match a new value.
73
- - **`shallowReactive<T>(initial): T`** -- Creates a SHALLOWLY reactive store: top-level property writes notify, but nested object mutations don't (Vue 3 parity). Use for large object graphs where deep proxying would be wasteful.
74
- - **`markRaw<T>(value): T`** -- Mark an object as RAW so `createStore` and `shallowReactive` return it unwrapped (Vue 3 parity). Useful for class instances, third-party objects, DOM nodes, or any shape that shouldn't be deeply proxied. Marking is one-way (no `unmarkRaw`); mark BEFORE the object enters a store.
179
+ Full docs: [docs.pyreon.dev/docs/reactivity](https://docs.pyreon.dev/docs/reactivity) (or `docs/docs/reactivity.md` in this repo).
75
180
 
76
181
  ## License
77
182
 
@@ -0,0 +1,280 @@
1
+ //#region src/reactive-devtools.ts
2
+ /**
3
+ * Time constant for the rate1s EWMA (milliseconds). Tuned for the "hot
4
+ * path debugging" use case: a 1-second time constant means a burst of
5
+ * fires shows up immediately, then decays to 1/e (~0.37×) after one
6
+ * second of silence, ~5% after 3 seconds, ~0.7% after 5 seconds.
7
+ *
8
+ * @internal — exported for tests + tunability.
9
+ */
10
+ const LPIH_RATE_TAU_MS = 1e3;
11
+ let _active = false;
12
+ let _nextId = 1;
13
+ const _byId = /* @__PURE__ */ new Map();
14
+ const _subId = /* @__PURE__ */ new WeakMap();
15
+ /** @internal — finalizer callback; prunes the record when a node is GC'd. */
16
+ function _rdPrune(id) {
17
+ _byId.delete(id);
18
+ }
19
+ const _finalizer = new FinalizationRegistry(_rdPrune);
20
+ const FIRE_CAP = 512;
21
+ let _fireBuf = null;
22
+ let _fireCount = 0;
23
+ const PREVIEW_MAX = 60;
24
+ function preview(v) {
25
+ let s;
26
+ try {
27
+ if (v === null) return "null";
28
+ if (v === void 0) return "undefined";
29
+ const t = typeof v;
30
+ if (t === "string") s = JSON.stringify(v);
31
+ else if (t === "number" || t === "boolean" || t === "bigint") s = String(v);
32
+ else if (t === "function") s = `[Function ${v.name || "anonymous"}]`;
33
+ else if (t === "symbol") s = v.toString();
34
+ else if (Array.isArray(v)) s = `Array(${v.length})`;
35
+ else {
36
+ const ctor = v.constructor?.name;
37
+ let keys = [];
38
+ try {
39
+ keys = Object.keys(v).slice(0, 3);
40
+ } catch {
41
+ keys = [];
42
+ }
43
+ s = `${ctor && ctor !== "Object" ? `${ctor} ` : ""}{${keys.join(", ")}${keys.length === 3 ? ", …" : ""}}`;
44
+ }
45
+ } catch {
46
+ s = "[unstringifiable]";
47
+ }
48
+ return s.length > PREVIEW_MAX ? `${s.slice(0, PREVIEW_MAX)}…` : s;
49
+ }
50
+ /** Activate the bridge. Idempotent. Called when a devtools client attaches. */
51
+ function activateReactiveDevtools() {
52
+ _active = true;
53
+ }
54
+ /**
55
+ * Deactivate + drop all retained state. Called when the devtools client
56
+ * disconnects so a closed panel leaves zero residue.
57
+ */
58
+ function deactivateReactiveDevtools() {
59
+ _active = false;
60
+ _byId.clear();
61
+ _fireBuf = null;
62
+ _fireCount = 0;
63
+ }
64
+ function isReactiveDevtoolsActive() {
65
+ return _active;
66
+ }
67
+ /**
68
+ * Parse the user's call site from `new Error().stack`. Returns undefined
69
+ * when devtools isn't active (zero-cost early-return — no Error allocated)
70
+ * OR when the stack format isn't recognized.
71
+ *
72
+ * `skipFrames` is the number of caller-frames to skip past _captureCallerLocation
73
+ * itself. The framework's hot-path callers (signal / computedLazy / effect)
74
+ * pass their own depth so the captured frame is the USER's call to
75
+ * `signal()` / `computed()` / `effect()`, not the framework's internals.
76
+ *
77
+ * Recognized stack formats:
78
+ * - V8 (Chrome / Node / Bun): ` at fn (file:line:col)`
79
+ * - V8 (anonymous): ` at file:line:col`
80
+ * - JSC (Safari) + SpiderMonkey: `fn@file:line:col`
81
+ *
82
+ * @internal
83
+ */
84
+ function _captureCallerLocation(skipFrames) {
85
+ if (!_active) return void 0;
86
+ const raw = (/* @__PURE__ */ new Error()).stack;
87
+ if (!raw) return void 0;
88
+ const lines = raw.split("\n");
89
+ const target = lines[(lines[0] && lines[0].trim().startsWith("Error") ? 1 : 0) + 1 + skipFrames];
90
+ if (!target) return void 0;
91
+ return parseStackLine(target);
92
+ }
93
+ function parseStackLine(line) {
94
+ const v8Paren = line.match(/\(([^()]+):(\d+):(\d+)\)\s*$/);
95
+ if (v8Paren && v8Paren[1] && v8Paren[2] && v8Paren[3]) {
96
+ const file = v8Paren[1];
97
+ const lineN = Number.parseInt(v8Paren[2], 10);
98
+ const col = Number.parseInt(v8Paren[3], 10);
99
+ if (Number.isFinite(lineN) && Number.isFinite(col)) return {
100
+ file,
101
+ line: lineN,
102
+ col
103
+ };
104
+ }
105
+ const v8Bare = line.match(/at\s+([^\s()]+):(\d+):(\d+)\s*$/);
106
+ if (v8Bare && v8Bare[1] && v8Bare[2] && v8Bare[3]) {
107
+ const file = v8Bare[1];
108
+ const lineN = Number.parseInt(v8Bare[2], 10);
109
+ const col = Number.parseInt(v8Bare[3], 10);
110
+ if (Number.isFinite(lineN) && Number.isFinite(col)) return {
111
+ file,
112
+ line: lineN,
113
+ col
114
+ };
115
+ }
116
+ const jsc = line.match(/@([^@\s]+):(\d+):(\d+)\s*$/);
117
+ if (jsc && jsc[1] && jsc[2] && jsc[3]) {
118
+ const file = jsc[1];
119
+ const lineN = Number.parseInt(jsc[2], 10);
120
+ const col = Number.parseInt(jsc[3], 10);
121
+ if (Number.isFinite(lineN) && Number.isFinite(col)) return {
122
+ file,
123
+ line: lineN,
124
+ col
125
+ };
126
+ }
127
+ }
128
+ /**
129
+ * Register a signal/computed/effect node. `host` is the object carrying
130
+ * the `_s` subscriber Set (the signal read fn itself, or a computed's
131
+ * internal host). `sub` is the notify closure (`recompute`/`run`) whose
132
+ * identity appears in upstream `_s` Sets — used to resolve edges.
133
+ *
134
+ * @internal
135
+ */
136
+ function _rdRegister(node, kind, host, sub, label, loc) {
137
+ if (!_active) return void 0;
138
+ const id = _nextId++;
139
+ _byId.set(id, {
140
+ id,
141
+ kind,
142
+ name: label ?? `${kind === "signal" ? "signal" : kind}#${id}`,
143
+ ref: new WeakRef(node),
144
+ hostRef: host ? new WeakRef(host) : null,
145
+ fires: 0,
146
+ lastFire: null,
147
+ loc,
148
+ rate1s: 0
149
+ });
150
+ if (sub) _subId.set(sub, id);
151
+ _finalizer.register(node, id);
152
+ Object.defineProperty(node, "__pxRdId", {
153
+ value: id,
154
+ enumerable: false,
155
+ configurable: true
156
+ });
157
+ return id;
158
+ }
159
+ /**
160
+ * Record that a node fired (signal write / computed recompute / effect
161
+ * run). Bumps counters + appends to the bounded fire buffer.
162
+ *
163
+ * @internal
164
+ */
165
+ function _rdRecordFire(node) {
166
+ if (!_active) return;
167
+ const id = node.__pxRdId;
168
+ if (id === void 0) return;
169
+ const rec = _byId.get(id);
170
+ const ts = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
171
+ if (rec) {
172
+ rec.fires++;
173
+ if (rec.lastFire !== null) {
174
+ const dt = ts - rec.lastFire;
175
+ const decay = Math.exp(-dt / LPIH_RATE_TAU_MS);
176
+ rec.rate1s = rec.rate1s * decay + 1;
177
+ } else rec.rate1s = 1;
178
+ rec.lastFire = ts;
179
+ }
180
+ if (_fireBuf === null) _fireBuf = new Array(FIRE_CAP);
181
+ _fireBuf[_fireCount % FIRE_CAP] = {
182
+ id,
183
+ ts
184
+ };
185
+ _fireCount++;
186
+ }
187
+ function resolveSubId(sub) {
188
+ const direct = sub.__pxRdId;
189
+ if (direct !== void 0) return direct;
190
+ return _subId.get(sub);
191
+ }
192
+ /**
193
+ * Fresh snapshot of the live reactive graph. Edges are recomputed from
194
+ * each live node's current subscriber Set — always consistent with the
195
+ * framework's real subscription state, no incremental drift.
196
+ */
197
+ function getReactiveGraph() {
198
+ const nodes = [];
199
+ const edges = [];
200
+ for (const rec of _byId.values()) {
201
+ const node = rec.ref.deref();
202
+ if (!node) continue;
203
+ const subs = (rec.hostRef?.deref() ?? null)?._s ?? null;
204
+ const valueStr = rec.kind === "effect" ? "" : preview(node._v);
205
+ nodes.push({
206
+ id: rec.id,
207
+ kind: rec.kind,
208
+ name: rec.name,
209
+ value: valueStr,
210
+ subscribers: subs?.size ?? 0,
211
+ fires: rec.fires,
212
+ lastFire: rec.lastFire,
213
+ ...rec.loc ? { loc: rec.loc } : {}
214
+ });
215
+ if (subs) for (const cb of subs) {
216
+ const to = resolveSubId(cb);
217
+ if (to !== void 0) edges.push({
218
+ from: rec.id,
219
+ to
220
+ });
221
+ }
222
+ }
223
+ return {
224
+ nodes,
225
+ edges
226
+ };
227
+ }
228
+ /**
229
+ * Aggregate fire counts by source-location — powers Live Program Inlay
230
+ * Hints. Walks the live node registry, keys each node by its captured
231
+ * `loc`, and returns one summary per unique `file:line:col`. Nodes
232
+ * without a captured location are skipped (their fires are still
233
+ * visible via `getReactiveGraph()` and `getReactiveFires()` for the
234
+ * existing graph / timeline surfaces).
235
+ *
236
+ * Returns a fresh array, JSON-serializable, safe to ship across the
237
+ * devtools-host bridge or to write into an LSP cache file.
238
+ */
239
+ function getFireSummaries() {
240
+ const byKey = /* @__PURE__ */ new Map();
241
+ const nowTs = typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
242
+ for (const rec of _byId.values()) {
243
+ if (!rec.loc) continue;
244
+ if (!rec.ref.deref()) continue;
245
+ const k = `${rec.loc.file}:${rec.loc.line}:${rec.loc.col}`;
246
+ const decayedRate = rec.lastFire !== null ? rec.rate1s * Math.exp(-(nowTs - rec.lastFire) / LPIH_RATE_TAU_MS) : 0;
247
+ const existing = byKey.get(k);
248
+ if (existing) {
249
+ existing.count += rec.fires;
250
+ existing.rate1s += decayedRate;
251
+ if (rec.lastFire !== null && (existing.lastFire === null || rec.lastFire > existing.lastFire)) {
252
+ existing.lastFire = rec.lastFire;
253
+ existing.kind = rec.kind;
254
+ }
255
+ } else byKey.set(k, {
256
+ loc: rec.loc,
257
+ count: rec.fires,
258
+ lastFire: rec.lastFire,
259
+ kind: rec.kind,
260
+ rate1s: decayedRate
261
+ });
262
+ }
263
+ return [...byKey.values()];
264
+ }
265
+ /** Bounded recent-fire timeline (oldest → newest). Fresh copy. */
266
+ function getReactiveFires() {
267
+ if (_fireBuf === null || _fireCount === 0) return [];
268
+ if (_fireCount <= FIRE_CAP) return _fireBuf.slice(0, _fireCount);
269
+ const start = _fireCount % FIRE_CAP;
270
+ const out = [];
271
+ for (let i = 0; i < FIRE_CAP; i++) {
272
+ const e = _fireBuf[(start + i) % FIRE_CAP];
273
+ if (e) out.push(e);
274
+ }
275
+ return out;
276
+ }
277
+
278
+ //#endregion
279
+ export { deactivateReactiveDevtools as a, getReactiveGraph as c, activateReactiveDevtools as i, isReactiveDevtoolsActive as l, _rdRecordFire as n, getFireSummaries as o, _rdRegister as r, getReactiveFires as s, _captureCallerLocation as t };
280
+ //# sourceMappingURL=reactive-devtools-BCpGoGZ5.js.map
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"c60e3531-1","name":"batch.ts"},{"uid":"c60e3531-3","name":"cell.ts"},{"uid":"c60e3531-5","name":"reactive-devtools.ts"},{"uid":"c60e3531-7","name":"scope.ts"},{"uid":"c60e3531-9","name":"tracking.ts"},{"uid":"c60e3531-11","name":"effect.ts"},{"uid":"c60e3531-13","name":"computed.ts"},{"uid":"c60e3531-15","name":"createSelector.ts"},{"uid":"c60e3531-17","name":"debug.ts"},{"uid":"c60e3531-19","name":"reactive-trace.ts"},{"uid":"c60e3531-21","name":"signal.ts"},{"uid":"c60e3531-23","name":"store.ts"},{"uid":"c60e3531-25","name":"reconcile.ts"},{"uid":"c60e3531-27","name":"resource.ts"},{"uid":"c60e3531-29","name":"watch.ts"},{"uid":"c60e3531-31","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"c60e3531-1":{"renderedLength":3016,"gzipLength":1167,"brotliLength":0,"metaUid":"c60e3531-0"},"c60e3531-3":{"renderedLength":1636,"gzipLength":786,"brotliLength":0,"metaUid":"c60e3531-2"},"c60e3531-5":{"renderedLength":4438,"gzipLength":1940,"brotliLength":0,"metaUid":"c60e3531-4"},"c60e3531-7":{"renderedLength":3026,"gzipLength":1226,"brotliLength":0,"metaUid":"c60e3531-6"},"c60e3531-9":{"renderedLength":2227,"gzipLength":858,"brotliLength":0,"metaUid":"c60e3531-8"},"c60e3531-11":{"renderedLength":7605,"gzipLength":2433,"brotliLength":0,"metaUid":"c60e3531-10"},"c60e3531-13":{"renderedLength":4983,"gzipLength":1524,"brotliLength":0,"metaUid":"c60e3531-12"},"c60e3531-15":{"renderedLength":2244,"gzipLength":981,"brotliLength":0,"metaUid":"c60e3531-14"},"c60e3531-17":{"renderedLength":2469,"gzipLength":1092,"brotliLength":0,"metaUid":"c60e3531-16"},"c60e3531-19":{"renderedLength":2721,"gzipLength":1363,"brotliLength":0,"metaUid":"c60e3531-18"},"c60e3531-21":{"renderedLength":3535,"gzipLength":1513,"brotliLength":0,"metaUid":"c60e3531-20"},"c60e3531-23":{"renderedLength":5232,"gzipLength":1867,"brotliLength":0,"metaUid":"c60e3531-22"},"c60e3531-25":{"renderedLength":2278,"gzipLength":940,"brotliLength":0,"metaUid":"c60e3531-24"},"c60e3531-27":{"renderedLength":1205,"gzipLength":524,"brotliLength":0,"metaUid":"c60e3531-26"},"c60e3531-29":{"renderedLength":1249,"gzipLength":582,"brotliLength":0,"metaUid":"c60e3531-28"},"c60e3531-31":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"c60e3531-30"}},"nodeMetas":{"c60e3531-0":{"id":"/src/batch.ts","moduleParts":{"index.js":"c60e3531-1"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-20"},{"uid":"c60e3531-8"}]},"c60e3531-2":{"id":"/src/cell.ts","moduleParts":{"index.js":"c60e3531-3"},"imported":[],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-4":{"id":"/src/reactive-devtools.ts","moduleParts":{"index.js":"c60e3531-5"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-10"},{"uid":"c60e3531-20"}]},"c60e3531-6":{"id":"/src/scope.ts","moduleParts":{"index.js":"c60e3531-7"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-10"}]},"c60e3531-8":{"id":"/src/tracking.ts","moduleParts":{"index.js":"c60e3531-9"},"imported":[{"uid":"c60e3531-0"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-10"},{"uid":"c60e3531-26"},{"uid":"c60e3531-20"}]},"c60e3531-10":{"id":"/src/effect.ts","moduleParts":{"index.js":"c60e3531-11"},"imported":[{"uid":"c60e3531-4"},{"uid":"c60e3531-6"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-26"},{"uid":"c60e3531-28"}]},"c60e3531-12":{"id":"/src/computed.ts","moduleParts":{"index.js":"c60e3531-13"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-10"},{"uid":"c60e3531-4"},{"uid":"c60e3531-6"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-14":{"id":"/src/createSelector.ts","moduleParts":{"index.js":"c60e3531-15"},"imported":[{"uid":"c60e3531-10"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-16":{"id":"/src/debug.ts","moduleParts":{"index.js":"c60e3531-17"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-20"}]},"c60e3531-18":{"id":"/src/reactive-trace.ts","moduleParts":{"index.js":"c60e3531-19"},"imported":[],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-20"}]},"c60e3531-20":{"id":"/src/signal.ts","moduleParts":{"index.js":"c60e3531-21"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-16"},{"uid":"c60e3531-4"},{"uid":"c60e3531-18"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-26"},{"uid":"c60e3531-22"}]},"c60e3531-22":{"id":"/src/store.ts","moduleParts":{"index.js":"c60e3531-23"},"imported":[{"uid":"c60e3531-20"}],"importedBy":[{"uid":"c60e3531-30"},{"uid":"c60e3531-24"}]},"c60e3531-24":{"id":"/src/reconcile.ts","moduleParts":{"index.js":"c60e3531-25"},"imported":[{"uid":"c60e3531-22"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-26":{"id":"/src/resource.ts","moduleParts":{"index.js":"c60e3531-27"},"imported":[{"uid":"c60e3531-10"},{"uid":"c60e3531-20"},{"uid":"c60e3531-8"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-28":{"id":"/src/watch.ts","moduleParts":{"index.js":"c60e3531-29"},"imported":[{"uid":"c60e3531-10"}],"importedBy":[{"uid":"c60e3531-30"}]},"c60e3531-30":{"id":"/src/index.ts","moduleParts":{"index.js":"c60e3531-31"},"imported":[{"uid":"c60e3531-0"},{"uid":"c60e3531-2"},{"uid":"c60e3531-12"},{"uid":"c60e3531-14"},{"uid":"c60e3531-16"},{"uid":"c60e3531-4"},{"uid":"c60e3531-18"},{"uid":"c60e3531-10"},{"uid":"c60e3531-24"},{"uid":"c60e3531-26"},{"uid":"c60e3531-6"},{"uid":"c60e3531-20"},{"uid":"c60e3531-22"},{"uid":"c60e3531-8"},{"uid":"c60e3531-28"}],"importedBy":[],"isEntry":true}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"8f0a5281-1","name":"batch.ts"},{"uid":"8f0a5281-3","name":"cell.ts"},{"uid":"8f0a5281-5","name":"scope.ts"},{"uid":"8f0a5281-7","name":"tracking.ts"},{"uid":"8f0a5281-9","name":"effect.ts"},{"uid":"8f0a5281-11","name":"computed.ts"},{"uid":"8f0a5281-13","name":"createSelector.ts"},{"uid":"8f0a5281-15","name":"debug.ts"},{"uid":"8f0a5281-17","name":"reactive-trace.ts"},{"uid":"8f0a5281-19","name":"signal.ts"},{"uid":"8f0a5281-21","name":"store.ts"},{"uid":"8f0a5281-23","name":"reconcile.ts"},{"uid":"8f0a5281-25","name":"resource.ts"},{"uid":"8f0a5281-27","name":"watch.ts"},{"uid":"8f0a5281-29","name":"index.ts"}]}]},{"name":"lpih.js","children":[{"name":"src/lpih.ts","uid":"8f0a5281-31"}]},{"name":"_chunks/reactive-devtools-BCpGoGZ5.js","children":[{"name":"src/reactive-devtools.ts","uid":"8f0a5281-33"}]}],"isRoot":true},"nodeParts":{"8f0a5281-1":{"renderedLength":3016,"gzipLength":1167,"brotliLength":0,"metaUid":"8f0a5281-0"},"8f0a5281-3":{"renderedLength":1636,"gzipLength":786,"brotliLength":0,"metaUid":"8f0a5281-2"},"8f0a5281-5":{"renderedLength":3026,"gzipLength":1226,"brotliLength":0,"metaUid":"8f0a5281-4"},"8f0a5281-7":{"renderedLength":2227,"gzipLength":858,"brotliLength":0,"metaUid":"8f0a5281-6"},"8f0a5281-9":{"renderedLength":7697,"gzipLength":2469,"brotliLength":0,"metaUid":"8f0a5281-8"},"8f0a5281-11":{"renderedLength":5143,"gzipLength":1570,"brotliLength":0,"metaUid":"8f0a5281-10"},"8f0a5281-13":{"renderedLength":2244,"gzipLength":981,"brotliLength":0,"metaUid":"8f0a5281-12"},"8f0a5281-15":{"renderedLength":2469,"gzipLength":1092,"brotliLength":0,"metaUid":"8f0a5281-14"},"8f0a5281-17":{"renderedLength":2721,"gzipLength":1363,"brotliLength":0,"metaUid":"8f0a5281-16"},"8f0a5281-19":{"renderedLength":3643,"gzipLength":1554,"brotliLength":0,"metaUid":"8f0a5281-18"},"8f0a5281-21":{"renderedLength":5232,"gzipLength":1867,"brotliLength":0,"metaUid":"8f0a5281-20"},"8f0a5281-23":{"renderedLength":2278,"gzipLength":940,"brotliLength":0,"metaUid":"8f0a5281-22"},"8f0a5281-25":{"renderedLength":1205,"gzipLength":524,"brotliLength":0,"metaUid":"8f0a5281-24"},"8f0a5281-27":{"renderedLength":1249,"gzipLength":582,"brotliLength":0,"metaUid":"8f0a5281-26"},"8f0a5281-29":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"8f0a5281-28"},"8f0a5281-31":{"renderedLength":6380,"gzipLength":2579,"brotliLength":0,"metaUid":"8f0a5281-30"},"8f0a5281-33":{"renderedLength":8588,"gzipLength":3409,"brotliLength":0,"metaUid":"8f0a5281-32"}},"nodeMetas":{"8f0a5281-0":{"id":"/src/batch.ts","moduleParts":{"index.js":"8f0a5281-1"},"imported":[],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-10"},{"uid":"8f0a5281-18"},{"uid":"8f0a5281-6"}]},"8f0a5281-2":{"id":"/src/cell.ts","moduleParts":{"index.js":"8f0a5281-3"},"imported":[],"importedBy":[{"uid":"8f0a5281-28"}]},"8f0a5281-4":{"id":"/src/scope.ts","moduleParts":{"index.js":"8f0a5281-5"},"imported":[],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-10"},{"uid":"8f0a5281-8"}]},"8f0a5281-6":{"id":"/src/tracking.ts","moduleParts":{"index.js":"8f0a5281-7"},"imported":[{"uid":"8f0a5281-0"}],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-10"},{"uid":"8f0a5281-12"},{"uid":"8f0a5281-8"},{"uid":"8f0a5281-24"},{"uid":"8f0a5281-18"}]},"8f0a5281-8":{"id":"/src/effect.ts","moduleParts":{"index.js":"8f0a5281-9"},"imported":[{"uid":"8f0a5281-32"},{"uid":"8f0a5281-4"},{"uid":"8f0a5281-6"}],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-10"},{"uid":"8f0a5281-12"},{"uid":"8f0a5281-24"},{"uid":"8f0a5281-26"}]},"8f0a5281-10":{"id":"/src/computed.ts","moduleParts":{"index.js":"8f0a5281-11"},"imported":[{"uid":"8f0a5281-0"},{"uid":"8f0a5281-8"},{"uid":"8f0a5281-32"},{"uid":"8f0a5281-4"},{"uid":"8f0a5281-6"}],"importedBy":[{"uid":"8f0a5281-28"}]},"8f0a5281-12":{"id":"/src/createSelector.ts","moduleParts":{"index.js":"8f0a5281-13"},"imported":[{"uid":"8f0a5281-8"},{"uid":"8f0a5281-6"}],"importedBy":[{"uid":"8f0a5281-28"}]},"8f0a5281-14":{"id":"/src/debug.ts","moduleParts":{"index.js":"8f0a5281-15"},"imported":[],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-18"}]},"8f0a5281-16":{"id":"/src/reactive-trace.ts","moduleParts":{"index.js":"8f0a5281-17"},"imported":[],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-18"}]},"8f0a5281-18":{"id":"/src/signal.ts","moduleParts":{"index.js":"8f0a5281-19"},"imported":[{"uid":"8f0a5281-0"},{"uid":"8f0a5281-14"},{"uid":"8f0a5281-32"},{"uid":"8f0a5281-16"},{"uid":"8f0a5281-6"}],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-24"},{"uid":"8f0a5281-20"}]},"8f0a5281-20":{"id":"/src/store.ts","moduleParts":{"index.js":"8f0a5281-21"},"imported":[{"uid":"8f0a5281-18"}],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-22"}]},"8f0a5281-22":{"id":"/src/reconcile.ts","moduleParts":{"index.js":"8f0a5281-23"},"imported":[{"uid":"8f0a5281-20"}],"importedBy":[{"uid":"8f0a5281-28"}]},"8f0a5281-24":{"id":"/src/resource.ts","moduleParts":{"index.js":"8f0a5281-25"},"imported":[{"uid":"8f0a5281-8"},{"uid":"8f0a5281-18"},{"uid":"8f0a5281-6"}],"importedBy":[{"uid":"8f0a5281-28"}]},"8f0a5281-26":{"id":"/src/watch.ts","moduleParts":{"index.js":"8f0a5281-27"},"imported":[{"uid":"8f0a5281-8"}],"importedBy":[{"uid":"8f0a5281-28"}]},"8f0a5281-28":{"id":"/src/index.ts","moduleParts":{"index.js":"8f0a5281-29"},"imported":[{"uid":"8f0a5281-0"},{"uid":"8f0a5281-2"},{"uid":"8f0a5281-10"},{"uid":"8f0a5281-12"},{"uid":"8f0a5281-14"},{"uid":"8f0a5281-32"},{"uid":"8f0a5281-16"},{"uid":"8f0a5281-8"},{"uid":"8f0a5281-22"},{"uid":"8f0a5281-24"},{"uid":"8f0a5281-4"},{"uid":"8f0a5281-18"},{"uid":"8f0a5281-20"},{"uid":"8f0a5281-6"},{"uid":"8f0a5281-26"}],"importedBy":[],"isEntry":true},"8f0a5281-30":{"id":"/src/lpih.ts","moduleParts":{"lpih.js":"8f0a5281-31"},"imported":[{"uid":"8f0a5281-32"},{"uid":"8f0a5281-34","dynamic":true}],"importedBy":[],"isEntry":true},"8f0a5281-32":{"id":"/src/reactive-devtools.ts","moduleParts":{"_chunks/reactive-devtools-BCpGoGZ5.js":"8f0a5281-33"},"imported":[],"importedBy":[{"uid":"8f0a5281-28"},{"uid":"8f0a5281-10"},{"uid":"8f0a5281-8"},{"uid":"8f0a5281-18"},{"uid":"8f0a5281-30"}]},"8f0a5281-34":{"id":"node:fs/promises","moduleParts":{},"imported":[],"importedBy":[{"uid":"8f0a5281-30"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;