@delmaredigital/payload-experiments 0.3.0-rc.4 → 0.4.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/README.md +122 -27
  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 +2 -2
  7. package/dist/admin/TestsDashboardCards.d.ts +1 -0
  8. package/dist/admin/TestsDashboardCards.js +4 -8
  9. package/dist/admin/TestsNavLink.d.ts +1 -0
  10. package/dist/admin/TestsNavLink.js +4 -10
  11. package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
  12. package/dist/admin/actions/ConfirmDialog.js +2 -2
  13. package/dist/admin/actions/DecideDialog.d.ts +2 -1
  14. package/dist/admin/actions/DecideDialog.js +2 -2
  15. package/dist/admin/api.d.ts +18 -2
  16. package/dist/admin/api.js +47 -2
  17. package/dist/admin/blocks/BlockPanel.d.ts +4 -0
  18. package/dist/admin/blocks/BlockPanel.js +199 -0
  19. package/dist/admin/blocks/TestBlockField.d.ts +5 -0
  20. package/dist/admin/blocks/TestBlockField.js +163 -0
  21. package/dist/admin/blocks/formApi.d.ts +46 -0
  22. package/dist/admin/blocks/formApi.js +159 -0
  23. package/dist/admin/blocks/inject.d.ts +7 -0
  24. package/dist/admin/blocks/inject.js +41 -0
  25. package/dist/admin/blocks/model.d.ts +17 -0
  26. package/dist/admin/blocks/model.js +45 -0
  27. package/dist/admin/blocks/statusCache.d.ts +9 -0
  28. package/dist/admin/blocks/statusCache.js +92 -0
  29. package/dist/admin/blocks/useBlockForm.d.ts +17 -0
  30. package/dist/admin/blocks/useBlockForm.js +46 -0
  31. package/dist/admin/copy/actions.d.ts +2 -0
  32. package/dist/admin/copy/actions.js +9 -0
  33. package/dist/admin/copy/blocks.d.ts +43 -0
  34. package/dist/admin/copy/blocks.js +64 -0
  35. package/dist/admin/copy/common.d.ts +1 -0
  36. package/dist/admin/copy/common.js +1 -1
  37. package/dist/admin/copy/goals.d.ts +2 -0
  38. package/dist/admin/copy/goals.js +4 -1
  39. package/dist/admin/copy/newTest.d.ts +10 -0
  40. package/dist/admin/copy/newTest.js +10 -0
  41. package/dist/admin/copy/pages.d.ts +24 -0
  42. package/dist/admin/copy/pages.js +24 -0
  43. package/dist/admin/copy/readiness.d.ts +23 -3
  44. package/dist/admin/copy/readiness.js +24 -3
  45. package/dist/admin/copy/results.d.ts +1 -0
  46. package/dist/admin/copy/results.js +1 -0
  47. package/dist/admin/copy/testPage.d.ts +4 -0
  48. package/dist/admin/copy/testPage.js +4 -0
  49. package/dist/admin/goals/XpGoalField.d.ts +4 -0
  50. package/dist/admin/goals/XpGoalField.js +168 -0
  51. package/dist/admin/goals/xpGoalField.css +48 -0
  52. package/dist/admin/metricSource.model.d.ts +2 -0
  53. package/dist/admin/metricSource.model.js +4 -0
  54. package/dist/admin/pageCollections.d.ts +10 -0
  55. package/dist/admin/pageCollections.js +20 -0
  56. package/dist/admin/paths.d.ts +9 -0
  57. package/dist/admin/paths.js +9 -0
  58. package/dist/admin/placementStatus.model.d.ts +2 -0
  59. package/dist/admin/placementStatus.model.js +19 -0
  60. package/dist/admin/placements/placementLabels.d.ts +11 -0
  61. package/dist/admin/placements/placementLabels.js +21 -0
  62. package/dist/admin/placements/placementLabels.model.d.ts +13 -0
  63. package/dist/admin/placements/placementLabels.model.js +12 -0
  64. package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
  65. package/dist/admin/placements/usePlacementLabels.js +102 -0
  66. package/dist/admin/results/DataQualityPanel.js +4 -1
  67. package/dist/admin/results/results.model.d.ts +3 -0
  68. package/dist/admin/results/results.model.js +8 -4
  69. package/dist/admin/surface/TestsHome.js +39 -10
  70. package/dist/admin/surface/TestsSurface.js +7 -6
  71. package/dist/admin/surface/contracts.d.ts +6 -5
  72. package/dist/admin/surface/goals/GoalForm.js +71 -44
  73. package/dist/admin/surface/goals/GoalsList.js +5 -4
  74. package/dist/admin/surface/goals/data.js +4 -1
  75. package/dist/admin/surface/goals/goals.model.d.ts +1 -2
  76. package/dist/admin/surface/goals/goals.model.js +1 -3
  77. package/dist/admin/surface/home.model.d.ts +3 -3
  78. package/dist/admin/surface/home.model.js +8 -4
  79. package/dist/admin/surface/newTest/NewTest.js +56 -17
  80. package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
  81. package/dist/admin/surface/newTest/PageWizard.js +214 -0
  82. package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
  83. package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
  84. package/dist/admin/surface/pagePicker/PageCombobox.d.ts +10 -0
  85. package/dist/admin/surface/pagePicker/PageCombobox.js +395 -0
  86. package/dist/admin/surface/pagePicker/pagePicker.css +10 -0
  87. package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +27 -0
  88. package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
  89. package/dist/admin/surface/readiness/ActionDialogs.js +99 -64
  90. package/dist/admin/surface/readiness/Banner.js +30 -6
  91. package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
  92. package/dist/admin/surface/readiness/readiness.model.js +92 -9
  93. package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
  94. package/dist/admin/surface/results/ReleasePages.js +3 -7
  95. package/dist/admin/surface/results/ResultsBody.js +21 -2
  96. package/dist/admin/surface/results/ResultsHeader.js +5 -13
  97. package/dist/admin/surface/results/useResults.d.ts +20 -1
  98. package/dist/admin/surface/test/AdvancedSection.js +111 -104
  99. package/dist/admin/surface/test/GoalSection.js +94 -14
  100. package/dist/admin/surface/test/LengthSection.js +1 -1
  101. package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
  102. package/dist/admin/surface/test/PageHoldout.js +67 -0
  103. package/dist/admin/surface/test/TestPage.js +5 -9
  104. package/dist/admin/surface/test/VersionsSection.js +12 -5
  105. package/dist/admin/surface/test/fields.d.ts +13 -4
  106. package/dist/admin/surface/test/fields.js +46 -14
  107. package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
  108. package/dist/admin/surface/test/jumpToSection.js +4 -3
  109. package/dist/admin/surface/test/pageParts.d.ts +4 -1
  110. package/dist/admin/surface/test/pageParts.js +49 -1
  111. package/dist/admin/surface/test/testPage.css +3 -1
  112. package/dist/admin/surface/testDocument.model.d.ts +1 -0
  113. package/dist/admin/surface/testDocument.model.js +2 -1
  114. package/dist/admin/surface/useTestDocument.d.ts +15 -2
  115. package/dist/admin/surface/useTestDocument.js +53 -15
  116. package/dist/admin/surfaceUrls.d.ts +7 -0
  117. package/dist/admin/surfaceUrls.js +19 -0
  118. package/dist/admin/theme.css +2 -0
  119. package/dist/admin/useApi.d.ts +21 -2
  120. package/dist/admin/useApi.js +3 -5
  121. package/dist/admin/views/CollectionListRedirect.d.ts +1 -0
  122. package/dist/admin/views/CollectionListRedirect.js +1 -1
  123. package/dist/admin/views/ExperimentsRootView.js +28 -133
  124. package/dist/admin/views/GoalEditRedirect.d.ts +4 -0
  125. package/dist/admin/views/GoalEditRedirect.js +14 -0
  126. package/dist/admin/views/GoalsCollectionListView.d.ts +2 -0
  127. package/dist/admin/views/GoalsCollectionListView.js +4 -0
  128. package/dist/admin/views/TestsCollectionListView.d.ts +2 -0
  129. package/dist/admin/views/TestsCollectionListView.js +4 -0
  130. package/dist/admin/views/TestsNavLinkServer.d.ts +1 -0
  131. package/dist/admin/views/TestsNavLinkServer.js +1 -1
  132. package/dist/admin/views/buildSurfaceProps.d.ts +8 -0
  133. package/dist/admin/views/buildSurfaceProps.js +143 -0
  134. package/dist/admin/views/renderCollectionList.d.ts +2 -0
  135. package/dist/admin/views/renderCollectionList.js +34 -0
  136. package/dist/admin/views/renderTestsSurface.d.ts +3 -0
  137. package/dist/admin/views/renderTestsSurface.js +34 -0
  138. package/dist/blocks/bindings.d.ts +3 -0
  139. package/dist/blocks/bindings.js +34 -0
  140. package/dist/blocks/createExperimentBlock.d.ts +6 -0
  141. package/dist/blocks/createExperimentBlock.js +77 -0
  142. package/dist/blocks/formState.d.ts +31 -0
  143. package/dist/blocks/formState.js +146 -0
  144. package/dist/blocks/model.d.ts +41 -0
  145. package/dist/blocks/model.js +34 -0
  146. package/dist/client/ExperimentGroup.d.ts +6 -0
  147. package/dist/client/ExperimentGroup.js +9 -0
  148. package/dist/client/ExperimentsProvider.js +20 -4
  149. package/dist/client/clickGoals.d.ts +4 -0
  150. package/dist/client/clickGoals.js +43 -0
  151. package/dist/collections/experiments/config.js +21 -7
  152. package/dist/collections/experiments/deleteGuard.d.ts +2 -1
  153. package/dist/collections/experiments/deleteGuard.js +12 -1
  154. package/dist/collections/experiments/editability.js +4 -2
  155. package/dist/collections/experiments/pickSlug.d.ts +2 -0
  156. package/dist/collections/experiments/pickSlug.js +8 -0
  157. package/dist/collections/labels.d.ts +3 -0
  158. package/dist/collections/labels.js +20 -0
  159. package/dist/collections/metrics.js +28 -6
  160. package/dist/collections/phases.js +1 -0
  161. package/dist/collections/serialize.js +7 -0
  162. package/dist/db/clickGoals.d.ts +9 -0
  163. package/dist/db/clickGoals.js +22 -0
  164. package/dist/db/delete.d.ts +1 -0
  165. package/dist/db/delete.js +1 -0
  166. package/dist/db/locks.d.ts +8 -0
  167. package/dist/db/locks.js +27 -4
  168. package/dist/db/names.d.ts +4 -0
  169. package/dist/db/names.js +4 -0
  170. package/dist/db/pageBindings.d.ts +4 -0
  171. package/dist/db/pageBindings.js +17 -0
  172. package/dist/db/pageBusy.d.ts +2 -0
  173. package/dist/db/pageBusy.js +10 -0
  174. package/dist/db/pageContent.d.ts +10 -0
  175. package/dist/db/pageContent.js +79 -0
  176. package/dist/db/placements.d.ts +33 -0
  177. package/dist/db/placements.js +54 -0
  178. package/dist/db/slugs.d.ts +8 -0
  179. package/dist/db/slugs.js +40 -0
  180. package/dist/db/snapshots.d.ts +6 -0
  181. package/dist/db/snapshots.js +8 -5
  182. package/dist/endpoints/clickKeys.d.ts +5 -0
  183. package/dist/endpoints/clickKeys.js +17 -0
  184. package/dist/endpoints/events.js +4 -1
  185. package/dist/endpoints/index.js +28 -0
  186. package/dist/endpoints/pages.d.ts +5 -0
  187. package/dist/endpoints/pages.js +120 -0
  188. package/dist/endpoints/placementStatus.d.ts +6 -0
  189. package/dist/endpoints/placementStatus.js +238 -0
  190. package/dist/endpoints/readBody.d.ts +2 -0
  191. package/dist/endpoints/readBody.js +24 -0
  192. package/dist/endpoints/results.js +5 -1
  193. package/dist/endpoints/summary.js +11 -4
  194. package/dist/endpoints/wrapBlock.d.ts +6 -0
  195. package/dist/endpoints/wrapBlock.js +76 -0
  196. package/dist/errors.d.ts +3 -3
  197. package/dist/errors.js +4 -4
  198. package/dist/events/rows.d.ts +1 -1
  199. package/dist/events/rows.js +5 -3
  200. package/dist/exports/adminClient.d.ts +5 -0
  201. package/dist/exports/adminClient.js +5 -0
  202. package/dist/exports/adminRsc.d.ts +3 -0
  203. package/dist/exports/adminRsc.js +3 -0
  204. package/dist/exports/blocks.d.ts +2 -0
  205. package/dist/exports/blocks.js +1 -0
  206. package/dist/exports/client.d.ts +1 -0
  207. package/dist/exports/client.js +1 -0
  208. package/dist/exports/index.d.ts +6 -1
  209. package/dist/exports/index.js +3 -0
  210. package/dist/exports/puck.d.ts +3 -1
  211. package/dist/exports/puck.js +2 -0
  212. package/dist/exports/puckEditor.d.ts +3 -0
  213. package/dist/exports/puckEditor.js +2 -0
  214. package/dist/exports/rsc.d.ts +3 -0
  215. package/dist/exports/rsc.js +2 -0
  216. package/dist/fields/xpGoal.d.ts +3 -0
  217. package/dist/fields/xpGoal.js +16 -0
  218. package/dist/globals/state.js +10 -0
  219. package/dist/goals/cache.d.ts +3 -0
  220. package/dist/goals/cache.js +35 -0
  221. package/dist/goals/fairness.d.ts +40 -0
  222. package/dist/goals/fairness.js +200 -0
  223. package/dist/goals/key.d.ts +4 -0
  224. package/dist/goals/key.js +3 -0
  225. package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
  226. package/dist/goals/picker/clickGoalPicker.model.js +45 -0
  227. package/dist/goals/picker/copy.d.ts +16 -0
  228. package/dist/goals/picker/copy.js +15 -0
  229. package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
  230. package/dist/goals/picker/useClickGoalPicker.js +220 -0
  231. package/dist/goals/validate.d.ts +3 -0
  232. package/dist/goals/validate.js +14 -0
  233. package/dist/hooks/pageGuard.d.ts +5 -1
  234. package/dist/hooks/pageGuard.js +157 -95
  235. package/dist/hooks/puckGoals.d.ts +3 -0
  236. package/dist/hooks/puckGoals.js +38 -0
  237. package/dist/onInit.js +29 -0
  238. package/dist/options.d.ts +2 -0
  239. package/dist/options.js +41 -1
  240. package/dist/plugin.js +50 -43
  241. package/dist/puck/copy.d.ts +1 -0
  242. package/dist/puck/copy.js +1 -0
  243. package/dist/puck/editor/editor.chrome.css +14 -0
  244. package/dist/puck/editor/editor.css +106 -0
  245. package/dist/puck/editor/editor.model.d.ts +4 -2
  246. package/dist/puck/editor/editor.model.js +6 -3
  247. package/dist/puck/editor/integration.d.ts +10 -0
  248. package/dist/puck/editor/integration.js +30 -0
  249. package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
  250. package/dist/puck/editor/xpGoalPuckEditorField.js +142 -0
  251. package/dist/puck/integration.d.ts +6 -0
  252. package/dist/puck/integration.js +21 -0
  253. package/dist/puck/xpGoalField.d.ts +3 -0
  254. package/dist/puck/xpGoalField.js +6 -0
  255. package/dist/results/health.d.ts +4 -0
  256. package/dist/results/health.js +27 -0
  257. package/dist/results/snapshot.js +15 -2
  258. package/dist/sanitizeReadinessDetail.js +43 -0
  259. package/dist/server/pageKindSeoContract.d.ts +27 -0
  260. package/dist/server/pageKindSeoContract.js +80 -0
  261. package/dist/server/recordContentChange.js +58 -8
  262. package/dist/server/resolveExperimentBlocks.d.ts +5 -0
  263. package/dist/server/resolveExperimentBlocks.js +54 -0
  264. package/dist/server/runResults.js +56 -15
  265. package/dist/server/toClientAssignments.js +2 -1
  266. package/dist/transitions/addMetric.d.ts +2 -1
  267. package/dist/transitions/addMetric.js +28 -1
  268. package/dist/transitions/checkStart.d.ts +14 -1
  269. package/dist/transitions/checkStart.js +92 -8
  270. package/dist/transitions/decide.js +25 -0
  271. package/dist/transitions/deleteExperiment.d.ts +3 -1
  272. package/dist/transitions/deleteExperiment.js +27 -1
  273. package/dist/transitions/kernel.js +4 -0
  274. package/dist/transitions/makeContent.d.ts +5 -0
  275. package/dist/transitions/makeContent.js +39 -11
  276. package/dist/transitions/pageCollection.d.ts +10 -0
  277. package/dist/transitions/pageCollection.js +20 -0
  278. package/dist/transitions/pageVersions.d.ts +10 -0
  279. package/dist/transitions/pageVersions.js +44 -0
  280. package/dist/transitions/phaseRecord.d.ts +1 -0
  281. package/dist/transitions/placementInteractions.d.ts +40 -0
  282. package/dist/transitions/placementInteractions.js +94 -0
  283. package/dist/transitions/placements.d.ts +8 -0
  284. package/dist/transitions/placements.js +148 -1
  285. package/dist/transitions/preflightData.d.ts +2 -2
  286. package/dist/transitions/preflightData.js +137 -5
  287. package/dist/transitions/resolveCaps.d.ts +1 -0
  288. package/dist/transitions/resolveCaps.js +6 -2
  289. package/dist/transitions/start.js +46 -1
  290. package/dist/types/admin.d.ts +1 -1
  291. package/dist/types/assignments.d.ts +1 -0
  292. package/dist/types/contracts.d.ts +15 -0
  293. package/dist/types/endpoints.d.ts +92 -5
  294. package/dist/types/index.d.ts +3 -0
  295. package/dist/types/puck.d.ts +7 -1
  296. package/dist/types/snapshot.d.ts +17 -4
  297. package/dist/types/surface.d.ts +13 -0
  298. package/dist/ui/ui.css +4 -0
  299. package/package.json +8 -1
