@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.9

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.
Files changed (134) hide show
  1. package/dist/dev-shared.js +6357 -0
  2. package/dist/dev.attribution.js +2485 -0
  3. package/dist/dev.js +2851 -7027
  4. package/dist/observe/affects.js +129 -0
  5. package/dist/observe/attribution.js +7 -0
  6. package/dist/observe/boundaries.js +641 -0
  7. package/dist/observe/core/action.js +177 -0
  8. package/dist/observe/core/async.js +846 -0
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +282 -0
  11. package/dist/observe/core/attribution-hooks.js +82 -0
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +2095 -0
  14. package/dist/observe/core/constants.js +248 -0
  15. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  16. package/dist/observe/core/core.js +1789 -0
  17. package/dist/observe/core/dev.js +293 -0
  18. package/dist/observe/core/effect.js +195 -0
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/error.js +80 -0
  21. package/dist/observe/core/external.js +98 -0
  22. package/dist/observe/core/graph.js +155 -0
  23. package/dist/observe/core/heap.js +147 -0
  24. package/dist/observe/core/invariants.js +45 -0
  25. package/dist/observe/core/lanes.js +205 -0
  26. package/dist/observe/core/optimistic.js +585 -0
  27. package/dist/observe/core/owner.js +322 -0
  28. package/dist/observe/core/scheduler.js +1428 -0
  29. package/dist/observe/core/verdict.js +669 -0
  30. package/dist/observe/index.js +59 -0
  31. package/dist/observe/map.js +484 -0
  32. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +311 -317
  33. package/dist/observe/store/index.js +40 -0
  34. package/dist/observe/store/next/optimistic.js +660 -0
  35. package/dist/observe/store/next/projection.js +255 -0
  36. package/dist/observe/store/next/reconcile.js +375 -0
  37. package/dist/observe/store/next/store.js +2327 -0
  38. package/dist/observe/store/next/target.js +51 -0
  39. package/dist/observe/store/store.js +325 -0
  40. package/dist/observe/store/storePath.js +103 -0
  41. package/dist/observe/store/utils.js +1023 -0
  42. package/dist/prod/attribution.js +41 -0
  43. package/dist/prod/boundaries.js +128 -76
  44. package/dist/prod/core/action.js +65 -37
  45. package/dist/prod/core/async.js +324 -172
  46. package/dist/prod/core/constants.js +57 -1
  47. package/dist/prod/core/context.js +3 -3
  48. package/dist/prod/core/core.js +1003 -364
  49. package/dist/prod/core/dev.js +17 -1
  50. package/dist/prod/core/effect.js +76 -42
  51. package/dist/prod/core/error-hooks.js +71 -0
  52. package/dist/prod/core/external.js +4 -4
  53. package/dist/prod/core/graph.js +37 -37
  54. package/dist/prod/core/heap.js +61 -56
  55. package/dist/prod/core/lanes.js +107 -63
  56. package/dist/prod/core/optimistic.js +374 -93
  57. package/dist/prod/core/owner.js +62 -46
  58. package/dist/prod/core/scheduler.js +653 -286
  59. package/dist/prod/core/verdict.js +245 -127
  60. package/dist/prod/index.js +15 -5
  61. package/dist/prod/map.js +292 -141
  62. package/dist/prod/signals.js +29 -16
  63. package/dist/prod/store/next/optimistic.js +77 -55
  64. package/dist/prod/store/next/projection.js +31 -20
  65. package/dist/prod/store/next/reconcile.js +87 -84
  66. package/dist/prod/store/next/store.js +718 -401
  67. package/dist/prod/store/next/target.js +32 -10
  68. package/dist/prod/store/store.js +12 -14
  69. package/dist/prod/store/utils.js +950 -130
  70. package/dist/types/attribution.d.ts +20 -0
  71. package/dist/types/attribution.prod.d.ts +22 -0
  72. package/dist/types/boundaries.d.ts +10 -1
  73. package/dist/types/core/action.d.ts +12 -5
  74. package/dist/types/core/async.d.ts +1 -1
  75. package/dist/types/core/attribution-costs.d.ts +35 -0
  76. package/dist/types/core/attribution-feedback.d.ts +133 -0
  77. package/dist/types/core/attribution-hooks.d.ts +147 -9
  78. package/dist/types/core/attribution-queries.d.ts +10 -0
  79. package/dist/types/core/attribution.d.ts +216 -182
  80. package/dist/types/core/constants.d.ts +56 -0
  81. package/dist/types/core/core.d.ts +190 -2
  82. package/dist/types/core/dev.d.ts +246 -28
  83. package/dist/types/core/effect.d.ts +8 -1
  84. package/dist/types/core/error-hooks.d.ts +71 -0
  85. package/dist/types/core/index.d.ts +4 -1
  86. package/dist/types/core/invariants.d.ts +4 -0
  87. package/dist/types/core/lanes.d.ts +39 -5
  88. package/dist/types/core/scheduler.d.ts +153 -0
  89. package/dist/types/core/types.d.ts +32 -9
  90. package/dist/types/index.d.ts +10 -3
  91. package/dist/types/map.d.ts +2 -0
  92. package/dist/types/signals.d.ts +8 -0
  93. package/dist/types/store/index.d.ts +2 -0
  94. package/dist/types/store/next/optimistic.d.ts +1 -1
  95. package/dist/types/store/next/store.d.ts +11 -5
  96. package/dist/types/store/next/target.d.ts +37 -10
  97. package/dist/types/store/utils.d.ts +178 -0
  98. package/package.json +21 -17
  99. package/dist/node.cjs +0 -10541
  100. package/dist/node.dev.cjs +0 -13724
  101. package/dist/types-cjs/affects.d.cts +0 -47
  102. package/dist/types-cjs/boundaries.d.cts +0 -175
  103. package/dist/types-cjs/core/action.d.cts +0 -64
  104. package/dist/types-cjs/core/async.d.cts +0 -23
  105. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  106. package/dist/types-cjs/core/attribution.d.cts +0 -495
  107. package/dist/types-cjs/core/constants.d.cts +0 -159
  108. package/dist/types-cjs/core/core.d.cts +0 -185
  109. package/dist/types-cjs/core/dev.d.cts +0 -136
  110. package/dist/types-cjs/core/effect.d.cts +0 -30
  111. package/dist/types-cjs/core/error.d.cts +0 -56
  112. package/dist/types-cjs/core/external.d.cts +0 -15
  113. package/dist/types-cjs/core/graph.d.cts +0 -28
  114. package/dist/types-cjs/core/heap.d.cts +0 -24
  115. package/dist/types-cjs/core/index.d.cts +0 -13
  116. package/dist/types-cjs/core/invariants.d.cts +0 -59
  117. package/dist/types-cjs/core/lanes.d.cts +0 -54
  118. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  119. package/dist/types-cjs/core/owner.d.cts +0 -124
  120. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  121. package/dist/types-cjs/core/types.d.cts +0 -204
  122. package/dist/types-cjs/core/verdict.d.cts +0 -2
  123. package/dist/types-cjs/index.d.cts +0 -10
  124. package/dist/types-cjs/map.d.cts +0 -69
  125. package/dist/types-cjs/package.json +0 -3
  126. package/dist/types-cjs/store/index.d.cts +0 -18
  127. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  128. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  129. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  130. package/dist/types-cjs/store/next/store.d.cts +0 -121
  131. package/dist/types-cjs/store/next/target.d.cts +0 -166
  132. package/dist/types-cjs/store/store.d.cts +0 -143
  133. package/dist/types-cjs/store/storePath.d.cts +0 -58
  134. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,6 +1,7 @@
