@solidjs/signals 2.0.0-rc.8 → 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 (90) hide show
  1. package/dist/dev-shared.js +1399 -285
  2. package/dist/dev.attribution.js +459 -307
  3. package/dist/dev.js +1864 -435
  4. package/dist/observe/affects.js +3 -1
  5. package/dist/observe/attribution.js +7 -1
  6. package/dist/observe/boundaries.js +209 -154
  7. package/dist/observe/core/action.js +18 -8
  8. package/dist/observe/core/async.js +220 -110
  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 +23 -1
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +262 -485
  14. package/dist/observe/core/constants.js +34 -1
  15. package/dist/observe/core/context.js +3 -3
  16. package/dist/observe/core/core.js +867 -367
  17. package/dist/observe/core/dev.js +78 -17
  18. package/dist/observe/core/effect.js +67 -51
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/external.js +4 -4
  21. package/dist/observe/core/graph.js +37 -37
  22. package/dist/observe/core/heap.js +45 -45
  23. package/dist/observe/core/invariants.js +2 -0
  24. package/dist/observe/core/lanes.js +86 -49
  25. package/dist/observe/core/optimistic.js +242 -95
  26. package/dist/observe/core/owner.js +98 -84
  27. package/dist/observe/core/scheduler.js +543 -305
  28. package/dist/observe/core/verdict.js +247 -129
  29. package/dist/observe/index.js +7 -3
  30. package/dist/observe/map.js +126 -124
  31. package/dist/observe/signals.js +33 -17
  32. package/dist/observe/store/index.js +2 -0
  33. package/dist/observe/store/next/optimistic.js +141 -132
  34. package/dist/observe/store/next/projection.js +34 -21
  35. package/dist/observe/store/next/reconcile.js +58 -56
  36. package/dist/observe/store/next/store.js +260 -146
  37. package/dist/observe/store/store.js +5 -3
  38. package/dist/observe/store/utils.js +948 -135
  39. package/dist/prod/attribution.js +26 -17
  40. package/dist/prod/boundaries.js +128 -76
  41. package/dist/prod/core/action.js +16 -8
  42. package/dist/prod/core/async.js +251 -143
  43. package/dist/prod/core/constants.js +34 -1
  44. package/dist/prod/core/context.js +3 -3
  45. package/dist/prod/core/core.js +843 -347
  46. package/dist/prod/core/dev.js +17 -1
  47. package/dist/prod/core/effect.js +48 -34
  48. package/dist/prod/core/error-hooks.js +71 -0
  49. package/dist/prod/core/external.js +4 -4
  50. package/dist/prod/core/graph.js +37 -37
  51. package/dist/prod/core/heap.js +45 -45
  52. package/dist/prod/core/lanes.js +90 -53
  53. package/dist/prod/core/optimistic.js +247 -100
  54. package/dist/prod/core/owner.js +57 -45
  55. package/dist/prod/core/scheduler.js +543 -305
  56. package/dist/prod/core/verdict.js +245 -127
  57. package/dist/prod/index.js +7 -3
  58. package/dist/prod/map.js +112 -112
  59. package/dist/prod/signals.js +28 -12
  60. package/dist/prod/store/next/optimistic.js +135 -128
  61. package/dist/prod/store/next/projection.js +31 -20
  62. package/dist/prod/store/next/store.js +325 -252
  63. package/dist/prod/store/store.js +2 -2
  64. package/dist/prod/store/utils.js +946 -135
  65. package/dist/types/attribution.d.ts +7 -2
  66. package/dist/types/attribution.prod.d.ts +10 -1
  67. package/dist/types/boundaries.d.ts +10 -1
  68. package/dist/types/core/action.d.ts +12 -5
  69. package/dist/types/core/attribution-costs.d.ts +35 -0
  70. package/dist/types/core/attribution-feedback.d.ts +133 -0
  71. package/dist/types/core/attribution-hooks.d.ts +28 -3
  72. package/dist/types/core/attribution-queries.d.ts +10 -0
  73. package/dist/types/core/attribution.d.ts +51 -172
  74. package/dist/types/core/constants.d.ts +33 -0
  75. package/dist/types/core/core.d.ts +186 -5
  76. package/dist/types/core/dev.d.ts +129 -9
  77. package/dist/types/core/error-hooks.d.ts +71 -0
  78. package/dist/types/core/index.d.ts +3 -1
  79. package/dist/types/core/invariants.d.ts +4 -0
  80. package/dist/types/core/lanes.d.ts +31 -4
  81. package/dist/types/core/scheduler.d.ts +95 -2
  82. package/dist/types/core/types.d.ts +3 -0
  83. package/dist/types/index.d.ts +2 -2
  84. package/dist/types/signals.d.ts +8 -0
  85. package/dist/types/store/index.d.ts +2 -1
  86. package/dist/types/store/next/optimistic.d.ts +1 -1
  87. package/dist/types/store/next/store.d.ts +6 -5
  88. package/dist/types/store/next/target.d.ts +1 -1
  89. package/dist/types/store/utils.d.ts +177 -6
  90. package/package.json +1 -1