@@ -5,3 +5,4 @@ export { useExperiment } from '../client/useExperiment.js';
5
5
  export { ExperimentPageview } from '../client/ExperimentPageview.js';
6
6
  export { trackConversionBeacon } from '../client/trackConversionBeacon.js';
7
7
  export { MAX_EVENTS_PER_BODY, DEFAULT_MAX_BODY_BYTES } from '../constants.js';
8
+ export { ExperimentGroup } from '../client/ExperimentGroup.js';
@@ -2,4 +2,9 @@ export { createExperimentsPlugin } from '../plugin.js';
2
2
  export { defineFlagPoint, defineMetricSource } from '../define.js';
3
3
  export { contentDone } from '../contentDone.js';
4
4
  export { createArmSelectField } from '../fields/armSelect.js';
5
- export type { SerializedFlagPoint, SerializedMetricSource, SerializedAttributeKeys, EditableField, Assignment, Assignments, FlagPointResult, ClientAssignments, RenderedExposure, DocId, PreviewPlacement, Attributes, ExposurePolicy, ExperimentKind, Status, UnitType, ArmKey, ArmKind, TargetingRule, Arm, ParamField, MetricSource, FlagPoint, RateLimiter, ExperimentsAccess, ExperimentsPluginOptions, ExperimentsPageCollection, ArmIssueCode, RefusalCode, ReadinessCode, ErrorEnvelope, TransitionSuccess, AllocateBody, AddMetricBody, DecideBody, ArchiveBody, MakeContentResult, DismissContentEntryBody, DismissContentEntryResult, RestoreContentEntryBody, RestoreContentEntryResult, ContentReport, PreflightResult, PreviewTokenResult, PhaseSummary, ResultsResponse, SummaryResponse, EventBody, FlagDefault, Manifest, ManifestExperiment, ArmSlot, ExperimentRef, ExperimentSlotProps, ExperimentsProxyRuntimeOptions, ExperimentsProxyRuntime, ExperimentsServerRuntimeOptions, ExperimentsServerRuntime, VerdictCode, Snapshot, MetricUnavailableReason, ContentReportReason, } from '../types/index.js';
5
+ export type { SerializedFlagPoint, SerializedMetricSource, SerializedAttributeKeys, EditableField, Assignment, Assignments, FlagPointResult, ClientAssignments, RenderedExposure, DocId, PreviewPlacement, Attributes, ExposurePolicy, ExperimentKind, Status, UnitType, ArmKey, ArmKind, TargetingRule, Arm, ParamField, MetricSource, FlagPoint, RateLimiter, ExperimentsAccess, ExperimentsPluginOptions, ExperimentsPageCollection, ArmIssueCode, RefusalCode, ReadinessCode, ErrorEnvelope, TransitionSuccess, AllocateBody, AddMetricBody, AddMetricWarning, AddMetricResult, ClickKeysResult, DecideBody, ArchiveBody, MakeContentResult, DismissContentEntryBody, DismissContentEntryResult, RestoreContentEntryBody, RestoreContentEntryResult, ContentReport, PreflightResult, PreviewTokenResult, PhaseSummary, ResultsResponse, SummaryResponse, EventBody, FlagDefault, Manifest, ManifestExperiment, ArmSlot, ExperimentRef, ExperimentSlotProps, ExperimentsProxyRuntimeOptions, ExperimentsProxyRuntime, ExperimentsServerRuntimeOptions, ExperimentsServerRuntime, VerdictCode, Snapshot, MetricUnavailableReason, ContentReportReason, } from '../types/index.js';
6
+ export { createExperimentBlock } from '../blocks/createExperimentBlock.js';
7
+ export { EXPERIMENT_BLOCK_SLUG } from '../blocks/model.js';
8
+ export type { HostBlock, ExperimentBlock, ExperimentBlockGroup, ResolvedHostBlock, ResolvedExperimentBlocks } from '../blocks/model.js';
9
+ export type { WrapBlockBody, SnapshotHealth } from '../types/index.js';
10
+ export { xpGoalField } from '../fields/xpGoal.js';
@@ -2,3 +2,6 @@ export { createExperimentsPlugin } from '../plugin.js';
2
2
  export { defineFlagPoint, defineMetricSource } from '../define.js';
