@lowdefy/server-dev 6.1.0 → 7.1.0

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 (624) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +2 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +222 -36
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  104. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  105. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  106. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  107. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +133 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +83 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
  133. package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
  134. package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
  135. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  136. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
  137. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  138. package/lib/docs/devEventBus.js +136 -0
  139. package/lib/docs/devEventBus.test.mjs +269 -0
  140. package/lib/docs/devToolDefinitions.js +529 -0
  141. package/lib/docs/docSources.js +21 -0
  142. package/lib/docs/docs.test.mjs +171 -15
  143. package/lib/docs/docsIndex.test.mjs +377 -0
  144. package/lib/docs/evalOperator.js +5 -3
  145. package/lib/docs/evalOperatorHeadless.js +84 -12
  146. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  147. package/lib/docs/evalOperatorInTab.js +2 -1
  148. package/lib/docs/findConfig.js +95 -49
  149. package/lib/docs/fitResponse.js +81 -0
  150. package/lib/docs/fitResponse.test.mjs +128 -0
  151. package/lib/docs/formatFeedback.js +26 -5
  152. package/lib/docs/formatFeedback.test.mjs +48 -0
  153. package/lib/docs/getAppMap.js +7 -1
  154. package/lib/docs/getBrowser.js +150 -80
  155. package/lib/docs/getBrowser.test.mjs +407 -16
  156. package/lib/docs/getBuildId.js +61 -0
  157. package/lib/docs/getBuildId.test.mjs +84 -0
  158. package/lib/docs/getBuildStatus.js +81 -7
  159. package/lib/docs/getBuildStatus.test.mjs +229 -0
  160. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  161. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  162. package/lib/docs/getCoreDoc.js +9 -1
  163. package/lib/docs/getDoc.js +100 -0
  164. package/lib/docs/getDocsIndex.js +152 -0
  165. package/lib/docs/getHazards.js +115 -0
  166. package/lib/docs/getHazards.test.mjs +175 -0
  167. package/lib/docs/getMailOutboxDirectory.js +26 -0
  168. package/lib/docs/getOverview.js +79 -13
  169. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  170. package/lib/docs/getPageBuildStatus.js +46 -0
  171. package/lib/docs/getPluginDoc.js +21 -23
  172. package/lib/docs/getSchema.js +33 -1
  173. package/lib/docs/getStaleStatus.js +36 -0
  174. package/lib/docs/getStaleStatus.test.mjs +74 -0
  175. package/lib/docs/guardJourneyOrigin.js +39 -0
  176. package/lib/docs/inspectState.js +5 -3
  177. package/lib/docs/inspectStateFromTab.js +2 -2
  178. package/lib/docs/inspectStateHeadless.js +89 -27
  179. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  180. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  181. package/lib/docs/installHeadlessShell.js +123 -0
  182. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  183. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  184. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  185. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  186. package/lib/docs/isPageReady.js +77 -0
  187. package/lib/docs/isPageReady.test.mjs +235 -0
  188. package/lib/docs/isRunErrorEntry.js +25 -0
  189. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  190. package/lib/docs/journeyRunRecording.js +35 -0
  191. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  192. package/lib/docs/keepEventStreamAlive.js +68 -0
  193. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  194. package/lib/docs/launchBrowser.js +83 -0
  195. package/lib/docs/launchBrowser.test.mjs +175 -0
  196. package/lib/docs/listModuleDocEntries.js +69 -0
  197. package/lib/docs/listPackageDocFiles.js +67 -0
  198. package/lib/docs/listPluginDocEntries.js +111 -0
  199. package/lib/docs/listPlugins.js +9 -58
  200. package/lib/docs/listTypes.js +14 -6
  201. package/lib/docs/loadFrameworkHazards.js +40 -0
  202. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  203. package/lib/docs/loadState.js +103 -35
  204. package/lib/docs/makeModuleManifestDoc.js +174 -0
  205. package/lib/docs/matchExercisedPages.js +35 -0
  206. package/lib/docs/matchJourneyRoute.js +69 -0
  207. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  208. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  209. package/lib/docs/nextJourneySequence.js +30 -0
  210. package/lib/docs/noBrowserError.js +22 -0
  211. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  212. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  213. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  214. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  215. package/lib/docs/observe/evaluateInvariants.js +183 -0
  216. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  217. package/lib/docs/observe/findingKey.js +29 -0
  218. package/lib/docs/observe/hasNoEffect.js +31 -0
  219. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  220. package/lib/docs/observe/installStepObserver.js +72 -0
  221. package/lib/docs/observe/isAppApiUrl.js +33 -0
  222. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  223. package/lib/docs/observe/readStepObserver.js +42 -0
  224. package/lib/docs/observe/relativeSource.js +29 -0
  225. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  226. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  227. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  228. package/lib/docs/observe/usesSearchStage.js +45 -0
  229. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  230. package/lib/docs/observe/watchJourneyContext.js +75 -0
  231. package/lib/docs/observe/withAppErrors.js +31 -0
  232. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  233. package/lib/docs/openJourney.js +94 -0
  234. package/lib/docs/openJourneyEmail.js +41 -0
  235. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  236. package/lib/docs/pageBlocks.js +47 -0
  237. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  238. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  239. package/lib/docs/rails/authEngineActions.js +53 -0
  240. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  241. package/lib/docs/rails/authEngineMethods.js +52 -0
  242. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  243. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  244. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  245. package/lib/docs/rails/checkClickRails.js +56 -0
  246. package/lib/docs/rails/createDataSetRails.js +82 -0
  247. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  248. package/lib/docs/rails/findExternalBlocks.js +147 -0
  249. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  250. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  251. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  252. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  253. package/lib/docs/readConnectionPlugins.js +26 -0
  254. package/lib/docs/readDevAuthMode.js +32 -0
  255. package/lib/docs/readDocEntry.js +40 -0
  256. package/lib/docs/readDocTitle.js +26 -0
  257. package/lib/docs/readJourneyEmailMatch.js +57 -0
  258. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  259. package/lib/docs/readJourneySession.js +56 -0
  260. package/lib/docs/readMailOutbox.js +37 -0
  261. package/lib/docs/readPagePath.js +27 -0
  262. package/lib/docs/readPluginPackages.js +99 -0
  263. package/lib/docs/readProxyBuildWait.js +38 -0
  264. package/lib/docs/readRoutineSteps.js +45 -0
  265. package/lib/docs/registerShownTextEngine.js +118 -0
  266. package/lib/docs/requestRestart.js +30 -0
  267. package/lib/docs/requestRestart.test.mjs +49 -0
  268. package/lib/docs/resolvePageInstance.js +46 -0
  269. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  270. package/lib/docs/resolveSource.js +11 -7
  271. package/lib/docs/resolveSource.test.mjs +1 -1
  272. package/lib/docs/resolveToolCaller.js +62 -0
  273. package/lib/docs/reviewPage.js +51 -0
  274. package/lib/docs/reviewPageBuilds.js +56 -0
  275. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  276. package/lib/docs/runCheck.js +75 -0
  277. package/lib/docs/runCheck.test.mjs +91 -0
  278. package/lib/docs/runCheckChild.js +49 -0
  279. package/lib/docs/runEndpoint.js +185 -0
  280. package/lib/docs/runEndpoint.test.mjs +509 -0
  281. package/lib/docs/runErrorBuffers.js +99 -0
  282. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  283. package/lib/docs/runJourney.js +347 -0
  284. package/lib/docs/runJourney.test.mjs +2383 -0
  285. package/lib/docs/runJourneyAsUsers.js +44 -0
  286. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  287. package/lib/docs/runJourneySteps.js +1320 -0
  288. package/lib/docs/runRequest.js +100 -59
  289. package/lib/docs/runRequest.test.mjs +190 -0
  290. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  291. package/lib/docs/scaffoldPage.js +3 -1
  292. package/lib/docs/screenshotPage.js +163 -11
  293. package/lib/docs/screenshotPage.test.mjs +263 -1
  294. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  295. package/lib/docs/searchDocs.js +82 -30
  296. package/lib/docs/searchDocs.test.mjs +100 -0
  297. package/lib/docs/searchIcons.js +90 -0
  298. package/lib/docs/searchIcons.test.mjs +127 -0
  299. package/lib/docs/selectExercised.js +39 -0
  300. package/lib/docs/selectFinalState.js +66 -0
  301. package/lib/docs/selectFinalState.test.mjs +51 -0
  302. package/lib/docs/serverErrorStore.js +54 -0
  303. package/lib/docs/serverErrorStore.test.mjs +50 -0
  304. package/lib/docs/setupTestFixtures.mjs +133 -39
  305. package/lib/docs/shownTextSelector.js +28 -0
  306. package/lib/docs/snapshotState.js +13 -5
  307. package/lib/docs/splitSearchTerms.js +62 -0
  308. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  309. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  310. package/lib/docs/tabAvailable.js +6 -5
  311. package/lib/docs/tabChannel.js +127 -21
  312. package/lib/docs/tabChannel.test.mjs +187 -29
  313. package/lib/docs/unsettledPageNote.js +24 -0
  314. package/lib/docs/validateJourneyMail.js +36 -0
  315. package/lib/docs/validateJourneyTimeout.js +38 -0
  316. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  317. package/lib/docs/validateRecordingParam.js +42 -0
  318. package/lib/docs/validateStateSelection.js +33 -0
  319. package/lib/docs/validateStateSelection.test.mjs +33 -0
  320. package/lib/docs/validateViewport.js +51 -0
  321. package/lib/docs/validateViewport.test.mjs +54 -0
  322. package/lib/docs/waitForBuild.js +50 -0
  323. package/lib/docs/waitForBuild.test.mjs +75 -0
  324. package/lib/docs/waitForJourneyEmail.js +77 -0
  325. package/lib/docs/withBrowserSlot.js +45 -0
  326. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  327. package/lib/docs/withDataSession.js +53 -0
  328. package/lib/server/applyDataSetRedirect.js +92 -0
  329. package/lib/server/applyDataSetRedirect.test.mjs +446 -0
  330. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
  331. package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
  332. package/lib/server/auth/createSystemContext.js +73 -0
  333. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  334. package/lib/server/auth/getHeadlessUser.js +9 -8
  335. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  336. package/lib/server/auth/getMockUser.js +18 -14
  337. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  338. package/lib/server/auth/headlessUser.js +3 -2
  339. package/lib/server/auth/journeyActor.js +35 -0
  340. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  341. package/lib/server/checkPageRecord.js +44 -0
  342. package/lib/server/contextMapBudget.js +24 -0
  343. package/lib/server/contextMapBudget.test.mjs +98 -0
  344. package/lib/server/continueJitKeys.js +28 -0
  345. package/lib/server/continueJitKeys.test.mjs +43 -0
  346. package/lib/server/createDataSessionEndedError.js +33 -0
  347. package/lib/server/createDataSetRefusal.js +26 -0
  348. package/lib/server/createLowdefyContext.js +168 -41
  349. package/lib/server/createLowdefyContext.test.mjs +331 -8
  350. package/lib/server/getClientAddress.js +51 -0
  351. package/lib/server/getClientAddress.test.mjs +56 -0
  352. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  353. package/lib/server/hashConfigContent.js +26 -0
  354. package/lib/server/importAppCode.test.mjs +57 -0
  355. package/lib/server/isRoutineStep.js +27 -0
  356. package/lib/server/jitPageBuilder.js +309 -126
  357. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  358. package/lib/server/jitPageBuilder.test.mjs +218 -0
  359. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  360. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  361. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  362. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  363. package/lib/server/journeyCookies.js +22 -0
  364. package/lib/server/journeyCookies.test.mjs +145 -0
  365. package/lib/server/loadDynamicJsMap.js +47 -0
  366. package/lib/server/log/createHandleError.js +38 -2
  367. package/lib/server/log/createHandleError.test.mjs +125 -2
  368. package/lib/server/log/createLogger.js +3 -0
  369. package/lib/server/log/createLogger.test.mjs +15 -0
  370. package/lib/server/log/logRequest.js +2 -3
  371. package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
  372. package/lib/server/mutants/applyMutant.js +39 -0
  373. package/lib/server/mutants/artifactPatterns.js +25 -0
  374. package/lib/server/mutants/buildAnchor.js +45 -0
  375. package/lib/server/mutants/collectSharedIds.js +41 -0
  376. package/lib/server/mutants/configPathSegments.js +38 -0
  377. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  378. package/lib/server/mutants/describeEvent.js +22 -0
  379. package/lib/server/mutants/enumerateArtifact.js +56 -0
  380. package/lib/server/mutants/findNodeByKey.js +45 -0
  381. package/lib/server/mutants/findNodeByPointer.js +49 -0
  382. package/lib/server/mutants/getArtifactKind.js +32 -0
  383. package/lib/server/mutants/groupMutantCopies.js +56 -0
  384. package/lib/server/mutants/hashArtifact.js +25 -0
  385. package/lib/server/mutants/hashMutantTarget.js +34 -0
  386. package/lib/server/mutants/listMutants.js +122 -0
  387. package/lib/server/mutants/listMutants.test.mjs +502 -0
  388. package/lib/server/mutants/mutantId.js +30 -0
  389. package/lib/server/mutants/mutantRuns.js +57 -0
  390. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  391. package/lib/server/mutants/operators/dropAction.js +47 -0
  392. package/lib/server/mutants/operators/dropBlock.js +44 -0
  393. package/lib/server/mutants/operators/dropPayload.js +52 -0
  394. package/lib/server/mutants/operators/dropStep.js +44 -0
  395. package/lib/server/mutants/operators/flipVisible.js +53 -0
  396. package/lib/server/mutants/operators/index.js +37 -0
  397. package/lib/server/mutants/operators/retargetLink.js +72 -0
  398. package/lib/server/mutants/operators/skipValidate.js +77 -0
  399. package/lib/server/mutants/operators/swapIf.js +112 -0
  400. package/lib/server/mutants/operators.test.mjs +396 -0
  401. package/lib/server/mutants/positionFreePath.js +38 -0
  402. package/lib/server/mutants/removeFromParent.js +28 -0
  403. package/lib/server/mutants/test/fixtures.mjs +173 -0
  404. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  405. package/lib/server/mutants/validateMutantParam.js +69 -0
  406. package/lib/server/mutants/validateMutantsBody.js +88 -0
  407. package/lib/server/mutants/walkArtifact.js +179 -0
  408. package/lib/server/pageBuildRecords.js +131 -0
  409. package/lib/server/pageBuildRecords.test.mjs +258 -0
  410. package/lib/server/pageCache.mjs +22 -7
  411. package/lib/server/pageCache.test.mjs +22 -7
  412. package/lib/server/pageRebuilds.test.mjs +543 -0
  413. package/lib/server/pruneJitMaps.js +36 -0
  414. package/lib/server/pruneJitMaps.test.mjs +55 -0
  415. package/lib/server/readInstanceRecord.js +33 -0
  416. package/lib/server/readInstanceRecord.test.mjs +99 -0
  417. package/lib/server/readMergedMaps.js +86 -0
  418. package/lib/server/readMergedMaps.test.mjs +79 -0
  419. package/lib/server/recording/appendRecords.js +33 -0
  420. package/lib/server/recording/errorRecordingStamp.js +34 -0
  421. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  422. package/lib/server/recording/isRecordingEnabled.js +31 -0
  423. package/lib/server/recording/readRecordingCookie.js +71 -0
  424. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  425. package/lib/server/recording/recordingCookiePayload.js +33 -0
  426. package/lib/server/recording/servedBuilds.js +46 -0
  427. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +25 -3
  439. package/manager/processes/initialBuild.mjs +17 -1
  440. package/manager/processes/initialBuild.test.mjs +104 -0
  441. package/manager/processes/lowdefyBuild.mjs +31 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +60 -47
  527. package/package.original.json +60 -47
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +173 -3
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +3 -1
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  621. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  622. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  623. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  624. package/src/routes/apiPage.js +0 -37
@@ -14,7 +14,7 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import React, { Suspense, useEffect, useState } from 'react';
17
+ import React, { Suspense, useCallback, useEffect, useState } from 'react';
18
18
 