@@ -10,6 +10,11 @@
10
10
  * `attribution.prod.ts`, an inert engine with the same surface, so app code
11
11
  * can import it unconditionally.
12
12
  */
13
- export { attribution } from "./core/attribution.js";
14
- export type { Acknowledgement, Attribution, AttributionFeedbackTables, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightLink, FlightStats, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, ScopeCost, WaterfallRecord, WriteCost } from "./core/attribution.js";
13
+ export { attribution, formatOrigin, formatRerun } from "./core/attribution.js";
14
+ export { costs } from "./core/attribution-costs.js";
15
+ export { feedback } from "./core/attribution-feedback.js";
16
+ export { subscriptions, why } from "./core/attribution-queries.js";
17
+ export type { Acknowledgement, Attribution, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FlightLink, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, WaterfallRecord } from "./core/attribution.js";
18
+ export type { AttributionCostTables, ScopeCost, WriteCost } from "./core/attribution-costs.js";
19
+ export type { AttributionFeedbackTables, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightStats } from "./core/attribution-feedback.js";
15
20
  export type { InteractionRef, NavigationRef, OriginRef } from "./core/attribution-hooks.js";
@@ -8,6 +8,15 @@
8
8
  * real engine's interface so the two cannot drift.
9
9
  */
10
10
  import type { Attribution } from "./core/attribution.js";
11
+ import type * as Engine from "./attribution.js";
11
12
  export declare const attribution: Attribution;
12
- export type { Acknowledgement, Attribution, AttributionFeedbackTables, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightLink, FlightStats, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, ScopeCost, WaterfallRecord, WriteCost } from "./core/attribution.js";
13
+ export declare const costs: typeof Engine.costs;
14
+ export declare const feedback: typeof Engine.feedback;
15
+ export declare const why: typeof Engine.why;
16
+ export declare const subscriptions: typeof Engine.subscriptions;
17
+ export declare const formatRerun: typeof Engine.formatRerun;
18
+ export declare const formatOrigin: typeof Engine.formatOrigin;
19
+ export type { Acknowledgement, Attribution, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FlightLink, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, WaterfallRecord } from "./core/attribution.js";
20
+ export type { AttributionCostTables, ScopeCost, WriteCost } from "./core/attribution-costs.js";
21
+ export type { AttributionFeedbackTables, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightStats } from "./core/attribution-feedback.js";
13
22
  export type { InteractionRef, NavigationRef, OriginRef } from "./core/attribution-hooks.js";
@@ -1,4 +1,4 @@
1
- import { Queue, type Computed, type Effect } from "./core/index.js";
1
+ import { Queue, type Computed, type Effect, type Owner } from "./core/index.js";
2
2
  import type { Signal } from "./core/index.js";
3
3
  import { type Accessor } from "./signals.js";
