@buoy-gg/highlight-updates 7.0.38 → 7.0.39

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 (103) hide show
  1. package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +281 -1
  2. package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1474 -1
  3. package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +641 -1
  4. package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +273 -1
  5. package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +188 -1
  6. package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +293 -1
  7. package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +508 -1
  8. package/lib/commonjs/highlight-updates/components/RenderDetailView.js +846 -1
  9. package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +915 -1
  10. package/lib/commonjs/highlight-updates/components/RenderListItem.js +235 -1
  11. package/lib/commonjs/highlight-updates/components/RendersCopySettingsView.js +469 -1
  12. package/lib/commonjs/highlight-updates/components/StatsDisplay.js +73 -1
  13. package/lib/commonjs/highlight-updates/types/copySettings.js +107 -1
  14. package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +446 -1
  15. package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1920 -1
  16. package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +359 -1
  17. package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +371 -1
  18. package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +1840 -1
  19. package/lib/commonjs/highlight-updates/utils/RenderTracker.js +952 -1
  20. package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +264 -1
  21. package/lib/commonjs/highlight-updates/utils/copySettingsStorage.js +32 -1
  22. package/lib/commonjs/highlight-updates/utils/devtoolsComponentNames.js +188 -1
  23. package/lib/commonjs/highlight-updates/utils/namedComposite.js +41 -1
  24. package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +103 -1
  25. package/lib/commonjs/highlight-updates/utils/rawEventEmitter.js +50 -1
  26. package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +58 -1
  27. package/lib/commonjs/highlight-updates/utils/rendersExportFormatter.js +485 -1
  28. package/lib/commonjs/highlight-updates/utils/screenElements.js +1248 -1
  29. package/lib/commonjs/highlight-updates/utils/tapMatch.js +83 -1
  30. package/lib/commonjs/highlight-updates/utils/testIDs.js +68 -1
  31. package/lib/commonjs/highlight-updates/utils/touchCapture.js +277 -1
  32. package/lib/commonjs/index.js +429 -1
  33. package/lib/commonjs/preset.js +278 -1
  34. package/lib/commonjs/sync/highlightUpdatesSyncAdapter.js +235 -1
  35. package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +274 -1
  36. package/lib/module/highlight-updates/components/HighlightFilterView.js +1468 -1
  37. package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +634 -1
  38. package/lib/module/highlight-updates/components/IdentifierBadge.js +265 -1
  39. package/lib/module/highlight-updates/components/IsolatedRenderList.js +183 -1
  40. package/lib/module/highlight-updates/components/ModalHeaderContent.js +288 -1
  41. package/lib/module/highlight-updates/components/RenderCauseBadge.js +499 -1
  42. package/lib/module/highlight-updates/components/RenderDetailView.js +840 -1
  43. package/lib/module/highlight-updates/components/RenderHistoryViewer.js +908 -1
  44. package/lib/module/highlight-updates/components/RenderListItem.js +230 -1
  45. package/lib/module/highlight-updates/components/RendersCopySettingsView.js +465 -1
  46. package/lib/module/highlight-updates/components/StatsDisplay.js +70 -1
  47. package/lib/module/highlight-updates/types/copySettings.js +102 -1
  48. package/lib/module/highlight-updates/utils/CommitProfiler.js +442 -1
  49. package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1916 -1
  50. package/lib/module/highlight-updates/utils/PerformanceLogger.js +353 -1
  51. package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +358 -1
  52. package/lib/module/highlight-updates/utils/RenderCauseDetector.js +1830 -1
  53. package/lib/module/highlight-updates/utils/RenderTracker.js +949 -1
  54. package/lib/module/highlight-updates/utils/ViewTypeMapper.js +255 -1
  55. package/lib/module/highlight-updates/utils/copySettingsStorage.js +26 -1
  56. package/lib/module/highlight-updates/utils/devtoolsComponentNames.js +178 -1
  57. package/lib/module/highlight-updates/utils/namedComposite.js +36 -1
  58. package/lib/module/highlight-updates/utils/ownStateChanged.js +98 -1
  59. package/lib/module/highlight-updates/utils/rawEventEmitter.js +46 -1
  60. package/lib/module/highlight-updates/utils/renderExportFormatter.js +54 -1
  61. package/lib/module/highlight-updates/utils/rendersExportFormatter.js +478 -1
  62. package/lib/module/highlight-updates/utils/screenElements.js +1242 -1
  63. package/lib/module/highlight-updates/utils/tapMatch.js +76 -1
  64. package/lib/module/highlight-updates/utils/testIDs.js +60 -1
  65. package/lib/module/highlight-updates/utils/touchCapture.js +270 -1
  66. package/lib/module/index.js +130 -1
  67. package/lib/module/preset.js +272 -1
  68. package/lib/module/sync/highlightUpdatesSyncAdapter.js +230 -1
  69. package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +1 -0
  70. package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +1 -0
  71. package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +1 -0
  72. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +1 -0
  73. package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +1 -0
  74. package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +1 -0
  75. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +1 -0
  76. package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +1 -0
  77. package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +1 -0
  78. package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +1 -0
  79. package/lib/typescript/highlight-updates/components/RendersCopySettingsView.d.ts.map +1 -0
  80. package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +1 -0
  81. package/lib/typescript/highlight-updates/types/copySettings.d.ts.map +1 -0
  82. package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +1 -0
  83. package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +1 -0
  84. package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +1 -0
  85. package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +1 -0
  86. package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +1 -0
  87. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +1 -0
  88. package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +1 -0
  89. package/lib/typescript/highlight-updates/utils/copySettingsStorage.d.ts.map +1 -0
  90. package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts.map +1 -0
  91. package/lib/typescript/highlight-updates/utils/namedComposite.d.ts.map +1 -0
  92. package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts.map +1 -0
  93. package/lib/typescript/highlight-updates/utils/rawEventEmitter.d.ts.map +1 -0
  94. package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +1 -0
  95. package/lib/typescript/highlight-updates/utils/rendersExportFormatter.d.ts.map +1 -0
  96. package/lib/typescript/highlight-updates/utils/screenElements.d.ts.map +1 -0
  97. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts.map +1 -0
  98. package/lib/typescript/highlight-updates/utils/testIDs.d.ts.map +1 -0
  99. package/lib/typescript/highlight-updates/utils/touchCapture.d.ts.map +1 -0
  100. package/lib/typescript/index.d.ts.map +1 -0
  101. package/lib/typescript/preset.d.ts.map +1 -0
  102. package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts.map +1 -0
  103. package/package.json +7 -7
