@treelocator/runtime 0.4.0 → 0.4.5

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 (67) hide show
  1. package/dist/_generated_styles.js +115 -15
  2. package/dist/adapters/getElementInfo.js +3 -17
  3. package/dist/adapters/getParentsPath.js +3 -33
  4. package/dist/adapters/getTree.js +3 -33
  5. package/dist/adapters/jsx/getJSXComponentBoundingBox.js +0 -1
  6. package/dist/adapters/jsx/jsxAdapter.js +0 -3
  7. package/dist/adapters/jsx/runtimeStore.js +0 -12
  8. package/dist/adapters/react/reactAdapter.js +0 -8
  9. package/dist/adapters/resolveAdapter.d.ts +8 -0
  10. package/dist/adapters/resolveAdapter.js +28 -0
  11. package/dist/adapters/vue/vueAdapter.js +0 -14
  12. package/dist/browserApi.d.ts +1 -1
  13. package/dist/browserApi.js +8 -10
  14. package/dist/components/MaybeOutline.d.ts +1 -0
  15. package/dist/components/MaybeOutline.js +38 -29
  16. package/dist/components/Outline.d.ts +1 -0
  17. package/dist/components/Outline.js +20 -16
  18. package/dist/components/Runtime.js +30 -18
  19. package/dist/dejitter/recorder.js +8 -13
  20. package/dist/functions/formatAncestryChain.d.ts +6 -0
  21. package/dist/functions/formatAncestryChain.js +11 -0
  22. package/dist/functions/formatAncestryChain.test.js +75 -1
  23. package/dist/functions/getUsableName.js +0 -21
  24. package/dist/output.css +10 -40
  25. package/dist/types/types.d.ts +1 -32
  26. package/package.json +1 -1
  27. package/src/_generated_styles.ts +115 -15
  28. package/src/adapters/getElementInfo.tsx +3 -23
  29. package/src/adapters/getParentsPath.tsx +3 -42
  30. package/src/adapters/getTree.tsx +3 -42
  31. package/src/adapters/jsx/getJSXComponentBoundingBox.ts +0 -1
  32. package/src/adapters/jsx/jsxAdapter.ts +0 -2
  33. package/src/adapters/jsx/runtimeStore.ts +0 -11
  34. package/src/adapters/react/reactAdapter.ts +0 -7
  35. package/src/adapters/resolveAdapter.ts +38 -0
  36. package/src/adapters/vue/vueAdapter.ts +0 -14
  37. package/src/browserApi.ts +9 -12
  38. package/src/components/MaybeOutline.tsx +4 -2
  39. package/src/components/Outline.tsx +2 -1
  40. package/src/components/Runtime.tsx +27 -18
  41. package/src/dejitter/recorder.ts +51 -56
  42. package/src/functions/formatAncestryChain.test.ts +47 -0
  43. package/src/functions/formatAncestryChain.ts +11 -0
  44. package/src/functions/getUsableName.ts +0 -21
  45. package/src/types/types.ts +1 -32
  46. package/src/adapters/react/fiberToSimple.tsx +0 -72
  47. package/src/adapters/react/gatherFiberRoots.tsx +0 -36
  48. package/src/adapters/react/makeFiberId.tsx +0 -19
  49. package/src/adapters/react/searchDevtoolsRenderersForClosestTarget.ts +0 -15
  50. package/src/components/Button.tsx +0 -14
  51. package/src/components/ComponentOutline.tsx +0 -98
  52. package/src/components/SimpleNodeOutline.tsx +0 -27
  53. package/src/components/Tooltip.tsx +0 -28
  54. package/src/functions/cropPath.test.ts +0 -18
  55. package/src/functions/cropPath.ts +0 -12
  56. package/src/functions/evalTemplate.test.ts +0 -12
  57. package/src/functions/evalTemplate.ts +0 -8
  58. package/src/functions/findNames.ts +0 -20
  59. package/src/functions/getBoundingRect.tsx +0 -11
  60. package/src/functions/getComposedBoundingBox.tsx +0 -25
  61. package/src/functions/getIdsOnPathToRoot.tsx +0 -21
  62. package/src/functions/getMultipleElementsBoundingBox.tsx +0 -25
  63. package/src/functions/getPathToParent.tsx +0 -17
  64. package/src/functions/getUsableFileName.test.tsx +0 -24
  65. package/src/functions/getUsableFileName.tsx +0 -19
  66. package/src/functions/transformPath.test.ts +0 -28
  67. package/src/functions/transformPath.ts +0 -7
@@ -1,11 +1,11 @@
1
1
  import { template as _$template } from "solid-js/web";