4
4
  export interface BoundaryComputed<T> extends Computed<T> {
@@ -45,9 +45,18 @@ export declare class CollectionQueue extends Queue {
45
45
  _initialized: boolean;
46
46
  _onFn: (() => any) | undefined;
47
47
  _prevOn: any;
48
+ /** The boundary's owner — where a `caught` report locates itself, set before the children are built (a creation-time throw arrives before `_tree`). */
49
+ _owner?: Owner;
48
50
  constructor(type: number);
49
51
  run(type: number): void;
52
+ /** The `on` key, or ON_INIT when it throws. Evaluated mid-propagation,
53
+ * inside the pending node's own pass: read as a spectator so the key's
54
+ * sources never become that node's dependencies (#3528). */
55
+ _readOn(): any;
50
56
  notify(node: Effect<any>, type: number, flags: number, error?: any): boolean;
57
+ /** Is `reporter` live and routed to this boundary — under it, with no
58
+ * collecting pending-type boundary in between (`reporterBlocksSource`'s test)? */
59
+ _holds(reporter: Computed<any>): boolean;
51
60
  _checkSources(): void;
52
61
  }
53
62
  /**
@@ -23,15 +23,22 @@
23
23
  * `yield` is the transaction-safe suspension point: the action waits for a
24
24
  * yielded promise and re-enters the transaction before running the code after
25
25
  * it. A plain `await` does NOT — the runtime has no hook into an async
26
- * generator's internal await continuations, so writes to fresh signals
27
- * between an `await` and the next `yield` escape the transaction and commit
28
- * immediately. `await` is still the ergonomic choice for typed results; just
29
- * put a bare `yield` before any writes that follow it:
26
+ * generator's internal await continuations, so code between an `await` and
27
+ * the next `yield` runs OUTSIDE the transaction: writes to fresh signals
28
+ * commit immediately, and anything that creates a reader there `until()`,
29
+ * `latest()`, a memo or effect, a mount is created mainline, where a read of
30
+ * this action's held state makes it born held (A29): staged with the
31
+ * transaction and replayed at its commit. For `until()` that commit is the
32
+ * settle its own promise holds open (#3482). `await` is still the ergonomic
33
+ * choice for typed results; just put a bare `yield` before any write or
34
+ * reader creation that follows it — including the expression of the next
35
+ * `yield`, which is evaluated before the step re-enters:
30
36
  *
31
37
  * ```ts
32
38
  * const saved = await api.createTodo(text); // typed result
33
- * yield; // re-enter the transaction before writing
39
+ * yield; // re-enter the transaction before writing or reading
34
40
  * setTodos(t => { ... });
41
+ * yield until(() => todos.some(t => t.id === saved.id));
35
42
  * ```
36
43
  *
37
44
  * (For the same reason, don't call `flush()` inside an action body — it
@@ -0,0 +1,35 @@
1
+ export interface ScopeCost {
2
+ name: string;
3
+ kind: "effect" | "memo";
4
+ runs: number;
5
+ selfMs: number;
6
+ /**
7
+ * Self-time of PLAIN, non-held runs that produced an unchanged value —
8
+ * the recoverable number. Overlay runs (optimistic/transition) are never
9
+ * counted here: an optimistic recompute landing back on the committed
10
+ * value is the mechanism working, not waste.
11
+ */
12
+ wastedMs: number;
13
+ /** Self-time spent in optimistic/transition (overlay) runs. */
14
+ overlayMs: number;
15
+ }
16
+ export interface WriteCost {
17
+ /** Root cause name (a signal write, async landing, or refresh target). */
18
+ name: string;
19
+ /** Number of downstream re-runs this root triggered. */
20
+ runs: number;
21
+ /** Summed self-time of every downstream re-run it caused. */
22
+ downstreamMs: number;
23
+ }
24
+ export interface AttributionCostTables {
25
+ /** Ranked by self-time. */
26
+ scopes: ScopeCost[];
27
+ /** Ranked by the total downstream re-run time each root write caused. */
28
+ writes: WriteCost[];
29
+ }
30
+ /**
31
+ * Aggregated cost tables since `enable()`: `scopes` ranked by self-time
32
+ * (with `wastedMs` = time spent on unchanged-value runs), `writes` ranked by
33
+ * total downstream re-run time each root write caused.
34
+ */
35
+ export declare function costs(): AttributionCostTables;
@@ -0,0 +1,133 @@
1
+ export interface FeedbackSource {
2
+ /** The async nodes the holds waited on; empty when an action alone kept them open. */
3
+ sources: string[];
4
+ holds: number;
5
+ /** Summed wait across the holds (ms). */
6
+ heldMs: number;
7
+ worstMs: number;
8
+ /** Holds with no acknowledgment at all — the SILENT_HOLD signature, at any duration. */
9
+ silent: number;
10
+ silentMs: number;
11
+ /** Holds whose only acknowledgment was a `latest()` shadow: the input showed, nothing said "loading". */
12
+ latestOnly: number;
13
+ /**
14
+ * Holds whose quiescent tail (last write to join → commit) reached
15
+ * `longHolds.infoMs`, acknowledged or not — the LONG_HOLD signature at the
16
+ * table level. The affordance is not the whole answer there: a fallback
17
+ * (`Loading` keyed with `on`), a preload, a cache, or a faster source is.
18
+ * `longMs` sums the tails.
19
+ */
20
+ long: number;
21
+ longMs: number;
22
+ /** Which affordances answered, and in how many holds — ranked. */
23
+ acknowledgedBy: {
24
+ by: string;
25
+ holds: number;
26
+ }[];
27
+ /** Interactions whose writes were held here, ranked by holds. */
28
+ interactions: {
29
+ interaction: string;
30
+ holds: number;
31
+ }[];
32
+ /** Distinct root writes that were held. */
33
+ writes: string[];
34
+ /** Holds an action opened or joined. */
35
+ actions: number;
36
+ }
37
+ export interface FeedbackInteraction {
38
+ /** `click on button#next "Next →"` — type and target; repeated dispatches fold together. */
39
+ interaction: string;
40
+ /** Distinct dispatches seen (by dispatch time). */
41
+ dispatches: number;
42
+ /** Re-runs traced back to this interaction, and their summed self-time. */
43
+ runs: number;
44
+ selfMs: number;
45
+ /** The most re-run self-time a single dispatch caused — the long-flush hazard. */
46
+ worstDispatchMs: number;
47
+ /** Holds this interaction's writes waited in — the silent-hold hazard. */
48
+ holds: number;
49
+ heldMs: number;
50
+ silentMs: number;
51
+ worstHoldMs: number;
52
+ }
53
+ /** Per async source: how many flights it started, and how many it threw away. */
54
+ export interface FlightStats {
55
+ source: string;
56
+ /** Flights registered (a recompute that produced a new promise/iterable). */
57
+ flights: number;
58
+ /** Flights that landed (whether or not the value changed). */
59
+ landed: number;
60
+ /**
61
+ * Flights superseded by a newer one before landing — the search-as-you-type
62
+ * signature when large: every keystroke asked, most answers were discarded.
63
+ * A debounced/equality-gated derivation between input and fetch is the repair.
64
+ */
65
+ abandoned: number;
66
+ /** Summed and worst wall time of landed flights (ms). */
67
+ landedMs: number;
68
+ worstMs: number;
69
+ }
70
+ /** Per loading boundary: how long, and how briefly, it showed its fallback. */
71
+ export interface FallbackStats {
72
+ /** The boundary's owner path (`<App> › <Feed>`), or `boundary` when unnamed. */
73
+ boundary: string;
74
+ /** Times the fallback was shown. */
75
+ shows: number;
76
+ /** Summed and worst fallback duration (ms) across completed shows. */
77
+ shownMs: number;
78
+ worstMs: number;
79
+ /**
80
+ * Shows shorter than the flash window (default 150ms): a spinner that
81
+ * appeared and vanished — the other end of the SILENT_HOLD spectrum, too
82
+ * much feedback for too little wait. A preload, a cache, or lifting the
83
+ * fetch above the boundary removes the flash.
84
+ */
85
+ flashes: number;
86
+ }
87
+ /**
88
+ * Per route: what navigating to it cost, folded from settled
89
+ * `NavigationEvent`s — the route-level view a router integration used to
90
+ * have to build itself, from the runtime's own facts.
91
+ */
92
+ export interface FeedbackNavigation {
93
+ /** The route pattern (`/users/:id`), or the concrete `to` when the router gave no pattern. */
94
+ name: string;
95
+ navigations: number;
96
+ /** Summed and worst request-to-settle time (ms) across settled navigations. */
97
+ settledMs: number;
98
+ worstMs: number;
99
+ /** Navigations whose writes waited in a hold, and the time they waited. */
100
+ held: number;
101
+ heldMs: number;
102
+ /** Held navigations the screen acknowledged nothing for — the SILENT_HOLD signature. */
103
+ silent: number;
104
+ /** Navigations overwritten by a later one before they landed. */
105
+ superseded: number;
106
+ /** Navigations a redirect sent elsewhere on the way (keyed by where they ended up). */
107
+ redirected: number;
108
+ }
109
+ export interface AttributionFeedbackTables {
110
+ sources: FeedbackSource[];
111
+ interactions: FeedbackInteraction[];
112
+ /** Routes ranked by the time spent held navigating to them, then by total settle time. */
113
+ navigations: FeedbackNavigation[];
114
+ /** Async sources ranked by abandoned flights, then by flights. */
115
+ flights: FlightStats[];
116
+ /** Loading boundaries ranked by flashes, then by time shown. */
117
+ fallbacks: FallbackStats[];
118
+ }
119
+ /**
120
+ * What the user waited on, folded from holds and the interaction on each
121
+ * re-run: `sources` ranks async sources by the silent time writes spent held
122
+ * behind them (with which affordances answered, how often, and which
123
+ * interactions were held); `interactions` ranks user events by the total time
124
+ * they cost — re-run work caused (long-flush hazard) beside time held
125
+ * (silent-hold hazard). Facts at every duration; SILENT_HOLD is the
126
+ * thresholded verdict. Three more tables round out the picture: `navigations`
127
+ * ranks routes by the time spent held navigating to them (folded from settled
128
+ * navigations), `flights` counts each async source's flights and how many
129
+ * were abandoned before landing (the re-ask storm), and `fallbacks` measures
130
+ * how long each loading boundary showed its fallback and how often that was a
131
+ * flash.
132
+ */
133
+ export declare function feedback(): AttributionFeedbackTables;
@@ -1,5 +1,6 @@
1
+ import type { ChangeOrigin } from "./attribution.js";
1
2
  import type { Transition } from "./scheduler.js";
