@solidjs/signals 2.0.0-rc.6 → 2.0.0-rc.8

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 (131) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1758 -6621
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/boundaries.js +3 -1
  40. package/dist/prod/core/action.js +52 -32
  41. package/dist/prod/core/async.js +177 -128
  42. package/dist/prod/core/attribution-hooks.js +3 -0
  43. package/dist/prod/core/constants.js +32 -1
  44. package/dist/prod/core/context.js +10 -16
  45. package/dist/prod/core/core.js +505 -286
  46. package/dist/prod/core/dev.js +2 -0
  47. package/dist/prod/core/effect.js +72 -43
  48. package/dist/prod/core/external.js +2 -2
  49. package/dist/prod/core/graph.js +40 -36
  50. package/dist/prod/core/heap.js +50 -51
  51. package/dist/prod/core/lanes.js +43 -22
  52. package/dist/prod/core/optimistic.js +224 -80
  53. package/dist/prod/core/owner.js +43 -39
  54. package/dist/prod/core/scheduler.js +347 -232
  55. package/dist/prod/core/verdict.js +71 -74
  56. package/dist/prod/index.js +9 -5
  57. package/dist/prod/map.js +311 -150
  58. package/dist/prod/signals.js +52 -38
  59. package/dist/prod/store/next/optimistic.js +127 -131
  60. package/dist/prod/store/next/projection.js +3 -3
  61. package/dist/prod/store/next/reconcile.js +149 -294
  62. package/dist/prod/store/next/store.js +694 -380
  63. package/dist/prod/store/next/target.js +32 -10
  64. package/dist/prod/store/store.js +12 -14
  65. package/dist/prod/store/utils.js +36 -27
  66. package/dist/types/attribution.d.ts +15 -0
  67. package/dist/types/attribution.prod.d.ts +13 -0
  68. package/dist/types/core/async.d.ts +1 -1
  69. package/dist/types/core/attribution-hooks.d.ts +183 -6
  70. package/dist/types/core/attribution.d.ts +435 -24
  71. package/dist/types/core/constants.d.ts +31 -0
  72. package/dist/types/core/core.d.ts +19 -3
  73. package/dist/types/core/dev.d.ts +165 -26
  74. package/dist/types/core/effect.d.ts +8 -1
  75. package/dist/types/core/heap.d.ts +5 -3
  76. package/dist/types/core/index.d.ts +2 -1
  77. package/dist/types/core/invariants.d.ts +1 -1
  78. package/dist/types/core/lanes.d.ts +17 -0
  79. package/dist/types/core/scheduler.d.ts +69 -4
  80. package/dist/types/core/types.d.ts +29 -9
  81. package/dist/types/index.d.ts +9 -2
  82. package/dist/types/map.d.ts +2 -0
  83. package/dist/types/signals.d.ts +10 -0
  84. package/dist/types/store/index.d.ts +4 -6
  85. package/dist/types/store/next/optimistic.d.ts +4 -2
  86. package/dist/types/store/next/reconcile.d.ts +5 -12
  87. package/dist/types/store/next/store.d.ts +8 -9
  88. package/dist/types/store/next/target.d.ts +56 -55
  89. package/dist/types/store/store.d.ts +14 -9
  90. package/dist/types/store/utils.d.ts +7 -0
  91. package/package.json +21 -16
  92. package/dist/node.cjs +0 -11121
  93. package/dist/prod/store/next/patch-hooks.js +0 -13
  94. package/dist/prod/store/next/patch.js +0 -614
  95. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  96. package/dist/types/store/next/patch.d.ts +0 -91
  97. package/dist/types-cjs/affects.d.cts +0 -47
  98. package/dist/types-cjs/boundaries.d.cts +0 -175
  99. package/dist/types-cjs/core/action.d.cts +0 -64
  100. package/dist/types-cjs/core/async.d.cts +0 -23
  101. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
  102. package/dist/types-cjs/core/attribution.d.cts +0 -239
  103. package/dist/types-cjs/core/core.d.cts +0 -176
  104. package/dist/types-cjs/core/dev.d.cts +0 -95
  105. package/dist/types-cjs/core/effect.d.cts +0 -30
  106. package/dist/types-cjs/core/error.d.cts +0 -56
  107. package/dist/types-cjs/core/external.d.cts +0 -15
  108. package/dist/types-cjs/core/graph.d.cts +0 -28
  109. package/dist/types-cjs/core/heap.d.cts +0 -22
  110. package/dist/types-cjs/core/index.d.cts +0 -13
  111. package/dist/types-cjs/core/invariants.d.cts +0 -59
  112. package/dist/types-cjs/core/lanes.d.cts +0 -44
  113. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  114. package/dist/types-cjs/core/owner.d.cts +0 -124
  115. package/dist/types-cjs/core/scheduler.d.cts +0 -231
  116. package/dist/types-cjs/core/types.d.cts +0 -204
  117. package/dist/types-cjs/core/verdict.d.cts +0 -2
  118. package/dist/types-cjs/index.d.cts +0 -10
  119. package/dist/types-cjs/map.d.cts +0 -69
  120. package/dist/types-cjs/package.json +0 -3
  121. package/dist/types-cjs/store/index.d.cts +0 -21
  122. package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
  123. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  124. package/dist/types-cjs/store/next/patch.d.cts +0 -91
  125. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  126. package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
  127. package/dist/types-cjs/store/next/store.d.cts +0 -127
  128. package/dist/types-cjs/store/next/target.d.cts +0 -192
  129. package/dist/types-cjs/store/store.d.cts +0 -138
  130. package/dist/types-cjs/store/storePath.d.cts +0 -58
  131. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,6 +1,7 @@
