@lowdefy/server-dev 6.1.0 → 7.0.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 (623) 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 +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -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 +44 -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 +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -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 +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -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/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{session.js → getStrategies.js} +17 -16
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +168 -41
  348. package/lib/server/createLowdefyContext.test.mjs +328 -8
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +15 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/sinkExternalConnections.js +95 -0
  428. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  429. package/lib/server/skipStaleMapWrites.js +62 -0
  430. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  431. package/lib/server/startIdleGc.js +67 -0
  432. package/lib/server/startIdleGc.test.mjs +138 -0
  433. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  434. package/lib/server/trackSessionWork.js +30 -0
  435. package/lib/vite/createDevServerExclude.js +40 -0
  436. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  437. package/manager/getContext.mjs +24 -2
  438. package/manager/processes/initialBuild.mjs +14 -0
  439. package/manager/processes/initialBuild.test.mjs +101 -0
  440. package/manager/processes/lowdefyBuild.mjs +30 -7
  441. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  442. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  443. package/manager/processes/optimizeDependencies.mjs +59 -0
  444. package/manager/processes/restartServer.mjs +34 -6
  445. package/manager/processes/restartServer.test.mjs +162 -0
  446. package/manager/processes/shutdownServer.mjs +11 -0
  447. package/manager/processes/shutdownServer.test.mjs +45 -0
  448. package/manager/processes/startFirstServer.mjs +46 -0
  449. package/manager/processes/startFirstServer.test.mjs +326 -0
  450. package/manager/processes/startMailSink.mjs +135 -0
  451. package/manager/processes/startMailSink.test.mjs +146 -0
  452. package/manager/processes/startProxy.mjs +129 -12
  453. package/manager/processes/startProxy.test.mjs +366 -0
  454. package/manager/processes/startServer.mjs +47 -6
  455. package/manager/processes/startServer.test.mjs +155 -0
  456. package/manager/processes/startWatchers.mjs +6 -4
  457. package/manager/processes/syncServer.mjs +69 -0
  458. package/manager/processes/syncServer.test.mjs +141 -0
  459. package/manager/processes/waitForDevServer.mjs +48 -0
  460. package/manager/processes/waitForDevServer.test.mjs +112 -0
  461. package/manager/run.mjs +128 -43
  462. package/manager/utils/BatchChanges.mjs +21 -5
  463. package/manager/utils/BatchChanges.test.mjs +77 -0
  464. package/manager/utils/acquireDevInstance.mjs +99 -0
  465. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  466. package/manager/utils/createBuildActivity.mjs +67 -0
  467. package/manager/utils/createBuildActivity.test.mjs +58 -0
  468. package/manager/utils/createChangeSignal.mjs +39 -0
  469. package/manager/utils/createChangeSignal.test.mjs +62 -0
  470. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  471. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  472. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  473. package/manager/utils/createDotPathIgnore.mjs +44 -0
  474. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  475. package/manager/utils/createIdleGc.mjs +51 -0
  476. package/manager/utils/createIdleGc.test.mjs +77 -0
  477. package/manager/utils/createRequestActivity.mjs +100 -0
  478. package/manager/utils/createRequestActivity.test.mjs +121 -0
  479. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  480. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  481. package/manager/utils/createServerEnv.mjs +39 -0
  482. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  483. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  485. package/manager/utils/importFresh.mjs +46 -0
  486. package/manager/utils/importFresh.test.mjs +72 -0
  487. package/manager/utils/importFreshWorker.mjs +20 -0
  488. package/manager/utils/killTaggedBrowser.mjs +35 -0
  489. package/manager/utils/pagePathChanged.mjs +56 -0
  490. package/manager/utils/pruneRecordings.mjs +85 -0
  491. package/manager/utils/pruneRecordings.test.mjs +117 -0
  492. package/manager/utils/publishBuildDirectory.mjs +121 -0
  493. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  494. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  495. package/manager/utils/readBasePath.mjs +27 -0
  496. package/manager/utils/readBasePath.test.mjs +43 -0
  497. package/manager/utils/readBuildStatusWait.mjs +67 -0
  498. package/manager/utils/readManagerVersion.mjs +30 -0
  499. package/manager/utils/readManagerVersion.test.mjs +35 -0
  500. package/manager/utils/readPluginDefinitions.mjs +36 -0
  501. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  502. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  503. package/manager/utils/resolvePorts.mjs +49 -0
  504. package/manager/utils/setupWatcher.mjs +19 -11
  505. package/manager/utils/startRecordingPruner.mjs +45 -0
  506. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  507. package/manager/utils/waitForChildExit.mjs +53 -0
  508. package/manager/utils/waitForChildExit.test.mjs +70 -0
  509. package/manager/utils/waitForServer.mjs +44 -0
  510. package/manager/utils/writeBuildStatus.mjs +1 -1
  511. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  512. package/manager/watchers/envWatcher.mjs +9 -2
  513. package/manager/watchers/envWatcher.test.mjs +87 -0
  514. package/manager/watchers/listServerSidePackages.mjs +47 -0
  515. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  516. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  517. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  518. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  519. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  520. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  521. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  522. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  523. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  524. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  525. package/package.json +60 -47
  526. package/package.original.json +60 -47
  527. package/src/app.js +131 -20
  528. package/src/html/renderDevPage.js +63 -11
  529. package/src/html/renderDevPage.test.mjs +138 -0
  530. package/src/lib/getRequestPath.js +29 -0
  531. package/src/middleware/createSameOriginGuard.js +37 -0
  532. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  533. package/src/middleware/errorHandler.js +51 -1
  534. package/src/middleware/errorHandler.test.mjs +173 -3
  535. package/src/middleware/isRebindingSafeHost.js +44 -0
  536. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  537. package/src/middleware/localDevToolsOnly.js +38 -0
  538. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  539. package/src/middleware/noStore.js +27 -0
  540. package/src/middleware/noStore.test.mjs +48 -0
  541. package/src/middleware/requestTimeout.js +38 -0
  542. package/src/middleware/staleFlag.js +76 -0
  543. package/src/middleware/staleFlag.test.mjs +127 -0
  544. package/src/middleware/stampCaller.js +38 -0
  545. package/src/middleware/stampCaller.test.mjs +64 -0
  546. package/src/routes/agent.js +15 -3
  547. package/src/routes/auth.js +29 -20
  548. package/src/routes/auth.test.mjs +30 -56
  549. package/src/routes/clientError.js +13 -13
  550. package/src/routes/cron.js +3 -1
  551. package/src/routes/cronForward.js +3 -1
  552. package/src/routes/detached.js +22 -9
  553. package/src/routes/devInspect.js +17 -8
  554. package/src/routes/devInspect.test.mjs +44 -7
  555. package/src/routes/devRecording.js +113 -0
  556. package/src/routes/devRecording.test.mjs +253 -0
  557. package/src/routes/docs/buildStatus.js +10 -2
  558. package/src/routes/docs/buildStatus.test.mjs +46 -0
  559. package/src/routes/docs/content.js +5 -3
  560. package/src/routes/docs/evalOperator.js +15 -1
  561. package/src/routes/docs/events.js +50 -0
  562. package/src/routes/docs/events.test.mjs +117 -0
  563. package/src/routes/docs/find.js +9 -1
  564. package/src/routes/docs/icons.js +30 -0
  565. package/src/routes/docs/inspectState.js +16 -1
  566. package/src/routes/docs/journey.js +194 -0
  567. package/src/routes/docs/journey.test.mjs +401 -0
  568. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  569. package/src/routes/docs/loadState.js +4 -2
  570. package/src/routes/docs/mcp.js +34 -3
  571. package/src/routes/docs/mcp.test.mjs +130 -0
  572. package/src/routes/docs/mutants.js +78 -0
  573. package/src/routes/docs/mutants.test.mjs +180 -0
  574. package/src/routes/docs/parsePathParamsParam.js +41 -0
  575. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  576. package/src/routes/docs/parseUserParam.js +27 -21
  577. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  578. package/src/routes/docs/restart.js +35 -0
  579. package/src/routes/docs/restart.test.mjs +64 -0
  580. package/src/routes/docs/runEndpoint.js +54 -0
  581. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  582. package/src/routes/docs/runRequest.js +25 -8
  583. package/src/routes/docs/runRequest.test.mjs +92 -0
  584. package/src/routes/docs/screenshot.js +50 -2
  585. package/src/routes/docs/screenshot.test.mjs +117 -0
  586. package/src/routes/docs/search.js +15 -2
  587. package/src/routes/docs/snapshotState.js +17 -3
  588. package/src/routes/endpoints.js +27 -26
  589. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  590. package/src/routes/feedback.js +10 -13
  591. package/src/routes/jitPage.js +99 -35
  592. package/src/routes/jitPage.test.mjs +248 -0
  593. package/src/routes/mcp.js +82 -5
  594. package/src/routes/mcp.test.mjs +215 -0
  595. package/src/routes/mcpMemberToken.test.mjs +125 -0
  596. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  597. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  598. package/src/routes/mountDevPageRoutes.js +25 -0
  599. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  600. package/src/routes/mountOauthDiscovery.js +44 -0
  601. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  602. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  604. package/src/routes/reload.js +16 -0
  605. package/src/routes/request.js +6 -4
  606. package/src/routes/request.test.mjs +9 -5
  607. package/src/routes/requestBody.test.mjs +159 -0
  608. package/src/routes/usage.js +6 -2
  609. package/src/routes/user.js +27 -0
  610. package/src/routes/websocket.js +29 -8
  611. package/src/routes/websocket.test.mjs +65 -0
  612. package/src/routes/wellKnownFallback.js +30 -0
  613. package/src/routes/wellKnownFallback.test.mjs +32 -0
  614. package/src/websocket/devWebSocket.js +41 -26
  615. package/src/websocket/devWebSocket.test.mjs +157 -0
  616. package/vite.config.js +53 -23
  617. package/lib/server/auth/getDevSession.js +0 -70
  618. package/lib/server/auth/getDevSession.test.mjs +0 -138
  619. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  620. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  621. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  622. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  623. package/src/routes/apiPage.js +0 -37