3
3
  export { contentDone } from '../contentDone.js';
4
4
  export { createArmSelectField } from '../fields/armSelect.js';
5
+ export { createExperimentBlock } from '../blocks/createExperimentBlock.js';
6
+ export { EXPERIMENT_BLOCK_SLUG } from '../blocks/model.js';
7
+ export { xpGoalField } from '../fields/xpGoal.js';
@@ -1,5 +1,7 @@
1
1
  export { ExperimentSlot, createExperimentSlot } from '../puck/ExperimentSlot.js';
2
2
  export { createExperimentSlotField } from '../puck/experimentSlotField.js';
3
+ export { experimentsPuck } from '../puck/integration.js';
3
4
  export { EXPERIMENT_SLOT_TYPE } from '../types/puck.js';
4
- export type { ArmSlot, ExperimentRef, ExperimentSlotProps } from '../types/puck.js';
5
+ export type { ArmSlot, ExperimentRef, ExperimentSlotProps, ExperimentsPuckIntegration } from '../types/puck.js';
5
6
  export type { PuckRoutes } from '../puck/routes.js';
7
+ export { xpGoalPuckField } from '../puck/xpGoalField.js';
@@ -1,3 +1,5 @@
1
1
  export { ExperimentSlot, createExperimentSlot } from '../puck/ExperimentSlot.js';