1
- import type { Computed } from "./types.js";
1
+ import type { Computed, Signal } from "./types.js";
2
2
  /**
3
- * Dev-mode "why did this run" attribution.
3
+ * "Why did this run" attribution — the engine behind
4
+ * `@solidjs/signals/attribution`.
4
5
  *
5
6
  * The runtime already knows the full dependency set of every scope; this
6
7
  * module surfaces it. Every value commit stamps its node with a ChangeRecord
@@ -15,11 +16,13 @@ import type { Computed } from "./types.js";
15
16
  *
16
17
  * This module is the attribution ENGINE: all semantics live here, and it is
17
18
  * decoupled from the core. `enable()` installs it into the core's narrow
18
- * dev-only hook points (attribution-hooks.ts); core's only obligation is to
19
- * call those hooks with true facts. Disabled cost is one null check per hook
20
- * site; prod builds fold the sites out entirely. The same hook surface is the
21
- * intended substrate for external consumers (devtools) one mechanism, two
22
- * front-ends.
19
+ * observe-tier hook points (attribution-hooks.ts); core's only obligation is
20
+ * to call those hooks with true facts. Disabled cost is one null check per
21
+ * hook site; prod builds fold the sites out entirely. Nothing in the core
22
+ * imports this module it is reachable only through the package's
23
+ * `./attribution` entry, so an observe build that never imports it never
24
+ * ships it. The same hook surface is the intended substrate for external
25
+ * consumers (devtools) — one mechanism, two front-ends.
23
26
  */
24
27
  export type ChangeKind = "write" | "derived" | "async" | "refresh";
25
28
  /**
@@ -36,30 +39,32 @@ export type ChangeKind = "write" | "derived" | "async" | "refresh";
36
39
  * name, when it has one). Writes after an `await` (not a `yield`) run in a
37
40
  * bare microtask and stamp `external` — the documented escape.
38
41
  * - `async` — an async landing (`name` = the node whose flight landed).
42
+ * - `navigation` — a router's navigation, declared via `withOrigin` around
43
+ * the location write (`name` = the matched route pattern `/users/:id`;
44
+ * `to`/`from` the concrete paths; `params` what the pattern bound; `at`
45
+ * when it was requested). The router-agnostic seam: any router that wraps
46
+ * its write gets navigations named by route in every hold, re-run and
47
+ * verdict, with no per-router knowledge anywhere in the engine.
39
48
  * - `external` — none of the above: timers, sockets, promise callbacks, setup.
40
49
  *
41
50
  * `interaction` on a non-interaction frame is the user event the frame runs
42
51
  * under — an action started by a click, an effect whose run was caused by a
43
- * click's write, a landing whose flight a click started. It is what lets
44
- * every downstream cost be keyed by the interaction that paid for it.
52
+ * click's write, a landing whose flight a click started, a navigation a link
53
+ * click performed. It is what lets every downstream cost be keyed by the
54
+ * interaction that paid for it.
45
55
  */
