@descryy/runtime-identity-grounding 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * React 19's replacement for `_debugSource`, and the one rule this package
3
+ * needs in order to read it.
4
+ *
5
+ * ## What actually changed between the majors
6
+ *
7
+ * Measured directly, in a real Chromium, against React 18.3.1 and 19.2.8
8
+ * development bundles of the same authored source
9
+ * (`fixture-apps/react-identity`):
10
+ *
11
+ * | fiber field | React 18.3.1 dev | React 19.2.8 dev | either, production |
12
+ * | --- | --- | --- | --- |
13
+ * | `_debugSource` | `{ fileName: "src/app.jsx", lineNumber, columnNumber }` | **absent** | absent |
14
+ * | `_debugStack` | absent | an `Error`, message `react-stack-top-frame` | absent |
15
+ * | `_debugOwner` | present | present | absent |
16
+ * | `type.name` | authored | authored | minified |
17
+ *
18
+ * So React 19 did not move the source location, it **deleted** it and left
19
+ * a captured stack in its place. The stack's frames point into *compiled*
20
+ * output — the bundle the browser actually ran — which is why this path
21
+ * costs a source-map resolution that React 18's does not, and why the two
22
+ * mechanisms report different `SourceLocation.reliability` values
23
+ * (`self-contained` versus `side-artifact-resolved`). That difference is
24
+ * not cosmetic: one is the application telling us where it came from, the
25
+ * other is us translating through a file that can be missing or stale.
26
+ *
27
+ * ## The rule: drop React's own element-creation frame, and only that
28
+ *
29
+ * The captured stack's innermost frame is React's own JSX entry point:
30
+ *
31
+ * ```text
32
+ * Error: react-stack-top-frame
33
+ * at exports.jsxDEV (http://…/dev.js:21690:32)
34
+ * at PayButton (http://…/dev.js:21714:62) <- the site we want
35
+ * at Object.react_stack_bottom_frame (…)
36
+ * ```
37
+ *
38
+ * The second frame is the JSX call site inside the owning component, which
39
+ * is exactly the fact React 18's `_debugSource` carries. Verified against
40
+ * the fixture: React 18 reports `src/app.jsx:20`, and mapping React 19's
41
+ * second frame through the bundle's source map lands on `src/app.jsx:20`
42
+ * as well. `react-fixtures.test.ts` asserts that equality rather than
43
+ * asserting each major separately, because two independently-written
44
+ * expectations can both be wrong in the same direction.
45
+ *
46
+ * ## Why not `stripInitiatorWrapperFrame`'s positional rule
47
+ *
48
+ * `@descryy/runtime-browser`'s initiator capture drops its first frame *by
49
+ * position*, and argues correctly that its own wrapper is deterministically
50
+ * innermost because it captured the stack itself. Nothing here captured
51
+ * this stack — React did — so the same reasoning is not available. Worse,
52
+ * `createV8StackTraceParser` **omits** frames it cannot parse a
53
+ * `line:column` from, so "the first frame of the parsed trace" is not
54
+ * reliably "the first frame of the raw text" either.
55
+ *
56
+ * The rule used instead is by name, against a closed list of React's own
57
+ * element-creation entry points, and it **refuses** when the innermost
58
+ * frame is not one of them. A refusal here costs a source location; a
59
+ * positional guess that is off by one costs a confident wrong file and
60
+ * line, attributed to a component that did not create the element. Rule 2.
61
+ */
62
+ import type { StackTrace } from "@descryy/runtime-contracts";
63
+ /**
64
+ * React's own element-creation entry points, matched against the **last
65
+ * dotted segment** of a frame's function name so that V8's `exports.jsxDEV`
66
+ * and a bare `jsxDEV` are the same frame.
67
+ *
68
+ * `jsxDEV` / `jsxDEVImpl` are the automatic runtime's development entry;
69
+ * `jsx` / `jsxs` its production ones (listed for completeness — a
70
+ * production build has no `_debugStack` to reach this code with);
71
+ * `createElement` / `createElementWithValidation` the classic runtime's.
72
+ *
73
+ * Deliberately a closed list rather than a "does this frame live inside
74
+ * react-dom" heuristic: the latter needs a resolved path for the frame,
75
+ * which is exactly the thing that may not resolve.
76
+ */
77
+ export declare const REACT_ELEMENT_CREATION_FRAMES: readonly ["jsxDEV", "jsxDEVImpl", "jsxWithValidation", "jsx", "jsxs", "createElement", "createElementWithValidation"];
78
+ export type OwnerFrameSelection = {
79
+ readonly ok: true;
80
+ readonly index: number;
81
+ } | {
82
+ readonly ok: false;
83
+ readonly reason: string;
84
+ };
85
+ /**
86
+ * Index of the frame that is the JSX call site inside the owning component,
87
+ * or a refusal naming why the stack could not be read that way.
88
+ */
89
+ export declare function selectOwnerFrame(stack: StackTrace): OwnerFrameSelection;
90
+ //# sourceMappingURL=owner-stack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-stack.d.ts","sourceRoot":"","sources":["../src/owner-stack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,6BAA6B,uHAQhC,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAOpD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,mBAAmB,CAsCvE"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * React 19's replacement for `_debugSource`, and the one rule this package
3
+ * needs in order to read it.
4
+ *
5
+ * ## What actually changed between the majors
6
+ *
7
+ * Measured directly, in a real Chromium, against React 18.3.1 and 19.2.8
8
+ * development bundles of the same authored source
9
+ * (`fixture-apps/react-identity`):
10
+ *
11
+ * | fiber field | React 18.3.1 dev | React 19.2.8 dev | either, production |
12
+ * | --- | --- | --- | --- |
13
+ * | `_debugSource` | `{ fileName: "src/app.jsx", lineNumber, columnNumber }` | **absent** | absent |
14
+ * | `_debugStack` | absent | an `Error`, message `react-stack-top-frame` | absent |
15
+ * | `_debugOwner` | present | present | absent |
16
+ * | `type.name` | authored | authored | minified |
17
+ *
18
+ * So React 19 did not move the source location, it **deleted** it and left
19
+ * a captured stack in its place. The stack's frames point into *compiled*
20
+ * output — the bundle the browser actually ran — which is why this path
21
+ * costs a source-map resolution that React 18's does not, and why the two
22
+ * mechanisms report different `SourceLocation.reliability` values
23
+ * (`self-contained` versus `side-artifact-resolved`). That difference is
24
+ * not cosmetic: one is the application telling us where it came from, the
25
+ * other is us translating through a file that can be missing or stale.
26
+ *
27
+ * ## The rule: drop React's own element-creation frame, and only that
28
+ *
29
+ * The captured stack's innermost frame is React's own JSX entry point:
30
+ *
31
+ * ```text
32
+ * Error: react-stack-top-frame
33
+ * at exports.jsxDEV (http://…/dev.js:21690:32)
34
+ * at PayButton (http://…/dev.js:21714:62) <- the site we want
35
+ * at Object.react_stack_bottom_frame (…)
36
+ * ```
37
+ *
38
+ * The second frame is the JSX call site inside the owning component, which
39
+ * is exactly the fact React 18's `_debugSource` carries. Verified against
40
+ * the fixture: React 18 reports `src/app.jsx:20`, and mapping React 19's
41
+ * second frame through the bundle's source map lands on `src/app.jsx:20`
42
+ * as well. `react-fixtures.test.ts` asserts that equality rather than
43
+ * asserting each major separately, because two independently-written
44
+ * expectations can both be wrong in the same direction.
45
+ *
46
+ * ## Why not `stripInitiatorWrapperFrame`'s positional rule
47
+ *
48
+ * `@descryy/runtime-browser`'s initiator capture drops its first frame *by
49
+ * position*, and argues correctly that its own wrapper is deterministically
50
+ * innermost because it captured the stack itself. Nothing here captured
51
+ * this stack — React did — so the same reasoning is not available. Worse,
52
+ * `createV8StackTraceParser` **omits** frames it cannot parse a
53
+ * `line:column` from, so "the first frame of the parsed trace" is not
54
+ * reliably "the first frame of the raw text" either.
55
+ *
56
+ * The rule used instead is by name, against a closed list of React's own
57
+ * element-creation entry points, and it **refuses** when the innermost
58
+ * frame is not one of them. A refusal here costs a source location; a
59
+ * positional guess that is off by one costs a confident wrong file and
60
+ * line, attributed to a component that did not create the element. Rule 2.
61
+ */
62
+ /**
63
+ * React's own element-creation entry points, matched against the **last
64
+ * dotted segment** of a frame's function name so that V8's `exports.jsxDEV`
65
+ * and a bare `jsxDEV` are the same frame.
66
+ *
67
+ * `jsxDEV` / `jsxDEVImpl` are the automatic runtime's development entry;
68
+ * `jsx` / `jsxs` its production ones (listed for completeness — a
69
+ * production build has no `_debugStack` to reach this code with);
70
+ * `createElement` / `createElementWithValidation` the classic runtime's.
71
+ *
72
+ * Deliberately a closed list rather than a "does this frame live inside
73
+ * react-dom" heuristic: the latter needs a resolved path for the frame,
74
+ * which is exactly the thing that may not resolve.
75
+ */
76
+ export const REACT_ELEMENT_CREATION_FRAMES = [
77
+ "jsxDEV",
78
+ "jsxDEVImpl",
79
+ "jsxWithValidation",
80
+ "jsx",
81
+ "jsxs",
82
+ "createElement",
83
+ "createElementWithValidation",
84
+ ];
85
+ function lastSegment(functionName) {
86
+ const parts = functionName.split(".");
87
+ return parts[parts.length - 1] ?? functionName;
88
+ }
89
+ /**
90
+ * Index of the frame that is the JSX call site inside the owning component,
91
+ * or a refusal naming why the stack could not be read that way.
92
+ */
93
+ export function selectOwnerFrame(stack) {
94
+ const first = stack.frames[0];
95
+ if (first === undefined) {
96
+ return { ok: false, reason: "the captured owner stack parsed to zero frames, so there is no call site in it to read." };
97
+ }
98
+ const functionName = first.location.functionName;
99
+ if (functionName === null) {
100
+ return {
101
+ ok: false,
102
+ reason: "the innermost frame of the captured owner stack has no function name, so it cannot be confirmed to be " +
103
+ "React's own element-creation frame. Dropping it on position alone would shift every subsequent frame " +
104
+ "by one and attribute the element to whichever component happened to land there.",
105
+ };
106
+ }
107
+ if (!REACT_ELEMENT_CREATION_FRAMES.includes(lastSegment(functionName))) {
108
+ return {
109
+ ok: false,
110
+ reason: `the innermost frame of the captured owner stack is "${functionName}", which is not one of React's own ` +
111
+ `element-creation entry points (${REACT_ELEMENT_CREATION_FRAMES.join(", ")}). A minified React build, or a ` +
112
+ "React version whose owner-stack shape this rule has not been measured against — refused rather than " +
113
+ "guessed at by position.",
114
+ };
115
+ }
116
+ if (stack.frames.length < 2) {
117
+ return {
118
+ ok: false,
119
+ reason: "the captured owner stack contains React's element-creation frame and nothing after it, so no application " +
120
+ "call site was recorded (or none of the remaining frames carried a parseable line and column).",
121
+ };
122
+ }
123
+ return { ok: true, index: 1 };
124
+ }
125
+ //# sourceMappingURL=owner-stack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-stack.js","sourceRoot":"","sources":["../src/owner-stack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAIH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,QAAQ;IACR,YAAY;IACZ,mBAAmB;IACnB,KAAK;IACL,MAAM;IACN,eAAe;IACf,6BAA6B;CACrB,CAAC;AAMX,SAAS,WAAW,CAAC,YAAoB;IACvC,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,YAAY,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yFAAyF,EAAE,CAAC;IAC1H,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC;IACjD,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EACJ,wGAAwG;gBACxG,uGAAuG;gBACvG,iFAAiF;SACpF,CAAC;IACJ,CAAC;IAED,IAAI,CAAE,6BAAmD,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;QAC9F,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EACJ,uDAAuD,YAAY,qCAAqC;gBACxG,kCAAkC,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,kCAAkC;gBAC5G,sGAAsG;gBACtG,yBAAyB;SAC5B,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EACJ,2GAA2G;gBAC3G,+FAA+F;SAClG,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAChC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@descryy/runtime-identity-grounding",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "Identity grounding: resolves an on-screen DOM element back to the React component that produced it, and to that component's authored file and line. Injects through @descryy/runtime-browser's existing page.addInitScript seam; refuses by name on a production React build rather than guessing.",
6
+ "license": "UNLICENSED",
7
+ "engines": {
8
+ "node": ">=22.5"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "registry": "https://registry.npmjs.org",
21
+ "access": "public"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -b"
25
+ },
26
+ "dependencies": {
27
+ "@descryy/runtime-contracts": "0.2.0",
28
+ "@descryy/runtime-adapter-typescript": "0.2.0",
29
+ "@descryy/runtime-browser": "0.2.0",
30
+ "playwright-core": "^1.62.1"
31
+ },
32
+ "devDependencies": {
33
+ "@descryy/runtime-graph-correlator": "*",
34
+ "@descryy/runtime-controller": "*"
35
+ }
36
+ }