catalyst-core 0.2.0-beta.3 → 0.3.0-beta.2

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 (167) hide show
  1. package/babel.native.config.cjs +8 -0
  2. package/bin/catalyst.js +49 -105
  3. package/changelog.md +8 -0
  4. package/dist/caching.js +1 -1
  5. package/dist/globals.d.ts +7 -0
  6. package/dist/index.jsx +8 -0
  7. package/dist/logger.js +74 -3
  8. package/dist/native/androidProject/app/build.gradle.kts +45 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
  12. package/dist/native/androidProject/settings.gradle.kts +10 -0
  13. package/dist/native/androidSetup.js +300 -8
  14. package/dist/native/bridge/WebBridge.js +481 -59
  15. package/dist/native/bridge/constants/NativeInterfaces.js +304 -45
  16. package/dist/native/bridge/errors.js +250 -26
  17. package/dist/native/bridge/hooks/index.js +132 -1
  18. package/dist/native/bridge/hooks/useAI.js +94 -0
  19. package/dist/native/bridge/hooks/useCamera.js +347 -9
  20. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  21. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  22. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  23. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  24. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  25. package/dist/native/bridge/hooks/useIntent.js +95 -1
  26. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  27. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  28. package/dist/native/bridge/hooks/useNotification.js +318 -2
  29. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  30. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  31. package/dist/native/bridge/hooks/utils.js +27 -1
  32. package/dist/native/bridge/hooks.js +16 -1
  33. package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
  34. package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
  35. package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
  36. package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
  37. package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
  38. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
  39. package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
  40. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
  41. package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
  42. package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
  43. package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
  44. package/dist/native/bridge/perf/core/constants.js +103 -20
  45. package/dist/native/bridge/perf/core/measure.js +179 -30
  46. package/dist/native/bridge/perf/core/session.js +89 -23
  47. package/dist/native/bridge/perf/core/store.js +554 -1
  48. package/dist/native/bridge/perf/core/summary.js +174 -1
  49. package/dist/native/bridge/perf/core/traceExport.js +240 -1
  50. package/dist/native/bridge/perf/core/waterfalls.js +146 -1
  51. package/dist/native/bridge/perf/index.js +359 -38
  52. package/dist/native/bridge/safeArea.js +53 -5
  53. package/dist/native/bridge/standardizedHooks.js +595 -82
  54. package/dist/native/bridge/useBaseHook.js +274 -38
  55. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  56. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  57. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  58. package/dist/native/buildAndroid/assets.js +510 -13
  59. package/dist/native/buildAndroid/build.js +296 -19
  60. package/dist/native/buildAndroid/config.js +87 -1
  61. package/dist/native/buildAndroid/index.js +406 -1
  62. package/dist/native/buildAppAndroid.js +34 -2
  63. package/dist/native/buildAppIos.js +62 -2
  64. package/dist/native/buildIos/assets.js +609 -8
  65. package/dist/native/buildIos/build.js +639 -20
  66. package/dist/native/buildIos/config.js +294 -12
  67. package/dist/native/buildIos/index.js +343 -6
  68. package/dist/native/buildIos/plugins.js +343 -16
  69. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  70. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  71. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  72. package/dist/native/internalPluginUtils.js +232 -7
  73. package/dist/native/package.json +3 -0
  74. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  75. package/dist/native/pluginComposerAndroid.js +407 -23
  76. package/dist/native/pluginComposerIos.js +435 -11
  77. package/dist/native/renameAndroidProject.js +1094 -88
  78. package/dist/native/setupEmulatorIos.js +289 -22
  79. package/dist/native/setupServer.js +160 -19
  80. package/dist/native/terminalProgress.js +199 -11
  81. package/dist/native/utils.js +193 -15
  82. package/dist/offline/catalyst-sw.js +145 -1
  83. package/dist/offline/registerServiceWorker.js +33 -1
  84. package/dist/otel.js +375 -46
  85. package/dist/router/ClientRouter.js +14 -2
  86. package/dist/router/ServerRouter.js +9 -1
  87. package/dist/router.js +1 -1
  88. package/dist/scripts/build.js +112 -3
  89. package/dist/scripts/checkVersion.js +3 -1
  90. package/dist/scripts/cleanCache.js +30 -1
  91. package/dist/scripts/generateOfflineManifest.js +116 -1
  92. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  93. package/dist/scripts/plugins.js +309 -1
  94. package/dist/scripts/preServerInit.js +13 -0
  95. package/dist/scripts/scriptUtils.js +54 -3
  96. package/dist/scripts/serve.js +51 -4
  97. package/dist/scripts/start.js +47 -10
  98. package/dist/scripts/validator.js +149 -1
  99. package/dist/sentry.cjs +127 -0
  100. package/dist/sentry.js +125 -5
  101. package/dist/server/expressServer.js +296 -5
  102. package/dist/server/manifestCache.js +36 -0
  103. package/dist/server/renderer/ChunkExtractor.js +144 -0
  104. package/dist/server/renderer/document/Body.jsx +59 -0
  105. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  106. package/dist/server/renderer/document/Head.jsx +60 -0
  107. package/dist/server/renderer/document/index.jsx +2 -0
  108. package/dist/server/renderer/extract.js +149 -31
  109. package/dist/server/renderer/handler.jsx +450 -0
  110. package/dist/server/renderer/index.js +8 -1
  111. package/dist/server/renderer/render.js +47 -0
  112. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  113. package/dist/server/utils/cjsRequire.js +10 -0
  114. package/dist/server/utils/userAgentUtil.js +87 -9
  115. package/dist/server/utils/validator.js +164 -1
  116. package/dist/vite/FastRefresh.jsx +20 -0
  117. package/dist/vite/inject-cache-key-plugin.js +276 -0
  118. package/dist/vite/loadCustomViteConfig.js +22 -0
  119. package/dist/vite/manifest-categorization-plugin.js +455 -0
  120. package/dist/vite/node-loader.mjs +458 -0
  121. package/dist/vite/scssParams.js +21 -0
  122. package/dist/vite/vite.config.client.js +130 -0
  123. package/dist/vite/vite.config.js +498 -0
  124. package/dist/vite/vite.config.server.js +84 -0
  125. package/dist/web-router/components/MetaTag.jsx +34 -0
  126. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  127. package/dist/web-router/components/Split.jsx +200 -0
  128. package/dist/web-router/components/SplitInview.jsx +106 -0
  129. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  130. package/dist/web-router/context.jsx +12 -0
  131. package/dist/web-router/hooks.jsx +72 -0
  132. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  133. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  134. package/dist/web-router/types/context.d.ts +14 -0
  135. package/dist/web-router/types/hooks.d.ts +1 -0
  136. package/dist/web-router/types/index.d.ts +3 -0
  137. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  138. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  139. package/mcp_v2/conversion-tasks.json +35 -17
  140. package/mcp_v2/knowledge-base.json +176 -17
  141. package/mcp_v2/lib/helpers.js +10 -0
  142. package/mcp_v2/mcp.js +40 -8
  143. package/mcp_v2/package.json +1 -0
  144. package/mcp_v2/tools/build.js +161 -37
  145. package/mcp_v2/tools/conversion.js +106 -5
  146. package/mcp_v2/tools/tasks.js +18 -14
  147. package/package.json +35 -68
  148. package/babel.config.js +0 -32
  149. package/dist/index.js +0 -1
  150. package/dist/scripts/devBuild.js +0 -3
  151. package/dist/scripts/devServe.js +0 -3
  152. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  153. package/dist/scripts/registerAliases.js +0 -6
  154. package/dist/server/renderer/document/Body.js +0 -11
  155. package/dist/server/renderer/document/Head.js +0 -9
  156. package/dist/server/renderer/document/index.js +0 -1
  157. package/dist/server/renderer/handler.js +0 -47
  158. package/dist/server/startServer.js +0 -57
  159. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  160. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  161. package/dist/webpack/babel.config.client.js +0 -1
  162. package/dist/webpack/babel.config.ssr.js +0 -1
  163. package/dist/webpack/base.babel.js +0 -7
  164. package/dist/webpack/development.client.babel.js +0 -10
  165. package/dist/webpack/production.client.babel.js +0 -4
  166. package/dist/webpack/production.ssr.babel.js +0 -8
  167. package/dist/webpack/scssParams.js +0 -9
