@blac/react 2.0.17 → 2.0.19

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
@@ -4,6 +4,12 @@ React bindings for BlaC — `useBloc` hook with proxy-based automatic re-render
4
4
 
5
5
  **[Documentation](https://blac-docs.pages.dev/react/getting-started)** · **[npm](https://www.npmjs.com/package/@blac/react)**
6
6
 
7
+ > [!WARNING]
8
+ > **BlaC v2 is in pre-release (beta).** While in beta, **breaking API changes may
9
+ > ship in patch releases** without a major version bump. Pin an exact version and
10
+ > check the changelog before upgrading. Strict semver resumes once v2 is officially
11
+ > out of beta.
12
+
7
13
  ## Installation
8
14
 
9
15
  ```bash
@@ -38,6 +44,53 @@ function Counter() {
38
44
  }
39
45
  ```
40
46
 
47
+ ## Reactivity model
48
+
49
+ BlaC follows one rule:
50
+
51
+ > **A component becomes reactive only to the paths it reads during its own render.**
52
+
53
+ `useBloc` returns a proxy that records which paths you read; the component
54
+ re-renders only when one of *those* paths changes. Reads outside render — effects,
55
+ event handlers, `setTimeout`, `await` — record nothing.
56
+
57
+ ### Props are snapshots — `untracked()`
58
+
59
+ A tracking proxy stays bound to the component that *created* it. If a parent passes
60
+ a proxied value to a child and the child reads from it, that read is attributed to
61
+ the **parent** — so the parent over-subscribes and re-renders for paths only the
62
+ child uses.
63
+
64
+ Pass the value through `untracked()` to hand the child a detached snapshot instead:
65
+
66
+ ```tsx
67
+ import { useBloc, untracked } from '@blac/react';
68
+
69
+ function List() {
70
+ const [state] = useBloc(TodoBloc);
71
+ // The parent reads each id (for keys). Rows read `.label` etc. off the
72
+ // snapshot, which is detached — those reads are NOT attributed to List.
73
+ return (
74
+ <ul>
75
+ {state.items.map((item) => (
76
+ <Row key={item.id} item={untracked(item)} />
77
+ ))}
78
+ </ul>
79
+ );
80
+ }
81
+ ```
82
+
83
+ The trade-off is intentional and React-like: a component that receives an
84
+ `untracked()` value is **not** reactive to it — a change triggers nothing, memo or
85
+ not. If no component reads a path during its *own* render, a change to that path
86
+ re-renders nothing. To react to data, a component must be a consumer itself (call
87
+ its own `useBloc`). Nobody up the tree re-renders for a path unless they read it
88
+ themselves.
89
+
90
+ > Making this automatic — per-component read scoping so `untracked()` isn't needed
91
+ > by hand — is a planned compiler/Babel plugin. See
92
+ > [`plans/blac-ambient-tracking/design.md`](../../plans/blac-ambient-tracking/design.md).
93
+
41
94
  ## useBloc
42
95
 
43
96
  ```tsx
@@ -88,11 +141,11 @@ function UserCard({ userId }: { userId: string }) {
88
141
  - **Required when declared** — omitting `args` or passing the wrong shape is a type error.
89
142
  - **Drives identity** — different `args` ⇒ different instance. Same `args` ⇒ same instance (dev-warn if args mismatch on same-keyed second call).
90
143
  - **Serializable only** — non-serializable values (refs, callbacks) belong in the `deps` lane (below).
91
- - **Per-component private instances** — pass a per-mount `instanceId` (keyed by React's `useId()`) to give each mount its own instance, disposed on unmount:
144
+ - **Per-component private instances** — embed a per-mount unique ID inside `args` (using React's `useId()`) to give each mount its own instance, disposed on unmount:
92
145
 
93
146
  ```tsx
94
- const instanceId = useId();
95
- const [state, cubit] = useBloc(FormCubit, { args: options, instanceId });
147
+ const id = useId();
148
+ const [state, cubit] = useBloc(FormCubit, { args: { ...options, _id: id } });
96
149
  ```
97
150
 
98
151
  ### `deps`: Non-Serializable Refs and Callbacks
@@ -204,24 +257,29 @@ const [state] = useBloc(CounterCubit, {
204
257
 
205
258
  ### Options
206
259
 
207
- | Option | Type | Description |
208
- | ------------ | ---------------------------- | -------------------------------------------------------------------------------- |
209
- | `args` | `Args` type | Required when bloc declares `Args != void`; forbidden when `void` |
210
- | `select` | `(state, bloc) => unknown[]` | Manual dependency selector (renamed from `dependencies`); disables auto-tracking |
211
- | `instanceId` | `string \| number` | Explicit identity key; pass `useId()` for a per-mount instance |
212
- | `onMount` | `(bloc) => void` | Called when component mounts |
213
- | `onUnmount` | `(bloc) => void` | Called when component unmounts |
260
+ | Option | Type | Description |
261
+ | ----------- | ---------------------------- | -------------------------------------------------------------------------------- |
262
+ | `args` | `Args` type | Required when bloc declares `Args != void`; forbidden when `void` |
263
+ | `select` | `(state, bloc) => unknown[]` | Manual dependency selector (renamed from `dependencies`); disables auto-tracking |
264
+ | `onMount` | `(bloc) => void` | Called when component mounts |
265
+ | `onUnmount` | `(bloc) => void` | Called when component unmounts |
214
266
 
215
- > Auto-tracking is always on when `select` is omitted — it is **not** a configurable option. `deps` and `autoInstance` are not `useBloc` options either: wire deps from a mount effect (`APPLY_DEPS` / `REMOVE_DEPS_OWNER`), and use `instanceId: useId()` for per-mount instances.
267
+ > Auto-tracking is always on when `select` is omitted — it is **not** a configurable option. `deps`, `autoInstance`, and `instanceId` are not `useBloc` options: wire deps from a mount effect (`APPLY_DEPS` / `REMOVE_DEPS_OWNER`); for per-mount private instances, embed a stable unique ID in `args` (e.g. `{ args: { _id: useId() } }`).
216
268
 
217
269
  ### Identity and Keying
218
270
 
219
271
  Instance identity is resolved in precedence order:
220
272
 
221
- 1. **Explicit `instanceId`** — hard override (pass `useId()` for a per-mount private instance)
222
- 2. **`<BlocProvider>` context id** — inherited from an ancestor provider
223
- 3. **`static key(args)` → structural hash of `args`** — default when the bloc declares `Args` (`static key` wins if defined; otherwise a stable hash of all `args`)
224
- 4. **`'default'`** — singleton fallback
273
+ 1. **`<BlocProvider>` context id** — inherited from an ancestor provider
274
+ 2. **`static key(args)` → structural hash of `args`** — default when the bloc declares `Args` (`static key` wins if defined; otherwise a stable hash of all `args`)
275
+ 3. **`'default'`** — singleton fallback
276
+
277
+ For a per-mount private instance, embed a stable unique ID inside `args` so each mount hashes to a distinct key:
278
+
279
+ ```tsx
280
+ const id = useId();
281
+ useBloc(FormCubit, { args: { ...options, _id: id } });
282
+ ```
225
283
 
226
284
  Blocs declare explicit identity via a static class property:
227
285
 
@@ -237,24 +295,24 @@ class DocumentCubit extends Cubit<
237
295
 
238
296
  ### Instance Sharing and Lifecycle
239
297
 
240
- By default, all components using `useBloc(MyBloc)` with the same identity share one instance. Pass a per-mount `instanceId` (via `useId()`) for per-component private instances:
298
+ By default, all components using `useBloc(MyBloc)` with the same identity share one instance. For per-component private instances, embed a unique ID in `args` so each mount derives a distinct key:
241
299
 
242
300
  ```tsx
243
301
  // All users with userId=123 share one instance
244
302
  useBloc(UserCardCubit, { args: { userId: 123 } });
245
303
 
246
304
  // Each component mount gets its own instance, disposed on unmount
247
- const instanceId = useId();
248
- useBloc(FormCubit, { args: options, instanceId });
305
+ const id = useId();
306
+ useBloc(FormCubit, { args: { ...options, _id: id } });
249
307
 
250
- // Explicit id (escape hatch for non-derivable identity)
251
- useBloc(EditorCubit, { instanceId: 'editor-1' });
308
+ // Explicit stable key (escape hatch for non-derivable identity)
309
+ useBloc(EditorCubit, { args: { _id: 'editor-1' } });
252
310
  ```
253
311
 
254
312
  ### Breaking Changes (v2)
255
313
 
256
314
  - **`dependencies` option renamed to `select`** — avoids confusion with the `deps` (non-serializable handles) lane.
257
- - **`autoTrack`, `autoInstance`, and `deps` are no longer `useBloc` options** — auto-tracking is always on (opt out per-consumer with `select`); per-mount instances use `instanceId: useId()`; deps are wired from a mount effect via `APPLY_DEPS` / `REMOVE_DEPS_OWNER`.
315
+ - **`autoTrack`, `autoInstance`, `instanceId`, and `deps` are no longer `useBloc` options** — auto-tracking is always on (opt out per-consumer with `select`); per-mount private instances embed a unique ID in `args`; deps are wired from a mount effect via `APPLY_DEPS` / `REMOVE_DEPS_OWNER`.
258
316
  - **Zero-arg constructor + `init(args)` lifecycle** — all blocs now use `new Type()` with no constructor args. Blocs that declare `Args` receive them via `init(args)` called by the framework before the first state snapshot.
259
317
  - **`args` is required when declared, forbidden when void** — enforced by the type system; no runtime guard needed.
260
318
 
package/dist/index.cjs CHANGED
@@ -112,6 +112,7 @@ function buildTrackedProxy(instance, trackedStateRef, onDepHandle) {
112
112
  //#endregion
113
113
  //#region src/useBloc.ts
114
114
  let nextConsumerId = 0;
115
+ const ARGS_UNSET = Symbol("blac.argsKeyUnset");
115
116
  const primaryRefId = (consumerId) => `useBloc@${consumerId}`;
116
117
  const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
117
118
  /**
@@ -125,7 +126,7 @@ const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
125
126
  * {@link trackRender} + the container's path-scoped `DirtyChannel`.
126
127
  * - **Manual select**: pass `options.select` to opt out of auto-tracking.
127
128
  * The hook re-renders only when the returned array's elements change
128
- * (per-index `Object.is`). This replaces the v1 `dependencies` option.
129
+ * (per-index `Object.is`).
129
130
  *
130
131
  * Lifecycle:
131
132
  * - The bloc is acquired from the registry on mount and released on
@@ -152,7 +153,7 @@ const depRefId = (consumerId) => `useBloc@${consumerId}:dep`;
152
153
  * const [state, bloc] = useBloc(MyBloc);
153
154
  * ```
154
155
  *
155
- * @example Manual select (replaces v1 `dependencies`)
156
+ * @example Manual select
156
157
  * ```ts
157
158
  * const [state, bloc] = useBloc(MyBloc, {
158
159
  * select: (state) => [state.count],
@@ -179,30 +180,48 @@ function useBloc(BlocClass, options) {
179
180
  const ownArgs = options?.args;
180
181
  const ownArgsRef = (0, react.useRef)(ownArgs);
181
182
  ownArgsRef.current = ownArgs;
182
- const ownArgsKey = ownArgs === void 0 ? void 0 : JSON.stringify(ownArgs);
183
+ const ownArgsKeyRef = (0, react.useRef)({
184
+ ref: ARGS_UNSET,
185
+ key: void 0
186
+ });
187
+ if (!Object.is(ownArgsKeyRef.current.ref, ownArgs)) ownArgsKeyRef.current = {
188
+ ref: ownArgs,
189
+ key: ownArgs === void 0 ? void 0 : JSON.stringify(ownArgs)
190
+ };
191
+ const ownArgsKey = ownArgsKeyRef.current.key;
183
192
  const providerArgs = useProvidedArgs(BlocClass);
184
193
  const providerArgsRef = (0, react.useRef)(providerArgs);
185
194
  providerArgsRef.current = providerArgs;
186
- const providerArgsKey = providerArgs === void 0 ? void 0 : JSON.stringify(providerArgs);
195
+ const providerArgsKeyRef = (0, react.useRef)({
196
+ ref: ARGS_UNSET,
197
+ key: void 0
198
+ });
199
+ if (!Object.is(providerArgsKeyRef.current.ref, providerArgs)) providerArgsKeyRef.current = {
200
+ ref: providerArgs,
201
+ key: providerArgs === void 0 ? void 0 : JSON.stringify(providerArgs)
202
+ };
203
+ const providerArgsKey = providerArgsKeyRef.current.key;
187
204
  const trackedStateRef = (0, react.useRef)(null);
188
205
  const sessionRef = (0, react.useRef)(/* @__PURE__ */ new Map());
189
206
  const depSubsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
190
207
  const depWrapperCacheRef = (0, react.useRef)(/* @__PURE__ */ new Map());
208
+ const proxyCacheRef = (0, react.useRef)(new _dirtytalk_structural.ProxyCache());
209
+ const lastReconcileRef = (0, react.useRef)(null);
210
+ const [rebindNonce, bumpRebind] = (0, react.useReducer)((x) => x + 1, 0);
211
+ const ownedBlocRef = (0, react.useRef)(null);
191
212
  const { bloc, instanceKey, trackedBloc } = (0, react.useMemo)(() => {
192
213
  const effectiveArgs = ownArgsRef.current !== void 0 ? ownArgsRef.current : providerArgsRef.current;
193
214
  const resolvedKey = (0, _blac_core.resolveInstanceKey)(BlocClass, effectiveArgs);
194
- const refId = primaryRefId(consumerId);
195
215
  const instance = (0, _blac_core.getRegistry)().acquire(BlocClass, resolvedKey, {
196
216
  canCreate: true,
197
- countRef: true,
198
- refId,
217
+ countRef: false,
199
218
  args: effectiveArgs
200
219
  });
201
220
  const onDepHandle = (handle) => {
202
221
  const cache = depWrapperCacheRef.current;
203
222
  const cached = cache.get(handle);
204
223
  if (cached !== void 0) return cached;
205
- const wrapper = makeDepWrapper(handle, consumerId, trackedStateRef, sessionRef, depSubsRef, onDepHandle);
224
+ const wrapper = makeDepWrapper(handle, consumerId, trackedStateRef, sessionRef, onDepHandle);
206
225
  cache.set(handle, wrapper);
207
226
  return wrapper;
208
227
  };
@@ -215,47 +234,80 @@ function useBloc(BlocClass, options) {
215
234
  }, [
216
235
  BlocClass,
217
236
  ownArgsKey,
218
- providerArgsKey
237
+ providerArgsKey,
238
+ rebindNonce
219
239
  ]);
220
240
  const [, force] = (0, react.useReducer)((x) => x + 1, 0);
221
241
  const pathRef = (0, react.useRef)((0, _dirtytalk_structural.emptyPathSet)());
222
242
  const expandedInterestRef = (0, react.useRef)((0, _dirtytalk_structural.emptyPathSet)());
223
243
  const lastSelectionRef = (0, react.useRef)(null);
244
+ const renderStateRef = (0, react.useRef)(void 0);
245
+ const prevBlocRef = (0, react.useRef)(null);
224
246
  (0, react.useEffect)(() => {
225
247
  const channel = bloc.channel;
226
- if (selectRef.current !== void 0) return channel.subscribe(() => _dirtytalk_structural.ALL_PATHS, () => {
227
- const select = selectRef.current;
228
- if (!select) {
248
+ if (selectRef.current !== void 0) {
249
+ const unsub = channel.subscribe(() => _dirtytalk_structural.ALL_PATHS, () => {
250
+ const select = selectRef.current;
251
+ if (!select) {
252
+ force();
253
+ return;
254
+ }
255
+ const next = select(bloc.state, bloc);
256
+ const prev = lastSelectionRef.current;
257
+ if (prev !== null && shallowArrayEqual(prev, next)) return;
258
+ lastSelectionRef.current = next;
229
259
  force();
230
- return;
260
+ });
261
+ const select = selectRef.current;
262
+ if (select) {
263
+ const next = select(bloc.state, bloc);
264
+ const prev = lastSelectionRef.current;
265
+ if (prev === null || !shallowArrayEqual(prev, next)) {
266
+ lastSelectionRef.current = next;
267
+ force();
268
+ }
231
269
  }
232
- const next = select(bloc.state, bloc);
233
- const prev = lastSelectionRef.current;
234
- if (prev !== null && shallowArrayEqual(prev, next)) return;
235
- lastSelectionRef.current = next;
236
- force();
237
- });
270
+ return unsub;
271
+ }
238
272
  const unsub = channel.subscribe(() => expandedInterestRef.current, () => force());
239
273
  bloc.registerConsumerPaths(consumerId, pathRef.current);
274
+ if (bloc.state !== renderStateRef.current) force();
240
275
  return () => {
241
276
  unsub();
242
277
  bloc.unregisterConsumer(consumerId);
243
278
  };
244
279
  }, [bloc, consumerId]);
245
- (0, react.useEffect)(() => {
246
- onMountRef.current?.(bloc);
280
+ (0, react.useLayoutEffect)(() => {
281
+ const registry = (0, _blac_core.getRegistry)();
282
+ const live = registry.acquire(BlocClass, instanceKey, {
283
+ canCreate: true,
284
+ countRef: true,
285
+ refId: primaryRefId(consumerId)
286
+ });
287
+ ownedBlocRef.current = live;
288
+ onMountRef.current?.(live);
289
+ if (live !== bloc) bumpRebind();
247
290
  return () => {
248
- onUnmountRef.current?.(bloc);
249
- (0, _blac_core.getRegistry)().release(BlocClass, instanceKey, false, primaryRefId(consumerId));
291
+ onUnmountRef.current?.(ownedBlocRef.current ?? bloc);
292
+ registry.release(BlocClass, instanceKey, false, primaryRefId(consumerId));
250
293
  };
251
- }, [bloc, instanceKey]);
294
+ }, [
295
+ BlocClass,
296
+ instanceKey,
297
+ consumerId
298
+ ]);
252
299
  const rawState = bloc.state;
300
+ renderStateRef.current = rawState;
301
+ if (prevBlocRef.current !== bloc) {
302
+ prevBlocRef.current = bloc;
303
+ lastSelectionRef.current = null;
304
+ }
253
305
  let state;
254
306
  if (selectRef.current !== void 0) {
255
307
  state = rawState;
256
308
  if (lastSelectionRef.current === null) lastSelectionRef.current = selectRef.current(rawState, bloc);
257
309
  } else {
258
- const tracked = (0, _dirtytalk_structural.trackRender)(rawState, bloc.interner);
310
+ const tracked = (0, _dirtytalk_structural.trackRender)(rawState, bloc.interner, proxyCacheRef.current);
259
311
  state = tracked.value;
260
312
  trackedStateRef.current = tracked.value;
261
313
  pathRef.current = tracked.paths;
@@ -265,16 +317,33 @@ function useBloc(BlocClass, options) {
265
317
  kind: "primary",
266
318
  paths: tracked.paths
267
319
  });
320
+ queueMicrotask(tracked.disarm);
268
321
  }
269
322
  (0, react.useLayoutEffect)(() => {
270
323
  trackedStateRef.current = null;
271
- if (selectRef.current !== void 0) return;
324
+ if (selectRef.current !== void 0) {
325
+ lastReconcileRef.current = null;
326
+ return;
327
+ }
272
328
  const container = bloc;
273
329
  const paths = pathRef.current;
330
+ const session = sessionRef.current;
331
+ const last = lastReconcileRef.current;
332
+ if (last !== null && last.primaryContainer === container && (0, _dirtytalk_structural.pathSetEquals)(last.primaryPaths, paths)) {
333
+ let unchanged = last.deps.size === session.size - 1;
334
+ if (unchanged) for (const [depContainer, entry] of session) {
335
+ if (entry.kind === "primary") continue;
336
+ const prevEntry = last.deps.get(depContainer);
337
+ if (prevEntry === void 0 || prevEntry.key !== entry.key || prevEntry.refId !== entry.refId || !Object.is(prevEntry.args, entry.args) || !(0, _dirtytalk_structural.pathSetEquals)(prevEntry.paths, entry.paths)) {
338
+ unchanged = false;
339
+ break;
340
+ }
341
+ }
342
+ if (unchanged) return;
343
+ }
274
344
  container.registerConsumerPaths(consumerId, paths);
275
345
  expandedInterestRef.current = expandWithAncestors(paths, container.interner);
276
346
  const subs = depSubsRef.current;
277
- const session = sessionRef.current;
278
347
  for (const [depContainer, sub] of subs) if (!session.has(depContainer)) {
279
348
  sub.unsubscribe();
280
349
  depContainer.unregisterConsumer(consumerId);
@@ -290,6 +359,12 @@ function useBloc(BlocClass, options) {
290
359
  existing.interestRef.current = interest;
291
360
  continue;
292
361
  }
362
+ (0, _blac_core.getRegistry)().acquire(entry.Type, entry.key, {
363
+ canCreate: true,
364
+ countRef: true,
365
+ refId: entry.refId,
366
+ args: entry.args
367
+ });
293
368
  const interestRef = { current: interest };
294
369
  const unsubscribe = depContainer.channel.subscribe(() => interestRef.current, () => force());
295
370
  subs.set(depContainer, {
@@ -297,9 +372,25 @@ function useBloc(BlocClass, options) {
297
372
  interestRef,
298
373
  Type: entry.Type,
299
374
  key: entry.key,
300
- refId: entry.refId
375
+ refId: entry.refId,
376
+ args: entry.args
377
+ });
378
+ }
379
+ const depsSignature = /* @__PURE__ */ new Map();
380
+ for (const [depContainer, entry] of session) {
381
+ if (entry.kind === "primary") continue;
382
+ depsSignature.set(depContainer, {
383
+ paths: entry.paths,
384
+ key: entry.key,
385
+ refId: entry.refId,
386
+ args: entry.args
301
387
  });
302
388
  }
389
+ lastReconcileRef.current = {
390
+ primaryContainer: container,
391
+ primaryPaths: paths,
392
+ deps: depsSignature
393
+ };
303
394
  });
304
395
  (0, react.useEffect)(() => {
305
396
  const subs = depSubsRef.current;
@@ -323,10 +414,11 @@ function useBloc(BlocClass, options) {
323
414
  * tracked getter's `this`. The wrapper exposes the same accessors as the core
324
415
  * handle and overrides `.track()`:
325
416
  *
326
- * - **Inside a render** (`trackedStateRef.current != null`): resolve the dep,
327
- * take a refcount (once per consumer), `trackRender` its state, merge the
328
- * recorded paths into the session entry, build/reuse a tracked proxy for the
329
- * dep so its OWN getters track too, and return `[trackedValue, depProxy]`.
417
+ * - **Inside a render** (`trackedStateRef.current != null`): resolve (ENSURE,
418
+ * no ref) the dep, `trackRender` its state, merge the recorded paths into the
419
+ * session entry, build/reuse a tracked proxy for the dep so its OWN getters
420
+ * track too, and return `[trackedValue, depProxy]`. The ownership ref is taken
421
+ * by the layout-effect reconcile pass, not here.
330
422
  * - **Outside a render**: degrade to live `[dep.state, dep]` — matches the core
331
423
  * base impl, safe in event handlers/effects/methods.
332
424
  *
@@ -339,11 +431,12 @@ function useBloc(BlocClass, options) {
339
431
  * the dep already has a non-primary session entry this render, reuse its proxy
340
432
  * + union its paths instead of re-acquiring.
341
433
  */
342
- function makeDepWrapper(handle, consumerId, trackedStateRef, sessionRef, depSubsRef, onDepHandle) {
434
+ function makeDepWrapper(handle, consumerId, trackedStateRef, sessionRef, onDepHandle) {
343
435
  const brand = handle[_blac_core.DEP_BRAND];
344
436
  const refId = depRefId(consumerId);
345
437
  const registry = (0, _blac_core.getRegistry)();
346
438
  const perDep = /* @__PURE__ */ new Map();
439
+ const proxyCache = new _dirtytalk_structural.ProxyCache();
347
440
  const resolve = (options) => {
348
441
  const args = options?.args ?? brand.defaultArgs;
349
442
  const key = registry.resolveKey(brand.Type, void 0, args);
@@ -360,13 +453,7 @@ function makeDepWrapper(handle, consumerId, trackedStateRef, sessionRef, depSubs
360
453
  if (trackedStateRef.current == null) return [dep.state, dep];
361
454
  const session = sessionRef.current;
362
455
  const existing = session.get(dep);
363
- if (existing === void 0 && !depSubsRef.current.has(dep)) registry.acquire(brand.Type, key, {
364
- canCreate: true,
365
- countRef: true,
366
- refId,
367
- args
368
- });
369
- const tracked = (0, _dirtytalk_structural.trackRender)(dep.state, dep.interner);
456
+ const tracked = (0, _dirtytalk_structural.trackRender)(dep.state, dep.interner, proxyCache);
370
457
  let cache = perDep.get(dep);
371
458
  if (cache === void 0) {
372
459
  const ref = { current: tracked.value };
@@ -382,7 +469,8 @@ function makeDepWrapper(handle, consumerId, trackedStateRef, sessionRef, depSubs
382
469
  paths: tracked.paths,
383
470
  Type: brand.Type,
384
471
  key,
385
- refId
472
+ refId,
473
+ args
386
474
  });
387
475
  return [tracked.value, cache.proxy];
388
476
  }
@@ -461,6 +549,12 @@ function configureBlacReact(config) {
461
549
  //#endregion
462
550
  exports.BlocProvider = BlocProvider;
463
551
  exports.configureBlacReact = configureBlacReact;
552
+ Object.defineProperty(exports, "untracked", {
553
+ enumerable: true,
554
+ get: function() {
555
+ return _dirtytalk_structural.untracked;
556
+ }
557
+ });
464
558
  exports.useBloc = useBloc;
465
559
  exports.useProvidedArgs = useProvidedArgs;
466
560