2
+ import { className as _$className } from "solid-js/web";
2
3
  import { effect as _$effect } from "solid-js/web";
3
4
  import { insert as _$insert } from "solid-js/web";
4
5
  import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
5
6
  import { createComponent as _$createComponent } from "solid-js/web";
6
7
  import { memo as _$memo } from "solid-js/web";
7
- var _tmpl$ = /*#__PURE__*/_$template(`<div><div class="fixed rounded border border-solid border-amber-500"style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em">`),
8
- _tmpl$2 = /*#__PURE__*/_$template(`<div><div class="fixed rounded border border-solid border-gray-500"style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em">`);
8
+ var _tmpl$ = /*#__PURE__*/_$template(`<div><div style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em">`);
9
9
  import { createMemo } from "solid-js";
10
10
  import { getElementInfo } from "../adapters/getElementInfo";
11
11
  import { Outline } from "./Outline";
@@ -37,6 +37,9 @@ export function MaybeOutline(props) {
37
37
  },
38
38
  get targets() {
39
39
  return props.targets;
40
+ },
41
+ get dashed() {
42
+ return props.dashed;
40
43
  }
41
44
  }) : _$memo(() => !!phoenixInfo())() ? (() => {
42
45
  var _el$ = _tmpl$(),
@@ -48,18 +51,20 @@ export function MaybeOutline(props) {
48
51
  _$setStyleProperty(_el$3, "border", "1px solid rgba(255, 255, 255, 0.15)");
49
52
  _$insert(_el$3, () => phoenixInfo().filter(sc => sc.type === "component").map(sc => sc.name.split(".").pop()).join(" > "));
50
53
  _$effect(_p$ => {
51
- var _v$ = box().x + "px",
52
- _v$2 = box().y + "px",
53
- _v$3 = box().width + "px",
54
- _v$4 = box().height + "px",
55
- _v$5 = box().x + 4 + "px",
56
- _v$6 = box().y + 4 + "px";
57
- _v$ !== _p$.e && _$setStyleProperty(_el$2, "left", _p$.e = _v$);
58
- _v$2 !== _p$.t && _$setStyleProperty(_el$2, "top", _p$.t = _v$2);
59
- _v$3 !== _p$.a && _$setStyleProperty(_el$2, "width", _p$.a = _v$3);
60
- _v$4 !== _p$.o && _$setStyleProperty(_el$2, "height", _p$.o = _v$4);
61
- _v$5 !== _p$.i && _$setStyleProperty(_el$3, "left", _p$.i = _v$5);
62
- _v$6 !== _p$.n && _$setStyleProperty(_el$3, "top", _p$.n = _v$6);
54
+ var _v$ = `fixed rounded border ${props.dashed ? "border-dashed" : "border-solid"} border-amber-500`,
55
+ _v$2 = box().x + "px",
56
+ _v$3 = box().y + "px",
57
+ _v$4 = box().width + "px",
58
+ _v$5 = box().height + "px",
59
+ _v$6 = box().x + 4 + "px",
60
+ _v$7 = box().y + 4 + "px";
61
+ _v$ !== _p$.e && _$className(_el$2, _p$.e = _v$);
62
+ _v$2 !== _p$.t && _$setStyleProperty(_el$2, "left", _p$.t = _v$2);
63
+ _v$3 !== _p$.a && _$setStyleProperty(_el$2, "top", _p$.a = _v$3);
64
+ _v$4 !== _p$.o && _$setStyleProperty(_el$2, "width", _p$.o = _v$4);
65
+ _v$5 !== _p$.i && _$setStyleProperty(_el$2, "height", _p$.i = _v$5);
66
+ _v$6 !== _p$.n && _$setStyleProperty(_el$3, "left", _p$.n = _v$6);
67
+ _v$7 !== _p$.s && _$setStyleProperty(_el$3, "top", _p$.s = _v$7);
63
68
  return _p$;
64
69
  }, {
65
70
  e: undefined,
@@ -67,11 +72,12 @@ export function MaybeOutline(props) {
67
72
  a: undefined,
68
73
  o: undefined,
69
74
  i: undefined,
70
- n: undefined
75
+ n: undefined,
76
+ s: undefined
71
77
  });
72
78
  return _el$;
73
79
  })() : (() => {
74
- var _el$4 = _tmpl$2(),
80
+ var _el$4 = _tmpl$(),
75
81
  _el$5 = _el$4.firstChild,
76
82
  _el$6 = _el$5.nextSibling;
77
83
  _$setStyleProperty(_el$6, "padding", "4px 10px");
@@ -88,18 +94,20 @@ export function MaybeOutline(props) {
88
94
  return () => _c$2() ? `.${props.currentElement.getAttribute('class').split(" ")[0]}` : "";
89
95
  })(), null);
90
96
  _$effect(_p$ => {
91
- var _v$7 = box().x + "px",
92
- _v$8 = box().y + "px",
93
- _v$9 = box().width + "px",
94
- _v$0 = box().height + "px",
95
- _v$1 = box().x + 4 + "px",
96
- _v$10 = box().y + 4 + "px";
97
- _v$7 !== _p$.e && _$setStyleProperty(_el$5, "left", _p$.e = _v$7);
98
- _v$8 !== _p$.t && _$setStyleProperty(_el$5, "top", _p$.t = _v$8);
99
- _v$9 !== _p$.a && _$setStyleProperty(_el$5, "width", _p$.a = _v$9);
100
- _v$0 !== _p$.o && _$setStyleProperty(_el$5, "height", _p$.o = _v$0);
101
- _v$1 !== _p$.i && _$setStyleProperty(_el$6, "left", _p$.i = _v$1);
102
- _v$10 !== _p$.n && _$setStyleProperty(_el$6, "top", _p$.n = _v$10);
97
+ var _v$8 = `fixed rounded border ${props.dashed ? "border-dashed" : "border-solid"} border-gray-500`,
98
+ _v$9 = box().x + "px",
99
+ _v$0 = box().y + "px",
100
+ _v$1 = box().width + "px",
101
+ _v$10 = box().height + "px",
102
+ _v$11 = box().x + 4 + "px",
103
+ _v$12 = box().y + 4 + "px";
104
+ _v$8 !== _p$.e && _$className(_el$5, _p$.e = _v$8);
105
+ _v$9 !== _p$.t && _$setStyleProperty(_el$5, "left", _p$.t = _v$9);
106
+ _v$0 !== _p$.a && _$setStyleProperty(_el$5, "top", _p$.a = _v$0);
107
+ _v$1 !== _p$.o && _$setStyleProperty(_el$5, "width", _p$.o = _v$1);
108
+ _v$10 !== _p$.i && _$setStyleProperty(_el$5, "height", _p$.i = _v$10);
109
+ _v$11 !== _p$.n && _$setStyleProperty(_el$6, "left", _p$.n = _v$11);
110
+ _v$12 !== _p$.s && _$setStyleProperty(_el$6, "top", _p$.s = _v$12);
103
111
  return _p$;
104
112
  }, {
105
113
  e: undefined,
@@ -107,7 +115,8 @@ export function MaybeOutline(props) {
107
115
  a: undefined,
108
116
  o: undefined,
109
117
  i: undefined,
110
- n: undefined
118
+ n: undefined,
119
+ s: undefined
111
120
  });
112
121
  return _el$4;
113
122
  })());