19
19
  import Head from '@lowdefy/client/adapters/Head.js';
20
20
  import createLinkComponent from '@lowdefy/client/adapters/Link.js';
@@ -23,10 +23,12 @@ import BuildingPage from '../lib/client/BuildingPage.jsx';
23
23
  import RestartingPage from '../lib/client/RestartingPage.jsx';
24
24
  import FeedbackMount from './feedback/FeedbackMount.jsx';
25
25
  import Inspector from './Inspector.jsx';
26
+ import JourneyObserver from './JourneyObserver.jsx';
26
27
  import OpenInEditorListener from './openInEditor/OpenInEditorListener.jsx';
28
+ import Recorder from './Recorder.jsx';
27
29
  import Reload from './Reload.jsx';
28
30
  import Page from './Page.jsx';
29
- import setPageId from '../lib/client/setPageId.js';
31
+ import getPagePath from '../lib/client/getPagePath.js';
30
32
  import { bumpNavVersion, getReloadVersion } from '../lib/client/utils/useMutateCache.js';
31
33
  import useRootConfig from '../lib/client/utils/useRootConfig.js';
32
34
 
@@ -39,12 +41,18 @@ import staticJsMap from '../build/plugins/operators/clientJsMap.js';
39
41
 
40
42
  // Replaces lib/client/App.js — page resolution driven by the custom router
