@delmaredigital/payload-experiments 0.3.0-rc.1 → 0.4.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/README.md +172 -21
  2. package/dist/admin/MetricSourceSelect.js +3 -3
  3. package/dist/admin/PlacementStatus.d.ts +3 -0
  4. package/dist/admin/PlacementStatus.js +54 -0
  5. package/dist/admin/PreviewLinks.d.ts +2 -1
  6. package/dist/admin/PreviewLinks.js +47 -26
  7. package/dist/admin/TestsNavLink.d.ts +3 -1
  8. package/dist/admin/TestsNavLink.js +2 -2
  9. package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
  10. package/dist/admin/actions/ConfirmDialog.js +2 -2
  11. package/dist/admin/actions/DecideDialog.d.ts +2 -1
  12. package/dist/admin/actions/DecideDialog.js +2 -2
  13. package/dist/admin/api.d.ts +11 -2
  14. package/dist/admin/api.js +20 -2
  15. package/dist/admin/armSelect/ArmSelectField.js +14 -22
  16. package/dist/admin/arms/arms.model.d.ts +1 -21
  17. package/dist/admin/arms/arms.model.js +1 -153
  18. package/dist/admin/blocks/BlockPanel.d.ts +4 -0
  19. package/dist/admin/blocks/BlockPanel.js +153 -0
  20. package/dist/admin/blocks/TestBlockField.d.ts +5 -0
  21. package/dist/admin/blocks/TestBlockField.js +163 -0
  22. package/dist/admin/blocks/formApi.d.ts +43 -0
  23. package/dist/admin/blocks/formApi.js +153 -0
  24. package/dist/admin/blocks/inject.d.ts +7 -0
  25. package/dist/admin/blocks/inject.js +41 -0
  26. package/dist/admin/blocks/model.d.ts +17 -0
  27. package/dist/admin/blocks/model.js +45 -0
  28. package/dist/admin/blocks/statusCache.d.ts +7 -0
  29. package/dist/admin/blocks/statusCache.js +83 -0
  30. package/dist/admin/blocks/useBlockForm.d.ts +17 -0
  31. package/dist/admin/blocks/useBlockForm.js +46 -0
  32. package/dist/admin/copy/actions.d.ts +2 -0
  33. package/dist/admin/copy/actions.js +9 -0
  34. package/dist/admin/copy/arms.d.ts +1 -6
  35. package/dist/admin/copy/arms.js +1 -16
  36. package/dist/admin/copy/blocks.d.ts +27 -0
  37. package/dist/admin/copy/blocks.js +38 -0
  38. package/dist/admin/copy/goals.d.ts +2 -0
  39. package/dist/admin/copy/goals.js +4 -1
  40. package/dist/admin/copy/newTest.d.ts +10 -0
  41. package/dist/admin/copy/newTest.js +10 -0
  42. package/dist/admin/copy/pages.d.ts +26 -0
  43. package/dist/admin/copy/pages.js +26 -0
  44. package/dist/admin/copy/readiness.d.ts +25 -3
  45. package/dist/admin/copy/readiness.js +25 -3
  46. package/dist/admin/copy/results.d.ts +3 -0
  47. package/dist/admin/copy/results.js +4 -1
  48. package/dist/admin/copy/review.d.ts +1 -1
  49. package/dist/admin/copy/review.js +1 -1
  50. package/dist/admin/copy/surface.d.ts +0 -1
  51. package/dist/admin/copy/surface.js +0 -1
  52. package/dist/admin/copy/testPage.d.ts +4 -0
  53. package/dist/admin/copy/testPage.js +4 -0
  54. package/dist/admin/goals/XpGoalField.d.ts +2 -0
  55. package/dist/admin/goals/XpGoalField.js +152 -0
  56. package/dist/admin/metricSource.model.d.ts +2 -0
  57. package/dist/admin/metricSource.model.js +4 -0
  58. package/dist/admin/pageCollections.d.ts +10 -0
  59. package/dist/admin/pageCollections.js +20 -0
  60. package/dist/admin/paths.d.ts +8 -1
  61. package/dist/admin/paths.js +8 -1
  62. package/dist/admin/placementStatus.model.d.ts +2 -0
  63. package/dist/admin/placementStatus.model.js +19 -0
  64. package/dist/admin/placements/placementLabels.d.ts +11 -0
  65. package/dist/admin/placements/placementLabels.js +21 -0
  66. package/dist/admin/placements/placementLabels.model.d.ts +13 -0
  67. package/dist/admin/placements/placementLabels.model.js +12 -0
  68. package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
  69. package/dist/admin/placements/usePlacementLabels.js +102 -0
  70. package/dist/admin/previewLinks.model.d.ts +2 -0
  71. package/dist/admin/previewLinks.model.js +3 -0
  72. package/dist/admin/results/DataQualityPanel.js +4 -1
  73. package/dist/admin/results/legacy.d.ts +16 -0
  74. package/dist/admin/results/legacy.js +27 -0
  75. package/dist/admin/results/results.model.d.ts +9 -6
  76. package/dist/admin/results/results.model.js +22 -9
  77. package/dist/admin/results/snapshot.d.ts +14 -0
  78. package/dist/admin/results/snapshot.js +3 -0
  79. package/dist/admin/surface/TestsHome.js +43 -15
  80. package/dist/admin/surface/TestsSurface.js +5 -1
  81. package/dist/admin/surface/contracts.d.ts +6 -5
  82. package/dist/admin/surface/goals/GoalForm.js +67 -34
  83. package/dist/admin/surface/goals/GoalsList.js +3 -3
  84. package/dist/admin/surface/goals/data.js +19 -16
  85. package/dist/admin/surface/goals/goals.model.d.ts +1 -2
  86. package/dist/admin/surface/goals/goals.model.js +1 -3
  87. package/dist/admin/surface/home.model.d.ts +2 -1
  88. package/dist/admin/surface/home.model.js +8 -4
  89. package/dist/admin/surface/newTest/NewTest.js +58 -17
  90. package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
  91. package/dist/admin/surface/newTest/PageWizard.js +209 -0
  92. package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
  93. package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
  94. package/dist/admin/surface/pagePicker/PagePicker.d.ts +10 -0
  95. package/dist/admin/surface/pagePicker/PagePicker.js +172 -0
  96. package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +25 -0
  97. package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
  98. package/dist/admin/surface/readiness/ActionDialogs.js +103 -69
  99. package/dist/admin/surface/readiness/Banner.js +30 -6
  100. package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
  101. package/dist/admin/surface/readiness/readiness.model.js +95 -6
  102. package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
  103. package/dist/admin/surface/results/ReleasePages.js +3 -7
  104. package/dist/admin/surface/results/ResultsBody.js +23 -3
  105. package/dist/admin/surface/results/ResultsHeader.js +2 -5
  106. package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
  107. package/dist/admin/surface/results/recommendation.model.js +10 -3
  108. package/dist/admin/surface/results/useResults.d.ts +10 -1
  109. package/dist/admin/surface/surface.css +0 -1
  110. package/dist/admin/surface/test/AdvancedSection.js +111 -104
  111. package/dist/admin/surface/test/GoalSection.js +97 -22
  112. package/dist/admin/surface/test/LengthSection.js +1 -1
  113. package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
  114. package/dist/admin/surface/test/PageHoldout.js +67 -0
  115. package/dist/admin/surface/test/TestPage.js +2 -1
  116. package/dist/admin/surface/test/VersionsSection.js +12 -5
  117. package/dist/admin/surface/test/fields.d.ts +13 -4
  118. package/dist/admin/surface/test/fields.js +46 -14
  119. package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
  120. package/dist/admin/surface/test/jumpToSection.js +4 -3
  121. package/dist/admin/surface/test/pageParts.d.ts +4 -1
  122. package/dist/admin/surface/test/pageParts.js +79 -2
  123. package/dist/admin/surface/test/testPage.css +8 -1
  124. package/dist/admin/surface/test/testPage.model.d.ts +2 -3
  125. package/dist/admin/surface/test/testPage.model.js +1 -21
  126. package/dist/admin/surface/testDocument.model.d.ts +1 -0
  127. package/dist/admin/surface/testDocument.model.js +2 -1
  128. package/dist/admin/surface/useTestDocument.d.ts +11 -2
  129. package/dist/admin/surface/useTestDocument.js +55 -16
  130. package/dist/admin/useApi.d.ts +11 -2
  131. package/dist/admin/useApi.js +3 -5
  132. package/dist/admin/useLayerUsage.js +14 -23
  133. package/dist/admin/views/ExperimentsRootView.js +58 -16
  134. package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
  135. package/dist/admin/views/TestsNavLinkServer.js +43 -0
  136. package/dist/arms/copy.d.ts +6 -0
  137. package/dist/arms/copy.js +16 -0
  138. package/dist/arms/model.d.ts +23 -0
  139. package/dist/arms/model.js +173 -0
  140. package/dist/blocks/bindings.d.ts +3 -0
  141. package/dist/blocks/bindings.js +34 -0
  142. package/dist/blocks/createExperimentBlock.d.ts +6 -0
  143. package/dist/blocks/createExperimentBlock.js +77 -0
  144. package/dist/blocks/formState.d.ts +31 -0
  145. package/dist/blocks/formState.js +146 -0
  146. package/dist/blocks/model.d.ts +41 -0
  147. package/dist/blocks/model.js +34 -0
  148. package/dist/client/ExperimentGroup.d.ts +6 -0
  149. package/dist/client/ExperimentGroup.js +9 -0
  150. package/dist/client/ExperimentsProvider.js +20 -4
  151. package/dist/client/clickGoals.d.ts +4 -0
  152. package/dist/client/clickGoals.js +43 -0
  153. package/dist/collections/experiments/config.js +12 -2
  154. package/dist/collections/experiments/deleteGuard.d.ts +2 -1
  155. package/dist/collections/experiments/deleteGuard.js +12 -1
  156. package/dist/collections/experiments/editability.js +4 -2
  157. package/dist/collections/experiments/pickSlug.d.ts +2 -0
  158. package/dist/collections/experiments/pickSlug.js +8 -0
  159. package/dist/collections/labels.d.ts +3 -0
  160. package/dist/collections/labels.js +20 -0
  161. package/dist/collections/metrics.js +12 -2
  162. package/dist/collections/phases.js +1 -0
  163. package/dist/collections/serialize.js +7 -0
  164. package/dist/db/appends.d.ts +6 -2
  165. package/dist/db/appends.js +10 -4
  166. package/dist/db/clickGoals.d.ts +9 -0
  167. package/dist/db/clickGoals.js +22 -0
  168. package/dist/db/delete.d.ts +1 -0
  169. package/dist/db/delete.js +1 -0
  170. package/dist/db/locks.d.ts +14 -0
  171. package/dist/db/locks.js +36 -1
  172. package/dist/db/names.d.ts +4 -0
  173. package/dist/db/names.js +4 -0
  174. package/dist/db/pageBindings.d.ts +4 -0
  175. package/dist/db/pageBindings.js +17 -0
  176. package/dist/db/pageBusy.d.ts +2 -0
  177. package/dist/db/pageBusy.js +10 -0
  178. package/dist/db/pageContent.d.ts +10 -0
  179. package/dist/db/pageContent.js +79 -0
  180. package/dist/db/pageOccupancy.d.ts +12 -0
  181. package/dist/db/pageOccupancy.js +45 -0
  182. package/dist/db/placements.d.ts +33 -0
  183. package/dist/db/placements.js +54 -0
  184. package/dist/db/slugs.d.ts +8 -0
  185. package/dist/db/slugs.js +40 -0
  186. package/dist/db/snapshots.d.ts +6 -0
  187. package/dist/db/snapshots.js +9 -5
  188. package/dist/endpoints/clickKeys.d.ts +5 -0
  189. package/dist/endpoints/clickKeys.js +17 -0
  190. package/dist/endpoints/events.js +4 -1
  191. package/dist/endpoints/index.js +28 -0
  192. package/dist/endpoints/pages.d.ts +5 -0
  193. package/dist/endpoints/pages.js +120 -0
  194. package/dist/endpoints/placementStatus.d.ts +6 -0
  195. package/dist/endpoints/placementStatus.js +238 -0
  196. package/dist/endpoints/readBody.d.ts +2 -0
  197. package/dist/endpoints/readBody.js +24 -0
  198. package/dist/endpoints/results.js +5 -1
  199. package/dist/endpoints/summary.js +11 -4
  200. package/dist/endpoints/wrapBlock.d.ts +6 -0
  201. package/dist/endpoints/wrapBlock.js +76 -0
  202. package/dist/errors.d.ts +3 -3
  203. package/dist/errors.js +4 -4
  204. package/dist/events/rows.d.ts +1 -1
  205. package/dist/events/rows.js +5 -3
  206. package/dist/exports/adminClient.d.ts +5 -0
  207. package/dist/exports/adminClient.js +5 -0
  208. package/dist/exports/adminRsc.d.ts +1 -0
  209. package/dist/exports/adminRsc.js +1 -0
  210. package/dist/exports/blocks.d.ts +2 -0
  211. package/dist/exports/blocks.js +1 -0
  212. package/dist/exports/client.d.ts +1 -0
  213. package/dist/exports/client.js +1 -0
  214. package/dist/exports/index.d.ts +6 -1
  215. package/dist/exports/index.js +3 -0
  216. package/dist/exports/puck.d.ts +5 -2
  217. package/dist/exports/puck.js +3 -1
  218. package/dist/exports/puckEditor.d.ts +6 -1
  219. package/dist/exports/puckEditor.js +4 -1
  220. package/dist/exports/rsc.d.ts +4 -0
  221. package/dist/exports/rsc.js +2 -0
  222. package/dist/fields/xpGoal.d.ts +3 -0
  223. package/dist/fields/xpGoal.js +28 -0
  224. package/dist/globals/state.js +10 -0
  225. package/dist/goals/cache.d.ts +3 -0
  226. package/dist/goals/cache.js +35 -0
  227. package/dist/goals/fairness.d.ts +40 -0
  228. package/dist/goals/fairness.js +200 -0
  229. package/dist/goals/key.d.ts +4 -0
  230. package/dist/goals/key.js +3 -0
  231. package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
  232. package/dist/goals/picker/clickGoalPicker.model.js +45 -0
  233. package/dist/goals/picker/copy.d.ts +16 -0
  234. package/dist/goals/picker/copy.js +15 -0
  235. package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
  236. package/dist/goals/picker/useClickGoalPicker.js +220 -0
  237. package/dist/hooks/pageGuard.d.ts +5 -1
  238. package/dist/hooks/pageGuard.js +160 -80
  239. package/dist/lib/fetchAllDocs.d.ts +16 -0
  240. package/dist/lib/fetchAllDocs.js +56 -0
  241. package/dist/onInit.js +29 -0
  242. package/dist/options.d.ts +2 -0
  243. package/dist/options.js +43 -10
  244. package/dist/plugin.js +45 -28
  245. package/dist/puck/ExperimentPicker.js +47 -16
  246. package/dist/puck/ExperimentSlot.d.ts +51 -1
  247. package/dist/puck/ExperimentSlot.js +23 -12
  248. package/dist/puck/copy.d.ts +6 -4
  249. package/dist/puck/copy.js +6 -4
  250. package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
  251. package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
  252. package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
  253. package/dist/puck/editor/ResultsReadout.d.ts +6 -0
  254. package/dist/puck/editor/ResultsReadout.js +99 -0
  255. package/dist/puck/editor/VersionBuilder.d.ts +34 -0
  256. package/dist/puck/editor/VersionBuilder.js +317 -0
  257. package/dist/puck/editor/armWrites.d.ts +7 -0
  258. package/dist/puck/editor/armWrites.js +71 -0
  259. package/dist/puck/editor/armsCache.d.ts +14 -0
  260. package/dist/puck/editor/armsCache.js +78 -0
  261. package/dist/puck/editor/copy.d.ts +33 -1
  262. package/dist/puck/editor/copy.js +45 -8
  263. package/dist/puck/editor/createTest.d.ts +1 -1
  264. package/dist/puck/editor/createTest.js +25 -14
  265. package/dist/puck/editor/editor.chrome.css +48 -161
  266. package/dist/puck/editor/editor.css +48 -161
  267. package/dist/puck/editor/editor.model.d.ts +8 -3
  268. package/dist/puck/editor/editor.model.js +16 -10
  269. package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
  270. package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
  271. package/dist/puck/editor/integration.d.ts +10 -0
  272. package/dist/puck/editor/integration.js +30 -0
  273. package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
  274. package/dist/puck/editor/xpGoalPuckEditorField.js +134 -0
  275. package/dist/puck/experimentSlotField.d.ts +2 -0
  276. package/dist/puck/experimentSlotField.js +11 -3
  277. package/dist/puck/hostContext.d.ts +10 -0
  278. package/dist/puck/hostContext.js +12 -0
  279. package/dist/puck/integration.d.ts +6 -0
  280. package/dist/puck/integration.js +21 -0
  281. package/dist/puck/routes.d.ts +12 -0
  282. package/dist/puck/routes.js +11 -0
  283. package/dist/puck/slotConfig.d.ts +1 -1
  284. package/dist/puck/slotConfig.js +2 -2
  285. package/dist/puck/xpGoalField.d.ts +3 -0
  286. package/dist/puck/xpGoalField.js +6 -0
  287. package/dist/results/health.d.ts +4 -0
  288. package/dist/results/health.js +27 -0
  289. package/dist/results/snapshot.js +15 -2
  290. package/dist/sanitizeReadinessDetail.js +51 -1
  291. package/dist/server/metadata.js +9 -2
  292. package/dist/server/normaliseContentRelationships.d.ts +7 -0
  293. package/dist/server/normaliseContentRelationships.js +62 -0
  294. package/dist/server/pageKindSeoContract.d.ts +27 -0
  295. package/dist/server/pageKindSeoContract.js +80 -0
  296. package/dist/server/recordContentChange.d.ts +1 -1
  297. package/dist/server/recordContentChange.js +72 -13
  298. package/dist/server/resolveExperimentBlocks.d.ts +5 -0
  299. package/dist/server/resolveExperimentBlocks.js +54 -0
  300. package/dist/server/runResults.js +92 -19
  301. package/dist/server/toClientAssignments.js +2 -1
  302. package/dist/transitions/addMetric.d.ts +2 -1
  303. package/dist/transitions/addMetric.js +28 -1
  304. package/dist/transitions/checkStart.d.ts +17 -1
  305. package/dist/transitions/checkStart.js +99 -7
  306. package/dist/transitions/decide.js +40 -1
  307. package/dist/transitions/deleteExperiment.d.ts +3 -1
  308. package/dist/transitions/deleteExperiment.js +27 -1
  309. package/dist/transitions/kernel.js +4 -0
  310. package/dist/transitions/makeContent.d.ts +5 -0
  311. package/dist/transitions/makeContent.js +39 -11
  312. package/dist/transitions/pageCollection.d.ts +10 -0
  313. package/dist/transitions/pageCollection.js +20 -0
  314. package/dist/transitions/pageVersions.d.ts +10 -0
  315. package/dist/transitions/pageVersions.js +44 -0
  316. package/dist/transitions/phaseRecord.d.ts +1 -0
  317. package/dist/transitions/placementInteractions.d.ts +40 -0
  318. package/dist/transitions/placementInteractions.js +94 -0
  319. package/dist/transitions/placements.d.ts +10 -6
  320. package/dist/transitions/placements.js +156 -2
  321. package/dist/transitions/preflightData.d.ts +8 -0
  322. package/dist/transitions/preflightData.js +248 -36
  323. package/dist/transitions/resolveCaps.d.ts +1 -0
  324. package/dist/transitions/resolveCaps.js +6 -2
  325. package/dist/transitions/start.js +77 -3
  326. package/dist/types/admin.d.ts +1 -1
  327. package/dist/types/assignments.d.ts +1 -0
  328. package/dist/types/contracts.d.ts +31 -10
  329. package/dist/types/endpoints.d.ts +92 -5
  330. package/dist/types/index.d.ts +3 -0
  331. package/dist/types/puck.d.ts +7 -1
  332. package/dist/types/snapshot.d.ts +17 -4
  333. package/dist/types/surface.d.ts +13 -0
  334. package/dist/ui/Tabs.d.ts +4 -2
  335. package/dist/ui/Tabs.js +33 -26
  336. package/dist/ui/accent.d.ts +22 -3
  337. package/dist/ui/accent.js +74 -16
  338. package/package.json +12 -2
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # @delmaredigital/payload-experiments
2
2
 
