@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.
Files changed (166) hide show
  1. package/.eslintignore +1 -0
  2. package/dist/_generated_styles.d.ts +1 -1
  3. package/dist/_generated_styles.js +20 -0
  4. package/dist/_generated_tree_icon.d.ts +1 -1
  5. package/dist/adapters/HtmlElementTreeNode.d.ts +2 -2
  6. package/dist/adapters/HtmlElementTreeNode.js +4 -6
  7. package/dist/adapters/createTreeNode.js +17 -44
  8. package/dist/adapters/detectFramework.d.ts +8 -0
  9. package/dist/adapters/detectFramework.js +25 -0
  10. package/dist/adapters/detectFramework.test.d.ts +1 -0
  11. package/dist/adapters/detectFramework.test.js +60 -0
  12. package/dist/adapters/jsx/jsxAdapter.js +54 -89
  13. package/dist/adapters/jsx/jsxAdapter.test.d.ts +1 -0
  14. package/dist/adapters/jsx/jsxAdapter.test.js +273 -0
  15. package/dist/adapters/nextjs/parseNextjsDataAttributes.js +1 -1
  16. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.d.ts +1 -0
  17. package/dist/adapters/nextjs/parseNextjsDataAttributes.test.js +158 -0
  18. package/dist/adapters/react/findFiberByHtmlElement.d.ts +1 -1
  19. package/dist/adapters/react/findFiberByHtmlElement.js +1 -1
  20. package/dist/adapters/react/getAllParentsElementsAndRootComponent.js +4 -0
  21. package/dist/adapters/resolveAdapter.d.ts +1 -1
  22. package/dist/adapters/resolveAdapter.js +4 -8
  23. package/dist/adapters/svelte/svelteAdapter.test.d.ts +1 -0
  24. package/dist/adapters/svelte/svelteAdapter.test.js +280 -0
  25. package/dist/adapters/vue/vueAdapter.test.d.ts +1 -0
  26. package/dist/adapters/vue/vueAdapter.test.js +222 -0
  27. package/dist/browserApi.d.ts +148 -0
  28. package/dist/browserApi.js +146 -5
  29. package/dist/browserApi.test.d.ts +1 -0
  30. package/dist/browserApi.test.js +287 -0
  31. package/dist/components/RecordingPillButton.d.ts +11 -0
  32. package/dist/components/RecordingPillButton.js +202 -0
  33. package/dist/components/RecordingResults.d.ts +2 -0
  34. package/dist/components/RecordingResults.js +213 -78
  35. package/dist/components/Runtime.js +161 -554
  36. package/dist/components/SettingsPanel.d.ts +5 -0
  37. package/dist/components/SettingsPanel.js +312 -0
  38. package/dist/consoleCapture.d.ts +9 -0
  39. package/dist/consoleCapture.js +95 -0
  40. package/dist/dejitter/recorder.d.ts +7 -1
  41. package/dist/dejitter/recorder.js +64 -1
  42. package/dist/functions/cssRuleInspector.d.ts +83 -0
  43. package/dist/functions/cssRuleInspector.js +608 -0
  44. package/dist/functions/cssRuleInspector.test.d.ts +1 -0
  45. package/dist/functions/cssRuleInspector.test.js +439 -0
  46. package/dist/functions/deduplicateLabels.test.d.ts +1 -0
  47. package/dist/functions/deduplicateLabels.test.js +178 -0
  48. package/dist/functions/enrichAncestrySourceMaps.js +0 -1
  49. package/dist/functions/extractComputedStyles.d.ts +51 -0
  50. package/dist/functions/extractComputedStyles.js +447 -0
  51. package/dist/functions/extractComputedStyles.test.d.ts +1 -0
  52. package/dist/functions/extractComputedStyles.test.js +549 -0
  53. package/dist/functions/formatAncestryChain.d.ts +8 -0
  54. package/dist/functions/formatAncestryChain.js +21 -1
  55. package/dist/functions/formatAncestryChain.test.js +18 -0
  56. package/dist/functions/getUsableName.test.d.ts +1 -0
  57. package/dist/functions/getUsableName.test.js +219 -0
  58. package/dist/functions/isCombinationModifiersPressed.test.d.ts +1 -0
  59. package/dist/functions/isCombinationModifiersPressed.test.js +192 -0
  60. package/dist/functions/mergeRects.test.js +210 -1
  61. package/dist/functions/namedSnapshots.d.ts +52 -0
  62. package/dist/functions/namedSnapshots.js +161 -0
  63. package/dist/functions/namedSnapshots.test.d.ts +1 -0
  64. package/dist/functions/namedSnapshots.test.js +85 -0
  65. package/dist/functions/normalizeFilePath.test.d.ts +1 -0
  66. package/dist/functions/normalizeFilePath.test.js +66 -0
  67. package/dist/functions/parseDataId.test.d.ts +1 -0
  68. package/dist/functions/parseDataId.test.js +101 -0
  69. package/dist/hooks/getStorage.d.ts +3 -0
  70. package/dist/hooks/getStorage.js +17 -0
  71. package/dist/hooks/useEventListeners.d.ts +15 -0
  72. package/dist/hooks/useEventListeners.js +56 -0
  73. package/dist/hooks/useLocatorStorage.d.ts +18 -0
  74. package/dist/hooks/useLocatorStorage.js +41 -0
  75. package/dist/hooks/useLocatorStorage.test.d.ts +1 -0
  76. package/dist/hooks/useLocatorStorage.test.js +124 -0
  77. package/dist/hooks/useRecordingState.d.ts +43 -0
  78. package/dist/hooks/useRecordingState.js +387 -0
  79. package/dist/hooks/useSettings.d.ts +13 -0
  80. package/dist/hooks/useSettings.js +66 -0
  81. package/dist/index.d.ts +5 -2
  82. package/dist/index.js +4 -2
  83. package/dist/initRuntime.d.ts +3 -1
  84. package/dist/initRuntime.js +4 -1
  85. package/dist/mcpBridge.d.ts +61 -0
  86. package/dist/mcpBridge.js +534 -0
  87. package/dist/mcpBridge.test.d.ts +1 -0
  88. package/dist/mcpBridge.test.js +248 -0
  89. package/dist/output.css +20 -0
  90. package/dist/visualDiff/diff.d.ts +9 -0
  91. package/dist/visualDiff/diff.js +209 -0
  92. package/dist/visualDiff/diff.test.d.ts +1 -0
  93. package/dist/visualDiff/diff.test.js +253 -0
  94. package/dist/visualDiff/settle.d.ts +3 -0
  95. package/dist/visualDiff/settle.js +50 -0
  96. package/dist/visualDiff/settle.test.d.ts +1 -0
  97. package/dist/visualDiff/settle.test.js +65 -0
  98. package/dist/visualDiff/snapshot.d.ts +4 -0
  99. package/dist/visualDiff/snapshot.js +84 -0
  100. package/dist/visualDiff/snapshot.test.d.ts +1 -0
  101. package/dist/visualDiff/snapshot.test.js +245 -0
  102. package/dist/visualDiff/types.d.ts +37 -0
  103. package/dist/visualDiff/types.js +1 -0
  104. package/package.json +2 -2
  105. package/scripts/wrapCSS.js +1 -1
  106. package/scripts/wrapImage.js +1 -1
  107. package/src/_generated_styles.ts +21 -1
  108. package/src/_generated_tree_icon.ts +1 -1
  109. package/src/adapters/HtmlElementTreeNode.ts +10 -7
  110. package/src/adapters/createTreeNode.ts +12 -51
  111. package/src/adapters/detectFramework.test.ts +73 -0
  112. package/src/adapters/detectFramework.ts +28 -0
  113. package/src/adapters/jsx/jsxAdapter.test.ts +240 -0
  114. package/src/adapters/jsx/jsxAdapter.ts +53 -106
  115. package/src/adapters/nextjs/parseNextjsDataAttributes.test.ts +212 -0
  116. package/src/adapters/nextjs/parseNextjsDataAttributes.ts +1 -1
  117. package/src/adapters/react/findDebugSource.ts +5 -6
  118. package/src/adapters/react/findFiberByHtmlElement.ts +3 -3
  119. package/src/adapters/react/getAllParentsElementsAndRootComponent.ts +3 -0
  120. package/src/adapters/react/reactAdapter.ts +1 -2
  121. package/src/adapters/resolveAdapter.ts +4 -14
  122. package/src/adapters/svelte/svelteAdapter.test.ts +334 -0
  123. package/src/adapters/vue/vueAdapter.test.ts +259 -0
  124. package/src/browserApi.test.ts +329 -0
  125. package/src/browserApi.ts +351 -4
  126. package/src/components/RecordingPillButton.tsx +301 -0
  127. package/src/components/RecordingResults.tsx +114 -13
  128. package/src/components/Runtime.tsx +176 -621
  129. package/src/components/SettingsPanel.tsx +339 -0
  130. package/src/consoleCapture.ts +113 -0
  131. package/src/dejitter/recorder.ts +67 -3
  132. package/src/functions/cssRuleInspector.test.ts +517 -0
  133. package/src/functions/cssRuleInspector.ts +708 -0
  134. package/src/functions/deduplicateLabels.test.ts +115 -0
  135. package/src/functions/enrichAncestrySourceMaps.ts +6 -3
  136. package/src/functions/extractComputedStyles.test.ts +681 -0
  137. package/src/functions/extractComputedStyles.ts +768 -0
  138. package/src/functions/formatAncestryChain.test.ts +23 -1
  139. package/src/functions/formatAncestryChain.ts +22 -1
  140. package/src/functions/getUsableName.test.ts +242 -0
  141. package/src/functions/isCombinationModifiersPressed.test.ts +156 -0
  142. package/src/functions/mergeRects.test.ts +111 -1
  143. package/src/functions/namedSnapshots.test.ts +106 -0
  144. package/src/functions/namedSnapshots.ts +232 -0
  145. package/src/functions/normalizeFilePath.test.ts +80 -0
  146. package/src/functions/parseDataId.test.ts +125 -0
  147. package/src/hooks/getStorage.ts +26 -0
  148. package/src/hooks/useEventListeners.ts +97 -0
  149. package/src/hooks/useLocatorStorage.test.ts +127 -0
  150. package/src/hooks/useLocatorStorage.ts +60 -0
  151. package/src/hooks/useRecordingState.ts +516 -0
  152. package/src/hooks/useSettings.ts +83 -0
  153. package/src/index.ts +10 -5
  154. package/src/initRuntime.ts +5 -0
  155. package/src/mcpBridge.test.ts +260 -0
  156. package/src/mcpBridge.ts +677 -0
  157. package/src/visualDiff/diff.test.ts +167 -0
  158. package/src/visualDiff/diff.ts +242 -0
  159. package/src/visualDiff/settle.test.ts +77 -0
  160. package/src/visualDiff/settle.ts +62 -0
  161. package/src/visualDiff/snapshot.test.ts +200 -0
  162. package/src/visualDiff/snapshot.ts +119 -0
  163. package/src/visualDiff/types.ts +40 -0
  164. package/tsconfig.json +3 -1
  165. package/vitest.config.ts +18 -0
  166. package/jest.config.ts +0 -195
@@ -0,0 +1,5 @@
1
+ type SettingsPanelProps = {
2
+ onDismiss: () => void;
3
+ };
4
+ export declare function SettingsPanel(props: SettingsPanelProps): import("solid-js").JSX.Element;
5
+ export {};
@@ -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>&times;</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;