1
1
  import type { Computed } 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,13 +16,56 @@ 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";
28
+ /**
29
+ * Provenance of a root change: the imperative frame that performed it.
30
+ *
31
+ * - `interaction` — a user event handler (the web runtime marks dispatch via
32
+ * `withInteraction`). `name` is the event type, `target` the element hit
33
+ * (`button#next "Next →"`), `at` the dispatch time on the `performance.now()`
34
+ * clock — the base every feedback-latency number is measured from.
35
+ * - `effect` — an effect callback (`name` = the effect's name; `run` = the
36
+ * compute run whose effect phase performed the write, when that run was
37
+ * recorded — so a write can be joined to the re-run that produced it).
38
+ * - `action` — a step of an `action()` generator (`name` = the generator's
39
+ * name, when it has one). Writes after an `await` (not a `yield`) run in a
40
+ * bare microtask and stamp `external` — the documented escape.
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.
48
+ * - `external` — none of the above: timers, sockets, promise callbacks, setup.
49
+ *
50
+ * `interaction` on a non-interaction frame is the user event the frame runs
51
+ * under — an action started by a click, an effect whose run was caused by a
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.
55
+ */
56
+ export interface ChangeOrigin {
57
+ kind: "interaction" | "effect" | "action" | "async" | "navigation" | "external";
58
+ name?: string;
59
+ target?: string;
60
+ at?: number;
61
+ interaction?: ChangeOrigin;
62
+ /** `effect` only: the `RerunEvent.run` of the compute run this callback belongs to. */
63
+ run?: 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>>;
68
+ }
25
69
  export interface ChangeRecord {
26
70
  /** Global monotonic change sequence — orders causes across the app. */
27
71
  seq: number;
@@ -34,10 +78,16 @@ export interface ChangeRecord {
34
78
  stack?: string[];
35
79
  /** For derived changes: the upstream changes that produced this one. */
36
80
  causes?: ChangeRecord[];
81
+ /** Root changes only: who performed the write. */
82
+ origin?: ChangeOrigin;
83
+ /** Root changes only: when the write was stamped (`performance.now()` clock). */
84
+ at?: number;
37
85
  }
38
86
  export interface RerunEvent {
39
87
  /** Global monotonic run sequence. */
40
88
  run: number;
89
+ /** When the run started (`performance.now()` clock). */
90
+ at: number;
41
91
  /** How many times this node has re-run since attribution was enabled. */
42
92
  nodeRuns: number;
43
93
  nodeKind: "effect" | "memo";
@@ -74,19 +124,21 @@ export interface RerunEvent {
74
124
  changed: boolean;
75
125
  /**
76
126
  * Which posture this run executed under. "optimistic" = under an
77
- * optimistic lane (overlay recompute); "transition" = a transition was
78
- * active or owns the node (the run may be replayed/settled later);
79
- * "plain" = an ordinary committed run. Overlay runs are real work (they
80
- * count toward time budgets) but are never blamed as waste, and costs()
81
- * reports their time separately as `overlayMs`.
127
+ * optimistic lane (overlay recompute); "held" = a hold was open or owns
128
+ * the node (the run may be replayed/settled later); "plain" = an ordinary
129
+ * committed run. Overlay runs are real work (they count toward time
130
+ * budgets) but are never blamed as waste, and costs() reports their time
131
+ * separately as `overlayMs`.
82
132
  */
83
- phase: "plain" | "transition" | "optimistic";
133
+ phase: "plain" | "held" | "optimistic";
84
134
  /**
85
- * The changed value was held in `_pendingValue` (a transition hold) rather
86
- * than committed directly; its reveal happens on the transition's own
87
- * schedule. Held runs are excluded from waste accounting.
135
+ * The changed value was parked in `_pendingValue` (held) rather than
136
+ * committed directly; its reveal happens on the hold's own schedule. Held
137
+ * runs are excluded from waste accounting.
88
138
  */
89
139
  held: boolean;
140
+ /** The user interaction this run traces back to through its causes, if any. */
141
+ interaction?: ChangeOrigin;
90
142
  }
91
143
  export interface AttributionOptions {
92
144
  /** Pretty-print each re-run to the console (default true). */
@@ -133,14 +185,11 @@ export interface AttributionOptions {
133
185
  /**
134
186
  * Written-fan-out warning: emit a diagnostic when a committed root
135
187
  * invalidation (write, refresh, async landing) reaches a node with at
136
- * least this many subscribers (default 250). Complements the always-on
137
- * HUGE_FAN_OUT graph-size warning, specced against it deliberately:
138
- * HUGE_FAN_OUT fires at LINK time from GRAPH_SIZE_WARN_AT (2000) up —
139
- * static structure so large it warns even if never written — while this
140
- * fires at WRITE time from a much lower bar, because fan-out only costs
141
- * anything when the node actually changes. Once per node, re-warning only
142
- * on 2x subscriber growth, so the two never spam the same node. `false`
143
- * disables.
188
+ * least this many subscribers (default 250). The lower-bar, opt-in sibling
189
+ * of the always-on HUGE_FAN_OUT graph-size warning, which fires on the
190
+ * same kind of write from GRAPH_SIZE_WARN_AT (2000) up; this one hands
191
+ * over to it there, so a write never carries both. Once per node,
192
+ * re-warning only on 2x subscriber growth. `false` disables.
144
193
  */
145
194
  wideWrites?: number | false;
146
195
  /**
@@ -161,7 +210,56 @@ export interface AttributionOptions {
161
210
  waterfalls?: {
162
211
  minFlightMs: number;
163
212
  } | false;
213
+ /**
214
+ * Silent-hold warning: emit a diagnostic when a user's writes were held
215
+ * behind async work for at least `infoMs` (default 100ms — RAIL's "feels
216
+ * instant" ceiling) and the screen never acknowledged the wait — no
217
+ * `isPending()`/`latest()` reader downstream of the held writes or their
218
+ * blockers, no optimistic value, no `affects()` mark, and no lane effect
219
+ * painted while held. Below `warnMs`
220
+ * (default 200ms — the INP "good" ceiling) the event is advisory
221
+ * (structured channel only); at or above it the console gets the finding.
222
+ * The engine measures to the commit, not the paint, so every number is a
223
+ * floor on what the user saw; the thresholds sit at the strict end of the
224
+ * band on purpose. Holds that staged no root write (initial loads, bare
225
+ * `refresh()`) are never judged: nothing the user did went unanswered.
226
+ * `false` disables hold tracking altogether (`longHolds` included).
227
+ */
228
+ holds?: {
229
+ infoMs: number;
230
+ warnMs: number;
231
+ } | false;
232
+ /**
233
+ * Long-hold warning: emit a diagnostic when a hold's quiescent tail — the
234
+ * time from the LAST write to join it until it committed — reached
235
+ * `infoMs` (default 500ms), `warn` from `warnMs` (default 1000ms, where
236
+ * RAIL says the user loses the thread). Measured from the last join so a
237
+ * hold that keeps taking input (typing) is judged by each wait, not by its
238
+ * lifetime. A hold this long is past what a stale screen should carry,
239
+ * acknowledged or not: the honest UI is a fallback, which a `Loading`
240
+ * boundary gives only when it has not revealed yet or its `on` prop
241
+ * changed. Reported as LONG_HOLD when the hold was acknowledged; a silent
242
+ * long hold stays one SILENT_HOLD with the boundary repair appended.
243
+ * `false` disables.
244
+ */
245
+ longHolds?: {
246
+ infoMs: number;
247
+ warnMs: number;
248
+ } | false;
164
249
  }
250
+ /**
251
+ * The records the engine delivers, by `subscribe(type, …)` name. Every one is
252
+ * delivered synchronously at the moment it is complete — a re-run when its
253
+ * recompute ends, an interaction / hold / navigation when it settles — so a
254
+ * consumer never polls the ring buffers to learn that something finished.
255
+ */
256
+ export interface AttributionRecords {
257
+ rerun: RerunEvent;
258
+ interaction: InteractionEvent;
259
+ hold: HoldEvent;
260
+ navigation: NavigationEvent;
261
+ }
262
+ export type AttributionRecordType = keyof AttributionRecords;
165
263
  export interface ScopeCost {
166
264
  name: string;
167
265
  kind: "effect" | "memo";
@@ -185,11 +283,21 @@ export interface WriteCost {
185
283
  /** Summed self-time of every downstream re-run it caused. */
186
284
  downstreamMs: number;
187
285
  }
286
+ /** `click on button#next "Next →"`, `effect "syncTitle"`, `action "save"`, `navigation to /users/:id`, … */
287
+ export declare function formatOrigin(origin: ChangeOrigin): string;
188
288
  export declare function formatRerun(event: RerunEvent): string;
189
289
  export interface Attribution {
190
290
  enable(opts?: AttributionOptions): void;
191
291
  disable(): void;
292
+ /**
293
+ * Deliver records as they complete — see `AttributionRecords`. The bare
294
+ * form is `subscribe("rerun", …)`. Records are the same objects the ring
295
+ * buffers hold (`history()`, `interactions()`, `holds()`, `navigations()`),
296
+ * delivered synchronously from the engine, so a listener must not write
297
+ * signals. All subscriptions are dropped by `disable()`.
298
+ */
192
299
  subscribe(listener: (event: RerunEvent) => void): () => void;
300
+ subscribe<K extends AttributionRecordType>(type: K, listener: (record: AttributionRecords[K]) => void): () => void;
193
301
  history(): readonly RerunEvent[];
194
302
  /** Re-run history for one node — pass a memo/effect accessor or raw node. */
195
303
  why(target: unknown): RerunEvent[];
@@ -211,6 +319,48 @@ export interface Attribution {
211
319
  * thresholded view of the same data.
212
320
  */
213
321
  waterfalls(): readonly WaterfallRecord[];
322
+ /**
323
+ * Every settled transition hold that staged at least one root write since
324
+ * enable() (ring-buffered like history()). Facts, not verdicts: recorded
325
+ * regardless of duration or acknowledgment — the SILENT_HOLD diagnostic is
326
+ * the thresholded, unacknowledged subset.
327
+ */
328
+ holds(): readonly HoldEvent[];
329
+ /**
330
+ * Every navigation a router declared via `withOrigin` since enable()
331
+ * (ring-buffered like history()), settled or not: what route, under which
332
+ * interaction, how many writes, and — once its writes are through — how
333
+ * long that took and how (`committed` in a plain drain, `held` behind
334
+ * route data with the HoldEvent attached, or `superseded` by a later
335
+ * navigation before it landed). Facts for any consumer that wants
336
+ * navigation spans named by route: no router integration needed.
337
+ */
338
+ navigations(): readonly NavigationEvent[];
339
+ /**
340
+ * Every user interaction a runtime declared via `withInteraction` since
341
+ * enable() (ring-buffered like history()), settled or not: what was
342
+ * dispatched, when, what it wrote, the re-runs and creations it caused,
343
+ * the holds its writes waited in and the navigations it performed — and,
344
+ * once all of that is through, how long the person waited (`settledMs`)
345
+ * and how it ended (`idle` / `committed` / `held`). The per-dispatch
346
+ * record `feedback().interactions` folds by name.
347
+ */
348
+ interactions(): readonly InteractionEvent[];
349
+ /**
350
+ * What the user waited on, folded from holds() and the interaction on each
351
+ * re-run: `sources` ranks async sources by the silent time writes spent
352
+ * held behind them (with which affordances answered, how often, and which
353
+ * interactions were held); `interactions` ranks user events by the total
354
+ * time they cost — re-run work caused (long-flush hazard) beside time held
355
+ * (silent-hold hazard). Facts at every duration; SILENT_HOLD is the
356
+ * thresholded verdict. Three more tables round out the picture:
357
+ * `navigations` ranks routes by the time spent held navigating to them
358
+ * (folded from navigations()), `flights` counts each async source's
359
+ * flights and how many were abandoned before landing (the re-ask storm),
360
+ * and `fallbacks` measures how long each loading boundary showed its
361
+ * fallback and how often that was a flash.
362
+ */
363
+ feedback(): AttributionFeedbackTables;
214
364
  /**
215
365
  * Cooperative preload declaration: stamp a flight object (promise or async
216
366
  * iterable) with its true kickoff time BEFORE the reactive graph sees it.
@@ -220,10 +370,11 @@ export interface Attribution {
220
370
  * is then judged against the real start — work already in the air when its
221
371
  * upstream landed is parallel, never a waterfall link. Callable while
222
372
  * attribution is disabled (marks made at navigation time must survive a
223
- * later enable()). Dev-only, like the whole DEV surface.
373
+ * later enable()).
224
374
  */
225
375
  markFlight(flight: object, startedAt?: number): void;
226
376
  format: typeof formatRerun;
377
+ formatOrigin: typeof formatOrigin;
227
378
  }
228
379
  /** One landed flight: its node name, wall duration, and upstream chain. */
229
380
  export interface FlightLink {
@@ -236,4 +387,264 @@ export interface WaterfallRecord {
236
387
  /** Summed wall time of the chain — the serialized cost. */
237
388
  sequentialMs: number;
238
389
  }
390
+ export interface HeldWrite {
391
+ name: string;
392
+ prev?: string;
393
+ value?: string;
394
+ origin?: ChangeOrigin;
395
+ }
396
+ export interface HoldEvent {
397
+ /**
398
+ * When the wait began (`performance.now()` clock): the interaction that
399
+ * performed the held writes when one is known (`interaction.at`) or the
400
+ * first flush that parked them, whichever is earlier. `at + holdMs` is the
401
+ * commit.
402
+ */
403
+ at: number;
404
+ /** Wall time the user waited: `at` to the commit. */
405
+ holdMs: number;
406
+ /**
407
+ * The quiescent tail: from the LAST held write to join (the user's final
408
+ * input) to the commit. Equal to `holdMs` for a single write; shorter when
409
+ * the hold kept taking input. The LONG_HOLD measure.
410
+ */
411
+ tailMs: number;
412
+ /** The user interaction whose writes were held, when the stamp is known. */
413
+ interaction?: ChangeOrigin;
414
+ /**
415
+ * The declared unit of work the held writes belong to — the `navigation`
416
+ * a router described via `withOrigin` — when one is known. What names the
417
+ * hold by route (`navigation to /users/:id`) rather than by signal; the
418
+ * same object as `navigations()[].origin`, so the two join by identity.
419
+ */
420
+ origin?: ChangeOrigin;
421
+ /** Flushes that ended with the hold still open. */
422
+ flushes: number;
423
+ /** Root signal writes staged behind the hold (the user's unanswered input). */
424
+ heldWrites: HeldWrite[];
425
+ /** Async nodes the hold waited on (union across its parked flushes). */
426
+ blockers: string[];
427
+ /**
428
+ * Feedback the graph provably rendered for this hold — an `isPending()`
429
+ * reader, a `latest()` shadow, an optimistic value, an `affects()` mark —
430
+ * one entry per affordance, in the order found. Empty and
431
+ * `paintedDuringHold === 0` is the SILENT_HOLD signature.
432
+ */
433
+ acknowledgements: Acknowledgement[];
434
+ /**
435
+ * Effect callbacks that ran inside the hold's parked flushes. Mainline
436
+ * effects are stashed while a hold is open, so these are lane effects —
437
+ * readers of optimistic values and of `isPending()`/`latest()` companions,
438
+ * i.e. the screen changing in response to the hold. An unrelated effect
439
+ * cannot land here: it waits with everything else.
440
+ */
441
+ paintedDuringHold: number;
442
+ /** The hold was opened (or joined) by an `action()`. */
443
+ action: boolean;
444
+ }
445
+ /**
446
+ * One way the screen acknowledged a hold. `reader` is where it was painted —
447
+ * the owner path of the first effect the census found reading the
448
+ * affordance (`["<App>", "<Feed>", "effect"]`), so a consumer can say WHICH
449
+ * screen answered, not only that one did. Absent when the affordance was
450
+ * registered but the census found no reader through the graph (an optimistic
451
+ * store: its readers are proxy traps, not nodes). `feedback()` ranks
452
+ * acknowledgements by `kind:source` (`isPending:posts`).
453
+ */
454
+ export interface Acknowledgement {
455
+ kind: "isPending" | "latest" | "optimistic" | "affects";
456
+ /** The node the affordance hangs on — the async source for `isPending`/`latest`, the optimistic/affected node otherwise. */
457
+ source: string;
458
+ reader?: string[];
459
+ }
460
+ /** A destination a navigation abandoned when a redirect sent it elsewhere. */
461
+ export interface NavigationHop {
462
+ name?: string;
463
+ to?: string;
464
+ params?: Readonly<Record<string, string | undefined>>;
465
+ /** When the redirect away from it was declared (`performance.now()` clock). */
466
+ at: number;
467
+ }
468
+ export interface NavigationEvent {
469
+ /** The matched route pattern the router gave — `/users/:id`. After a redirect, the final one. */
470
+ name?: string;
471
+ to?: string;
472
+ from?: string;
473
+ params?: Readonly<Record<string, string | undefined>>;
474
+ /** When the navigation was requested (`performance.now()` clock). */
475
+ at: number;
476
+ /** The user interaction it ran under, when known — a link click. */
477
+ interaction?: ChangeOrigin;
478
+ /** Root writes the frame performed, redirect hops included. */
479
+ writes: number;
480
+ /** Destinations abandoned along the way, in order — present only when a redirect occurred. */
481
+ redirects?: NavigationHop[];
482
+ /**
483
+ * Wall time from the request to settle: the end of the drain that committed
484
+ * its writes, or the commit of the hold they waited in. `undefined` while
485
+ * unsettled.
486
+ */
487
+ settledMs?: number;
488
+ outcome?: "committed" | "held" | "superseded";
489
+ /** The hold its writes waited in, when hold tracking recorded one. */
490
+ hold?: HoldEvent;
491
+ /**
492
+ * The frame object its writes were stamped with — `ChangeRecord.origin` on
493
+ * each, `HoldEvent.origin` on the hold. Join key, by identity.
494
+ */
495
+ origin: ChangeOrigin;
496
+ }
497
+ export interface InteractionEvent {
498
+ /** Event type — `click`, `keydown`, `input`… */
499
+ name: string;
500
+ /** The element hit, as the runtime described it — `button#next "Next →"`. */
501
+ target?: string;
502
+ /** Dispatch time (`performance.now()` clock). */
503
+ at: number;
504
+ /** Wall time of the handler itself, dispatch to return. */
505
+ handlerMs: number;
506
+ /** Root writes attributed to the frame: the handler's, and those of frames it opened (a navigation). */
507
+ writes: number;
508
+ /** Re-runs traced back to this interaction while the record was open. */
509
+ runs: number;
510
+ /** Computations created in those runs or in the frame's flushes (the "create 1,000 rows" work). */
511
+ created: number;
512
+ /** Summed self-time of `runs` and `created` (ms). Quantized per run; `settledMs` is the wall clock. */
513
+ runMs: number;
514
+ /** Holds its writes waited in, in settle order. */
515
+ holds: HoldEvent[];
516
+ /** Navigations performed under it, in open order. */
517
+ navigations: NavigationEvent[];
518
+ /**
519
+ * Dispatch to settle: the handler's return when it wrote nothing, the end of
520
+ * the drain that committed its writes, or the commit of the last hold they
521
+ * waited in — whichever came last. `undefined` while unsettled.
522
+ */
523
+ settledMs?: number;
524
+ outcome?: "idle" | "committed" | "held";
525
+ /**
526
+ * The frame object every downstream fact carries — `ChangeOrigin.interaction`
527
+ * on writes and frames, `RerunEvent.interaction`, `HoldEvent.interaction`,
528
+ * `NavigationEvent.interaction`. Join key, by identity.
529
+ */
530
+ origin: ChangeOrigin;
531
+ }
532
+ export interface FeedbackSource {
533
+ /** The async nodes the holds waited on; empty when an action alone kept them open. */
534
+ sources: string[];
535
+ holds: number;
536
+ /** Summed wait across the holds (ms). */
537
+ heldMs: number;
538
+ worstMs: number;
539
+ /** Holds with no acknowledgment at all — the SILENT_HOLD signature, at any duration. */
540
+ silent: number;
541
+ silentMs: number;
542
+ /** Holds whose only acknowledgment was a `latest()` shadow: the input showed, nothing said "loading". */
543
+ latestOnly: number;
544
+ /**
545
+ * Holds whose quiescent tail (last write to join → commit) reached
546
+ * `longHolds.infoMs`, acknowledged or not — the LONG_HOLD signature at the
547
+ * table level. The affordance is not the whole answer there: a fallback
548
+ * (`Loading` keyed with `on`), a preload, a cache, or a faster source is.
549
+ * `longMs` sums the tails.
550
+ */
551
+ long: number;
552
+ longMs: number;
553
+ /** Which affordances answered, and in how many holds — ranked. */
554
+ acknowledgedBy: {
555
+ by: string;
556
+ holds: number;
557
+ }[];
558
+ /** Interactions whose writes were held here, ranked by holds. */
559
+ interactions: {
560
+ interaction: string;
561
+ holds: number;
562
+ }[];
563
+ /** Distinct root writes that were held. */
564
+ writes: string[];
565
+ /** Holds an action opened or joined. */
566
+ actions: number;
567
+ }
568
+ export interface FeedbackInteraction {
569
+ /** `click on button#next "Next →"` — type and target; repeated dispatches fold together. */
570
+ interaction: string;
571
+ /** Distinct dispatches seen (by dispatch time). */
572
+ dispatches: number;
573
+ /** Re-runs traced back to this interaction, and their summed self-time. */
574
+ runs: number;
575
+ selfMs: number;
576
+ /** The most re-run self-time a single dispatch caused — the long-flush hazard. */
577
+ worstDispatchMs: number;
578
+ /** Holds this interaction's writes waited in — the silent-hold hazard. */
579
+ holds: number;
580
+ heldMs: number;
581
+ silentMs: number;
582
+ worstHoldMs: number;
583
+ }
584
+ /** Per async source: how many flights it started, and how many it threw away. */
585
+ export interface FlightStats {
586
+ source: string;
587
+ /** Flights registered (a recompute that produced a new promise/iterable). */
588
+ flights: number;
589
+ /** Flights that landed (whether or not the value changed). */
590
+ landed: number;
591
+ /**
592
+ * Flights superseded by a newer one before landing — the search-as-you-type
593
+ * signature when large: every keystroke asked, most answers were discarded.
594
+ * A debounced/equality-gated derivation between input and fetch is the repair.
595
+ */
596
+ abandoned: number;
597
+ /** Summed and worst wall time of landed flights (ms). */
598
+ landedMs: number;
599
+ worstMs: number;
600
+ }
601
+ /** Per loading boundary: how long, and how briefly, it showed its fallback. */
602
+ export interface FallbackStats {
603
+ /** The boundary's owner path (`<App> › <Feed>`), or `boundary` when unnamed. */
604
+ boundary: string;
605
+ /** Times the fallback was shown. */
606
+ shows: number;
607
+ /** Summed and worst fallback duration (ms) across completed shows. */
608
+ shownMs: number;
609
+ worstMs: number;
610
+ /**
611
+ * Shows shorter than the flash window (default 150ms): a spinner that
612
+ * appeared and vanished — the other end of the SILENT_HOLD spectrum, too
613
+ * much feedback for too little wait. A preload, a cache, or lifting the
614
+ * fetch above the boundary removes the flash.
615
+ */
616
+ flashes: number;
617
+ }
618
+ /**
619
+ * Per route: what navigating to it cost, folded from settled
620
+ * `NavigationEvent`s — the route-level view a router integration used to
621
+ * have to build itself, from the runtime's own facts.
622
+ */
623
+ export interface FeedbackNavigation {
624
+ /** The route pattern (`/users/:id`), or the concrete `to` when the router gave no pattern. */
625
+ name: string;
626
+ navigations: number;
627
+ /** Summed and worst request-to-settle time (ms) across settled navigations. */
628
+ settledMs: number;
629
+ worstMs: number;
630
+ /** Navigations whose writes waited in a hold, and the time they waited. */
631
+ held: number;
632
+ heldMs: number;
633
+ /** Held navigations the screen acknowledged nothing for — the SILENT_HOLD signature. */
634
+ silent: number;
635
+ /** Navigations overwritten by a later one before they landed. */
636
+ superseded: number;
637
+ /** Navigations a redirect sent elsewhere on the way (keyed by where they ended up). */
638
+ redirected: number;
639
+ }
640
+ export interface AttributionFeedbackTables {
641
+ sources: FeedbackSource[];
642
+ interactions: FeedbackInteraction[];
643
+ /** Routes ranked by the time spent held navigating to them, then by total settle time. */
644
+ navigations: FeedbackNavigation[];
645
+ /** Async sources ranked by abandoned flights, then by flights. */
646
+ flights: FlightStats[];
647
+ /** Loading boundaries ranked by flashes, then by time shown. */
648
+ fallbacks: FallbackStats[];
649
+ }
239
650
  export declare const attribution: Attribution;
@@ -105,6 +105,34 @@ export declare const CONFIG_FRESH_READ: number;
105
105
  * (A17). Cleared at commit (the commit IS the reveal); subscribers masked
106
106
  * during the hold are woken by finalizePureQueue's post-revert pass. */
107
107
  export declare const CONFIG_HELD_TRUTH: number;
108
+ /** SLOT node (store leaf): created through `slotSignal` with `_host`/`_key`
109
+ * backrefs baked into the literal. The unobserved sweep dispatches these to
110
+ * the ONE shared hook (`setSlotUnobserved`) instead of a per-node closure
111
+ * held in a per-node extension — store mounts materialize one signal per
112
+ * touched leaf, so per-node allocations (options object, equals closure,
113
+ * unobserved closure, NodeExtension) were the measured create-floor bytes
114
+ * (warm dbmon profile: store node machinery ~36% + GC ~29%). */
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
+ /** In-flight async node whose inputs were PUBLISHED while it was pending: a
126
+ * batch or transaction committed with the node still `STATUS_PENDING` (an
127
+ * unobserved flight, #3305), so the inputs are on screen and the node's
128
+ * committed `_value` is stale against them. Governs read()'s reveal
129
+ * carve-out: a stale (render) reader in some OTHER transaction may show a
130
+ * foreign-held pending node's committed value — parallel transactions, no
131
+ * entanglement — only while that value is coherent with the visible frame,
132
+ * i.e. while the flight's inputs are themselves held (unpublished) and not
133
+ * lane-revealed. Set by `commitPendingNodes`; cleared when the node next
134
+ * enters pending fresh (a new flight from a settled state). */
135
+ export declare const CONFIG_INPUTS_PUBLISHED: number;
108
136
  export declare const STATUS_NONE = 0;
109
137
  export declare const STATUS_PENDING: number;
110
138
  export declare const STATUS_ERROR: number;
@@ -113,6 +141,9 @@ export declare const EFFECT_PURE = 0;
113
141
  export declare const EFFECT_RENDER = 1;
114
142
  export declare const EFFECT_USER = 2;
115
143
  export declare const EFFECT_TRACKED = 3;
144
+ /** OR-ed into the `type` a lane passes to its effect runners: lane runs
145
+ * apply ahead of their transaction and are exempt from ownership parking. */
146
+ export declare const LANE_RUN = 4;
116
147
  export declare const NOT_PENDING: {};
117
148
  export declare const NO_SNAPSHOT: {};
118
149
  /**
@@ -54,6 +54,20 @@ export declare function setEffectStatusNotify(fn: NonNullable<typeof effectStatu
54
54
  export declare function statusNotifierOf(el: any): ((this: any, status?: number, error?: any) => void) | undefined;
55
55
  export declare function signal<T>(v: T, options?: NodeOptions<T>): Signal<T>;
56
56
  export declare function signal<T>(v: T, options?: NodeOptions<T>, firewall?: Computed<any>): FirewallSignal<T>;
57
+ /** The shared slot-node unobserved handler — a live binding read directly by
58
+ * the sweep sites (no wrapper frame, no null check: a CONFIG_SLOT_NODE node
59
+ * existing implies the store module loaded and registered the hook). */
60
+ export declare let slotUnobservedHook: (node: Signal<any>) => void;
61
+ /** Install the shared slot-node unobserved handler (store module, once). */
62
+ export declare function setSlotUnobserved(fn: (node: Signal<any>) => void): void;
63
+ /** Release a firewall child the store no longer addresses (unobserved sweep
64
+ * dropped it from its target's cache): unlink it from the chain so the
65
+ * projection stops retaining it and its last value. The node keeps its own
66
+ * `_nextChild` so a walk that is mid-chain on it still terminates. Nodes in
67
+ * `_companionChildren` stay there — companions are permanent by contract
68
+ * and snap through that set, not the chain. */
69
+ export declare function unlinkFirewallChild(node: Signal<any>): void;
70
+ export declare function slotSignal<T>(v: T, equals: (a: T, b: T) => boolean, host: object, key: PropertyKey, acc: boolean, firewall?: Computed<unknown> | null): Signal<T>;
57
71
  export declare function optimisticSignal<T>(v: T, options?: NodeOptions<T>): Signal<T>;
58
72
  export declare function optimisticComputed<T>(fn: (prev?: T) => T | PromiseLike<T> | AsyncIterable<T>, options?: NodeOptions<T>): Computed<T>;
59
73
  export declare function isEqual<T>(a: T, b: T): boolean;
@@ -115,9 +129,11 @@ export declare const READ_SLOW: unique symbol;
115
129
  * installed at first until() call — apps that never use until() shake it.
116
130
  */
117
131
  export declare function notifyAuthoritativeObservers(el: Signal<any> | Computed<any>): void;
118
- /** Installs the until() machinery hook. Idempotent; called by until() before
119
- * any authoritative-view read happens (same late-binding contract as the
120
- * optimistic engine). */
132
+ /** Installs the authoritative-reader wakeup hook. Idempotent; called by every
133
+ * creator of a CONFIG_AUTHORITATIVE_READ computation — until() and refresh() —
134
+ * before its first read (same late-binding contract as the optimistic engine;
135
+ * the gating bit is only ever set by such a read, so the `!` call sites are
136
+ * safe once every setter installs, #3303). */
121
137
  export declare function installAuthoritativeRead(): void;
122
138
  export declare function readNodeFast<T>(el: Signal<T>): T | typeof READ_SLOW;
123
139
  export declare function read<T>(el: Signal<T> | Computed<T>): T;