3
+ > ⚠️ Upgrading to 0.4? Run `payload migrate:create` for four nullable plugin columns (`xp_experiments.page_collection` and its `_xp_experiments_v` counterpart, `xp_phases.placement_docs`, `xp_state.retired_slugs`), plus the Experiment block and `xpGoal` columns if you adopt them, then `payload generate:importmap`. Saves on pages, blocks and Puck collections can now be refused while a test runs on them (`page-busy`, `interacting-tests`); Puck tests whose slot is only on a draft can't start until the page is published; host `metricSources` ids may not start with `click:`. See the CHANGELOG's BREAKING section.
4
+
3
5
  > ⚠️ Upgrading to 0.3? For 0.3.0-rc.1, every `linkIdentity` call must supply `verified`; it now returns an outcome. Run `payload migrate:create` for the nullable `xp_identities.verified_at` column and `user_id` index, plus the release-pages columns listed in the CHANGELOG (`decision_pages_released_at` / `decision_pages_released_by` on `xp_experiments` and their `version_` counterparts on `_xp_experiments_v`). Existing identity links become unverified; see the CHANGELOG for optional backfill.
4
6
 
5
7
  > ⚠️ Upgrading to 0.2? After upgrading, run `payload generate:importmap`: the Tests surface is registered through admin.dependencies, and without it the Tests routes show a 'not installed' message instead of the Tests screen. In 0.2.0-rc.2, the Tests server views moved from `/rsc` to `/admin/rsc`, so the import map must be regenerated when upgrading from rc.1 too. The old experiments edit-form components are gone from `/admin/client`, and the `xp-experiments` admin list and edit views now redirect to the Tests surface.