41
43
  // instead of next/router, everything else preserved.
42
- function Routing({ auth, lowdefy, router }) {
44
+ function Routing({ auth, lowdefy, recording, router }) {
43
45
  const { data: rootConfig } = useRootConfig(router.basePath);
44
46
  const [location, setLocation] = useState(() => router.getLocation());
45
47
 
46
48
  useEffect(() => {
47
49
  return router.subscribe((location) => {
50
+ // The URL already names the next page, but window.lowdefy.pageId and
51
+ // its context stay the previous page's until Page renders the next
52
+ // one. Marked here, synchronously inside the navigation (a Link in
53
+ // onInit included), so a headless caller waiting on isPageReady does
54
+ // not settle on the page being left. Page.jsx clears it.
55
+ lowdefy._devNavigating = true;
48
56
  // Dynamic pages re-resolve per navigation — bump before setLocation so
49
57
  // the page config SWR key changes with the navigation. The router
50
58
  // notifies with a fresh location object per event, so same-URL
@@ -52,40 +60,55 @@ function Routing({ auth, lowdefy, router }) {
52
60
  bumpNavVersion();
53
61
  setLocation(location);
54
62
  });
55
- }, [router]);
63
+ }, [lowdefy, router]);
56
64
 
57
65
  const [Link] = useState(() => createLinkComponent({ router }));
58
66
 
67
+ // The page instance on screen, { pageId, pathParams, instanceKey }, as the fetched page names
68
+ // it. null until the first page renders; it names the previous page while the next one fetches.
69
+ const [shownPage, setShownPage] = useState(null);
70
+ const onPageShown = useCallback((page) => {
71
+ setShownPage((current) => (current?.instanceKey === page.instanceKey ? current : page));
72
+ }, []);
73
+
59
74
  if (rootConfig?.theme) {
60
75
  lowdefy.theme = rootConfig.theme;
61
76
  }
62
77
 
63
- const { redirect, pageId } = setPageId(location, rootConfig);
78
+ // The page is fetched by path; the server says which page it is.
79
+ const { redirect, path } = getPagePath(location, rootConfig);
64
80
  useEffect(() => {
65
81
  if (redirect) {
66
- router.replace({ pathname: `/${pageId}` });
82
+ router.replace({ pathname: `/${path}` });
67
83
  }
68
- }, [redirect, pageId, router]);
84
+ }, [redirect, path, router]);
69
85
  if (redirect) {
70
86
  return '';
71
87
  }