2
2
  export { createExperimentSlotField } from '../puck/experimentSlotField.js';
3
+ export { experimentsPuck } from '../puck/integration.js';
3
4
  export { EXPERIMENT_SLOT_TYPE } from '../types/puck.js';
5
+ export { xpGoalPuckField } from '../puck/xpGoalField.js';
@@ -1,4 +1,7 @@
1
1
  export { ExperimentSlotEditor, createExperimentSlotEditor } from '../puck/editor/ExperimentSlotEditor.js';
2
2
  export { experimentOutlinePlugin } from '../puck/editor/experimentOutlinePlugin.js';
3
+ export { experimentsPuckEditor } from '../puck/editor/integration.js';
4
+ export type { ExperimentsPuckIntegration } from '../types/puck.js';
3
5
  export { deriveAccent } from '../ui/accent.js';
6
+ export { xpGoalPuckEditorField } from '../puck/editor/xpGoalPuckEditorField.js';
4
7
  export type { PuckRoutes } from '../puck/routes.js';
@@ -1,4 +1,6 @@
1
1
  'use client';
2
2
  export { ExperimentSlotEditor, createExperimentSlotEditor } from '../puck/editor/ExperimentSlotEditor.js';
3
3
  export { experimentOutlinePlugin } from '../puck/editor/experimentOutlinePlugin.js';