@@ -271,7 +273,10 @@ Other server integrations:
271
273
  - `recordContentChange({ doc, previousDoc, req, collection })` can be called
272
274
  from a host collection's `afterChange` for content tagged with the arm-select
273
275
  field; for a collection with drafts, also add `captureContentBaseline` to its
274
- `beforeChange` hooks so the previously published content can be compared. Supply that hook's transaction request. Puck collections get the hook
276
+ `beforeChange` hooks so the previously published content can be compared.
277
+ Collections without drafts can also add `captureContentBaseline` for an exact
278
+ raw comparison; without it, the helper normalises relationships to ids. No host
279
+ change is required for collections without drafts. Supply that hook's transaction request. Puck collections get the hook
275
280
  automatically when enabled. Markers compare published content; drafts and
276
281
  autosaves do not add markers (collections without drafts record on save).
277
282
  Neither the helper nor the installed hooks ever throw into your save: a
@@ -281,7 +286,10 @@ Other server integrations:
281
286
  canonical; archived losing paths point canonically to the winner. It reads
282
287
  the signed request context via `headers()`, so it decides from the URL the
283
288
  visitor requested, not the rewritten route, and the Original's URL is never
284
- noindex. Call it only from a dynamic `generateMetadata`, never inside
289
+ noindex. While a test is live the Original's URL also gets a canonical to
290
+ itself, even when it is served with a variant's route: merge this result after
291
+ your own metadata so its `alternates.canonical` wins over one derived from the
292
+ rendered route. Call it only from a dynamic `generateMetadata`, never inside
285
293
  `'use cache'`; `path` is the fallback when the proxy did not run.