46
56
  export interface ChangeOrigin {
47
- kind: "interaction" | "effect" | "action" | "async" | "external";
57
+ kind: "interaction" | "effect" | "action" | "async" | "navigation" | "external";
48
58
  name?: string;
49
59
  target?: string;
50
60
  at?: number;
51
61
  interaction?: ChangeOrigin;
52
62
  /** `effect` only: the `RerunEvent.run` of the compute run this callback belongs to. */
53
63
  run?: number;
54
- }
55
- /** A user interaction, as the web runtime describes it to `withInteraction`. */
56
- export interface InteractionRef {
57
- /** Event type `click`, `keydown`, `input`… */
58
- type: string;
59
- /** The element hit, e.g. `button#next "Next →"`. */
60
- target?: string;
61
- /** Dispatch time on the `performance.now()` clock; defaults to now. */
62
- at?: number;
64
+ /** `navigation` only: concrete destination and departure paths, and the bound params. */
65
+ to?: string;
66
+ from?: string;
67
+ params?: Readonly<Record<string, string | undefined>>;
63
68
  }
64
69
  export interface ChangeRecord {
65
70
  /** Global monotonic change sequence — orders causes across the app. */
@@ -81,11 +86,22 @@ export interface ChangeRecord {
81
86
  export interface RerunEvent {
82
87
  /** Global monotonic run sequence. */
83
88
  run: number;
89
+ /** When the run started (`performance.now()` clock). */
90
+ at: number;
84
91
  /** How many times this node has re-run since attribution was enabled. */
85
92
  nodeRuns: number;
86
93
  nodeKind: "effect" | "memo";
87
94
  nodeName: string;
88
- node: Computed<any>;
95
+ /**
96
+ * Identity of the scope that ran, stable for the node's lifetime within
97
+ * the process: every run of one memo/effect carries the same `nodeId`, so
98
+ * runs join to a scope after the record has left the process (where
99
+ * `nodeName` alone would merge every unnamed `effect`). The engine's own
100
+ * per-node id, also what `ChangeOrigin.run` and the cycle/relay checks
101
+ * key on; not meaningful across processes or sessions. In-process
102
+ * consumers that want the live node ask `OBSERVE.subjectOf(event)`.
103
+ */
104
+ nodeId: number;
89
105
  /**
90
106
  * The deps that changed since this node's previous run. Empty means the
91
107
  * re-run was not triggered by a tracked value change (creation-adjacent
@@ -178,14 +194,11 @@ export interface AttributionOptions {
178
194
  /**
179
195
  * Written-fan-out warning: emit a diagnostic when a committed root
180
196
  * invalidation (write, refresh, async landing) reaches a node with at
181
- * least this many subscribers (default 250). Complements the always-on
182
- * HUGE_FAN_OUT graph-size warning, specced against it deliberately:
183
- * HUGE_FAN_OUT fires at LINK time from GRAPH_SIZE_WARN_AT (2000) up
184
- * static structure so large it warns even if never written while this
185
- * fires at WRITE time from a much lower bar, because fan-out only costs
186
- * anything when the node actually changes. Once per node, re-warning only
187
- * on 2x subscriber growth, so the two never spam the same node. `false`
188
- * disables.
197
+ * least this many subscribers (default 250). The lower-bar, opt-in sibling
198
+ * of the always-on HUGE_FAN_OUT graph-size warning, which fires on the
199
+ * same kind of write from GRAPH_SIZE_WARN_AT (2000) up; this one hands
200
+ * over to it there, so a write never carries both. Once per node,
201
+ * re-warning only on 2x subscriber growth. `false` disables.
189
202
  */
190
203
  wideWrites?: number | false;
191
204
  /**
@@ -243,57 +256,71 @@ export interface AttributionOptions {
243
256
  warnMs: number;
244
257
  } | false;
245
258
  }
246
- export interface ScopeCost {
247
- name: string;
248
- kind: "effect" | "memo";
249
- runs: number;
250
- selfMs: number;
251
- /**
252
- * Self-time of PLAIN, non-held runs that produced an unchanged value —
253
- * the recoverable number. Overlay runs (optimistic/transition) are never
254
- * counted here: an optimistic recompute landing back on the committed
255
- * value is the mechanism working, not waste.
256
- */
257
- wastedMs: number;
258
- /** Self-time spent in optimistic/transition (overlay) runs. */
259
- overlayMs: number;
260
- }
261
- export interface WriteCost {
262
- /** Root cause name (a signal write, async landing, or refresh target). */
263
- name: string;
264
- /** Number of downstream re-runs this root triggered. */
265
- runs: number;
266
- /** Summed self-time of every downstream re-run it caused. */
267
- downstreamMs: number;
259
+ /**
260
+ * The records the engine delivers, by `subscribe(type, …)` name. Every one is
261
+ * delivered synchronously at the moment it is complete — a re-run when its
262
+ * recompute ends, an interaction / hold / navigation when it settles — so a
263
+ * consumer never polls the ring buffers to learn that something finished.
264
+ */
265
+ export interface AttributionRecords {
266
+ rerun: RerunEvent;
267
+ interaction: InteractionEvent;
268
+ hold: HoldEvent;
269
+ navigation: NavigationEvent;
268
270
  }
271
+ export type AttributionRecordType = keyof AttributionRecords;
272
+ /** @internal The engine's clock: `performance.now()` where it exists. */
273
+ export declare const now: () => number;
269
274
  /**
270
- * Run `fn` as the handler of a user interaction: every root write it performs
271
- * (and every action step or effect the write causes) carries the interaction
272
- * as provenance. The web runtime wraps event dispatch in this; it is dev-only
273
- * and safe to call with no engine enabled.
275
+ * @internal The fold tables' seam. The engine emits records; the tables that
276
+ * fold them `costs()` (scopes and writes), `feedback()` (sources,
277
+ * interactions, navigations, flights, fallbacks) live in their own modules
278
+ * and register here when imported, so a consumer that only subscribes to
279
+ * records never ships them. Each hook fires at the moment the engine has the
280
+ * fact; `reset` on `enable()`/`disable()`. With nothing registered every
281
+ * site is one empty loop.
274
282
  */
275
- export declare function withInteraction<T>(ref: InteractionRef, fn: () => T): T;
276
- /** `click on button#next "Next →"`, `effect "syncTitle"`, `action "save"`, … */
283
+ export interface FoldHooks {
284
+ rerun?(el: Computed<any>, event: RerunEvent): void;
285
+ hold?(event: HoldEvent): void;
286
+ navigation?(event: NavigationEvent): void;
287
+ /** A flight started; `abandoned` when it superseded one still in the air. */
288
+ flightStart?(el: Computed<any>, abandoned: boolean): void;
289
+ flightLanded?(el: Computed<any>, ms: number): void;
290
+ fallback?(boundary: object, tree: Computed<any> | undefined, shown: boolean): void;
291
+ reset?(): void;
292
+ }
293
+ /** @internal */
294
+ export declare function registerFold(hooks: FoldHooks): void;
295
+ /** @internal Root cause names of a cause chain — the writes/landings/refreshes the chain bottoms out in. */
296
+ export declare function rootsOf(causes: ChangeRecord[], out: Set<string>): void;
297
+ export declare function nodeName(node: Signal<any> | Computed<any>): string;
298
+ /** `click on button#next "Next →"`, `effect "syncTitle"`, `action "save"`, `navigation to /users/:id`, … */
277
299
  export declare function formatOrigin(origin: ChangeOrigin): string;
278
300
  export declare function formatRerun(event: RerunEvent): string;
301
+ /**
302
+ * The engine: turn it on, subscribe to its records, read its ring buffers.
303
+ * The folds over those records — `costs()`, `feedback()` — and the point
304
+ * queries — `why()`, `subscriptions()` — and the formatters —
305
+ * `formatRerun()`, `formatOrigin()` — are named exports of
306
+ * `@solidjs/signals/attribution` rather than methods here, so a consumer
307
+ * that only wants records (a production adapter) does not ship the tables a
308
+ * console or an agent reads; importing a fold is what turns its accounting
309
+ * on.
310
+ */
279
311
  export interface Attribution {
280
312
  enable(opts?: AttributionOptions): void;
281
313
  disable(): void;
282
- subscribe(listener: (event: RerunEvent) => void): () => void;
283
- history(): readonly RerunEvent[];
284
- /** Re-run history for one node — pass a memo/effect accessor or raw node. */
285
- why(target: unknown): RerunEvent[];
286
- /** Current dependency names of one scope — the devtools subscription view. */
287
- subscriptions(target: unknown): string[];
288
314
  /**
289
- * Aggregated cost tables since enable(): `scopes` ranked by self-time
290
- * (with `wastedMs` = time spent on unchanged-value runs), `writes` ranked
291
- * by total downstream re-run time each root write caused.
315
+ * Deliver records as they complete — see `AttributionRecords`. The bare
316
+ * form is `subscribe("rerun", …)`. Records are the same objects the ring
317
+ * buffers hold (`history()`, `interactions()`, `holds()`, `navigations()`),
318
+ * delivered synchronously from the engine, so a listener must not write
319
+ * signals. All subscriptions are dropped by `disable()`.
292
320
  */
293
- costs(): {
294
- scopes: ScopeCost[];
295
- writes: WriteCost[];
296
- };
321
+ subscribe(listener: (event: RerunEvent) => void): () => void;
322
+ subscribe<K extends AttributionRecordType>(type: K, listener: (record: AttributionRecords[K]) => void): () => void;
323
+ history(): readonly RerunEvent[];
297
324
  /**
298
325
  * Every graph-provable sequential flight chain observed since enable()
299
326
  * (ring-buffered like history()). Facts, not verdicts: chains are recorded
@@ -309,18 +336,25 @@ export interface Attribution {
309
336
  */
310
337
  holds(): readonly HoldEvent[];
311
338
  /**
312
- * What the user waited on, folded from holds() and the interaction on each
313
- * re-run: `sources` ranks async sources by the silent time writes spent
314
- * held behind them (with which affordances answered, how often, and which
315
- * interactions were held); `interactions` ranks user events by the total
316
- * time they cost re-run work caused (long-flush hazard) beside time held
317
- * (silent-hold hazard). Facts at every duration; SILENT_HOLD is the
318
- * thresholded verdict. Two more tables round out the picture: `flights`
319
- * counts each async source's flights and how many were abandoned before
320
- * landing (the re-ask storm), and `fallbacks` measures how long each
321
- * loading boundary showed its fallback and how often that was a flash.
339
+ * Every navigation a router declared via `withOrigin` since enable()
340
+ * (ring-buffered like history()), settled or not: what route, under which
341
+ * interaction, how many writes, and once its writes are through — how
342
+ * long that took and how (`committed` in a plain drain, `held` behind
343
+ * route data with the HoldEvent attached, or `superseded` by a later
344
+ * navigation before it landed). Facts for any consumer that wants
345
+ * navigation spans named by route: no router integration needed.
322
346
  */
323
- feedback(): AttributionFeedbackTables;
347
+ navigations(): readonly NavigationEvent[];
348
+ /**
349
+ * Every user interaction a runtime declared via `withInteraction` since
350
+ * enable() (ring-buffered like history()), settled or not: what was
351
+ * dispatched, when, what it wrote, the re-runs and creations it caused,
352
+ * the holds its writes waited in and the navigations it performed — and,
353
+ * once all of that is through, how long the person waited (`settledMs`)
354
+ * and how it ended (`idle` / `committed` / `held`). The per-dispatch
355
+ * record `feedback().interactions` folds by name.
356
+ */
357
+ interactions(): readonly InteractionEvent[];
324
358
  /**
325
359
  * Cooperative preload declaration: stamp a flight object (promise or async
326
360
  * iterable) with its true kickoff time BEFORE the reactive graph sees it.
@@ -330,18 +364,9 @@ export interface Attribution {
330
364
  * is then judged against the real start — work already in the air when its
331
365
  * upstream landed is parallel, never a waterfall link. Callable while
332
366
  * attribution is disabled (marks made at navigation time must survive a
333
- * later enable()). Dev-only, like the whole DEV surface.
367
+ * later enable()).
334
368
  */
335
369
  markFlight(flight: object, startedAt?: number): void;
336
- /**
337
- * Run `fn` as a user interaction's handler: root writes inside stamp it as
338
- * their origin, and actions/effects/flights it causes carry it. The web
339
- * runtime wraps every event dispatch in this; custom renderers and test
340
- * harnesses call it themselves. Callable while attribution is disabled.
341
- */
342
- withInteraction: typeof withInteraction;
343
- format: typeof formatRerun;
344
- formatOrigin: typeof formatOrigin;
345
370
  }
346
371
  /** One landed flight: its node name, wall duration, and upstream chain. */
347
372
  export interface FlightLink {
@@ -362,10 +387,13 @@ export interface HeldWrite {
362
387
  }
363
388
  export interface HoldEvent {
364
389
  /**
365
- * Wall time the user waited: from the interaction that performed the held
366
- * writes when one is known (`interaction.at`) or the first flush that
367
- * parked them, whichever is earlier, to the commit.
390
+ * When the wait began (`performance.now()` clock): the interaction that
391
+ * performed the held writes when one is known (`interaction.at`) or the
392
+ * first flush that parked them, whichever is earlier. `at + holdMs` is the
393
+ * commit.
368
394
  */
395
+ at: number;
396
+ /** Wall time the user waited: `at` to the commit. */
369
397
  holdMs: number;
370
398
  /**
371
399
  * The quiescent tail: from the LAST held write to join (the user's final
@@ -375,6 +403,13 @@ export interface HoldEvent {
375
403
  tailMs: number;
376
404
  /** The user interaction whose writes were held, when the stamp is known. */
377
405
  interaction?: ChangeOrigin;
406
+ /**
407
+ * The declared unit of work the held writes belong to — the `navigation`
408
+ * a router described via `withOrigin` — when one is known. What names the
409
+ * hold by route (`navigation to /users/:id`) rather than by signal; the
410
+ * same object as `navigations()[].origin`, so the two join by identity.
411
+ */
412
+ origin?: ChangeOrigin;
378
413
  /** Flushes that ended with the hold still open. */
379
414
  flushes: number;
380
415
  /** Root signal writes staged behind the hold (the user's unanswered input). */
@@ -382,11 +417,12 @@ export interface HoldEvent {
382
417
  /** Async nodes the hold waited on (union across its parked flushes). */
383
418
  blockers: string[];
384
419
  /**
385
- * Feedback the graph provably rendered for this hold, as `"<kind>:<node>"`
386
- * `isPending:posts`, `latest:page`, `optimistic:todos`, `affects:list`.
387
- * Empty and `paintedDuringHold === 0` is the SILENT_HOLD signature.
420
+ * Feedback the graph provably rendered for this hold an `isPending()`
421
+ * reader, a `latest()` shadow, an optimistic value, an `affects()` mark —
422
+ * one entry per affordance, in the order found. Empty and
423
+ * `paintedDuringHold === 0` is the SILENT_HOLD signature.
388
424
  */
389
- acknowledgedBy: string[];
425
+ acknowledgements: Acknowledgement[];
390
426
  /**
391
427
  * Effect callbacks that ran inside the hold's parked flushes. Mainline
392
428
  * effects are stashed while a hold is open, so these are lane effects —
@@ -398,98 +434,96 @@ export interface HoldEvent {
398
434
  /** The hold was opened (or joined) by an `action()`. */
399
435
  action: boolean;
400
436
  }
401
- export interface FeedbackSource {
402
- /** The async nodes the holds waited on; empty when an action alone kept them open. */
403
- sources: string[];
404
- holds: number;
405
- /** Summed wait across the holds (ms). */
406
- heldMs: number;
407
- worstMs: number;
408
- /** Holds with no acknowledgment at all — the SILENT_HOLD signature, at any duration. */
409
- silent: number;
410
- silentMs: number;
411
- /** Holds whose only acknowledgment was a `latest()` shadow: the input showed, nothing said "loading". */
412
- latestOnly: number;
437
+ /**
438
+ * One way the screen acknowledged a hold. `reader` is where it was painted
439
+ * the owner path of the first effect the census found reading the
440
+ * affordance (`["<App>", "<Feed>", "effect"]`), so a consumer can say WHICH
441
+ * screen answered, not only that one did. Absent when the affordance was
442
+ * registered but the census found no reader through the graph (an optimistic
443
+ * store: its readers are proxy traps, not nodes). `feedback()` ranks
444
+ * acknowledgements by `kind:source` (`isPending:posts`).
445
+ */
446
+ export interface Acknowledgement {
447
+ kind: "isPending" | "latest" | "optimistic" | "affects";
448
+ /** The node the affordance hangs on — the async source for `isPending`/`latest`, the optimistic/affected node otherwise. */
449
+ source: string;
450
+ reader?: string[];
451
+ }
452
+ /** `isLongHold` — the tail outlasted `longHolds.infoMs`. */
453
+ /** @internal */
454
+ export declare function isLongHold(event: HoldEvent): boolean;
455
+ /** A destination a navigation abandoned when a redirect sent it elsewhere. */
456
+ export interface NavigationHop {
457
+ name?: string;
458
+ to?: string;
459
+ params?: Readonly<Record<string, string | undefined>>;
460
+ /** When the redirect away from it was declared (`performance.now()` clock). */
461
+ at: number;
462
+ }
463
+ export interface NavigationEvent {
464
+ /** The matched route pattern the router gave — `/users/:id`. After a redirect, the final one. */
465
+ name?: string;
466
+ to?: string;
467
+ from?: string;
468
+ params?: Readonly<Record<string, string | undefined>>;
469
+ /** When the navigation was requested (`performance.now()` clock). */
470
+ at: number;
471
+ /** The user interaction it ran under, when known — a link click. */
472
+ interaction?: ChangeOrigin;
473
+ /** Root writes the frame performed, redirect hops included. */
474
+ writes: number;
475
+ /** Destinations abandoned along the way, in order — present only when a redirect occurred. */
476
+ redirects?: NavigationHop[];
477
+ /**
478
+ * Wall time from the request to settle: the end of the drain that committed
479
+ * its writes, or the commit of the hold they waited in. `undefined` while
480
+ * unsettled.
481
+ */
482
+ settledMs?: number;
483
+ outcome?: "committed" | "held" | "superseded";
484
+ /** The hold its writes waited in, when hold tracking recorded one. */
485
+ hold?: HoldEvent;
413
486
  /**
414
- * Holds whose quiescent tail (last write to join commit) reached
415
- * `longHolds.infoMs`, acknowledged or not the LONG_HOLD signature at the
416
- * table level. The affordance is not the whole answer there: a fallback
417
- * (`Loading` keyed with `on`), a preload, a cache, or a faster source is.
418
- * `longMs` sums the tails.
487
+ * The frame object its writes were stamped with `ChangeRecord.origin` on
488
+ * each, `HoldEvent.origin` on the hold. Join key, by identity.
419
489
  */
420
- long: number;
421
- longMs: number;
422
- /** Which affordances answered, and in how many holds — ranked. */
423
- acknowledgedBy: {
424
- by: string;
425
- holds: number;
426
- }[];
427
- /** Interactions whose writes were held here, ranked by holds. */
428
- interactions: {
429
- interaction: string;
430
- holds: number;
431
- }[];
432
- /** Distinct root writes that were held. */
433
- writes: string[];
434
- /** Holds an action opened or joined. */
435
- actions: number;
490
+ origin: ChangeOrigin;
436
491
  }
437
- export interface FeedbackInteraction {
438
- /** `click on button#next "Next →"` type and target; repeated dispatches fold together. */
439
- interaction: string;
440
- /** Distinct dispatches seen (by dispatch time). */
441
- dispatches: number;
442
- /** Re-runs traced back to this interaction, and their summed self-time. */
492
+ export interface InteractionEvent {
493
+ /** Event type `click`, `keydown`, `input`… */
494
+ name: string;
495
+ /** The element hit, as the runtime described it — `button#next "Next →"`. */
496
+ target?: string;
497
+ /** Dispatch time (`performance.now()` clock). */
498
+ at: number;
499
+ /** Wall time of the handler itself, dispatch to return. */
500
+ handlerMs: number;
501
+ /** Root writes attributed to the frame: the handler's, and those of frames it opened (a navigation). */
502
+ writes: number;
503
+ /** Re-runs traced back to this interaction while the record was open. */
443
504
  runs: number;
444
- selfMs: number;
445
- /** The most re-run self-time a single dispatch caused — the long-flush hazard. */
446
- worstDispatchMs: number;
447
- /** Holds this interaction's writes waited in — the silent-hold hazard. */
448
- holds: number;
449
- heldMs: number;
450
- silentMs: number;
451
- worstHoldMs: number;
452
- }
453
- /** Per async source: how many flights it started, and how many it threw away. */
454
- export interface FlightStats {
455
- source: string;
456
- /** Flights registered (a recompute that produced a new promise/iterable). */
457
- flights: number;
458
- /** Flights that landed (whether or not the value changed). */
459
- landed: number;
505
+ /** Computations created in those runs or in the frame's flushes (the "create 1,000 rows" work). */
506
+ created: number;
507
+ /** Summed self-time of `runs` and `created` (ms). Quantized per run; `settledMs` is the wall clock. */
508
+ runMs: number;
509
+ /** Holds its writes waited in, in settle order. */
510
+ holds: HoldEvent[];
511
+ /** Navigations performed under it, in open order. */
512
+ navigations: NavigationEvent[];
460
513
  /**
461
- * Flights superseded by a newer one before landing the search-as-you-type
462
- * signature when large: every keystroke asked, most answers were discarded.
463
- * A debounced/equality-gated derivation between input and fetch is the repair.
514
+ * Dispatch to settle: the handler's return when it wrote nothing, the end of
515
+ * the drain that committed its writes, or the commit of the last hold they
516
+ * waited in whichever came last. `undefined` while unsettled.
464
517
  */
465
- abandoned: number;
466
- /** Summed and worst wall time of landed flights (ms). */
467
- landedMs: number;
468
- worstMs: number;
469
- }
470
- /** Per loading boundary: how long, and how briefly, it showed its fallback. */
471
- export interface FallbackStats {
472
- /** The boundary's owner path (`<App> › <Feed>`), or `boundary` when unnamed. */
473
- boundary: string;
474
- /** Times the fallback was shown. */
475
- shows: number;
476
- /** Summed and worst fallback duration (ms) across completed shows. */
477
- shownMs: number;
478
- worstMs: number;
518
+ settledMs?: number;
519
+ outcome?: "idle" | "committed" | "held";
479
520
  /**
480
- * Shows shorter than the flash window (default 150ms): a spinner that
481
- * appeared and vanished the other end of the SILENT_HOLD spectrum, too
482
- * much feedback for too little wait. A preload, a cache, or lifting the
483
- * fetch above the boundary removes the flash.
521
+ * The frame object every downstream fact carries `ChangeOrigin.interaction`
522
+ * on writes and frames, `RerunEvent.interaction`, `HoldEvent.interaction`,
523
+ * `NavigationEvent.interaction`. Join key, by identity.
484
524
  */
485
- flashes: number;
486
- }
487
- export interface AttributionFeedbackTables {
488
- sources: FeedbackSource[];
489
- interactions: FeedbackInteraction[];
490
- /** Async sources ranked by abandoned flights, then by flights. */
491
- flights: FlightStats[];
492
- /** Loading boundaries ranked by flashes, then by time shown. */
493
- fallbacks: FallbackStats[];
525
+ origin: ChangeOrigin;
494
526
  }
527
+ /** @internal No affordance answered and nothing painted while held. */
528
+ export declare function isSilentHold(event: HoldEvent): boolean;
495
529
  export declare const attribution: Attribution;
@@ -113,6 +113,59 @@ export declare const CONFIG_HELD_TRUTH: number;
113
113
  * unobserved closure, NodeExtension) were the measured create-floor bytes
114
114
  * (warm dbmon profile: store node machinery ~36% + GC ~29%). */
115
115
  export declare const CONFIG_SLOT_NODE: number;
116
+ /** Optimistic node whose own source arrived with a value DIFFERENT from its
117
+ * active override (A18 supersession, #3331). The override survives only as
118
+ * the displayed value — untracked reads and the applied frame keep it until
119
+ * the owning transaction commits — while the graph has already moved to the
120
+ * staged truth in `_pendingValue`: tracked readers see it and the corrected
121
+ * cascade is that transaction's held work. Set by the two own-source write
122
+ * paths (asyncWrite, transition-held recompute); cleared by a fresh optimistic
123
+ * write (a new override re-masks) and by the revert. */
124
+ export declare const CONFIG_OVERRIDE_SUPERSEDED: number;
125
+ /** HELD children (#3404): this node's `_firstChild` chain (and `_disposal`
126
+ * list) was built by a recompute whose result has not committed — a staged
127
+ * value, a pending window, or a run under a held transaction. A later
128
+ * recompute may tear those children down immediately: nothing observable
129
+ * was ever built on them. Unset, the children belong to the committed frame
130
+ * and a recompute defers them as zombies (`_pendingFirstChild`) until this
131
+ * node commits — regardless of whether the recompute runs under a
132
+ * transaction. A parked node (status propagation stamps `_transition`
133
+ * without recomputing) recomputed when its source lands otherwise disposed
134
+ * its committed children mid-hold, running their cleanups before the
135
+ * transaction's atomic reveal. Cleared by `commitPendingNode`. */
136
+ export declare const CONFIG_HELD_CHILDREN: number;
137
+ /** In-flight async node whose inputs were PUBLISHED while it was pending: a
138
+ * batch or transaction committed with the node still `STATUS_PENDING` (an
139
+ * unobserved flight, #3305), so the inputs are on screen and the node's
140
+ * committed `_value` is stale against them. Governs read()'s reveal
141
+ * carve-out: a stale (render) reader in some OTHER transaction may show a
142
+ * foreign-held pending node's committed value — parallel transactions, no
143
+ * entanglement — only while that value is coherent with the visible frame,
144
+ * i.e. while the flight's inputs are themselves held (unpublished) and not
145
+ * lane-revealed. Set by `commitPendingNodes`; cleared when the node next
146
+ * enters pending fresh (a new flight from a settled state). */
147
+ export declare const CONFIG_INPUTS_PUBLISHED: number;
148
+ /** A28 (4): the node was written inside a recompute that ran OUTSIDE a flush
149
+ * (a creation-time compute — boundary machinery, a mapArray's first run). Such
150
+ * a write is promoted at that recompute's end: readers in the same block see
151
+ * it. Cleared when the next flush begins; set only on that rare path. */
152
+ export declare const CONFIG_PROMOTED: number;
153
+ /** A28 for same-tick adoption: the node was staged outside a flush and then
154
+ * adopted by a transaction (initTransition) before any flush carried the
155
+ * staging — the stamp says "held", but nothing flushed is staged for it, so
156
+ * on no channel is the write visible yet: `latest()` answers the committed
157
+ * value, the verdict sees nothing pending (as the store's leaves already did
158
+ * through their own selection). Cleared when the carrying flush re-stamps the
159
+ * transaction's pending nodes (reassignPendingTransition). */
160
+ export declare const CONFIG_ADOPTED_UNFLUSHED: number;
161
+ /** The node's active override is a DERIVED one: a lane pass published its
162
+ * speculative result into the override slot instead of `_value` (lanes
163
+ * stage — an optimistic derivation is an override, #3479). Its truth is not
164
+ * `_value` but a recompute from its inputs' truth, so the body-end
165
+ * supersession (`endOptimism`) and the authoritative-flight blockage
166
+ * (`transitionBlocked`) skip it; the revert drops the override and re-derives
167
+ * it (`resolveOptimisticNodes`). Cleared with the override. */
168
+ export declare const CONFIG_DERIVED_OVERRIDE: number;
116
169
  export declare const STATUS_NONE = 0;
117
170
  export declare const STATUS_PENDING: number;
118
171
  export declare const STATUS_ERROR: number;
@@ -121,6 +174,9 @@ export declare const EFFECT_PURE = 0;
121
174
  export declare const EFFECT_RENDER = 1;
122
175
  export declare const EFFECT_USER = 2;
123
176
  export declare const EFFECT_TRACKED = 3;
177
+ /** OR-ed into the `type` a lane passes to its effect runners: lane runs
178
+ * apply ahead of their transaction and are exempt from ownership parking. */
179
+ export declare const LANE_RUN = 4;
124
180
  export declare const NOT_PENDING: {};
125
181
  export declare const NO_SNAPSHOT: {};
126
182
  /**