@@ -1,4 +1,12 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.InteractionCollector=void 0;var _constants=require("../core/constants.js");var _session=require("../core/session.js");/**
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.InteractionCollector = void 0;
7
+ var _constants = require("../core/constants.js");
8
+ var _session = require("../core/session.js");
9
+ /**
2
10
  * InteractionCollector.js
3
11
  *
4
12
  * Instruments: bugs #6, #8, #13, #17
@@ -27,12 +35,239 @@
27
35
  * On pointerdown, a 2-second interaction session opens.
28
36
  * RenderCollector queries activeSessionId to tag its spans.
29
37
  * Session closes after SESSION_WINDOW_MS (2s) or next pointerdown, whichever comes first.
30
- */const SESSION_WINDOW_MS=2000;const MAX_RECENT_INTERACTIONS=50;class InteractionCollector{constructor(measure){this._measure=measure;this._pendingTaps=new Map();// pointerId → { startMark, startTime, target }
31
- this._session=new _session.Session(_constants.PREFIX.SESSION_INTERACTION,measure,_constants.TRACK.INTERACTION);this._sessionTimer=null;this._sessionId=null;this._sessionNetworkCalls=0;this._sessionFpsDrop=false;this._activeInteraction=null;this._recentInteractions=[];this._interactionSeq=0;}// ─── Public API — queried by native API and render collectors ──────────────
32
- get activeInteractionId(){return this._activeInteraction?.id??null;}interactionIdForRange(startTime,endTime=startTime){const interactions=this._activeInteraction?[...this._recentInteractions,this._activeInteraction]:this._recentInteractions;return interactions.slice().reverse().find(interaction=>startTime<=(interaction.endTime??performance.now())&&endTime>=interaction.startTime)?.id??null;}/** Called by index.js to wire up insights notifications. */setInsightsSource(fn){this._notifyInsights=fn;}/** Called by index.js to feed the normalized perf store. */setWaterfallSource(fn){this._notifyWaterfall=fn;}/** Called by RenderCollector when an fps-drop-episode fires. */onFpsDrop(){if(this._session.isOpen){this._sessionFpsDrop=true;this._session.set("fpsDrop",true);}}/** Called by NetworkTimingCollector when a fetch/XHR starts during an interaction. */onNetworkCall(){if(this._session.isOpen){this._sessionNetworkCalls++;this._session.set("networkCalls",this._sessionNetworkCalls);}}init(){document.addEventListener("pointerdown",this._onPointerDown.bind(this),{passive:true});document.addEventListener("pointerup",this._onPointerUp.bind(this),{passive:true});document.addEventListener("pointercancel",this._onPointerCancel.bind(this),{passive:true});}_openSession(target,startTime){// Close any existing session immediately — new tap = new session
33
- this._closeSession();this._sessionId=`${Math.round(startTime)}`;this._sessionNetworkCalls=0;this._sessionFpsDrop=false;this._session.open({target,sessionId:this._sessionId,networkCalls:0,fpsDrop:false},startTime);this._notifyWaterfall?.({phase:"start",target,sessionId:this._sessionId,startTime});// Auto-close after 2s
34
- this._sessionTimer=setTimeout(()=>this._closeSession(),SESSION_WINDOW_MS);}_closeSession(){if(this._sessionTimer){clearTimeout(this._sessionTimer);this._sessionTimer=null;}if(this._session.isOpen){const target=this._session.get("target");const startTime=this._session.startTime;const endTime=performance.now();this._session.close({sessionId:this._sessionId,networkCalls:this._sessionNetworkCalls,fpsDrop:this._sessionFpsDrop},endTime,this._sessionFpsDrop?"error":"secondary");this._notifyWaterfall?.({phase:"end",target,sessionId:this._sessionId,networkCalls:this._sessionNetworkCalls,fpsDrop:this._sessionFpsDrop,startTime,endTime,durationMs:Math.round(endTime-startTime)});}this._sessionId=null;}_onPointerDown(e){const startTime=performance.now();const target=this._describeTarget(e.target);const startMark=`${_constants.PREFIX.INTERACTION}:${e.pointerId}:start`;const interactionId=`Tap #${++this._interactionSeq}`;performance.mark(startMark,{startTime});this._activeInteraction={id:interactionId,startTime,endTime:null};this._measure.marker(`${interactionId}: ${target}`,startTime,{interactionId,target},"secondary");// The old 2s parent session
35
- // this._openSession(target, startTime)
36
- // Long-press detection timer
37
- const longPressTimer=setTimeout(()=>{this._emitLongPress(e.pointerId,target,startTime);},_constants.THRESHOLD.LONG_PRESS_MS);this._pendingTaps.set(e.pointerId,{startMark,startTime,target,interactionId,longPressTimer});// Measure tap → next paint (interaction responsiveness)
38
- requestAnimationFrame(()=>{const endTime=performance.now();const tap=this._pendingTaps.get(e.pointerId);if(!tap)return;tap.frameComplete=true;if(tap.released)this._pendingTaps.delete(e.pointerId);this._rememberInteraction(interactionId,startTime,endTime);const responseMs=Math.round(endTime-startTime);if(responseMs>_constants.THRESHOLD.INTERACTION_SLOW_MS){this._notifyInsights?.({target,responseMs,startTime,endTime,interactionId});}this._measure.emit(`${interactionId}: ${target} - ${responseMs}ms`,startMark,endTime,{responseMs,loafOverlap:this._measure.overlapsLoaf(startTime,endTime),target,interactionId,simulatorValid:false},_constants.TRACK.INTERACTION);this._notifyWaterfall?.({phase:"end",target,sessionId:interactionId,networkCalls:0,fpsDrop:false,startTime,endTime,durationMs:responseMs,responseMs,shortInteraction:true});});}_onPointerUp(e){const tap=this._pendingTaps.get(e.pointerId);if(tap){clearTimeout(tap.longPressTimer);tap.released=true;if(tap.frameComplete)this._pendingTaps.delete(e.pointerId);}}_onPointerCancel(e){const tap=this._pendingTaps.get(e.pointerId);if(!tap)return;clearTimeout(tap.longPressTimer);this._pendingTaps.delete(e.pointerId);if(this._activeInteraction?.id===tap.interactionId)this._activeInteraction=null;}_rememberInteraction(id,startTime,endTime){this._recentInteractions.push({id,startTime,endTime});if(this._recentInteractions.length>MAX_RECENT_INTERACTIONS){this._recentInteractions.shift();}if(this._activeInteraction?.id===id)this._activeInteraction=null;}_emitLongPress(pointerId,target,startTime){const tap=this._pendingTaps.get(pointerId);if(!tap)return;const endTime=performance.now();const heldMs=Math.round(endTime-startTime);const markName=`${_constants.PREFIX.LONG_PRESS}:${Math.round(startTime)}`;performance.mark(markName,{startTime});this._measure.emit(`${tap.interactionId}: Long press ${target} - ${heldMs}ms`,markName,endTime,{target,heldMs,interactionId:tap.interactionId,simulatorValid:true},_constants.TRACK.INTERACTION,"tertiary");}_describeTarget(el){if(!el)return"unknown";const tag=el.tagName?.toLowerCase()??"unknown";const id=el.id?`#${el.id}`:"";const cls=el.classList?.length?`.${[...el.classList].slice(0,2).join(".")}`:"";return`${tag}${id}${cls}`.slice(0,40);}}exports.InteractionCollector=InteractionCollector;
38
+ */
39
+
40
+ const SESSION_WINDOW_MS = 2000;
41
+ const MAX_RECENT_INTERACTIONS = 50;
42
+ class InteractionCollector {
43
+ constructor(measure) {
44
+ this._measure = measure;
45
+ this._pendingTaps = new Map(); // pointerId → { startMark, startTime, target }
46
+ this._session = new _session.Session(_constants.PREFIX.SESSION_INTERACTION, measure, _constants.TRACK.INTERACTION);
47
+ this._sessionTimer = null;
48
+ this._sessionId = null;
49
+ this._sessionNetworkCalls = 0;
50
+ this._sessionFpsDrop = false;
51
+ this._activeInteraction = null;
52
+ this._recentInteractions = [];
53
+ this._interactionSeq = 0;
54
+ }
55
+
56
+ // ─── Public API — queried by native API and render collectors ──────────────
57
+
58
+ get activeInteractionId() {
59
+ return this._activeInteraction?.id ?? null;
60
+ }
61
+ interactionIdForRange(startTime, endTime = startTime) {
62
+ const interactions = this._activeInteraction ? [...this._recentInteractions, this._activeInteraction] : this._recentInteractions;
63
+ return interactions.slice().reverse().find(interaction => startTime <= (interaction.endTime ?? performance.now()) && endTime >= interaction.startTime)?.id ?? null;
64
+ }
65
+
66
+ /** Called by index.js to wire up insights notifications. */
67
+ setInsightsSource(fn) {
68
+ this._notifyInsights = fn;
69
+ }
70
+
71
+ /** Called by index.js to feed the normalized perf store. */
72
+ setWaterfallSource(fn) {
73
+ this._notifyWaterfall = fn;
74
+ }
75
+
76
+ /** Called by RenderCollector when an fps-drop-episode fires. */
77
+ onFpsDrop() {
78
+ if (this._session.isOpen) {
79
+ this._sessionFpsDrop = true;
80
+ this._session.set("fpsDrop", true);
81
+ }
82
+ }
83
+
84
+ /** Called by NetworkTimingCollector when a fetch/XHR starts during an interaction. */
85
+ onNetworkCall() {
86
+ if (this._session.isOpen) {
87
+ this._sessionNetworkCalls++;
88
+ this._session.set("networkCalls", this._sessionNetworkCalls);
89
+ }
90
+ }
91
+ init() {
92
+ document.addEventListener("pointerdown", this._onPointerDown.bind(this), {
93
+ passive: true
94
+ });
95
+ document.addEventListener("pointerup", this._onPointerUp.bind(this), {
96
+ passive: true
97
+ });
98
+ document.addEventListener("pointercancel", this._onPointerCancel.bind(this), {
99
+ passive: true
100
+ });
101
+ }
102
+ _openSession(target, startTime) {
103
+ // Close any existing session immediately — new tap = new session
104
+ this._closeSession();
105
+ this._sessionId = `${Math.round(startTime)}`;
106
+ this._sessionNetworkCalls = 0;
107
+ this._sessionFpsDrop = false;
108
+ this._session.open({
109
+ target,
110
+ sessionId: this._sessionId,
111
+ networkCalls: 0,
112
+ fpsDrop: false
113
+ }, startTime);
114
+ this._notifyWaterfall?.({
115
+ phase: "start",
116
+ target,
117
+ sessionId: this._sessionId,
118
+ startTime
119
+ });
120
+
121
+ // Auto-close after 2s
122
+ this._sessionTimer = setTimeout(() => this._closeSession(), SESSION_WINDOW_MS);
123
+ }
124
+ _closeSession() {
125
+ if (this._sessionTimer) {
126
+ clearTimeout(this._sessionTimer);
127
+ this._sessionTimer = null;
128
+ }
129
+ if (this._session.isOpen) {
130
+ const target = this._session.get("target");
131
+ const startTime = this._session.startTime;
132
+ const endTime = performance.now();
133
+ this._session.close({
134
+ sessionId: this._sessionId,
135
+ networkCalls: this._sessionNetworkCalls,
136
+ fpsDrop: this._sessionFpsDrop
137
+ }, endTime, this._sessionFpsDrop ? "error" : "secondary");
138
+ this._notifyWaterfall?.({
139
+ phase: "end",
140
+ target,
141
+ sessionId: this._sessionId,
142
+ networkCalls: this._sessionNetworkCalls,
143
+ fpsDrop: this._sessionFpsDrop,
144
+ startTime,
145
+ endTime,
146
+ durationMs: Math.round(endTime - startTime)
147
+ });
148
+ }
149
+ this._sessionId = null;
150
+ }
151
+ _onPointerDown(e) {
152
+ const startTime = performance.now();
153
+ const target = this._describeTarget(e.target);
154
+ const startMark = `${_constants.PREFIX.INTERACTION}:${e.pointerId}:start`;
155
+ const interactionId = `Tap #${++this._interactionSeq}`;
156
+ performance.mark(startMark, {
157
+ startTime
158
+ });
159
+ this._activeInteraction = {
160
+ id: interactionId,
161
+ startTime,
162
+ endTime: null
163
+ };
164
+ this._measure.marker(`${interactionId}: ${target}`, startTime, {
165
+ interactionId,
166
+ target
167
+ }, "secondary");
168
+
169
+ // The old 2s parent session
170
+ // this._openSession(target, startTime)
171
+
172
+ // Long-press detection timer
173
+ const longPressTimer = setTimeout(() => {
174
+ this._emitLongPress(e.pointerId, target, startTime);
175
+ }, _constants.THRESHOLD.LONG_PRESS_MS);
176
+ this._pendingTaps.set(e.pointerId, {
177
+ startMark,
178
+ startTime,
179
+ target,
180
+ interactionId,
181
+ longPressTimer
182
+ });
183
+
184
+ // Measure tap → next paint (interaction responsiveness)
185
+ requestAnimationFrame(() => {
186
+ const endTime = performance.now();
187
+ const tap = this._pendingTaps.get(e.pointerId);
188
+ if (!tap) return;
189
+ tap.frameComplete = true;
190
+ if (tap.released) this._pendingTaps.delete(e.pointerId);
191
+ this._rememberInteraction(interactionId, startTime, endTime);
192
+ const responseMs = Math.round(endTime - startTime);
193
+ if (responseMs > _constants.THRESHOLD.INTERACTION_SLOW_MS) {
194
+ this._notifyInsights?.({
195
+ target,
196
+ responseMs,
197
+ startTime,
198
+ endTime,
199
+ interactionId
200
+ });
201
+ }
202
+ this._measure.emit(`${interactionId}: ${target} - ${responseMs}ms`, startMark, endTime, {
203
+ responseMs,
204
+ loafOverlap: this._measure.overlapsLoaf(startTime, endTime),
205
+ target,
206
+ interactionId,
207
+ simulatorValid: false
208
+ }, _constants.TRACK.INTERACTION);
209
+ this._notifyWaterfall?.({
210
+ phase: "end",
211
+ target,
212
+ sessionId: interactionId,
213
+ networkCalls: 0,
214
+ fpsDrop: false,
215
+ startTime,
216
+ endTime,
217
+ durationMs: responseMs,
218
+ responseMs,
219
+ shortInteraction: true
220
+ });
221
+ });
222
+ }
223
+ _onPointerUp(e) {
224
+ const tap = this._pendingTaps.get(e.pointerId);
225
+ if (tap) {
226
+ clearTimeout(tap.longPressTimer);
227
+ tap.released = true;
228
+ if (tap.frameComplete) this._pendingTaps.delete(e.pointerId);
229
+ }
230
+ }
231
+ _onPointerCancel(e) {
232
+ const tap = this._pendingTaps.get(e.pointerId);
233
+ if (!tap) return;
234
+ clearTimeout(tap.longPressTimer);
235
+ this._pendingTaps.delete(e.pointerId);
236
+ if (this._activeInteraction?.id === tap.interactionId) this._activeInteraction = null;
237
+ }
238
+ _rememberInteraction(id, startTime, endTime) {
239
+ this._recentInteractions.push({
240
+ id,
241
+ startTime,
242
+ endTime
243
+ });
244
+ if (this._recentInteractions.length > MAX_RECENT_INTERACTIONS) {
245
+ this._recentInteractions.shift();
246
+ }
247
+ if (this._activeInteraction?.id === id) this._activeInteraction = null;
248
+ }
249
+ _emitLongPress(pointerId, target, startTime) {
250
+ const tap = this._pendingTaps.get(pointerId);
251
+ if (!tap) return;
252
+ const endTime = performance.now();
253
+ const heldMs = Math.round(endTime - startTime);
254
+ const markName = `${_constants.PREFIX.LONG_PRESS}:${Math.round(startTime)}`;
255
+ performance.mark(markName, {
256
+ startTime
257
+ });
258
+ this._measure.emit(`${tap.interactionId}: Long press ${target} - ${heldMs}ms`, markName, endTime, {
259
+ target,
260
+ heldMs,
261
+ interactionId: tap.interactionId,
262
+ simulatorValid: true
263
+ }, _constants.TRACK.INTERACTION, "tertiary");
264
+ }
265
+ _describeTarget(el) {
266
+ if (!el) return "unknown";
267
+ const tag = el.tagName?.toLowerCase() ?? "unknown";
268
+ const id = el.id ? `#${el.id}` : "";
269
+ const cls = el.classList?.length ? `.${[...el.classList].slice(0, 2).join(".")}` : "";
270
+ return `${tag}${id}${cls}`.slice(0, 40);
271
+ }
272
+ }
273
+ exports.InteractionCollector = InteractionCollector;
@@ -1,4 +1,12 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.KeyboardCollector=void 0;var _constants=require("../core/constants.js");var _session=require("../core/session.js");/**
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.KeyboardCollector = void 0;
7
+ var _constants = require("../core/constants.js");
8
+ var _session = require("../core/session.js");
9
+ /**
2
10
  * KeyboardCollector.js
3
11
  *
4
12
  * Instruments: bugs #3, #12
@@ -16,10 +24,101 @@
16
24
  * detail: keyboardHeight, viewportDelta, hadScrollDuringSession
17
25
  * catalyst:viewport-resize — visualViewport resize → next rAF (Track: Keyboard)
18
26
  * detail: deltaHeight
19
- */class KeyboardCollector{constructor(measure,nativeToWeb){this._measure=measure;this._nativeToWeb=nativeToWeb;this._session=new _session.Session(_constants.PREFIX.SESSION_KEYBOARD,measure,_constants.TRACK.INPUT);}init(){this._observeVisualViewport();}setWaterfallSource(fn){this._notifyWaterfall=fn;}// ─── Native event handlers ────────────────────────────────────────────────
20
- onKeyboardShow(event){const webNow=this._nativeToWeb(event.nativeTime);this._session.open({keyboardHeight:event.keyboardHeight,hadScroll:false},webNow);}onKeyboardHide(event){const webNow=this._nativeToWeb(event.nativeTime);if(this._session.isOpen){this._closeSession(webNow);}// Emit a point mark so keyboard-hide is visible on the timeline
21
- const hideMark=`catalyst:keyboard-hide:${Math.round(webNow)}`;performance.mark(hideMark,{startTime:webNow});}// Called by ScrollCollector (via index.js) if scroll fires while keyboard open
22
- onScrollDuringKeyboard(){if(this._session.isOpen){this._session.set("hadScroll",true);}}// ─── Viewport resize ──────────────────────────────────────────────────────
23
- // Fires when keyboard pushes/pulls the viewport.
24
- // Also closes the keyboard session — viewport settling = keyboard done.
25
- _observeVisualViewport(){if(!window.visualViewport)return;let lastHeight=window.visualViewport.height;window.visualViewport.addEventListener("resize",()=>{const now=performance.now();const newHeight=window.visualViewport.height;const delta=Math.abs(newHeight-lastHeight);lastHeight=newHeight;if(delta<_constants.THRESHOLD.VIEWPORT_MIN_DELTA)return;const markName=`${_constants.PREFIX.VIEWPORT_RESIZE}:start:${Math.round(now)}`;performance.mark(markName,{startTime:now});requestAnimationFrame(()=>{const endTime=performance.now();const durationMs=Math.round(endTime-now);this._measure.emit(_constants.PREFIX.VIEWPORT_RESIZE,markName,endTime,{deltaHeight:Math.round(delta),simulatorValid:true},_constants.TRACK.INPUT);this._notifyWaterfall?.({metric:"viewport-resize",startTime:now,endTime,durationMs,deltaHeight:Math.round(delta)});if(this._session.isOpen){this._closeSession(endTime);}});});}_closeSession(endTime){const startTime=this._session.startTime;const detail={keyboardHeight:this._session.get("keyboardHeight"),hadScrollDuringSession:this._session.get("hadScroll"),simulatorValid:false};this._session.close(detail,endTime);this._notifyWaterfall?.({kind:"keyboard",startTime,endTime,durationMs:Math.round(endTime-startTime),...detail});}}exports.KeyboardCollector=KeyboardCollector;
27
+ */
28
+
29
+ class KeyboardCollector {
30
+ constructor(measure, nativeToWeb) {
31
+ this._measure = measure;
32
+ this._nativeToWeb = nativeToWeb;
33
+ this._session = new _session.Session(_constants.PREFIX.SESSION_KEYBOARD, measure, _constants.TRACK.INPUT);
34
+ }
35
+ init() {
36
+ this._observeVisualViewport();
37
+ }
38
+ setWaterfallSource(fn) {
39
+ this._notifyWaterfall = fn;
40
+ }
41
+
42
+ // ─── Native event handlers ────────────────────────────────────────────────
43
+
44
+ onKeyboardShow(event) {
45
+ const webNow = this._nativeToWeb(event.nativeTime);
46
+ this._session.open({
47
+ keyboardHeight: event.keyboardHeight,
48
+ hadScroll: false
49
+ }, webNow);
50
+ }
51
+ onKeyboardHide(event) {
52
+ const webNow = this._nativeToWeb(event.nativeTime);
53
+ if (this._session.isOpen) {
54
+ this._closeSession(webNow);
55
+ }
56
+ // Emit a point mark so keyboard-hide is visible on the timeline
57
+ const hideMark = `catalyst:keyboard-hide:${Math.round(webNow)}`;
58
+ performance.mark(hideMark, {
59
+ startTime: webNow
60
+ });
61
+ }
62
+
63
+ // Called by ScrollCollector (via index.js) if scroll fires while keyboard open
64
+ onScrollDuringKeyboard() {
65
+ if (this._session.isOpen) {
66
+ this._session.set("hadScroll", true);
67
+ }
68
+ }
69
+
70
+ // ─── Viewport resize ──────────────────────────────────────────────────────
71
+ // Fires when keyboard pushes/pulls the viewport.
72
+ // Also closes the keyboard session — viewport settling = keyboard done.
73
+
74
+ _observeVisualViewport() {
75
+ if (!window.visualViewport) return;
76
+ let lastHeight = window.visualViewport.height;
77
+ window.visualViewport.addEventListener("resize", () => {
78
+ const now = performance.now();
79
+ const newHeight = window.visualViewport.height;
80
+ const delta = Math.abs(newHeight - lastHeight);
81
+ lastHeight = newHeight;
82
+ if (delta < _constants.THRESHOLD.VIEWPORT_MIN_DELTA) return;
83
+ const markName = `${_constants.PREFIX.VIEWPORT_RESIZE}:start:${Math.round(now)}`;
84
+ performance.mark(markName, {
85
+ startTime: now
86
+ });
87
+ requestAnimationFrame(() => {
88
+ const endTime = performance.now();
89
+ const durationMs = Math.round(endTime - now);
90
+ this._measure.emit(_constants.PREFIX.VIEWPORT_RESIZE, markName, endTime, {
91
+ deltaHeight: Math.round(delta),
92
+ simulatorValid: true
93
+ }, _constants.TRACK.INPUT);
94
+ this._notifyWaterfall?.({
95
+ metric: "viewport-resize",
96
+ startTime: now,
97
+ endTime,
98
+ durationMs,
99
+ deltaHeight: Math.round(delta)
100
+ });
101
+ if (this._session.isOpen) {
102
+ this._closeSession(endTime);
103
+ }
104
+ });
105
+ });
106
+ }
107
+ _closeSession(endTime) {
108
+ const startTime = this._session.startTime;
109
+ const detail = {
110
+ keyboardHeight: this._session.get("keyboardHeight"),
111
+ hadScrollDuringSession: this._session.get("hadScroll"),
112
+ simulatorValid: false
113
+ };
114
+ this._session.close(detail, endTime);
115
+ this._notifyWaterfall?.({
116
+ kind: "keyboard",
117
+ startTime,
118
+ endTime,
119
+ durationMs: Math.round(endTime - startTime),
120
+ ...detail
121
+ });
122
+ }
123
+ }
124
+ exports.KeyboardCollector = KeyboardCollector;