286
294
  `experimentSitemapFilter(path, manifest)` is a synchronous build-time filter:
287
295
  pass a privately server-loaded manifest and keep paths for which it returns
@@ -420,6 +428,7 @@ schema. Optional callbacks integrate host-owned content:
420
428
 
421
429
  A `MetricSource`, registered in `metricSources`, has `id`, `label`,
422
430
  `kind: 'binomial' | 'count' | 'revenue'`, and `keyedBy: 'visitor' | 'user'`.
431
+ Source ids starting with `click:` are reserved for click goals and refused at boot.
423
432
  `clientWritable` optionally permits client conversions (revenue is always
424
433
  server-only). `query({ from, to, unitIds? })` returns a promise of
425
434
  `[{ unitId, value, ts }]`, **one row per event**, with `ts` a `Date`; use the
@@ -450,6 +459,21 @@ are recorded with `trackConversion`. Beacon conversions for a non-writable name
450
459
  are dropped silently. Only listed names become `events:<name>` sources. They
451
460
  support visitor and user units and do not represent counts or revenue in v1.
452
461
 
462
+ ### Testing page SEO in the host
463
+
464
+ `pageKindSeoContract({ secret, original, versions, state, winner? })` from `/rsc`
465
+ returns `{ manifest, rows }`. States are `running`, `decided` and `archived`;
466
+ provide a winner path for the latter two. Build the host's test runtime with
467
+ `loadManifest: async () => manifest` and the same secret. For each row, set
468
+ `next/headers` to its signed `headers`, render metadata for `servedPath`, and
469
+ assert `expect.robots`, `expect.canonical` and `expect.inSitemap` against the
470
+ host's metadata and sitemap functions. This includes the Original served as
471
+ each version, including the homepage.
472
+
473
+ A host-level test mocks four boundaries: `next/headers`, the page loader,
474
+ the runtime and the site URL. Set `metadataBase` so relative canonical paths
475
+ resolve to your site URL.
476
+
453
477
  ### Whole-page options
454
478
 
455
479
  | Option | Purpose |