@@ -1 +1,76 @@
1
- "use strict";export function isOnScreenTap(t){return!!t&&t.x>=-.1&&t.x<=1.1&&t.y>=-.1&&t.y<=1.1}export function isRectCenterOnScreen(t,e,a){if(!t||e<=0||a<=0)return!1;const r=t.x+t.width/2,o=t.y+t.height/2,n=.1*e,s=.1*a;return r>=-n&&r<=e+n&&o>=-s&&o<=a+s}export function looseText(t){return String(t??"").toLowerCase().replace(/\s+/g," ").replace(/(\d)[,\u00a0\u202f](?=\d{3}\b)/g,"$1").trim()}export function scoreTapMatch(t,e){if(null!=e.nativeTag&&t.nativeTag===e.nativeTag)return 1e3;const a=String(e.query??e.testID??"").trim().toLowerCase();if(!a)return 0;const r=[t.testID,t.label,t.text,t.name].filter(Boolean).map(t=>t.toLowerCase());let o=0;for(const t of r)t===a?o=Math.max(o,100):t.startsWith(a)?o=Math.max(o,50):t.includes(a)&&(o=Math.max(o,25));const n=looseText(a);if(n)for(const t of r){const e=looseText(t);e===n?o=Math.max(o,90):e.startsWith(n)?o=Math.max(o,45):e.includes(n)&&(o=Math.max(o,22))}return e.testID&&t.testID&&t.testID.toLowerCase()===e.testID.toLowerCase()&&(o=Math.max(o,200)),0===o?0:((t.testID??"").toLowerCase().startsWith("buoy-dial-")&&(o+=40),t.tap&&(isOnScreenTap(t.tap)?o+=20:o-=40),o)}
1
+ "use strict";
2
+
3
+ /**
4
+ * Shared tap-target scoring for describe_screen / tap_element.
5
+ *
6
+ * Ranking used to be substring-on-testID only, so `tap: "ASSETS"` preferred
7
+ * `nav-assets-tool-test` (score 25) over a visible dial icon, and off-screen
8
+ * Expo Router screens (y > 1) stayed in the candidate list.
9
+ */
10
+
11
+ /** True when the measured center is inside the window (10% slack for home-indicator / portal hosts). */
12
+ export function isOnScreenTap(tap) {
13
+ if (!tap) return false;
14
+ return tap.x >= -0.1 && tap.x <= 1.1 && tap.y >= -0.1 && tap.y <= 1.1;
15
+ }
16
+
17
+ /**
18
+ * Point-in-window test in *points* (measureInWindow coords), with slack so
19
+ * the floating menu and rows clipped at the home indicator still count.
20
+ * Previous Expo Router screens typically measure at y > 1.2× window height.
21
+ */
22
+ export function isRectCenterOnScreen(rect, screenW, screenH) {
23
+ if (!rect || screenW <= 0 || screenH <= 0) return false;
24
+ const cx = rect.x + rect.width / 2;
25
+ const cy = rect.y + rect.height / 2;
26
+ const slackX = screenW * 0.1;
27
+ const slackY = screenH * 0.1;
28
+ return cx >= -slackX && cx <= screenW + slackX && cy >= -slackY && cy <= screenH + slackY;
29
+ }
30
+
31
+ /**
32
+ * The comparable form of a piece of text.
33
+ *
34
+ * "1,450", `1450` and "1,450 Points" are one request as far as a person is
35
+ * concerned. Measured on a live QA run: waits for BOTH "1,450" and 1450 failed
36
+ * against a balance that was on screen, because the thousands separator made
37
+ * every comparison miss. Lowercased, whitespace collapsed, digit-group
38
+ * separators dropped. A number arrives here as a number often enough that the
39
+ * coercion is not optional — `.trim()` on one is a crash.
40
+ */
41
+ export function looseText(value) {
42
+ return String(value ?? "").toLowerCase().replace(/\s+/g, " ").replace(/(\d)[,\u00a0\u202f](?=\d{3}\b)/g, "$1").trim();
43
+ }
44
+ export function scoreTapMatch(c, params) {
45
+ if (params.nativeTag != null && c.nativeTag === params.nativeTag) return 1000;
46
+ const q = String(params.query ?? params.testID ?? "").trim().toLowerCase();
47
+ if (!q) return 0;
48
+ const fields = [c.testID, c.label, c.text, c.name].filter(Boolean).map(s => s.toLowerCase());
49
+ let best = 0;
50
+ for (const f of fields) {
51
+ if (f === q) best = Math.max(best, 100);else if (f.startsWith(q)) best = Math.max(best, 50);else if (f.includes(q)) best = Math.max(best, 25);
52
+ }
53
+ // Same tiers on the loose form, one notch lower so an exact hit still wins
54
+ // when both match. Skipped when the loose query is empty, or `includes("")`
55
+ // would score every candidate on the screen.
56
+ const looseQuery = looseText(q);
57
+ if (looseQuery) {
58
+ for (const f of fields) {
59
+ const loose = looseText(f);
60
+ if (loose === looseQuery) best = Math.max(best, 90);else if (loose.startsWith(looseQuery)) best = Math.max(best, 45);else if (loose.includes(looseQuery)) best = Math.max(best, 22);
61
+ }
62
+ }
63
+ if (params.testID && c.testID && c.testID.toLowerCase() === params.testID.toLowerCase()) {
64
+ best = Math.max(best, 200);
65
+ }
66
+ if (best === 0) return 0;
67
+ const id = (c.testID ?? "").toLowerCase();
68
+ // Dial icons are the thing agents mean when they tap a tool name. (No
69
+ // penalties for app testID patterns — this ships in the library, and we
70
+ // don't know consumer apps' naming conventions.)
71
+ if (id.startsWith("buoy-dial-")) best += 40;
72
+ if (c.tap) {
73
+ if (isOnScreenTap(c.tap)) best += 20;else best -= 40;
74
+ }
75
+ return best;
76
+ }
@@ -1 +1,60 @@
1
- "use strict";export function identifierSlug(e){return String(e).toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"unknown"}export function renderRowTestID(e){return`buoy-highlight-row-${identifierSlug(e)}`}export function detectedItemTestID(e,t){return`buoy-highlight-detected-${identifierSlug(e)}-${identifierSlug(t)}`}export function patternChipTestID(e,t,i){return`buoy-highlight-${e}-chip-${identifierSlug(t)}-${identifierSlug(i)}`}export function settingTestID(e){return`buoy-highlight-setting-${e}`}
1
+ "use strict";
2
+
3
+ /**
4
+ * Stable e2e handles for the Render Highlighter (highlight-updates).
5
+ *
6
+ * This tool's list is the hardest thing in Buoy to address, because the thing
7
+ * it lists is a NATIVE VIEW and the only key it has for one is the fiber's
8
+ * `nativeTag` — a per-session counter that changes on every launch, on every
9
+ * remount, and (on a dynamic screen) between two runs of the same test. A
10
+ * handle built from it would name a different component every time.
11
+ *
12
+ * So rows are addressed by their IDENTIFIER, in exactly the priority the list
13
+ * item itself uses to label them: testID → nativeID → component name →
14
+ * accessibility label → nativeTag. That is the same string the developer sees
15
+ * on the row, it is stable as long as the component is, and it is what a test
16
+ * can name up front. `nativeTag` remains the last resort for an anonymous host
17
+ * view, which no test should be naming anyway.
18
+ *
19
+ * Slug rules are mirrored in `example-dev-build/utils/renderBench.js` so the
20
+ * fixture and the tool agree about what a row is called; the suite's
21
+ * `fixture.js` spot-checks that they still do.
22
+ */
23
+
24
+ /** Any identifier string → a testID-safe slug. */
25
+ export function identifierSlug(value) {
26
+ return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
27
+ }
28
+
29
+ /**
30
+ * `buoy-highlight-row-<slug>` for one row of the render list.
31
+ *
32
+ * Sub-handles hang off it: `-name`, `-count`, `-type`, `-cause`, `-identifier`,
33
+ * `-time`. The count is the tool's whole claim, so it gets its own handle
34
+ * rather than living inside the row's merged label.
35
+ */
36
+ export function renderRowTestID(identifier) {
37
+ return `buoy-highlight-row-${identifierSlug(identifier)}`;
38
+ }
39
+
40
+ /**
41
+ * `buoy-highlight-detected-<type>-<slug>` for one chip in DETECTED ITEMS.
42
+ *
43
+ * The type is in the handle because the same string legitimately appears under
44
+ * two categories — a component called `Pressable` and a viewType called
45
+ * `Pressable` are different filters with the same name, and a suite that added
46
+ * the wrong one would still see "a pattern was added".
47
+ */
48
+ export function detectedItemTestID(type, value) {
49
+ return `buoy-highlight-detected-${identifierSlug(type)}-${identifierSlug(value)}`;
50
+ }
51
+
52
+ /** `buoy-highlight-<mode>-chip-<type>-<slug>` for an active filter pattern. */
53
+ export function patternChipTestID(mode, type, value) {
54
+ return `buoy-highlight-${mode}-chip-${identifierSlug(type)}-${identifierSlug(value)}`;
55
+ }
56
+
57
+ /** `buoy-highlight-setting-<key>` for a settings row's control. */
58
+ export function settingTestID(key) {
59
+ return `buoy-highlight-setting-${key}`;
60
+ }
@@ -1 +1,270 @@
1
- "use strict";import{resolveTouchTarget}from"./screenElements";import{resolveRawEventEmitter}from"./rawEventEmitter";const TAP_SLOP_PT=14,LONG_PRESS_MS=550,MAX_TAP_MS=4e3,TEXT_COALESCE_MS=8e3,MAX_RECORDS=600;let emitter=null,emitterResolved=!1,unavailableReason=null,subscriptions=[],status="idle",records=[],seq=0,pending=null;function getEmitter(){if(emitterResolved)return emitter;emitterResolved=!0;const t=resolveRawEventEmitter();return emitter=t.emitter,unavailableReason=t.reason,emitter}function num(t){return"number"==typeof t&&Number.isFinite(t)?t:null}function tagOf(t){const e=t?.nativeEvent;return e?num(e.target)??num(e.tag):null}function pointOf(t){const e=t?.nativeEvent;if(!e)return null;const n={x:num(e.pageX),y:num(e.pageY)};if(null!==n.x&&null!==n.y)return{x:n.x,y:n.y};for(const t of["changedTouches","touches"]){const n=e[t];if(Array.isArray(n)&&n.length){const t=n[0],e=num(t?.pageX),r=num(t?.pageY);if(null!==e&&null!==r)return{x:e,y:r}}}return null}function push(t){records.push({...t,seq:++seq}),records.length>600&&(records=records.slice(-600))}function onTouchStart(t){const e=tagOf(t);if(null===e)return;const n=pointOf(t);let r=null;try{r=resolveTouchTarget(e)}catch{}pending={tag:e,x:n?.x??0,y:n?.y??0,t:Date.now(),drift:0,target:r}}function onTouchMove(t){if(!pending)return;const e=pointOf(t);if(!e)return;const n=e.x-pending.x,r=e.y-pending.y;pending.drift=Math.max(pending.drift,Math.sqrt(n*n+r*r))}function onTouchEnd(t){const e=pending;if(pending=null,!e||!e.target)return;const n=pointOf(t);if(n){const t=n.x-e.x,r=n.y-e.y;e.drift=Math.max(e.drift,Math.sqrt(t*t+r*r))}if(e.drift>14)return;const r=Date.now()-e.t;r>4e3||"text"!==e.target.control&&"toggle"!==e.target.control&&"slider"!==e.target.control&&push({t:Date.now(),kind:r>=550?"longPress":"press",target:e.target})}function onTouchCancel(){pending=null}function onChange(t){const e=tagOf(t);if(null===e)return;const n=t.nativeEvent;let r,o;if("string"==typeof n.text)r="text",o=n.text;else if("boolean"==typeof n.value)r="toggle",o=n.value;else{if("number"!=typeof n.value)return;r="slider",o=n.value}const u=records[records.length-1];if(u&&u.kind===r&&u.target.nativeTag===e&&Date.now()-u.t<8e3)return u.value=o,void(u.t=Date.now());let a=null;try{a=resolveTouchTarget(e)}catch{}a&&push({t:Date.now(),kind:r,target:a,value:o})}export function startTouchCapture(){if("capturing"===status)return getTouchCaptureStatus();const t=getEmitter();if(!t)return status="unavailable",getTouchCaptureStatus();try{subscriptions=[t.addListener("topTouchStart",onTouchStart),t.addListener("topTouchMove",onTouchMove),t.addListener("topTouchEnd",onTouchEnd),t.addListener("topTouchCancel",onTouchCancel),t.addListener("topChange",onChange),t.addListener("topValueChange",onChange),t.addListener("topSlidingComplete",onChange)],pending=null,status="capturing"}catch(t){subscriptions=[],status="unavailable",unavailableReason=t?.message??"addListener failed"}return getTouchCaptureStatus()}export function stopTouchCapture(){subscriptions.forEach(t=>{try{t.remove()}catch{}}),subscriptions=[],pending=null,"capturing"===status&&(status="idle")}export function clearTouchCapture(){records=[],seq=0}export function getTouchCaptureStatus(){return{status:status,reason:"unavailable"===status?unavailableReason:null}}export function readTouchCapture(t=0){return{...getTouchCaptureStatus(),records:records.filter(e=>e.seq>t),seq:seq}}
1
+ "use strict";
2
+
3
+ /**
4
+ * touchCapture — what a HUMAN is doing to the app, as replayable intents.
5
+ *
6
+ * This is the recording half of the pair whose playback half is `tapElement`:
7
+ * it watches real finger input and names each interaction in exactly the
8
+ * vocabulary `tapElement` searches by, so a recorded macro can be replayed
9
+ * without a second guess about what "that button" meant.
10
+ *
11
+ * Why RawEventEmitter and not the obvious alternatives:
12
+ * - A capture-phase responder on a root View would be the textbook answer, but
13
+ * Buoy mounts as a SIBLING of the app tree, not a parent. There is no root to
14
+ * wrap without changing every host app's integration.
15
+ * - Patching `Pressability`, or rewriting `onPress` props on the fly, breaks the
16
+ * moment a component re-renders (props are rebuilt every render) and misses
17
+ * everything driven by react-native-gesture-handler anyway.
18
+ * - RawEventEmitter mirrors every dispatched event before React's plugins see
19
+ * it, needs zero app instrumentation, and is already proven in-tree by
20
+ * focus-inspector's TV focus observer.
21
+ *
22
+ * Three rules the recorder above depends on:
23
+ *
24
+ * THE TARGET IS RESOLVED AT touchStart, NOT touchEnd. A press handler can
25
+ * navigate synchronously, so resolving after the fact names an element on the
26
+ * screen the app moved TO. We resolve while the finger is still down and emit
27
+ * when it lifts.
28
+ *
29
+ * A DRAG IS NOT A TAP. Scrolling a list produces the same touchStart the
30
+ * button above it does. Movement past a threshold, or an explicit
31
+ * touchCancel when a ScrollView steals the responder, drops the record —
32
+ * otherwise every recording is half scroll noise.
33
+ *
34
+ * KEYSTROKES ARE NOT INTENTS. Typing "SAVE10" fires six change events; a
35
+ * macro step wants the finished string. Consecutive edits to the same input
36
+ * collapse into the latest value as they arrive.
37
+ */
38
+
39
+ import { resolveTouchTarget } from "./screenElements";
40
+ import { resolveRawEventEmitter } from "./rawEventEmitter";
41
+
42
+ /** Points of finger travel past which the gesture stops being a tap. */
43
+ const TAP_SLOP_PT = 14;
44
+ /** Hold time at or past which a tap is recorded as a long-press. */
45
+ const LONG_PRESS_MS = 550;
46
+ /** A tap that outlasts this was a gesture we don't model — drop it. */
47
+ const MAX_TAP_MS = 4_000;
48
+ /** Consecutive edits to the same input within this window are one intent. */
49
+ const TEXT_COALESCE_MS = 8_000;
50
+ /** Ring buffer size. A recording longer than this is not a macro anyway. */
51
+ const MAX_RECORDS = 600;
52
+ let emitter = null;
53
+ let emitterResolved = false;
54
+ let unavailableReason = null;
55
+ let subscriptions = [];
56
+ let status = "idle";
57
+ let records = [];
58
+ let seq = 0;
59
+ let pending = null;
60
+ function getEmitter() {
61
+ if (emitterResolved) return emitter;
62
+ emitterResolved = true;
63
+ const resolution = resolveRawEventEmitter();
64
+ emitter = resolution.emitter;
65
+ unavailableReason = resolution.reason;
66
+ return emitter;
67
+ }
68
+ function num(v) {
69
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
70
+ }
71
+
72
+ /** Fabric puts the touched view on `target`; `tag` is the older shape. */
73
+ function tagOf(event) {
74
+ const native = event?.nativeEvent;
75
+ if (!native) return null;
76
+ return num(native.target) ?? num(native.tag);
77
+ }
78
+
79
+ /**
80
+ * Touch coordinates. A touch event carries them at the top level on the
81
+ * synthesized event and inside `touches`/`changedTouches` on the raw one, and
82
+ * which of those is populated differs by platform and architecture — so read
83
+ * whichever answers rather than betting on one.
84
+ */
85
+ function pointOf(event) {
86
+ const native = event?.nativeEvent;
87
+ if (!native) return null;
88
+ const direct = {
89
+ x: num(native.pageX),
90
+ y: num(native.pageY)
91
+ };
92
+ if (direct.x !== null && direct.y !== null) return {
93
+ x: direct.x,
94
+ y: direct.y
95
+ };
96
+ for (const key of ["changedTouches", "touches"]) {
97
+ const list = native[key];
98
+ if (Array.isArray(list) && list.length) {
99
+ const first = list[0];
100
+ const x = num(first?.pageX);
101
+ const y = num(first?.pageY);
102
+ if (x !== null && y !== null) return {
103
+ x,
104
+ y
105
+ };
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+ function push(record) {
111
+ records.push({
112
+ ...record,
113
+ seq: ++seq
114
+ });
115
+ if (records.length > MAX_RECORDS) records = records.slice(-MAX_RECORDS);
116
+ }
117
+ function onTouchStart(event) {
118
+ const tag = tagOf(event);
119
+ if (tag === null) return;
120
+ const point = pointOf(event);
121
+ let target = null;
122
+ try {
123
+ target = resolveTouchTarget(tag);
124
+ } catch {
125
+ // A fiber walk that throws must never break the app's touch handling.
126
+ }
127
+ pending = {
128
+ tag,
129
+ x: point?.x ?? 0,
130
+ y: point?.y ?? 0,
131
+ t: Date.now(),
132
+ drift: 0,
133
+ target
134
+ };
135
+ }
136
+ function onTouchMove(event) {
137
+ if (!pending) return;
138
+ const point = pointOf(event);
139
+ if (!point) return;
140
+ const dx = point.x - pending.x;
141
+ const dy = point.y - pending.y;
142
+ pending.drift = Math.max(pending.drift, Math.sqrt(dx * dx + dy * dy));
143
+ }
144
+ function onTouchEnd(event) {
145
+ const p = pending;
146
+ pending = null;
147
+ if (!p || !p.target) return;
148
+ const point = pointOf(event);
149
+ if (point) {
150
+ const dx = point.x - p.x;
151
+ const dy = point.y - p.y;
152
+ p.drift = Math.max(p.drift, Math.sqrt(dx * dx + dy * dy));
153
+ }
154
+ if (p.drift > TAP_SLOP_PT) return; // a drag, a scroll, a swipe — not a step
155
+
156
+ const held = Date.now() - p.t;
157
+ if (held > MAX_TAP_MS) return;
158
+
159
+ // A text input's tap is just "focus this field"; the intent is the text it
160
+ // ends up with, which arrives separately as a change event. Recording the
161
+ // focus tap too would put a step in every macro that does nothing on replay.
162
+ if (p.target.control === "text") return;
163
+ // Same for a toggle/slider: the tap and the value change are one intent, and
164
+ // the change event is the half that carries what it became.
165
+ if (p.target.control === "toggle" || p.target.control === "slider") return;
166
+ push({
167
+ t: Date.now(),
168
+ kind: held >= LONG_PRESS_MS ? "longPress" : "press",
169
+ target: p.target
170
+ });
171
+ }
172
+ function onTouchCancel() {
173
+ // A ScrollView (or any ancestor) taking the responder terminates the touch.
174
+ pending = null;
175
+ }
176
+
177
+ /**
178
+ * `topChange` is the shared channel for "this control's value became X" —
179
+ * TextInput sends `text`, Switch sends `value`. Sliders arrive on their own
180
+ * channel. All three are the same idea: a control the user set.
181
+ */
182
+ function onChange(event) {
183
+ const tag = tagOf(event);
184
+ if (tag === null) return;
185
+ const native = event.nativeEvent;
186
+ let kind;
187
+ let value;
188
+ if (typeof native.text === "string") {
189
+ kind = "text";
190
+ value = native.text;
191
+ } else if (typeof native.value === "boolean") {
192
+ kind = "toggle";
193
+ value = native.value;
194
+ } else if (typeof native.value === "number") {
195
+ kind = "slider";
196
+ value = native.value;
197
+ } else {
198
+ return;
199
+ }
200
+ const last = records[records.length - 1];
201
+ if (last && last.kind === kind && last.target.nativeTag === tag && Date.now() - last.t < TEXT_COALESCE_MS) {
202
+ // Same control, still typing/dragging: this IS the previous intent, further
203
+ // along. Replace rather than append, or "SAVE10" becomes six macro steps.
204
+ last.value = value;
205
+ last.t = Date.now();
206
+ return;
207
+ }
208
+ let target = null;
209
+ try {
210
+ target = resolveTouchTarget(tag);
211
+ } catch {
212
+ /* see onTouchStart */
213
+ }
214
+ if (!target) return;
215
+ push({
216
+ t: Date.now(),
217
+ kind,
218
+ target,
219
+ value
220
+ });
221
+ }
222
+ export function startTouchCapture() {
223
+ if (status === "capturing") return getTouchCaptureStatus();
224
+ const raw = getEmitter();
225
+ if (!raw) {
226
+ status = "unavailable";
227
+ return getTouchCaptureStatus();
228
+ }
229
+ try {
230
+ subscriptions = [raw.addListener("topTouchStart", onTouchStart), raw.addListener("topTouchMove", onTouchMove), raw.addListener("topTouchEnd", onTouchEnd), raw.addListener("topTouchCancel", onTouchCancel), raw.addListener("topChange", onChange), raw.addListener("topValueChange", onChange), raw.addListener("topSlidingComplete", onChange)];
231
+ pending = null;
232
+ status = "capturing";
233
+ } catch (e) {
234
+ subscriptions = [];
235
+ status = "unavailable";
236
+ unavailableReason = e?.message ?? "addListener failed";
237
+ }
238
+ return getTouchCaptureStatus();
239
+ }
240
+ export function stopTouchCapture() {
241
+ subscriptions.forEach(s => {
242
+ try {
243
+ s.remove();
244
+ } catch {
245
+ /* already gone is fine */
246
+ }
247
+ });
248
+ subscriptions = [];
249
+ pending = null;
250
+ if (status === "capturing") status = "idle";
251
+ }
252
+ export function clearTouchCapture() {
253
+ records = [];
254
+ seq = 0;
255
+ }
256
+ export function getTouchCaptureStatus() {
257
+ return {
258
+ status,
259
+ reason: status === "unavailable" ? unavailableReason : null
260
+ };
261
+ }
262
+
263
+ /** Everything recorded after `sinceSeq`, plus where the caller now is. */
264
+ export function readTouchCapture(sinceSeq = 0) {
265
+ return {
266
+ ...getTouchCaptureStatus(),
267
+ records: records.filter(r => r.seq > sinceSeq),
268
+ seq
269
+ };
270
+ }
@@ -1 +1,130 @@
1
- "use strict";export{highlightUpdatesPreset,createHighlightUpdatesTool,highlightUpdatesModalPreset,createHighlightUpdatesModalTool}from"./preset";export{default as HighlightUpdatesController}from"./highlight-updates/utils/HighlightUpdatesController";export{highlightUpdatesSyncAdapter}from"./sync/highlightUpdatesSyncAdapter";export{HighlightUpdatesOverlay}from"./highlight-updates/HighlightUpdatesOverlay";export{installProfilerInterceptor,uninstallProfilerInterceptor,setComparisonCallback,isInterceptorInstalled,enableProfilerLogging,disableProfilerLogging,isLoggingEnabled}from"./highlight-updates/utils/ProfilerInterceptor";export{CommitProfiler}from"./highlight-updates/utils/CommitProfiler";export{setCaptureBuoyToolRenders,isCapturingBuoyToolRenders}from"./highlight-updates/utils/devtoolsComponentNames";import{setRenderCaptureProvider}from"@buoy-gg/shared-ui";import{CommitProfiler as _commitProfilerForRegistry}from"./highlight-updates/utils/CommitProfiler";import _controllerForRegistry from"./highlight-updates/utils/HighlightUpdatesController";try{setRenderCaptureProvider({isSupported:_commitProfilerForRegistry.isSupported,start:_commitProfilerForRegistry.startCapture,stop:_commitProfilerForRegistry.stopCapture,startLiveHighlights:()=>{try{const e=_controllerForRegistry.isEnabled(),t=_controllerForRegistry.getHighlightsSuppressed();return _controllerForRegistry.enable(),()=>{try{e?t&&_controllerForRegistry.setHighlightsSuppressed(!0):_controllerForRegistry.disable()}catch{}}}catch{return null}}})}catch{}export{RenderTracker}from"./highlight-updates/utils/RenderTracker";export const onRenderEvent=e=>{const{RenderTracker:t}=require("./highlight-updates/utils/RenderTracker");return t.onRenderEvent(e)};export{detectRenderCause,clearRenderCauseState,removeRenderCauseState,getRenderCauseStats,safeCloneForHistory,capturePropsSnapshot,captureStateSnapshot}from"./highlight-updates/utils/RenderCauseDetector";export{PerformanceLogger}from"./highlight-updates/utils/PerformanceLogger";export{VIEW_TYPE_MAP,getComponentDisplayName,getNativeViewType,isKnownViewType,getAllNativeViewTypes,getAllComponentNames}from"./highlight-updates/utils/ViewTypeMapper";export{HighlightUpdatesModal,HighlightToolView}from"./highlight-updates/components/HighlightUpdatesModal";export{RenderListItem}from"./highlight-updates/components/RenderListItem";export{RenderDetailView}from"./highlight-updates/components/RenderDetailView";export{HighlightFilterView}from"./highlight-updates/components/HighlightFilterView";export{RenderCauseBadge,TwoLevelCauseBadge,CAUSE_CONFIG,COMPONENT_CAUSE_CONFIG}from"./highlight-updates/components/RenderCauseBadge";export{RenderHistoryViewer,RenderHistoryFooter}from"./highlight-updates/components/RenderHistoryViewer";export{generateRendersJson}from"./highlight-updates/utils/renderExportFormatter";export{identifierSlug,renderRowTestID,detectedItemTestID,patternChipTestID,settingTestID}from"./highlight-updates/utils/testIDs";
1
+ "use strict";
2
+
3
+ /**
4
+ * @buoy-gg/highlight-updates
5
+ *
6
+ * Standalone implementation of React DevTools' "Highlight updates when components
7
+ * render" feature. Works without requiring DevTools to be connected.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * // Toggle-only preset - tap to enable/disable highlights
12
+ * import { highlightUpdatesPreset } from '@buoy-gg/highlight-updates';
13
+ *
14
+ * <FloatingDevTools apps={[highlightUpdatesPreset]} />
15
+ *
16
+ * // Modal preset - full interface with filters and render list
17
+ * import { highlightUpdatesModalPreset } from '@buoy-gg/highlight-updates';
18
+ *
19
+ * <FloatingDevTools apps={[highlightUpdatesModalPreset]} />
20
+ *
21
+ * // Or use the standalone controller programmatically
22
+ * import { HighlightUpdatesController } from '@buoy-gg/highlight-updates';
23
+ *
24
+ * HighlightUpdatesController.toggle();
25
+ * HighlightUpdatesController.enable();
26
+ * HighlightUpdatesController.disable();
27
+ * ```
28
+ */
29
+
30
+ // Preset exports for FloatingDevTools integration
31
+ export { highlightUpdatesPreset, createHighlightUpdatesTool, highlightUpdatesModalPreset, createHighlightUpdatesModalTool } from "./preset";
32
+
33
+ // Controller export for standalone usage
34
+ export { default as HighlightUpdatesController } from "./highlight-updates/utils/HighlightUpdatesController";
35
+
36
+ // External sync (adapter for @buoy-gg/external-sync's useExternalSync)
37
+ export { highlightUpdatesSyncAdapter } from "./sync/highlightUpdatesSyncAdapter";
38
+
39
+ // Overlay component - auto-rendered by FloatingDevTools, but exported for manual usage
40
+ export { HighlightUpdatesOverlay } from "./highlight-updates/HighlightUpdatesOverlay";
41
+
42
+ // Profiler interceptor for debugging - captures what DevTools detects
43
+ export { installProfilerInterceptor, uninstallProfilerInterceptor, setComparisonCallback, isInterceptorInstalled, enableProfilerLogging, disableProfilerLogging, isLoggingEnabled } from "./highlight-updates/utils/ProfilerInterceptor";
44
+
45
+ // CommitProfiler — per-component render counts/durations over a capture window
46
+ export { CommitProfiler } from "./highlight-updates/utils/CommitProfiler";
47
+
48
+ // Bench "profile our own tools" mode: lets a dedicated benchmark screen
49
+ // include Buoy's tool UI in render capture (normally pruned wholesale).
50
+ // Highlight/events + perf-monitor components stay excluded regardless.
51
+ export { setCaptureBuoyToolRenders, isCapturingBuoyToolRenders } from "./highlight-updates/utils/devtoolsComponentNames";
52
+
53
+ // Register the render-capture provider so perf-monitor benchmarks can capture
54
+ // renders without depending on this package. Runs at module scope: whenever
55
+ // this package is installed, FloatingDevTools require()s it (including on the
56
+ // fresh realm after a reload-between-cases batch step), and Metro evaluates
57
+ // module side effects regardless of the package.json "sideEffects" hint.
58
+ import { setRenderCaptureProvider } from "@buoy-gg/shared-ui";
59
+ import { CommitProfiler as _commitProfilerForRegistry } from "./highlight-updates/utils/CommitProfiler";
60
+ import _controllerForRegistry from "./highlight-updates/utils/HighlightUpdatesController";
61
+ try {
62
+ setRenderCaptureProvider({
63
+ isSupported: _commitProfilerForRegistry.isSupported,
64
+ start: _commitProfilerForRegistry.startCapture,
65
+ stop: _commitProfilerForRegistry.stopCapture,
66
+ // Real-time bounding boxes (the Render Highlighter visual) for the span
67
+ // of a recording. Saves the user's prior overlay state and hands back a
68
+ // restore fn: previously ON stays ON, silent tracking (events tool) gets
69
+ // re-suppressed, otherwise fully disabled again.
70
+ startLiveHighlights: () => {
71
+ try {
72
+ const wasEnabled = _controllerForRegistry.isEnabled();
73
+ const wasSuppressed = _controllerForRegistry.getHighlightsSuppressed();
74
+ _controllerForRegistry.enable(); // idempotent; clears suppression
75
+ return () => {
76
+ try {
77
+ if (!wasEnabled) {
78
+ _controllerForRegistry.disable(); // respects background tracking
79
+ } else if (wasSuppressed) {
80
+ _controllerForRegistry.setHighlightsSuppressed(true);
81
+ }
82
+ } catch {
83
+ // restoring the overlay must never throw into the recorder
84
+ }
85
+ };
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ });
91
+ } catch {
92
+ // shared-ui too old to have the registry — capture is simply unavailable.
93
+ }
94
+
95
+ // RenderTracker singleton for tracking render history
96
+ export { RenderTracker } from "./highlight-updates/utils/RenderTracker";
97
+ // Convenience export for unified events integration
98
+ export const onRenderEvent = callback => {
99
+ const {
100
+ RenderTracker
101
+ } = require("./highlight-updates/utils/RenderTracker");
102
+ return RenderTracker.onRenderEvent(callback);
103
+ };
104
+
105
+ // RenderCauseDetector for detecting why components rendered
106
+ export { detectRenderCause, clearRenderCauseState, removeRenderCauseState, getRenderCauseStats,
107
+ // Snapshot capture for render history
108
+ safeCloneForHistory, capturePropsSnapshot, captureStateSnapshot } from "./highlight-updates/utils/RenderCauseDetector";
109
+
110
+ // PerformanceLogger for performance measurement and debugging
111
+ export { PerformanceLogger } from "./highlight-updates/utils/PerformanceLogger";
112
+ // ViewTypeMapper for translating native view names to component names
113
+ export { VIEW_TYPE_MAP, getComponentDisplayName, getNativeViewType, isKnownViewType, getAllNativeViewTypes, getAllComponentNames } from "./highlight-updates/utils/ViewTypeMapper";
114
+
115
+ // Modal components for custom integrations
116
+ // `HighlightToolView` is the same tool rendered inline (header bar + body +
117
+ // footer) instead of inside a JsModal — for hosts that show tools in a panel.
118
+ export { HighlightUpdatesModal, HighlightToolView } from "./highlight-updates/components/HighlightUpdatesModal";
119
+ export { RenderListItem } from "./highlight-updates/components/RenderListItem";
120
+ export { RenderDetailView } from "./highlight-updates/components/RenderDetailView";
121
+ export { HighlightFilterView } from "./highlight-updates/components/HighlightFilterView";
122
+ export { RenderCauseBadge, TwoLevelCauseBadge, CAUSE_CONFIG, COMPONENT_CAUSE_CONFIG } from "./highlight-updates/components/RenderCauseBadge";
123
+ export { RenderHistoryViewer, RenderHistoryFooter } from "./highlight-updates/components/RenderHistoryViewer";
124
+
125
+ // Simple JSON export for renders
126
+ export { generateRendersJson } from "./highlight-updates/utils/renderExportFormatter";
127
+
128
+ // e2e handle builders — exported so the example app's fixture can derive the
129
+ // same row handles the tool renders instead of hardcoding a second copy.
130
+ export { identifierSlug, renderRowTestID, detectedItemTestID, patternChipTestID, settingTestID } from "./highlight-updates/utils/testIDs";