catalyst-core 0.2.0-beta.2 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/babel.native.config.cjs +8 -0
- package/bin/catalyst.js +49 -105
- package/changelog.md +8 -0
- package/dist/caching.js +1 -1
- package/dist/index.jsx +8 -0
- package/dist/logger.js +74 -3
- package/dist/native/androidProject/app/build.gradle.kts +1 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +143 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +26 -6
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +143 -17
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +6 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +26 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +56 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +48 -8
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfDeliveryState.kt +36 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfEventBuffer.kt +289 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/ProfilerNavigationState.kt +13 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ProfilerLifecycleTest.kt +51 -0
- package/dist/native/androidSetup.js +300 -8
- package/dist/native/bridge/WebBridge.js +481 -59
- package/dist/native/bridge/constants/NativeInterfaces.js +290 -44
- package/dist/native/bridge/errors.js +250 -26
- package/dist/native/bridge/hooks/index.js +125 -1
- package/dist/native/bridge/hooks/useCamera.js +347 -9
- package/dist/native/bridge/hooks/useDataProtection.js +200 -2
- package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
- package/dist/native/bridge/hooks/useFilePicker.js +429 -2
- package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
- package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
- package/dist/native/bridge/hooks/useIntent.js +95 -1
- package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
- package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
- package/dist/native/bridge/hooks/useNotification.js +318 -2
- package/dist/native/bridge/hooks/useSafeArea.js +84 -2
- package/dist/native/bridge/hooks/useVideoStream.js +434 -6
- package/dist/native/bridge/hooks/utils.js +27 -1
- package/dist/native/bridge/hooks.js +16 -1
- package/dist/native/bridge/perf/collectors/ApiCollector.js +85 -0
- package/dist/native/bridge/perf/collectors/CacheCollector.js +215 -0
- package/dist/native/bridge/perf/collectors/HookCollector.js +165 -0
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +505 -0
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +273 -0
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +124 -0
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +294 -0
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +183 -0
- package/dist/native/bridge/perf/collectors/RenderCollector.js +284 -0
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +111 -0
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +111 -0
- package/dist/native/bridge/perf/core/constants.js +126 -0
- package/dist/native/bridge/perf/core/measure.js +189 -0
- package/dist/native/bridge/perf/core/session.js +100 -0
- package/dist/native/bridge/perf/core/store.js +554 -0
- package/dist/native/bridge/perf/core/summary.js +174 -0
- package/dist/native/bridge/perf/core/traceExport.js +240 -0
- package/dist/native/bridge/perf/core/waterfalls.js +146 -0
- package/dist/native/bridge/perf/index.js +402 -0
- package/dist/native/bridge/safeArea.js +53 -5
- package/dist/native/bridge/standardizedHooks.js +595 -82
- package/dist/native/bridge/useBaseHook.js +274 -38
- package/dist/native/bridge/utils/CameraUtils.js +201 -56
- package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
- package/dist/native/bridge/utils/NativeBridge.js +425 -141
- package/dist/native/buildAndroid/assets.js +510 -13
- package/dist/native/buildAndroid/build.js +296 -19
- package/dist/native/buildAndroid/config.js +87 -1
- package/dist/native/buildAndroid/index.js +374 -1
- package/dist/native/buildAppAndroid.js +34 -2
- package/dist/native/buildAppIos.js +62 -2
- package/dist/native/buildIos/assets.js +609 -8
- package/dist/native/buildIos/build.js +639 -20
- package/dist/native/buildIos/config.js +294 -12
- package/dist/native/buildIos/index.js +343 -6
- package/dist/native/buildIos/plugins.js +343 -16
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
- package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
- package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
- package/dist/native/internalPluginUtils.js +232 -7
- package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +19 -0
- package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +4 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +5 -1
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +10 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/DisplayLinkPerfMonitor.swift +89 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +373 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +35 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +24 -11
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +9 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +126 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +75 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +13 -0
- package/dist/native/package.json +3 -0
- package/dist/native/plugin-bridge/PluginBridge.js +227 -1
- package/dist/native/pluginComposerAndroid.js +407 -23
- package/dist/native/pluginComposerIos.js +435 -11
- package/dist/native/renameAndroidProject.js +1094 -88
- package/dist/native/setupEmulatorIos.js +289 -22
- package/dist/native/setupServer.js +160 -19
- package/dist/native/terminalProgress.js +199 -11
- package/dist/native/utils.js +193 -15
- package/dist/offline/catalyst-sw.js +145 -1
- package/dist/offline/registerServiceWorker.js +33 -1
- package/dist/otel.js +375 -46
- package/dist/router/ClientRouter.js +14 -2
- package/dist/router/ServerRouter.js +9 -1
- package/dist/router.js +1 -1
- package/dist/scripts/build.js +112 -3
- package/dist/scripts/checkVersion.js +3 -1
- package/dist/scripts/cleanCache.js +30 -1
- package/dist/scripts/generateOfflineManifest.js +116 -1
- package/dist/scripts/loadEnvironmentVariables.js +32 -6
- package/dist/scripts/plugins.js +309 -1
- package/dist/scripts/preServerInit.js +13 -0
- package/dist/scripts/scriptUtils.js +54 -3
- package/dist/scripts/serve.js +51 -4
- package/dist/scripts/start.js +47 -10
- package/dist/scripts/validator.js +149 -1
- package/dist/sentry.cjs +127 -0
- package/dist/sentry.js +125 -5
- package/dist/server/expressServer.js +250 -5
- package/dist/server/manifestCache.js +36 -0
- package/dist/server/renderer/ChunkExtractor.js +144 -0
- package/dist/server/renderer/document/Body.jsx +59 -0
- package/dist/server/renderer/document/FastRefresh.jsx +20 -0
- package/dist/server/renderer/document/Head.jsx +60 -0
- package/dist/server/renderer/document/index.jsx +2 -0
- package/dist/server/renderer/extract.js +149 -31
- package/dist/server/renderer/handler.jsx +433 -0
- package/dist/server/renderer/index.js +8 -1
- package/dist/server/renderer/render.js +47 -0
- package/dist/server/utils/botDetectionMiddleware.js +29 -0
- package/dist/server/utils/cjsRequire.js +10 -0
- package/dist/server/utils/userAgentUtil.js +87 -9
- package/dist/server/utils/validator.js +164 -1
- package/dist/vite/FastRefresh.jsx +20 -0
- package/dist/vite/inject-cache-key-plugin.js +276 -0
- package/dist/vite/loadCustomViteConfig.js +22 -0
- package/dist/vite/manifest-categorization-plugin.js +455 -0
- package/dist/vite/node-loader.mjs +458 -0
- package/dist/vite/scssParams.js +21 -0
- package/dist/vite/vite.config.client.js +130 -0
- package/dist/vite/vite.config.js +422 -0
- package/dist/vite/vite.config.server.js +84 -0
- package/dist/web-router/components/MetaTag.jsx +34 -0
- package/dist/web-router/components/RouterDataProvider.jsx +332 -0
- package/dist/web-router/components/Split.jsx +200 -0
- package/dist/web-router/components/SplitInview.jsx +106 -0
- package/dist/web-router/components/SsrRequestContext.jsx +14 -0
- package/dist/web-router/context.jsx +12 -0
- package/dist/web-router/hooks.jsx +72 -0
- package/dist/web-router/types/components/MetaTag.d.ts +2 -0
- package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
- package/dist/web-router/types/context.d.ts +14 -0
- package/dist/web-router/types/hooks.d.ts +1 -0
- package/dist/web-router/types/index.d.ts +3 -0
- package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +119 -0
- package/mcp_v2/conversion-tasks.json +35 -17
- package/mcp_v2/knowledge-base.json +124 -13
- package/mcp_v2/lib/helpers.js +10 -0
- package/mcp_v2/mcp.js +4 -4
- package/mcp_v2/package.json +1 -0
- package/mcp_v2/tools/build.js +116 -20
- package/mcp_v2/tools/conversion.js +106 -5
- package/mcp_v2/tools/tasks.js +18 -14
- package/package.json +35 -68
- package/babel.config.js +0 -32
- package/dist/index.js +0 -1
- package/dist/scripts/devBuild.js +0 -3
- package/dist/scripts/devServe.js +0 -3
- package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
- package/dist/scripts/registerAliases.js +0 -6
- package/dist/server/renderer/document/Body.js +0 -11
- package/dist/server/renderer/document/Head.js +0 -9
- package/dist/server/renderer/document/index.js +0 -1
- package/dist/server/renderer/handler.js +0 -47
- package/dist/server/startServer.js +0 -57
- package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
- package/dist/webpack/babel.config.client.js +0 -1
- package/dist/webpack/babel.config.ssr.js +0 -1
- package/dist/webpack/base.babel.js +0 -7
- package/dist/webpack/development.client.babel.js +0 -10
- package/dist/webpack/production.client.babel.js +0 -4
- package/dist/webpack/production.ssr.babel.js +0 -8
- package/dist/webpack/scssParams.js +0 -9
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.CacheCollector = void 0;
|
|
7
|
+
var _constants = require("../core/constants.js");
|
|
8
|
+
/**
|
|
9
|
+
* CacheCollector.js
|
|
10
|
+
*
|
|
11
|
+
* Instruments: bugs #1, #2, #10
|
|
12
|
+
* #1 page slowness OTC pages → cache miss → slow fetch → resource blocks render
|
|
13
|
+
* #2 back-forward reloads screen → back navigation triggers a burst of cache misses
|
|
14
|
+
* #10 footer flash on Drug/OTC → cache miss for footer chunk → layout shift after load
|
|
15
|
+
*
|
|
16
|
+
* Native events received in two modes:
|
|
17
|
+
*
|
|
18
|
+
* 1. Live (legacy path — only used if buffer disabled):
|
|
19
|
+
* cache-hit-memory / cache-hit-disk / cache-miss-fetch / network-fetch-complete
|
|
20
|
+
* → Each emits immediately to Catalyst > Cache track via onNativeEvent()
|
|
21
|
+
*
|
|
22
|
+
* 2. Batched (primary path via __catalystPerfBatch drain in index.js):
|
|
23
|
+
* Same event types arrive as part of the native buffer flush after onPageFinished.
|
|
24
|
+
* JS drains via requestIdleCallback → onBatchEvent() → emits spans on Catalyst > Cache > Detail
|
|
25
|
+
*
|
|
26
|
+
* cache-summary { hits, misses, fetches, total, hitRatePct, avgFetchMs, topSlowest[] }
|
|
27
|
+
* → One summary span emitted on main Catalyst > Cache track for InsightsCollector
|
|
28
|
+
*
|
|
29
|
+
* Spans emitted:
|
|
30
|
+
* Catalyst > Cache > Detail individual spans (one per file) during idle drain
|
|
31
|
+
* Catalyst > Cache summary span (hit rate, avg fetch, top slowest)
|
|
32
|
+
*
|
|
33
|
+
* Reading the track:
|
|
34
|
+
* Blue = cache HIT memory — fastest path
|
|
35
|
+
* Blue-light = cache HIT disk — good, slightly slower
|
|
36
|
+
* Red = cache MISS — matched pattern but not cached
|
|
37
|
+
* Yellow/Orange = network fetch — orange if slow >300ms
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const TRACK_CACHE_DETAIL = "Cache > Detail";
|
|
41
|
+
class CacheCollector {
|
|
42
|
+
constructor(measure, nativeToWeb) {
|
|
43
|
+
this._measure = measure;
|
|
44
|
+
this._nativeToWeb = nativeToWeb;
|
|
45
|
+
this._notifyInsights = null; // set by index.js after construction
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Called by index.js to wire up insights notifications for cache-summary. */
|
|
49
|
+
setInsightsSource(fn) {
|
|
50
|
+
this._notifyInsights = fn;
|
|
51
|
+
}
|
|
52
|
+
init() {
|
|
53
|
+
// No web-side observers — all events come from native
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Live path (individual events routed from index.js) ──────────────────
|
|
57
|
+
|
|
58
|
+
onNativeEvent(event) {
|
|
59
|
+
const {
|
|
60
|
+
type
|
|
61
|
+
} = event;
|
|
62
|
+
if (!type?.startsWith("cache-") && type !== "network-fetch-complete") return;
|
|
63
|
+
this._emitCacheSpan(event, _constants.TRACK.CACHE);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Batch path (native buffer flush) ────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
onBatchEvent(event) {
|
|
69
|
+
const {
|
|
70
|
+
type
|
|
71
|
+
} = event;
|
|
72
|
+
if (type === "cache-summary") {
|
|
73
|
+
this._emitSummarySpan(event);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!type?.startsWith("cache-") && type !== "network-fetch-complete") return;
|
|
77
|
+
// Individual cache spans go on the Detail sub-track
|
|
78
|
+
this._emitCacheSpan(event, TRACK_CACHE_DETAIL);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ── Internal ─────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
_emitCacheSpan(event, track) {
|
|
84
|
+
const {
|
|
85
|
+
type,
|
|
86
|
+
url,
|
|
87
|
+
durationMs,
|
|
88
|
+
nativeStartMs,
|
|
89
|
+
resourceType,
|
|
90
|
+
thread,
|
|
91
|
+
statusCode
|
|
92
|
+
} = event;
|
|
93
|
+
const resType = resourceType ?? "other";
|
|
94
|
+
const filename = this._filename(url);
|
|
95
|
+
const endTime = nativeStartMs != null ? this._nativeToWeb(nativeStartMs) + (durationMs ?? 0) : performance.now();
|
|
96
|
+
const startTime = endTime - (durationMs ?? 0);
|
|
97
|
+
let measureName, color, detail;
|
|
98
|
+
const interceptThread = event.interceptThread ?? null;
|
|
99
|
+
const emitThread = event.emitThread ?? null;
|
|
100
|
+
if (type === "cache-hit-memory") {
|
|
101
|
+
measureName = `${_constants.PREFIX.CACHE}/hit|memory|${resType}|${filename}`;
|
|
102
|
+
color = _constants.COLOR.PRIMARY;
|
|
103
|
+
detail = {
|
|
104
|
+
url,
|
|
105
|
+
source: "memory",
|
|
106
|
+
resourceType: resType,
|
|
107
|
+
durationMs,
|
|
108
|
+
thread: thread ?? null,
|
|
109
|
+
interceptThread,
|
|
110
|
+
emitThread,
|
|
111
|
+
simulatorValid: false
|
|
112
|
+
};
|
|
113
|
+
} else if (type === "cache-hit-disk") {
|
|
114
|
+
measureName = `${_constants.PREFIX.CACHE}/hit|disk|${resType}|${filename}`;
|
|
115
|
+
color = _constants.COLOR.PRIMARY_LIGHT;
|
|
116
|
+
detail = {
|
|
117
|
+
url,
|
|
118
|
+
source: "disk",
|
|
119
|
+
resourceType: resType,
|
|
120
|
+
durationMs,
|
|
121
|
+
thread: thread ?? null,
|
|
122
|
+
interceptThread,
|
|
123
|
+
emitThread,
|
|
124
|
+
simulatorValid: false
|
|
125
|
+
};
|
|
126
|
+
} else if (type === "cache-miss-fetch") {
|
|
127
|
+
measureName = `${_constants.PREFIX.CACHE}/miss|${resType}|${filename}`;
|
|
128
|
+
color = _constants.COLOR.ERROR;
|
|
129
|
+
detail = {
|
|
130
|
+
url,
|
|
131
|
+
resourceType: resType,
|
|
132
|
+
durationMs,
|
|
133
|
+
thread: thread ?? null,
|
|
134
|
+
interceptThread,
|
|
135
|
+
emitThread,
|
|
136
|
+
simulatorValid: false
|
|
137
|
+
};
|
|
138
|
+
} else if (type === "network-fetch-complete") {
|
|
139
|
+
const slow = (durationMs ?? 0) > _constants.THRESHOLD.FETCH_SLOW_MS;
|
|
140
|
+
measureName = `${_constants.PREFIX.CACHE}/fetch|${resType}|${filename}`;
|
|
141
|
+
color = slow ? _constants.COLOR.TERTIARY_DARK : _constants.COLOR.TERTIARY;
|
|
142
|
+
detail = {
|
|
143
|
+
url,
|
|
144
|
+
resourceType: resType,
|
|
145
|
+
durationMs,
|
|
146
|
+
statusCode: statusCode ?? null,
|
|
147
|
+
thread: thread ?? null,
|
|
148
|
+
emitThread,
|
|
149
|
+
slow,
|
|
150
|
+
simulatorValid: false
|
|
151
|
+
};
|
|
152
|
+
} else {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const startMark = `${measureName}:start:${Math.round(startTime)}`;
|
|
156
|
+
performance.mark(startMark, {
|
|
157
|
+
startTime
|
|
158
|
+
});
|
|
159
|
+
this._measure.emit(measureName, startMark, endTime, detail, track, color);
|
|
160
|
+
}
|
|
161
|
+
_emitSummarySpan(event) {
|
|
162
|
+
const {
|
|
163
|
+
hits,
|
|
164
|
+
misses,
|
|
165
|
+
fetches,
|
|
166
|
+
total,
|
|
167
|
+
hitRatePct,
|
|
168
|
+
avgFetchMs,
|
|
169
|
+
topSlowest,
|
|
170
|
+
nativeTime
|
|
171
|
+
} = event;
|
|
172
|
+
if (total === 0) return;
|
|
173
|
+
|
|
174
|
+
// Place summary span at current time (post-load, idle phase)
|
|
175
|
+
const startTime = this._nativeToWeb(nativeTime ?? 0) || performance.now();
|
|
176
|
+
const endTime = startTime + 1; // point span
|
|
177
|
+
|
|
178
|
+
const color = hitRatePct >= 80 ? _constants.COLOR.PRIMARY_DARK : hitRatePct >= 50 ? _constants.COLOR.SECONDARY : _constants.COLOR.ERROR;
|
|
179
|
+
const topNames = Array.isArray(topSlowest) ? topSlowest.map(e => `${e.filename}(${e.durationMs}ms)`).join(", ") : "";
|
|
180
|
+
const measureName = `${_constants.PREFIX.CACHE}/summary`;
|
|
181
|
+
const startMark = `${measureName}:start:${Math.round(startTime)}`;
|
|
182
|
+
performance.mark(startMark, {
|
|
183
|
+
startTime
|
|
184
|
+
});
|
|
185
|
+
this._measure.emit(measureName, startMark, endTime, {
|
|
186
|
+
hits,
|
|
187
|
+
misses,
|
|
188
|
+
fetches,
|
|
189
|
+
total,
|
|
190
|
+
hitRatePct,
|
|
191
|
+
avgFetchMs,
|
|
192
|
+
topSlowest: topNames,
|
|
193
|
+
simulatorValid: false
|
|
194
|
+
}, _constants.TRACK.CACHE, color);
|
|
195
|
+
|
|
196
|
+
// Notify InsightsCollector so it can flag low hit rates
|
|
197
|
+
this._notifyInsights?.({
|
|
198
|
+
hitRatePct,
|
|
199
|
+
total,
|
|
200
|
+
avgFetchMs,
|
|
201
|
+
startTime,
|
|
202
|
+
endTime
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
_filename(url) {
|
|
206
|
+
try {
|
|
207
|
+
const path = new URL(url).pathname;
|
|
208
|
+
const name = path.split("/").filter(Boolean).pop() ?? path;
|
|
209
|
+
return name.slice(0, 40);
|
|
210
|
+
} catch {
|
|
211
|
+
return String(url).slice(0, 40);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.CacheCollector = CacheCollector;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.HookCollector = void 0;
|
|
7
|
+
var _constants = require("../core/constants.js");
|
|
8
|
+
/**
|
|
9
|
+
* HookCollector.js — Option A (symptom detection only)
|
|
10
|
+
*
|
|
11
|
+
* Instruments: bugs #13, #15, #16
|
|
12
|
+
* #13 cart computation loop jank → LoAF bursts sustained over time, not tied to a single interaction
|
|
13
|
+
* detected as: LoAF count > threshold within a rolling window
|
|
14
|
+
* #15 quantity selector slow first → interaction → first LoAF burst → long gap → paint
|
|
15
|
+
* detected as: LoAF fires within LAZY_LOAF_WINDOW_MS after tap
|
|
16
|
+
* #16 prescription screen touch lag → same pattern as #15
|
|
17
|
+
*
|
|
18
|
+
* Option A strategy (no React hooks needed):
|
|
19
|
+
* - Watch for LoAF bursts within 2s of a pointerdown (taps that might trigger lazy load)
|
|
20
|
+
* - If LoAF count > 0 in that window → emit hook/lazy span showing the blocked period
|
|
21
|
+
* - Sustained LoAF (3+ frames in 5s with no user interaction) → emit hook/mount span
|
|
22
|
+
* flagging a background computation loop
|
|
23
|
+
*
|
|
24
|
+
* Spans emitted:
|
|
25
|
+
* catalyst:hook/lazy|<label> — LoAF burst after interaction (Track: Hook)
|
|
26
|
+
* detail: loafCount, totalBlockingMs, triggerTarget
|
|
27
|
+
* catalyst:hook/mount|loop — sustained background LoAF (Track: Hook)
|
|
28
|
+
* detail: loafCount, windowMs, likelyCause='computation-loop'
|
|
29
|
+
*
|
|
30
|
+
* TODO (Option B — future):
|
|
31
|
+
* Expose window.catalyst.markComponentMount(name) / markComponentReady(name)
|
|
32
|
+
* for precise hook-lifecycle spans without symptom inference.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const LOOP_LOAF_COUNT = 3; // ≥3 LoAFs in the loop window = computation loop
|
|
36
|
+
const LOOP_WINDOW_MS = 5000; // rolling window to count sustained LoAFs
|
|
37
|
+
|
|
38
|
+
class HookCollector {
|
|
39
|
+
constructor(measure) {
|
|
40
|
+
this._measure = measure;
|
|
41
|
+
|
|
42
|
+
// Lazy-load detection state
|
|
43
|
+
this._pendingLazy = null; // { startMark, startTime, target, loafCount, totalBlocking, timer }
|
|
44
|
+
|
|
45
|
+
// Computation loop detection state
|
|
46
|
+
this._loopLoafs = []; // [{ start, end }] in rolling LOOP_WINDOW_MS window
|
|
47
|
+
}
|
|
48
|
+
init() {
|
|
49
|
+
// Watch for taps that might trigger lazy component loads
|
|
50
|
+
document.addEventListener("pointerdown", this._onPointerDown.bind(this), {
|
|
51
|
+
passive: true
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Called by index.js to feed the normalized perf store. */
|
|
56
|
+
setWaterfallSource(fn) {
|
|
57
|
+
this._notifyWaterfall = fn;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Called by RenderCollector via addLoafListener() on every LoAF
|
|
61
|
+
onLoaf(start, end) {
|
|
62
|
+
// ── Lazy-load window ────────────────────────────────────────────────
|
|
63
|
+
if (this._pendingLazy) {
|
|
64
|
+
const p = this._pendingLazy;
|
|
65
|
+
p.loafCount++;
|
|
66
|
+
p.totalBlocking += end - start;
|
|
67
|
+
|
|
68
|
+
// Reset the close timer — extend window on each LoAF
|
|
69
|
+
clearTimeout(p.timer);
|
|
70
|
+
p.timer = setTimeout(() => this._closeLazySpan(), _constants.THRESHOLD.LAZY_LOAF_WINDOW_MS);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Computation loop detection ───────────────────────────────────────
|
|
74
|
+
const now = performance.now();
|
|
75
|
+
this._loopLoafs.push({
|
|
76
|
+
start,
|
|
77
|
+
end
|
|
78
|
+
});
|
|
79
|
+
// Evict entries outside the rolling window
|
|
80
|
+
this._loopLoafs = this._loopLoafs.filter(e => now - e.start < LOOP_WINDOW_MS);
|
|
81
|
+
if (this._loopLoafs.length >= LOOP_LOAF_COUNT) {
|
|
82
|
+
this._emitLoopSpan();
|
|
83
|
+
this._loopLoafs = []; // reset after emitting to avoid duplicate spans
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Lazy-load span ───────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
_onPointerDown(e) {
|
|
90
|
+
// If a lazy span is already open, let it close naturally
|
|
91
|
+
if (this._pendingLazy) return;
|
|
92
|
+
const target = e.target?.tagName?.toLowerCase() ?? "unknown";
|
|
93
|
+
const startTime = performance.now();
|
|
94
|
+
const startMark = `${_constants.PREFIX.HOOK_LAZY}:start:${Math.round(startTime)}`;
|
|
95
|
+
performance.mark(startMark, {
|
|
96
|
+
startTime
|
|
97
|
+
});
|
|
98
|
+
const timer = setTimeout(() => this._closeLazySpan(), _constants.THRESHOLD.LAZY_LOAF_WINDOW_MS);
|
|
99
|
+
this._pendingLazy = {
|
|
100
|
+
startMark,
|
|
101
|
+
startTime,
|
|
102
|
+
target,
|
|
103
|
+
loafCount: 0,
|
|
104
|
+
totalBlocking: 0,
|
|
105
|
+
timer
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
_closeLazySpan() {
|
|
109
|
+
const p = this._pendingLazy;
|
|
110
|
+
if (!p) return;
|
|
111
|
+
this._pendingLazy = null;
|
|
112
|
+
clearTimeout(p.timer);
|
|
113
|
+
|
|
114
|
+
// Only emit if LoAF actually fired — otherwise it was a clean interaction
|
|
115
|
+
if (p.loafCount === 0) return;
|
|
116
|
+
const endTime = performance.now();
|
|
117
|
+
this._measure.emit(`${_constants.PREFIX.HOOK_LAZY}|${p.target}`, p.startMark, endTime, {
|
|
118
|
+
loafCount: p.loafCount,
|
|
119
|
+
totalBlockingMs: Math.round(p.totalBlocking),
|
|
120
|
+
triggerTarget: p.target,
|
|
121
|
+
likelyCause: "lazy-load",
|
|
122
|
+
simulatorValid: false
|
|
123
|
+
}, _constants.TRACK.HOOK);
|
|
124
|
+
this._notifyWaterfall?.({
|
|
125
|
+
hookType: "lazy-load",
|
|
126
|
+
label: `Hook lazy-load ${p.target}`,
|
|
127
|
+
startTime: p.startTime,
|
|
128
|
+
endTime,
|
|
129
|
+
durationMs: Math.round(endTime - p.startTime),
|
|
130
|
+
loafCount: p.loafCount,
|
|
131
|
+
totalBlockingMs: Math.round(p.totalBlocking),
|
|
132
|
+
likelyCause: "lazy-load"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── Computation loop span ────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
_emitLoopSpan() {
|
|
139
|
+
const first = this._loopLoafs[0];
|
|
140
|
+
const last = this._loopLoafs[this._loopLoafs.length - 1];
|
|
141
|
+
const markName = `${_constants.PREFIX.HOOK_MOUNT}:loop:${Math.round(first.start)}`;
|
|
142
|
+
performance.mark(markName, {
|
|
143
|
+
startTime: first.start
|
|
144
|
+
});
|
|
145
|
+
const totalBlocking = this._loopLoafs.reduce((sum, e) => sum + (e.end - e.start), 0);
|
|
146
|
+
this._measure.emit(`${_constants.PREFIX.HOOK_MOUNT}|loop`, markName, last.end, {
|
|
147
|
+
loafCount: this._loopLoafs.length,
|
|
148
|
+
windowMs: Math.round(last.end - first.start),
|
|
149
|
+
totalBlockingMs: Math.round(totalBlocking),
|
|
150
|
+
likelyCause: "computation-loop",
|
|
151
|
+
simulatorValid: false
|
|
152
|
+
}, _constants.TRACK.HOOK);
|
|
153
|
+
this._notifyWaterfall?.({
|
|
154
|
+
hookType: "computation-loop",
|
|
155
|
+
label: "Hook computation loop",
|
|
156
|
+
startTime: first.start,
|
|
157
|
+
endTime: last.end,
|
|
158
|
+
durationMs: Math.round(last.end - first.start),
|
|
159
|
+
loafCount: this._loopLoafs.length,
|
|
160
|
+
totalBlockingMs: Math.round(totalBlocking),
|
|
161
|
+
likelyCause: "computation-loop"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.HookCollector = HookCollector;
|