@@ -483,18 +507,39 @@ pages: {
483
507
  Adapt both callbacks for the site's homepage path. These checks run before Start;
484
508
  they do not protect pages from later deletion, unpublishing or renaming. Keep an
485
509
  archived winner published at the same address: archived test URLs still point to it.
486
- No migration is needed.
510
+ Whole-page registrations also install the sidebar test status and content-change hooks.
511
+ For 0.4.0, run `payload migrate:create` for `xp_state.retired_slugs`,
512
+ `xp_experiments.page_collection` and its version column, then
513
+ `payload generate:importmap`.
514
+
515
+ `contentOf(doc)` optionally selects the content your site renders (for example,
516
+ `doc.layout`). Published copies are compared with nested `id` and `blockName`
517
+ keys removed. Without it, content checks and page content-change comparisons
518
+ are skipped. `copyHint` (up to 200 characters), `copyUrl` (a root-relative path)
519
+ and `copyLabel` (up to 40 characters, requires `copyUrl`) customize the guidance
520
+ for making version pages. The default copy link opens the registered collection's
521
+ list under the configured admin route.
487
522
 
488
523
  ### `admin` options
489
524
 
490
525
  Host links and kind descriptions are optional. For a host using payload-puck's
491
- editor route and a `puck-templates` collection, pass:
526
+ editor route, public pages and a `puck-templates` collection:
527
+
528
+ | Option | Signature and behavior |
529
+ | --- | --- |
530
+ | `admin.previewUrl` | `(placement: PreviewPlacement) => string \| null \| undefined \| Promise<string \| null \| undefined>`. Receives `collection`, `id`, `title`, `published`, and optional `slug`. May be async; return `null` to hide preview for that placement. Each call has a 2 s limit, with at most 8 lookups awaited concurrently. |
531
+
532
+ For example, pass:
492
533
 
493
534
  ```ts
494
535
  admin: {
495
- editorUrl: ({ collection, id, draft }) =>
536
+ editorUrl: ({ collection, id, draft, editor, doc }) =>
496
537
  `/admin/puck-editor/${encodeURIComponent(collection)}/${encodeURIComponent(String(id))}`,
497
- previewUrl: ({ id }) => `/preview/template/${encodeURIComponent(String(id))}`,
538
+ previewUrl: async ({ collection, slug }) => {
539
+ if (collection !== 'pages' || slug === undefined) return null
540
+ // Match the site's public page routing, including its homepage convention.
541
+ return slug === 'home' ? '/' : `/${slug}`
542
+ },
498
543
  descriptions: {
499
544
  block: 'Try different content on a landing page.',
500
545
  document: 'Try different versions of a donation ask.',
@@ -503,11 +548,21 @@ admin: {
503
548
  }
504
549
  ```
505
550
 
506
- Use your host's preview route for `previewUrl`. Preflight resolves both URL
507
- callbacks on the server for block placements; it passes only URL strings to the
508
- client. Missing or failed callbacks leave the URL unset, and the client uses the
509
- admin collection route when no editor URL is supplied. Kind descriptions are
510
- available to the server views through `config.custom.xp`.
551
+ `PreviewPlacement` is exported from the package root and `/rsc`. Its `slug` is
552
+ the published main row's string slug, including for a placement found in a newer
553
+ draft with a different slug. It is absent for unpublished documents. `published`
554
+ describes the placement's version; the callback receives no other document fields.
555
+ An async callback can also look up the public path by `collection` and `id` using
556
+ the host's own data access. The old `({ collection, id }) => string` form still works.
557
+
558
+ Preflight resolves the URL callbacks on the server for block placements and sends
559
+ only URL strings to the client. A null/undefined result, a non-string, an exception,
560
+ a rejected promise or a timeout leaves preview hidden for that placement. When an
561
+ editor URL exists, the admin offers "Edit on page"; when neither URL exists, it
562
+ omits the row. Timed-out host work cannot be cancelled and keeps its concurrency
563
+ slot until it settles; if all 8 slots remain occupied, further preview lookups
564
+ are skipped for that preflight. Kind descriptions are available to the server views through
565
+ `config.custom.xp`.
511
566
 
512
567
  A flag point can also declare `description?: string` to explain the setting below its label.
513
568
 
@@ -544,7 +599,10 @@ separately. Only manual, viewport and pageview exposure policies are accepted.
544
599
  Conversions require a client-writable source, never revenue; client values are
545
600
  stored as `1`, attributes are allow-listed, and the host's `events.rateLimit`
546
601
  adapter runs when supplied. Accepted and dropped events both return `202 {}`;
547
- malformed requests can return errors.
602
+ malformed requests can return errors. `click:<key>` conversions are also accepted
603
+ for defined click-goal keys matching `[a-z0-9][a-z0-9_-]{0,63}`; undefined or
604
+ invalid keys are dropped with `202`. Clicks carry no attributes, and defined
605
+ keys may be cached for up to 30 seconds.
548
606
 
549
607
  **The real bound on slow or method-override bodies is the host firewall/platform
550
608
  timeout.** A stream under the byte limit can remain open, and Payload can read
@@ -567,7 +625,8 @@ dashboard also includes **Tests** and **Goals** cards above its widgets. Goals
567
625
  appears only for users who can manage tests. Both additions follow the host's
568
626
  admin route and keep host-provided navigation and dashboard entries first.
569
627
  After upgrading, run `payload generate:importmap` to register the dashboard card
570
- component.
628
+ component and the server nav wrapper.
629
+ A host nav group also labelled "Tests" shares Payload's NavGroup DOM id and open-state preference with the plugin's group, so pick another label.
571
630
 
572
631
  ### Client assignments and exposure
573
632
 
@@ -620,19 +679,70 @@ configured to allow client writes; record revenue on the server.
620
679
 
621
680
  ### Puck render and editor configs
622
681
 
623
- Register `ExperimentSlot` from `@delmaredigital/payload-experiments/puck` in
624
- your render config and `ExperimentSlotEditor` from
625
- `@delmaredigital/payload-experiments/puck/editor` in your editor config, using
626
- the same `ExperimentSlot` component name:
682
+ Works with: payload-puck ≥0.10.3 (experiments ≥0.4.0).
683
+
684
+ For payload-puck hosts, add one integration object on each side. In the render
685
+ config, pass `integrations` to `extendConfig`:
627
686
 
628
687
  ```ts
629
- // Render config
630
- components: { ExperimentSlot }
688
+ import { extendConfig } from '@delmaredigital/payload-puck/config'
689
+ import { experimentsPuck } from '@delmaredigital/payload-experiments/puck'
690
+ import { baseConfig } from './baseConfig'
691
+
692
+ export const renderConfig = extendConfig({
693
+ base: baseConfig,
694
+ integrations: [experimentsPuck()],
695
+ })
696
+ ```
697
+
698
+ Call `experimentsPuckEditor()` once at module scope in a `'use client'` module,
699
+ then pass that array to `PuckConfigProvider`. A new object per render rebuilds
700
+ Puck's config and plugin list.
701
+
702
+ ```tsx
703
+ 'use client'
631
704
 
632
- // Editor config
633
- components: { ExperimentSlot: ExperimentSlotEditor }
705
+ import { PuckConfigProvider } from '@delmaredigital/payload-puck/client'
706
+ import { experimentsPuckEditor } from '@delmaredigital/payload-experiments/puck/editor'
707
+ import { editorConfig } from './editorConfig'
708
+
709
+ const integrations = [experimentsPuckEditor()]
710
+
711
+ export function PuckProvider({ children }: { children: React.ReactNode }) {
712
+ return <PuckConfigProvider config={editorConfig} integrations={integrations}>
713
+ {children}
714
+ </PuckConfigProvider>
715
+ }
634
716
  ```
635
717
 
718
+ Each object includes the test block and its **A/B tests** sidebar category. The
719
+ editor object also includes the outline plugin: it hides empty version slots
720
+ that the block's test doesn't use, while content in unused slots stays visible
721
+ with a warning. Both helpers accept `{ routes }`; the editor also accepts
722
+ `accent`. Use the host's Payload routes and the plugin's `theme.accent`.
723
+
724
+ #### Wiring Puck by hand
725
+
726
+ The separate pieces stay exported:
727
+
728
+ - `/puck`: `ExperimentSlot`, `createExperimentSlot({ routes })`,
729
+ `EXPERIMENT_SLOT_TYPE` and `createExperimentSlotField({ routes })`.
730
+ - `/puck/editor`: `ExperimentSlotEditor`,
731
+ `createExperimentSlotEditor({ routes, accent })` and `experimentOutlinePlugin()`.
732
+
733
+ Register the render and editor blocks under the same `EXPERIMENT_SLOT_TYPE` key (import it from `/puck`),
734
+ add a sidebar category listing it, and add `experimentOutlinePlugin()` to Puck's
735
+ `plugins`. It composes with the host's outline override.
736
+
737
+ Routes default to Payload's `/admin` and `/api` when omitted. The existing
738
+ `ExperimentSlot` and `ExperimentSlotEditor` exports remain available with these
739
+ defaults. Hosts on non-default routes switch to the factories. The standalone
740
+ `createExperimentSlotField({ routes })` also accepts the host's routes.
741
+
742
+ Pass the same accent as the plugin's `theme.accent`: the editor cannot read
743
+ Payload config. Both use the same validator and report the same error for an
744
+ invalid colour. Without an accent the editor uses the neutral palette.
745
+
636
746
  Load the editor stylesheet inside the Puck iframe. With Tailwind v4, add the
637
747
  package import **after** Tailwind in the CSS that builds your editor iframe
638
748
  stylesheet (the stylesheet listed in payload-puck's `editorStylesheets`):
@@ -648,6 +758,15 @@ The editor CSS includes its own light and dark palette, selected by
648
758
  `data-theme="dark"` or the `dark` class on the iframe's `<html>`. The client component's CSS import alone
649
759
  does not reach an iframe configured with `syncHostStyles: false`.
650
760
 
761
+ If Puck runs outside Payload's admin, the page needs Payload's admin styles (the plugin's tokens map onto them) and the plugin's admin styles, plus `data-theme="light"` or `data-theme="dark"` on `<html>`. Inside the admin all of this is already in place:
762
+
763
+ ```css
764
+ @import "@payloadcms/next/css";
765
+ @import "@payloadcms/ui/styles.css";
766
+ @import "@delmaredigital/payload-experiments/admin/theme.css";
767
+ @import "@delmaredigital/payload-experiments/admin/ui.css";
768
+ ```
769
+
651
770
  Resolve the Puck data on the server with `resolveExperimentData` before
652
771
  rendering it. The render component shows only the selected group's slot. The
653
772
  editor lets you select a block experiment, edit each group's content, copy the
@@ -757,3 +876,35 @@ The WCAG 2.x gate requires at least 4.5:1 **foreground text on the accent fill**
757
876
  in each theme (the accent is used as a fill, not as page text). A failing
758
877
  colour is refused with its ratio. Status chips retain Payload semantic colours.
759
878
  `deriveAccent` is also exported from `/puck/editor` for editor integrations.
879
+
880
+ ## Click goals
881
+
882
+ Click goals use a goal's short name as their key (`click:<short-name>`). Add
883
+ `xpGoalField()` from the root entry to a host block or array row. Its manager
884
+ picker includes inline creation with "+ New click goal"; run
885
+ `payload generate:importmap` to register the picker. For Puck's `xpGoal` prop,
886
+ use `xpGoalPuckEditorField()` from `/puck/editor` in your editor config and
887
+ `xpGoalPuckField()` from `/puck` in your render config. Run the
888
+ host's `payload migrate:create`: each table using the Payload field (and its
889
+ version table) gains a nullable text column. Render the value on the actual
890
+ interactive element as `data-xp-goal={xpGoal}`; code can also set
891
+ `data-xp-goal="join"` directly. Define a click goal with that short name first.
892
+ For managers, an undefined key blocks the save through the Payload field's
893
+ server validation, refreshing a cached miss immediately.
894
+ Draft saves are also refused when the collection validates drafts
895
+ (`versions.drafts.validate`). Editors without goal-management access see a
896
+ plain text input. Goals in
897
+ related documents and keys placed only in code are invisible to the content
898
+ fairness check.
899
+
900
+ `ExperimentsProvider` records native clicks when its server assignments arm
901
+ click goals. Nested providers share one document listener; the deepest armed
902
+ provider supplies the route and pageview id. Enter on links/buttons and Space
903
+ on buttons count through the browser's native click. A focusable `div` needs
904
+ `role="button"` and the host's own keyboard handler. Beacons use `sendBeacon`
905
+ (with a keepalive fallback) so navigation/unload does not discard the click.
906
+ Click and exposure beacons share `events.rateLimit`; size the adapter for both.
907
+ New click goals are accepted within 30 seconds. A click goal may be primary;
908
+ primary and guardrail goals must appear in every tested version or outside all
909
+ of them. Secondary asymmetry warns, and results health checks published rows
910
+ again on every run.
@@ -1,14 +1,14 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { SelectInput, useField } from '@payloadcms/ui';
4
- import { metricSourceHint, metricSourceOptions } from './metricSource.model.js';
4
+ import { metricSourceHint, metricSourceOptions, sourceFor } from './metricSource.model.js';
5
5
  import { METRICS_COPY } from './copy/metrics.js';
6
6
  export function MetricSourceSelect({ path, field, readOnly }) {
7
7
  const { value, setValue } = useField({
8
8
  path
9
9
  });
10
10
  const sources = field.admin?.custom?.sources ?? [];
11
- const hint = metricSourceHint(sources.find((source)=>source.id === value));
11
+ const hint = metricSourceHint(sourceFor(value, sources));
12
12
  const locked = Boolean(readOnly || field.admin?.readOnly);
13
13
  return /*#__PURE__*/ _jsxs("div", {
14
14
  children: [
@@ -19,7 +19,7 @@ export function MetricSourceSelect({ path, field, readOnly }) {
19
19
  description: field.admin?.description,
20
20
  required: field.required,
21
21
  options: metricSourceOptions(sources),
22
- value: value ?? '',
22
+ value: sourceFor(value, sources)?.id ?? value ?? '',
23
23
  readOnly: locked,
24
24
  onChange: (option)=>{
25
25
  if (!locked) setValue(option && !Array.isArray(option) ? option.value : null);
@@ -0,0 +1,3 @@
1
+ import type { UIFieldClientProps } from 'payload';
2
+ /** Manage-only endpoint: access-filtered editors see no status or error text. */
3
+ export declare function PlacementStatus(_props: UIFieldClientProps): import("react").JSX.Element;
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useConfig, useDocumentInfo } from '@payloadcms/ui';
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ import { formatAdminURL } from 'payload/shared';
6
+ import { createApi } from './api.js';
7
+ import { pageStatusCopy } from './placementStatus.model.js';
8
+ /** Manage-only endpoint: access-filtered editors see no status or error text. */ export function PlacementStatus(_props) {
9
+ const { id, collectionSlug } = useDocumentInfo();
10
+ const { config } = useConfig();
11
+ const api = useMemo(()=>createApi(config.routes.api), [
12
+ config.routes.api
13
+ ]);
14
+ const [tests, setTests] = useState([]);
15
+ useEffect(()=>{
16
+ let active = true;
17
+ setTests([]);
18
+ if (id != null && collectionSlug) void api.placementStatus({
19
+ docs: [
20
+ {
21
+ collection: collectionSlug,
22
+ id
23
+ }
24
+ ]
25
+ }).then((result)=>{
26
+ if (active && result.ok) setTests(result.data?.docs[`${collectionSlug}:${id}`]?.tests ?? []);
27
+ });
28
+ return ()=>{
29
+ active = false;
30
+ };
31
+ }, [
32
+ api,
33
+ collectionSlug,
34
+ id
35
+ ]);
36
+ return /*#__PURE__*/ _jsx(_Fragment, {
37
+ children: tests.map((test)=>{
38
+ const copy = pageStatusCopy(test);
39
+ return copy ? /*#__PURE__*/ _jsxs("p", {
40
+ children: [
41
+ copy,
42
+ ' ',
43
+ /*#__PURE__*/ _jsx("a", {
44
+ href: formatAdminURL({
45
+ adminRoute: config.routes.admin,
46
+ path: `/experiments/by-slug/${encodeURIComponent(test.slug)}`
47
+ }),
48
+ children: "Back to the test"
49
+ })
50
+ ]
51
+ }, test.slug) : null;
52
+ })
53
+ });
54
+ }
@@ -1,6 +1,7 @@
1
1
  import type { DocId } from '../types/contracts.js';
2
2
  import type { PreflightResult } from '../types/endpoints.js';
3
- export declare function PreviewLinks({ experimentId, placements, arms, flagPoint }: {
3
+ export declare function PreviewLinks({ experimentId, placements, arms, flagPoint, apiRoute }: {
4
+ apiRoute: string;
4
5
  flagPoint?: string;
5
6
  experimentId: DocId;
6
7
  placements: PreflightResult['placements'];
@@ -5,10 +5,11 @@ import { Button } from '../ui/Button.js';
5
5
  import { armLabel } from './labels.js';
6
6
  import { useApi } from './useApi.js';
7
7
  import { REVIEW_COPY as C } from './copy/review.js';
8
- import { API_MESSAGES } from './copy/common.js';
9
- import { previewPages } from './previewLinks.model.js';
10
- export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
11
- const api = useApi();
8
+ import { TEST_PAGE } from './copy/testPage.js';
9
+ import { API_MESSAGES, refusalMessage } from './copy/common.js';
10
+ import { actionablePreviewPages } from './previewLinks.model.js';
11
+ export function PreviewLinks({ experimentId, placements, arms, flagPoint, apiRoute }) {
12
+ const api = useApi(apiRoute);
12
13
  const inFlight = useRef(false);
13
14
  const [busy, setBusy] = useState(false);
14
15
  const [error, setError] = useState(null);
@@ -17,20 +18,37 @@ export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
17
18
  inFlight.current = true;
18
19
  setBusy(true);
19
20
  setError(null);
21
+ let win = null;
20
22
  try {
23
+ win = window.open('', '_blank');
24
+ if (!win) {
25
+ setError(C.previewBlocked);
26
+ return;
27
+ }
28
+ // Clearing opener immediately gives the same opener protection as noopener: the
29
+ // previewed page cannot reach window.opener. The admin URL may be sent as
30
+ // referrer to the host's own site (resolved against location.origin); accepted.
31
+ try {
32
+ win.opener = null;
33
+ } catch {}
21
34
  const result = await api.previewToken(experimentId, arm);
35
+ // The person closed the blank tab while the token loaded: nothing to show or report.
36
+ if (win.closed) return;
22
37
  if (!result.ok) {
23
- setError(result.message);
38
+ win.close();
39
+ setError(result.status === 0 ? API_MESSAGES.network : refusalMessage(result.code, result.status));
24
40
  return;
25
41
  }
26
- if (!result.data?.token) {
42
+ if (typeof result.data?.token !== 'string' || !result.data.token) {
43
+ win.close();
27
44
  setError(API_MESSAGES.unreadable);
28
45
  return;
29
46
  }
30
47
  const target = new URL(url, window.location.origin);
31
48
  target.searchParams.set('xp_preview', result.data.token);
32
- window.open(target.href, '_blank', 'noopener,noreferrer');
49
+ win.location.replace(target.href);
33
50
  } catch {
51
+ win?.close();
34
52
  setError(API_MESSAGES.failed);
35
53
  } finally{
36
54
  inFlight.current = false;
@@ -40,7 +58,7 @@ export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
40
58
  return /*#__PURE__*/ _jsxs("div", {
41
59
  className: "xp-preview-links",
42
60
  children: [
43
- previewPages(placements, flagPoint).map((placement)=>/*#__PURE__*/ _jsxs("div", {
61
+ actionablePreviewPages(placements, flagPoint).map((placement)=>/*#__PURE__*/ _jsxs("div", {
44
62
  className: "xp-preview-row",
45
63
  children: [
46
64
  /*#__PURE__*/ _jsxs("p", {
@@ -49,26 +67,29 @@ export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
49
67
  placement.slotCount > 1 ? ` ${C.placedTimes(placement.slotCount)}` : null
50
68
  ]
51
69
  }),
52
- /*#__PURE__*/ _jsx("div", {
70
+ /*#__PURE__*/ _jsxs("div", {
53
71
  className: "xp-ui-button-group",
54
- children: placement.url ? arms.filter((arm)=>arm.kind !== 'holdout' && arm.key !== 'holdout').map((arm)=>/*#__PURE__*/ _jsxs(Button, {
55
- variant: "secondary",
72
+ children: [
73
+ placement.url ? arms.filter((arm)=>arm.kind !== 'holdout' && arm.key !== 'holdout').map((arm)=>/*#__PURE__*/ _jsxs(Button, {
74
+ variant: "secondary",
75
+ size: "sm",
76
+ disabled: busy,
77
+ onClick: ()=>{
78
+ void preview(placement.url, arm.key);
79
+ },
80
+ children: [
81
+ C.preview,
82
+ " ",
83
+ arm.label?.trim() || armLabel(arm.key)
84
+ ]
85
+ }, arm.key)) : null,
86
+ placement.editorUrl ? /*#__PURE__*/ _jsx(Button, {
87
+ variant: "link",
56
88
  size: "sm",
57
- disabled: busy,
58
- onClick: ()=>{
59
- void preview(placement.url, arm.key);
60
- },
61
- children: [
62
- C.preview,
63
- " ",
64
- arm.label?.trim() || armLabel(arm.key)
65
- ]
66
- }, arm.key)) : /*#__PURE__*/ _jsx(Button, {
67
- variant: "secondary",
68
- size: "sm",
69
- disabled: true,
70
- children: C.noPreview
71
- })
89
+ href: placement.editorUrl,
90
+ children: TEST_PAGE.editOnPage
91
+ }) : null
92
+ ]
72
93
  })
73
94
  ]
74
95
  }, placement.key)),
@@ -1,3 +1,5 @@
1
1
  import './theme.css';
2
- declare function TestsNav(): import("react").JSX.Element | null;
2
+ declare function TestsNav({ isOpen }: {
3
+ isOpen?: boolean;
4
+ }): import("react").JSX.Element | null;
3
5
  export { TestsNav as TestsNavLink };
@@ -5,7 +5,7 @@ import { Link, NavGroup, useAuth, useConfig } from '@payloadcms/ui';
5
5
  import { formatAdminURL } from 'payload/shared';
6
6
  import { usePathname } from 'next/navigation.js';
7
7
  import { SURFACE_COPY } from './copy/surface.js';
8
- function TestsNav() {
8
+ function TestsNav({ isOpen = true }) {
9
9
  const { config } = useConfig();
10
10
  const { permissions } = useAuth();
11
11
  const pathname = usePathname();
@@ -38,9 +38,9 @@ function TestsNav() {
38
38
  active: goalsActive
39
39
  }
40
40
  ];
41
- // getPreference is async and can fetch; let NavGroup default to open.
42
41
  return /*#__PURE__*/ _jsx(NavGroup, {
43
42
  label: SURFACE_COPY.title,
43
+ isOpen: isOpen,
44
44
  children: entries.map((entry)=>{
45
45
  const props = {
46
46
  className: 'nav__link',
@@ -8,7 +8,8 @@ export declare function DialogFrame({ title, onClose, children, initialFocus, di
8
8
  onClose: () => void;
9
9
  children: ReactNode;
10
10
  }): import("react").JSX.Element;
11
- export declare function ConfirmDialog({ id, action, winnerLabel, onClose, canManage, isDecided }: {
11
+ export declare function ConfirmDialog({ id, action, winnerLabel, onClose, canManage, isDecided, apiRoute }: {
12
+ apiRoute: string;
12
13
  canManage?: boolean;
13
14
  isDecided?: boolean;
14
15
  id: DocId;
@@ -20,8 +20,8 @@ export function DialogFrame({ title, onClose, children, initialFocus, dismissibl
20
20
  children: children
21
21
  });
22
22
  }
23
- export function ConfirmDialog({ id, action, winnerLabel, onClose, canManage = false, isDecided = false }) {
24
- const api = useApi();
23
+ export function ConfirmDialog({ id, action, winnerLabel, onClose, canManage = false, isDecided = false, apiRoute }) {
24
+ const api = useApi(apiRoute);
25
25
  const [typed, setTyped] = useState('');
26
26
  const [busy, setBusy] = useState(false);
27
27
  const [reportBusy, setReportBusy] = useState(false);
@@ -1,7 +1,8 @@
1
1
  import type { DocId } from '../../types/contracts.js';
2
2
  import type { PreflightResult } from '../../types/endpoints.js';
3
3
  import type { ActionGroup } from './actions.model.js';
4
- export declare function DecideDialog({ id, arms, checks, onClose }: {
4
+ export declare function DecideDialog({ id, arms, checks, onClose, apiRoute }: {
5
+ apiRoute: string;
5
6
  id: DocId;
6
7
  arms: readonly ActionGroup[];
7
8
  checks?: PreflightResult['checks'];
@@ -10,8 +10,8 @@ import { RESULTS_COPY } from '../copy/results.js';
10
10
  import { ACTIONS_COPY as C, decideCopy } from '../copy/actions.js';
11
11
  import { decideState } from './decide.model.js';
12
12
  import { DialogFrame } from './ConfirmDialog.js';
13
- export function DecideDialog({ id, arms, checks = [], onClose }) {
14
- const api = useApi();
13
+ export function DecideDialog({ id, arms, checks = [], onClose, apiRoute }) {
14
+ const api = useApi(apiRoute);
15
15
  const [snapshot, setSnapshot] = useState(null);
16
16
  const [chosenArm, setChosenArm] = useState(null);
17
17
  const [note, setNote] = useState('');
@@ -1,5 +1,5 @@
1
1
  import type { DocId } from '../types/contracts.js';
2
- import type { RestoreContentEntryBody, RestoreContentEntryResult, DismissContentEntryBody, DismissContentEntryResult, AddMetricBody, AllocateBody, ArchiveBody, DecideBody, MakeContentResult, PreflightResult, PreviewTokenResult, RefusalCode, ResultsResponse, SummaryResponse, TransitionSuccess } from '../types/endpoints.js';
2
+ import type { WrapBlockBody, PagesResult, PlacementStatusBody, PlacementStatusResult, RestoreContentEntryBody, RestoreContentEntryResult, DismissContentEntryBody, DismissContentEntryResult, AddMetricBody, AllocateBody, ArchiveBody, DecideBody, MakeContentResult, AddMetricResult, ClickKeysResult, PreflightResult, PreviewTokenResult, RefusalCode, ResultsResponse, SummaryResponse, TransitionSuccess } from '../types/endpoints.js';
3
3
  import { refusalMessage } from './copy/common.js';
4
4
  export type ApiResult<T> = {
5
5
  ok: true;
@@ -13,9 +13,17 @@ export type ApiResult<T> = {
13
13
  };
14
14
  type RequestOptions = NonNullable<Parameters<typeof refusalMessage>[3]>;
15
15
  export declare function createApi(apiBase: string): {
16
+ wrapBlock: (body: WrapBlockBody) => Promise<ApiResult<Record<string, unknown>>>;
17
+ pages: (query: {
18
+ collection: string;
19
+ q?: string;
20
+ path?: string;
21
+ page?: number;
22
+ }, signal?: AbortSignal) => Promise<ApiResult<PagesResult>>;
23
+ placementStatus: (body: PlacementStatusBody) => Promise<ApiResult<PlacementStatusResult>>;
16
24
  start: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
17
25
  allocate: (id: DocId, body: AllocateBody, options?: RequestOptions) => Promise<ApiResult<TransitionSuccess>>;
18
- addMetric: (id: DocId, body: AddMetricBody, options?: RequestOptions) => Promise<ApiResult<TransitionSuccess>>;
26
+ addMetric: (id: DocId, body: AddMetricBody, options?: RequestOptions) => Promise<ApiResult<AddMetricResult>>;
19
27
  stop: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
20
28
  decide: (id: DocId, body: DecideBody) => Promise<ApiResult<TransitionSuccess>>;
21
29
  releaseHoldout: (id: DocId) => Promise<ApiResult<TransitionSuccess>>;
@@ -24,6 +32,7 @@ export declare function createApi(apiBase: string): {
24
32
  dismissContentEntry: (id: DocId, body: DismissContentEntryBody) => Promise<ApiResult<DismissContentEntryResult>>;
25
33
  restoreContentEntry: (id: DocId, body: RestoreContentEntryBody) => Promise<ApiResult<RestoreContentEntryResult>>;
26
34
  archive: (id: DocId, body: ArchiveBody) => Promise<ApiResult<TransitionSuccess>>;
35
+ clickKeys: (id: DocId) => Promise<ApiResult<ClickKeysResult>>;
27
36
  preflight: (id: DocId) => Promise<ApiResult<PreflightResult>>;
28
37
  previewToken: (id: DocId, arm: string) => Promise<ApiResult<PreviewTokenResult>>;
29
38
  results: (id: DocId, options?: {