@@ -21,5 +21,6 @@ export type AllBoxes = {
21
21
  export declare function Outline(props: {
22
22
  element: FullElementInfo;
23
23
  targets: Targets;
24
+ dashed?: boolean;
24
25
  }): import("solid-js").JSX.Element;
25
26
  export {};
@@ -1,10 +1,11 @@
1
1
  import { template as _$template } from "solid-js/web";
2
2
  import { createComponent as _$createComponent } from "solid-js/web";
3
+ import { className as _$className } from "solid-js/web";
3
4
  import { effect as _$effect } from "solid-js/web";
4
5
  import { insert as _$insert } from "solid-js/web";
5
6
  import { setStyleProperty as _$setStyleProperty } from "solid-js/web";
6
7
  import { memo as _$memo } from "solid-js/web";
7
- var _tmpl$ = /*#__PURE__*/_$template(`<div><div class="fixed rounded border border-solid border-sky-500"style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em;text-overflow:ellipsis;white-space:nowrap">`);
8
+ var _tmpl$ = /*#__PURE__*/_$template(`<div><div style=z-index:2></div><div class="fixed text-xs font-medium rounded-md"style="z-index:3;box-shadow:0 4px 16px rgba(0, 0, 0, 0.2);font-family:ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco, Consolas, monospace;letter-spacing:0.01em;text-overflow:ellipsis;white-space:nowrap">`);
8
9
  import { RenderBoxes } from "./RenderBoxes";
9
10
  export function Outline(props) {
10
11
  const box = () => props.element.thisElement.box;
@@ -118,20 +119,22 @@ export function Outline(props) {
118
119
  _$setStyleProperty(_el$3, "overflow", "hidden");
119
120
  _$insert(_el$3, () => props.element.thisElement.label);
120
121
  _$effect(_p$ => {
121
- var _v$ = box().x + "px",
122
- _v$2 = box().y + "px",
123
- _v$3 = box().width + "px",
124
- _v$4 = box().height + "px",
125
- _v$5 = box().x + 4 + "px",
126
- _v$6 = box().y + 4 + "px",
127
- _v$7 = box().width - 8 + "px";
128
- _v$ !== _p$.e && _$setStyleProperty(_el$2, "left", _p$.e = _v$);
129
- _v$2 !== _p$.t && _$setStyleProperty(_el$2, "top", _p$.t = _v$2);
130
- _v$3 !== _p$.a && _$setStyleProperty(_el$2, "width", _p$.a = _v$3);
131
- _v$4 !== _p$.o && _$setStyleProperty(_el$2, "height", _p$.o = _v$4);
132
- _v$5 !== _p$.i && _$setStyleProperty(_el$3, "left", _p$.i = _v$5);
133
- _v$6 !== _p$.n && _$setStyleProperty(_el$3, "top", _p$.n = _v$6);
134
- _v$7 !== _p$.s && _$setStyleProperty(_el$3, "max-width", _p$.s = _v$7);
122
+ var _v$ = `fixed rounded border ${props.dashed ? "border-dashed" : "border-solid"} border-sky-500`,
123
+ _v$2 = box().x + "px",
124
+ _v$3 = box().y + "px",
125
+ _v$4 = box().width + "px",
126
+ _v$5 = box().height + "px",
127
+ _v$6 = box().x + 4 + "px",
128
+ _v$7 = box().y + 4 + "px",
129
+ _v$8 = box().width - 8 + "px";
130
+ _v$ !== _p$.e && _$className(_el$2, _p$.e = _v$);
131
+ _v$2 !== _p$.t && _$setStyleProperty(_el$2, "left", _p$.t = _v$2);
132
+ _v$3 !== _p$.a && _$setStyleProperty(_el$2, "top", _p$.a = _v$3);
133
+ _v$4 !== _p$.o && _$setStyleProperty(_el$2, "width", _p$.o = _v$4);
134
+ _v$5 !== _p$.i && _$setStyleProperty(_el$2, "height", _p$.i = _v$5);
135
+ _v$6 !== _p$.n && _$setStyleProperty(_el$3, "left", _p$.n = _v$6);
136
+ _v$7 !== _p$.s && _$setStyleProperty(_el$3, "top", _p$.s = _v$7);
137
+ _v$8 !== _p$.h && _$setStyleProperty(_el$3, "max-width", _p$.h = _v$8);
135
138
  return _p$;
136
139
  }, {
137
140
  e: undefined,
@@ -140,7 +143,8 @@ export function Outline(props) {
140
143
  o: undefined,
141
144
  i: undefined,
142
145
  n: undefined,
143
- s: undefined
146
+ s: undefined,
147
+ h: undefined
144
148
  });
145
149
  return _el$;
146
150
  })();
@@ -16,15 +16,24 @@ import { isCombinationModifiersPressed } from "../functions/isCombinationModifie
16
16
  import { MaybeOutline } from "./MaybeOutline";
17
17
  import { isLocatorsOwnElement } from "../functions/isLocatorsOwnElement";
18
18
  import { Toast } from "./Toast";
19
- import { collectAncestry, formatAncestryChain } from "../functions/formatAncestryChain";
19
+ import { collectAncestry, formatAncestryChain, truncateAtFirstFile } from "../functions/formatAncestryChain";
20
20
  import { enrichAncestryWithSourceMaps } from "../functions/enrichAncestrySourceMaps";
21
21
  import { createTreeNode } from "../adapters/createTreeNode";
22
22
  import treeIconUrl from "../_generated_tree_icon";
23
23
  import { createDejitterRecorder } from "../dejitter/recorder";
24
24
  import { RecordingOutline } from "./RecordingOutline";
25
25
  import { RecordingResults } from "./RecordingResults";
26
+ const DEJITTER_CONFIG = {
27
+ selector: '[data-treelocator-recording]',
28
+ props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
29
+ sampleRate: 15,
30
+ maxDuration: 30000,
31
+ idleTimeout: 0,
32
+ mutations: true
33
+ };
26
34
  function Runtime(props) {
27
35
  const [holdingModKey, setHoldingModKey] = createSignal(false);
36
+ const [holdingShift, setHoldingShift] = createSignal(false);
28
37
  const [currentElement, setCurrentElement] = createSignal(null);
29
38
  const [toastMessage, setToastMessage] = createSignal(null);
30
39
  const [locatorActive, setLocatorActive] = createSignal(false);
@@ -86,13 +95,16 @@ function Runtime(props) {
86
95
  }
87
96
  function keyUpListener(e) {
88
97
  setHoldingModKey(isCombinationModifiersPressed(e));
98
+ setHoldingShift(e.shiftKey);
89
99
  }
90
100
  function keyDownListener(e) {
91
101
  setHoldingModKey(isCombinationModifiersPressed(e, true));
102
+ setHoldingShift(e.shiftKey);
92
103
  }
93
104
  function mouseMoveListener(e) {
94
105
  // Update modifier state from mouse events - more reliable than keydown/keyup
95
106
  setHoldingModKey(e.altKey);
107
+ setHoldingShift(e.shiftKey);
96
108
  }
97
109
  function findElementAtPoint(e) {
98
110
  const elementsAtPoint = document.elementsFromPoint(e.clientX, e.clientY);
@@ -134,14 +146,7 @@ function Runtime(props) {
134
146
  element.setAttribute('data-treelocator-recording', 'true');
135
147
  setRecordedElement(element);
136
148
  dejitterInstance = createDejitterRecorder();
137
- dejitterInstance.configure({
138
- selector: '[data-treelocator-recording]',
139
- props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
140
- sampleRate: 15,
141
- maxDuration: 30000,
142
- idleTimeout: 0,
143
- mutations: true
144
- });
149
+ dejitterInstance.configure(DEJITTER_CONFIG);
145
150
  dejitterInstance.start();
146
151
  startInteractionTracker();
147
152
  setRecordingState('recording');
@@ -251,14 +256,7 @@ function Runtime(props) {
251
256
  element.setAttribute('data-treelocator-recording', 'true');
252
257
  setRecordedElement(element);
253
258
  dejitterInstance = createDejitterRecorder();
254
- dejitterInstance.configure({
255
- selector: '[data-treelocator-recording]',
256
- props: ['opacity', 'transform', 'boundingRect', 'width', 'height'],
257
- sampleRate: 15,
258
- maxDuration: 30000,
259
- idleTimeout: 0,
260
- mutations: true
261
- });
259
+ dejitterInstance.configure(DEJITTER_CONFIG);
262
260
  dejitterInstance.start();
263
261
  setRecordingState('recording');
264
262
  setReplaying(true);
@@ -349,6 +347,9 @@ function Runtime(props) {
349
347
  setRecordedElement(null);
350
348
  setViewingPrevious(false);
351
349
  setRecordingState('idle');
350
+ try {
351
+ localStorage.removeItem(STORAGE_KEY);
352
+ } catch {}
352
353
  }
353
354
  function hasPreviousRecording() {
354
355
  return loadFromStorage().previous !== null;
@@ -408,6 +409,7 @@ function Runtime(props) {
408
409
  }
409
410
  function mouseOverListener(e) {
410
411
  setHoldingModKey(e.altKey);
412
+ setHoldingShift(e.shiftKey);
411
413
 
412
414
  // Don't update hovered element while recording -- highlight is sticky
413
415
  if (recordingState() === 'recording') return;
@@ -456,7 +458,12 @@ function Runtime(props) {
456
458
  // Copy ancestry to clipboard on alt+click
457
459
  const treeNode = createTreeNode(element, props.adapterId);
458
460
  if (treeNode) {
459
- const ancestry = collectAncestry(treeNode);
461
+ let ancestry = collectAncestry(treeNode);
462
+
463
+ // Alt+Shift: keep from bottom up to the first element with a file, discard above
464
+ if (e.shiftKey) {
465
+ ancestry = truncateAtFirstFile(ancestry);
466
+ }
460
467
 
461
468
  // Write immediately with component names (preserves user gesture for clipboard API)
462
469
  const formatted = formatAncestryChain(ancestry);
@@ -513,6 +520,8 @@ function Runtime(props) {
513
520
  root.addEventListener("scroll", scrollListener);
514
521
  }
515
522
  onCleanup(() => {
523
+ stopReplay();
524
+ stopInteractionTracker();
516
525
  for (const root of roots) {
517
526
  root.removeEventListener("keyup", keyUpListener);
518
527
  root.removeEventListener("keydown", keyDownListener);
@@ -540,6 +549,9 @@ function Runtime(props) {
540
549
  },
541
550
  get targets() {
542
551
  return props.targets;
552
+ },
553
+ get dashed() {
554
+ return holdingShift();
543
555
  }
544
556
  }) : null), _$memo(() => _$memo(() => !!(recordingState() === 'recording' && recordedElement()))() ? _$createComponent(RecordingOutline, {
545
557
  get element() {
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  /**
3
2
  * Dejitter — Animation frame recorder & jank detector (vendored)
4
3
  *
@@ -493,11 +492,10 @@ export function createDejitterRecorder() {
493
492
  }
494
493
  return findings;
495
494
  }
496
- function detectShiverFindings(propStats, elements, existingFindings) {
495
+ function detectShiverFindings(propStats, elements) {
497
496
  const findings = [];
498
497
  for (const p of propStats.props) {
499
498
  if (p.raw < 10) continue;
500
- if (existingFindings.some(f => f.elem === p.elem && f.prop === p.prop)) continue;
501
499
  const timeline = getTimeline(p.elem, p.prop);
502
500
  if (timeline.length < 10) continue;
503
501
  const numeric = [];
@@ -539,11 +537,10 @@ export function createDejitterRecorder() {
539
537
  }
540
538
  return findings;
541
539
  }
542
- function detectJumpFindings(propStats, elements, existingFindings) {
540
+ function detectJumpFindings(propStats, elements) {
543
541
  const findings = [];
544
542
  for (const p of propStats.props) {
545
543
  if (p.raw < 3) continue;
546
- if (existingFindings.some(f => f.elem === p.elem && f.prop === p.prop)) continue;
547
544
  const timeline = getTimeline(p.elem, p.prop);
548
545
  if (timeline.length < 3) continue;
549
546
  const deltas = [];
@@ -581,12 +578,11 @@ export function createDejitterRecorder() {
581
578
  }
582
579
  return findings;
583
580
  }
584
- function detectStutterFindings(propStats, elements, existingFindings) {
581
+ function detectStutterFindings(propStats, elements) {
585
582
  const findings = [];
586
583
  const st = config.thresholds.stutter;
587
584
  for (const p of propStats.props) {
588
585
  if (p.raw < 6) continue;
589
- if (existingFindings.some(f => f.elem === p.elem && f.prop === p.prop)) continue;
590
586
  const timeline = getTimeline(p.elem, p.prop);
591
587
  if (timeline.length < 6) continue;
592
588
  const numeric = [];
@@ -672,12 +668,11 @@ export function createDejitterRecorder() {
672
668
  }
673
669
  return findings;
674
670
  }
675
- function detectStuckFindings(propStats, elements, existingFindings) {
671
+ function detectStuckFindings(propStats, elements) {
676
672
  const findings = [];
677
673
  const sk = config.thresholds.stuck;
678
674
  for (const p of propStats.props) {
679
675
  if (p.raw < 6) continue;
680
- if (existingFindings.some(f => f.elem === p.elem && f.prop === p.prop)) continue;
681
676
  const timeline = getTimeline(p.elem, p.prop);
682
677
  if (timeline.length < 6) continue;
683
678
  const numeric = [];
@@ -770,10 +765,10 @@ export function createDejitterRecorder() {
770
765
  const propStats = buildPropStats();
771
766
  const elements = buildElementMap();
772
767
  let findings = detectOutlierFindings(propStats, elements);
773
- findings = findings.concat(detectShiverFindings(propStats, elements, findings));
774
- findings = findings.concat(detectJumpFindings(propStats, elements, findings));
775
- findings = findings.concat(detectStutterFindings(propStats, elements, findings));
776
- findings = findings.concat(detectStuckFindings(propStats, elements, findings));
768
+ findings = findings.concat(detectShiverFindings(propStats, elements));
769
+ findings = findings.concat(detectJumpFindings(propStats, elements));
770
+ findings = findings.concat(detectStutterFindings(propStats, elements));
771
+ findings = findings.concat(detectStuckFindings(propStats, elements));
777
772
  findings = deduplicateShivers(findings);
778
773
  const sevOrder = {
779
774
  high: 0,
@@ -18,4 +18,10 @@ export interface AncestryItem {
18
18
  serverComponents?: ServerComponentInfo[];
19
19
  }
20
20
  export declare function collectAncestry(node: TreeNode): AncestryItem[];
21
+ /**
22
+ * Truncate ancestry from the bottom (clicked element) up to and including
23
+ * the first item that has a filePath. Everything above that is discarded.
24
+ * The ancestry array is bottom-up: index 0 = clicked element, last = root.
25
+ */
26
+ export declare function truncateAtFirstFile(items: AncestryItem[]): AncestryItem[];
21
27
  export declare function formatAncestryChain(items: AncestryItem[]): string;
@@ -121,6 +121,17 @@ function getInnermostNamedComponent(item) {
121
121
  }
122
122
  return undefined;
123
123
  }
124
+
125
+ /**
126
+ * Truncate ancestry from the bottom (clicked element) up to and including
127
+ * the first item that has a filePath. Everything above that is discarded.
128
+ * The ancestry array is bottom-up: index 0 = clicked element, last = root.
129
+ */
130
+ export function truncateAtFirstFile(items) {
131
+ const firstWithFile = items.findIndex(item => item.filePath);
132
+ if (firstWithFile === -1) return items;
133
+ return items.slice(0, firstWithFile + 1);
134
+ }
124
135
  export function formatAncestryChain(items) {
125
136
  if (items.length === 0) {
126
137
  return "";
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { formatAncestryChain } from "./formatAncestryChain";
2
+ import { formatAncestryChain, truncateAtFirstFile } from "./formatAncestryChain";
3
3
  describe("formatAncestryChain", () => {
4
4
  it("uses component name only at component boundaries", () => {
5
5
  const items = [{
@@ -239,4 +239,78 @@ describe("formatAncestryChain", () => {
239
239
  └─ GlassPanel in Sidebar at src/Sidebar.tsx:20`);
240
240
  });
241
241
  });
242
+ describe("truncateAtFirstFile", () => {
243
+ it("keeps from clicked element up to first ancestor with filePath", () => {
244
+ // Bottom-up: clicked element first, root last
245
+ const items = [{
246
+ elementName: "span",
247
+ componentName: "Button"
248
+ }, {
249
+ elementName: "div",
250
+ componentName: "Card"
251
+ }, {
252
+ elementName: "div",
253
+ componentName: "Layout",
254
+ filePath: "src/Layout.tsx",
255
+ line: 10
256
+ }, {
257
+ elementName: "div",
258
+ componentName: "App",
259
+ filePath: "src/App.tsx",
260
+ line: 1
261
+ }];
262
+ const result = truncateAtFirstFile(items);
263
+ expect(result).toEqual([{
264
+ elementName: "span",
265
+ componentName: "Button"
266
+ }, {
267
+ elementName: "div",
268
+ componentName: "Card"
269
+ }, {
270
+ elementName: "div",
271
+ componentName: "Layout",
272
+ filePath: "src/Layout.tsx",
273
+ line: 10
274
+ }]);
275
+ });
276
+ it("returns just the clicked element when it already has a filePath", () => {
277
+ const items = [{
278
+ elementName: "button",
279
+ componentName: "Button",
280
+ filePath: "src/Button.tsx",
281
+ line: 5
282
+ }, {
283
+ elementName: "div",
284
+ componentName: "Layout",
285
+ filePath: "src/Layout.tsx",
286
+ line: 10
287
+ }, {
288
+ elementName: "div",
289
+ componentName: "App",
290
+ filePath: "src/App.tsx",
291
+ line: 1
292
+ }];
293
+ const result = truncateAtFirstFile(items);
294
+ expect(result).toEqual([{
295
+ elementName: "button",
296
+ componentName: "Button",
297
+ filePath: "src/Button.tsx",
298
+ line: 5
299
+ }]);
300
+ });
301
+ it("returns all items when none have a filePath", () => {
302
+ const items = [{
303
+ elementName: "span",
304
+ componentName: "A"
305
+ }, {
306
+ elementName: "div",
307
+ componentName: "B"
308
+ }];
309
+ const result = truncateAtFirstFile(items);
310
+ expect(result).toEqual(items);
311
+ });
312
+ it("returns empty array for empty input", () => {
313
+ expect(truncateAtFirstFile([])).toEqual([]);
314
+ });
315
+ });
242
316
  });
@@ -1,24 +1,3 @@
1
- // function printDebugOwnerTree(fiber: Fiber): string | null {
2
- // let current: Fiber | null = fiber || null;
3
- // let results = [];
4
- // while (current) {
5
- // results.push(getUsableName(current));
6
- // current = current._debugOwner || null;
7
- // }
8
- // console.log('DEBUG OWNER: ', results);
9
- // return null;
10
- // }
11
- // function printReturnTree(fiber: Fiber): string | null {
12
- // let current: Fiber | null = fiber || null;
13
- // let results = [];
14
- // while (current) {
15
- // results.push(getUsableName(current));
16
- // current = current.return || null;
17
- // }
18
- // console.log('RETURN: ', results);
19
- // return null;
20
- // }
21
-
22
1
  export function getUsableName(fiber) {
23
2
  if (!fiber) {
24
3
  return "Not found";
package/dist/output.css CHANGED
@@ -879,10 +879,6 @@ input:where([type='file']):focus {
879
879
  left: 0.25rem;
880
880
  }
881
881
 
882
- .left-1\/2 {
883
- left: 50%;
884
- }
885
-
886
882
  .left-3 {
887
883
  left: 0.75rem;
888
884
  }
@@ -895,10 +891,6 @@ input:where([type='file']):focus {
895
891
  top: 0.25rem;
896
892
  }
897
893
 
898
- .top-1\/2 {
899
- top: 50%;
900
- }
901
-
902
894
  .z-10 {
903
895
  z-index: 10;
904
896
  }
@@ -1078,11 +1070,6 @@ input:where([type='file']):focus {
1078
1070
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1079
1071
  }
1080
1072
 
1081
- .-translate-x-1\/2 {
1082
- --tw-translate-x: -50%;
1083
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1084
- }
1085
-
1086
1073
  .-translate-x-full {
1087
1074
  --tw-translate-x: -100%;
1088
1075
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -1093,11 +1080,6 @@ input:where([type='file']):focus {
1093
1080
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1094
1081
  }
1095
1082
 
1096
- .-translate-y-1\/2 {
1097
- --tw-translate-y: -50%;
1098
- transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1099
- }
1100
-
1101
1083
  .translate-x-full {
1102
1084
  --tw-translate-x: 100%;
1103
1085
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@@ -1175,6 +1157,12 @@ input:where([type='file']):focus {
1175
1157
  overflow: scroll;
1176
1158
  }
1177
1159
 
1160
+ .truncate {
1161
+ overflow: hidden;
1162
+ text-overflow: ellipsis;
1163
+ white-space: nowrap;
1164
+ }
1165
+
1178
1166
  .text-ellipsis {
1179
1167
  text-overflow: ellipsis;
1180
1168
  }
@@ -1231,6 +1219,10 @@ input:where([type='file']):focus {
1231
1219
  border-style: solid;
1232
1220
  }
1233
1221
 
1222
+ .border-dashed {
1223
+ border-style: dashed;
1224
+ }
1225
+
1234
1226
  .border-amber-500 {
1235
1227
  --tw-border-opacity: 1;
1236
1228
  border-color: rgb(245 158 11 / var(--tw-border-opacity, 1));
@@ -1465,11 +1457,6 @@ input:where([type='file']):focus {
1465
1457
  padding-bottom: 0px;
1466
1458
  }
1467
1459
 
1468
- .py-0\.5 {
1469
- padding-top: 0.125rem;
1470
- padding-bottom: 0.125rem;
1471
- }
1472
-
1473
1460
  .py-1 {
1474
1461
  padding-top: 0.25rem;
1475
1462
  padding-bottom: 0.25rem;
@@ -1803,21 +1790,4 @@ input:where([type='file']):focus {
1803
1790
 
1804
1791
  .ease-out {
1805
1792
  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
1806
- }
1807
-
1808
- .hover\:bg-white\/30:hover {
1809
- background-color: rgb(255 255 255 / 0.3);
1810
- }
1811
-
1812
- .hover\:text-gray-100:hover {
1813
- --tw-text-opacity: 1;
1814
- color: rgb(243 244 246 / var(--tw-text-opacity, 1));
1815
- }
1816
-
1817
- .group\/tooltip:hover .group-hover\/tooltip\:visible {
1818
- visibility: visible;
1819
- }
1820
-
1821
- .group\/tooltip:hover .group-hover\/tooltip\:opacity-100 {
1822
- opacity: 1;
1823
1793
  }
@@ -1,35 +1,10 @@
1
- import { Fiber, Target } from "@locator/shared";
1
+ import { Target } from "@locator/shared";
2
2
  export type Source = {
3
3
  fileName: string;
4
4
  lineNumber: number;
5
5
  columnNumber?: number;
6
6
  projectPath?: string;
7
7
  };
8
- type SimpleElement = {
9
- type: "element";
10
- name: string;
11
- uniqueId: string;
12
- fiber: Fiber;
13
- box: SimpleDOMRect | null;
14
- element: Element | Text;
15
- children: (SimpleElement | SimpleComponent)[];
16
- source: Source | null;
17
- };
18
- type SimpleComponent = {
19
- type: "component";
20
- uniqueId: string;
21
- name: string;
22
- fiber: Fiber;
23
- box: SimpleDOMRect | null;
24
- children: (SimpleElement | SimpleComponent)[];
25
- source: Source | null;
26
- definitionSourceFile: string | null;
27
- };
28
- export type SimpleNode = SimpleElement | SimpleComponent;
29
- export type HighlightedNode = {
30
- getNode: () => SimpleNode | null;
31
- setNode: (node: SimpleNode | null) => void;
32
- };
33
8
  export type SimpleDOMRect = {
34
9
  height: number;
35
10
  width: number;
@@ -45,9 +20,3 @@ export type LinkProps = {
45
20
  line: number;
46
21
  column: number;
47
22
  };
48
- export type ContextMenuState = {
49
- target: HTMLElement;
50
- x: number;
51
- y: number;
52
- };
53
- export {};