72
-
73
88
  return (
74
89
  <>
75
- <FeedbackMount basePath={router.basePath} lowdefy={lowdefy} pageId={pageId} />
76
- <OpenInEditorListener basePath={router.basePath} pageId={pageId} />
90
+ <FeedbackMount basePath={router.basePath} lowdefy={lowdefy} page={shownPage} />
91
+ <OpenInEditorListener basePath={router.basePath} pageId={shownPage?.pageId} />
77
92
  <Reload basePath={router.basePath} lowdefy={lowdefy}>
78
93
  {(resetContext) => (
79
94
  <>
80
95
  {/* Inside Reload so it can share Reload's event stream (DevStreamContext). */}
81
- <Inspector basePath={router.basePath} lowdefy={lowdefy} pageId={pageId} />
96
+ <Inspector basePath={router.basePath} lowdefy={lowdefy} page={shownPage} />
97
+ <JourneyObserver lowdefy={lowdefy} />
98
+ <Recorder
99
+ basePath={router.basePath}
100
+ lowdefy={lowdefy}
101
+ page={shownPage}
102
+ path={path}
103
+ recording={recording}
104
+ />
82
105
  {/* Rendered here, not in Page — Page sits below the Suspense boundary
83
106
  and cannot render anything while its config fetch is suspended, so
84
107
  a restarting server would present as "Building page..." forever. */}
85
108
  {resetContext.restarting ? (
86
109
  <RestartingPage />
87
110
  ) : (
88
- <Suspense key={`${pageId}_${getReloadVersion()}`} fallback={<BuildingPage />}>
111
+ <Suspense key={`${path}_${getReloadVersion()}`} fallback={<BuildingPage />}>
89
112
  <Page
90
113
  auth={auth}
91
114
  Components={{ Head, Link }}
@@ -94,7 +117,8 @@ function Routing({ auth, lowdefy, router }) {
94
117
  }}
95
118
  jsMap={staticJsMap}
96
119
  lowdefy={lowdefy}
97
- pageId={pageId}
120
+ onPageShown={onPageShown}
121
+ path={path}
98
122
  resetContext={resetContext}
99
123
  router={router}
100
124
  types={{
@@ -36,6 +36,8 @@ const allowed = new Set([
36
36
  'plugins/icons.js',
37
37
  'plugins/operators/client.js',
38
38
  'plugins/operators/clientJsMap.js',
39
+ // The whole theme already reaches the browser in the root config.
40
+ 'theme.json',
39
41
  ]);
40
42
 
41
43
  const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -0,0 +1,40 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+
21
+ const clientDirectory = path.dirname(fileURLToPath(import.meta.url));
22
+
23
+ function listSourceFiles(directory) {
24
+ return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
25
+ const full = path.join(directory, entry.name);
26
+ if (entry.isDirectory()) return listSourceFiles(full);
27
+ return /\.(js|jsx|mjs)$/.test(entry.name) && !entry.name.includes('.test.') ? [full] : [];
28
+ });
29
+ }
30
+
31
+ // Dev serves HTTP/1.1, where a browser holds at most six connections per host.
32
+ // A second long-lived stream per tab once made dev pages load forever, so
33
+ // every dev channel shares Reload.jsx's one EventSource (DevStreamContext).
34
+ test('the dev client constructs exactly one EventSource, in Reload.jsx', () => {
35
+ const constructions = listSourceFiles(clientDirectory).flatMap((file) => {
36
+ const count = (fs.readFileSync(file, 'utf8').match(/new EventSource\(/g) ?? []).length;
37
+ return Array.from({ length: count }, () => path.relative(clientDirectory, file));
38
+ });
39
+ expect(constructions).toEqual(['Reload.jsx']);
40
+ });
@@ -53,7 +53,7 @@ const LazyOverlay = React.lazy(() => import('./FeedbackOverlay.jsx'));
53
53
  // contract — nothing here may ever throw into the app it's riding along
54
54
  // with. Stays inert until the developer presses Cmd/Ctrl+/, at which point
55
55
  // the overlay mounts.
56
- function FeedbackMount({ basePath, lowdefy, pageId }) {
56
+ function FeedbackMount({ basePath, lowdefy, page }) {
57
57
  const [active, setActive] = useState(false);
58
58
 
59
59
  useFeedbackToggle({
@@ -71,7 +71,7 @@ function FeedbackMount({ basePath, lowdefy, pageId }) {
71
71
  <LazyOverlay
72
72
  basePath={basePath}
73
73
  lowdefy={lowdefy}
74
- pageId={pageId}
74
+ page={page}
75
75
  onClose={() => setActive(false)}
76
76
  />
77
77
  </Suspense>
@@ -188,12 +188,15 @@ function buildAnnotation(state) {
188
188
  };
189
189
  }
190
190
 
191
- function buildBatch({ annotations, includeScreenshot, pageId }) {
191
+ function buildBatch({ annotations, includeScreenshot, page }) {
192
+ const pageId = page?.pageId;
193
+ const pathParams = page?.pathParams ?? {};
192
194
  return {
193
195
  batchId: generateId(),
194
196
  timestamp: new Date().toISOString(),
195
197
  pageId,
196
198
  url: `${window.location.pathname}${window.location.search}`,
199
+ pathParams,
197
200
  urlQuery: window.location.search,
198
201
  viewport: {
199
202
  width: window.innerWidth,
@@ -204,7 +207,7 @@ function buildBatch({ annotations, includeScreenshot, pageId }) {
204
207
  },
205
208
  annotations,
206
209
  includeScreenshot,
207
- stateRef: { captured: true, pageId },
210
+ stateRef: { captured: true, pageId, pathParams },
208
211
  };
209
212
  }
210
213
 
@@ -288,7 +291,7 @@ function renderShape(shape, key) {
288
291
  // app down with it — event handlers are individually try/caught, and the
289
292
  // whole render is wrapped so a bad state shape returns null rather than
290
293
  // throwing into the app's React tree.
291
- function FeedbackOverlay({ basePath, pageId, onClose }) {
294
+ function FeedbackOverlay({ basePath, page, onClose }) {
292
295
  const [state, dispatch] = useReducer(reducer, initialState);
293
296
  const primarySendRef = useRef(null);
294
297
  const stateRef = useRef(state);
@@ -624,7 +627,7 @@ function FeedbackOverlay({ basePath, pageId, onClose }) {
624
627
  const batch = buildBatch({
625
628
  annotations,
626
629
  includeScreenshot: current.includeScreenshot,
627
- pageId,
630
+ page,
628
631
  });
629
632
  // Capture the annotated screenshot in THIS tab — the pixels the
630
633
  // developer is actually looking at (theme, loaded data) — and ship it
@@ -18,6 +18,8 @@
18
18
  // above ErrorBar.jsx (bottom: 40px, zIndex 99999) but under nothing — this
19
19
  // overlay is the topmost thing on the page while active.
20
20
 
21
+ import getCspNonce from '../../lib/client/getCspNonce.js';
22
+
21
23
  const STYLE_TAG_ID = 'lowdefy-feedback-style-tag';
22
24
 
23
25
  const colors = {
@@ -248,6 +250,7 @@ function injectFeedbackStyleTag() {
248
250
  }
249
251
  const style = document.createElement('style');
250
252
  style.id = STYLE_TAG_ID;
253
+ style.nonce = getCspNonce();
251
254
  style.textContent = `
252
255
  .lowdefy-feedback-picking, .lowdefy-feedback-picking * {
253
256
  cursor: crosshair !important;
@@ -0,0 +1,101 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ const OBSERVE_BINDING = '__lowdefyJourneyObserve';
18
+
19
+ // Reports what a journey's page runs to the journey runner, through the
20
+ // binding the runner exposes on its own browser contexts only
21
+ // (lib/docs/createJourneyActors.js): every completed Lowdefy event from the
22
+ // engine's trace hook, and every block once it becomes visible, sampled in
23
+ // the animation frame after any DOM change so a message shown and cleared
24
+ // within one step is still seen. In a developer's tab there is no binding,
25
+ // and it does nothing. Returns a stop function, or null when it did not
26
+ // start. Never throws into the app: every callback is wrapped.
27
+ function startJourneyObserver({ window, lowdefy, getTrace, collectVisibleBlockIds }) {
28
+ const observe = window[OBSERVE_BINDING];
29
+ if (typeof observe !== 'function') {
30
+ return null;
31
+ }
32
+
33
+ function forward(message) {
34
+ try {
35
+ Promise.resolve(observe(message)).catch(() => {});
36
+ } catch {
37
+ // The binding is gone with its context; nothing to report to.
38
+ }
39
+ }
40
+
41
+ // The hook emits only completed events: bounced debounces and handledBy
42
+ // returns never arrive, so nothing is filtered here.
43
+ const unsubscribe = getTrace(lowdefy).subscribe((payload) => {
44
+ try {
45
+ forward({
46
+ kind: 'event',
47
+ scope: payload.scope,
48
+ pageId: payload.pageId,
49
+ blockId: payload.blockId,
50
+ eventName: payload.eventName,
51
+ actionIds: (payload.actions ?? []).map((action) => action?.id),
52
+ });
53
+ } catch {
54
+ // A payload this observer cannot read is skipped.
55
+ }
56
+ });
57
+
58
+ const sent = new Set();
59
+ let frame = null;
60
+ function sample() {
61
+ frame = null;
62
+ try {
63
+ const pageId = lowdefy.pageId;
64
+ const fresh = collectVisibleBlockIds().filter(
65
+ (blockId) => !sent.has(JSON.stringify([pageId, blockId]))
66
+ );
67
+ if (fresh.length === 0) {
68
+ return;
69
+ }
70
+ fresh.forEach((blockId) => sent.add(JSON.stringify([pageId, blockId])));
71
+ forward({ kind: 'rendered', pageId, blockIds: fresh });
72
+ } catch {
73
+ // Sampled again on the next change.
74
+ }
75
+ }
76
+
77
+ const observer = new window.MutationObserver(() => {
78
+ if (frame === null) {
79
+ frame = window.requestAnimationFrame(sample);
80
+ }
81
+ });
82
+ // Attributes too: antd shows and hides a modal or an alert by its style and
83
+ // class, without adding or removing nodes.
84
+ observer.observe(window.document.body, {
85
+ attributes: true,
86
+ characterData: true,
87
+ childList: true,
88
+ subtree: true,
89
+ });
90
+ sample();
91
+
92
+ return function stop() {
93
+ unsubscribe();
94
+ observer.disconnect();
95
+ if (frame !== null) {
96
+ window.cancelAnimationFrame(frame);
97
+ }
98
+ };
99
+ }
100
+
101
+ export default startJourneyObserver;
@@ -0,0 +1,149 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { jest } from '@jest/globals';
18
+
19
+ import startJourneyObserver from './startJourneyObserver.js';
20
+
21
+ function createFakeWindow({ binding } = {}) {
22
+ const observers = [];
23
+ const frames = [];
24
+ class FakeMutationObserver {
25
+ constructor(callback) {
26
+ this.callback = callback;
27
+ this.disconnect = jest.fn();
28
+ this.observe = jest.fn();
29
+ observers.push(this);
30
+ }
31
+ }
32
+ const window = {
33
+ document: { body: {} },
34
+ MutationObserver: FakeMutationObserver,
35
+ requestAnimationFrame: jest.fn((callback) => {
36
+ frames.push(callback);
37
+ return frames.length;
38
+ }),
39
+ cancelAnimationFrame: jest.fn(),
40
+ };
41
+ if (binding !== undefined) {
42
+ window.__lowdefyJourneyObserve = binding;
43
+ }
44
+ function mutate() {
45
+ observers.forEach((observer) => observer.callback([]));
46
+ }
47
+ function runFrames() {
48
+ frames.splice(0).forEach((callback) => callback());
49
+ }
50
+ return { window, observers, mutate, runFrames };
51
+ }
52
+
53
+ function createFakeTrace() {
54
+ const unsubscribe = jest.fn();
55
+ const trace = { subscribe: jest.fn(() => unsubscribe) };
56
+ return { getTrace: jest.fn(() => trace), trace, unsubscribe };
57
+ }
58
+
59
+ test('startJourneyObserver does nothing in a developer tab, where there is no binding', () => {
60
+ const { window, observers } = createFakeWindow();
61
+ const { getTrace, trace } = createFakeTrace();
62
+ const stop = startJourneyObserver({
63
+ window,
64
+ lowdefy: { pageId: 'home' },
65
+ getTrace,
66
+ collectVisibleBlockIds: jest.fn(() => ['a']),
67
+ });
68
+ expect(stop).toBeNull();
69
+ expect(trace.subscribe).not.toHaveBeenCalled();
70
+ expect(observers).toHaveLength(0);
71
+ });
72
+
73
+ test('startJourneyObserver forwards completed events and each newly visible block once', () => {
74
+ const binding = jest.fn(async () => {});
75
+ const { window, observers, mutate, runFrames } = createFakeWindow({ binding });
76
+ const { getTrace, trace, unsubscribe } = createFakeTrace();
77
+ const lowdefy = { pageId: 'home' };
78
+ let visible = ['home_title'];
79
+ const stop = startJourneyObserver({
80
+ window,
81
+ lowdefy,
82
+ getTrace,
83
+ collectVisibleBlockIds: () => visible,
84
+ });
85
+ expect(getTrace).toHaveBeenCalledWith(lowdefy);
86
+ // Subscribed without state: the observer never reads it.
87
+ expect(trace.subscribe.mock.calls[0]).toHaveLength(1);
88
+ expect(binding).toHaveBeenCalledWith({
89
+ kind: 'rendered',
90
+ pageId: 'home',
91
+ blockIds: ['home_title'],
92
+ });
93
+
94
+ const listener = trace.subscribe.mock.calls[0][0];
95
+ listener({
96
+ scope: 'page',
97
+ pageId: 'home',
98
+ blockId: 'save_button',
99
+ blockType: 'Button',
100
+ eventName: 'onClick',
101
+ actions: [{ id: 'save' }, { id: 'set_saved' }],
102
+ record: {},
103
+ });
104
+ expect(binding).toHaveBeenLastCalledWith({
105
+ kind: 'event',
106
+ scope: 'page',
107
+ pageId: 'home',
108
+ blockId: 'save_button',
109
+ eventName: 'onClick',
110
+ actionIds: ['save', 'set_saved'],
111
+ });
112
+
113
+ // Two changes before the next frame sample once; only the new block is sent.
114
+ visible = ['home_title', 'flash_alert'];
115
+ mutate();
116
+ mutate();
117
+ expect(window.requestAnimationFrame).toHaveBeenCalledTimes(1);
118
+ runFrames();
119
+ expect(binding).toHaveBeenLastCalledWith({
120
+ kind: 'rendered',
121
+ pageId: 'home',
122
+ blockIds: ['flash_alert'],
123
+ });
124
+ const calls = binding.mock.calls.length;
125
+ mutate();
126
+ runFrames();
127
+ expect(binding.mock.calls).toHaveLength(calls);
128
+
129
+ stop();
130
+ expect(unsubscribe).toHaveBeenCalled();
131
+ expect(observers[0].disconnect).toHaveBeenCalled();
132
+ });
133
+
134
+ test('startJourneyObserver keeps the app running when the binding or a payload throws', () => {
135
+ const binding = jest.fn(() => {
136
+ throw new Error('context closed');
137
+ });
138
+ const { window } = createFakeWindow({ binding });
139
+ const { getTrace, trace } = createFakeTrace();
140
+ startJourneyObserver({
141
+ window,
142
+ lowdefy: { pageId: 'home' },
143
+ getTrace,
144
+ collectVisibleBlockIds: () => ['a'],
145
+ });
146
+ const listener = trace.subscribe.mock.calls[0][0];
147
+ expect(() => listener(null)).not.toThrow();
148
+ expect(() => listener({ scope: 'page', actions: [] })).not.toThrow();
149
+ });
@@ -20,6 +20,7 @@ import { createPortal } from 'react-dom';
20
20
  import { blockAncestorChain, nearestBlock } from '../feedback/elementInspect.js';
21
21
  import { highlightBox, labelChip, overlayContainer } from '../feedback/feedbackStyles.js';
22
22
  import findBlockLocation from './findBlockLocation.js';
23
+ import getCspNonce from '../../lib/client/getCspNonce.js';
23
24
 
24
25
  const STYLE_TAG_ID = 'lowdefy-open-in-editor-style-tag';
25
26
  const BODY_CLASS = 'lowdefy-open-in-editor-target';
@@ -32,6 +33,7 @@ function injectStyleTag() {
32
33
  }
33
34
  const style = document.createElement('style');
34
35
  style.id = STYLE_TAG_ID;
36
+ style.nonce = getCspNonce();
35
37
  style.textContent = `
36
38
  .${BODY_CLASS}, .${BODY_CLASS} * {
37
39
  cursor: pointer !important;
@@ -0,0 +1,37 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ // An engine event no interaction caused (a mount event, an app event, a
18
+ // programmatic or off-path event) as a v1 trace record of kind engine. The
19
+ // compiler decides what each compiles to.
20
+ function buildEngineRecord({ engineEvent, session, roles }) {
21
+ return {
22
+ v: 1,
23
+ session,
24
+ t: new Date(engineEvent.startTimestamp).toISOString(),
25
+ kind: 'engine',
26
+ scope: engineEvent.scope,
27
+ page_id: engineEvent.pageId,
28
+ roles: roles ?? [],
29
+ person: null,
30
+ org: null,
31
+ target: null,
32
+ event: engineEvent.traceEvent,
33
+ build: engineEvent.build ?? null,
34
+ };
35
+ }
36
+
37
+ export default buildEngineRecord;
@@ -0,0 +1,64 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ // describeElement's result without what a record keeps elsewhere: the page
20
+ // sits on the record and the enclosing block ids feed pairing only.
21
+ function recordTarget(target) {
22
+ if (type.isNone(target)) return null;
23
+ const { page_id: _pageId, block_ids: _blockIds, ...rest } = target;
24
+ return rest;
25
+ }
26
+
27
+ // A DOM interaction as a v1 trace record. `paired` are the trace events the
28
+ // pairing rule gave it, innermost first: the first is `event`, the rest go to
29
+ // `also`. `build` is the build of the page config the interaction happened
30
+ // on; `source` and `run` are stamped by the dev server, never the page.
31
+ function buildInteractionRecord({ interaction, paired = [], session, roles }) {
32
+ const record = {
33
+ v: 1,
34
+ session,
35
+ t: new Date(interaction.t).toISOString(),
36
+ kind: interaction.kind,
37
+ scope: 'page',
38
+ page_id: interaction.target?.page_id ?? interaction.pageId,
39
+ roles: roles ?? [],
40
+ person: null,
41
+ org: null,
42
+ target: recordTarget(interaction.target),
43
+ event: paired[0] ?? null,
44
+ build: interaction.build ?? null,
45
+ };
46
+ if (paired.length > 1) {
47
+ record.also = paired.slice(1);
48
+ }
49
+ if (interaction.kind === 'pageview') {
50
+ record.url = interaction.url;
51
+ if (!type.isNone(interaction.pathParams)) {
52
+ record.path_params = interaction.pathParams;
53
+ }
54
+ }
55
+ if (interaction.kind === 'change') {
56
+ record.value = interaction.value ?? null;
57
+ }
58
+ if (interaction.kind === 'key') {
59
+ record.key = interaction.key;
60
+ }
61
+ return record;
62
+ }
63
+
64
+ export default buildInteractionRecord;
@@ -0,0 +1,57 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ import { type } from '@lowdefy/helpers';
18
+
19
+ import collectEventActions from './collectEventActions.js';
20
+ import collectEventEndpoints from './collectEventEndpoints.js';
21
+ import collectEventRequests from './collectEventRequests.js';
22
+ import diffStateWrites from './diffStateWrites.js';
23
+
24
+ // One completed engine event as a trace record's `event` (or `also` entry),
25
+ // built from the engine's trace payload the moment it arrives: the state the
26
+ // event wrote is the difference between the copy taken before its actions ran
27
+ // and the state now, which later events would change.
28
+ function buildTraceEvent({ payload, urlAfter }) {
29
+ const { context, failure, record } = payload;
30
+ const responses = record?.responses ?? {};
31
+ const event = {
32
+ name: payload.eventName,
33
+ block_id: payload.blockId,
34
+ success: payload.success,
35
+ actions: collectEventActions({ responses }),
36
+ requests: collectEventRequests({ blockId: payload.blockId, context, responses }),
37
+ endpoints: collectEventEndpoints({ blockId: payload.blockId, context, responses }),
38
+ state_writes: diffStateWrites({
39
+ before: payload.stateBefore,
40
+ after: context?.state,
41
+ values: true,
42
+ }),
43
+ url_after: urlAfter ?? null,
44
+ };
45
+ if (!type.isNone(failure)) {
46
+ event.error = {
47
+ name: failure.errorName ?? 'Error',
48
+ config_key: failure.configKey ?? null,
49
+ action_type: failure.actionType ?? null,
50
+ action_id: failure.actionId ?? null,
51
+ };
52
+ event.invalid_blocks = failure.invalidBlocks ?? [];
53
+ }
54
+ return event;
55
+ }
56
+
57
+ export default buildTraceEvent;