2
- import type { Computed, Signal } from "./types.js";
3
+ import type { Computed, Owner, Signal } from "./types.js";
3
4
  /**
4
5
  * Observe-tier hook points for the reactive core.
5
6
  *
@@ -127,9 +128,12 @@ export interface AttributionHooks {
127
128
  * `prevTotal` leaves in the old one. Containers above 64 leaves are not
128
129
  * announced. Fired per written key from the write channel's notify. The
129
130
  * engine decides whether the replacement was a spread-copy worth a
130
- * diagnostic.
131
+ * diagnostic. `owner` is the owner the store root was created under
132
+ * (undefined when unrecorded): the finding is about the store, not about
133
+ * whoever wrote it, so an `OBSERVE.exclude`d panel's store stays silent
134
+ * however its writes arrive.
131
135
  */
132
- storeReplaced(path: string, isArray: boolean, total: number, unchanged: number, prevTotal: number): void;
136
+ storeReplaced(path: string, isArray: boolean, total: number, unchanged: number, prevTotal: number, owner: Owner | null | undefined): void;
133
137
  /**
134
138
  * A `mapArray` update both disposed and created rows: `removed` are the
135
139
  * items whose rows were disposed, `created` the items that got new rows,
@@ -147,6 +151,18 @@ export interface AttributionHooks {
147
151
  * registers / last one clears), not per flush.
148
152
  */
