@treelocator/runtime 0.4.7 → 0.6.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/.eslintignore +1 -0
- package/dist/_generated_styles.d.ts +1 -1
- package/dist/_generated_styles.js +20 -0
- package/dist/_generated_tree_icon.d.ts +1 -1
- package/dist/adapters/HtmlElementTreeNode.d.ts +2 -2
- package/dist/adapters/HtmlElementTreeNode.js +4 -6
- package/dist/adapters/createTreeNode.js +17 -44
- package/dist/adapters/detectFramework.d.ts +8 -0
- package/dist/adapters/detectFramework.js +25 -0
- package/dist/adapters/detectFramework.test.d.ts +1 -0
- package/dist/adapters/detectFramework.test.js +60 -0
- package/dist/adapters/jsx/jsxAdapter.js +54 -89
- package/dist/adapters/jsx/jsxAdapter.test.d.ts +1 -0
- package/dist/adapters/jsx/jsxAdapter.test.js +273 -0
- package/dist/adapters/nextjs/parseNextjsDataAttributes.js +1 -1
- package/dist/adapters/nextjs/parseNextjsDataAttributes.test.d.ts +1 -0
- package/dist/adapters/nextjs/parseNextjsDataAttributes.test.js +158 -0
- package/dist/adapters/react/findFiberByHtmlElement.d.ts +1 -1
- package/dist/adapters/react/findFiberByHtmlElement.js +1 -1
- package/dist/adapters/react/getAllParentsElementsAndRootComponent.js +4 -0
- package/dist/adapters/resolveAdapter.d.ts +1 -1
- package/dist/adapters/resolveAdapter.js +4 -8
- package/dist/adapters/svelte/svelteAdapter.test.d.ts +1 -0
- package/dist/adapters/svelte/svelteAdapter.test.js +280 -0
- package/dist/adapters/vue/vueAdapter.test.d.ts +1 -0
- package/dist/adapters/vue/vueAdapter.test.js +222 -0
- package/dist/browserApi.d.ts +148 -0
- package/dist/browserApi.js +146 -5
- package/dist/browserApi.test.d.ts +1 -0
- package/dist/browserApi.test.js +287 -0
- package/dist/components/RecordingPillButton.d.ts +11 -0
- package/dist/components/RecordingPillButton.js +202 -0
- package/dist/components/RecordingResults.d.ts +2 -0
- package/dist/components/RecordingResults.js +213 -78
- package/dist/components/Runtime.js +161 -554
- package/dist/components/SettingsPanel.d.ts +5 -0
- package/dist/components/SettingsPanel.js +312 -0
- package/dist/consoleCapture.d.ts +9 -0
- package/dist/consoleCapture.js +95 -0
- package/dist/dejitter/recorder.d.ts +7 -1
- package/dist/dejitter/recorder.js +64 -1
- package/dist/functions/cssRuleInspector.d.ts +83 -0
- package/dist/functions/cssRuleInspector.js +608 -0
- package/dist/functions/cssRuleInspector.test.d.ts +1 -0
- package/dist/functions/cssRuleInspector.test.js +439 -0
- package/dist/functions/deduplicateLabels.test.d.ts +1 -0
- package/dist/functions/deduplicateLabels.test.js +178 -0
- package/dist/functions/enrichAncestrySourceMaps.js +0 -1
- package/dist/functions/extractComputedStyles.d.ts +51 -0
- package/dist/functions/extractComputedStyles.js +447 -0
- package/dist/functions/extractComputedStyles.test.d.ts +1 -0
- package/dist/functions/extractComputedStyles.test.js +549 -0
- package/dist/functions/formatAncestryChain.d.ts +8 -0
- package/dist/functions/formatAncestryChain.js +21 -1
- package/dist/functions/formatAncestryChain.test.js +18 -0
- package/dist/functions/getUsableName.test.d.ts +1 -0
- package/dist/functions/getUsableName.test.js +219 -0
- package/dist/functions/isCombinationModifiersPressed.test.d.ts +1 -0
- package/dist/functions/isCombinationModifiersPressed.test.js +192 -0
- package/dist/functions/mergeRects.test.js +210 -1
- package/dist/functions/namedSnapshots.d.ts +52 -0
- package/dist/functions/namedSnapshots.js +161 -0
- package/dist/functions/namedSnapshots.test.d.ts +1 -0
- package/dist/functions/namedSnapshots.test.js +85 -0
- package/dist/functions/normalizeFilePath.test.d.ts +1 -0
- package/dist/functions/normalizeFilePath.test.js +66 -0
- package/dist/functions/parseDataId.test.d.ts +1 -0
- package/dist/functions/parseDataId.test.js +101 -0
- package/dist/hooks/getStorage.d.ts +3 -0
- package/dist/hooks/getStorage.js +17 -0
- package/dist/hooks/useEventListeners.d.ts +15 -0
- package/dist/hooks/useEventListeners.js +56 -0
- package/dist/hooks/useLocatorStorage.d.ts +18 -0
- package/dist/hooks/useLocatorStorage.js +41 -0
- package/dist/hooks/useLocatorStorage.test.d.ts +1 -0
- package/dist/hooks/useLocatorStorage.test.js +124 -0
- package/dist/hooks/useRecordingState.d.ts +43 -0
- package/dist/hooks/useRecordingState.js +387 -0
- package/dist/hooks/useSettings.d.ts +13 -0
- package/dist/hooks/useSettings.js +66 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/initRuntime.d.ts +3 -1
- package/dist/initRuntime.js +4 -1
- package/dist/mcpBridge.d.ts +61 -0
- package/dist/mcpBridge.js +534 -0
- package/dist/mcpBridge.test.d.ts +1 -0
- package/dist/mcpBridge.test.js +248 -0
- package/dist/output.css +20 -0
- package/dist/visualDiff/diff.d.ts +9 -0
- package/dist/visualDiff/diff.js +209 -0
- package/dist/visualDiff/diff.test.d.ts +1 -0
- package/dist/visualDiff/diff.test.js +253 -0
- package/dist/visualDiff/settle.d.ts +3 -0
- package/dist/visualDiff/settle.js +50 -0
- package/dist/visualDiff/settle.test.d.ts +1 -0
- package/dist/visualDiff/settle.test.js +65 -0
- package/dist/visualDiff/snapshot.d.ts +4 -0
- package/dist/visualDiff/snapshot.js +84 -0
- package/dist/visualDiff/snapshot.test.d.ts +1 -0
- package/dist/visualDiff/snapshot.test.js +245 -0
- package/dist/visualDiff/types.d.ts +37 -0
- package/dist/visualDiff/types.js +1 -0
- package/package.json +2 -2
- package/scripts/wrapCSS.js +1 -1
- package/scripts/wrapImage.js +1 -1
- package/src/_generated_styles.ts +21 -1
- package/src/_generated_tree_icon.ts +1 -1
- package/src/adapters/HtmlElementTreeNode.ts +10 -7
- package/src/adapters/createTreeNode.ts +12 -51
- package/src/adapters/detectFramework.test.ts +73 -0
- package/src/adapters/detectFramework.ts +28 -0
- package/src/adapters/jsx/jsxAdapter.test.ts +240 -0
- package/src/adapters/jsx/jsxAdapter.ts +53 -106
- package/src/adapters/nextjs/parseNextjsDataAttributes.test.ts +212 -0
- package/src/adapters/nextjs/parseNextjsDataAttributes.ts +1 -1
- package/src/adapters/react/findDebugSource.ts +5 -6
- package/src/adapters/react/findFiberByHtmlElement.ts +3 -3
- package/src/adapters/react/getAllParentsElementsAndRootComponent.ts +3 -0
- package/src/adapters/react/reactAdapter.ts +1 -2
- package/src/adapters/resolveAdapter.ts +4 -14
- package/src/adapters/svelte/svelteAdapter.test.ts +334 -0
- package/src/adapters/vue/vueAdapter.test.ts +259 -0
- package/src/browserApi.test.ts +329 -0
- package/src/browserApi.ts +351 -4
- package/src/components/RecordingPillButton.tsx +301 -0
- package/src/components/RecordingResults.tsx +114 -13
- package/src/components/Runtime.tsx +176 -621
- package/src/components/SettingsPanel.tsx +339 -0
- package/src/consoleCapture.ts +113 -0
- package/src/dejitter/recorder.ts +67 -3
- package/src/functions/cssRuleInspector.test.ts +517 -0
- package/src/functions/cssRuleInspector.ts +708 -0
- package/src/functions/deduplicateLabels.test.ts +115 -0
- package/src/functions/enrichAncestrySourceMaps.ts +6 -3
- package/src/functions/extractComputedStyles.test.ts +681 -0
- package/src/functions/extractComputedStyles.ts +768 -0
- package/src/functions/formatAncestryChain.test.ts +23 -1
- package/src/functions/formatAncestryChain.ts +22 -1
- package/src/functions/getUsableName.test.ts +242 -0
- package/src/functions/isCombinationModifiersPressed.test.ts +156 -0
- package/src/functions/mergeRects.test.ts +111 -1
- package/src/functions/namedSnapshots.test.ts +106 -0
- package/src/functions/namedSnapshots.ts +232 -0
- package/src/functions/normalizeFilePath.test.ts +80 -0
- package/src/functions/parseDataId.test.ts +125 -0
- package/src/hooks/getStorage.ts +26 -0
- package/src/hooks/useEventListeners.ts +97 -0
- package/src/hooks/useLocatorStorage.test.ts +127 -0
- package/src/hooks/useLocatorStorage.ts +60 -0
- package/src/hooks/useRecordingState.ts +516 -0
- package/src/hooks/useSettings.ts +83 -0
- package/src/index.ts +10 -5
- package/src/initRuntime.ts +5 -0
- package/src/mcpBridge.test.ts +260 -0
- package/src/mcpBridge.ts +677 -0
- package/src/visualDiff/diff.test.ts +167 -0
- package/src/visualDiff/diff.ts +242 -0
- package/src/visualDiff/settle.test.ts +77 -0
- package/src/visualDiff/settle.ts +62 -0
- package/src/visualDiff/snapshot.test.ts +200 -0
- package/src/visualDiff/snapshot.ts +119 -0
- package/src/visualDiff/types.ts +40 -0
- package/tsconfig.json +3 -1
- package/vitest.config.ts +18 -0
- package/jest.config.ts +0 -195
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { template as _$template } from "solid-js/web";
|
|
2
|
+
import { delegateEvents as _$delegateEvents } from "solid-js/web";
|
|
3
|
+
import { style as _$style } from "solid-js/web";
|
|
4
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
5
|
+
import { addEventListener as _$addEventListener } from "solid-js/web";
|
|
6
|
+
import { insert as _$insert } from "solid-js/web";
|
|
7
|
+
import { setAttribute as _$setAttribute } from "solid-js/web";
|
|
8
|
+
import { effect as _$effect } from "solid-js/web";
|
|
9
|
+
import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
|
|
10
|
+
var _tmpl$ = /*#__PURE__*/_$template(`<div role=switch style=border-radius:9px;flex-shrink:0><div style=border-radius:50%>`),
|
|
11
|
+
_tmpl$2 = /*#__PURE__*/_$template(`<div style=align-items:center;flex-shrink:0><input type=number style=border-radius:4px;font-family:inherit;font-size:11px;text-align:right><span style=font-size:11px;min-width:18px>`),
|
|
12
|
+
_tmpl$3 = /*#__PURE__*/_$template(`<div data-treelocator-settings-panel style="z-index:2147483646;max-height:400px;overflow-y:auto;backdrop-filter:blur(12px);border-radius:12px;box-shadow:0 8px 32px rgba(0, 0, 0, 0.4);font-family:system-ui, -apple-system, sans-serif;font-size:12px;pointer-events:auto"><div style="align-items:center;justify-content:space-between;border-bottom:1px solid rgba(255, 255, 255, 0.08)"><div><div style=font-weight:600;font-size:13px>Settings</div><div style=margin-top:2px>Persisted to localStorage</div></div><div style=align-items:center><div style=border-radius:4px;font-size:11px;font-weight:600>Reset</div><div style=border-radius:4px;font-size:16px;line-height:1>×</div></div></div><div><div>Features</div></div><div style="border-top:1px solid rgba(255, 255, 255, 0.08)"><div>Thresholds</div><div style=font-size:11px;margin-top:8px>Defaults: sample <!>Hz, max <!>ms, jump <!>px, lag <!>ms`),
|
|
13
|
+
_tmpl$4 = /*#__PURE__*/_$template(`<div><div><div></div><div>`);
|
|
14
|
+
import { For } from "solid-js";
|
|
15
|
+
import { DEFAULT_SETTINGS, resetSettings, setSetting, settings } from "../hooks/useSettings";
|
|
16
|
+
const TOGGLES = [{
|
|
17
|
+
key: "anomalyTracking",
|
|
18
|
+
label: "Anomaly tracking",
|
|
19
|
+
hint: "Detect jitter, jumps, stutter, stuck frames"
|
|
20
|
+
}, {
|
|
21
|
+
key: "visualDiff",
|
|
22
|
+
label: "Visual diff",
|
|
23
|
+
hint: "Snapshot before/after element tree"
|
|
24
|
+
}, {
|
|
25
|
+
key: "computedStyles",
|
|
26
|
+
label: "Computed styles",
|
|
27
|
+
hint: "Include styles in alt+click clipboard output"
|
|
28
|
+
}, {
|
|
29
|
+
key: "computedStylesIncludeDefaults",
|
|
30
|
+
label: "Include default styles",
|
|
31
|
+
hint: "Show browser-default values like display:block and font-weight:bold"
|
|
32
|
+
}];
|
|
33
|
+
const NUMBERS = [{
|
|
34
|
+
key: "sampleRate",
|
|
35
|
+
label: "Sample rate",
|
|
36
|
+
hint: "Max frames per second to capture",
|
|
37
|
+
unit: "Hz",
|
|
38
|
+
min: 1,
|
|
39
|
+
max: 60,
|
|
40
|
+
step: 1
|
|
41
|
+
}, {
|
|
42
|
+
key: "maxDurationMs",
|
|
43
|
+
label: "Max duration",
|
|
44
|
+
hint: "Auto-stop recording after this long",
|
|
45
|
+
unit: "ms",
|
|
46
|
+
min: 1000,
|
|
47
|
+
max: 120000,
|
|
48
|
+
step: 1000
|
|
49
|
+
}, {
|
|
50
|
+
key: "jumpMinAbsolute",
|
|
51
|
+
label: "Jump threshold",
|
|
52
|
+
hint: "Minimum px delta to flag a jump",
|
|
53
|
+
unit: "px",
|
|
54
|
+
min: 1,
|
|
55
|
+
max: 1000,
|
|
56
|
+
step: 1
|
|
57
|
+
}, {
|
|
58
|
+
key: "lagMinDelay",
|
|
59
|
+
label: "Lag threshold",
|
|
60
|
+
hint: "Minimum rAF delay to flag lag",
|
|
61
|
+
unit: "ms",
|
|
62
|
+
min: 1,
|
|
63
|
+
max: 1000,
|
|
64
|
+
step: 1
|
|
65
|
+
}];
|
|
66
|
+
const rowStyle = {
|
|
67
|
+
display: "flex",
|
|
68
|
+
"align-items": "center",
|
|
69
|
+
"justify-content": "space-between",
|
|
70
|
+
gap: "12px",
|
|
71
|
+
padding: "8px 0",
|
|
72
|
+
"border-bottom": "1px solid rgba(255, 255, 255, 0.05)"
|
|
73
|
+
};
|
|
74
|
+
const labelStyle = {
|
|
75
|
+
"min-width": "0",
|
|
76
|
+
flex: "1 1 auto"
|
|
77
|
+
};
|
|
78
|
+
const labelTitle = {
|
|
79
|
+
"font-weight": "600",
|
|
80
|
+
color: "#fff"
|
|
81
|
+
};
|
|
82
|
+
const labelHint = {
|
|
83
|
+
color: "#9ca3af",
|
|
84
|
+
"font-size": "11px",
|
|
85
|
+
"margin-top": "2px",
|
|
86
|
+
"line-height": "1.3"
|
|
87
|
+
};
|
|
88
|
+
const sectionTitleStyle = {
|
|
89
|
+
"margin-bottom": "4px",
|
|
90
|
+
color: "#9ca3af",
|
|
91
|
+
"font-size": "11px",
|
|
92
|
+
"text-transform": "uppercase",
|
|
93
|
+
"letter-spacing": "0.5px"
|
|
94
|
+
};
|
|
95
|
+
function Toggle(props) {
|
|
96
|
+
return (() => {
|
|
97
|
+
var _el$ = _tmpl$(),
|
|
98
|
+
_el$2 = _el$.firstChild;
|
|
99
|
+
_el$.$$click = () => props.onChange(!props.checked);
|
|
100
|
+
_$setStyleProperty(_el$, "cursor", "pointer");
|
|
101
|
+
_$setStyleProperty(_el$, "width", "32px");
|
|
102
|
+
_$setStyleProperty(_el$, "height", "18px");
|
|
103
|
+
_$setStyleProperty(_el$, "position", "relative");
|
|
104
|
+
_$setStyleProperty(_el$, "transition", "background 0.15s");
|
|
105
|
+
_$setStyleProperty(_el$2, "position", "absolute");
|
|
106
|
+
_$setStyleProperty(_el$2, "top", "2px");
|
|
107
|
+
_$setStyleProperty(_el$2, "width", "14px");
|
|
108
|
+
_$setStyleProperty(_el$2, "height", "14px");
|
|
109
|
+
_$setStyleProperty(_el$2, "background", "#fff");
|
|
110
|
+
_$setStyleProperty(_el$2, "transition", "left 0.15s");
|
|
111
|
+
_$effect(_p$ => {
|
|
112
|
+
var _v$ = props.checked,
|
|
113
|
+
_v$2 = props.checked ? "rgba(59, 130, 246, 0.6)" : "rgba(255, 255, 255, 0.12)",
|
|
114
|
+
_v$3 = props.checked ? "16px" : "2px";
|
|
115
|
+
_v$ !== _p$.e && _$setAttribute(_el$, "aria-checked", _p$.e = _v$);
|
|
116
|
+
_v$2 !== _p$.t && _$setStyleProperty(_el$, "background", _p$.t = _v$2);
|
|
117
|
+
_v$3 !== _p$.a && _$setStyleProperty(_el$2, "left", _p$.a = _v$3);
|
|
118
|
+
return _p$;
|
|
119
|
+
}, {
|
|
120
|
+
e: undefined,
|
|
121
|
+
t: undefined,
|
|
122
|
+
a: undefined
|
|
123
|
+
});
|
|
124
|
+
return _el$;
|
|
125
|
+
})();
|
|
126
|
+
}
|
|
127
|
+
function NumberInput(props) {
|
|
128
|
+
return (() => {
|
|
129
|
+
var _el$3 = _tmpl$2(),
|
|
130
|
+
_el$4 = _el$3.firstChild,
|
|
131
|
+
_el$5 = _el$4.nextSibling;
|
|
132
|
+
_$setStyleProperty(_el$3, "display", "flex");
|
|
133
|
+
_$setStyleProperty(_el$3, "gap", "4px");
|
|
134
|
+
_el$4.$$input = e => {
|
|
135
|
+
const n = Number(e.currentTarget.value);
|
|
136
|
+
if (!Number.isFinite(n)) return;
|
|
137
|
+
const clamped = Math.min(props.max, Math.max(props.min, n));
|
|
138
|
+
props.onChange(clamped);
|
|
139
|
+
};
|
|
140
|
+
_$setStyleProperty(_el$4, "width", "72px");
|
|
141
|
+
_$setStyleProperty(_el$4, "padding", "4px 6px");
|
|
142
|
+
_$setStyleProperty(_el$4, "background", "rgba(255, 255, 255, 0.08)");
|
|
143
|
+
_$setStyleProperty(_el$4, "border", "1px solid rgba(255, 255, 255, 0.1)");
|
|
144
|
+
_$setStyleProperty(_el$4, "color", "#fff");
|
|
145
|
+
_$setStyleProperty(_el$4, "outline", "none");
|
|
146
|
+
_$setStyleProperty(_el$5, "color", "#9ca3af");
|
|
147
|
+
_$insert(_el$5, () => props.unit);
|
|
148
|
+
_$effect(_p$ => {
|
|
149
|
+
var _v$4 = props.min,
|
|
150
|
+
_v$5 = props.max,
|
|
151
|
+
_v$6 = props.step;
|
|
152
|
+
_v$4 !== _p$.e && _$setAttribute(_el$4, "min", _p$.e = _v$4);
|
|
153
|
+
_v$5 !== _p$.t && _$setAttribute(_el$4, "max", _p$.t = _v$5);
|
|
154
|
+
_v$6 !== _p$.a && _$setAttribute(_el$4, "step", _p$.a = _v$6);
|
|
155
|
+
return _p$;
|
|
156
|
+
}, {
|
|
157
|
+
e: undefined,
|
|
158
|
+
t: undefined,
|
|
159
|
+
a: undefined
|
|
160
|
+
});
|
|
161
|
+
_$effect(() => _el$4.value = props.value);
|
|
162
|
+
return _el$3;
|
|
163
|
+
})();
|
|
164
|
+
}
|
|
165
|
+
export function SettingsPanel(props) {
|
|
166
|
+
const current = () => settings();
|
|
167
|
+
return (() => {
|
|
168
|
+
var _el$6 = _tmpl$3(),
|
|
169
|
+
_el$7 = _el$6.firstChild,
|
|
170
|
+
_el$8 = _el$7.firstChild,
|
|
171
|
+
_el$9 = _el$8.firstChild,
|
|
172
|
+
_el$0 = _el$9.nextSibling,
|
|
173
|
+
_el$1 = _el$8.nextSibling,
|
|
174
|
+
_el$10 = _el$1.firstChild,
|
|
175
|
+
_el$11 = _el$10.nextSibling,
|
|
176
|
+
_el$12 = _el$7.nextSibling,
|
|
177
|
+
_el$13 = _el$12.firstChild,
|
|
178
|
+
_el$14 = _el$12.nextSibling,
|
|
179
|
+
_el$15 = _el$14.firstChild,
|
|
180
|
+
_el$16 = _el$15.nextSibling,
|
|
181
|
+
_el$17 = _el$16.firstChild,
|
|
182
|
+
_el$23 = _el$17.nextSibling,
|
|
183
|
+
_el$18 = _el$23.nextSibling,
|
|
184
|
+
_el$24 = _el$18.nextSibling,
|
|
185
|
+
_el$20 = _el$24.nextSibling,
|
|
186
|
+
_el$25 = _el$20.nextSibling,
|
|
187
|
+
_el$21 = _el$25.nextSibling,
|
|
188
|
+
_el$26 = _el$21.nextSibling,
|
|
189
|
+
_el$22 = _el$26.nextSibling;
|
|
190
|
+
_$setStyleProperty(_el$6, "position", "fixed");
|
|
191
|
+
_$setStyleProperty(_el$6, "bottom", "180px");
|
|
192
|
+
_$setStyleProperty(_el$6, "right", "20px");
|
|
193
|
+
_$setStyleProperty(_el$6, "width", "340px");
|
|
194
|
+
_$setStyleProperty(_el$6, "background", "rgba(15, 15, 15, 0.92)");
|
|
195
|
+
_$setStyleProperty(_el$6, "border", "1px solid rgba(255, 255, 255, 0.1)");
|
|
196
|
+
_$setStyleProperty(_el$6, "color", "#e5e5e5");
|
|
197
|
+
_$setStyleProperty(_el$7, "display", "flex");
|
|
198
|
+
_$setStyleProperty(_el$7, "padding", "12px 14px 8px");
|
|
199
|
+
_$setStyleProperty(_el$9, "color", "#fff");
|
|
200
|
+
_$setStyleProperty(_el$0, "color", "#9ca3af");
|
|
201
|
+
_$setStyleProperty(_el$1, "display", "flex");
|
|
202
|
+
_$setStyleProperty(_el$1, "gap", "4px");
|
|
203
|
+
_$addEventListener(_el$10, "click", resetSettings, true);
|
|
204
|
+
_$setStyleProperty(_el$10, "cursor", "pointer");
|
|
205
|
+
_$setStyleProperty(_el$10, "padding", "4px 10px");
|
|
206
|
+
_$setStyleProperty(_el$10, "background", "rgba(255, 255, 255, 0.08)");
|
|
207
|
+
_$setStyleProperty(_el$10, "color", "#9ca3af");
|
|
208
|
+
_$addEventListener(_el$11, "click", props.onDismiss, true);
|
|
209
|
+
_$setStyleProperty(_el$11, "cursor", "pointer");
|
|
210
|
+
_$setStyleProperty(_el$11, "padding", "4px 8px");
|
|
211
|
+
_$setStyleProperty(_el$11, "color", "#9ca3af");
|
|
212
|
+
_$setStyleProperty(_el$12, "padding", "8px 14px");
|
|
213
|
+
_$insert(_el$12, _$createComponent(For, {
|
|
214
|
+
each: TOGGLES,
|
|
215
|
+
children: item => (() => {
|
|
216
|
+
var _el$27 = _tmpl$4(),
|
|
217
|
+
_el$28 = _el$27.firstChild,
|
|
218
|
+
_el$29 = _el$28.firstChild,
|
|
219
|
+
_el$30 = _el$29.nextSibling;
|
|
220
|
+
_$insert(_el$29, () => item.label);
|
|
221
|
+
_$insert(_el$30, () => item.hint);
|
|
222
|
+
_$insert(_el$27, _$createComponent(Toggle, {
|
|
223
|
+
get checked() {
|
|
224
|
+
return current()[item.key];
|
|
225
|
+
},
|
|
226
|
+
onChange: v => setSetting(item.key, v)
|
|
227
|
+
}), null);
|
|
228
|
+
_$effect(_p$ => {
|
|
229
|
+
var _v$9 = rowStyle,
|
|
230
|
+
_v$0 = labelStyle,
|
|
231
|
+
_v$1 = labelTitle,
|
|
232
|
+
_v$10 = labelHint;
|
|
233
|
+
_p$.e = _$style(_el$27, _v$9, _p$.e);
|
|
234
|
+
_p$.t = _$style(_el$28, _v$0, _p$.t);
|
|
235
|
+
_p$.a = _$style(_el$29, _v$1, _p$.a);
|
|
236
|
+
_p$.o = _$style(_el$30, _v$10, _p$.o);
|
|
237
|
+
return _p$;
|
|
238
|
+
}, {
|
|
239
|
+
e: undefined,
|
|
240
|
+
t: undefined,
|
|
241
|
+
a: undefined,
|
|
242
|
+
o: undefined
|
|
243
|
+
});
|
|
244
|
+
return _el$27;
|
|
245
|
+
})()
|
|
246
|
+
}), null);
|
|
247
|
+
_$setStyleProperty(_el$14, "padding", "8px 14px 12px");
|
|
248
|
+
_$insert(_el$14, _$createComponent(For, {
|
|
249
|
+
each: NUMBERS,
|
|
250
|
+
children: item => (() => {
|
|
251
|
+
var _el$31 = _tmpl$4(),
|
|
252
|
+
_el$32 = _el$31.firstChild,
|
|
253
|
+
_el$33 = _el$32.firstChild,
|
|
254
|
+
_el$34 = _el$33.nextSibling;
|
|
255
|
+
_$insert(_el$33, () => item.label);
|
|
256
|
+
_$insert(_el$34, () => item.hint);
|
|
257
|
+
_$insert(_el$31, _$createComponent(NumberInput, {
|
|
258
|
+
get value() {
|
|
259
|
+
return current()[item.key];
|
|
260
|
+
},
|
|
261
|
+
get min() {
|
|
262
|
+
return item.min;
|
|
263
|
+
},
|
|
264
|
+
get max() {
|
|
265
|
+
return item.max;
|
|
266
|
+
},
|
|
267
|
+
get step() {
|
|
268
|
+
return item.step;
|
|
269
|
+
},
|
|
270
|
+
get unit() {
|
|
271
|
+
return item.unit;
|
|
272
|
+
},
|
|
273
|
+
onChange: v => setSetting(item.key, v)
|
|
274
|
+
}), null);
|
|
275
|
+
_$effect(_p$ => {
|
|
276
|
+
var _v$11 = rowStyle,
|
|
277
|
+
_v$12 = labelStyle,
|
|
278
|
+
_v$13 = labelTitle,
|
|
279
|
+
_v$14 = labelHint;
|
|
280
|
+
_p$.e = _$style(_el$31, _v$11, _p$.e);
|
|
281
|
+
_p$.t = _$style(_el$32, _v$12, _p$.t);
|
|
282
|
+
_p$.a = _$style(_el$33, _v$13, _p$.a);
|
|
283
|
+
_p$.o = _$style(_el$34, _v$14, _p$.o);
|
|
284
|
+
return _p$;
|
|
285
|
+
}, {
|
|
286
|
+
e: undefined,
|
|
287
|
+
t: undefined,
|
|
288
|
+
a: undefined,
|
|
289
|
+
o: undefined
|
|
290
|
+
});
|
|
291
|
+
return _el$31;
|
|
292
|
+
})()
|
|
293
|
+
}), _el$16);
|
|
294
|
+
_$setStyleProperty(_el$16, "color", "#6b7280");
|
|
295
|
+
_$insert(_el$16, () => DEFAULT_SETTINGS.sampleRate, _el$23);
|
|
296
|
+
_$insert(_el$16, () => DEFAULT_SETTINGS.maxDurationMs, _el$24);
|
|
297
|
+
_$insert(_el$16, () => DEFAULT_SETTINGS.jumpMinAbsolute, _el$25);
|
|
298
|
+
_$insert(_el$16, () => DEFAULT_SETTINGS.lagMinDelay, _el$26);
|
|
299
|
+
_$effect(_p$ => {
|
|
300
|
+
var _v$7 = sectionTitleStyle,
|
|
301
|
+
_v$8 = sectionTitleStyle;
|
|
302
|
+
_p$.e = _$style(_el$13, _v$7, _p$.e);
|
|
303
|
+
_p$.t = _$style(_el$15, _v$8, _p$.t);
|
|
304
|
+
return _p$;
|
|
305
|
+
}, {
|
|
306
|
+
e: undefined,
|
|
307
|
+
t: undefined
|
|
308
|
+
});
|
|
309
|
+
return _el$6;
|
|
310
|
+
})();
|
|
311
|
+
}
|
|
312
|
+
_$delegateEvents(["click", "input"]);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type ConsoleLevel = "log" | "info" | "warn" | "error" | "debug";
|
|
2
|
+
export interface ConsoleEntry {
|
|
3
|
+
level: ConsoleLevel;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function installConsoleCapture(): void;
|
|
8
|
+
export declare function getConsoleEntries(last?: number): ConsoleEntry[];
|
|
9
|
+
export declare function clearConsoleEntries(): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const MAX_ENTRIES = 500;
|
|
2
|
+
const LEVELS = ["log", "info", "warn", "error", "debug"];
|
|
3
|
+
const CAPTURE_FLAG = "__treelocator_console_captured__";
|
|
4
|
+
const entries = [];
|
|
5
|
+
function formatArg(value, seen) {
|
|
6
|
+
if (value === null) return "null";
|
|
7
|
+
if (value === undefined) return "undefined";
|
|
8
|
+
const t = typeof value;
|
|
9
|
+
if (t === "string") return value;
|
|
10
|
+
if (t === "number" || t === "boolean" || t === "bigint") return String(value);
|
|
11
|
+
if (t === "function") {
|
|
12
|
+
const name = value.name || "anonymous";
|
|
13
|
+
return `[Function: ${name}]`;
|
|
14
|
+
}
|
|
15
|
+
if (value instanceof Error) {
|
|
16
|
+
return `${value.name}: ${value.message}`;
|
|
17
|
+
}
|
|
18
|
+
if (typeof Element !== "undefined" && value instanceof Element) {
|
|
19
|
+
const el = value;
|
|
20
|
+
const id = el.id ? `#${el.id}` : "";
|
|
21
|
+
const cls = el.className && typeof el.className === "string" ? `.${el.className.trim().split(/\s+/).join(".")}` : "";
|
|
22
|
+
return `<${el.tagName.toLowerCase()}${id}${cls}>`;
|
|
23
|
+
}
|
|
24
|
+
if (t === "object") {
|
|
25
|
+
if (seen.has(value)) return "[Circular]";
|
|
26
|
+
seen.add(value);
|
|
27
|
+
try {
|
|
28
|
+
const localSeen = new WeakSet();
|
|
29
|
+
return JSON.stringify(value, (_key, v) => {
|
|
30
|
+
if (v === null || v === undefined) return v;
|
|
31
|
+
if (typeof v === "bigint") return v.toString();
|
|
32
|
+
if (typeof v === "function") return `[Function: ${v.name || "anonymous"}]`;
|
|
33
|
+
if (typeof v === "object") {
|
|
34
|
+
if (localSeen.has(v)) return "[Circular]";
|
|
35
|
+
localSeen.add(v);
|
|
36
|
+
}
|
|
37
|
+
return v;
|
|
38
|
+
});
|
|
39
|
+
} catch {
|
|
40
|
+
try {
|
|
41
|
+
return String(value);
|
|
42
|
+
} catch {
|
|
43
|
+
return "[Unserializable]";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return String(value);
|
|
48
|
+
}
|
|
49
|
+
function formatArgs(args) {
|
|
50
|
+
const seen = new WeakSet();
|
|
51
|
+
return args.map(arg => formatArg(arg, seen)).join(" ");
|
|
52
|
+
}
|
|
53
|
+
function pushEntry(level, args) {
|
|
54
|
+
entries.push({
|
|
55
|
+
level,
|
|
56
|
+
timestamp: Date.now(),
|
|
57
|
+
message: formatArgs(args)
|
|
58
|
+
});
|
|
59
|
+
if (entries.length > MAX_ENTRIES) {
|
|
60
|
+
entries.splice(0, entries.length - MAX_ENTRIES);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function installConsoleCapture() {
|
|
64
|
+
if (typeof console === "undefined") return;
|
|
65
|
+
const target = console;
|
|
66
|
+
if (target[CAPTURE_FLAG]) return;
|
|
67
|
+
target[CAPTURE_FLAG] = true;
|
|
68
|
+
for (const level of LEVELS) {
|
|
69
|
+
const original = target[level];
|
|
70
|
+
if (typeof original !== "function") continue;
|
|
71
|
+
const wrapped = function (...args) {
|
|
72
|
+
try {
|
|
73
|
+
pushEntry(level, args);
|
|
74
|
+
} catch {
|
|
75
|
+
// Swallow — capture must never break logging.
|
|
76
|
+
}
|
|
77
|
+
return original.apply(this, args);
|
|
78
|
+
};
|
|
79
|
+
try {
|
|
80
|
+
target[level] = wrapped;
|
|
81
|
+
} catch {
|
|
82
|
+
// Some environments freeze console — ignore.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function getConsoleEntries(last) {
|
|
87
|
+
if (typeof last !== "number" || !Number.isFinite(last) || last <= 0) {
|
|
88
|
+
return entries.slice();
|
|
89
|
+
}
|
|
90
|
+
const n = Math.min(Math.floor(last), entries.length);
|
|
91
|
+
return entries.slice(entries.length - n);
|
|
92
|
+
}
|
|
93
|
+
export function clearConsoleEntries() {
|
|
94
|
+
entries.length = 0;
|
|
95
|
+
}
|
|
@@ -47,10 +47,15 @@ export interface DejitterConfig {
|
|
|
47
47
|
outlier: {
|
|
48
48
|
ratioThreshold: number;
|
|
49
49
|
};
|
|
50
|
+
lag: {
|
|
51
|
+
minDelay: number;
|
|
52
|
+
highDelay: number;
|
|
53
|
+
medDelay: number;
|
|
54
|
+
};
|
|
50
55
|
};
|
|
51
56
|
}
|
|
52
57
|
export interface DejitterFinding {
|
|
53
|
-
type: 'jitter' | 'flicker' | 'shiver' | 'jump' | 'stutter' | 'stuck' | 'outlier';
|
|
58
|
+
type: 'jitter' | 'flicker' | 'shiver' | 'jump' | 'stutter' | 'stuck' | 'outlier' | 'lag';
|
|
54
59
|
severity: 'high' | 'medium' | 'low' | 'info';
|
|
55
60
|
elem: string;
|
|
56
61
|
elemLabel: {
|
|
@@ -85,6 +90,7 @@ export interface DejitterAPI {
|
|
|
85
90
|
getRaw(): {
|
|
86
91
|
rawFrames: any[];
|
|
87
92
|
mutations: any[];
|
|
93
|
+
interactions: any[];
|
|
88
94
|
};
|
|
89
95
|
toJSON(): string;
|
|
90
96
|
}
|
|
@@ -48,6 +48,11 @@ export function createDejitterRecorder() {
|
|
|
48
48
|
},
|
|
49
49
|
outlier: {
|
|
50
50
|
ratioThreshold: 3
|
|
51
|
+
},
|
|
52
|
+
lag: {
|
|
53
|
+
minDelay: 50,
|
|
54
|
+
highDelay: 200,
|
|
55
|
+
medDelay: 100
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
};
|
|
@@ -59,12 +64,14 @@ export function createDejitterRecorder() {
|
|
|
59
64
|
let rafId = null;
|
|
60
65
|
let stopTimer = null;
|
|
61
66
|
let mutationObserver = null;
|
|
67
|
+
let interactionAbort = null;
|
|
62
68
|
let onStopCallbacks = [];
|
|
63
69
|
let lastSeen = new Map();
|
|
64
70
|
let rawFrames = [];
|
|
65
71
|
let lastChangeTime = 0;
|
|
66
72
|
let hasSeenChange = false;
|
|
67
73
|
let mutations = [];
|
|
74
|
+
let interactions = [];
|
|
68
75
|
let nextElemId = 0;
|
|
69
76
|
function elemId(el) {
|
|
70
77
|
if (!el.__dj_id) {
|
|
@@ -210,6 +217,24 @@ export function createDejitterRecorder() {
|
|
|
210
217
|
characterData: true
|
|
211
218
|
});
|
|
212
219
|
}
|
|
220
|
+
function startInteractionListeners() {
|
|
221
|
+
interactionAbort = new AbortController();
|
|
222
|
+
const opts = {
|
|
223
|
+
capture: true,
|
|
224
|
+
signal: interactionAbort.signal
|
|
225
|
+
};
|
|
226
|
+
const handler = e => {
|
|
227
|
+
if (!recording) return;
|
|
228
|
+
const t = Math.round(performance.now() - startTime);
|
|
229
|
+
interactions.push({
|
|
230
|
+
t,
|
|
231
|
+
type: e.type
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
for (const evt of ['click', 'pointerdown', 'keydown']) {
|
|
235
|
+
document.addEventListener(evt, handler, opts);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
213
238
|
|
|
214
239
|
// --- Downsampling ---
|
|
215
240
|
|
|
@@ -736,6 +761,39 @@ export function createDejitterRecorder() {
|
|
|
736
761
|
}
|
|
737
762
|
return findings;
|
|
738
763
|
}
|
|
764
|
+
function detectLagFindings(elements) {
|
|
765
|
+
const findings = [];
|
|
766
|
+
if (interactions.length === 0 || rawFrames.length === 0) return findings;
|
|
767
|
+
const lt = config.thresholds.lag;
|
|
768
|
+
for (const interaction of interactions) {
|
|
769
|
+
let firstFrame = null;
|
|
770
|
+
for (const frame of rawFrames) {
|
|
771
|
+
if (frame.t > interaction.t) {
|
|
772
|
+
firstFrame = frame;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (!firstFrame) continue;
|
|
777
|
+
const delay = firstFrame.t - interaction.t;
|
|
778
|
+
if (delay < lt.minDelay) continue;
|
|
779
|
+
const severity = delay >= lt.highDelay ? 'high' : delay >= lt.medDelay ? 'medium' : 'low';
|
|
780
|
+
const firstChange = firstFrame.changes[0];
|
|
781
|
+
const label = firstChange && elements[firstChange.id] || {
|
|
782
|
+
tag: '?',
|
|
783
|
+
cls: '',
|
|
784
|
+
text: ''
|
|
785
|
+
};
|
|
786
|
+
findings.push(makeFinding('lag', severity, firstChange?.id || '?', label, interaction.type, `${delay}ms between ${interaction.type} at t=${interaction.t}ms and first visual change at t=${firstFrame.t}ms`, {
|
|
787
|
+
lag: {
|
|
788
|
+
interactionType: interaction.type,
|
|
789
|
+
interactionT: interaction.t,
|
|
790
|
+
firstChangeT: firstFrame.t,
|
|
791
|
+
delay
|
|
792
|
+
}
|
|
793
|
+
}));
|
|
794
|
+
}
|
|
795
|
+
return findings;
|
|
796
|
+
}
|
|
739
797
|
function deduplicateShivers(findings) {
|
|
740
798
|
const shiverFindings = findings.filter(f => f.type === 'shiver');
|
|
741
799
|
const otherFindings = findings.filter(f => f.type !== 'shiver');
|
|
@@ -769,6 +827,7 @@ export function createDejitterRecorder() {
|
|
|
769
827
|
findings = findings.concat(detectJumpFindings(propStats, elements));
|
|
770
828
|
findings = findings.concat(detectStutterFindings(propStats, elements));
|
|
771
829
|
findings = findings.concat(detectStuckFindings(propStats, elements));
|
|
830
|
+
findings = findings.concat(detectLagFindings(elements));
|
|
772
831
|
findings = deduplicateShivers(findings);
|
|
773
832
|
const sevOrder = {
|
|
774
833
|
high: 0,
|
|
@@ -810,6 +869,7 @@ export function createDejitterRecorder() {
|
|
|
810
869
|
start() {
|
|
811
870
|
rawFrames = [];
|
|
812
871
|
mutations = [];
|
|
872
|
+
interactions = [];
|
|
813
873
|
lastSeen = new Map();
|
|
814
874
|
nextElemId = 0;
|
|
815
875
|
recording = true;
|
|
@@ -818,6 +878,7 @@ export function createDejitterRecorder() {
|
|
|
818
878
|
hasSeenChange = false;
|
|
819
879
|
onStopCallbacks = [];
|
|
820
880
|
startMutationObserver();
|
|
881
|
+
startInteractionListeners();
|
|
821
882
|
rafId = requestAnimationFrame(loop);
|
|
822
883
|
if (config.maxDuration > 0) {
|
|
823
884
|
stopTimer = setTimeout(() => api.stop(), config.maxDuration);
|
|
@@ -830,6 +891,7 @@ export function createDejitterRecorder() {
|
|
|
830
891
|
if (rafId) cancelAnimationFrame(rafId);
|
|
831
892
|
if (stopTimer) clearTimeout(stopTimer);
|
|
832
893
|
mutationObserver?.disconnect();
|
|
894
|
+
interactionAbort?.abort();
|
|
833
895
|
const msg = `Stopped. ${rawFrames.length} raw frames, ${mutations.length} mutation events.`;
|
|
834
896
|
for (const cb of onStopCallbacks) {
|
|
835
897
|
try {
|
|
@@ -892,7 +954,8 @@ export function createDejitterRecorder() {
|
|
|
892
954
|
getRaw() {
|
|
893
955
|
return {
|
|
894
956
|
rawFrames,
|
|
895
|
-
mutations
|
|
957
|
+
mutations,
|
|
958
|
+
interactions
|
|
896
959
|
};
|
|
897
960
|
},
|
|
898
961
|
toJSON() {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS Rule Inspector
|
|
3
|
+
*
|
|
4
|
+
* Walks all loaded stylesheets, finds every rule that matches an element,
|
|
5
|
+
* calculates specificity, and determines which rule "wins" for each property.
|
|
6
|
+
* Designed to produce AI-friendly output for debugging CSS issues.
|
|
7
|
+
*/
|
|
8
|
+
export type SpecificityTuple = [number, number, number];
|
|
9
|
+
export interface CSSRuleInfo {
|
|
10
|
+
selector: string;
|
|
11
|
+
property: string;
|
|
12
|
+
value: string;
|
|
13
|
+
specificity: SpecificityTuple;
|
|
14
|
+
important: boolean;
|
|
15
|
+
source: string;
|
|
16
|
+
/** Monotonic index in source order — higher = later in cascade. */
|
|
17
|
+
sourceIndex: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CSSPropertyResult {
|
|
20
|
+
property: string;
|
|
21
|
+
value: string;
|
|
22
|
+
rules: {
|
|
23
|
+
selector: string;
|
|
24
|
+
value: string;
|
|
25
|
+
specificity: SpecificityTuple;
|
|
26
|
+
important: boolean;
|
|
27
|
+
source: string;
|
|
28
|
+
winning: boolean;
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
export interface CSSInspectionResult {
|
|
32
|
+
element: string;
|
|
33
|
+
properties: CSSPropertyResult[];
|
|
34
|
+
unreachableSheets: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Calculate CSS specificity for a selector string.
|
|
38
|
+
*
|
|
39
|
+
* Returns [ids, classes, elements] where:
|
|
40
|
+
* - ids: count of #id selectors
|
|
41
|
+
* - classes: count of .class, [attr], :pseudo-class (except :not, :is, :where, :has)
|
|
42
|
+
* - elements: count of element, ::pseudo-element
|
|
43
|
+
*
|
|
44
|
+
* This is a lightweight parser that handles the vast majority of real-world
|
|
45
|
+
* selectors without pulling in a full CSS parser.
|
|
46
|
+
*/
|
|
47
|
+
export declare function calculateSpecificity(selector: string): SpecificityTuple;
|
|
48
|
+
/**
|
|
49
|
+
* Compare two specificity tuples.
|
|
50
|
+
* Returns positive if a > b, negative if a < b, 0 if equal.
|
|
51
|
+
*/
|
|
52
|
+
export declare function compareSpecificity(a: SpecificityTuple, b: SpecificityTuple): number;
|
|
53
|
+
/**
|
|
54
|
+
* Format specificity tuple as a human-readable string.
|
|
55
|
+
*/
|
|
56
|
+
export declare function formatSpecificity(s: SpecificityTuple): string;
|
|
57
|
+
/**
|
|
58
|
+
* Describe an element for display: tagName.class1.class2#id
|
|
59
|
+
*/
|
|
60
|
+
export declare function describeElement(element: Element): string;
|
|
61
|
+
/**
|
|
62
|
+
* Inspect an element and return all CSS rules grouped by property,
|
|
63
|
+
* showing which rule wins for each property.
|
|
64
|
+
*/
|
|
65
|
+
export declare function inspectCSSRules(element: Element): CSSInspectionResult;
|
|
66
|
+
/**
|
|
67
|
+
* Format CSS inspection result as a human-readable string for AI consumption.
|
|
68
|
+
*
|
|
69
|
+
* Example output:
|
|
70
|
+
*
|
|
71
|
+
* CSS Rules for button.primary#submit
|
|
72
|
+
* ════════════════════════════════════
|
|
73
|
+
*
|
|
74
|
+
* color: #333
|
|
75
|
+
* ✓ .button.primary (0,2,0) — components.css
|
|
76
|
+
* ✗ .button (0,1,0) — base.css
|
|
77
|
+
* ✗ button (0,0,1) — reset.css
|
|
78
|
+
*
|
|
79
|
+
* font-size: 14px
|
|
80
|
+
* ✓ element.style (inline) — inline
|
|
81
|
+
* ✗ .button (0,1,0) — base.css
|
|
82
|
+
*/
|
|
83
|
+
export declare function formatCSSInspection(result: CSSInspectionResult): string;
|