package/client/App.jsx CHANGED
@@ -18,19 +18,30 @@ import React, { Suspense, useCallback, useEffect, useRef, useState } from 'react
18
18
  import useSWR from 'swr';
19
19
 
20
20
  import { ErrorBoundary } from '@lowdefy/block-utils';
21
- import { useDarkMode, useLocale } from '@lowdefy/client';
21
+ import { IconProvider, useDarkMode, useLocale } from '@lowdefy/client';
22
22
  import { StyleProvider } from '@ant-design/cssinjs';
23
23
  import { App as AntdApp, theme as antdTheme } from 'antd';
24
24
  import { XProvider } from '@ant-design/x';
25
+ import { appStorageKey, serializer } from '@lowdefy/helpers';
25
26
 
26
27
  import antdLocaleLoaders from '../build/i18n/antdLocales.js';
27
28
  import antdXLocaleLoaders from '../build/i18n/antdXLocales.js';
28
29
  import dayjsLocaleMap from '../build/i18n/dayjsLocales.js';
30
+ import rawTheme from '../build/theme.json';
29
31
  import Auth from '../lib/client/auth/Auth.jsx';
30
32
  import ErrorBar from '../lib/client/ErrorBar.jsx';
33
+ import getCspNonce from '../lib/client/getCspNonce.js';
31
34
  import request from '../lib/client/utils/request.js';
