rerender-lens 0.3.0 → 0.5.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.
- package/CHANGELOG.md +118 -0
- package/README.md +192 -353
- package/dist/{budget-COBu7jBU.d.cts → budget-BnJlYwrV.d.ts} +20 -10
- package/dist/{budget-LkNjGtRc.d.ts → budget-CRswn-nC.d.cts} +20 -10
- package/dist/cli.cjs +248 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +248 -31
- package/dist/cli.js.map +1 -1
- package/dist/{devtools-BHGUUo-p.d.ts → devtools-BYWat7nQ.d.ts} +17 -3
- package/dist/{devtools-DmhWiWcN.d.cts → devtools-DJi_6AfJ.d.cts} +17 -3
- package/dist/index.cjs +635 -109
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +131 -8
- package/dist/index.d.ts +131 -8
- package/dist/index.js +620 -110
- package/dist/index.js.map +1 -1
- package/dist/jest-setup.cjs +1673 -0
- package/dist/jest-setup.cjs.map +1 -0
- package/dist/jest-setup.d.cts +8 -0
- package/dist/jest-setup.d.ts +8 -0
- package/dist/jest-setup.js +1671 -0
- package/dist/jest-setup.js.map +1 -0
- package/dist/jest.cjs +1760 -0
- package/dist/jest.cjs.map +1 -0
- package/dist/jest.d.cts +23 -0
- package/dist/jest.d.ts +23 -0
- package/dist/jest.js +1751 -0
- package/dist/jest.js.map +1 -0
- package/dist/{notifiers-BjjGSHpp.d.ts → notifiers-DwKlvv4H.d.cts} +8 -4
- package/dist/{notifiers-BGQtWKfX.d.cts → notifiers-FNzKHs0a.d.ts} +8 -4
- package/dist/playwright.cjs +235 -30
- package/dist/playwright.cjs.map +1 -1
- package/dist/playwright.d.cts +8 -6
- package/dist/playwright.d.ts +8 -6
- package/dist/playwright.js +235 -31
- package/dist/playwright.js.map +1 -1
- package/dist/relay.cjs +10 -2
- package/dist/relay.cjs.map +1 -1
- package/dist/relay.js +10 -2
- package/dist/relay.js.map +1 -1
- package/dist/rerender-lens.iife.js +1100 -337
- package/dist/runner-report-B8Sc3FZO.d.cts +46 -0
- package/dist/runner-report-CW8X5RwK.d.ts +46 -0
- package/dist/setup.cjs +308 -95
- package/dist/setup.cjs.map +1 -1
- package/dist/setup.js +308 -95
- package/dist/setup.js.map +1 -1
- package/dist/{types-BzUEVkxJ.d.cts → types-DW5-N2XH.d.cts} +55 -2
- package/dist/{types-BzUEVkxJ.d.ts → types-DW5-N2XH.d.ts} +55 -2
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vitest-setup.cjs +452 -87
- package/dist/vitest-setup.cjs.map +1 -1
- package/dist/vitest-setup.d.cts +4 -4
- package/dist/vitest-setup.d.ts +4 -4
- package/dist/vitest-setup.js +452 -87
- package/dist/vitest-setup.js.map +1 -1
- package/dist/vitest.cjs +515 -123
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +21 -39
- package/dist/vitest.d.ts +21 -39
- package/dist/vitest.js +516 -124
- package/dist/vitest.js.map +1 -1
- package/package.json +41 -5
- package/panel/panel.css +1 -34
- package/panel/panel.js +865 -713
package/panel/panel.js
CHANGED
|
@@ -1,188 +1,288 @@
|
|
|
1
1
|
/* Built from extension/src/panel.ts by `npm run build`; do not edit by hand. */
|
|
2
2
|
"use strict";
|
|
3
3
|
(() => {
|
|
4
|
-
//
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
var PROTOCOL = 2;
|
|
12
|
-
var KIND_LABEL = {
|
|
13
|
-
"deep-equal": "equal by value",
|
|
14
|
-
function: "new function",
|
|
15
|
-
element: "equal element",
|
|
16
|
-
different: "changed",
|
|
17
|
-
added: "added",
|
|
18
|
-
removed: "removed"
|
|
19
|
-
};
|
|
20
|
-
var AVOIDABLE_KINDS = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
|
|
21
|
-
var FN_PREFIX = "\u0192 ";
|
|
22
|
-
var MAX_REPORTS = 2e3;
|
|
23
|
-
var MAX_PER_NODE = 200;
|
|
24
|
-
var MAX_COMMITS = 500;
|
|
25
|
-
var ROW_H = 22;
|
|
26
|
-
var ITEM_H = 20;
|
|
27
|
-
var OVERSCAN = 8;
|
|
28
|
-
var FALLBACK_VIEWPORT = 800;
|
|
29
|
-
function el(tag, attrs, children) {
|
|
30
|
-
const node = document.createElement(tag);
|
|
31
|
-
if (attrs) {
|
|
32
|
-
for (const k of Object.keys(attrs)) {
|
|
33
|
-
const v = attrs[k];
|
|
34
|
-
if (k === "class") node.className = String(v);
|
|
35
|
-
else if (k === "text") node.textContent = String(v);
|
|
36
|
-
else if (k.startsWith("on")) {
|
|
37
|
-
if (typeof v === "function") node.addEventListener(k.slice(2), v);
|
|
38
|
-
} else if (v === true) node.setAttribute(k, "");
|
|
39
|
-
else if (v !== void 0 && v !== null && v !== false) node.setAttribute(k, String(v));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (children) {
|
|
43
|
-
for (const c of [].concat(children)) if (c != null) node.append(c);
|
|
44
|
-
}
|
|
45
|
-
return node;
|
|
4
|
+
// src/diff.ts
|
|
5
|
+
var REACT_ELEMENT = /* @__PURE__ */ Symbol.for("react.element");
|
|
6
|
+
var REACT_TRANSITIONAL_ELEMENT = /* @__PURE__ */ Symbol.for("react.transitional.element");
|
|
7
|
+
function isReactElement(v) {
|
|
8
|
+
if (typeof v !== "object" || v === null) return false;
|
|
9
|
+
const t = v.$$typeof;
|
|
10
|
+
return t === REACT_ELEMENT || t === REACT_TRANSITIONAL_ELEMENT;
|
|
46
11
|
}
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
var fmtMs = (n) => typeof n === "number" && Number.isFinite(n) ? `${n.toFixed(1)} ms` : "";
|
|
53
|
-
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
54
|
-
var componentList = (m) => [...m].map(([c, n]) => `<${c}>${n > 1 ? " \xD7" + n : ""}`).join(", ");
|
|
55
|
-
function changesOf(report) {
|
|
56
|
-
return [].concat(report.propChanges || [], report.stateChanges || [], report.hookChanges || []);
|
|
12
|
+
function isPlainObject(v) {
|
|
13
|
+
if (typeof v !== "object" || v === null) return false;
|
|
14
|
+
const proto = Object.getPrototypeOf(v);
|
|
15
|
+
return proto === Object.prototype || proto === null;
|
|
57
16
|
}
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
17
|
+
function remember(seen, a, b) {
|
|
18
|
+
let set = seen.get(a);
|
|
19
|
+
if (!set) {
|
|
20
|
+
set = /* @__PURE__ */ new Set();
|
|
21
|
+
seen.set(a, set);
|
|
22
|
+
}
|
|
23
|
+
if (set.has(b)) return true;
|
|
24
|
+
set.add(b);
|
|
25
|
+
return false;
|
|
63
26
|
}
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
component: p.component,
|
|
72
|
-
instanceId: typeof p.instanceId === "number" ? p.instanceId : 0,
|
|
73
|
-
commitId: typeof p.commitId === "number" ? p.commitId : 0,
|
|
74
|
-
renderCount: typeof p.renderCount === "number" ? p.renderCount : 0,
|
|
75
|
-
trigger: typeof p.trigger === "string" ? p.trigger : "parent",
|
|
76
|
-
avoidable: !!p.avoidable,
|
|
77
|
-
props: { prev: isRecord(props.prev) ? props.prev : {}, next: isRecord(props.next) ? props.next : {} },
|
|
78
|
-
propChanges: arr(p.propChanges),
|
|
79
|
-
stateChanges: arr(p.stateChanges),
|
|
80
|
-
hookChanges: arr(p.hookChanges),
|
|
81
|
-
parent,
|
|
82
|
-
owner: typeof p.owner === "string" ? p.owner : null,
|
|
83
|
-
path: Array.isArray(p.path) ? p.path.filter((x) => typeof x === "string") : [],
|
|
84
|
-
reasons: Array.isArray(p.reasons) ? p.reasons.filter((x) => typeof x === "string") : [],
|
|
85
|
-
time: typeof p.time === "number" ? p.time : 0,
|
|
86
|
-
receivedAt: typeof p.receivedAt === "number" ? p.receivedAt : 0
|
|
27
|
+
var scope = null;
|
|
28
|
+
var DEFAULT_DIFF_BUDGET = 5e4;
|
|
29
|
+
function beginDiffScope(budget = 2e5) {
|
|
30
|
+
const previous = scope;
|
|
31
|
+
scope = { cache: /* @__PURE__ */ new WeakMap(), remaining: budget, exhausted: false };
|
|
32
|
+
return () => {
|
|
33
|
+
scope = previous;
|
|
87
34
|
};
|
|
88
|
-
if (typeof p.selfDuration === "number") r.selfDuration = p.selfDuration;
|
|
89
|
-
if (typeof p.treeDuration === "number") r.treeDuration = p.treeDuration;
|
|
90
|
-
if (typeof p.memoized === "boolean") r.memoized = p.memoized;
|
|
91
|
-
if (typeof p.commitPriority === "string") r.commitPriority = p.commitPriority;
|
|
92
|
-
if (Array.isArray(p.hookState)) r.hookState = p.hookState.filter((h) => isRecord(h) && typeof h.path === "string");
|
|
93
|
-
if (Array.isArray(p.contexts)) r.contexts = p.contexts.filter((c) => isRecord(c) && typeof c.name === "string");
|
|
94
|
-
if (isRecord(p.state)) r.state = p.state;
|
|
95
|
-
if (Array.isArray(p.updaters)) r.updaters = p.updaters.filter((u) => typeof u === "string");
|
|
96
|
-
if (p.commitCause === "effect-after-commit" || p.commitCause === "suspense-resolved") r.commitCause = p.commitCause;
|
|
97
|
-
if (typeof p.afterCommit === "number") r.afterCommit = p.afterCommit;
|
|
98
|
-
if (typeof p.key === "string") r.key = p.key;
|
|
99
|
-
if (isRecord(p.source) && typeof p.source.fileName === "string") r.source = p.source;
|
|
100
|
-
return r;
|
|
101
35
|
}
|
|
102
|
-
function
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
if (typeof
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
36
|
+
function deepEqual(a, b, seen = /* @__PURE__ */ new Map()) {
|
|
37
|
+
if (Object.is(a, b)) return true;
|
|
38
|
+
if (typeof a !== typeof b) return false;
|
|
39
|
+
if (typeof a !== "object" || a === null || b === null) return false;
|
|
40
|
+
if (!scope) {
|
|
41
|
+
const end = beginDiffScope(DEFAULT_DIFF_BUDGET);
|
|
42
|
+
try {
|
|
43
|
+
return deepEqual(a, b, seen);
|
|
44
|
+
} finally {
|
|
45
|
+
end();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const sc = scope;
|
|
49
|
+
const objA = a;
|
|
50
|
+
const objB = b;
|
|
51
|
+
const hit = sc.cache.get(objA)?.get(objB);
|
|
52
|
+
if (hit !== void 0) return hit;
|
|
53
|
+
if (--sc.remaining < 0) {
|
|
54
|
+
sc.exhausted = true;
|
|
55
|
+
return false;
|
|
109
56
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
57
|
+
const result = deepEqualObjects(objA, objB, seen);
|
|
58
|
+
let m = sc.cache.get(objA);
|
|
59
|
+
if (!m) {
|
|
60
|
+
m = /* @__PURE__ */ new WeakMap();
|
|
61
|
+
sc.cache.set(objA, m);
|
|
62
|
+
}
|
|
63
|
+
m.set(objB, result);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
function deepEqualObjects(a, b, seen) {
|
|
67
|
+
const objA = a;
|
|
68
|
+
const objB = b;
|
|
69
|
+
if (remember(seen, objA, objB)) return true;
|
|
70
|
+
if (a instanceof Date) return b instanceof Date && a.getTime() === b.getTime();
|
|
71
|
+
if (a instanceof RegExp) return b instanceof RegExp && a.source === b.source && a.flags === b.flags;
|
|
72
|
+
if (Array.isArray(a)) {
|
|
73
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
74
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i], seen)) return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(b)) return false;
|
|
78
|
+
if (ArrayBuffer.isView(a)) {
|
|
79
|
+
if (!ArrayBuffer.isView(b) || a.constructor !== b.constructor) return false;
|
|
80
|
+
const ta = a;
|
|
81
|
+
const tb = b;
|
|
82
|
+
if (ta.length !== tb.length) return false;
|
|
83
|
+
for (let i = 0; i < ta.length; i++) if (ta[i] !== tb[i]) return false;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (a instanceof Map) {
|
|
87
|
+
if (!(b instanceof Map) || a.size !== b.size) return false;
|
|
88
|
+
for (const [k, v] of a) {
|
|
89
|
+
if (!b.has(k) || !deepEqual(v, b.get(k), seen)) return false;
|
|
122
90
|
}
|
|
123
|
-
return
|
|
91
|
+
return true;
|
|
124
92
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
93
|
+
if (a instanceof Set) {
|
|
94
|
+
if (!(b instanceof Set) || a.size !== b.size) return false;
|
|
95
|
+
if (a.size > 50) {
|
|
96
|
+
for (const v of a) if (!b.has(v)) return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
outer: for (const v of a) {
|
|
100
|
+
if (b.has(v)) continue;
|
|
101
|
+
for (const w of b) if (deepEqual(v, w, seen)) continue outer;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
130
105
|
}
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
106
|
+
if (isReactElement(a)) {
|
|
107
|
+
if (!isReactElement(b)) return false;
|
|
108
|
+
return a.type === b.type && a.key === b.key && deepEqual(a.props, b.props, seen);
|
|
109
|
+
}
|
|
110
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
111
|
+
const ka = Object.keys(a);
|
|
112
|
+
const kb = Object.keys(b);
|
|
113
|
+
if (ka.length !== kb.length) return false;
|
|
114
|
+
const rb = b;
|
|
115
|
+
for (const k of ka) {
|
|
116
|
+
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
117
|
+
if (!deepEqual(a[k], rb[k], seen)) return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
138
122
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (d.open && !d.querySelector("pre")) d.append(el("pre", { text: JSON.stringify(obj, null, 2) }));
|
|
145
|
-
},
|
|
146
|
-
{ once: true }
|
|
147
|
-
);
|
|
148
|
-
return d;
|
|
123
|
+
var AVOIDABLE_KINDS = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
|
|
124
|
+
function joinPath(base, key) {
|
|
125
|
+
if (typeof key === "number") return `${base}[${key}]`;
|
|
126
|
+
if (base === "") return key;
|
|
127
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? `${base}.${key}` : `${base}[${JSON.stringify(key)}]`;
|
|
149
128
|
}
|
|
150
|
-
function firstDifferentPath(a, b,
|
|
151
|
-
if (
|
|
152
|
-
if (!isRecord(a) || !isRecord(b)) return base || "(value)";
|
|
153
|
-
if (Array.isArray(a) !== Array.isArray(b)) return base || "(value)";
|
|
129
|
+
function firstDifferentPath(a, b, path = "", depth = 0) {
|
|
130
|
+
if (depth > 8) return path || "(value)";
|
|
154
131
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
155
|
-
if (a.length !== b.length) return
|
|
132
|
+
if (a.length !== b.length) return path ? `${path}.length` : "length";
|
|
156
133
|
for (let i = 0; i < a.length; i++) {
|
|
157
|
-
|
|
158
|
-
if (p) return p;
|
|
134
|
+
if (!deepEqual(a[i], b[i])) return firstDifferentPath(a[i], b[i], joinPath(path, i), depth + 1);
|
|
159
135
|
}
|
|
160
136
|
return null;
|
|
161
137
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
138
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
139
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
140
|
+
for (const k of keys) {
|
|
141
|
+
if (!deepEqual(a[k], b[k])) return firstDifferentPath(a[k], b[k], joinPath(path, k), depth + 1);
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
166
144
|
}
|
|
167
|
-
return null;
|
|
145
|
+
return deepEqual(a, b) ? null : path || "(value)";
|
|
168
146
|
}
|
|
169
|
-
function diffLeaves(a, b, limit = 20,
|
|
170
|
-
if (out.length >= limit || a
|
|
171
|
-
if (!isRecord(a) || !isRecord(b) || Array.isArray(a) !== Array.isArray(b)) {
|
|
172
|
-
out.push({ path: base || "(value)", prev: a, next: b });
|
|
173
|
-
return out;
|
|
174
|
-
}
|
|
147
|
+
function diffLeaves(a, b, limit = 20, path = "", out = []) {
|
|
148
|
+
if (out.length >= limit || Object.is(a, b)) return out;
|
|
175
149
|
if (Array.isArray(a) && Array.isArray(b)) {
|
|
176
150
|
const n = Math.max(a.length, b.length);
|
|
177
|
-
for (let i = 0; i < n && out.length < limit; i++) diffLeaves(a[i], b[i], limit,
|
|
151
|
+
for (let i = 0; i < n && out.length < limit; i++) diffLeaves(a[i], b[i], limit, joinPath(path, i), out);
|
|
178
152
|
return out;
|
|
179
153
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
154
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
155
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)])) {
|
|
156
|
+
if (out.length >= limit) break;
|
|
157
|
+
diffLeaves(a[k], b[k], limit, joinPath(path, k), out);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
183
160
|
}
|
|
161
|
+
if (!deepEqual(a, b)) out.push({ path: path || "(value)", prev: a, next: b });
|
|
184
162
|
return out;
|
|
185
163
|
}
|
|
164
|
+
|
|
165
|
+
// src/causes.ts
|
|
166
|
+
var isAncestorReport = (anc, r) => anc.path.length < r.path.length && r.path[anc.path.length] === anc.component && anc.path.every((p, i) => r.path[i] === p);
|
|
167
|
+
var bump = (m, key, n = 1) => void m.set(key, (m.get(key) || 0) + n);
|
|
168
|
+
function indexByComponent(reports) {
|
|
169
|
+
const index = /* @__PURE__ */ new Map();
|
|
170
|
+
for (const r of reports) {
|
|
171
|
+
const list = index.get(r.component);
|
|
172
|
+
if (list) list.push(r);
|
|
173
|
+
else index.set(r.component, [r]);
|
|
174
|
+
}
|
|
175
|
+
return index;
|
|
176
|
+
}
|
|
177
|
+
function rootCauseOf(r, commitReports, index = indexByComponent(commitReports)) {
|
|
178
|
+
let cur = r;
|
|
179
|
+
const seen = /* @__PURE__ */ new Set([r]);
|
|
180
|
+
while (cur.trigger === "parent" && cur.parent) {
|
|
181
|
+
const parent = cur.parent;
|
|
182
|
+
const candidates = index.get(parent.name);
|
|
183
|
+
const p = candidates && candidates.find((x) => isAncestorReport(x, cur));
|
|
184
|
+
if (!p || seen.has(p)) return { name: parent.name, trigger: parent.trigger, report: null };
|
|
185
|
+
seen.add(p);
|
|
186
|
+
cur = p;
|
|
187
|
+
}
|
|
188
|
+
return cur === r ? null : { name: cur.component, trigger: cur.trigger, report: cur };
|
|
189
|
+
}
|
|
190
|
+
function rootCausesOf(reports) {
|
|
191
|
+
const roots = /* @__PURE__ */ new Map();
|
|
192
|
+
const rootByReport = /* @__PURE__ */ new Map();
|
|
193
|
+
const index = indexByComponent(reports);
|
|
194
|
+
let avoidable = 0;
|
|
195
|
+
let wasted = 0;
|
|
196
|
+
for (const r of reports) {
|
|
197
|
+
if (!r.avoidable) continue;
|
|
198
|
+
avoidable++;
|
|
199
|
+
if (typeof r.selfDuration === "number") wasted += r.selfDuration;
|
|
200
|
+
const root = rootCauseOf(r, reports, index);
|
|
201
|
+
const name = root ? root.name : r.parent && r.parent.name || "(unknown)";
|
|
202
|
+
const trigger = root ? root.trigger : r.parent && r.parent.trigger || "parent";
|
|
203
|
+
rootByReport.set(r, name);
|
|
204
|
+
let agg = roots.get(name);
|
|
205
|
+
if (!agg) {
|
|
206
|
+
agg = { name, trigger, count: 0, components: /* @__PURE__ */ new Map() };
|
|
207
|
+
roots.set(name, agg);
|
|
208
|
+
}
|
|
209
|
+
agg.count++;
|
|
210
|
+
bump(agg.components, r.component);
|
|
211
|
+
}
|
|
212
|
+
return { roots: [...roots.values()].sort((a, b) => b.count - a.count), rootByReport, avoidable, wasted };
|
|
213
|
+
}
|
|
214
|
+
function analyzeCommit(reports) {
|
|
215
|
+
const first = reports[0];
|
|
216
|
+
return {
|
|
217
|
+
id: first && typeof first.commitId === "number" ? first.commitId : 0,
|
|
218
|
+
total: reports.length,
|
|
219
|
+
...rootCausesOf(reports),
|
|
220
|
+
contexts: contextAttribution(reports),
|
|
221
|
+
reports
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function contextAttribution(reports) {
|
|
225
|
+
const byCtx = /* @__PURE__ */ new Map();
|
|
226
|
+
for (const r of reports) {
|
|
227
|
+
for (const c of r.hookChanges || []) {
|
|
228
|
+
if (c.hook !== "useContext" && !/^useContext/.test(c.path)) continue;
|
|
229
|
+
const m = /useContext\((.*)\)/.exec(c.path);
|
|
230
|
+
const name = m && m[1] ? m[1] : c.path;
|
|
231
|
+
let agg = byCtx.get(name);
|
|
232
|
+
if (!agg) {
|
|
233
|
+
agg = { name, consumers: 0, avoidable: 0, components: /* @__PURE__ */ new Map(), commits: /* @__PURE__ */ new Set(), providers: /* @__PURE__ */ new Map(), changedKeys: /* @__PURE__ */ new Set(), totalKeys: 0 };
|
|
234
|
+
byCtx.set(name, agg);
|
|
235
|
+
}
|
|
236
|
+
agg.consumers++;
|
|
237
|
+
if (AVOIDABLE_KINDS.has(c.kind)) agg.avoidable++;
|
|
238
|
+
bump(agg.components, r.component);
|
|
239
|
+
agg.commits.add(typeof r.commitId === "number" ? r.commitId : 0);
|
|
240
|
+
if (c.provider && c.provider.component) bump(agg.providers, c.provider.component);
|
|
241
|
+
if (c.changedKeys) for (const k of c.changedKeys) agg.changedKeys.add(k);
|
|
242
|
+
if (typeof c.totalKeys === "number") agg.totalKeys = Math.max(agg.totalKeys, c.totalKeys);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return [...byCtx.values()].sort((a, b) => b.consumers - a.consumers);
|
|
246
|
+
}
|
|
247
|
+
function cascadeTree(reports) {
|
|
248
|
+
const root = { name: "", children: /* @__PURE__ */ new Map(), report: null };
|
|
249
|
+
for (const r of reports) {
|
|
250
|
+
let node = root;
|
|
251
|
+
for (const seg of r.path.concat([r.component])) {
|
|
252
|
+
let next = node.children.get(seg);
|
|
253
|
+
if (!next) {
|
|
254
|
+
next = { name: seg, children: /* @__PURE__ */ new Map(), report: null };
|
|
255
|
+
node.children.set(seg, next);
|
|
256
|
+
}
|
|
257
|
+
node = next;
|
|
258
|
+
}
|
|
259
|
+
if (!node.report || r.avoidable) node.report = r;
|
|
260
|
+
node.count = (node.count || 0) + 1;
|
|
261
|
+
if (r.avoidable) node.avoidable = (node.avoidable || 0) + 1;
|
|
262
|
+
}
|
|
263
|
+
return root;
|
|
264
|
+
}
|
|
265
|
+
function rootCauseSummary(name, commits, analyze) {
|
|
266
|
+
const analyzeFn = analyze || ((_, reports) => analyzeCommit(reports));
|
|
267
|
+
const out = { name, trigger: "parent", commits: [], total: 0, components: /* @__PURE__ */ new Map(), affected: [] };
|
|
268
|
+
for (const [key, reports] of commits) {
|
|
269
|
+
const analysis = analyzeFn(key, reports);
|
|
270
|
+
const root = analysis.roots.find((x) => x.name === name);
|
|
271
|
+
if (!root) continue;
|
|
272
|
+
out.trigger = root.trigger;
|
|
273
|
+
out.commits.push({ key, analysis, count: root.count, components: root.components });
|
|
274
|
+
out.total += root.count;
|
|
275
|
+
for (const [c, n] of root.components) bump(out.components, c, n);
|
|
276
|
+
for (const r of reports) if (r.avoidable && analysis.rootByReport.get(r) === name) out.affected.push(r);
|
|
277
|
+
}
|
|
278
|
+
out.commits.reverse();
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/fixes.ts
|
|
283
|
+
var MAX_FIX_REPORTS = 50;
|
|
284
|
+
var rootOf = (path) => path.split(/[.[]/)[0] || path;
|
|
285
|
+
var identifier = (name) => /^[A-Za-z_$][\w$]*$/.test(name) ? name : "value";
|
|
186
286
|
function shortValue(v, max = 60) {
|
|
187
287
|
let s;
|
|
188
288
|
try {
|
|
@@ -193,12 +293,11 @@
|
|
|
193
293
|
if (s === void 0) s = String(v);
|
|
194
294
|
return s.length > max ? s.slice(0, max - 3) + "..." : s;
|
|
195
295
|
}
|
|
196
|
-
var identifier = (name) => /^[A-Za-z_$][\w$]*$/.test(name) ? name : "value";
|
|
197
296
|
function fixesFor(r) {
|
|
198
297
|
const out = [];
|
|
199
298
|
const ownerName = r.owner || r.parent && r.parent.name || null;
|
|
200
|
-
const
|
|
201
|
-
const
|
|
299
|
+
const propChanges = r.propChanges || [];
|
|
300
|
+
const changes = [...propChanges, ...r.stateChanges || [], ...r.hookChanges || []];
|
|
202
301
|
if (r.avoidable && (changes.length === 0 || r.memoized === false)) {
|
|
203
302
|
const identical = changes.length === 0;
|
|
204
303
|
out.push({
|
|
@@ -217,9 +316,10 @@ export const ${r.component} = memo(function ${r.component}(props) {
|
|
|
217
316
|
// class components: extend PureComponent instead`
|
|
218
317
|
});
|
|
219
318
|
}
|
|
220
|
-
for (const c of
|
|
319
|
+
for (const c of propChanges) {
|
|
320
|
+
if (!AVOIDABLE_KINDS.has(c.kind)) continue;
|
|
221
321
|
const owner = ownerName || "?";
|
|
222
|
-
const root = c.path
|
|
322
|
+
const root = rootOf(c.path);
|
|
223
323
|
const id = identifier(root);
|
|
224
324
|
if (root === "children" && (c.kind === "element" || c.kind === "deep-equal")) {
|
|
225
325
|
out.push({
|
|
@@ -241,9 +341,7 @@ const children = useMemo(() => (
|
|
|
241
341
|
// static children: hoist them to module scope
|
|
242
342
|
const STATIC = <em>hi</em>;`
|
|
243
343
|
});
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
if (c.kind === "function") {
|
|
344
|
+
} else if (c.kind === "function") {
|
|
247
345
|
out.push({
|
|
248
346
|
kind: "useCallback",
|
|
249
347
|
owner,
|
|
@@ -293,7 +391,7 @@ const ${id.toUpperCase()} = ${shortValue(c.next, 80)};`
|
|
|
293
391
|
});
|
|
294
392
|
}
|
|
295
393
|
}
|
|
296
|
-
for (const c of [
|
|
394
|
+
for (const c of [...r.stateChanges || [], ...r.hookChanges || []]) {
|
|
297
395
|
const isContext = c.hook === "useContext" || /^useContext/.test(c.path);
|
|
298
396
|
const ctxName = isContext ? (/useContext\((.*)\)/.exec(c.path) || [])[1] || "Context" : "";
|
|
299
397
|
const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;
|
|
@@ -317,17 +415,16 @@ const ${c.changedKeys[0]} = useContextSelector(${ctxName}, (v) => v.${c.changedK
|
|
|
317
415
|
}
|
|
318
416
|
if (!AVOIDABLE_KINDS.has(c.kind)) continue;
|
|
319
417
|
if (isContext) {
|
|
320
|
-
const name = ctxName;
|
|
321
418
|
out.push({
|
|
322
419
|
kind: "contextValue",
|
|
323
|
-
owner: providerOwner || `${
|
|
420
|
+
owner: providerOwner || `${ctxName}.Provider`,
|
|
324
421
|
target: r.component,
|
|
325
|
-
prop:
|
|
326
|
-
label: `memoize the ${
|
|
327
|
-
detail: `<${r.component}> re-rendered because ${
|
|
328
|
-
snippet: `// ${providerOwner || `where <${
|
|
422
|
+
prop: ctxName,
|
|
423
|
+
label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ""}`,
|
|
424
|
+
detail: `<${r.component}> re-rendered because ${ctxName} produced a new value that is deep-equal to the previous one${providerOwner ? ` (Provider rendered by <${providerOwner}>)` : ""}.`,
|
|
425
|
+
snippet: `// ${providerOwner || `where <${ctxName}.Provider> is rendered`}
|
|
329
426
|
const value = useMemo(() => ({ /* ... */ }), [/* deps */]);
|
|
330
|
-
<${
|
|
427
|
+
<${ctxName}.Provider value={value}>`
|
|
331
428
|
});
|
|
332
429
|
} else if (c.hook === "useSyncExternalStore") {
|
|
333
430
|
const chain = c.custom || [];
|
|
@@ -372,127 +469,41 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
372
469
|
for (const r of reports) {
|
|
373
470
|
if (!r.avoidable) continue;
|
|
374
471
|
for (const f of fixesFor(r)) {
|
|
375
|
-
const
|
|
376
|
-
let agg = byKey.get(
|
|
472
|
+
const key = fixKey(f);
|
|
473
|
+
let agg = byKey.get(key);
|
|
377
474
|
if (!agg) {
|
|
378
|
-
agg = { ...f, key
|
|
379
|
-
byKey.set(
|
|
475
|
+
agg = { ...f, key, count: 0, components: {}, reports: [] };
|
|
476
|
+
byKey.set(key, agg);
|
|
380
477
|
}
|
|
381
478
|
agg.count++;
|
|
382
|
-
agg.components
|
|
383
|
-
if (agg.reports.length <
|
|
479
|
+
agg.components[r.component] = (agg.components[r.component] || 0) + 1;
|
|
480
|
+
if (agg.reports.length < MAX_FIX_REPORTS) agg.reports.push(r);
|
|
384
481
|
}
|
|
385
482
|
}
|
|
386
483
|
return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
|
|
387
484
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const roots = /* @__PURE__ */ new Map();
|
|
403
|
-
let avoidable = 0;
|
|
404
|
-
let wasted = 0;
|
|
405
|
-
for (const r of reports) {
|
|
406
|
-
if (!r.avoidable) continue;
|
|
407
|
-
avoidable++;
|
|
408
|
-
if (typeof r.selfDuration === "number") wasted += r.selfDuration;
|
|
409
|
-
const root = rootCauseOf(r, reports);
|
|
410
|
-
const name = root ? root.name : r.parent && r.parent.name || "(unknown)";
|
|
411
|
-
const trigger = root ? root.trigger : r.parent && r.parent.trigger || "parent";
|
|
412
|
-
let agg = roots.get(name);
|
|
413
|
-
if (!agg) {
|
|
414
|
-
agg = { name, trigger, count: 0, components: /* @__PURE__ */ new Map() };
|
|
415
|
-
roots.set(name, agg);
|
|
416
|
-
}
|
|
417
|
-
agg.count++;
|
|
418
|
-
agg.components.set(r.component, (agg.components.get(r.component) || 0) + 1);
|
|
419
|
-
}
|
|
420
|
-
const first = reports[0];
|
|
421
|
-
return {
|
|
422
|
-
id: first ? first.commitId : 0,
|
|
423
|
-
receivedAt: first ? first.receivedAt : 0,
|
|
424
|
-
total: reports.length,
|
|
425
|
-
avoidable,
|
|
426
|
-
wasted,
|
|
427
|
-
roots: [...roots.values()].sort((a, b) => b.count - a.count),
|
|
428
|
-
contexts: contextAttribution(reports),
|
|
429
|
-
fixes: rankFixes(reports),
|
|
430
|
-
reports
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
function contextAttribution(reports) {
|
|
434
|
-
const byCtx = /* @__PURE__ */ new Map();
|
|
435
|
-
for (const r of reports) {
|
|
436
|
-
for (const c of r.hookChanges || []) {
|
|
437
|
-
if (c.hook !== "useContext" && !/^useContext/.test(c.path)) continue;
|
|
438
|
-
const m = /useContext\((.*)\)/.exec(c.path);
|
|
439
|
-
const name = m && m[1] ? m[1] : c.path;
|
|
440
|
-
let agg = byCtx.get(name);
|
|
441
|
-
if (!agg) {
|
|
442
|
-
agg = { name, consumers: 0, avoidable: 0, components: /* @__PURE__ */ new Map(), commits: /* @__PURE__ */ new Set(), providers: /* @__PURE__ */ new Map(), changedKeys: /* @__PURE__ */ new Set(), totalKeys: 0 };
|
|
443
|
-
byCtx.set(name, agg);
|
|
444
|
-
}
|
|
445
|
-
agg.consumers++;
|
|
446
|
-
if (AVOIDABLE_KINDS.has(c.kind)) agg.avoidable++;
|
|
447
|
-
agg.components.set(r.component, (agg.components.get(r.component) || 0) + 1);
|
|
448
|
-
agg.commits.add(r.commitId);
|
|
449
|
-
if (c.provider && c.provider.component) agg.providers.set(c.provider.component, (agg.providers.get(c.provider.component) || 0) + 1);
|
|
450
|
-
if (c.changedKeys) for (const k of c.changedKeys) agg.changedKeys.add(k);
|
|
451
|
-
if (typeof c.totalKeys === "number") agg.totalKeys = Math.max(agg.totalKeys, c.totalKeys);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return [...byCtx.values()].sort((a, b) => b.consumers - a.consumers);
|
|
455
|
-
}
|
|
456
|
-
function cascadeTree(reports) {
|
|
457
|
-
const root = { name: "", children: /* @__PURE__ */ new Map(), report: null };
|
|
458
|
-
for (const r of reports) {
|
|
459
|
-
let node = root;
|
|
460
|
-
for (const seg of r.path.concat([r.component])) {
|
|
461
|
-
let next = node.children.get(seg);
|
|
462
|
-
if (!next) {
|
|
463
|
-
next = { name: seg, children: /* @__PURE__ */ new Map(), report: null };
|
|
464
|
-
node.children.set(seg, next);
|
|
465
|
-
}
|
|
466
|
-
node = next;
|
|
467
|
-
}
|
|
468
|
-
if (!node.report || r.avoidable) node.report = r;
|
|
469
|
-
node.count = (node.count || 0) + 1;
|
|
470
|
-
if (r.avoidable) node.avoidable = (node.avoidable || 0) + 1;
|
|
471
|
-
}
|
|
472
|
-
return root;
|
|
473
|
-
}
|
|
474
|
-
function rootCauseSummary(name, commits) {
|
|
475
|
-
const out = { name, trigger: "parent", commits: [], total: 0, components: /* @__PURE__ */ new Map(), fixes: [] };
|
|
476
|
-
const affected = [];
|
|
477
|
-
for (const [key, reports] of commits) {
|
|
478
|
-
const analysis = analyzeCommit(reports);
|
|
479
|
-
const root = analysis.roots.find((x) => x.name === name);
|
|
480
|
-
if (!root) continue;
|
|
481
|
-
out.trigger = root.trigger;
|
|
482
|
-
out.commits.push({ key, analysis, count: root.count, components: root.components });
|
|
483
|
-
out.total += root.count;
|
|
484
|
-
for (const [c, n] of root.components) out.components.set(c, (out.components.get(c) || 0) + n);
|
|
485
|
-
for (const r of reports) {
|
|
486
|
-
if (!r.avoidable) continue;
|
|
487
|
-
const rc = rootCauseOf(r, reports);
|
|
488
|
-
if ((rc ? rc.name : r.parent && r.parent.name) === name) affected.push(r);
|
|
489
|
-
}
|
|
485
|
+
|
|
486
|
+
// src/report.ts
|
|
487
|
+
var KIND_LABEL = {
|
|
488
|
+
"deep-equal": "equal by value",
|
|
489
|
+
function: "new function",
|
|
490
|
+
element: "equal element",
|
|
491
|
+
different: "changed",
|
|
492
|
+
added: "added",
|
|
493
|
+
removed: "removed"
|
|
494
|
+
};
|
|
495
|
+
function summarize(report) {
|
|
496
|
+
const counts = /* @__PURE__ */ new Map();
|
|
497
|
+
for (const c of [...report.propChanges || [], ...report.stateChanges || [], ...report.hookChanges || []]) {
|
|
498
|
+
counts.set(c.kind, (counts.get(c.kind) ?? 0) + 1);
|
|
490
499
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return
|
|
500
|
+
const parts = [...counts].map(([kind, n]) => `${n} ${KIND_LABEL[kind] || kind}`);
|
|
501
|
+
if (parts.length === 0) parts.push("no changes");
|
|
502
|
+
return parts.join(", ");
|
|
494
503
|
}
|
|
495
|
-
|
|
504
|
+
|
|
505
|
+
// src/sessions.ts
|
|
506
|
+
function summarizeReports(reports, meta = {}) {
|
|
496
507
|
var _a;
|
|
497
508
|
const byComponent = {};
|
|
498
509
|
let avoidable = 0;
|
|
@@ -510,10 +521,10 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
510
521
|
}
|
|
511
522
|
}
|
|
512
523
|
return {
|
|
513
|
-
id:
|
|
514
|
-
name:
|
|
515
|
-
startedAt:
|
|
516
|
-
endedAt:
|
|
524
|
+
id: meta.id ?? `s${Date.now().toString(36)}`,
|
|
525
|
+
name: meta.name ?? "session",
|
|
526
|
+
startedAt: meta.startedAt ?? (reports[0]?.time ?? 0),
|
|
527
|
+
endedAt: meta.endedAt ?? (reports[reports.length - 1]?.time ?? null),
|
|
517
528
|
total: reports.length,
|
|
518
529
|
avoidable,
|
|
519
530
|
wasted,
|
|
@@ -521,7 +532,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
521
532
|
fixes: rankFixes(reports).map((f) => ({ key: f.key, label: f.label, count: f.count }))
|
|
522
533
|
};
|
|
523
534
|
}
|
|
524
|
-
function
|
|
535
|
+
function compareSummaries(before, after) {
|
|
525
536
|
const names = /* @__PURE__ */ new Set([...Object.keys(before.byComponent), ...Object.keys(after.byComponent)]);
|
|
526
537
|
const rows = [];
|
|
527
538
|
for (const component of names) {
|
|
@@ -540,69 +551,157 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
540
551
|
avoidable: { before: before.avoidable, after: after.avoidable, delta: after.avoidable - before.avoidable },
|
|
541
552
|
wasted: { before: before.wasted, after: after.wasted, delta: after.wasted - before.wasted },
|
|
542
553
|
resolvedFixes: before.fixes.filter((f) => !afterKeys.has(f.key)),
|
|
543
|
-
newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key))
|
|
554
|
+
newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key)),
|
|
555
|
+
regressions: rows.filter((r) => r.delta > 0)
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
var summarizeSession = (session, reports) => summarizeReports(reports, session);
|
|
559
|
+
var compareSessions = compareSummaries;
|
|
560
|
+
|
|
561
|
+
// extension/src/panel.ts
|
|
562
|
+
var PROTOCOL = 2;
|
|
563
|
+
var FN_PREFIX = "\u0192 ";
|
|
564
|
+
var MAX_REPORTS = 2e3;
|
|
565
|
+
var MAX_PER_NODE = 200;
|
|
566
|
+
var MAX_COMMITS = 500;
|
|
567
|
+
var MAX_PER_COMMIT = 500;
|
|
568
|
+
var THROTTLE_MIN_REPORTS = 200;
|
|
569
|
+
var BEST_FIX_INTERVAL = 500;
|
|
570
|
+
var LEFT_RENDER_INTERVAL = 250;
|
|
571
|
+
var SYNC_RETRY_MIN = 500;
|
|
572
|
+
var SYNC_RETRY_MAX = 5e3;
|
|
573
|
+
var SYNC_RETRIES = 20;
|
|
574
|
+
var NAVIGATION_SETTLE = 1200;
|
|
575
|
+
var INFO_REFRESH_MS = 3e3;
|
|
576
|
+
var ROW_H = 22;
|
|
577
|
+
var ITEM_H = 20;
|
|
578
|
+
var OVERSCAN = 8;
|
|
579
|
+
var FALLBACK_VIEWPORT = 800;
|
|
580
|
+
function el(tag, attrs, children) {
|
|
581
|
+
const node = document.createElement(tag);
|
|
582
|
+
if (attrs) {
|
|
583
|
+
for (const k of Object.keys(attrs)) {
|
|
584
|
+
const v = attrs[k];
|
|
585
|
+
if (k === "class") node.className = String(v);
|
|
586
|
+
else if (k === "text") node.textContent = String(v);
|
|
587
|
+
else if (k.startsWith("on")) {
|
|
588
|
+
if (typeof v === "function") node.addEventListener(k.slice(2), v);
|
|
589
|
+
} else if (v === true) node.setAttribute(k, "");
|
|
590
|
+
else if (v !== void 0 && v !== null && v !== false) node.setAttribute(k, String(v));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (children) {
|
|
594
|
+
for (const c of [].concat(children)) if (c != null) node.append(c);
|
|
595
|
+
}
|
|
596
|
+
return node;
|
|
597
|
+
}
|
|
598
|
+
function fmtTime(ms) {
|
|
599
|
+
const d = new Date(ms);
|
|
600
|
+
const p = (n, w) => String(n).padStart(w, "0");
|
|
601
|
+
return `${p(d.getHours(), 2)}:${p(d.getMinutes(), 2)}:${p(d.getSeconds(), 2)}.${p(d.getMilliseconds(), 3)}`;
|
|
602
|
+
}
|
|
603
|
+
var fmtMs = (n) => typeof n === "number" && Number.isFinite(n) ? `${n.toFixed(1)} ms` : "";
|
|
604
|
+
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
605
|
+
var componentList = (m) => (m instanceof Map ? [...m] : Object.entries(m)).map(([c, n]) => `<${c}>${n > 1 ? " \xD7" + n : ""}`).join(", ");
|
|
606
|
+
function changesOf(report) {
|
|
607
|
+
return [].concat(report.propChanges || [], report.stateChanges || [], report.hookChanges || []);
|
|
608
|
+
}
|
|
609
|
+
var isRecord = (v) => typeof v === "object" && v !== null;
|
|
610
|
+
function normalizeReport(p) {
|
|
611
|
+
if (!isRecord(p) || typeof p.component !== "string") return null;
|
|
612
|
+
const arr = (x) => Array.isArray(x) ? x.filter((c) => isRecord(c) && typeof c.path === "string") : [];
|
|
613
|
+
const props = isRecord(p.props) ? p.props : {};
|
|
614
|
+
const parent = isRecord(p.parent) && typeof p.parent.name === "string" ? { name: p.parent.name, trigger: typeof p.parent.trigger === "string" ? p.parent.trigger : "parent" } : null;
|
|
615
|
+
const r = {
|
|
616
|
+
component: p.component,
|
|
617
|
+
instanceId: typeof p.instanceId === "number" ? p.instanceId : 0,
|
|
618
|
+
commitId: typeof p.commitId === "number" ? p.commitId : 0,
|
|
619
|
+
renderCount: typeof p.renderCount === "number" ? p.renderCount : 0,
|
|
620
|
+
trigger: typeof p.trigger === "string" ? p.trigger : "parent",
|
|
621
|
+
avoidable: !!p.avoidable,
|
|
622
|
+
props: { prev: isRecord(props.prev) ? props.prev : {}, next: isRecord(props.next) ? props.next : {} },
|
|
623
|
+
propChanges: arr(p.propChanges),
|
|
624
|
+
stateChanges: arr(p.stateChanges),
|
|
625
|
+
hookChanges: arr(p.hookChanges),
|
|
626
|
+
parent,
|
|
627
|
+
owner: typeof p.owner === "string" ? p.owner : null,
|
|
628
|
+
path: Array.isArray(p.path) ? p.path.filter((x) => typeof x === "string") : [],
|
|
629
|
+
reasons: Array.isArray(p.reasons) ? p.reasons.filter((x) => typeof x === "string") : [],
|
|
630
|
+
time: typeof p.time === "number" ? p.time : 0,
|
|
631
|
+
receivedAt: typeof p.receivedAt === "number" ? p.receivedAt : 0
|
|
544
632
|
};
|
|
633
|
+
if (typeof p.selfDuration === "number") r.selfDuration = p.selfDuration;
|
|
634
|
+
if (typeof p.treeDuration === "number") r.treeDuration = p.treeDuration;
|
|
635
|
+
if (typeof p.memoized === "boolean") r.memoized = p.memoized;
|
|
636
|
+
if (p.compiled === true) r.compiled = true;
|
|
637
|
+
if (typeof p.commitPriority === "string") r.commitPriority = p.commitPriority;
|
|
638
|
+
if (Array.isArray(p.hookState)) r.hookState = p.hookState.filter((h) => isRecord(h) && typeof h.path === "string");
|
|
639
|
+
if (Array.isArray(p.contexts)) r.contexts = p.contexts.filter((c) => isRecord(c) && typeof c.name === "string");
|
|
640
|
+
if (isRecord(p.state)) r.state = p.state;
|
|
641
|
+
if (Array.isArray(p.updaters)) r.updaters = p.updaters.filter((u) => typeof u === "string");
|
|
642
|
+
if (p.commitCause === "effect-after-commit" || p.commitCause === "suspense-resolved") r.commitCause = p.commitCause;
|
|
643
|
+
if (typeof p.afterCommit === "number") r.afterCommit = p.afterCommit;
|
|
644
|
+
if (typeof p.key === "string") r.key = p.key;
|
|
645
|
+
if (isRecord(p.source) && typeof p.source.fileName === "string") r.source = p.source;
|
|
646
|
+
return r;
|
|
545
647
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
async function transformBytes(bytes, transform) {
|
|
554
|
-
const source = new ReadableStream({
|
|
555
|
-
start(controller) {
|
|
556
|
-
controller.enqueue(bytes);
|
|
557
|
-
controller.close();
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
const reader = source.pipeThrough(transform).getReader();
|
|
561
|
-
const chunks = [];
|
|
562
|
-
let total = 0;
|
|
563
|
-
for (; ; ) {
|
|
564
|
-
const { value, done } = await reader.read();
|
|
565
|
-
if (done) break;
|
|
566
|
-
chunks.push(value);
|
|
567
|
-
total += value.length;
|
|
568
|
-
}
|
|
569
|
-
const out = new Uint8Array(total);
|
|
570
|
-
let offset = 0;
|
|
571
|
-
for (const c of chunks) {
|
|
572
|
-
out.set(c, offset);
|
|
573
|
-
offset += c.length;
|
|
574
|
-
}
|
|
575
|
-
return out;
|
|
576
|
-
}
|
|
577
|
-
async function encodeShare(report) {
|
|
578
|
-
const json = JSON.stringify(report);
|
|
579
|
-
const bytes = new TextEncoder().encode(json);
|
|
580
|
-
if (typeof CompressionStream === "function") {
|
|
581
|
-
try {
|
|
582
|
-
return "d." + b64url(await transformBytes(bytes, new CompressionStream("deflate-raw")));
|
|
583
|
-
} catch {
|
|
584
|
-
}
|
|
648
|
+
function valueNode(v, depth = 0) {
|
|
649
|
+
if (v === null || v === void 0) return el("span", { class: "v nil", text: String(v) });
|
|
650
|
+
const t = typeof v;
|
|
651
|
+
if (typeof v === "string") {
|
|
652
|
+
if (v.startsWith(FN_PREFIX)) return el("span", { class: "v fn", text: v });
|
|
653
|
+
if (/^<[^>]+>$/.test(v)) return el("span", { class: "v", text: v });
|
|
654
|
+
return el("span", { class: "v str", text: JSON.stringify(v) });
|
|
585
655
|
}
|
|
586
|
-
return "
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
return
|
|
656
|
+
if (t === "number" || t === "bigint") return el("span", { class: "v num", text: String(v) });
|
|
657
|
+
if (t === "boolean") return el("span", { class: "v bool", text: String(v) });
|
|
658
|
+
if (Array.isArray(v)) {
|
|
659
|
+
const short = v.length <= 4 && v.every((x) => typeof x !== "object" || x === null);
|
|
660
|
+
if (short) {
|
|
661
|
+
const s = el("span", { class: "v" }, "[");
|
|
662
|
+
v.forEach((x, i) => {
|
|
663
|
+
if (i) s.append(", ");
|
|
664
|
+
s.append(valueNode(x, depth + 1));
|
|
665
|
+
});
|
|
666
|
+
s.append("]");
|
|
667
|
+
return s;
|
|
598
668
|
}
|
|
669
|
+
return objectDetails(`Array(${v.length})`, v);
|
|
599
670
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
671
|
+
const o = v;
|
|
672
|
+
if (o.$type === "element") {
|
|
673
|
+
const props = isRecord(o.props) ? o.props : {};
|
|
674
|
+
const label2 = `<${String(o.name)}${o.key !== void 0 ? ` key=${JSON.stringify(o.key)}` : ""}>`;
|
|
675
|
+
return Object.keys(props).length ? objectDetails(label2, props) : el("span", { class: "v", text: label2 });
|
|
604
676
|
}
|
|
677
|
+
if (o.$type === "Date") return el("span", { class: "v", text: `Date(${String(o.value)})` });
|
|
678
|
+
if (o.$type === "RegExp") return el("span", { class: "v", text: String(o.value) });
|
|
679
|
+
if (o.$type === "Map" && Array.isArray(o.entries)) return objectDetails(`Map(${o.entries.length})`, o.entries);
|
|
680
|
+
if (o.$type === "Set" && Array.isArray(o.values)) return objectDetails(`Set(${o.values.length})`, o.values);
|
|
681
|
+
const keys = Object.keys(o).filter((k) => k !== "$type");
|
|
682
|
+
const label = `${o.$type ? String(o.$type) + " " : ""}{${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", ..." : ""}}`;
|
|
683
|
+
return objectDetails(label, o);
|
|
684
|
+
}
|
|
685
|
+
function objectDetails(label, obj) {
|
|
686
|
+
const d = el("details", { class: "obj" }, [el("summary", { text: label })]);
|
|
687
|
+
d.addEventListener(
|
|
688
|
+
"toggle",
|
|
689
|
+
() => {
|
|
690
|
+
if (d.open && !d.querySelector("pre")) d.append(el("pre", { text: JSON.stringify(obj, null, 2) }));
|
|
691
|
+
},
|
|
692
|
+
{ once: true }
|
|
693
|
+
);
|
|
694
|
+
return d;
|
|
695
|
+
}
|
|
696
|
+
function analyzeCommit2(reports) {
|
|
697
|
+
const first = reports[0];
|
|
698
|
+
return { ...analyzeCommit(reports), receivedAt: first ? first.receivedAt : 0, fixes: rankFixes(reports) };
|
|
699
|
+
}
|
|
700
|
+
function rootCauseSummary2(name, commits, analyze = (_, reports) => analyzeCommit2(reports)) {
|
|
701
|
+
const s = rootCauseSummary(name, commits, analyze);
|
|
702
|
+
return { ...s, fixes: rankFixes(s.affected) };
|
|
605
703
|
}
|
|
704
|
+
var PRIORITY_LABEL = { immediate: "discrete input", "user-blocking": "continuous input", normal: "transition / async", low: "low", idle: "idle" };
|
|
606
705
|
function sourceContext(text, line, around = 3) {
|
|
607
706
|
const lines = text.split("\n");
|
|
608
707
|
const from = Math.max(1, line - around);
|
|
@@ -725,10 +824,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
725
824
|
const copy = actions.copy;
|
|
726
825
|
bar.append(el("button", { onclick: () => copy(reportToMarkdown(r)) }, "\u2398 Copy as Markdown"));
|
|
727
826
|
}
|
|
728
|
-
if (actions.share) {
|
|
729
|
-
const share = actions.share;
|
|
730
|
-
bar.append(el("button", { title: "Copy a link that opens this report in the panel", onclick: () => share(r) }, "\u{1F517} Copy link"));
|
|
731
|
-
}
|
|
732
827
|
if (bar.children.length) head.append(bar);
|
|
733
828
|
const loc = r.source;
|
|
734
829
|
if (loc && loc.lineNumber && actions.readSource) {
|
|
@@ -761,7 +856,8 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
761
856
|
if (r.parent) by.append(el("div", { text: `Triggered by <${r.parent.name}> (${r.parent.trigger})` }));
|
|
762
857
|
else by.append(el("div", { text: "Update started in this component" }));
|
|
763
858
|
if (r.owner) by.append(el("div", { class: "meta", text: `Created by <${r.owner}>` }));
|
|
764
|
-
if (r.
|
|
859
|
+
if (r.compiled) by.append(el("div", { class: "meta", text: "Compiled by React Compiler (output memoized per input; identical inputs make the render cheap)" }));
|
|
860
|
+
else if (r.memoized === false) by.append(el("div", { class: "meta", text: "Not memoized (re-renders whenever its parent does)" }));
|
|
765
861
|
else if (r.memoized === true) by.append(el("div", { class: "meta", text: "Memoized (React.memo / PureComponent)" }));
|
|
766
862
|
if (r.updaters && r.updaters.length) by.append(el("div", { text: `Update scheduled by ${r.updaters.map((u) => `<${u}>`).join(", ")}` }));
|
|
767
863
|
if (r.commitCause === "effect-after-commit") by.append(el("div", { class: "cause effect", text: `Effect loop: state set right after commit #${r.afterCommit ?? "?"}` }));
|
|
@@ -922,16 +1018,13 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
922
1018
|
flashOn: false,
|
|
923
1019
|
settingsOpen: false,
|
|
924
1020
|
origin: null,
|
|
925
|
-
legacyCommit: 0,
|
|
926
1021
|
tabLabel: transport.tabLabel ?? null,
|
|
927
1022
|
compact: false,
|
|
928
1023
|
sessions: [],
|
|
929
1024
|
recording: null,
|
|
930
1025
|
selectedSession: null,
|
|
931
1026
|
compareWith: null,
|
|
932
|
-
byInstance: false
|
|
933
|
-
columns: [],
|
|
934
|
-
notes: {}
|
|
1027
|
+
byInstance: false
|
|
935
1028
|
};
|
|
936
1029
|
let persistTimer = null;
|
|
937
1030
|
let queue = [];
|
|
@@ -1091,6 +1184,33 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1091
1184
|
const measure = () => setCompact(root.clientWidth > 0 && root.clientWidth < 720);
|
|
1092
1185
|
if (typeof ResizeObserver === "function") new ResizeObserver(measure).observe(root);
|
|
1093
1186
|
else window.addEventListener("resize", measure);
|
|
1187
|
+
const totals = { avoidable: 0, wasted: 0, perComponent: /* @__PURE__ */ new Map() };
|
|
1188
|
+
let bestFix;
|
|
1189
|
+
let bestFixAt = 0;
|
|
1190
|
+
let bestFixGen = -1;
|
|
1191
|
+
let bestFixTimer = null;
|
|
1192
|
+
const throttled = () => state.reports.length >= THROTTLE_MIN_REPORTS;
|
|
1193
|
+
function refreshBestFix() {
|
|
1194
|
+
if (!totals.avoidable) {
|
|
1195
|
+
bestFix = void 0;
|
|
1196
|
+
bestFixGen = dataGen;
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
if (bestFixGen === dataGen) return;
|
|
1200
|
+
const now = Date.now();
|
|
1201
|
+
if (throttled() && now - bestFixAt < BEST_FIX_INTERVAL) {
|
|
1202
|
+
if (!bestFixTimer) {
|
|
1203
|
+
bestFixTimer = setTimeout(() => {
|
|
1204
|
+
bestFixTimer = null;
|
|
1205
|
+
renderSummary();
|
|
1206
|
+
}, BEST_FIX_INTERVAL - (now - bestFixAt));
|
|
1207
|
+
}
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
bestFixAt = now;
|
|
1211
|
+
bestFixGen = dataGen;
|
|
1212
|
+
bestFix = rankFixes(state.reports)[0];
|
|
1213
|
+
}
|
|
1094
1214
|
function renderSummary() {
|
|
1095
1215
|
summary.textContent = "";
|
|
1096
1216
|
const total = state.reports.length;
|
|
@@ -1099,17 +1219,11 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1099
1219
|
return;
|
|
1100
1220
|
}
|
|
1101
1221
|
summary.hidden = false;
|
|
1102
|
-
|
|
1103
|
-
let
|
|
1104
|
-
const perComponent
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
avoidable++;
|
|
1108
|
-
if (typeof r.selfDuration === "number") wasted += r.selfDuration;
|
|
1109
|
-
if (!isMuted(r.component)) perComponent.set(r.component, (perComponent.get(r.component) || 0) + 1);
|
|
1110
|
-
}
|
|
1111
|
-
const top = [...perComponent].sort((a, b) => b[1] - a[1])[0];
|
|
1112
|
-
const fix = avoidable ? rankFixes(state.reports.filter((r) => !isMuted(r.component)))[0] : void 0;
|
|
1222
|
+
const { avoidable, wasted } = totals;
|
|
1223
|
+
let top;
|
|
1224
|
+
for (const entry of totals.perComponent) if (!top || entry[1] > top[1]) top = entry;
|
|
1225
|
+
refreshBestFix();
|
|
1226
|
+
const fix = bestFix;
|
|
1113
1227
|
const stat = (value, label, cls = "") => el("span", { class: "stat " + cls }, [el("b", { text: value }), el("span", { class: "label", text: label })]);
|
|
1114
1228
|
summary.append(stat(String(total), plural(total, "render").replace(/^\d+ /, "")), stat(String(avoidable), "avoidable", avoidable ? "bad" : "good"));
|
|
1115
1229
|
if (wasted) summary.append(stat(fmtMs(wasted), "wasted", "bad"));
|
|
@@ -1177,8 +1291,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1177
1291
|
collapsed: [...state.collapsed],
|
|
1178
1292
|
treeWidth: state.treeWidth,
|
|
1179
1293
|
flashOn: state.flashOn,
|
|
1180
|
-
byInstance: state.byInstance
|
|
1181
|
-
columns: state.columns
|
|
1294
|
+
byInstance: state.byInstance
|
|
1182
1295
|
};
|
|
1183
1296
|
transport.storage.set("panel", saved);
|
|
1184
1297
|
}, 150);
|
|
@@ -1230,7 +1343,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1230
1343
|
instancesBtn.classList.toggle("active", state.byInstance);
|
|
1231
1344
|
rebuildTree();
|
|
1232
1345
|
}
|
|
1233
|
-
if (Array.isArray(saved.columns)) state.columns = saved.columns.filter((c) => OPTIONAL_COLUMNS.some((o) => o.key === c));
|
|
1234
1346
|
if (saved.tab === "history" || saved.tab === "fix") state.tab = saved.tab;
|
|
1235
1347
|
if (saved.view && viewButtons.has(saved.view)) state.view = saved.view;
|
|
1236
1348
|
for (const n of state.nodesByKey.values()) n.expanded = !state.collapsed.has(n.key);
|
|
@@ -1266,7 +1378,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1266
1378
|
for (const r of state.reports) {
|
|
1267
1379
|
const node = nodeOfReport(r);
|
|
1268
1380
|
node.reports.push(r);
|
|
1269
|
-
if (node.reports.length > MAX_PER_NODE) node.reports.shift();
|
|
1270
1381
|
node.total++;
|
|
1271
1382
|
if (r.avoidable) {
|
|
1272
1383
|
node.avoidable++;
|
|
@@ -1274,49 +1385,93 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1274
1385
|
}
|
|
1275
1386
|
node.lastReport = r;
|
|
1276
1387
|
}
|
|
1388
|
+
for (const node of state.nodesByKey.values()) trimNode(node);
|
|
1277
1389
|
renderLeft();
|
|
1278
1390
|
renderDetails();
|
|
1279
1391
|
}
|
|
1280
|
-
function
|
|
1281
|
-
|
|
1282
|
-
|
|
1392
|
+
function trimNode(node) {
|
|
1393
|
+
const excess = node.reports.length - MAX_PER_NODE;
|
|
1394
|
+
if (excess > 0) node.reports.splice(0, excess);
|
|
1395
|
+
}
|
|
1396
|
+
const commitAnalyses = /* @__PURE__ */ new Map();
|
|
1397
|
+
function analysisFor(key, reports) {
|
|
1398
|
+
const cached = commitAnalyses.get(key);
|
|
1399
|
+
if (cached && cached.len === reports.length) return cached.analysis;
|
|
1400
|
+
const analysis = analyzeCommit2(reports);
|
|
1401
|
+
commitAnalyses.set(key, { len: reports.length, analysis });
|
|
1402
|
+
return analysis;
|
|
1403
|
+
}
|
|
1404
|
+
const commitMember = /* @__PURE__ */ new WeakSet();
|
|
1405
|
+
function forgetCommit(key) {
|
|
1406
|
+
state.commits.delete(key);
|
|
1407
|
+
commitAnalyses.delete(key);
|
|
1283
1408
|
}
|
|
1284
1409
|
function ingest(report) {
|
|
1285
1410
|
if (!report.receivedAt) report.receivedAt = Date.now();
|
|
1286
1411
|
state.reports.push(report);
|
|
1287
|
-
if (state.reports.length > MAX_REPORTS) state.reports.shift();
|
|
1288
1412
|
const node = nodeOfReport(report);
|
|
1289
1413
|
node.reports.push(report);
|
|
1290
|
-
if (node.reports.length > MAX_PER_NODE) node.reports.shift();
|
|
1291
1414
|
node.total++;
|
|
1292
1415
|
if (report.avoidable) {
|
|
1293
1416
|
node.avoidable++;
|
|
1294
|
-
|
|
1417
|
+
totals.avoidable++;
|
|
1418
|
+
totals.perComponent.set(report.component, (totals.perComponent.get(report.component) || 0) + 1);
|
|
1419
|
+
if (typeof report.selfDuration === "number") {
|
|
1420
|
+
node.wasted += report.selfDuration;
|
|
1421
|
+
totals.wasted += report.selfDuration;
|
|
1422
|
+
}
|
|
1295
1423
|
}
|
|
1296
1424
|
node.lastReport = report;
|
|
1297
1425
|
node.flash = true;
|
|
1298
1426
|
node.flashAt = Date.now();
|
|
1299
|
-
const ck =
|
|
1427
|
+
const ck = report.commitId;
|
|
1300
1428
|
let list = state.commits.get(ck);
|
|
1301
1429
|
if (!list) {
|
|
1302
1430
|
list = [];
|
|
1303
1431
|
state.commits.set(ck, list);
|
|
1304
1432
|
state.commitOrder.push(ck);
|
|
1305
|
-
if (state.commitOrder.length > MAX_COMMITS)
|
|
1433
|
+
if (state.commitOrder.length > MAX_COMMITS) forgetCommit(state.commitOrder.shift());
|
|
1434
|
+
}
|
|
1435
|
+
if (list.length < MAX_PER_COMMIT) {
|
|
1436
|
+
list.push(report);
|
|
1437
|
+
commitMember.add(report);
|
|
1306
1438
|
}
|
|
1307
|
-
list.push(report);
|
|
1308
1439
|
return node;
|
|
1309
1440
|
}
|
|
1441
|
+
function evict() {
|
|
1442
|
+
const excess = state.reports.length - MAX_REPORTS;
|
|
1443
|
+
if (excess <= 0) return;
|
|
1444
|
+
for (const r of state.reports.splice(0, excess)) {
|
|
1445
|
+
if (r.avoidable) {
|
|
1446
|
+
totals.avoidable--;
|
|
1447
|
+
const n = (totals.perComponent.get(r.component) || 0) - 1;
|
|
1448
|
+
if (n > 0) totals.perComponent.set(r.component, n);
|
|
1449
|
+
else totals.perComponent.delete(r.component);
|
|
1450
|
+
if (typeof r.selfDuration === "number") totals.wasted -= r.selfDuration;
|
|
1451
|
+
}
|
|
1452
|
+
if (!commitMember.has(r)) continue;
|
|
1453
|
+
const list = state.commits.get(r.commitId);
|
|
1454
|
+
if (!list || list[0] !== r) continue;
|
|
1455
|
+
list.shift();
|
|
1456
|
+
if (!list.length) {
|
|
1457
|
+
forgetCommit(r.commitId);
|
|
1458
|
+
const i = state.commitOrder.indexOf(r.commitId);
|
|
1459
|
+
if (i >= 0) state.commitOrder.splice(i, 1);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
if (totals.wasted < 0) totals.wasted = 0;
|
|
1463
|
+
}
|
|
1310
1464
|
function flush() {
|
|
1311
1465
|
flushScheduled = false;
|
|
1312
1466
|
if (!queue.length) return;
|
|
1313
1467
|
const batch = queue;
|
|
1314
1468
|
queue = [];
|
|
1315
|
-
state.legacyCommit++;
|
|
1316
1469
|
let touchedSelected = false;
|
|
1317
1470
|
let avoidableCount = 0;
|
|
1471
|
+
const touched = /* @__PURE__ */ new Set();
|
|
1318
1472
|
for (const r of batch) {
|
|
1319
1473
|
const node = ingest(r);
|
|
1474
|
+
touched.add(node);
|
|
1320
1475
|
if (state.recording && state.recording.reports.length < MAX_REPORTS) state.recording.reports.push(r);
|
|
1321
1476
|
if (r.avoidable) avoidableCount++;
|
|
1322
1477
|
if (state.selectedKey === node.key) {
|
|
@@ -1324,11 +1479,45 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1324
1479
|
if (state.tab === "latest") state.selectedReport = r;
|
|
1325
1480
|
}
|
|
1326
1481
|
}
|
|
1327
|
-
|
|
1482
|
+
for (const node of touched) trimNode(node);
|
|
1483
|
+
evict();
|
|
1484
|
+
dataGen++;
|
|
1328
1485
|
renderStream(batch);
|
|
1329
1486
|
renderSummary();
|
|
1330
|
-
|
|
1331
|
-
if (state.
|
|
1487
|
+
const heavy = state.view === "commits" || state.view === "fixes" || state.tab === "root" || !!(state.recording && state.tab === "session");
|
|
1488
|
+
if (state.view === "tree") {
|
|
1489
|
+
renderTree();
|
|
1490
|
+
if (touchedSelected && !heavy) renderDetails();
|
|
1491
|
+
}
|
|
1492
|
+
scheduleHeavy(heavy || touchedSelected && state.view !== "tree");
|
|
1493
|
+
if (state.polling && avoidableCount) transport.badge?.(totals.avoidable);
|
|
1494
|
+
}
|
|
1495
|
+
let heavyAt = 0;
|
|
1496
|
+
let heavyTimer = null;
|
|
1497
|
+
let heavyDetailsPending = false;
|
|
1498
|
+
function renderHeavy(details2) {
|
|
1499
|
+
heavyAt = Date.now();
|
|
1500
|
+
if (state.view !== "tree") renderLeft();
|
|
1501
|
+
if (details2) renderDetails();
|
|
1502
|
+
}
|
|
1503
|
+
function scheduleHeavy(details2) {
|
|
1504
|
+
const wait = throttled() ? LEFT_RENDER_INTERVAL - (Date.now() - heavyAt) : 0;
|
|
1505
|
+
if (wait <= 0) {
|
|
1506
|
+
if (heavyTimer) clearTimeout(heavyTimer);
|
|
1507
|
+
heavyTimer = null;
|
|
1508
|
+
heavyDetailsPending = false;
|
|
1509
|
+
renderHeavy(details2);
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
heavyDetailsPending = heavyDetailsPending || details2;
|
|
1513
|
+
if (!heavyTimer) {
|
|
1514
|
+
heavyTimer = setTimeout(() => {
|
|
1515
|
+
heavyTimer = null;
|
|
1516
|
+
const d = heavyDetailsPending;
|
|
1517
|
+
heavyDetailsPending = false;
|
|
1518
|
+
renderHeavy(d);
|
|
1519
|
+
}, wait);
|
|
1520
|
+
}
|
|
1332
1521
|
}
|
|
1333
1522
|
function enqueue(report) {
|
|
1334
1523
|
queue.push(report);
|
|
@@ -1343,6 +1532,13 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1343
1532
|
state.reports = [];
|
|
1344
1533
|
state.commits.clear();
|
|
1345
1534
|
state.commitOrder = [];
|
|
1535
|
+
commitAnalyses.clear();
|
|
1536
|
+
totals.avoidable = 0;
|
|
1537
|
+
totals.wasted = 0;
|
|
1538
|
+
totals.perComponent.clear();
|
|
1539
|
+
bestFix = void 0;
|
|
1540
|
+
bestFixAt = 0;
|
|
1541
|
+
dataGen++;
|
|
1346
1542
|
state.selectedKey = null;
|
|
1347
1543
|
state.selectedReport = null;
|
|
1348
1544
|
state.selectedCommit = null;
|
|
@@ -1355,18 +1551,28 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1355
1551
|
renderStream();
|
|
1356
1552
|
renderSummary();
|
|
1357
1553
|
}
|
|
1358
|
-
|
|
1359
|
-
function
|
|
1360
|
-
if (
|
|
1554
|
+
let filterCache = { filter: "", value: null, regex: null, text: "" };
|
|
1555
|
+
function parsedFilter() {
|
|
1556
|
+
if (filterCache.filter === state.filter) return filterCache;
|
|
1361
1557
|
const f = state.filter.trim();
|
|
1362
|
-
const
|
|
1558
|
+
const value = f.startsWith("~") ? f.slice(1).toLowerCase() : null;
|
|
1559
|
+
let regex = null;
|
|
1560
|
+
const m = value === null ? /^\/(.+)\/([a-z]*)$/.exec(f) : null;
|
|
1363
1561
|
if (m && m[1] !== void 0) {
|
|
1364
1562
|
try {
|
|
1365
|
-
|
|
1563
|
+
regex = new RegExp(m[1], (m[2] || "").replace(/[gy]/g, ""));
|
|
1366
1564
|
} catch {
|
|
1367
1565
|
}
|
|
1368
1566
|
}
|
|
1369
|
-
|
|
1567
|
+
filterCache = { filter: state.filter, value, regex, text: f.toLowerCase() };
|
|
1568
|
+
return filterCache;
|
|
1569
|
+
}
|
|
1570
|
+
const valueQuery = () => parsedFilter().value;
|
|
1571
|
+
function matchesFilter(name) {
|
|
1572
|
+
const f = parsedFilter();
|
|
1573
|
+
if (!f.filter || f.value !== null) return true;
|
|
1574
|
+
if (f.regex) return f.regex.test(name);
|
|
1575
|
+
return name.toLowerCase().includes(f.text);
|
|
1370
1576
|
}
|
|
1371
1577
|
const valueCache = /* @__PURE__ */ new WeakMap();
|
|
1372
1578
|
function matchesValues(r) {
|
|
@@ -1390,10 +1596,19 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1390
1596
|
for (const c of node.children.values()) if (visible(c)) return true;
|
|
1391
1597
|
return false;
|
|
1392
1598
|
}
|
|
1393
|
-
const isMuted = (component) => !!state.notes[component]?.muted;
|
|
1394
1599
|
const passes = (r) => (!state.avoidableOnly || r.avoidable) && matchesFilter(r.component) && matchesValues(r);
|
|
1395
|
-
|
|
1396
|
-
|
|
1600
|
+
let dataGen = 0;
|
|
1601
|
+
let filtered = { key: "", reports: [], fixes: null };
|
|
1602
|
+
function filteredReports() {
|
|
1603
|
+
const key = `${dataGen}|${state.avoidableOnly}|${state.filter}`;
|
|
1604
|
+
if (filtered.key !== key) filtered = { key, reports: state.reports.filter(passes), fixes: null };
|
|
1605
|
+
return filtered.reports;
|
|
1606
|
+
}
|
|
1607
|
+
function filteredFixes() {
|
|
1608
|
+
const reports = filteredReports();
|
|
1609
|
+
if (!filtered.fixes) filtered.fixes = rankFixes(reports);
|
|
1610
|
+
return filtered.fixes;
|
|
1611
|
+
}
|
|
1397
1612
|
function setView(view) {
|
|
1398
1613
|
state.view = view;
|
|
1399
1614
|
for (const [id, b] of viewButtons) b.classList.toggle("active", id === view);
|
|
@@ -1472,7 +1687,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1472
1687
|
row.classList.toggle("selected", state.selectedKey === node.key);
|
|
1473
1688
|
row.setAttribute("aria-selected", state.selectedKey === node.key ? "true" : "false");
|
|
1474
1689
|
row.setAttribute("aria-level", String(depth + 1));
|
|
1475
|
-
row.classList.toggle("muted", isMuted(node.lastReport ? node.lastReport.component : node.name));
|
|
1476
1690
|
const indent = row.querySelector(".indent");
|
|
1477
1691
|
if (indent.childElementCount !== depth) {
|
|
1478
1692
|
indent.textContent = "";
|
|
@@ -1574,7 +1788,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1574
1788
|
for (const r of filteredReports()) {
|
|
1575
1789
|
let o = byName.get(r.component);
|
|
1576
1790
|
if (!o) {
|
|
1577
|
-
o = { component: r.component, total: 0, avoidable: 0, wasted: 0, paths: /* @__PURE__ */ new Set(),
|
|
1791
|
+
o = { component: r.component, total: 0, avoidable: 0, wasted: 0, paths: /* @__PURE__ */ new Set(), reports: [], fix: "" };
|
|
1578
1792
|
byName.set(r.component, o);
|
|
1579
1793
|
}
|
|
1580
1794
|
o.total++;
|
|
@@ -1583,12 +1797,10 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1583
1797
|
if (typeof r.selfDuration === "number") o.wasted += r.selfDuration;
|
|
1584
1798
|
}
|
|
1585
1799
|
o.paths.add(keyOf(r.path));
|
|
1586
|
-
o.lastSeen = Math.max(o.lastSeen, r.receivedAt);
|
|
1587
1800
|
o.reports.push(r);
|
|
1588
1801
|
}
|
|
1589
1802
|
const rows = [...byName.values()];
|
|
1590
1803
|
for (const o of rows) {
|
|
1591
|
-
o.places = o.paths.size;
|
|
1592
1804
|
const fixes = o.avoidable ? rankFixes(o.reports) : [];
|
|
1593
1805
|
o.fix = fixes.length ? fixes[0].label : "";
|
|
1594
1806
|
}
|
|
@@ -1601,62 +1813,25 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1601
1813
|
});
|
|
1602
1814
|
return rows;
|
|
1603
1815
|
}
|
|
1604
|
-
function
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
return el("td", { class: "num", text: String(o.places) });
|
|
1624
|
-
case "lastSeen":
|
|
1625
|
-
return el("td", { class: "num mono", text: o.lastSeen ? fmtTime(o.lastSeen) : "" });
|
|
1626
|
-
default:
|
|
1627
|
-
return el("td");
|
|
1628
|
-
}
|
|
1629
|
-
};
|
|
1630
|
-
function offenderRow(o, tag) {
|
|
1631
|
-
const cells = [
|
|
1632
|
-
el("td", { class: "c" }, [el("span", { class: "name", text: o.component }), o.muted ? el("span", { class: "badge", text: "muted" }) : null, o.paths.size > 1 && !state.columns.includes("places") ? el("span", { class: "meta", text: ` \xD7${o.paths.size} places` }) : null]),
|
|
1633
|
-
el("td", { class: "num" }, o.avoidable ? el("span", { class: "badge avoid", text: String(o.avoidable) }) : "0"),
|
|
1634
|
-
el("td", { class: "num", text: String(o.total) }),
|
|
1635
|
-
el("td", { class: "num", text: o.wasted ? fmtMs(o.wasted) : "" }),
|
|
1636
|
-
...state.columns.map((k) => offenderCell(o, k)),
|
|
1637
|
-
el("td", { class: "fix", text: o.fix })
|
|
1638
|
-
];
|
|
1639
|
-
const attrs = {
|
|
1640
|
-
class: (o.avoidable ? "has-avoid" : "") + (o.muted ? " muted" : ""),
|
|
1641
|
-
role: tag === "div" ? "listitem" : null,
|
|
1642
|
-
onclick: () => {
|
|
1643
|
-
const last = o.reports[o.reports.length - 1];
|
|
1644
|
-
state.tab = "fix";
|
|
1645
|
-
select(nodeOfReport(last), last);
|
|
1646
|
-
}
|
|
1647
|
-
};
|
|
1648
|
-
if (tag === "tr") return el("tr", attrs, cells);
|
|
1649
|
-
const row = el("div", { ...attrs, class: "vrow " + attrs.class });
|
|
1650
|
-
for (const c of cells) {
|
|
1651
|
-
const span = el("span", { class: c.className });
|
|
1652
|
-
span.append(...c.childNodes);
|
|
1653
|
-
row.append(span);
|
|
1654
|
-
}
|
|
1655
|
-
return row;
|
|
1656
|
-
}
|
|
1657
|
-
const offendersList = virtualList(table, GRID_ROW_H, (o) => offenderRow(o, "div"), { attach: false, headerHeight: () => table.querySelector(".vhead")?.offsetHeight || 0 });
|
|
1658
|
-
function offendersHeader() {
|
|
1659
|
-
return [sortableHeader("Component", "component"), sortableHeader("Avoidable", "avoidable", true), sortableHeader("Total", "total", true), sortableHeader("Wasted", "wasted", true), ...state.columns.map((k) => sortableHeader(OPTIONAL_COLUMNS.find((c) => c.key === k).label, k, true)), el("th", { text: "Top fix" })];
|
|
1816
|
+
function offenderRow(o) {
|
|
1817
|
+
return el(
|
|
1818
|
+
"tr",
|
|
1819
|
+
{
|
|
1820
|
+
class: o.avoidable ? "has-avoid" : "",
|
|
1821
|
+
onclick: () => {
|
|
1822
|
+
const last = o.reports[o.reports.length - 1];
|
|
1823
|
+
state.tab = "fix";
|
|
1824
|
+
select(nodeOfReport(last), last);
|
|
1825
|
+
}
|
|
1826
|
+
},
|
|
1827
|
+
[
|
|
1828
|
+
el("td", { class: "c" }, [el("span", { class: "name", text: o.component }), o.paths.size > 1 ? el("span", { class: "meta", text: ` \xD7${o.paths.size} places` }) : null]),
|
|
1829
|
+
el("td", { class: "num" }, o.avoidable ? el("span", { class: "badge avoid", text: String(o.avoidable) }) : "0"),
|
|
1830
|
+
el("td", { class: "num", text: String(o.total) }),
|
|
1831
|
+
el("td", { class: "num", text: o.wasted ? fmtMs(o.wasted) : "" }),
|
|
1832
|
+
el("td", { class: "fix", text: o.fix })
|
|
1833
|
+
]
|
|
1834
|
+
);
|
|
1660
1835
|
}
|
|
1661
1836
|
function renderOffenders() {
|
|
1662
1837
|
table.textContent = "";
|
|
@@ -1665,28 +1840,12 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1665
1840
|
table.append(el("div", { class: "empty", text: "No re-renders reported yet." }));
|
|
1666
1841
|
return;
|
|
1667
1842
|
}
|
|
1668
|
-
const gridCols = `minmax(140px, 2fr) 70px 60px 80px ${state.columns.map(() => "90px").join(" ")} minmax(120px, 2fr)`;
|
|
1669
|
-
if (rows.length > VIRTUAL_THRESHOLD) {
|
|
1670
|
-
const head = el("div", { class: "vhead", style: `grid-template-columns:${gridCols}` });
|
|
1671
|
-
for (const th of offendersHeader()) {
|
|
1672
|
-
const cell = el("span", { class: th.className, onclick: null });
|
|
1673
|
-
cell.append(...th.childNodes);
|
|
1674
|
-
cell.addEventListener("click", () => th.click());
|
|
1675
|
-
head.append(cell);
|
|
1676
|
-
}
|
|
1677
|
-
head.append(columnsMenu());
|
|
1678
|
-
table.append(head, offendersList.inner);
|
|
1679
|
-
offendersList.inner.style.setProperty("--grid-cols", gridCols);
|
|
1680
|
-
offendersList.setItems(rows);
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
1843
|
const t = el("table", { class: "grid" });
|
|
1684
|
-
|
|
1685
|
-
t.append(el("thead", null, headRow));
|
|
1844
|
+
t.append(el("thead", null, el("tr", null, [sortableHeader("Component", "component"), sortableHeader("Avoidable", "avoidable", true), sortableHeader("Total", "total", true), sortableHeader("Wasted", "wasted", true), el("th", { text: "Top fix" })])));
|
|
1686
1845
|
const body = el("tbody");
|
|
1687
|
-
for (const o of rows) body.append(offenderRow(o
|
|
1846
|
+
for (const o of rows) body.append(offenderRow(o));
|
|
1688
1847
|
t.append(body);
|
|
1689
|
-
table.append(
|
|
1848
|
+
table.append(t);
|
|
1690
1849
|
}
|
|
1691
1850
|
function commitSummaries() {
|
|
1692
1851
|
const out = [];
|
|
@@ -1694,7 +1853,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1694
1853
|
const key = state.commitOrder[i];
|
|
1695
1854
|
const reports = state.commits.get(key);
|
|
1696
1855
|
if (!reports || !reports.some(passes)) continue;
|
|
1697
|
-
out.push({ key, analysis:
|
|
1856
|
+
out.push({ key, analysis: analysisFor(key, reports) });
|
|
1698
1857
|
}
|
|
1699
1858
|
return out;
|
|
1700
1859
|
}
|
|
@@ -1721,7 +1880,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1721
1880
|
const root2 = analysis.roots[0];
|
|
1722
1881
|
list.append(
|
|
1723
1882
|
el("li", { class: (state.selectedCommit === key && state.tab === "commit" ? "selected " : "") + (analysis.avoidable ? "has-avoid" : ""), onclick: () => showCommit(key) }, [
|
|
1724
|
-
el("span", { class: "id", text:
|
|
1883
|
+
el("span", { class: "id", text: `#${key}` }),
|
|
1725
1884
|
el("span", { class: "t", text: fmtTime(analysis.receivedAt) }),
|
|
1726
1885
|
el("span", { class: "n", text: plural(analysis.total, "render") }),
|
|
1727
1886
|
analysis.avoidable ? el("span", { class: "badge avoid", text: `${analysis.avoidable} avoidable` }) : el("span", { class: "badge", text: "ok" }),
|
|
@@ -1736,13 +1895,13 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1736
1895
|
}
|
|
1737
1896
|
table.append(list);
|
|
1738
1897
|
}
|
|
1739
|
-
function fixItem(f
|
|
1898
|
+
function fixItem(f) {
|
|
1899
|
+
const selected = state.selectedFix === f.key && state.tab === "fixlist";
|
|
1740
1900
|
return el(
|
|
1741
|
-
|
|
1901
|
+
"li",
|
|
1742
1902
|
{
|
|
1743
|
-
class:
|
|
1744
|
-
|
|
1745
|
-
"aria-selected": state.selectedFix === f.key && state.tab === "fixlist" ? "true" : null,
|
|
1903
|
+
class: selected ? "selected" : "",
|
|
1904
|
+
"aria-selected": selected ? "true" : null,
|
|
1746
1905
|
onclick: () => {
|
|
1747
1906
|
state.selectedFix = f.key;
|
|
1748
1907
|
state.tab = "fixlist";
|
|
@@ -1757,27 +1916,19 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1757
1916
|
]
|
|
1758
1917
|
);
|
|
1759
1918
|
}
|
|
1760
|
-
const fixesList = virtualList(table, GRID_ROW_H, (f) => fixItem(f, "div"), { attach: false, headerHeight: () => table.querySelector(".section-title")?.offsetHeight || 0 });
|
|
1761
1919
|
function renderFixes() {
|
|
1762
1920
|
table.textContent = "";
|
|
1763
|
-
const reports =
|
|
1764
|
-
const fixes =
|
|
1921
|
+
const reports = filteredReports();
|
|
1922
|
+
const fixes = filteredFixes();
|
|
1765
1923
|
const contexts = contextAttribution(reports);
|
|
1766
|
-
const mutedCount = Object.values(state.notes).filter((n) => n.muted).length;
|
|
1767
1924
|
if (!fixes.length && !contexts.length) {
|
|
1768
|
-
table.append(el("div", { class: "empty", text:
|
|
1925
|
+
table.append(el("div", { class: "empty", text: "No avoidable re-renders, nothing to fix." }));
|
|
1769
1926
|
return;
|
|
1770
1927
|
}
|
|
1771
1928
|
if (fixes.length) {
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
fixesList.setItems(fixes);
|
|
1776
|
-
} else {
|
|
1777
|
-
const list = el("ol", { class: "fixes", role: "list" });
|
|
1778
|
-
for (const f of fixes) list.append(fixItem(f, "li"));
|
|
1779
|
-
table.append(title, list);
|
|
1780
|
-
}
|
|
1929
|
+
const list = el("ol", { class: "fixes", role: "list" });
|
|
1930
|
+
for (const f of fixes) list.append(fixItem(f));
|
|
1931
|
+
table.append(el("div", { class: "section-title", text: "Ranked by avoidable re-renders removed" }), list);
|
|
1781
1932
|
}
|
|
1782
1933
|
if (contexts.length) {
|
|
1783
1934
|
const list = el("ul", { class: "contexts" });
|
|
@@ -1816,9 +1967,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1816
1967
|
openSource: transport.openResource ? (src) => transport.openResource(src.fileName, src.lineNumber, src.columnNumber) : null,
|
|
1817
1968
|
highlight: transport.highlight ? (id) => transport.highlight(id) : null,
|
|
1818
1969
|
copy: copyText,
|
|
1819
|
-
share: transport.panelUrl ? (r) => {
|
|
1820
|
-
void encodeShare(r).then((code) => copyText(`${transport.panelUrl}?report=${code}`));
|
|
1821
|
-
} : null,
|
|
1822
1970
|
readSource: transport.readSource ? (url) => transport.readSource(url) : null
|
|
1823
1971
|
});
|
|
1824
1972
|
const tabButton = (id, label, onclick) => el("button", { class: state.tab === id ? "active" : "", onclick }, label);
|
|
@@ -1827,15 +1975,9 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1827
1975
|
details.append(el("div", { class: "empty", text: "Select a component to see why it re-rendered." }));
|
|
1828
1976
|
return;
|
|
1829
1977
|
}
|
|
1830
|
-
const componentName = node.lastReport ? node.lastReport.component : node.name;
|
|
1831
|
-
const note = state.notes[componentName] || {};
|
|
1832
|
-
const noteBtn = el("button", { class: "ib small" + (note.note ? " active" : ""), title: note.note ? note.note : "Add a note for this component", "aria-label": "Note", onclick: () => toggleNoteEditor() }, [el("span", { class: "glyph", text: "\u270E" })]);
|
|
1833
|
-
const muteBtn = el("button", { class: "ib small" + (note.muted ? " active" : ""), title: note.muted ? "Muted: hidden from Fixes and the summary. Click to unmute." : "Mute: hide this component from Fixes and the summary", "aria-label": note.muted ? "Unmute" : "Mute", onclick: () => setNote(componentName, { muted: !note.muted }) }, [el("span", { class: "glyph", text: note.muted ? "\u{1F515}" : "\u{1F514}" })]);
|
|
1834
1978
|
const header = el("div", { class: "details-header" }, [
|
|
1835
1979
|
el("span", { class: "title" }, [el("span", { class: "bracket", text: "<" }), el("span", { class: "name", text: node.name }), el("span", { class: "bracket", text: ">" })]),
|
|
1836
1980
|
el("span", { class: "meta", text: `${plural(node.total, "re-render")}, ${node.avoidable} avoidable${node.wasted ? ", " + fmtMs(node.wasted) + " wasted" : ""}` }),
|
|
1837
|
-
noteBtn,
|
|
1838
|
-
muteBtn,
|
|
1839
1981
|
el("span", { class: "tabs" }, [
|
|
1840
1982
|
tabButton("latest", "Report", () => {
|
|
1841
1983
|
state.tab = "latest";
|
|
@@ -1858,21 +2000,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1858
2000
|
details.append(header);
|
|
1859
2001
|
const body = el("div", { class: "details-body" });
|
|
1860
2002
|
details.append(body);
|
|
1861
|
-
if (note.note || noteEditorOpen === componentName) {
|
|
1862
|
-
const input = el("input", { type: "text", class: "note-input", placeholder: 'Note for this component (e.g. "known, ticket #123")', value: note.note || "" });
|
|
1863
|
-
input.addEventListener("change", () => {
|
|
1864
|
-
setNote(componentName, { note: input.value.trim() || void 0 });
|
|
1865
|
-
noteEditorOpen = null;
|
|
1866
|
-
});
|
|
1867
|
-
input.addEventListener("keydown", (e) => {
|
|
1868
|
-
if (e.key === "Escape") {
|
|
1869
|
-
noteEditorOpen = null;
|
|
1870
|
-
renderDetails();
|
|
1871
|
-
}
|
|
1872
|
-
});
|
|
1873
|
-
body.append(el("div", { class: "note-box" + (note.muted ? " muted" : "") }, [el("span", { class: "glyph", text: "\u270E" }), input, note.muted ? el("span", { class: "badge", text: "muted" }) : null]));
|
|
1874
|
-
if (noteEditorOpen === componentName) setTimeout(() => input.focus(), 0);
|
|
1875
|
-
}
|
|
1876
2003
|
if (state.tab === "history") {
|
|
1877
2004
|
const list = el("ul", { class: "history" });
|
|
1878
2005
|
for (const r2 of [...node.reports].reverse()) {
|
|
@@ -1927,10 +2054,10 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1927
2054
|
details.append(el("div", { class: "empty", text: "This commit is no longer buffered." }));
|
|
1928
2055
|
return;
|
|
1929
2056
|
}
|
|
1930
|
-
const a =
|
|
2057
|
+
const a = analysisFor(key, reports);
|
|
1931
2058
|
details.append(
|
|
1932
2059
|
el("div", { class: "details-header" }, [
|
|
1933
|
-
el("span", { class: "title", text:
|
|
2060
|
+
el("span", { class: "title", text: `Commit #${key}` }),
|
|
1934
2061
|
el("span", { class: "meta", text: `${plural(a.total, "render")}, ${a.avoidable} avoidable${a.wasted ? ", " + fmtMs(a.wasted) + " wasted" : ""} \xB7 ${fmtTime(a.receivedAt)}` })
|
|
1935
2062
|
])
|
|
1936
2063
|
);
|
|
@@ -1988,7 +2115,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
1988
2115
|
return list;
|
|
1989
2116
|
}
|
|
1990
2117
|
function renderFixDetails() {
|
|
1991
|
-
const fix =
|
|
2118
|
+
const fix = filteredFixes().find((f) => f.key === state.selectedFix);
|
|
1992
2119
|
if (!fix) {
|
|
1993
2120
|
details.append(el("div", { class: "empty", text: "Select a fix." }));
|
|
1994
2121
|
return;
|
|
@@ -2001,7 +2128,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2001
2128
|
}
|
|
2002
2129
|
function renderRootDetails() {
|
|
2003
2130
|
const name = state.selectedRoot;
|
|
2004
|
-
const s =
|
|
2131
|
+
const s = rootCauseSummary2(name, state.commits, analysisFor);
|
|
2005
2132
|
details.append(
|
|
2006
2133
|
el("div", { class: "details-header" }, [
|
|
2007
2134
|
el("span", { class: "title" }, ["Root cause ", el("span", { class: "name", text: `<${name}>` })]),
|
|
@@ -2016,7 +2143,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2016
2143
|
for (const c of s.commits) {
|
|
2017
2144
|
list.append(
|
|
2018
2145
|
el("li", { onclick: () => showCommit(c.key) }, [
|
|
2019
|
-
el("span", { class: "id", text:
|
|
2146
|
+
el("span", { class: "id", text: `#${c.key}` }),
|
|
2020
2147
|
el("span", { class: "t", text: fmtTime(c.analysis.receivedAt) }),
|
|
2021
2148
|
el("span", { class: "badge avoid", text: `${c.count} avoidable` }),
|
|
2022
2149
|
el("span", { class: "root", text: componentList(c.components) })
|
|
@@ -2029,37 +2156,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2029
2156
|
body.append(fixView(s.fixes, { copy: copyText }));
|
|
2030
2157
|
}
|
|
2031
2158
|
}
|
|
2032
|
-
let noteEditorOpen = null;
|
|
2033
|
-
function toggleNoteEditor() {
|
|
2034
|
-
const node = state.selectedKey ? state.nodesByKey.get(state.selectedKey) : null;
|
|
2035
|
-
const name = node ? node.lastReport ? node.lastReport.component : node.name : null;
|
|
2036
|
-
noteEditorOpen = noteEditorOpen === name ? null : name;
|
|
2037
|
-
renderDetails();
|
|
2038
|
-
}
|
|
2039
|
-
function setNote(component, patch) {
|
|
2040
|
-
const next = { ...state.notes[component] || {}, ...patch };
|
|
2041
|
-
if (!next.note && !next.muted) delete state.notes[component];
|
|
2042
|
-
else state.notes[component] = next;
|
|
2043
|
-
if (transport.storage) transport.storage.set("notes", state.notes);
|
|
2044
|
-
renderSummary();
|
|
2045
|
-
renderLeft();
|
|
2046
|
-
renderDetails();
|
|
2047
|
-
}
|
|
2048
|
-
function restoreNotes(raw) {
|
|
2049
|
-
if (!isRecord(raw)) return;
|
|
2050
|
-
const notes = {};
|
|
2051
|
-
for (const [k, v] of Object.entries(raw)) {
|
|
2052
|
-
if (!isRecord(v)) continue;
|
|
2053
|
-
const n = {};
|
|
2054
|
-
if (typeof v.note === "string" && v.note) n.note = v.note;
|
|
2055
|
-
if (v.muted === true) n.muted = true;
|
|
2056
|
-
if (n.note || n.muted) notes[k] = n;
|
|
2057
|
-
}
|
|
2058
|
-
state.notes = notes;
|
|
2059
|
-
renderSummary();
|
|
2060
|
-
renderLeft();
|
|
2061
|
-
renderDetails();
|
|
2062
|
-
}
|
|
2063
2159
|
const sessionSummary = (s) => summarizeSession(s, s.reports);
|
|
2064
2160
|
function persistSessions() {
|
|
2065
2161
|
if (!transport.storage) return;
|
|
@@ -2278,10 +2374,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2278
2374
|
};
|
|
2279
2375
|
const text = JSON.stringify(data, null, 2);
|
|
2280
2376
|
const name = `rerender-lens-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`;
|
|
2281
|
-
if (transport.download) {
|
|
2282
|
-
transport.download(name, text);
|
|
2283
|
-
return;
|
|
2284
|
-
}
|
|
2285
2377
|
try {
|
|
2286
2378
|
const blob = new Blob([text], { type: "application/json" });
|
|
2287
2379
|
const url = URL.createObjectURL(blob);
|
|
@@ -2333,6 +2425,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2333
2425
|
cls = "connected";
|
|
2334
2426
|
title = state.relay ? "live via content script" : "polling the page";
|
|
2335
2427
|
if (lib.overhead) title += ` \xB7 library overhead ${lib.overhead.totalMs.toFixed(1)} ms over ${plural(lib.commits ?? 0, "commit")}, worst ${lib.overhead.maxCommitMs.toFixed(1)} ms`;
|
|
2428
|
+
if (lib.truncated) title += ` \xB7 ${plural(lib.truncated, "report")} skipped by the per-commit cap`;
|
|
2336
2429
|
} else if (state.relay || state.polling) {
|
|
2337
2430
|
text = "no library in page";
|
|
2338
2431
|
cls = "partial";
|
|
@@ -2347,15 +2440,12 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2347
2440
|
tabChip.textContent = state.tabLabel || "";
|
|
2348
2441
|
banner.textContent = "";
|
|
2349
2442
|
const warnings = [];
|
|
2350
|
-
if (lib && typeof lib.protocol === "number" && lib.protocol
|
|
2351
|
-
warnings.push(
|
|
2352
|
-
lib.protocol < PROTOCOL ? `The page runs rerender-lens ${lib.library || ""} (protocol ${lib.protocol}); this panel expects protocol ${PROTOCOL}. Update the rerender-lens package for commit grouping, source links and settings.` : `The page runs a newer rerender-lens (protocol ${lib.protocol}) than this panel (${PROTOCOL}). Update the extension.`
|
|
2353
|
-
);
|
|
2354
|
-
}
|
|
2443
|
+
if (lib && typeof lib.protocol === "number" && lib.protocol > PROTOCOL) warnings.push(`The page runs a newer rerender-lens (protocol ${lib.protocol}) than this panel (${PROTOCOL}). Update the extension.`);
|
|
2355
2444
|
if (lib && lib.production) warnings.push("Production React build detected: component names may be minified and hooks are unlabeled. Use a development build.");
|
|
2356
2445
|
if (lib && lib.injected && lib.source === "page")
|
|
2357
2446
|
warnings.push(`The page runs its own rerender-lens ${lib.library || ""}; the copy injected by the extension stepped aside. Turn injection off for this origin in Settings to avoid loading the library twice.`);
|
|
2358
2447
|
if (lib && lib.enabled === false) warnings.push("rerender-lens is present but disabled in this page.");
|
|
2448
|
+
if (lib && lib.truncated) warnings.push(`${plural(lib.truncated, "report")} skipped: a commit re-rendered more tracked components than the per-commit cap (200) or took over its time budget. Narrow "include" in Settings, or fix the top offenders first.`);
|
|
2359
2449
|
banner.hidden = warnings.length === 0;
|
|
2360
2450
|
for (const w of warnings) banner.append(el("div", { text: w }));
|
|
2361
2451
|
}
|
|
@@ -2460,10 +2550,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2460
2550
|
sec.append(el("div", { class: "meta", text: "Connect to a page running rerender-lens to change its options." }));
|
|
2461
2551
|
return;
|
|
2462
2552
|
}
|
|
2463
|
-
if (lib.protocol < PROTOCOL) {
|
|
2464
|
-
sec.append(el("div", { class: "meta", text: "The page library is too old to be configured from here." }));
|
|
2465
|
-
return;
|
|
2466
|
-
}
|
|
2467
2553
|
const current = Object.assign({}, lib.options || {});
|
|
2468
2554
|
const applyOptions = async (patch) => {
|
|
2469
2555
|
Object.assign(current, patch);
|
|
@@ -2495,6 +2581,33 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2495
2581
|
const max = el("input", { type: "number", min: "0", value: String(current.maxReportsPerComponent || 0) });
|
|
2496
2582
|
max.addEventListener("change", () => void applyOptions({ maxReportsPerComponent: Math.max(0, Number(max.value) || 0) }));
|
|
2497
2583
|
sec.append(el("label", { class: "opt col" }, ["Stop printing a component after N reports (0 = never)", max]));
|
|
2584
|
+
const help = el("div", { class: "section" }, [el("h3", { text: "Help" })]);
|
|
2585
|
+
help.append(
|
|
2586
|
+
el("div", { class: "meta", text: "Opens a GitHub issue prefilled with versions, options and counts. No report contents are included; attach an Export if it helps." }),
|
|
2587
|
+
el("a", { class: "report-link", href: issueUrl(), target: "_blank", rel: "noreferrer", text: "Report a problem \u2197" })
|
|
2588
|
+
);
|
|
2589
|
+
body.append(help);
|
|
2590
|
+
}
|
|
2591
|
+
function issueUrl() {
|
|
2592
|
+
const lib = state.library;
|
|
2593
|
+
const ext = typeof chrome !== "undefined" && chrome.runtime && typeof chrome.runtime.getManifest === "function" ? chrome.runtime.getManifest().version : "n/a";
|
|
2594
|
+
const react = lib?.react?.map((r) => `${r.version || "?"}${r.bundleType === 0 ? " (prod)" : ""}`).join(", ") || "not detected";
|
|
2595
|
+
const transportLabel = state.tabLabel || (state.relay ? "content script" : state.polling ? "polling" : "none");
|
|
2596
|
+
const lines = [
|
|
2597
|
+
"### What happened",
|
|
2598
|
+
"",
|
|
2599
|
+
"(what you did, what you expected, what the panel showed)",
|
|
2600
|
+
"",
|
|
2601
|
+
"### Environment",
|
|
2602
|
+
`- extension ${ext}, panel protocol ${PROTOCOL}, transport: ${transportLabel}`,
|
|
2603
|
+
`- library ${lib?.library || "not detected"} (protocol ${lib?.protocol ?? "?"}), React ${react}${lib?.production ? ", production build" : ""}`,
|
|
2604
|
+
`- options: ${JSON.stringify(lib?.options || {})}`,
|
|
2605
|
+
`- overhead: ${lib?.overhead ? `${lib.overhead.totalMs.toFixed(1)} ms over ${lib.commits ?? 0} commits, worst ${lib.overhead.maxCommitMs.toFixed(1)} ms` : "n/a"}; reports skipped by the cap: ${lib?.truncated ?? 0}`,
|
|
2606
|
+
`- buffered: ${state.reports.length} reports, ${totals.avoidable} avoidable; top fix: ${bestFix ? bestFix.label : "none"}`,
|
|
2607
|
+
`- browser: ${typeof navigator !== "undefined" ? navigator.userAgent : "n/a"}`
|
|
2608
|
+
];
|
|
2609
|
+
const params2 = new URLSearchParams({ title: "Panel: ", body: lines.join("\n"), labels: "bug" });
|
|
2610
|
+
return `https://github.com/NexaLeaf/rerender-lens/issues/new?${params2.toString()}`;
|
|
2498
2611
|
}
|
|
2499
2612
|
function handle(message) {
|
|
2500
2613
|
if (!isRecord(message)) return;
|
|
@@ -2522,7 +2635,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2522
2635
|
renderStatus();
|
|
2523
2636
|
break;
|
|
2524
2637
|
case "hello":
|
|
2525
|
-
|
|
2638
|
+
if (isRecord(message.payload)) setLibrary(message.payload);
|
|
2526
2639
|
break;
|
|
2527
2640
|
case "clear":
|
|
2528
2641
|
case "navigated":
|
|
@@ -2538,6 +2651,9 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2538
2651
|
if (r) enqueue(r);
|
|
2539
2652
|
break;
|
|
2540
2653
|
}
|
|
2654
|
+
case "batch":
|
|
2655
|
+
if (Array.isArray(message.items)) for (const item of message.items) handle(item);
|
|
2656
|
+
break;
|
|
2541
2657
|
}
|
|
2542
2658
|
}
|
|
2543
2659
|
const panelApi = {
|
|
@@ -2552,8 +2668,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2552
2668
|
setView,
|
|
2553
2669
|
openSettings: () => toggleSettings(true),
|
|
2554
2670
|
startRecording,
|
|
2555
|
-
stopRecording
|
|
2556
|
-
setNote
|
|
2671
|
+
stopRecording
|
|
2557
2672
|
};
|
|
2558
2673
|
if (options.theme === "dark") document.documentElement.classList.add("theme-dark");
|
|
2559
2674
|
state.origin = transport.origin || null;
|
|
@@ -2568,12 +2683,60 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2568
2683
|
});
|
|
2569
2684
|
Promise.resolve(transport.storage.get("sessions")).then(restoreSessions, () => {
|
|
2570
2685
|
});
|
|
2571
|
-
Promise.resolve(transport.storage.get("notes")).then(restoreNotes, () => {
|
|
2572
|
-
});
|
|
2573
2686
|
}
|
|
2574
2687
|
transport.subscribe(handle);
|
|
2575
2688
|
return panelApi;
|
|
2576
2689
|
}
|
|
2690
|
+
var systemTheme = () => typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
2691
|
+
var fetchSource = (url) => fetch(url).then((res) => res.ok ? res.text() : null).catch(() => null);
|
|
2692
|
+
function localStorageAdapter(prefix) {
|
|
2693
|
+
const mem = (key) => `${prefix}:${key}`;
|
|
2694
|
+
return {
|
|
2695
|
+
get: (key) => {
|
|
2696
|
+
try {
|
|
2697
|
+
const raw = localStorage.getItem(mem(key));
|
|
2698
|
+
return raw ? JSON.parse(raw) : void 0;
|
|
2699
|
+
} catch {
|
|
2700
|
+
return void 0;
|
|
2701
|
+
}
|
|
2702
|
+
},
|
|
2703
|
+
set: (key, value) => {
|
|
2704
|
+
try {
|
|
2705
|
+
localStorage.setItem(mem(key), JSON.stringify(value));
|
|
2706
|
+
} catch {
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2710
|
+
}
|
|
2711
|
+
function commandBridge(send, timeoutMs) {
|
|
2712
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2713
|
+
const bridge = (cmd, arg) => new Promise((resolve, reject) => {
|
|
2714
|
+
const id = Math.random().toString(36).slice(2);
|
|
2715
|
+
const fail = () => {
|
|
2716
|
+
clearTimeout(timer);
|
|
2717
|
+
pending.delete(id);
|
|
2718
|
+
resolve(null);
|
|
2719
|
+
};
|
|
2720
|
+
const timer = setTimeout(fail, timeoutMs);
|
|
2721
|
+
pending.set(id, { resolve: (v) => v instanceof Error ? reject(v) : resolve(v), timer });
|
|
2722
|
+
try {
|
|
2723
|
+
void Promise.resolve(send({ __rerenderLensCmd: true, id, cmd, arg })).catch(fail);
|
|
2724
|
+
} catch {
|
|
2725
|
+
fail();
|
|
2726
|
+
}
|
|
2727
|
+
});
|
|
2728
|
+
const reply = (m) => {
|
|
2729
|
+
if (m.__rerenderLensReply !== true || typeof m.id !== "string") return false;
|
|
2730
|
+
const p = pending.get(m.id);
|
|
2731
|
+
if (p) {
|
|
2732
|
+
pending.delete(m.id);
|
|
2733
|
+
clearTimeout(p.timer);
|
|
2734
|
+
p.resolve(typeof m.error === "string" ? new Error(m.error) : m.result);
|
|
2735
|
+
}
|
|
2736
|
+
return true;
|
|
2737
|
+
};
|
|
2738
|
+
return { bridge, reply };
|
|
2739
|
+
}
|
|
2577
2740
|
function createRelayTransport(io) {
|
|
2578
2741
|
let listener = null;
|
|
2579
2742
|
let relayConnected = false;
|
|
@@ -2600,18 +2763,72 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2600
2763
|
try {
|
|
2601
2764
|
const info = await io.bridge("info");
|
|
2602
2765
|
if (info) {
|
|
2603
|
-
emit({ type: "hello", version: info.protocol
|
|
2766
|
+
emit({ type: "hello", version: info.protocol, payload: info });
|
|
2767
|
+
startInfoRefresh();
|
|
2604
2768
|
return true;
|
|
2605
2769
|
}
|
|
2606
2770
|
} catch {
|
|
2607
2771
|
}
|
|
2608
2772
|
return false;
|
|
2609
2773
|
}
|
|
2774
|
+
let infoTimer = null;
|
|
2775
|
+
function startInfoRefresh() {
|
|
2776
|
+
if (infoTimer) return;
|
|
2777
|
+
infoTimer = setInterval(() => {
|
|
2778
|
+
if (disposed) return stopInfoRefresh();
|
|
2779
|
+
io.bridge("info").then((info) => {
|
|
2780
|
+
if (info) emit({ type: "hello", version: info.protocol, payload: info });
|
|
2781
|
+
else stopInfoRefresh();
|
|
2782
|
+
}).catch(() => stopInfoRefresh());
|
|
2783
|
+
}, INFO_REFRESH_MS);
|
|
2784
|
+
}
|
|
2785
|
+
function stopInfoRefresh() {
|
|
2786
|
+
if (infoTimer) clearInterval(infoTimer);
|
|
2787
|
+
infoTimer = null;
|
|
2788
|
+
}
|
|
2789
|
+
let syncGen = 0;
|
|
2790
|
+
let syncTimer = null;
|
|
2791
|
+
let disposed = false;
|
|
2792
|
+
function cancelSync() {
|
|
2793
|
+
syncGen++;
|
|
2794
|
+
if (syncTimer) clearTimeout(syncTimer);
|
|
2795
|
+
syncTimer = null;
|
|
2796
|
+
stopInfoRefresh();
|
|
2797
|
+
}
|
|
2798
|
+
function syncWithRetry(onReady, initialDelay = 0) {
|
|
2799
|
+
cancelSync();
|
|
2800
|
+
const gen = syncGen;
|
|
2801
|
+
let delay = SYNC_RETRY_MIN;
|
|
2802
|
+
let tries = 0;
|
|
2803
|
+
const attempt = async () => {
|
|
2804
|
+
syncTimer = null;
|
|
2805
|
+
if (gen !== syncGen || disposed) return;
|
|
2806
|
+
const ok = await syncWithPage();
|
|
2807
|
+
if (gen !== syncGen || disposed) return;
|
|
2808
|
+
if (ok) {
|
|
2809
|
+
onReady();
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
if (++tries >= SYNC_RETRIES) return;
|
|
2813
|
+
syncTimer = setTimeout(() => void attempt(), delay);
|
|
2814
|
+
delay = Math.min(delay * 2, SYNC_RETRY_MAX);
|
|
2815
|
+
};
|
|
2816
|
+
if (initialDelay > 0) syncTimer = setTimeout(() => void attempt(), initialDelay);
|
|
2817
|
+
else void attempt();
|
|
2818
|
+
}
|
|
2819
|
+
let droppedSeen = false;
|
|
2820
|
+
function resetSince() {
|
|
2821
|
+
since = 0;
|
|
2822
|
+
droppedSeen = false;
|
|
2823
|
+
}
|
|
2610
2824
|
async function pollOnce() {
|
|
2611
2825
|
try {
|
|
2612
2826
|
const res = await io.bridge("pull", since);
|
|
2613
2827
|
if (!res) return;
|
|
2614
|
-
if (res.dropped)
|
|
2828
|
+
if (res.dropped && !droppedSeen) {
|
|
2829
|
+
droppedSeen = true;
|
|
2830
|
+
emit({ type: "clear" });
|
|
2831
|
+
}
|
|
2615
2832
|
for (const p of res.reports) emit({ type: "report", payload: p });
|
|
2616
2833
|
since = res.seq;
|
|
2617
2834
|
} catch {
|
|
@@ -2627,19 +2844,23 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2627
2844
|
emit({ type: "polling", on: false });
|
|
2628
2845
|
}
|
|
2629
2846
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
});
|
|
2634
|
-
else {
|
|
2635
|
-
const res = await io.bridge("pull", 0).catch(() => null);
|
|
2636
|
-
if (res) {
|
|
2637
|
-
for (const p of res.reports) emit({ type: "report", payload: p });
|
|
2638
|
-
since = res.seq;
|
|
2639
|
-
} else io.bridge("replay").catch(() => {
|
|
2847
|
+
function attachToPage(initialDelay = 0) {
|
|
2848
|
+
syncWithRetry(() => {
|
|
2849
|
+
if (relayConnected) io.bridge("replay").catch(() => {
|
|
2640
2850
|
});
|
|
2641
|
-
|
|
2642
|
-
|
|
2851
|
+
else {
|
|
2852
|
+
const gen = syncGen;
|
|
2853
|
+
void io.bridge("pull", 0).catch(() => null).then((res) => {
|
|
2854
|
+
if (gen !== syncGen || disposed) return;
|
|
2855
|
+
if (res) {
|
|
2856
|
+
for (const p of res.reports) emit({ type: "report", payload: p });
|
|
2857
|
+
since = res.seq;
|
|
2858
|
+
} else io.bridge("replay").catch(() => {
|
|
2859
|
+
});
|
|
2860
|
+
setPolling(true);
|
|
2861
|
+
});
|
|
2862
|
+
}
|
|
2863
|
+
}, initialDelay);
|
|
2643
2864
|
}
|
|
2644
2865
|
function connect() {
|
|
2645
2866
|
if (panelPort) {
|
|
@@ -2660,16 +2881,20 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2660
2881
|
setPolling(false);
|
|
2661
2882
|
} else if (m.type === "disconnected") {
|
|
2662
2883
|
relayConnected = false;
|
|
2663
|
-
|
|
2884
|
+
syncWithRetry(() => setPolling(true));
|
|
2664
2885
|
}
|
|
2665
2886
|
emit(m);
|
|
2666
2887
|
});
|
|
2667
2888
|
port.onDisconnect.addListener(() => {
|
|
2668
2889
|
if (panelPort !== port) return;
|
|
2669
2890
|
panelPort = null;
|
|
2670
|
-
setTimeout(
|
|
2891
|
+
reconnectTimer = setTimeout(() => {
|
|
2892
|
+
reconnectTimer = null;
|
|
2893
|
+
if (!disposed) connect();
|
|
2894
|
+
}, 1e3);
|
|
2671
2895
|
});
|
|
2672
2896
|
}
|
|
2897
|
+
let reconnectTimer = null;
|
|
2673
2898
|
const transport = {
|
|
2674
2899
|
origin,
|
|
2675
2900
|
tabLabel: io.tabLabel ?? null,
|
|
@@ -2677,36 +2902,44 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2677
2902
|
listener = fn;
|
|
2678
2903
|
connect();
|
|
2679
2904
|
io.onNavigated(() => {
|
|
2680
|
-
|
|
2905
|
+
if (disposed) return;
|
|
2906
|
+
resetSince();
|
|
2907
|
+
setPolling(false);
|
|
2908
|
+
cancelSync();
|
|
2681
2909
|
fn({ type: "navigated" });
|
|
2682
2910
|
void resolveOrigin().then(() => {
|
|
2683
|
-
|
|
2911
|
+
if (!disposed) attachToPage(NAVIGATION_SETTLE);
|
|
2684
2912
|
});
|
|
2685
2913
|
});
|
|
2686
2914
|
io.onTabChange?.((label) => {
|
|
2915
|
+
if (disposed) return;
|
|
2687
2916
|
transport.tabLabel = label;
|
|
2688
2917
|
const next = io.tabId();
|
|
2689
2918
|
if (next === currentTab) {
|
|
2690
2919
|
fn({ type: "tab-label", payload: label });
|
|
2691
2920
|
return;
|
|
2692
2921
|
}
|
|
2693
|
-
|
|
2922
|
+
resetSince();
|
|
2694
2923
|
relayConnected = false;
|
|
2695
2924
|
setPolling(false);
|
|
2925
|
+
cancelSync();
|
|
2696
2926
|
currentTab = next;
|
|
2697
2927
|
void resolveOrigin().then(() => {
|
|
2928
|
+
if (disposed) return;
|
|
2698
2929
|
fn({ type: "tab", payload: label });
|
|
2699
2930
|
connect();
|
|
2700
|
-
|
|
2931
|
+
attachToPage();
|
|
2701
2932
|
});
|
|
2702
2933
|
});
|
|
2703
|
-
void resolveOrigin().then(() =>
|
|
2934
|
+
void resolveOrigin().then(() => {
|
|
2935
|
+
if (!disposed) attachToPage();
|
|
2936
|
+
});
|
|
2704
2937
|
},
|
|
2705
2938
|
replay() {
|
|
2706
2939
|
if (relayConnected) io.bridge("replay").catch(() => {
|
|
2707
2940
|
});
|
|
2708
2941
|
else {
|
|
2709
|
-
|
|
2942
|
+
resetSince();
|
|
2710
2943
|
emit({ type: "clear" });
|
|
2711
2944
|
void syncWithPage().then(() => pollOnce());
|
|
2712
2945
|
}
|
|
@@ -2714,7 +2947,22 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2714
2947
|
clear() {
|
|
2715
2948
|
io.bridge("clear").catch(() => {
|
|
2716
2949
|
});
|
|
2717
|
-
|
|
2950
|
+
resetSince();
|
|
2951
|
+
},
|
|
2952
|
+
dispose() {
|
|
2953
|
+
disposed = true;
|
|
2954
|
+
cancelSync();
|
|
2955
|
+
setPolling(false);
|
|
2956
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
2957
|
+
reconnectTimer = null;
|
|
2958
|
+
if (panelPort) {
|
|
2959
|
+
try {
|
|
2960
|
+
panelPort.disconnect();
|
|
2961
|
+
} catch {
|
|
2962
|
+
}
|
|
2963
|
+
panelPort = null;
|
|
2964
|
+
}
|
|
2965
|
+
listener = null;
|
|
2718
2966
|
},
|
|
2719
2967
|
configure: (options) => io.bridge("configure", options).then((r) => r ?? void 0),
|
|
2720
2968
|
highlight: (id) => io.bridge("highlight", id).catch(() => {
|
|
@@ -2737,7 +2985,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2737
2985
|
if (io.openResource) transport.openResource = io.openResource;
|
|
2738
2986
|
if (io.undock) transport.undock = io.undock;
|
|
2739
2987
|
if (io.readSource) transport.readSource = io.readSource;
|
|
2740
|
-
transport.panelUrl = chrome.runtime.getURL("panel.html");
|
|
2741
2988
|
return transport;
|
|
2742
2989
|
}
|
|
2743
2990
|
function devtoolsIO() {
|
|
@@ -2749,7 +2996,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2749
2996
|
})
|
|
2750
2997
|
);
|
|
2751
2998
|
const expressions = {
|
|
2752
|
-
info: () => "b.info
|
|
2999
|
+
info: () => "b.info()",
|
|
2753
3000
|
pull: (since) => `b.pull?b.pull(${Number(since) || 0}):null`,
|
|
2754
3001
|
replay: () => "b.replay()",
|
|
2755
3002
|
clear: () => "b.clear()",
|
|
@@ -2786,7 +3033,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2786
3033
|
try {
|
|
2787
3034
|
switch (cmd) {
|
|
2788
3035
|
case "info":
|
|
2789
|
-
return b.info
|
|
3036
|
+
return b.info();
|
|
2790
3037
|
case "pull":
|
|
2791
3038
|
return b.pull ? b.pull(arg) : null;
|
|
2792
3039
|
case "replay":
|
|
@@ -2893,19 +3140,17 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
2893
3140
|
},
|
|
2894
3141
|
openResource: (url) => void chrome.tabs.create({ url }),
|
|
2895
3142
|
undock: (mode) => tabId === null ? Promise.reject(new Error("no tab")) : openOutside(mode, tabId),
|
|
2896
|
-
//
|
|
2897
|
-
readSource:
|
|
3143
|
+
// Host permission for the origin is required to fetch the module source (and present when the panel works at all).
|
|
3144
|
+
readSource: fetchSource
|
|
2898
3145
|
};
|
|
2899
3146
|
return io;
|
|
2900
3147
|
}
|
|
2901
3148
|
function bootExtension() {
|
|
2902
|
-
const
|
|
2903
|
-
const theme = chrome.devtools.panels.themeName === "dark" || prefersDark ? "dark" : "light";
|
|
3149
|
+
const theme = chrome.devtools.panels.themeName === "dark" ? "dark" : systemTheme();
|
|
2904
3150
|
createPanel(document.getElementById("root"), createRelayTransport(devtoolsIO()), { theme });
|
|
2905
3151
|
}
|
|
2906
3152
|
function bootStandalone(opts = {}) {
|
|
2907
|
-
|
|
2908
|
-
return createPanel(document.getElementById("root"), createRelayTransport(standaloneIO(opts)), { theme: prefersDark ? "dark" : "light" });
|
|
3153
|
+
return createPanel(document.getElementById("root"), createRelayTransport(standaloneIO(opts)), { theme: systemTheme() });
|
|
2909
3154
|
}
|
|
2910
3155
|
function sampleReports() {
|
|
2911
3156
|
const fn = (name) => FN_PREFIX + name;
|
|
@@ -3036,7 +3281,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3036
3281
|
function createBroadcastTransport(name) {
|
|
3037
3282
|
let listener = null;
|
|
3038
3283
|
let channel = null;
|
|
3039
|
-
const pending = /* @__PURE__ */ new Map();
|
|
3040
3284
|
const emit = (m) => {
|
|
3041
3285
|
if (listener) listener(m);
|
|
3042
3286
|
};
|
|
@@ -3045,36 +3289,15 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3045
3289
|
channel = new BroadcastChannel(name);
|
|
3046
3290
|
channel.onmessage = (event) => {
|
|
3047
3291
|
const data = event.data;
|
|
3048
|
-
if (!data) return;
|
|
3049
|
-
if (data.__rerenderLensReply === true && typeof data.id === "string") {
|
|
3050
|
-
const p = pending.get(data.id);
|
|
3051
|
-
if (!p) return;
|
|
3052
|
-
pending.delete(data.id);
|
|
3053
|
-
clearTimeout(p.timer);
|
|
3054
|
-
p.resolve(typeof data.error === "string" ? new Error(data.error) : data.result);
|
|
3055
|
-
return;
|
|
3056
|
-
}
|
|
3292
|
+
if (!data || reply(data)) return;
|
|
3057
3293
|
if (data.__rerenderLens === true && typeof data.type === "string") emit({ type: data.type, version: typeof data.version === "number" ? data.version : void 0, payload: data.payload });
|
|
3058
3294
|
};
|
|
3059
3295
|
return channel;
|
|
3060
3296
|
};
|
|
3061
|
-
const bridge = (
|
|
3062
|
-
const id = Math.random().toString(36).slice(2);
|
|
3063
|
-
const timer = setTimeout(() => {
|
|
3064
|
-
pending.delete(id);
|
|
3065
|
-
resolve(null);
|
|
3066
|
-
}, 1500);
|
|
3067
|
-
pending.set(id, {
|
|
3068
|
-
resolve: (v) => v instanceof Error ? reject(v) : resolve(v),
|
|
3069
|
-
timer
|
|
3070
|
-
});
|
|
3071
|
-
open().postMessage({ __rerenderLensCmd: true, id, cmd, arg });
|
|
3072
|
-
});
|
|
3073
|
-
const mem = (key) => `rerender-lens:${name}:${key}`;
|
|
3297
|
+
const { bridge, reply } = commandBridge((message) => open().postMessage(message), 1500);
|
|
3074
3298
|
const transport = {
|
|
3075
3299
|
origin: location.origin,
|
|
3076
3300
|
tabLabel: `channel "${name}"`,
|
|
3077
|
-
panelUrl: location.origin + location.pathname,
|
|
3078
3301
|
subscribe(fn) {
|
|
3079
3302
|
listener = fn;
|
|
3080
3303
|
open();
|
|
@@ -3084,7 +3307,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3084
3307
|
fn({ type: "disconnected" });
|
|
3085
3308
|
return;
|
|
3086
3309
|
}
|
|
3087
|
-
fn({ type: "hello", version: info.protocol
|
|
3310
|
+
fn({ type: "hello", version: info.protocol, payload: info });
|
|
3088
3311
|
const res = await bridge("pull", 0);
|
|
3089
3312
|
if (res) for (const p of res.reports) fn({ type: "report", payload: p });
|
|
3090
3313
|
});
|
|
@@ -3096,23 +3319,8 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3096
3319
|
}),
|
|
3097
3320
|
flashAvoidable: (on) => bridge("flash", !!on).catch(() => {
|
|
3098
3321
|
}),
|
|
3099
|
-
storage: {
|
|
3100
|
-
|
|
3101
|
-
try {
|
|
3102
|
-
const raw = localStorage.getItem(mem(key));
|
|
3103
|
-
return raw ? JSON.parse(raw) : void 0;
|
|
3104
|
-
} catch {
|
|
3105
|
-
return void 0;
|
|
3106
|
-
}
|
|
3107
|
-
},
|
|
3108
|
-
set: (key, value) => {
|
|
3109
|
-
try {
|
|
3110
|
-
localStorage.setItem(mem(key), JSON.stringify(value));
|
|
3111
|
-
} catch {
|
|
3112
|
-
}
|
|
3113
|
-
}
|
|
3114
|
-
},
|
|
3115
|
-
readSource: (url) => fetch(url).then((res) => res.ok ? res.text() : null).catch(() => null),
|
|
3322
|
+
storage: localStorageAdapter(`rerender-lens:${name}`),
|
|
3323
|
+
readSource: fetchSource,
|
|
3116
3324
|
copy: (text) => navigator.clipboard.writeText(text).catch(() => {
|
|
3117
3325
|
})
|
|
3118
3326
|
};
|
|
@@ -3123,24 +3331,11 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3123
3331
|
let listener = null;
|
|
3124
3332
|
let stream = null;
|
|
3125
3333
|
let appsOnline = null;
|
|
3126
|
-
const pending = /* @__PURE__ */ new Map();
|
|
3127
3334
|
const emit = (m) => {
|
|
3128
3335
|
if (listener) listener(m);
|
|
3129
3336
|
};
|
|
3130
3337
|
const post = (message) => fetch(`${base}/message`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(message) }).then(() => void 0);
|
|
3131
|
-
const bridge
|
|
3132
|
-
const id = Math.random().toString(36).slice(2);
|
|
3133
|
-
const timer = setTimeout(() => {
|
|
3134
|
-
pending.delete(id);
|
|
3135
|
-
resolve(null);
|
|
3136
|
-
}, 2e3);
|
|
3137
|
-
pending.set(id, { resolve: (v) => v instanceof Error ? reject(v) : resolve(v), timer });
|
|
3138
|
-
post({ __rerenderLensCmd: true, id, cmd, arg }).catch(() => {
|
|
3139
|
-
clearTimeout(timer);
|
|
3140
|
-
pending.delete(id);
|
|
3141
|
-
resolve(null);
|
|
3142
|
-
});
|
|
3143
|
-
});
|
|
3338
|
+
const { bridge, reply } = commandBridge(post, 2e3);
|
|
3144
3339
|
const handleData = (data) => {
|
|
3145
3340
|
let parsed;
|
|
3146
3341
|
try {
|
|
@@ -3149,18 +3344,12 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3149
3344
|
return;
|
|
3150
3345
|
}
|
|
3151
3346
|
for (const m of Array.isArray(parsed) ? parsed : [parsed]) {
|
|
3152
|
-
if (!isRecord(m)) continue;
|
|
3153
|
-
if (m.
|
|
3154
|
-
const p = pending.get(m.id);
|
|
3155
|
-
if (!p) continue;
|
|
3156
|
-
pending.delete(m.id);
|
|
3157
|
-
clearTimeout(p.timer);
|
|
3158
|
-
p.resolve(typeof m.error === "string" ? new Error(m.error) : m.result);
|
|
3159
|
-
} else if (m.__rerenderLens === true && m.type === "relay") {
|
|
3347
|
+
if (!isRecord(m) || reply(m)) continue;
|
|
3348
|
+
if (m.__rerenderLens === true && m.type === "relay") {
|
|
3160
3349
|
const apps = isRecord(m.payload) && typeof m.payload.apps === "number" ? m.payload.apps : 0;
|
|
3161
3350
|
const wasOnline = appsOnline;
|
|
3162
3351
|
appsOnline = apps;
|
|
3163
|
-
if (apps > 0 && wasOnline
|
|
3352
|
+
if (apps > 0 && (wasOnline === null || wasOnline === 0)) void attach();
|
|
3164
3353
|
else if (apps === 0) emit({ type: "disconnected" });
|
|
3165
3354
|
} else if (m.__rerenderLens === true && typeof m.type === "string") emit({ type: m.type, version: typeof m.version === "number" ? m.version : void 0, payload: m.payload });
|
|
3166
3355
|
}
|
|
@@ -3172,15 +3361,13 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3172
3361
|
return;
|
|
3173
3362
|
}
|
|
3174
3363
|
emit({ type: "connected" });
|
|
3175
|
-
emit({ type: "hello", version: info.protocol
|
|
3364
|
+
emit({ type: "hello", version: info.protocol, payload: info });
|
|
3176
3365
|
const res = await bridge("pull", 0);
|
|
3177
3366
|
if (res) for (const p of res.reports) emit({ type: "report", payload: p });
|
|
3178
3367
|
}
|
|
3179
|
-
const mem = (key) => `rerender-lens:relay:${base}:${key}`;
|
|
3180
3368
|
const transport = {
|
|
3181
3369
|
origin: base,
|
|
3182
3370
|
tabLabel: `relay ${base.replace(/^https?:\/\//, "")}`,
|
|
3183
|
-
panelUrl: location.origin + location.pathname,
|
|
3184
3371
|
subscribe(fn) {
|
|
3185
3372
|
listener = fn;
|
|
3186
3373
|
const open = () => {
|
|
@@ -3191,7 +3378,6 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3191
3378
|
};
|
|
3192
3379
|
};
|
|
3193
3380
|
open();
|
|
3194
|
-
void attach();
|
|
3195
3381
|
},
|
|
3196
3382
|
replay: () => void bridge("replay"),
|
|
3197
3383
|
clear: () => void bridge("clear"),
|
|
@@ -3200,47 +3386,18 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3200
3386
|
}),
|
|
3201
3387
|
flashAvoidable: (on) => bridge("flash", !!on).catch(() => {
|
|
3202
3388
|
}),
|
|
3203
|
-
storage: {
|
|
3204
|
-
|
|
3205
|
-
try {
|
|
3206
|
-
const raw = localStorage.getItem(mem(key));
|
|
3207
|
-
return raw ? JSON.parse(raw) : void 0;
|
|
3208
|
-
} catch {
|
|
3209
|
-
return void 0;
|
|
3210
|
-
}
|
|
3211
|
-
},
|
|
3212
|
-
set: (key, value) => {
|
|
3213
|
-
try {
|
|
3214
|
-
localStorage.setItem(mem(key), JSON.stringify(value));
|
|
3215
|
-
} catch {
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
},
|
|
3219
|
-
readSource: (url) => fetch(url).then((res) => res.ok ? res.text() : null).catch(() => null),
|
|
3389
|
+
storage: localStorageAdapter(`rerender-lens:relay:${base}`),
|
|
3390
|
+
readSource: fetchSource,
|
|
3220
3391
|
copy: (text) => navigator.clipboard.writeText(text).catch(() => {
|
|
3221
3392
|
})
|
|
3222
3393
|
};
|
|
3223
3394
|
return transport;
|
|
3224
3395
|
}
|
|
3225
3396
|
function bootRelay(relayUrl) {
|
|
3226
|
-
|
|
3227
|
-
return createPanel(document.getElementById("root"), createRelayClientTransport(relayUrl), { theme: prefersDark ? "dark" : "light" });
|
|
3397
|
+
return createPanel(document.getElementById("root"), createRelayClientTransport(relayUrl), { theme: systemTheme() });
|
|
3228
3398
|
}
|
|
3229
3399
|
function bootBroadcast(name) {
|
|
3230
|
-
|
|
3231
|
-
return createPanel(document.getElementById("root"), createBroadcastTransport(name), { theme: prefersDark ? "dark" : "light" });
|
|
3232
|
-
}
|
|
3233
|
-
async function bootShared(code) {
|
|
3234
|
-
const report = await decodeShare(code);
|
|
3235
|
-
const transport = { subscribe() {
|
|
3236
|
-
}, panelUrl: location.origin + location.pathname };
|
|
3237
|
-
const panel = createPanel(document.getElementById("root"), transport, { theme: typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" });
|
|
3238
|
-
if (!report) {
|
|
3239
|
-
panel.handle({ type: "hello", version: PROTOCOL, payload: { protocol: PROTOCOL, library: "shared link", react: [], enabled: false } });
|
|
3240
|
-
return;
|
|
3241
|
-
}
|
|
3242
|
-
panel.importData([report]);
|
|
3243
|
-
panel.select(report.component);
|
|
3400
|
+
return createPanel(document.getElementById("root"), createBroadcastTransport(name), { theme: systemTheme() });
|
|
3244
3401
|
}
|
|
3245
3402
|
function bootDemo() {
|
|
3246
3403
|
const params2 = new URLSearchParams(location.search);
|
|
@@ -3253,7 +3410,7 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3253
3410
|
origin: "http://localhost:5199",
|
|
3254
3411
|
subscribe(fn) {
|
|
3255
3412
|
fn({ type: "connected" });
|
|
3256
|
-
fn({ type: "hello", version: PROTOCOL, payload: {
|
|
3413
|
+
fn({ type: "hello", version: PROTOCOL, payload: { library: "demo", protocol: PROTOCOL, react: [{ version: "19.2.0", bundleType: 1 }], production: false, enabled: true, options: { trackAllMemoized: true, silent: true }, source: "page", injected: false } });
|
|
3257
3414
|
if (flood > 0) {
|
|
3258
3415
|
const t0 = performance.now();
|
|
3259
3416
|
for (const r of floodReports(flood)) fn({ type: "report", payload: r });
|
|
@@ -3283,11 +3440,9 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3283
3440
|
originStatus: () => Promise.resolve({ origin: "http://localhost:5199", builtIn: true, permitted: true, enabled: true, inject: false, deferHook: false }),
|
|
3284
3441
|
setOrigin: () => Promise.resolve(),
|
|
3285
3442
|
storage: { get: (k) => Promise.resolve(mem[k]), set: (k, v) => Promise.resolve(mem[k] = v) },
|
|
3286
|
-
panelUrl: location.origin + location.pathname,
|
|
3287
3443
|
readSource: () => Promise.resolve("import { memo } from 'react';\n\nexport const ProductList = memo(function ProductList(props) {\n const [selected, setSelected] = useState(null);\n return (\n <ul>\n {props.products.map((p) => (\n <ProductRow key={p.id} product={p} style={{ color: 'red' }} onSelect={(id) => props.onSelect(id)} />\n ))}\n </ul>\n );\n});\n")
|
|
3288
3444
|
};
|
|
3289
|
-
|
|
3290
|
-
createPanel(document.getElementById("root"), transport, { theme: dark ? "dark" : "light" });
|
|
3445
|
+
createPanel(document.getElementById("root"), transport, { theme: /theme=dark/.test(location.search) ? "dark" : systemTheme() });
|
|
3291
3446
|
}
|
|
3292
3447
|
var api = {
|
|
3293
3448
|
PROTOCOL,
|
|
@@ -3306,18 +3461,15 @@ setState((prev) => (deepEqual(prev, next) ? prev : next));`
|
|
|
3306
3461
|
createRelayTransport,
|
|
3307
3462
|
createBroadcastTransport,
|
|
3308
3463
|
createRelayClientTransport,
|
|
3309
|
-
encodeShare,
|
|
3310
|
-
decodeShare,
|
|
3311
3464
|
sourceContext,
|
|
3312
|
-
analysis: { firstDifferentPath, diffLeaves, fixesFor, rankFixes, rootCauseOf, analyzeCommit, contextAttribution, cascadeTree, rootCauseSummary, summarizeSession, compareSessions }
|
|
3465
|
+
analysis: { firstDifferentPath, diffLeaves, fixesFor, rankFixes, rootCauseOf, analyzeCommit: analyzeCommit2, contextAttribution, cascadeTree, rootCauseSummary: rootCauseSummary2, summarizeSession, compareSessions }
|
|
3313
3466
|
};
|
|
3314
3467
|
window.RerenderLensPanel = api;
|
|
3315
3468
|
var hasChrome = typeof chrome !== "undefined" && !!chrome && !!chrome.runtime && !!chrome.runtime.id;
|
|
3316
3469
|
var hasDevtools = hasChrome && !!chrome.devtools && !!chrome.devtools.inspectedWindow;
|
|
3317
3470
|
var params = typeof location !== "undefined" ? new URLSearchParams(location.search) : new URLSearchParams();
|
|
3318
3471
|
var pathname = typeof location !== "undefined" ? String(location.pathname) : "";
|
|
3319
|
-
if (params.has("
|
|
3320
|
-
else if (params.has("relay") && typeof EventSource === "function") bootRelay(params.get("relay") || location.origin);
|
|
3472
|
+
if (params.has("relay") && typeof EventSource === "function") bootRelay(params.get("relay") || location.origin);
|
|
3321
3473
|
else if (params.has("channel") && typeof BroadcastChannel === "function") bootBroadcast(params.get("channel") || "rerender-lens");
|
|
3322
3474
|
else if (hasDevtools && /panel\.html/.test(pathname) && !params.has("tabId")) bootExtension();
|
|
3323
3475
|
else if (hasChrome && (/sidepanel\.html/.test(pathname) || params.has("tabId"))) bootStandalone({ tabId: params.has("tabId") ? Number(params.get("tabId")) : null });
|