catalyst-core 0.3.0-beta.5 → 0.3.0-canary.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 (316) hide show
  1. package/changelog.md +185 -71
  2. package/dist/errors/generateDocs.js +100 -0
  3. package/dist/errors/index.js +243 -0
  4. package/dist/errors/registry.js +483 -0
  5. package/dist/errors-index.json +506 -0
  6. package/dist/globals.d.ts +62 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.jsx +5 -8
  9. package/dist/logger.js +1 -1
  10. package/dist/native/androidProject/app/build.gradle.kts +142 -7
  11. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +4 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +2 -2
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +3 -0
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +3 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +43 -8
  16. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +181 -1
  17. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +156 -236
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileSizeRouterUtilsTest.kt +471 -0
  19. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +222 -226
  20. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FrameworkServerUtilsTest.kt +300 -0
  21. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/IntentUtilsTest.kt +244 -0
  22. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/MetricsMonitorTest.kt +359 -0
  23. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +108 -63
  24. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +329 -385
  25. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/OfflineCacheServiceTest.kt +302 -0
  26. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/PerfEventBufferTest.kt +133 -0
  27. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/UtilsKtTest.kt +152 -0
  28. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +145 -324
  29. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/BarcodeDetectorTest.kt +132 -0
  30. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/HoldControllerTest.kt +155 -0
  31. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/TorchControllerTest.kt +150 -0
  32. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateMachineTest.kt +120 -0
  33. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateTest.kt +62 -0
  34. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ViewfinderMapperTest.kt +143 -0
  35. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ZoomControllerTest.kt +421 -0
  36. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/design/DesignTokensTest.kt +207 -0
  37. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +368 -0
  38. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/FridaDetectorTest.kt +193 -0
  39. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckManagerTest.kt +260 -0
  40. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckSchedulerTest.kt +166 -0
  41. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DeviceInfoUtilsTest.kt +167 -0
  42. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DownloadUtilsTest.kt +197 -0
  43. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/NetworkMonitorTest.kt +271 -0
  44. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/PushNotificationUtilsTest.kt +119 -0
  45. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/SafeAreaUtilsTest.kt +108 -0
  46. package/dist/native/androidProject/build.gradle.kts +1 -0
  47. package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
  48. package/dist/native/androidProject/settings.gradle.kts +11 -8
  49. package/dist/native/androidSetup.js +2 -2
  50. package/dist/native/bridge/WebBridge.d.ts +98 -0
  51. package/dist/native/bridge/WebBridge.js +40 -6
  52. package/dist/native/bridge/constants/NativeInterfaces.d.ts +391 -0
  53. package/dist/native/bridge/errors.d.ts +67 -0
  54. package/dist/native/bridge/errors.js +66 -151
  55. package/dist/native/bridge/hooks/index.d.ts +13 -0
  56. package/dist/native/bridge/hooks/index.js +0 -6
  57. package/dist/native/bridge/hooks/useAI.d.ts +1 -0
  58. package/dist/native/bridge/hooks/useAI.js +3 -1
  59. package/dist/native/bridge/hooks/useCamera.d.ts +86 -0
  60. package/dist/native/bridge/hooks/useCamera.js +35 -6
  61. package/dist/native/bridge/hooks/useDataProtection.d.ts +17 -0
  62. package/dist/native/bridge/hooks/useDeviceInfo.d.ts +34 -0
  63. package/dist/native/bridge/hooks/useDeviceInfo.js +5 -0
  64. package/dist/native/bridge/hooks/useFilePicker.d.ts +53 -0
  65. package/dist/native/bridge/hooks/useFilePicker.js +13 -0
  66. package/dist/native/bridge/hooks/useGoogleSignIn.d.ts +12 -0
  67. package/dist/native/bridge/hooks/useHapticFeedback.d.ts +76 -0
  68. package/dist/native/bridge/hooks/useHapticFeedback.js +6 -1
  69. package/dist/native/bridge/hooks/useIntent.d.ts +33 -0
  70. package/dist/native/bridge/hooks/useIntent.js +2 -0
  71. package/dist/native/bridge/hooks/useNativeTransition.d.ts +53 -0
  72. package/dist/native/bridge/hooks/useNativeTransition.js +12 -5
  73. package/dist/native/bridge/hooks/useNetworkStatus.d.ts +22 -0
  74. package/dist/native/bridge/hooks/useNetworkStatus.js +7 -0
  75. package/dist/native/bridge/hooks/useNotification.d.ts +83 -0
  76. package/dist/native/bridge/hooks/useNotification.js +81 -34
  77. package/dist/native/bridge/hooks/useSafeArea.d.ts +12 -0
  78. package/dist/native/bridge/hooks/useSafeArea.js +8 -0
  79. package/dist/native/bridge/hooks/useVideoStream.d.ts +67 -0
  80. package/dist/native/bridge/hooks/useVideoStream.js +38 -52
  81. package/dist/native/bridge/hooks/utils.d.ts +4 -0
  82. package/dist/native/bridge/hooks.d.ts +1 -0
  83. package/dist/native/bridge/perf/collectors/ApiCollector.d.ts +30 -0
  84. package/dist/native/bridge/perf/collectors/ApiCollector.js +4 -0
  85. package/dist/native/bridge/perf/collectors/CacheCollector.d.ts +43 -0
  86. package/dist/native/bridge/perf/collectors/CacheCollector.js +4 -0
  87. package/dist/native/bridge/perf/collectors/HookCollector.d.ts +37 -0
  88. package/dist/native/bridge/perf/collectors/HookCollector.js +4 -0
  89. package/dist/native/bridge/perf/collectors/InsightsCollector.d.ts +103 -0
  90. package/dist/native/bridge/perf/collectors/InsightsCollector.js +4 -0
  91. package/dist/native/bridge/perf/collectors/InteractionCollector.d.ts +53 -0
  92. package/dist/native/bridge/perf/collectors/InteractionCollector.js +4 -0
  93. package/dist/native/bridge/perf/collectors/KeyboardCollector.d.ts +30 -0
  94. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +4 -0
  95. package/dist/native/bridge/perf/collectors/NavigationCollector.d.ts +37 -0
  96. package/dist/native/bridge/perf/collectors/NavigationCollector.js +4 -0
  97. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.d.ts +30 -0
  98. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +4 -0
  99. package/dist/native/bridge/perf/collectors/RenderCollector.d.ts +47 -0
  100. package/dist/native/bridge/perf/collectors/RenderCollector.js +8 -0
  101. package/dist/native/bridge/perf/collectors/ScrollCollector.d.ts +29 -0
  102. package/dist/native/bridge/perf/collectors/ScrollCollector.js +4 -0
  103. package/dist/native/bridge/perf/collectors/ThreadCollector.d.ts +31 -0
  104. package/dist/native/bridge/perf/collectors/ThreadCollector.js +4 -0
  105. package/dist/native/bridge/perf/core/constants.d.ts +76 -0
  106. package/dist/native/bridge/perf/core/measure.d.ts +39 -0
  107. package/dist/native/bridge/perf/core/measure.js +4 -0
  108. package/dist/native/bridge/perf/core/session.d.ts +49 -0
  109. package/dist/native/bridge/perf/core/session.js +4 -0
  110. package/dist/native/bridge/perf/core/store.d.ts +37 -0
  111. package/dist/native/bridge/perf/core/store.js +4 -0
  112. package/dist/native/bridge/perf/core/summary.d.ts +60 -0
  113. package/dist/native/bridge/perf/core/traceExport.d.ts +10 -0
  114. package/dist/native/bridge/perf/core/waterfalls.d.ts +2 -0
  115. package/dist/native/bridge/perf/index.d.ts +63 -0
  116. package/dist/native/bridge/perf/index.js +4 -0
  117. package/dist/native/bridge/safeArea.d.ts +11 -0
  118. package/dist/native/bridge/safeArea.js +2 -0
  119. package/dist/native/bridge/useBaseHook.d.ts +137 -0
  120. package/dist/native/bridge/useBaseHook.js +34 -4
  121. package/dist/native/bridge/utils/CameraUtils.d.ts +94 -0
  122. package/dist/native/bridge/utils/CameraUtils.js +4 -0
  123. package/dist/native/bridge/utils/FileObjectConverter.d.ts +36 -0
  124. package/dist/native/bridge/utils/NativeBridge.d.ts +270 -0
  125. package/dist/native/bridge/utils/NativeBridge.js +10 -0
  126. package/dist/native/buildAndroid/index.js +1 -1
  127. package/dist/native/buildAppAndroid.js +9 -0
  128. package/dist/native/buildAppIos.js +16 -1
  129. package/dist/native/buildErrorFormat.js +57 -0
  130. package/dist/native/buildIos/config.js +15 -1
  131. package/dist/native/buildIos/index.js +1 -1
  132. package/dist/native/buildIos/plugins.js +10 -1
  133. package/dist/native/iosnativeWebView/CoreLogic/Package.swift +36 -0
  134. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamState.swift +1 -1
  135. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamStateMachine.swift +6 -4
  136. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/BootTimingUtility.swift +3 -3
  137. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/FrameworkServerUtils.swift +14 -9
  138. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NetworkMonitor.swift +11 -9
  139. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NotificationHandlerProvider.swift +1 -1
  140. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/URLWhitelistManager.swift +7 -7
  141. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/AnyCodableTests.swift +154 -0
  142. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/BootTimingUtilityTests.swift +23 -5
  143. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/CatalystConstantsTests.swift +192 -0
  144. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/ConfigMappingTests.swift +1 -1
  145. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsErrorPathTests.swift +106 -0
  146. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsLoopbackTests.swift +243 -0
  147. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/FrameworkServerUtilsTests.swift +1 -1
  148. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NetworkMonitorTests.swift +119 -0
  149. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NotificationHandlerProviderTests.swift +59 -0
  150. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/NotificationHandlerTests.swift +1 -1
  151. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/URLWhitelistManagerTests.swift +1 -1
  152. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateMachineTests.swift +171 -0
  153. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateTests.swift +91 -0
  154. package/dist/native/iosnativeWebView/Package.swift +10 -9
  155. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +7 -2
  156. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +1 -2
  157. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +5 -4
  158. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +1 -0
  159. package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +1 -0
  160. package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +6 -5
  161. package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +1 -0
  162. package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +1 -0
  163. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +31 -30
  164. package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +2 -1
  165. package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +1 -1
  166. package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +22 -19
  167. package/dist/native/iosnativeWebView/Sources/Core/Utils/ImageHandler.swift +40 -39
  168. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +1 -0
  169. package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +15 -15
  170. package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +4 -3
  171. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +6 -1
  172. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +1 -0
  173. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +2 -1
  174. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +2 -1
  175. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +3 -5
  176. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +20 -13
  177. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +6 -5
  178. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +14 -13
  179. package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +3 -3
  180. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +9 -8
  181. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +7 -6
  182. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +24 -25
  183. package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +6 -1
  184. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +1 -0
  185. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +4 -0
  186. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +2 -22
  187. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +1 -0
  188. package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +1 -0
  189. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +2 -1
  190. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +15 -9
  191. package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +1 -0
  192. package/dist/native/setupEmulatorIos.js +2 -2
  193. package/dist/native/terminalProgress.js +17 -17
  194. package/dist/router/ClientRouter.d.ts +8 -0
  195. package/dist/router/ClientRouter.js +13 -11
  196. package/dist/router/ServerRouter.d.ts +13 -0
  197. package/dist/router/ServerRouter.js +19 -7
  198. package/dist/scripts/build.js +14 -2
  199. package/dist/scripts/loadEnvironmentVariables.js +3 -2
  200. package/dist/scripts/plugins.js +1 -1
  201. package/dist/scripts/preServerInit.js +49 -3
  202. package/dist/scripts/preflight.js +111 -0
  203. package/dist/scripts/scriptUtils.js +46 -1
  204. package/dist/scripts/serve.js +7 -3
  205. package/dist/scripts/start.js +8 -3
  206. package/dist/server/expressServer.d.ts +1 -0
  207. package/dist/server/expressServer.js +247 -271
  208. package/dist/server/manifestCache.d.ts +2 -0
  209. package/dist/server/manifestCache.js +25 -33
  210. package/dist/server/renderer/ChunkExtractor.d.ts +44 -0
  211. package/dist/server/renderer/ChunkExtractor.js +113 -125
  212. package/dist/server/renderer/document/Body.d.ts +21 -0
  213. package/dist/server/renderer/document/Body.jsx +45 -45
  214. package/dist/server/renderer/document/FastRefresh.d.ts +3 -0
  215. package/dist/server/renderer/document/FastRefresh.jsx +7 -13
  216. package/dist/server/renderer/document/Head.d.ts +20 -0
  217. package/dist/server/renderer/document/Head.jsx +32 -32
  218. package/dist/server/renderer/document/index.d.ts +2 -0
  219. package/dist/server/renderer/document/index.jsx +2 -2
  220. package/dist/server/renderer/extract.d.ts +67 -0
  221. package/dist/server/renderer/extract.js +92 -89
  222. package/dist/server/renderer/handler.d.ts +2 -0
  223. package/dist/server/renderer/handler.jsx +401 -404
  224. package/dist/server/renderer/index.d.ts +2 -0
  225. package/dist/server/renderer/index.js +5 -8
  226. package/dist/server/renderer/render.d.ts +35 -0
  227. package/dist/server/renderer/render.js +25 -32
  228. package/dist/server/utils/botDetectionMiddleware.d.ts +1 -0
  229. package/dist/server/utils/botDetectionMiddleware.js +33 -23
  230. package/dist/server/utils/cjsRequire.d.ts +8 -0
  231. package/dist/server/utils/cjsRequire.js +2 -2
  232. package/dist/server/utils/userAgentUtil.d.ts +7 -0
  233. package/dist/server/utils/userAgentUtil.js +68 -71
  234. package/dist/server/utils/validator.d.ts +31 -0
  235. package/dist/server/utils/validator.js +133 -158
  236. package/dist/vite/inject-cache-key-plugin.js +3 -27
  237. package/dist/vite/moduleAliases.js +36 -0
  238. package/dist/vite/node-loader.mjs +2 -29
  239. package/dist/vite/resolveDevServerConfig.js +4 -1
  240. package/dist/vite/vite.config.js +2 -29
  241. package/dist/web-router/components/MetaTag.d.ts +4 -0
  242. package/dist/web-router/components/MetaTag.jsx +26 -28
  243. package/dist/web-router/components/MetaTag.test.jsx +100 -0
  244. package/dist/web-router/components/RouterDataProvider.d.ts +96 -0
  245. package/dist/web-router/components/RouterDataProvider.jsx +260 -259
  246. package/dist/web-router/components/RouterDataProvider.server.test.jsx +98 -0
  247. package/dist/web-router/components/RouterDataProvider.test.jsx +310 -0
  248. package/dist/web-router/components/Split.d.ts +74 -0
  249. package/dist/web-router/components/Split.jsx +147 -156
  250. package/dist/web-router/components/Split.server.test.jsx +52 -0
  251. package/dist/web-router/components/Split.test.jsx +167 -0
  252. package/dist/web-router/components/SplitInview.d.ts +22 -0
  253. package/dist/web-router/components/SplitInview.jsx +78 -89
  254. package/dist/web-router/components/SplitInview.test.jsx +112 -0
  255. package/dist/web-router/components/SsrRequestContext.d.ts +20 -0
  256. package/dist/web-router/components/SsrRequestContext.jsx +17 -7
  257. package/dist/web-router/components/SsrRequestContext.test.jsx +53 -0
  258. package/dist/web-router/context.d.ts +15 -0
  259. package/dist/web-router/context.jsx +5 -5
  260. package/dist/web-router/context.test.jsx +10 -0
  261. package/dist/web-router/utils/metaDataUtils.d.ts +24 -0
  262. package/dist/web-router/utils/metaDataUtils.jsx +86 -86
  263. package/dist/web-router/utils/metaDataUtils.test.jsx +188 -0
  264. package/dist/web-router/vitest.setup.js +12 -0
  265. package/mcp_v2/knowledge-base.json +115 -17
  266. package/mcp_v2/mcp.js +141 -3
  267. package/mcp_v2/package-lock.json +2031 -0
  268. package/mcp_v2/package.json +13 -2
  269. package/mcp_v2/scripts/copyErrorsIndex.cjs +34 -0
  270. package/mcp_v2/test/errors.test.ts +123 -0
  271. package/mcp_v2/test/github.test.ts +177 -0
  272. package/mcp_v2/tools/errors.d.ts +20 -0
  273. package/mcp_v2/tools/errors.js +63 -0
  274. package/mcp_v2/tools/github.d.ts +41 -0
  275. package/mcp_v2/tools/github.js +231 -0
  276. package/mcp_v2/vitest.config.mjs +22 -0
  277. package/package.json +60 -11
  278. package/test/errors/docsDrift.test.ts +113 -0
  279. package/test/errors/index.test.ts +200 -0
  280. package/test/errors/registry.test.ts +108 -0
  281. package/test/scripts/preflight.test.ts +118 -0
  282. package/test/server/ChunkExtractor.test.ts +97 -0
  283. package/test/server/extract.test.ts +128 -0
  284. package/test/server/fixtures/template/server/document.jsx +19 -0
  285. package/test/server/fixtures/template/server/index.js +10 -0
  286. package/test/server/fixtures/template/src/js/containers/App/index.jsx +33 -0
  287. package/test/server/fixtures/template/src/js/routes/index.jsx +72 -0
  288. package/test/server/fixtures/template/src/js/routes/utils.jsx +39 -0
  289. package/test/server/fixtures/template/src/js/store/index.js +19 -0
  290. package/test/server/handler.assets.test.ts +330 -0
  291. package/test/server/handler.test.ts +208 -0
  292. package/test/server/manifestCache.test.ts +71 -0
  293. package/test/server/render.test.ts +71 -0
  294. package/test/server/scriptUtils.test.ts +103 -0
  295. package/test/server/userAgentUtil.test.ts +48 -0
  296. package/test/server/validator.test.ts +225 -0
  297. package/test/vite/resolveDevServerConfig.test.js +4 -1
  298. package/tsconfig.test.json +23 -0
  299. package/vitest.config.ts +93 -0
  300. package/dist/caching.js +0 -1
  301. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +0 -187
  302. package/dist/native/bridge/standardizedHooks.js +0 -611
  303. package/dist/router.js +0 -1
  304. package/dist/scripts/validator.js +0 -149
  305. package/dist/web-router/hooks.jsx +0 -72
  306. package/dist/web-router/types/components/MetaTag.d.ts +0 -2
  307. package/dist/web-router/types/components/RouterDataProvider.d.ts +0 -95
  308. package/dist/web-router/types/context.d.ts +0 -14
  309. package/dist/web-router/types/hooks.d.ts +0 -1
  310. package/dist/web-router/types/index.d.ts +0 -3
  311. package/dist/web-router/types/utils/metaDataUtils.d.ts +0 -40
  312. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/CatalystConstants.swift +0 -0
  313. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/ConfigConstants.swift +0 -0
  314. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Notifications/NotificationModels.swift +0 -0
  315. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Protocols/NotificationHandlerProtocol.swift +0 -0
  316. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NullNotificationHandler.swift +0 -0