149
153
  boundaryFallback(boundary: object, tree: Computed<any> | undefined, shown: boolean): void;
154
+ /**
155
+ * The one query on the surface: the provenance a root write performed at
156
+ * this moment would be stamped with — the innermost open frame (an effect
157
+ * callback, an action step, a navigation), the interaction the handler
158
+ * runs under, or, inside a recompute, the origin of the change that caused
159
+ * it — or `undefined` when none applies (external). For a runtime that
160
+ * records a fact of its own beside the engine's records — `@solidjs/web`'s
161
+ * `"call"` record stamps the server-function call it is about to make —
162
+ * so the fact joins the engine's interaction and navigation records by the
163
+ * identity of the object returned, not by time.
164
+ */
165
+ currentOrigin(): ChangeOrigin | undefined;
150
166
  }
151
167
  /** A user interaction, as a rendering runtime describes it to `withInteraction`. */
152
168
  export interface InteractionRef {
@@ -238,3 +254,12 @@ export declare function withInteraction<T>(ref: InteractionRef, fn: () => T): T;
238
254
  * ```
239
255
  */
240
256
  export declare function withOrigin<T>(ref: OriginRef, fn: () => T): T;
257
+ /**
258
+ * The provenance a root write performed now would carry, as the installed
259
+ * engine sees it (`AttributionHooks.currentOrigin`); `undefined` with no
260
+ * engine, or when nothing is in effect. Reachable as
261
+ * `OBSERVE.attribution.currentOrigin` — how a runtime stamps a fact of its
262
+ * own (a server-function call) with the interaction or navigation it ran
263
+ * for, so an observer joins the two by identity.
264
+ */
265
+ export declare function currentOrigin(): ChangeOrigin | undefined;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Point queries over the engine's live state — the devtools/console view of
3
+ * one scope. Their own module so a records-only consumer never ships them;
4
+ * they read the engine's ring buffer and the graph, and register nothing.
5
+ */
6
+ import { type RerunEvent } from "./attribution.js";
7
+ /** Re-run history for one node — pass a memo/effect accessor or raw node. */
8
+ export declare function why(target: unknown): RerunEvent[];
9
+ /** Current dependency names of one scope — the devtools subscription view. */
10
+ export declare function subscriptions(target: unknown): string[];
@@ -1,4 +1,4 @@
1
- import type { Computed } from "./types.js";
1
+ import type { Computed, Signal } from "./types.js";
2
2
  /**
3
3
  * "Why did this run" attribution — the engine behind
4
4
  * `@solidjs/signals/attribution`.
@@ -92,7 +92,16 @@ export interface RerunEvent {
92
92
  nodeRuns: number;
93
93
  nodeKind: "effect" | "memo";
94
94
  nodeName: string;
95
- 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;
96
105
  /**
97
106
  * The deps that changed since this node's previous run. Empty means the
98
107
  * re-run was not triggered by a tracked value change (creation-adjacent
@@ -260,32 +269,45 @@ export interface AttributionRecords {
260
269
  navigation: NavigationEvent;
261
270
  }
262
271
  export type AttributionRecordType = keyof AttributionRecords;
263
- export interface ScopeCost {
264
- name: string;
265
- kind: "effect" | "memo";
266
- runs: number;
267
- selfMs: number;
268
- /**
269
- * Self-time of PLAIN, non-held runs that produced an unchanged value —
270
- * the recoverable number. Overlay runs (optimistic/transition) are never
271
- * counted here: an optimistic recompute landing back on the committed
272
- * value is the mechanism working, not waste.
273
- */
274
- wastedMs: number;
275
- /** Self-time spent in optimistic/transition (overlay) runs. */
276
- overlayMs: number;
277
- }
278
- export interface WriteCost {
279
- /** Root cause name (a signal write, async landing, or refresh target). */
280
- name: string;
281
- /** Number of downstream re-runs this root triggered. */
282
- runs: number;
283
- /** Summed self-time of every downstream re-run it caused. */
284
- downstreamMs: number;
272
+ /** @internal The engine's clock: `performance.now()` where it exists. */
273
+ export declare const now: () => number;
274
+ /**
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.
282
+ */
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;
285
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;
286
298
  /** `click on button#next "Next →"`, `effect "syncTitle"`, `action "save"`, `navigation to /users/:id`, … */
287
299
  export declare function formatOrigin(origin: ChangeOrigin): string;
288
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
+ */
289
311
  export interface Attribution {
290
312
  enable(opts?: AttributionOptions): void;
291
313
  disable(): void;
@@ -299,19 +321,6 @@ export interface Attribution {
299
321
  subscribe(listener: (event: RerunEvent) => void): () => void;
300
322
  subscribe<K extends AttributionRecordType>(type: K, listener: (record: AttributionRecords[K]) => void): () => void;
301
323
  history(): readonly RerunEvent[];
302
- /** Re-run history for one node — pass a memo/effect accessor or raw node. */
303
- why(target: unknown): RerunEvent[];
304
- /** Current dependency names of one scope — the devtools subscription view. */
305
- subscriptions(target: unknown): string[];
306
- /**
307
- * Aggregated cost tables since enable(): `scopes` ranked by self-time
308
- * (with `wastedMs` = time spent on unchanged-value runs), `writes` ranked
309
- * by total downstream re-run time each root write caused.
310
- */
311
- costs(): {
312
- scopes: ScopeCost[];
313
- writes: WriteCost[];
314
- };
315
324
  /**
316
325
  * Every graph-provable sequential flight chain observed since enable()
317
326
  * (ring-buffered like history()). Facts, not verdicts: chains are recorded
@@ -346,21 +355,6 @@ export interface Attribution {
346
355
  * record `feedback().interactions` folds by name.
347
356
  */
348
357
  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;
364
358
  /**
365
359
  * Cooperative preload declaration: stamp a flight object (promise or async
366
360
  * iterable) with its true kickoff time BEFORE the reactive graph sees it.
@@ -373,8 +367,6 @@ export interface Attribution {
373
367
  * later enable()).
374
368
  */
375
369
  markFlight(flight: object, startedAt?: number): void;
376
- format: typeof formatRerun;
377
- formatOrigin: typeof formatOrigin;
378
370
  }
379
371
  /** One landed flight: its node name, wall duration, and upstream chain. */
380
372
  export interface FlightLink {
@@ -457,6 +449,9 @@ export interface Acknowledgement {
457
449
  source: string;
458
450
  reader?: string[];
459
451
  }
452
+ /** `isLongHold` — the tail outlasted `longHolds.infoMs`. */
453
+ /** @internal */
454
+ export declare function isLongHold(event: HoldEvent): boolean;
460
455
  /** A destination a navigation abandoned when a redirect sent it elsewhere. */
461
456
  export interface NavigationHop {
462
457
  name?: string;
@@ -529,122 +524,6 @@ export interface InteractionEvent {
529
524
  */
530
525
  origin: ChangeOrigin;
531
526
  }
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
- }
527
+ /** @internal No affordance answered and nothing painted while held. */
528
+ export declare function isSilentHold(event: HoldEvent): boolean;
650
529
  export declare const attribution: Attribution;
@@ -122,6 +122,18 @@ export declare const CONFIG_SLOT_NODE: number;
122
122
  * paths (asyncWrite, transition-held recompute); cleared by a fresh optimistic
123
123
  * write (a new override re-masks) and by the revert. */
124
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;
125
137
  /** In-flight async node whose inputs were PUBLISHED while it was pending: a
126
138
  * batch or transaction committed with the node still `STATUS_PENDING` (an
127
139
  * unobserved flight, #3305), so the inputs are on screen and the node's
@@ -133,6 +145,27 @@ export declare const CONFIG_OVERRIDE_SUPERSEDED: number;
133
145
  * lane-revealed. Set by `commitPendingNodes`; cleared when the node next
134
146
  * enters pending fresh (a new flight from a settled state). */
135
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;
136
169
  export declare const STATUS_NONE = 0;
137
170
  export declare const STATUS_PENDING: number;
138
171
  export declare const STATUS_ERROR: number;