4
+ export { experimentsPuckEditor } from '../puck/editor/integration.js';
4
5
  export { deriveAccent } from '../ui/accent.js';
6
+ export { xpGoalPuckEditorField } from '../puck/editor/xpGoalPuckEditorField.js';
@@ -16,3 +16,6 @@ export { compileManifest } from '../manifest/compile.js';
16
16
  export type { ManifestInputs, ManifestInputExperiment, ManifestPhase, ManifestCompileOptions } from '../manifest/compile.js';
17
17
  export type { Manifest, ManifestExperiment } from '../types/manifest.js';
18
18
  export { contentChangesBeforeHook as captureContentBaseline } from '../hooks/contentChanges.js';
19
+ export { pageKindSeoContract, type PageKindSeoContractOptions, type PageKindSeoContractRow } from '../server/pageKindSeoContract.js';
20
+ export { resolveExperimentBlocks } from '../server/resolveExperimentBlocks.js';
21
+ export type { HostBlock, ExperimentBlock, ExperimentBlockGroup, ResolvedHostBlock, ResolvedExperimentBlocks } from '../blocks/model.js';
@@ -13,3 +13,5 @@ export { toClientAssignments } from '../server/toClientAssignments.js';
13
13
  // Pure manifest builder for host tests (CORE-43): the same compile transitions run, with no database access.
14
14
  export { compileManifest } from '../manifest/compile.js';
15
15
  export { contentChangesBeforeHook as captureContentBaseline } from '../hooks/contentChanges.js';
16
+ export { pageKindSeoContract } from '../server/pageKindSeoContract.js';
17
+ export { resolveExperimentBlocks } from '../server/resolveExperimentBlocks.js';
@@ -0,0 +1,3 @@
1
+ import type { TextField } from 'payload';
2
+ /** The host renders this value as data-xp-goal on its interactive element. */
3
+ export declare function xpGoalField(): TextField;
@@ -0,0 +1,16 @@
1
+ import { XP_GOAL_FIELD } from '../admin/paths.js';
2
+ import { validateClickGoal } from '../goals/validate.js';
3
+ /** The host renders this value as data-xp-goal on its interactive element. */ export function xpGoalField() {
4
+ return {
5
+ name: 'xpGoal',
6
+ label: 'Click goal',
7
+ type: 'text',
8
+ admin: {
9
+ description: 'Choose the short name of the click goal to count here.',
10
+ components: {
11
+ Field: XP_GOAL_FIELD
12
+ }
13
+ },
14
+ validate: (value, { req })=>validateClickGoal(value, req)
15
+ };
16
+ }
@@ -1,3 +1,4 @@
1
+ import { preserveRetiredSlugs } from '../db/slugs.js';
1
2
  import { deny, isTrue } from '../collections/access.js';