@@ -0,0 +1,10 @@
1
+ // Fixture server hooks for the SSR handler tests (#348). handler.jsx does
2
+ // `await import("@catalyst/template/server/index.js")` in a try/catch and
3
+ // wires whatever named exports exist (onRouteMatch, onFetcherSuccess,
4
+ // onFetcherError, onAppServerSideSuccess, onAppServerSideError,
5
+ // onRenderError, onRequestError).
6
+ //
7
+ // All left undefined by default so safeCall skips them. Tests that need a
8
+ // hook (e.g. to assert onRenderError fired) vi.spyOn / re-mock this
9
+ // module per-case.
10
+ export {}
@@ -0,0 +1,33 @@
1
+ import React from "react"
2
+ import { Outlet } from "react-router"
3
+
4
+ // Fixture App for the SSR handler tests (#348).
5
+ //
6
+ // Renders <Outlet /> exactly like every real create-catalyst-app template's
7
+ // App — that is how react-router hands the matched child route down. The
8
+ // earlier version rendered `{children}`, which react-router never populates
9
+ // for a nested route, so route components never rendered on the server.
10
+ //
11
+ // App.serverSideFunction is an implicit contract: handler.jsx calls it
12
+ // unconditionally (via tracedAppServerSideFunction) on every request. All
13
+ // 6 real templates define it, so the fixture must too — omitting it would
14
+ // make every test throw in the SERVER_SIDE_FUNCTION stage rather than
15
+ // exercising the render path.
16
+ //
17
+ // The tests swap `serverSideFunction` per-case (e.g. to make it throw) via
18
+ // vi.spyOn on this module.
19
+ function App() {
20
+ return (
21
+ <main data-testid="app">
22
+ <Outlet />
23
+ </main>
24
+ )
25
+ }
26
+
27
+ App.serverSideFunction = async () => {
28
+ // no-op by default; tests override to test the SERVER_SIDE_FUNCTION
29
+ // error branch.
30
+ return undefined
31
+ }
32
+
33
+ export default App
@@ -0,0 +1,72 @@
1
+ import React from "react"
2
+
3
+ // Fixture route table for the SSR handler tests (#348). Concrete routes
4
+ // plus a catch-all so tests can exercise the 200 / 404 / fetcher-error
5
+ // status branches in _handler and _renderMarkUp.
6
+
7
+ function HomePage() {
8
+ return <p data-testid="home">home</p>
9
+ }
10
+ // __cacheKey mirrors what Split.jsx stamps on split() components. The SSR
11
+ // handler *asset* tests (handler.assets.test.ts) mock manifestCache to
12
+ // return a manifest entry under this key, so preloadRouteCss resolves a
13
+ // real critical JS/CSS chunk for this route. Inert for handler.test.ts,
14
+ // which runs with empty manifests (no entry -> no assets).
15
+ HomePage.__cacheKey = "src/js/routes/HomePage"
16
+
17
+ // A route component that registers a deferred chunk during render the exact
18
+ // way Split.jsx does on the server (Split.jsx:53-54:
19
+ // `global.__CHUNK_EXTRACTOR__.addComponent(cacheKey)`). Split's own
20
+ // server-side behavior is unit-tested in Split.server.test.jsx (#454); this
21
+ // fixture exists so the *handler* asset tests can exercise what happens
22
+ // once a component has self-registered: getRenderedComponentKeys()
23
+ // serialization into window.__SSR_RENDERED_COMPONENTS__, the post-body
24
+ // deferred <style>/<script> flush, and the cross-request deferred-CSS
25
+ // cache. handler.assets.test.ts mocks manifestCache so WIDGET_CACHE_KEY
26
+ // resolves to a chunk with its own CSS.
27
+ export const WIDGET_CACHE_KEY = "src/js/components/Widget"
28
+
29
+ function WidgetPage() {
30
+ if (typeof global !== "undefined" && global.__CHUNK_EXTRACTOR__) {
31
+ global.__CHUNK_EXTRACTOR__.addComponent(WIDGET_CACHE_KEY)
32
+ }
33
+ return <p data-testid="widget">widget</p>
34
+ }
35
+
36
+ function NotFoundPage() {
37
+ return <p data-testid="notfound">not found</p>
38
+ }
39
+
40
+ function FetcherErrorPage() {
41
+ return <p data-testid="fetcher-error">boom page</p>
42
+ }
43
+
44
+ // serverDataFetcher only reads serverFetcher off a *loadable* component
45
+ // (`route.component.load()` -> module with a `default`). On the server
46
+ // (no window) fetchRouteData picks module.default.serverFetcher; when it
47
+ // rejects, serverDataFetcher records fetcherData[url].error, which
48
+ // _handler turns into an `err.status_code || 404` render.
49
+ const loadable = (mod) => {
50
+ const C = mod.default
51
+ C.load = () => Promise.resolve(mod)
52
+ return C
53
+ }
54
+
55
+ const FetcherErrorLoadable = loadable({
56
+ default: Object.assign(FetcherErrorPage, {
57
+ serverFetcher: async () => {
58
+ const e = new Error("fetcher exploded")
59
+ e.status_code = 503
60
+ throw e
61
+ },
62
+ }),
63
+ })
64
+
65
+ const routes = [
66
+ { path: "/", component: HomePage },
67
+ { path: "/widget", component: WidgetPage },
68
+ { path: "/fetcher-error", component: FetcherErrorLoadable },
69
+ { path: "*", component: NotFoundPage },
70
+ ]
71
+
72
+ export default routes
@@ -0,0 +1,39 @@
1
+ import React from "react"
2
+ import { RouterDataProvider } from "catalyst-core"
3
+ import App from "../containers/App/index.jsx"
4
+ import routes from "./index.jsx"
5
+
6
+ // Fixture mirror of a scaffolded app's routes/utils. Same two exports the
7
+ // framework consumes: getRoutes() (raw table, used by handler.jsx via
8
+ // getCachedRoutes / NestedMatchRoutes) and preparedRoutes() (react-router
9
+ // shape, used by ServerRouter).
10
+ //
11
+ // The parent element renders <App />, which renders <Outlet /> — the same
12
+ // shape every real create-catalyst-app template uses. Without the Outlet,
13
+ // react-router never renders the matched child route, so route components
14
+ // (and anything they do during render, e.g. Split's addComponent) stay
15
+ // dark. handler.assets.test.ts's deferred-asset cases depend on the child
16
+ // actually rendering.
17
+
18
+ export const getRoutes = () => routes
19
+
20
+ export const preparedRoutes = ({ routerInitialState } = {}) => {
21
+ const prepare = (list) =>
22
+ list.map((route, index) => {
23
+ const Component = route.component
24
+ const prepared = { ...route, element: <Component key={index} /> }
25
+ if (route.children) prepared.children = prepare(route.children)
26
+ return prepared
27
+ })
28
+
29
+ return [
30
+ {
31
+ element: (
32
+ <RouterDataProvider config={{}} initialState={routerInitialState}>
33
+ <App />
34
+ </RouterDataProvider>
35
+ ),
36
+ children: prepare(routes),
37
+ },
38
+ ]
39
+ }
@@ -0,0 +1,19 @@
1
+ // Fixture Redux-ish store for the SSR handler tests (#348). handler.jsx
2
+ // calls `createStore({}, req, res)` when validateConfigureStore passes,
3
+ // then reads store.getState() during render. A minimal object with
4
+ // getState/dispatch/subscribe satisfies react-redux's <Provider>.
5
+
6
+ export default async function createStore(initialState = {}) {
7
+ let state = { ...initialState }
8
+ return {
9
+ getState: () => state,
10
+ dispatch: (action) => action,
11
+ subscribe: () => () => {},
12
+ replaceReducer: () => {},
13
+ // react-redux 8+ Provider checks for this symbol-less shape; the
14
+ // above four methods are enough for SSR render.
15
+ [Symbol.observable ?? "@@observable"]: () => ({
16
+ subscribe: () => ({ unsubscribe: () => {} }),
17
+ }),
18
+ }
19
+ }
@@ -0,0 +1,330 @@
1
+ import { Writable } from "node:stream"
2
+ import fs from "node:fs"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
6
+
7
+ // SSR handler — manifest → chunk → markup coverage (#348 follow-up).
8
+ //
9
+ // handler.test.ts proves the request lifecycle (200 / 404 / fetcher status,
10
+ // stream completion, error propagation) but runs with:
11
+ // - the fixture's CustomDocument always present, so handler.jsx:242 always
12
+ // short-circuits and the built-in Head/Body never render; and
13
+ // - NODE_ENV unset, so manifestCache returns null and _collectAssets
14
+ // builds a ChunkExtractor over {} / {} — every asset path executes with
15
+ // empty inputs and asserts nothing.
16
+ //
17
+ // This file drives the other side: no CustomDocument (built-in document),
18
+ // a populated fake manifest so preloadRouteCss / addComponent resolve real
19
+ // chunks, real CSS files on disk so readCssFromDisk returns content, and a
20
+ // bot UA so the !isBot gates in Head.jsx / registerDeferredAssetsForRoute
21
+ // are exercised end-to-end in a rendered response.
22
+ //
23
+ // Deliberately NOT here: real split() + lazy + Suspense streaming (that is
24
+ // Split.server.test.jsx, #454) and browser behavior (Playwright, #412).
25
+ // The fixture WidgetPage self-registers with global.__CHUNK_EXTRACTOR__ the
26
+ // exact way Split.jsx:53-54 does on the server, so the deferred-asset
27
+ // plumbing downstream of that call is covered without pulling the whole
28
+ // split() machinery in.
29
+
30
+ const TEMPLATE_DOC = "@catalyst/template/server/document"
31
+ const MANIFEST_CACHE = "../../src/server/manifestCache.js"
32
+
33
+ // Fake build manifest. HomePage's __cacheKey → a critical chunk with CSS;
34
+ // WIDGET_CACHE_KEY → a deferred chunk with its own CSS (registered during
35
+ // render via addComponent). `file` has no leading "assets/" — the emitted
36
+ // URL is PUBLIC_STATIC_ASSET_URL + PUBLIC_STATIC_ASSET_PATH + file.
37
+ const FAKE_MANIFEST = {
38
+ "src/js/routes/HomePage": { file: "home.abc123.js", css: ["home.abc123.css"] },
39
+ "src/js/components/Widget": { file: "widget.def456.js", css: ["widget.def456.css"] },
40
+ }
41
+
42
+ function mockManifest() {
43
+ vi.doMock(MANIFEST_CACHE, () => ({
44
+ getManifest: () => FAKE_MANIFEST,
45
+ getAssetManifest: () => ({ ssrTrue: {}, ssrFalse: {}, essential: {} }),
46
+ }))
47
+ }
48
+
49
+ // A real Writable so renderToPipeableStream's Transform → res pipe works.
50
+ function makeReqRes(url = "/", userAgent = "test") {
51
+ const chunks: Buffer[] = []
52
+ let ended = false
53
+
54
+ const res: any = new Writable({
55
+ write(chunk, _enc, cb) {
56
+ chunks.push(Buffer.from(chunk))
57
+ cb()
58
+ },
59
+ final(cb) {
60
+ ended = true
61
+ cb()
62
+ },
63
+ })
64
+ const endPromise = new Promise<void>((resolve) => {
65
+ res.on("finish", resolve)
66
+ res.on("close", resolve)
67
+ })
68
+ res.headers = {}
69
+ res.statusCode = 200
70
+ res.headersSent = false
71
+ res.set = vi.fn((obj: Record<string, string>) => {
72
+ Object.assign(res.headers, obj)
73
+ return res
74
+ })
75
+ res.setHeader = vi.fn((k: string, v: string) => {
76
+ res.headers[k] = v
77
+ return res
78
+ })
79
+ res.status = vi.fn((code: number) => {
80
+ res.statusCode = code
81
+ return res
82
+ })
83
+ res.send = vi.fn(() => {
84
+ res.headersSent = true
85
+ return res
86
+ })
87
+ res.getHtml = () => Buffer.concat(chunks).toString("utf8")
88
+ res.waitForEnd = () => endPromise
89
+ res.isEnded = () => ended
90
+
91
+ const req: any = {
92
+ originalUrl: url,
93
+ url,
94
+ method: "GET",
95
+ query: {},
96
+ headers: { "user-agent": userAgent },
97
+ get(name: string) {
98
+ return this.headers[name.toLowerCase()]
99
+ },
100
+ }
101
+ return { req, res }
102
+ }
103
+
104
+ let buildDir: string
105
+
106
+ beforeEach(() => {
107
+ // A real build dir so readCssFromDisk (fs.readFileSync) returns content.
108
+ // src_path + BUILD_OUTPUT_PATH is how handler.jsx:205 computes it.
109
+ buildDir = fs.mkdtempSync(path.join(os.tmpdir(), "catalyst-assets-"))
110
+ fs.writeFileSync(path.join(buildDir, "home.abc123.css"), ".home{color:red}")
111
+ fs.writeFileSync(path.join(buildDir, "widget.def456.css"), ".widget{color:blue}")
112
+
113
+ process.env.src_path = path.dirname(buildDir)
114
+ process.env.BUILD_OUTPUT_PATH = path.basename(buildDir)
115
+ process.env.APPLICATION = "test"
116
+ process.env.PUBLIC_STATIC_ASSET_URL = "http://localhost"
117
+ process.env.PUBLIC_STATIC_ASSET_PATH = "/assets/"
118
+ vi.spyOn(console, "error").mockImplementation(() => {})
119
+ vi.spyOn(console, "log").mockImplementation(() => {})
120
+ vi.spyOn(console, "warn").mockImplementation(() => {})
121
+ })
122
+
123
+ afterEach(() => {
124
+ vi.restoreAllMocks()
125
+ vi.resetModules()
126
+ vi.doUnmock(TEMPLATE_DOC)
127
+ vi.doUnmock(MANIFEST_CACHE)
128
+ try {
129
+ fs.rmSync(buildDir, { recursive: true, force: true })
130
+ } catch {
131
+ // best-effort cleanup
132
+ }
133
+ // ChunkExtractor's constructor sets this and nothing clears it; a leak
134
+ // would let one test's extractor catch the next test's addComponent.
135
+ delete (global as any).__CHUNK_EXTRACTOR__
136
+ // extract.js keeps its deferred-route cache and CSS-body cache on
137
+ // `process` — reset so cross-request-cache tests start clean and the
138
+ // first render of a route never sees a prior run's deferred paths.
139
+ delete (process as any).deferredAssetsByRoute
140
+ delete (process as any).cssFileCache
141
+ })
142
+
143
+ describe("SSR handler — built-in document (no CustomDocument)", () => {
144
+ it("renders the built-in Head/Body when the app exports no CustomDocument", async () => {
145
+ vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
146
+ mockManifest()
147
+
148
+ const { default: handler } = await import("../../src/server/renderer/handler.jsx")
149
+ const { req, res } = makeReqRes("/")
150
+
151
+ await handler(req, res)
152
+ await res.waitForEnd()
153
+
154
+ const html = res.getHtml()
155
+ // Built-in Head markers — never present when the fixture
156
+ // CustomDocument (which renders <title>fixture</title> +
157
+ // data-testid="doc-root") takes the branch.
158
+ expect(html).toContain('<meta charSet="utf-8"')
159
+ expect(html).toContain('<link rel="preconnect" href="http://localhost/assets/"')
160
+ expect(html).not.toContain('data-testid="doc-root"')
161
+ // Built-in Body marker.
162
+ expect(html).toContain("window.__SAFE_AREA_INITIAL__")
163
+ expect(res.isEnded()).toBe(true)
164
+ })
165
+
166
+ it("flags PREFLIGHT-021 and falls back to the built-in document when CustomDocument is not a function", async () => {
167
+ vi.doMock(TEMPLATE_DOC, () => ({ default: { not: "a function" } }))
168
+ mockManifest()
169
+
170
+ const { default: handler } = await import("../../src/server/renderer/handler.jsx")
171
+ const { req, res } = makeReqRes("/")
172
+
173
+ await handler(req, res)
174
+ await res.waitForEnd()
175
+
176
+ // validateCustomDocument -> handleError -> console.log (default
177
+ // output mode), not console.error.
178
+ const logged = (console.log as any).mock.calls.flat().join("\n")
179
+ expect(logged).toContain("PREFLIGHT-021")
180
+
181
+ // Fallback actually happened: built-in Head rendered, fixture
182
+ // CustomDocument did not.
183
+ const html = res.getHtml()
184
+ expect(html).toContain('<meta charSet="utf-8"')
185
+ expect(html).not.toContain('data-testid="doc-root"')
186
+ })
187
+ })
188
+
189
+ describe("SSR handler — critical asset collection", () => {
190
+ it("resolves the matched route's chunk from the manifest: inlines its CSS and emits its module script + modulepreload in <head>", async () => {
191
+ vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
192
+ mockManifest()
193
+
194
+ const { default: handler } = await import("../../src/server/renderer/handler.jsx")
195
+ const { req, res } = makeReqRes("/")
196
+
197
+ await handler(req, res)
198
+ await res.waitForEnd()
199
+
200
+ const html = res.getHtml()
201
+ const headEnd = html.indexOf("</head>")
202
+
203
+ // Critical CSS inlined as <style> from disk.
204
+ expect(html).toContain("<style>.home{color:red}</style>")
205
+ expect(html.indexOf("<style>.home{color:red}</style>")).toBeLessThan(headEnd)
206
+
207
+ // Critical JS as <script type="module"> + a modulepreload link, both
208
+ // in <head>. URL = PUBLIC_STATIC_ASSET_URL + PUBLIC_STATIC_ASSET_PATH
209
+ // + manifest file.
210
+ const jsUrl = "http://localhost/assets/home.abc123.js"
211
+ expect(html).toContain(`<script type="module" src="${jsUrl}">`)
212
+ expect(html).toContain(`<link rel="modulepreload" href="${jsUrl}"`)
213
+ expect(html.indexOf(`<script type="module" src="${jsUrl}">`)).toBeLessThan(headEnd)
214
+ })
215
+
216
+ it("suppresses module scripts and modulepreload links for a bot UA, but still inlines critical CSS", async () => {
217
+ vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
218
+ mockManifest()
219
+
220
+ const { default: handler } = await import("../../src/server/renderer/handler.jsx")
221
+ const { req, res } = makeReqRes(
222
+ "/",
223
+ "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
224
+ )
225
+
226
+ await handler(req, res)
227
+ await res.waitForEnd()
228
+
229
+ const html = res.getHtml()
230
+ expect(html).toContain("window.__CATALYST_IS_BOT__=true")
231
+ // CSS is not JS — bots still get it inlined (no FOUC).
232
+ expect(html).toContain("<style>.home{color:red}</style>")
233
+ // !isBot gates in Head.jsx (jsScripts, criticalPreloadLinks).
234
+ expect(html).not.toContain('<script type="module" src="http://localhost/assets/home.abc123.js">')
235
+ expect(html).not.toContain('rel="modulepreload"')
236
+ })
237
+ })
238
+
239
+ describe("SSR handler — deferred assets discovered during render", () => {
240
+ it("serializes render-tracked component keys and flushes deferred CSS/JS after the document", async () => {
241
+ vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
242
+ mockManifest()
243
+
244
+ const { default: handler } = await import("../../src/server/renderer/handler.jsx")
245
+ const { req, res } = makeReqRes("/widget")
246
+
247
+ await handler(req, res)
248
+ await res.waitForEnd()
249
+
250
+ const html = res.getHtml()
251
+ const htmlEnd = html.indexOf("</html>")
252
+
253
+ // WidgetPage called chunkExtractor.addComponent(WIDGET_CACHE_KEY)
254
+ // during render (the Split.jsx server path). getRenderedComponentKeys
255
+ // now reflects it.
256
+ expect(html).toContain('window.__SSR_RENDERED_COMPONENTS__=new Set(["src/js/components/Widget"])')
257
+
258
+ // Deferred chunk's CSS is flushed as a post-</html> <style> on the
259
+ // first visit (not yet route-cached), and its JS as a module script.
260
+ const deferredStyleIdx = html.indexOf("<style>.widget{color:blue}</style>")
261
+ expect(deferredStyleIdx).toBeGreaterThan(htmlEnd)
262
+ expect(html).toContain(
263
+ '<script type="module" src="http://localhost/assets/widget.def456.js"></script>',
264
+ )
265
+ expect(html.indexOf('src="http://localhost/assets/widget.def456.js"')).toBeGreaterThan(htmlEnd)
266
+ })
267
+
268
+ it("inlines previously-deferred CSS in <head> on the second visit to the same route", async () => {
269
+ vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
270
+ mockManifest()
271
+
272
+ const { default: handler } = await import("../../src/server/renderer/handler.jsx")
273
+
274
+ // First visit — deferred CSS learned, flushed after </html>.
275
+ const first = makeReqRes("/widget")
276
+ await handler(first.req, first.res)
277
+ await first.res.waitForEnd()
278
+ const firstHtml = first.res.getHtml()
279
+ expect(firstHtml.indexOf("<style>.widget{color:blue}</style>")).toBeGreaterThan(
280
+ firstHtml.indexOf("</html>"),
281
+ )
282
+
283
+ // Second visit — same route key. getCachedDeferredCssPathsForRoute
284
+ // now returns widget.def456.css, so it is inlined in <head> via
285
+ // deferredRouteInlineCss (Head.jsx:42) and NOT re-flushed after body
286
+ // (registerDeferredAssetsForRoute reports it as already seen).
287
+ const second = makeReqRes("/widget")
288
+ await handler(second.req, second.res)
289
+ await second.res.waitForEnd()
290
+ const secondHtml = second.res.getHtml()
291
+
292
+ const headEnd = secondHtml.indexOf("</head>")
293
+ const htmlEnd = secondHtml.indexOf("</html>")
294
+ const styleIdx = secondHtml.indexOf("<style>.widget{color:blue}</style>")
295
+ expect(styleIdx).toBeGreaterThan(-1)
296
+ expect(styleIdx).toBeLessThan(headEnd)
297
+ // No second post-body <style> for the same CSS.
298
+ expect(secondHtml.indexOf("<style>.widget{color:blue}</style>", htmlEnd)).toBe(-1)
299
+
300
+ // Warm-cache modulepreload for the deferred JS learned last visit
301
+ // (getDeferredPreloadScriptUrls -> deferredPreloadLinks, Head.jsx:33).
302
+ const preloadIdx = secondHtml.indexOf(
303
+ '<link rel="modulepreload" href="http://localhost/assets/widget.def456.js"',
304
+ )
305
+ expect(preloadIdx).toBeGreaterThan(-1)
306
+ expect(preloadIdx).toBeLessThan(headEnd)
307
+ })
308
+
309
+ it("does not flush deferred JS after the document for a bot UA", async () => {
310
+ vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
311
+ mockManifest()
312
+
313
+ const { default: handler } = await import("../../src/server/renderer/handler.jsx")
314
+ const { req, res } = makeReqRes(
315
+ "/widget",
316
+ "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
317
+ )
318
+
319
+ await handler(req, res)
320
+ await res.waitForEnd()
321
+
322
+ const html = res.getHtml()
323
+ const htmlEnd = html.indexOf("</html>")
324
+ // Deferred CSS still flushed (bots get styles); deferred JS is gated
325
+ // behind !isBot in the tail flush (handler.jsx:312) and skipped in
326
+ // registerDeferredAssetsForRoute.
327
+ expect(html.indexOf("<style>.widget{color:blue}</style>")).toBeGreaterThan(htmlEnd)
328
+ expect(html).not.toContain("widget.def456.js")
329
+ })
330
+ })