32
35
  import Routing from './Routing.jsx';
33
36
 
37
+ // rootConfig arrives after the first render; the build-written theme gives
38
+ // the icon provider its settings until then.
39
+ const buildTheme = serializer.deserialize(rawTheme);
40
+
41
+ // antd and antd X put this nonce on the style tags they add, so the page's
42
+ // Content-Security-Policy applies them. Empty (no nonce) when the policy is off.
43
+ const csp = { nonce: getCspNonce() };
44
+
34
45
  function ThemeTokenResolver({ lowdefyRef, children }) {
35
46
  const { token } = antdTheme.useToken();
36
47
  if (!lowdefyRef.current.theme) {
@@ -40,7 +51,7 @@ function ThemeTokenResolver({ lowdefyRef, children }) {
40
51
  return children;
41
52
  }
42
53
 
43
- function App({ router }) {
54
+ function App({ config, router }) {
44
55
  const lowdefyRef = useRef({});
45
56
  const [runtimeErrors, setRuntimeErrors] = useState([]);
46
57
  // Subscribe to rootConfig SWR cache — deduplicates with Routing's fetch.
@@ -50,9 +61,12 @@ function App({ router }) {
50
61
  lowdefyRef.current.theme = rootConfig.theme;
51
62
  }
52
63
 
64
+ // Device preferences, kept per app (the same key the HTML template's dark mode script reads).
65
+ const storageScope = { basePath: router.basePath, slug: config.slug };
53
66
  const { algorithm, token, components } = useDarkMode({
54
67
  antd: lowdefyRef.current.theme?.antd,
55
68
  configDarkMode: lowdefyRef.current.theme?.darkMode,
69
+ storageKey: appStorageKey({ ...storageScope, name: 'lowdefy_darkMode' }),
56
70
  });
57
71
 
58
72
  const {
@@ -64,6 +78,7 @@ function App({ router }) {
64
78
  antdLocaleLoaders,
65
79
  antdXLocaleLoaders,
66
80
  dayjsLocaleMap,
81
+ storageKey: appStorageKey({ ...storageScope, name: 'lowdefy_locale' }),
67
82
  });
68
83
 
69
84
  if (rootConfig?.i18n?.defaultLocale) {
@@ -88,6 +103,7 @@ function App({ router }) {
88
103
  message: error.message,
89
104
  source: error.source,
90
105
  stack: error.stack,
106
+ prodError: error.prodError === true,
91
107
  },
92
108
  ]);
93
109
  }, []);
@@ -115,6 +131,7 @@ function App({ router }) {
115
131
  return (
116
132
  <StyleProvider layer>
117
133
  <XProvider
134
+ csp={csp}
118
135
  locale={mergedLocale}
119
136
  form={
120
137
  antdLocale?.Form?.defaultValidateMessages
@@ -130,29 +147,40 @@ function App({ router }) {
130
147
  algorithm,
131
148
  }}
132
149
  >
133
- <AntdApp>
134
- <ThemeTokenResolver lowdefyRef={lowdefyRef}>
135
- <ErrorBoundary fullPage onError={handleError}>
136
- <Suspense
137
- fallback={
138
- <div
139
- style={{
140
- minHeight: '100vh',
141
- background: 'var(--ant-color-bg-layout)',
150
+ {/* Message, Notification and ConfirmModal render in the App.useApp()
151
+ holders here, above the Client's icon provider. */}
152
+ <IconProvider icons={rootConfig?.theme.icons ?? buildTheme.icons}>
153
+ <AntdApp>
154
+ <ThemeTokenResolver lowdefyRef={lowdefyRef}>
155
+ <ErrorBoundary fullPage onError={handleError}>
156
+ <Suspense
157
+ fallback={
158
+ <div
159
+ style={{
160
+ minHeight: '100vh',
161
+ background: 'var(--ant-color-bg-layout)',
162
+ }}
163
+ />
164
+ }
165
+ >
166
+ <Auth user={config?.user}>
167
+ {(auth) => {
168
+ return (
169
+ <Routing
170
+ auth={auth}
171
+ lowdefy={lowdefyRef.current}
172
+ recording={config?.recording}
173
+ router={router}
174
+ />
175
+ );
142
176
  }}
143
- />
144
- }
145
- >
146
- <Auth>
147
- {(auth) => {
148
- return <Routing auth={auth} lowdefy={lowdefyRef.current} router={router} />;
149
- }}
150
- </Auth>
151
- </Suspense>
152
- </ErrorBoundary>
153
- <ErrorBar errors={runtimeErrors} />
154
- </ThemeTokenResolver>
155
- </AntdApp>
177
+ </Auth>
178
+ </Suspense>
179
+ </ErrorBoundary>
180
+ <ErrorBar errors={runtimeErrors} />
181
+ </ThemeTokenResolver>
182
+ </AntdApp>
183
+ </IconProvider>
156
184
  </XProvider>
157
185
  </StyleProvider>
158
186
  );
@@ -26,11 +26,13 @@ import DevStreamContext from './DevStreamContext.js';
26
26
  // stream per tab exhausted the browser's HTTP/1.1 connection pool once a few
27
27
  // tabs of the same app were open, and every later fetch queued forever.
28
28
  //
29
- // pageId tracking: routes/reload.js registers every connection as an
29
+ // Page tracking: routes/reload.js registers every connection as an
30
30
  // inspectable tab (lib/docs/tabChannel.js) and announces the tab id on the
31
- // stream. This component then posts { tabId, pageId } to
32
- // /api/dev-inspect/page whenever the developer navigates, so the registry's
33
- // view of "what page is this tab on" stays current without reconnecting.
31
+ // stream. This component then posts { tabId, pageId, pathParams, instanceKey }
32
+ // to /api/dev-inspect/page whenever the page instance on screen changes, so the
33
+ // registry's view of "what instance is this tab on" stays current without
34
+ // reconnecting. A request names the instance to read (the dev server picks
35
+ // it); without one it reads the instance on screen.
34
36
  //
35
37
  // Never let a bad payload or a plugin's operator error crash the app this is
36
38
  // piggybacking on — every handler is wrapped, and the component itself
@@ -55,23 +57,62 @@ function postResult({ basePath, requestId, result }) {
55
57
  postJson({ basePath, path: '', body: { requestId, result } });
56
58
  }
57
59
 
58
- function postTabPage({ basePath, tabId, pageId }) {
59
- postJson({ basePath, path: '/page', body: { tabId, pageId } });
60
+ function postTabPage({ basePath, tabId, page }) {
61
+ postJson({
62
+ basePath,
63
+ path: '/page',
64
+ body: {
65
+ tabId,
66
+ pageId: page.pageId,
67
+ pathParams: page.pathParams,
68
+ instanceKey: page.instanceKey,
69
+ },
70
+ });
71
+ }
72
+
73
+ // `page "ticket"`, or `page "ticket" (tickets/s/1)` for an instance of a page with placeholders,
74
+ // whose instance key ends in the path its values build.
75
+ function describePage({ pageId, instanceKey }) {
76
+ const hashIndex = instanceKey.indexOf('#');
77
+ if (hashIndex === -1) {
78
+ return `page "${pageId}"`;
79
+ }
80
+ return `page "${pageId}" (${instanceKey.slice(hashIndex + 1)})`;
81
+ }
82
+
83
+ function requestedPage({ data, shownPage }) {
84
+ if (type.isString(data.instanceKey)) {
85
+ return { pageId: data.pageId, pathParams: data.pathParams, instanceKey: data.instanceKey };
86
+ }
87
+ return shownPage;
60
88
  }
61
89
 
62
- function buildSnapshot({ lowdefy, pageId }) {
63
- const context = lowdefy?.contexts?.[`page:${pageId}`];
90
+ function findContext({ lowdefy, page }) {
91
+ if (type.isNone(page)) {
92
+ return { error: 'No page has rendered in this tab yet.' };
93
+ }
94
+ const context = lowdefy?.contexts?.[page.instanceKey];
64
95
  if (type.isNone(context)) {
65
- return { error: `No live context for page "${pageId}".` };
96
+ return { error: `No live context for ${describePage(page)}.` };
97
+ }
98
+ return { context };
99
+ }
100
+
101
+ function buildSnapshot({ lowdefy, page }) {
102
+ const { context, error } = findContext({ lowdefy, page });
103
+ if (error) {
104
+ return { error };
66
105
  }
67
106
  return serializer.serializeToString({
68
- pageId,
107
+ pageId: page.pageId,
108
+ pathParams: context.pathParams,
109
+ instanceKey: page.instanceKey,
69
110
  state: context.state,
70
111
  requests: context.requests,
71
112
  eventLog: (context.eventLog ?? []).slice(-50),
72
113
  global: lowdefy.lowdefyGlobal,
73
114
  user: lowdefy.user,
74
- input: lowdefy.inputs?.[`page:${pageId}`],
115
+ input: lowdefy.inputs?.[page.instanceKey],
75
116
  urlQuery: window.location.search,
76
117
  });
77
118
  }
@@ -88,7 +129,7 @@ function buildSnapshot({ lowdefy, pageId }) {
88
129
  // has finished mounting. Never lets a bad checkpoint or a missing context
89
130
  // break the app — every step is try/catch, and failures just leave the app
90
131
  // running unmodified.
91
- async function bootstrapFromCheckpoint({ basePath, pageId, checkpointName, isCancelled }) {
132
+ async function bootstrapFromCheckpoint({ basePath, instanceKey, checkpointName, isCancelled }) {
92
133
  try {
93
134
  const response = await fetch(
94
135
  `${basePath}/api/dev-inspect/checkpoint/${encodeURIComponent(checkpointName)}`
@@ -106,7 +147,7 @@ async function bootstrapFromCheckpoint({ basePath, pageId, checkpointName, isCan
106
147
  const deadline = Date.now() + 5000;
107
148
  let context;
108
149
  while (!isCancelled() && Date.now() < deadline) {
109
- context = window.lowdefy?.contexts?.[`page:${pageId}`];
150
+ context = window.lowdefy?.contexts?.[instanceKey];
110
151
  if (context) {
111
152
  break;
112
153
  }
@@ -127,10 +168,10 @@ async function bootstrapFromCheckpoint({ basePath, pageId, checkpointName, isCan
127
168
  }
128
169
  }
129
170
 
130
- function evalExpression({ lowdefy, pageId, expression }) {
131
- const context = lowdefy?.contexts?.[`page:${pageId}`];
132
- if (type.isNone(context)) {
133
- return { error: `No live context for page "${pageId}".` };
171
+ function evalExpression({ lowdefy, page, expression }) {
172
+ const { context, error } = findContext({ lowdefy, page });
173
+ if (error) {
174
+ return { error };
134
175
  }
135
176
  // Callers may pass the operator expression as a JSON-serializable object,
136
177
  // or (since it travels as a JSON string end-to-end from an MCP tool
@@ -143,10 +184,10 @@ function evalExpression({ lowdefy, pageId, expression }) {
143
184
  };
144
185
  }
145
186
 
146
- const Inspector = ({ basePath, lowdefy, pageId }) => {
187
+ const Inspector = ({ basePath, lowdefy, page }) => {
147
188
  const { source, tabId } = useContext(DevStreamContext);
148
- const pageIdRef = useRef(pageId);
149
- pageIdRef.current = pageId;
189
+ const pageRef = useRef(page);
190
+ pageRef.current = page;
150
191
 
151
192
  useEffect(() => {
152
193
  if (type.isNone(source)) {
@@ -158,8 +199,10 @@ const Inspector = ({ basePath, lowdefy, pageId }) => {
158
199
  try {
159
200
  const data = JSON.parse(message.data);
160
201
  requestId = data.requestId;
161
- const targetPageId = data.pageId ?? pageIdRef.current;
162
- const result = buildSnapshot({ lowdefy, pageId: targetPageId });
202
+ const result = buildSnapshot({
203
+ lowdefy,
204
+ page: requestedPage({ data, shownPage: pageRef.current }),
205
+ });
163
206
  postResult({ basePath, requestId, result });
164
207
  } catch (error) {
165
208
  postResult({ basePath, requestId, result: { error: error.message } });
@@ -171,10 +214,9 @@ const Inspector = ({ basePath, lowdefy, pageId }) => {
171
214
  try {
172
215
  const data = JSON.parse(message.data);
173
216
  requestId = data.requestId;
174
- const targetPageId = data.pageId ?? pageIdRef.current;
175
217
  const result = evalExpression({
176
218
  lowdefy,
177
- pageId: targetPageId,
219
+ page: requestedPage({ data, shownPage: pageRef.current }),
178
220
  expression: data.expression,
179
221
  });
180
222
  postResult({ basePath, requestId, result });
@@ -192,14 +234,15 @@ const Inspector = ({ basePath, lowdefy, pageId }) => {
192
234
  }, [basePath, lowdefy, source]);
193
235
 
194
236
  useEffect(() => {
195
- if (type.isNone(tabId) || type.isNone(pageId)) {
237
+ if (type.isNone(tabId) || type.isNone(page)) {
196
238
  return;
197
239
  }
198
- postTabPage({ basePath, tabId, pageId });
199
- }, [basePath, tabId, pageId]);
240
+ postTabPage({ basePath, tabId, page });
241
+ }, [basePath, tabId, page]);
200
242
 
243
+ const instanceKey = page?.instanceKey;
201
244
  useEffect(() => {
202
- if (type.isNone(pageId)) {
245
+ if (type.isNone(instanceKey)) {
203
246
  return undefined;
204
247
  }
205
248
  const checkpointName = new URLSearchParams(window.location.search).get('_checkpoint');
@@ -210,7 +253,7 @@ const Inspector = ({ basePath, lowdefy, pageId }) => {
210
253
  let cancelled = false;
211
254
  bootstrapFromCheckpoint({
212
255
  basePath,
213
- pageId,
256
+ instanceKey,
214
257
  checkpointName,
215
258
  isCancelled: () => cancelled,
216
259
  });
@@ -218,7 +261,7 @@ const Inspector = ({ basePath, lowdefy, pageId }) => {
218
261
  return () => {
219
262
  cancelled = true;
220
263
  };
221
- }, [basePath, pageId]);
264
+ }, [basePath, instanceKey]);
222
265
 
223
266
  return null;
224
267
  };
@@ -0,0 +1,47 @@
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 { useEffect } from 'react';
18
+ import { getTrace } from '@lowdefy/engine';
19
+
20
+ import collectVisibleBlockIds from '../lib/client/collectVisibleBlockIds.js';
21
+ import startJourneyObserver from './journeyObserver/startJourneyObserver.js';
22
+
23
+ // Dev-only: tells the journey runner which events completed and which blocks
24
+ // were visible in a journey's own pages (see journeyObserver/). It renders
25
+ // null, like Inspector, does nothing in a developer's tab, and never lets an
26
+ // error reach the app.
27
+ function JourneyObserver({ lowdefy }) {
28
+ useEffect(() => {
29
+ let stop = null;
30
+ try {
31
+ stop = startJourneyObserver({ window, lowdefy, getTrace, collectVisibleBlockIds });
32
+ } catch {
33
+ return undefined;
34
+ }
35
+ return () => {
36
+ try {
37
+ stop?.();
38
+ } catch {
39
+ // Best effort.
40
+ }
41
+ };
42
+ }, [lowdefy]);
43
+
44
+ return null;
45
+ }
46
+
47
+ export default JourneyObserver;
package/client/Page.jsx CHANGED
@@ -15,11 +15,12 @@
15
15
  */
16
16
 
17
17
  import React, { useEffect, useRef } from 'react';
18
- import { GenIcon } from 'react-icons/lib';
19
18
  import Client from '@lowdefy/client';
19
+ import { pageInstanceKey } from '@lowdefy/helpers';
20
20
 
21
21
  import BuildErrorPage from '../lib/client/BuildErrorPage.jsx';
22
22
  import InstallingPluginsPage from '../lib/client/InstallingPluginsPage.jsx';
23
+ import RedirectingPage from '../lib/client/RedirectingPage.jsx';
23
24
  import usePageConfig from '../lib/client/utils/usePageConfig.js';
24
25
 
25
26
  const Page = ({
@@ -28,12 +29,18 @@ const Page = ({
28
29
  config,
29
30
  jsMap,
30
31
  lowdefy,
31
- pageId,
32
+ onPageShown,
33
+ path,
32
34
  resetContext,
33
35
  router,
34
36
  types,
35
37
  }) => {
36
- const { data: pageConfig } = usePageConfig(pageId, router.basePath);
38
+ const { data } = usePageConfig(path, router.basePath, auth.user, lowdefy);
39
+ const pageConfig = data?.pageConfig;
40
+ // Past the config fetch (which suspends), this render shows the page the
41
+ // URL names, so a navigation Routing marked has landed. Set during render,
42
+ // like lowdefy.pageId (initLowdefyContext), so the two change together.
43
+ lowdefy._devNavigating = false;
37
44
 
38
45
  // Push build warnings to ErrorBar via runtime error callback
39
46
  const pushedWarningsRef = useRef(null);
@@ -46,37 +53,60 @@ const Page = ({
46
53
  }
47
54
  }, [pageConfig?._warnings, lowdefy]);
48
55
 
49
- if (!pageConfig) {
56
+ // Tells the in-page dev tools which page instance is on screen, from the fetched page.
57
+ useEffect(() => {
58
+ if (pageConfig) {
59
+ onPageShown({
60
+ pageId: data.pageId,
61
+ pathParams: data.pathParams,
62
+ instanceKey: pageInstanceKey({
63
+ pageId: data.pageId,
64
+ path: pageConfig.path,
65
+ pathParams: data.pathParams,
66
+ }),
67
+ });
68
+ }
69
+ }, [pageConfig, data?.pageId, data?.pathParams, onPageShown]);
70
+
71
+ // Full load to the sign-in page so it can return here after sign-in — an
72
+ // effect, not a fetcher side effect, so the redirect re-fires if the same
73
+ // cached result renders again.
74
+ useEffect(() => {
75
+ if (data?.authRedirect) {
76
+ window.location.assign(data.authRedirect);
77
+ }
78
+ }, [data?.authRedirect]);
79
+
80
+ if (!data) {
50
81
  router.replace({ pathname: '/404' });
51
82
  return '';
52
83
  }
53
- if (pageConfig.buildError) {
54
- return (
55
- <BuildErrorPage
56
- errors={pageConfig.errors}
57
- message={pageConfig.message}
58
- source={pageConfig.source}
59
- />
60
- );
84
+ if (data.authRedirect) {
85
+ return <RedirectingPage redirect={data.authRedirect} />;
86
+ }
87
+ if (data.buildError) {
88
+ return <BuildErrorPage errors={data.errors} message={data.message} source={data.source} />;
61
89
  }
62
- if (pageConfig.installing) {
63
- return <InstallingPluginsPage packages={pageConfig.packages} />;
90
+ if (data.installing) {
91
+ return <InstallingPluginsPage packages={data.packages} />;
64
92
  }
65
93
 
66
94
  // Merge dynamic JS entries fetched after JIT build with the static jsMap
67
95
  const mergedJsMap = pageConfig._jsEntries ? { ...jsMap, ...pageConfig._jsEntries } : jsMap;
68
96
 
69
- // Merge JIT-discovered icon data into the static icons object.
70
- // createIcon.js looks up Icons[name] on every render from the captured reference,
71
- // so mutating the original object makes new icons available immediately.
97
+ // Merge JIT-delivered icon data into the static icons map. Icons are data
98
+ // (IconData), and createIcon looks up Icons[name] on every render from the
99
+ // captured reference, so mutating the original object makes them available
100
+ // immediately.
72
101
  if (pageConfig._dynamicIcons) {
73
- for (const [name, data] of Object.entries(pageConfig._dynamicIcons)) {
74
- if (!types.icons[name]) {
75
- types.icons[name] = GenIcon(data);
76
- }
77
- }
102
+ Object.assign(types.icons, pageConfig._dynamicIcons);
78
103
  }
79
104
 
105
+ // The build this config was served under, for the recorder to stamp on each
106
+ // interaction. Set during render, like the icons above, so it is in place
107
+ // before any interaction on this config.
108
+ lowdefy._devBuildId = pageConfig._buildId ?? null;
109
+
80
110
  return (
81
111
  <Client
82
112
  auth={auth}
@@ -87,6 +117,8 @@ const Page = ({
87
117
  }}
88
118
  jsMap={mergedJsMap}
89
119
  lowdefy={lowdefy}
120
+ matchedPath={data.matchedPath}
121
+ pathParams={data.pathParams}
90
122
  resetContext={resetContext}
91
123
  router={router}
92
124
  stage="dev"
@@ -0,0 +1,73 @@
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 { useContext, useEffect, useRef } from 'react';
18
+ import { getTrace } from '@lowdefy/engine';
19
+
20
+ import createRecorder from './recorder/createRecorder.js';
21
+ import DevStreamContext from './DevStreamContext.js';
22
+
23
+ // Dev-only journey recorder: records how the developer uses the app, locally,
24
+ // so an agent can turn what they tried into journeys (see recorder/). It
25
+ // renders null, like Inspector, and never lets an error reach the app.
26
+ //
27
+ // It opens no connection of its own: a second long-lived stream per tab once
28
+ // exhausted the browser's HTTP/1.1 connection pool (see DevStreamContext.js).
29
+ // It listens for `reload` on the tab's one shared stream and flushes there,
30
+ // only to send promptly: each record carries the build of the page config it
31
+ // was made on (lowdefy._devBuildId, set by Page.jsx).
32
+ // `path` is the path Page fetches, so a navigation starts a pageview; `page`
33
+ // is the page instance on screen, { pageId, pathParams, instanceKey }, as the
34
+ // fetched page names it, which completes it.
35
+ function Recorder({ basePath, lowdefy, page, path, recording }) {
36
+ const { source } = useContext(DevStreamContext);
37
+ const recorderRef = useRef(null);
38
+
39
+ useEffect(() => {
40
+ let recorder = null;
41
+ try {
42
+ recorder = createRecorder({ basePath, lowdefy, recording, window, getTrace });
43
+ } catch {
44
+ return undefined;
45
+ }
46
+ if (recorder === null) return undefined;
47
+ recorderRef.current = recorder;
48
+ return () => {
49
+ recorderRef.current = null;
50
+ try {
51
+ recorder.stop();
52
+ } catch {
53
+ // Best effort.
54
+ }
55
+ };
56
+ }, [recording?.enabled]);
57
+
58
+ useEffect(() => {
59
+ recorderRef.current?.pageview({ path });
60
+ }, [path, recording?.enabled]);
61
+
62
+ useEffect(() => {
63
+ if (page !== null) recorderRef.current?.pageShown(page);
64
+ }, [page?.instanceKey, recording?.enabled]);
65
+
66
+ useEffect(() => {
67
+ return recorderRef.current?.attachStream(source);
68
+ }, [source, recording?.enabled]);
69
+
70
+ return null;
71
+ }
72
+
73
+ export default Recorder;