2
3
  export function stateGlobal(options) {
3
4
  return {
@@ -13,12 +14,21 @@ export function stateGlobal(options) {
13
14
  update: deny,
14
15
  readVersions: deny
15
16
  },
17
+ hooks: {
18
+ beforeChange: [
19
+ preserveRetiredSlugs
20
+ ]
21
+ },
16
22
  fields: [
17
23
  {
18
24
  name: 'revision',
19
25
  type: 'number',
20
26
  defaultValue: 0
21
27
  },
28
+ {
29
+ name: 'retiredSlugs',
30
+ type: 'json'
31
+ },
22
32
  {
23
33
  name: 'manifest',
24
34
  type: 'json'
@@ -0,0 +1,3 @@
1
+ import type { Payload } from 'payload';
2
+ /** Cache failures too, retaining the last good set and bounding retry traffic. */
3
+ export declare function clickKeys(payload: Payload): Promise<ReadonlySet<string> | undefined>;
@@ -0,0 +1,35 @@
1
+ import { definedClickKeys } from '../db/clickGoals.js';
2
+ import { pooledExecutor } from '../db/executor.js';
3
+ import { safeError } from '../log.js';
4
+ import { CLICK_KEY } from './key.js';
5
+ const caches = new WeakMap();
6
+ /** Cache failures too, retaining the last good set and bounding retry traffic. */ export async function clickKeys(payload) {
7
+ let entry = caches.get(payload);
8
+ if (!entry) {
9
+ entry = {
10
+ until: 0
11
+ };
12
+ caches.set(payload, entry);
13
+ }
14
+ if (entry.pending) return entry.pending;
15
+ if (Date.now() < entry.until) return entry.keys;
16
+ const current = entry;
17
+ current.pending = (async ()=>{
18
+ try {
19
+ current.keys = new Set([
20
+ ...await definedClickKeys(pooledExecutor(payload))
21
+ ].filter((key)=>CLICK_KEY.test(key)));
22
+ } catch (error) {
23
+ try {
24
+ payload.logger.error({
25
+ err: safeError(error)
26
+ }, 'Experiments: click goal lookup failed');
27
+ } catch {}
28
+ } finally{
29
+ current.until = Date.now() + 30_000;
30
+ current.pending = undefined;
31
+ }
32
+ return current.keys;
33
+ })();
34
+ return current.pending;
35
+ }
@@ -0,0 +1,40 @@
1
+ import type { PayloadRequest } from 'payload';
2
+ import type { Arm, DocId, ResolvedOptions } from '../types/contracts.js';
3
+ import type { PlacementDoc } from '../db/placements.js';
4
+ export declare const OUTSIDE_GOAL = "This goal isn't inside the part being tested, so every version shows it the same way.";
5
+ export declare const LEGACY_FAIRNESS = "Goal fairness isn't checked for tests started before this update.";
6
+ export interface GoalPlacement {
7
+ collection?: string;
8
+ id?: DocId;
9
+ path?: string;
10
+ title?: string;
11
+ }
12
+ export interface GoalContent {
13
+ keys: Array<{
14
+ key: string;
15
+ arms: string[];
16
+ label: string;
17
+ }>;
18
+ unchecked: GoalPlacement[];
19
+ readArms: string[];
20
+ skipped?: boolean;
21
+ }
22
+ type Experiment = {
23
+ id: DocId;
24
+ slug: string;
25
+ kind: string;
26
+ arms: Arm[];
27
+ pageCollection?: string | null;
28
+ };
29
+ /** Call outside a transition, or on Start's detached request after its locks.
30
+ * Frozen null placements are deliberately skipped by results (no anonymous scan).
31
+ */
32
+ export declare function goalContent<TUser extends {
33
+ id: DocId;
34
+ }>(req: PayloadRequest, options: ResolvedOptions<TUser>, experiment: Experiment, placements?: readonly PlacementDoc[] | null): Promise<GoalContent>;
35
+ /** Readiness detail contains placement identifiers only. */
36
+ export declare function goalPlacementDetail({ collection, id, path }: GoalPlacement): Omit<GoalPlacement, 'title'>;
37
+ export declare function asymmetricGoal(content: GoalContent, source: string, arms: readonly {
38
+ key: string;
39
+ }[]): boolean;
40
+ export {};
@@ -0,0 +1,200 @@
1
+ import { clickGoalContent } from '../db/clickGoals.js';
2
+ import { safeError } from '../log.js';
3
+ import { documentEditor, puckCollections } from '../options.js';
4
+ import { EXPERIMENT_BLOCK_SLUG, hostBlocks, isObject, slotBlocks } from '../blocks/model.js';
5
+ import { resolvePageCollection } from '../transitions/pageCollection.js';
6
+ import { scanPlacements, uniquePlacementDocs } from '../transitions/placementInteractions.js';
7
+ import { CLICK_KEY } from './key.js';
8
+ export const OUTSIDE_GOAL = "This goal isn't inside the part being tested, so every version shows it the same way.";
9
+ export const LEGACY_FAIRNESS = "Goal fairness isn't checked for tests started before this update.";
10
+ /** Inspect inline JSON only; depth-zero relationship ids never dereference another document. */ function collect(value, into, label = '', labels) {
11
+ if (Array.isArray(value)) {
12
+ value.forEach((item)=>collect(item, into, label, labels));
13
+ return;
14
+ }
15
+ if (!isObject(value)) return;
16
+ const blockType = typeof value.blockType === 'string' ? value.blockType : typeof value.type === 'string' ? value.type : '';
17
+ const containing = labels.get(blockType) ?? (blockType || label);
18
+ if (typeof value.xpGoal === 'string' && CLICK_KEY.test(value.xpGoal)) into.set(value.xpGoal, containing || value.xpGoal);
19
+ Object.values(value).forEach((item)=>collect(item, into, containing, labels));
20
+ }
21
+ function blockLabels(req) {
22
+ const labels = new Map();
23
+ function walk(value) {
24
+ if (Array.isArray(value)) {
25
+ value.forEach(walk);
26
+ return;
27
+ }
28
+ if (!isObject(value)) return;
29
+ if (typeof value.slug === 'string' && isObject(value.labels) && typeof value.labels.singular === 'string') labels.set(value.slug, value.labels.singular);
30
+ for (const key of [
31
+ 'blocks',
32
+ 'fields',
33
+ 'tabs'
34
+ ])walk(value[key]);
35
+ }
36
+ walk(req.payload.config.collections);
37
+ walk(req.payload.config.blocks);
38
+ return labels;
39
+ }
40
+ /** Call outside a transition, or on Start's detached request after its locks.
41
+ * Frozen null placements are deliberately skipped by results (no anonymous scan).
42
+ */ export async function goalContent(req, options, experiment, placements) {
43
+ if (experiment.kind !== 'page' && experiment.kind !== 'block') return {
44
+ keys: [],
45
+ unchecked: [],
46
+ readArms: [],
47
+ skipped: true
48
+ };
49
+ const byArm = new Map(experiment.arms.filter((a)=>a.kind !== 'holdout').map((a)=>[
50
+ a.key,
51
+ new Map()
52
+ ]));
53
+ const labels = blockLabels(req), unchecked = [], readArms = new Set();
54
+ const failed = (error, placement)=>{
55
+ unchecked.push(placement);
56
+ req.payload.logger.error({
57
+ err: safeError(error),
58
+ experimentId: experiment.id,
59
+ collection: placement.collection,
60
+ id: placement.id
61
+ }, 'Experiment click goal content lookup failed');
62
+ };
63
+ if (experiment.kind === 'page') {
64
+ let collection;
65
+ try {
66
+ collection = await resolvePageCollection(req, options, experiment, true);
67
+ } catch (error) {
68
+ failed(error, {});
69
+ return {
70
+ keys: [],
71
+ unchecked,
72
+ readArms: []
73
+ };
74
+ }
75
+ if (!collection?.contentOf) return {
76
+ keys: [],
77
+ unchecked,
78
+ readArms: [],
79
+ skipped: true
80
+ };
81
+ for (const arm of experiment.arms.filter((a)=>a.kind !== 'holdout' && a.pageTarget)){
82
+ const placement = {
83
+ collection: collection.slug,
84
+ path: arm.pageTarget
85
+ };
86
+ try {
87
+ const page = await collection.findByPath(arm.pageTarget, req);
88
+ if (!page?.published) continue;
89
+ const doc = await clickGoalContent(req, collection.slug, page.id);
90
+ if (!doc) {
91
+ unchecked.push(placement);
92
+ continue;
93
+ }
94
+ collect(collection.contentOf(doc), byArm.get(arm.key), '', labels);
95
+ readArms.add(arm.key);
96
+ } catch (error) {
97
+ failed(error, placement);
98
+ }
99
+ }
100
+ } else if (experiment.kind === 'block') {
101
+ if (placements === null) return {
102
+ keys: [],
103
+ unchecked,
104
+ readArms: [],
105
+ skipped: true
106
+ };
107
+ let docs = placements;
108
+ if (!docs) {
109
+ try {
110
+ docs = uniquePlacementDocs((await scanPlacements(req, options, experiment.slug)).filter((placement)=>placement.published));
111
+ } catch (error) {
112
+ failed(error, {});
113
+ return {
114
+ keys: [],
115
+ unchecked,
116
+ readArms: []
117
+ };
118
+ }
119
+ }
120
+ for (const placement of docs){
121
+ try {
122
+ const doc = await clickGoalContent(req, placement.collection, placement.id);
123
+ if (!doc) continue;
124
+ const editor = documentEditor(options, placement.collection, doc);
125
+ if (editor === 'blocks') {
126
+ const registration = options.blocks?.collections.find((c)=>c.slug === placement.collection);
127
+ if (!registration) continue;
128
+ for (const block of hostBlocks(doc[registration.field])){
129
+ if (block.blockType !== (registration.blockSlug ?? EXPERIMENT_BLOCK_SLUG) || block.experiment !== experiment.slug) continue;
130
+ for (const arm of experiment.arms.filter((a)=>a.kind !== 'holdout'))collect(slotBlocks(block, arm.slot ?? arm.key), byArm.get(arm.key), '', labels);
131
+ }
132
+ } else {
133
+ const registration = puckCollections(options).find((c)=>c.slug === placement.collection);
134
+ if (!registration) continue;
135
+ const walk = (value)=>{
136
+ if (Array.isArray(value)) {
137
+ value.forEach(walk);
138
+ return;
139
+ }
140
+ if (!isObject(value)) return;
141
+ const props = isObject(value.props) ? value.props : undefined;
142
+ const ref = props?.experiment;
143
+ if (value.type === 'ExperimentSlot' && props && (typeof ref === 'string' ? ref : isObject(ref) ? ref.slug : undefined) === experiment.slug) {
144
+ for (const arm of experiment.arms.filter((a)=>a.kind !== 'holdout'))collect(props[arm.slot ?? arm.key], byArm.get(arm.key), '', labels);
145
+ return;
146
+ }
147
+ Object.values(value).forEach(walk);
148
+ };
149
+ walk(doc[registration.field]);
150
+ }
151
+ for (const arm of byArm.keys())readArms.add(arm);
152
+ } catch (error) {
153
+ failed(error, placement);
154
+ }
155
+ }
156
+ }
157
+ const rows = new Map();
158
+ for (const arm of experiment.arms){
159
+ // Holdout renders the Original and has no row or slot of its own.
160
+ const key = arm.kind === 'holdout' ? experiment.arms.find((a)=>a.kind === 'control')?.key : arm.key;
161
+ if (key && readArms.has(key)) readArms.add(arm.key);
162
+ for (const [goal, label] of (key ? byArm.get(key) : undefined) ?? []){
163
+ const row = rows.get(goal) ?? {
164
+ key: goal,
165
+ arms: [],
166
+ label
167
+ };
168
+ row.arms.push(arm.key);
169
+ rows.set(goal, row);
170
+ }
171
+ }
172
+ return {
173
+ keys: [
174
+ ...rows.values()
175
+ ].sort((a, b)=>a.key.localeCompare(b.key)),
176
+ unchecked,
177
+ readArms: [
178
+ ...readArms
179
+ ]
180
+ };
181
+ }
182
+ /** Readiness detail contains placement identifiers only. */ export function goalPlacementDetail({ collection, id, path }) {
183
+ return {
184
+ ...collection === undefined ? {} : {
185
+ collection
186
+ },
187
+ ...id === undefined ? {} : {
188
+ id
189
+ },
190
+ ...path === undefined ? {} : {
191
+ path
192
+ }
193
+ };
194
+ }
195
+ export function asymmetricGoal(content, source, arms) {
196
+ if (content.skipped) return false;
197
+ const row = content.keys.find((row)=>`click:${row.key}` === source);
198
+ const read = arms.filter((arm)=>content.readArms.includes(arm.key));
199
+ return !!row && read.some((arm)=>row.arms.includes(arm.key)) && read.some((arm)=>!row.arms.includes(arm.key));
200
+ }
@@ -0,0 +1,4 @@
1
+ /** Click keys are also metric slugs; keep the wire and field grammar identical. */
2
+ export declare const CLICK_KEY: RegExp;
3
+ export declare const isClickSource: (source: unknown) => source is string;
4
+ export declare const validClickSource: (source: unknown) => source is string;
@@ -0,0 +1,3 @@
1
+ /** Click keys are also metric slugs; keep the wire and field grammar identical. */ export const CLICK_KEY = /^[a-z0-9][a-z0-9_-]{0,63}$/;
2
+ export const isClickSource = (source)=>typeof source === 'string' && source.startsWith('click:');
3
+ export const validClickSource = (source)=>isClickSource(source) && CLICK_KEY.test(source.slice(6));
@@ -0,0 +1,29 @@
1
+ export interface ClickGoal {
2
+ id: string | number;
3
+ name: string;
4
+ slug: string;
5
+ source: string;
6
+ }
7
+ export type PickerStatus = 'loading' | 'ready' | 'not-manager' | 'error';
8
+ export declare function goalSlug(name: string): string;
9
+ export declare function clickGoalSlug(name: string): string;
10
+ export declare function definedClickGoals(rows: readonly ClickGoal[]): ClickGoal[];
11
+ export declare function undefinedClickKey(status: PickerStatus, goals: readonly ClickGoal[], value: string): boolean;
12
+ export declare function clickGoalBody(name: string, slug: string): {
13
+ name: string;
14
+ slug: string;
15
+ source: string;
16
+ direction: string;
17
+ attributionWindowHours: number;
18
+ minSampleSize: number;
19
+ capping: {
20
+ type: null;
21
+ value: null;
22
+ };
23
+ };
24
+ /** Payload nests field errors in data.errors; never show its raw message. */
25
+ export declare function hasSlugError(body: unknown): boolean;
26
+ export declare function clickGoalCreateError(slug: string, body?: unknown): {
27
+ slug?: string;
28
+ general?: string;
29
+ };
@@ -0,0 +1,45 @@
1
+ import { CLICK_KEY, isClickSource } from '../key.js';
2
+ import { CLICK_GOAL_COPY as C } from './copy.js';
3
+ export function goalSlug(name) {
4
+ return name.normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
5
+ }
6
+ export function clickGoalSlug(name) {
7
+ return goalSlug(name).slice(0, 64).replace(/[-_]+$/, '');
8
+ }
9
+ export function definedClickGoals(rows) {
10
+ return rows.filter((row)=>isClickSource(row.source));
11
+ }
12
+ export function undefinedClickKey(status, goals, value) {
13
+ return status === 'ready' && Boolean(value) && !goals.some((goal)=>goal.slug === value);
14
+ }
15
+ export function clickGoalBody(name, slug) {
16
+ return {
17
+ name: name.trim(),
18
+ slug,
19
+ source: `click:${slug}`,
20
+ direction: 'increase',
21
+ attributionWindowHours: 168,
22
+ minSampleSize: 150,
23
+ capping: {
24
+ type: null,
25
+ value: null
26
+ }
27
+ };
28
+ }
29
+ /** Payload nests field errors in data.errors; never show its raw message. */ export function hasSlugError(body) {
30
+ if (!body || typeof body !== 'object') return false;
31
+ const value = body;
32
+ if (value.path === 'slug') return true;
33
+ return Array.isArray(value.errors) && value.errors.some(hasSlugError) || hasSlugError(value.data);
34
+ }
35
+ export function clickGoalCreateError(slug, body) {
36
+ if (!CLICK_KEY.test(slug)) return {
37
+ slug: C.slugRule
38
+ };
39
+ if (hasSlugError(body)) return {
40
+ slug: C.duplicateSlug
41
+ };
42
+ return {
43
+ general: C.saveError
44
+ };
45
+ }
@@ -0,0 +1,16 @@
1
+ /** Shared by Payload's field and the Puck sidebar, without either UI dependency. */
2
+ export declare const CLICK_GOAL_COPY: {
3
+ readonly label: "Click goal";
4
+ readonly choose: "Choose a click goal";
5
+ readonly newGoal: "+ New click goal";
6
+ readonly title: "New click goal";
7
+ readonly name: "Name";
8
+ readonly slug: "Short name";
9
+ readonly advanced: "Advanced";
10
+ readonly createGoal: "Create goal";
11
+ readonly cancel: "Cancel";
12
+ readonly undefinedKey: "No click goal has this name yet. Create it with \"+ New click goal\".";
13
+ readonly slugRule: "Use 1–64 lowercase letters, numbers, underscores or hyphens, starting with a letter or number.";
14
+ readonly duplicateSlug: "Another goal already uses this short name. Change it under Advanced.";
15
+ readonly saveError: "Couldn't save the goal. Try again.";
16
+ };
@@ -0,0 +1,15 @@
1
+ /** Shared by Payload's field and the Puck sidebar, without either UI dependency. */ export const CLICK_GOAL_COPY = {
2
+ label: 'Click goal',
3
+ choose: 'Choose a click goal',
4
+ newGoal: '+ New click goal',
5
+ title: 'New click goal',
6
+ name: 'Name',
7
+ slug: 'Short name',
8
+ advanced: 'Advanced',
9
+ createGoal: 'Create goal',
10
+ cancel: 'Cancel',
11
+ undefinedKey: 'No click goal has this name yet. Create it with "+ New click goal".',
12
+ slugRule: 'Use 1–64 lowercase letters, numbers, underscores or hyphens, starting with a letter or number.',
13
+ duplicateSlug: 'Another goal already uses this short name. Change it under Advanced.',
14
+ saveError: "Couldn't save the goal. Try again."
15
+ };
@@ -0,0 +1,30 @@
1
+ import { type ClickGoal, type PickerStatus } from './clickGoalPicker.model.js';
2
+ /** UI-free state shared by the two editor hosts. A refused read silently removes management. */
3
+ export declare function useClickGoalPicker(apiRoute: string, { enabled, onSelect }: {
4
+ enabled?: boolean;
5
+ onSelect: (slug: string) => void;
6
+ }): {
7
+ status: PickerStatus;
8
+ goals: ClickGoal[];
9
+ canManage: boolean;
10
+ undefinedKey: (value: string) => boolean;
11
+ panel: {
12
+ open: boolean;
13
+ name: string;
14
+ slug: string;
15
+ slugEdited: boolean;
16
+ advanced: boolean;
17
+ pending: boolean;
18
+ errors: {
19
+ slug?: string;
20
+ general?: string;
21
+ };
22
+ };
23
+ open: () => void;
24
+ cancel: () => void;
25
+ setName: (name: string) => void;
26
+ setSlug: (slug: string) => void;
27
+ setAdvanced: (advanced: boolean) => void;
28
+ create: () => Promise<void>;
29
+ };
30
+ export type ClickGoalPicker = ReturnType<typeof useClickGoalPicker>;