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.
Files changed (184) 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/index.jsx +8 -0
  6. package/dist/logger.js +74 -3
  7. package/dist/native/androidProject/app/build.gradle.kts +1 -0
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +143 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +26 -6
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +143 -17
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +6 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +26 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +56 -1
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +48 -8
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfDeliveryState.kt +36 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfEventBuffer.kt +289 -0
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/ProfilerNavigationState.kt +13 -0
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ProfilerLifecycleTest.kt +51 -0
  19. package/dist/native/androidSetup.js +300 -8
  20. package/dist/native/bridge/WebBridge.js +481 -59
  21. package/dist/native/bridge/constants/NativeInterfaces.js +290 -44
  22. package/dist/native/bridge/errors.js +250 -26
  23. package/dist/native/bridge/hooks/index.js +125 -1
  24. package/dist/native/bridge/hooks/useCamera.js +347 -9
  25. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  26. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  27. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  28. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  29. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  30. package/dist/native/bridge/hooks/useIntent.js +95 -1
  31. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  32. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  33. package/dist/native/bridge/hooks/useNotification.js +318 -2
  34. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  35. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  36. package/dist/native/bridge/hooks/utils.js +27 -1
  37. package/dist/native/bridge/hooks.js +16 -1
  38. package/dist/native/bridge/perf/collectors/ApiCollector.js +85 -0
  39. package/dist/native/bridge/perf/collectors/CacheCollector.js +215 -0
  40. package/dist/native/bridge/perf/collectors/HookCollector.js +165 -0
  41. package/dist/native/bridge/perf/collectors/InsightsCollector.js +505 -0
  42. package/dist/native/bridge/perf/collectors/InteractionCollector.js +273 -0
  43. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +124 -0
  44. package/dist/native/bridge/perf/collectors/NavigationCollector.js +294 -0
  45. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +183 -0
  46. package/dist/native/bridge/perf/collectors/RenderCollector.js +284 -0
  47. package/dist/native/bridge/perf/collectors/ScrollCollector.js +111 -0
  48. package/dist/native/bridge/perf/collectors/ThreadCollector.js +111 -0
  49. package/dist/native/bridge/perf/core/constants.js +126 -0
  50. package/dist/native/bridge/perf/core/measure.js +189 -0
  51. package/dist/native/bridge/perf/core/session.js +100 -0
  52. package/dist/native/bridge/perf/core/store.js +554 -0
  53. package/dist/native/bridge/perf/core/summary.js +174 -0
  54. package/dist/native/bridge/perf/core/traceExport.js +240 -0
  55. package/dist/native/bridge/perf/core/waterfalls.js +146 -0
  56. package/dist/native/bridge/perf/index.js +402 -0
  57. package/dist/native/bridge/safeArea.js +53 -5
  58. package/dist/native/bridge/standardizedHooks.js +595 -82
  59. package/dist/native/bridge/useBaseHook.js +274 -38
  60. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  61. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  62. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  63. package/dist/native/buildAndroid/assets.js +510 -13
  64. package/dist/native/buildAndroid/build.js +296 -19
  65. package/dist/native/buildAndroid/config.js +87 -1
  66. package/dist/native/buildAndroid/index.js +374 -1
  67. package/dist/native/buildAppAndroid.js +34 -2
  68. package/dist/native/buildAppIos.js +62 -2
  69. package/dist/native/buildIos/assets.js +609 -8
  70. package/dist/native/buildIos/build.js +639 -20
  71. package/dist/native/buildIos/config.js +294 -12
  72. package/dist/native/buildIos/index.js +343 -6
  73. package/dist/native/buildIos/plugins.js +343 -16
  74. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  75. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  76. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  77. package/dist/native/internalPluginUtils.js +232 -7
  78. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +19 -0
  79. package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +4 -0
  80. package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +5 -1
  81. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +10 -0
  82. package/dist/native/iosnativeWebView/Sources/Core/Utils/DisplayLinkPerfMonitor.swift +89 -0
  83. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +373 -0
  84. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +35 -1
  85. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +24 -11
  86. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +9 -0
  87. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +126 -0
  88. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +75 -1
  89. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +13 -0
  90. package/dist/native/package.json +3 -0
  91. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  92. package/dist/native/pluginComposerAndroid.js +407 -23
  93. package/dist/native/pluginComposerIos.js +435 -11
  94. package/dist/native/renameAndroidProject.js +1094 -88
  95. package/dist/native/setupEmulatorIos.js +289 -22
  96. package/dist/native/setupServer.js +160 -19
  97. package/dist/native/terminalProgress.js +199 -11
  98. package/dist/native/utils.js +193 -15
  99. package/dist/offline/catalyst-sw.js +145 -1
  100. package/dist/offline/registerServiceWorker.js +33 -1
  101. package/dist/otel.js +375 -46
  102. package/dist/router/ClientRouter.js +14 -2
  103. package/dist/router/ServerRouter.js +9 -1
  104. package/dist/router.js +1 -1
  105. package/dist/scripts/build.js +112 -3
  106. package/dist/scripts/checkVersion.js +3 -1
  107. package/dist/scripts/cleanCache.js +30 -1
  108. package/dist/scripts/generateOfflineManifest.js +116 -1
  109. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  110. package/dist/scripts/plugins.js +309 -1
  111. package/dist/scripts/preServerInit.js +13 -0
  112. package/dist/scripts/scriptUtils.js +54 -3
  113. package/dist/scripts/serve.js +51 -4
  114. package/dist/scripts/start.js +47 -10
  115. package/dist/scripts/validator.js +149 -1
  116. package/dist/sentry.cjs +127 -0
  117. package/dist/sentry.js +125 -5
  118. package/dist/server/expressServer.js +250 -5
  119. package/dist/server/manifestCache.js +36 -0
  120. package/dist/server/renderer/ChunkExtractor.js +144 -0
  121. package/dist/server/renderer/document/Body.jsx +59 -0
  122. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  123. package/dist/server/renderer/document/Head.jsx +60 -0
  124. package/dist/server/renderer/document/index.jsx +2 -0
  125. package/dist/server/renderer/extract.js +149 -31
  126. package/dist/server/renderer/handler.jsx +433 -0
  127. package/dist/server/renderer/index.js +8 -1
  128. package/dist/server/renderer/render.js +47 -0
  129. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  130. package/dist/server/utils/cjsRequire.js +10 -0
  131. package/dist/server/utils/userAgentUtil.js +87 -9
  132. package/dist/server/utils/validator.js +164 -1
  133. package/dist/vite/FastRefresh.jsx +20 -0
  134. package/dist/vite/inject-cache-key-plugin.js +276 -0
  135. package/dist/vite/loadCustomViteConfig.js +22 -0
  136. package/dist/vite/manifest-categorization-plugin.js +455 -0
  137. package/dist/vite/node-loader.mjs +458 -0
  138. package/dist/vite/scssParams.js +21 -0
  139. package/dist/vite/vite.config.client.js +130 -0
  140. package/dist/vite/vite.config.js +422 -0
  141. package/dist/vite/vite.config.server.js +84 -0
  142. package/dist/web-router/components/MetaTag.jsx +34 -0
  143. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  144. package/dist/web-router/components/Split.jsx +200 -0
  145. package/dist/web-router/components/SplitInview.jsx +106 -0
  146. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  147. package/dist/web-router/context.jsx +12 -0
  148. package/dist/web-router/hooks.jsx +72 -0
  149. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  150. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  151. package/dist/web-router/types/context.d.ts +14 -0
  152. package/dist/web-router/types/hooks.d.ts +1 -0
  153. package/dist/web-router/types/index.d.ts +3 -0
  154. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  155. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  156. package/mcp_v2/conversion-tasks.json +35 -17
  157. package/mcp_v2/knowledge-base.json +124 -13
  158. package/mcp_v2/lib/helpers.js +10 -0
  159. package/mcp_v2/mcp.js +4 -4
  160. package/mcp_v2/package.json +1 -0
  161. package/mcp_v2/tools/build.js +116 -20
  162. package/mcp_v2/tools/conversion.js +106 -5
  163. package/mcp_v2/tools/tasks.js +18 -14
  164. package/package.json +35 -68
  165. package/babel.config.js +0 -32
  166. package/dist/index.js +0 -1
  167. package/dist/scripts/devBuild.js +0 -3
  168. package/dist/scripts/devServe.js +0 -3
  169. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  170. package/dist/scripts/registerAliases.js +0 -6
  171. package/dist/server/renderer/document/Body.js +0 -11
  172. package/dist/server/renderer/document/Head.js +0 -9
  173. package/dist/server/renderer/document/index.js +0 -1
  174. package/dist/server/renderer/handler.js +0 -47
  175. package/dist/server/startServer.js +0 -57
  176. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  177. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  178. package/dist/webpack/babel.config.client.js +0 -1
  179. package/dist/webpack/babel.config.ssr.js +0 -1
  180. package/dist/webpack/base.babel.js +0 -7
  181. package/dist/webpack/development.client.babel.js +0 -10
  182. package/dist/webpack/production.client.babel.js +0 -4
  183. package/dist/webpack/production.ssr.babel.js +0 -8
  184. package/dist/webpack/scssParams.js +0 -9
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TRACK_GROUP = exports.TRACK = exports.THRESHOLD = exports.PREFIX = exports.COLOR = void 0;
7
+ /**
8
+ * constants.js
9
+ *
10
+ * Single source of truth for track names, thresholds, and color rules.
11
+ * All collectors import from here — no magic strings scattered across files.
12
+ */
13
+
14
+ // ─── Chrome DevTools track group ─────────────────────────────────────────────
15
+
16
+ const TRACK_GROUP = exports.TRACK_GROUP = "Catalyst";
17
+ const TRACK = exports.TRACK = {
18
+ NAVIGATION: "Navigation",
19
+ SCROLL: "Scroll",
20
+ INPUT: "Input",
21
+ KEYBOARD: "Keyboard",
22
+ // alias kept for backward compat — collectors use TRACK.INPUT
23
+ RENDER: "Render",
24
+ MEMORY: "Memory",
25
+ CACHE: "Cache",
26
+ NETWORK: "Network",
27
+ HOOK: "Hook",
28
+ INTERACTION: "Interaction",
29
+ INSIGHTS: "Insights",
30
+ NATIVE_API: "Native API"
31
+ };
32
+
33
+ // ─── Measure name prefixes ────────────────────────────────────────────────────
34
+
35
+ const PREFIX = exports.PREFIX = {
36
+ SESSION_NAV: "catalyst:session/navigation",
37
+ SESSION_SCROLL: "catalyst:session/scroll",
38
+ SESSION_INPUT: "catalyst:session/input",
39
+ SESSION_KEYBOARD: "catalyst:session/keyboard",
40
+ // alias — KeyboardCollector still uses this
41
+ LCP: "catalyst:lcp",
42
+ FCP: "catalyst:fcp",
43
+ LOAF: "catalyst:loaf",
44
+ LAYOUT_SHIFT: "catalyst:layout-shift",
45
+ VIEWPORT_RESIZE: "catalyst:viewport-resize",
46
+ ROUTE_TRANSITION: "catalyst:route-transition",
47
+ CACHE: "catalyst:cache",
48
+ CACHE_GROUP: "catalyst:cache-group",
49
+ NETWORK_TIMING: "catalyst:network",
50
+ NATIVE_API: "catalyst:native-api",
51
+ HOOK_MOUNT: "catalyst:hook/mount",
52
+ HOOK_LAZY: "catalyst:hook/lazy",
53
+ INTERACTION: "catalyst:interaction",
54
+ SESSION_INTERACTION: "catalyst:session/interaction",
55
+ LONG_PRESS: "catalyst:long-press",
56
+ INSIGHT: "catalyst:insight",
57
+ HW_ACCEL: "catalyst:hw-accel",
58
+ // Native MetricsMonitor events
59
+ PAGE_LOAD: "catalyst:page-load",
60
+ COLD_START: "catalyst:cold-start",
61
+ FPS_DROP: "catalyst:fps-drop",
62
+ LONG_TASK: "catalyst:long-task",
63
+ MEMORY_JVM: "catalyst:mem/jvm",
64
+ MEMORY_NATIVE: "catalyst:mem/native",
65
+ MEMORY_TOTAL: "catalyst:mem/total"
66
+ };
67
+
68
+ // ─── Thresholds ───────────────────────────────────────────────────────────────
69
+
70
+ const THRESHOLD = exports.THRESHOLD = {
71
+ LCP_BAD_MS: 2500,
72
+ // LCP > 2.5s = poor (Core Web Vitals)
73
+ NAV_SESSION_BAD_MS: 2500,
74
+ // navigation session > 2.5s = slow page
75
+ FETCH_SLOW_MS: 300,
76
+ // network fetch > 300ms = slow
77
+ SCROLL_IDLE_MS: 150,
78
+ // scroll ends after 150ms of no scroll events
79
+ VIEWPORT_MIN_DELTA: 50,
80
+ // ignore viewport resize < 50px (not keyboard)
81
+ CACHE_GROUP_IDLE_MS: 300,
82
+ // close cache group after 300ms idle
83
+ LOAF_WINDOW: 5,
84
+ // keep last N LoAF entries for overlap detection
85
+ LONG_PRESS_MS: 500,
86
+ // pointerdown held > 500ms = long press
87
+ INTERACTION_SLOW_MS: 100,
88
+ // tap → paint > 100ms = slow response
89
+ LAZY_LOAF_WINDOW_MS: 2000,
90
+ // watch for LoAF bursts up to 2s after interaction
91
+ CLS_BAD: 0.1 // CLS > 0.1 per entry = significant shift
92
+ };
93
+
94
+ // ─── Colors ───────────────────────────────────────────────────────────────────
95
+ // Chrome DevTools Extensibility API color values (all 9 values):
96
+ //
97
+ // primary / primary-light / primary-dark → blue tones
98
+ // secondary / secondary-light / secondary-dark → purple/grey tones
99
+ // tertiary / tertiary-light / tertiary-dark → yellow/orange tones
100
+ // error → red
101
+ //
102
+ // Usage guide:
103
+ // error = bad outcome (LoAF, miss, slow native API, memory critical)
104
+ // primary = cache HIT memory (fastest path)
105
+ // primary-light = cache HIT disk (good, slightly slower)
106
+ // primary-dark = good LCP, clean scroll session
107
+ // secondary = informational (native API, keyboard, viewport, mem/total normal)
108
+ // primary = mem/jvm (JVM heap — your Kotlin/Java code)
109
+ // tertiary-dark = mem/native (V8 + Blink + JNI — WebView proxy; watch for growth)
110
+ // secondary-light = route transition, hook mount (neutral)
111
+ // tertiary = network fetch after miss (expected but slow)
112
+ // tertiary-light = small CLS, long-press (low signal)
113
+ // tertiary-dark = insight warning (non-critical)
114
+
115
+ const COLOR = exports.COLOR = {
116
+ ERROR: "error",
117
+ PRIMARY: "primary",
118
+ PRIMARY_LIGHT: "primary-light",
119
+ PRIMARY_DARK: "primary-dark",
120
+ SECONDARY: "secondary",
121
+ SECONDARY_LIGHT: "secondary-light",
122
+ SECONDARY_DARK: "secondary-dark",
123
+ TERTIARY: "tertiary",
124
+ TERTIARY_LIGHT: "tertiary-light",
125
+ TERTIARY_DARK: "tertiary-dark"
126
+ };
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Measure = void 0;
7
+ var _constants = require("./constants.js");
8
+ /**
9
+ * measure.js
10
+ *
11
+ * Shared _measure() and _colorFor() logic used by every collector.
12
+ * Injected as a dependency — collectors never call performance.measure() directly.
13
+ *
14
+ * Chrome DevTools Extensibility API rules (Chrome 128+):
15
+ * - detail must be { devtools: { dataType:'track-entry', ... } }
16
+ * - custom data goes in properties[] as [key, value] pairs
17
+ * - start AND end must both be numeric ms timestamps — passing a mark name
18
+ * string as start causes silent fallback to Timings row (confirmed Chrome 145)
19
+ */
20
+
21
+ const INTERNAL_PROPERTIES = new Set(["simulatorValid"]);
22
+ const PROPERTY_LABELS = {
23
+ interactionId: "Related tap",
24
+ parentOperation: "Parent operation",
25
+ callId: "Call ID",
26
+ requestId: "Request ID",
27
+ memoryId: "Memory sample"
28
+ };
29
+ const PROPERTY_PRIORITY = {
30
+ interactionId: 0,
31
+ parentOperation: 1,
32
+ outcome: 2,
33
+ ok: 3,
34
+ status: 4,
35
+ durationMs: 5,
36
+ roundTripMs: 5,
37
+ responseMs: 5
38
+ };
39
+ class Measure {
40
+ constructor() {
41
+ // Rolling window of recent LoAF entries — shared across all collectors
42
+ // so any collector can check loafOverlap without knowing about others.
43
+ this._recentLoafs = [];
44
+ }
45
+
46
+ /**
47
+ * Record a LoAF entry so overlap detection stays current.
48
+ * Called by RenderCollector whenever a long-animation-frame fires.
49
+ */
50
+ recordLoaf(start, end) {
51
+ this._recentLoafs.push({
52
+ start,
53
+ end
54
+ });
55
+ if (this._recentLoafs.length > _constants.THRESHOLD.LOAF_WINDOW) {
56
+ this._recentLoafs.shift();
57
+ }
58
+ }
59
+ overlapsLoaf(startMs, endMs) {
60
+ return this._recentLoafs.some(loaf => startMs < loaf.end && endMs > loaf.start);
61
+ }
62
+
63
+ /**
64
+ * Emit a performance.measure() with Chrome DevTools Extensibility API metadata.
65
+ *
66
+ * @param {string} name - measure name (catalyst:...)
67
+ * @param {string} startMark - name of the opening performance.mark()
68
+ * @param {number} endTime - end time in ms (performance.now()-space)
69
+ * @param {object} detail - arbitrary detail fields added as properties[]
70
+ * @param {string} track - Chrome DevTools track name (from TRACK constant)
71
+ * @param {string} [color] - override color; if omitted, derived from name+detail
72
+ * @param {string} [trackGroup] - Chrome DevTools track group
73
+ */
74
+ emit(name, startMark, endTime, detail = {}, track, color = null, trackGroup = _constants.TRACK_GROUP) {
75
+ try {
76
+ const startTime = performance.getEntriesByName(startMark, "mark")[0]?.startTime;
77
+ if (startTime == null) {
78
+ console.warn("[CatalystPerf] measure skipped — mark not found:", startMark);
79
+ return;
80
+ }
81
+ const resolvedColor = color ?? this._colorFor(name, detail, startTime, endTime);
82
+ const properties = this._properties(detail);
83
+ performance.measure(name, {
84
+ start: startTime,
85
+ end: endTime,
86
+ detail: {
87
+ devtools: {
88
+ dataType: "track-entry",
89
+ trackGroup,
90
+ track,
91
+ color: resolvedColor,
92
+ tooltipText: name,
93
+ properties
94
+ }
95
+ }
96
+ });
97
+ } catch (e) {
98
+ console.warn("[CatalystPerf] measure failed:", name, e.message);
99
+ }
100
+ }
101
+ marker(name, startTime, detail = {}, color = _constants.COLOR.SECONDARY) {
102
+ try {
103
+ performance.mark(name, {
104
+ startTime,
105
+ detail: {
106
+ devtools: {
107
+ dataType: "marker",
108
+ color,
109
+ tooltipText: name,
110
+ properties: this._properties(detail)
111
+ }
112
+ }
113
+ });
114
+ } catch (e) {
115
+ console.warn("[CatalystPerf] marker failed:", name, e.message);
116
+ }
117
+ }
118
+ _properties(detail) {
119
+ return Object.entries(detail).filter(([key, value]) => !INTERNAL_PROPERTIES.has(key) && value != null).sort(([left], [right]) => (PROPERTY_PRIORITY[left] ?? 100) - (PROPERTY_PRIORITY[right] ?? 100)).map(([key, value]) => [this._propertyName(key), this._propertyValue(value)]);
120
+ }
121
+ _propertyName(key) {
122
+ if (PROPERTY_LABELS[key]) return PROPERTY_LABELS[key];
123
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._-]+/g, " ").replace(/^./, char => char.toUpperCase());
124
+ }
125
+ _propertyValue(value) {
126
+ if (typeof value === "object") {
127
+ try {
128
+ return JSON.stringify(value);
129
+ } catch {
130
+ return String(value);
131
+ }
132
+ }
133
+ return String(value);
134
+ }
135
+
136
+ // ─── Color derivation ─────────────────────────────────────────────────────
137
+
138
+ _colorFor(name, detail, startTime, endTime) {
139
+ const duration = endTime - startTime;
140
+
141
+ // LoAF — always error (it's a bad frame by definition)
142
+ if (name.startsWith(_constants.PREFIX.LOAF)) return _constants.COLOR.ERROR;
143
+
144
+ // LCP — error if slow
145
+ if (name.startsWith(_constants.PREFIX.LCP)) return detail.renderTime > _constants.THRESHOLD.LCP_BAD_MS ? _constants.COLOR.ERROR : _constants.COLOR.PRIMARY;
146
+ if (name.startsWith(_constants.PREFIX.FCP)) return _constants.COLOR.PRIMARY;
147
+
148
+ // Navigation session
149
+ if (name.startsWith(_constants.PREFIX.SESSION_NAV)) return duration > _constants.THRESHOLD.NAV_SESSION_BAD_MS ? _constants.COLOR.ERROR : _constants.COLOR.PRIMARY;
150
+
151
+ // Route transition
152
+ if (name.startsWith(_constants.PREFIX.ROUTE_TRANSITION)) return _constants.COLOR.SECONDARY;
153
+
154
+ // Scroll session — error if janky
155
+ if (name.startsWith(_constants.PREFIX.SESSION_SCROLL)) return detail.loafCount > 0 ? _constants.COLOR.ERROR : _constants.COLOR.PRIMARY;
156
+
157
+ // Keyboard / viewport — informational
158
+ if (name.startsWith(_constants.PREFIX.SESSION_KEYBOARD)) return _constants.COLOR.SECONDARY;
159
+ if (name.startsWith(_constants.PREFIX.VIEWPORT_RESIZE)) return _constants.COLOR.SECONDARY;
160
+
161
+ // Layout shift
162
+ if (name.startsWith(_constants.PREFIX.LAYOUT_SHIFT)) return detail.value > _constants.THRESHOLD.CLS_BAD ? _constants.COLOR.ERROR : _constants.COLOR.TERTIARY;
163
+
164
+ // Cache spans — color passed explicitly by CacheCollector, this is fallback only
165
+ if (name.startsWith(_constants.PREFIX.CACHE + "/")) {
166
+ if (name.includes("/hit|memory|")) return _constants.COLOR.PRIMARY;
167
+ if (name.includes("/hit|disk|")) return _constants.COLOR.PRIMARY_LIGHT;
168
+ if (name.includes("/miss|")) return _constants.COLOR.ERROR;
169
+ return _constants.COLOR.TERTIARY;
170
+ }
171
+
172
+ // Browser network calls
173
+ if (name.startsWith(_constants.PREFIX.NETWORK_TIMING)) {
174
+ if (detail.ok === false || duration > _constants.THRESHOLD.FETCH_SLOW_MS) return _constants.COLOR.ERROR;
175
+ return _constants.COLOR.SECONDARY;
176
+ }
177
+
178
+ // Hook spans — error if LoAF burst detected during mount
179
+ if (name.startsWith(_constants.PREFIX.HOOK_MOUNT) || name.startsWith(_constants.PREFIX.HOOK_LAZY)) return detail.loafCount > 0 ? _constants.COLOR.ERROR : _constants.COLOR.SECONDARY;
180
+
181
+ // Interaction
182
+ if (name.startsWith(_constants.PREFIX.INTERACTION) || Number.isFinite(detail.responseMs)) return (detail.responseMs ?? duration) > _constants.THRESHOLD.INTERACTION_SLOW_MS ? _constants.COLOR.ERROR : _constants.COLOR.PRIMARY;
183
+
184
+ // Long press — just informational
185
+ if (name.startsWith(_constants.PREFIX.LONG_PRESS)) return _constants.COLOR.TERTIARY;
186
+ return _constants.COLOR.SECONDARY;
187
+ }
188
+ }
189
+ exports.Measure = Measure;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.Session = void 0;
7
+ /**
8
+ * session.js
9
+ *
10
+ * Reusable open/close session pattern.
11
+ * A session is a named span that stays open until explicitly closed.
12
+ *
13
+ * Usage:
14
+ * const session = new Session('catalyst:session/scroll', measure, TRACK.SCROLL)
15
+ * session.open({ loafCount: 0 })
16
+ * session.close({ loafCount: 3, janky: true })
17
+ *
18
+ * The session owns its own start mark so callers don't track mark names.
19
+ */
20
+
21
+ class Session {
22
+ /**
23
+ * @param {string} name - base measure name (catalyst:session/...)
24
+ * @param {Measure} measure - shared Measure instance
25
+ * @param {string} track - Chrome DevTools track name
26
+ */
27
+ constructor(name, measure, track) {
28
+ this._name = name;
29
+ this._measure = measure;
30
+ this._track = track;
31
+ this._state = null; // { startMark, startTime, ...extra } | null
32
+ }
33
+ get isOpen() {
34
+ return this._state !== null;
35
+ }
36
+ get startTime() {
37
+ return this._state?.startTime ?? null;
38
+ }
39
+
40
+ /**
41
+ * Open the session.
42
+ * @param {object} extra - any fields to carry in state (e.g. loafCount)
43
+ * @param {number} [atTime] - override start time (default: performance.now())
44
+ */
45
+ open(extra = {}, atTime = null) {
46
+ if (this._state) return; // already open — ignore double-open
47
+ const startTime = atTime ?? performance.now();
48
+ const startMark = `${this._name}:start:${Math.round(startTime)}`;
49
+ performance.mark(startMark, {
50
+ startTime
51
+ });
52
+ this._state = {
53
+ startMark,
54
+ startTime,
55
+ ...extra
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Close the session and emit the measure.
61
+ * @param {object} detail - fields added to properties[] in DevTools
62
+ * @param {number} [atTime] - override end time (default: performance.now())
63
+ * @param {string} [color] - override color
64
+ */
65
+ close(detail = {}, atTime = null, color = null) {
66
+ if (!this._state) return;
67
+ const {
68
+ startMark,
69
+ startTime
70
+ } = this._state;
71
+ const endTime = atTime ?? performance.now();
72
+ this._state = null;
73
+ this._measure.emit(this._name, startMark, endTime, {
74
+ duration: Math.round(endTime - startTime),
75
+ ...detail
76
+ }, this._track, color);
77
+ }
78
+
79
+ /**
80
+ * Read a value stored in session state (e.g. loafCount).
81
+ */
82
+ get(key) {
83
+ return this._state?.[key];
84
+ }
85
+
86
+ /**
87
+ * Update a value in session state without closing.
88
+ */
89
+ set(key, value) {
90
+ if (this._state) this._state[key] = value;
91
+ }
92
+
93
+ /**
94
+ * Increment a numeric counter in session state.
95
+ */
96
+ increment(key) {
97
+ if (this._state) this._state[key] = (this._state[key] ?? 0) + 1;
98
+ }
99
+ }
100
+ exports.Session = Session;