@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
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { createUsePuck, walkTree } from '@puckeditor/core';
4
+ import { useEffect, useMemo, useState } from 'react';
5
+ import { EXPERIMENT_SLOT_TYPE } from '../../types/puck.js';
6
+ import { useArms } from './armsCache.js';
7
+ const usePuckSelector = createUsePuck();
8
+ const slots = [
9
+ 'control',
10
+ 'variantA',
11
+ 'variantB',
12
+ 'variantC'
13
+ ];
14
+ // Puck 0.24.0 dist/chunk-DQXMVPVM.mjs:8040 (item attribute), 8126–8135
15
+ // (direct zone containers). Zones have no stable attribute; avoid the CSS-module hash.
16
+ const zone = 'div[class*="Layer-zones"]';
17
+ const chain = (count)=>Array(count).fill(zone).join(' ~ ');
18
+ function SlotRules({ item: [id, testId, emptySlots] }) {
19
+ const known = useArms(testId ?? undefined);
20
+ const [mounted, setMounted] = useState(false);
21
+ useEffect(()=>{
22
+ setMounted(true);
23
+ }, []);
24
+ // CSS.escape is a browser API: SSR and the first hydration render emit no rules.
25
+ if (!mounted) return null;
26
+ const rules = slots.flatMap((slot, index)=>{
27
+ // Original always remains available. Unknown bound tests must reveal every zone.
28
+ if (slot === 'control' || testId !== null && (!known || known.arms.includes(slot))) return [];
29
+ if (!emptySlots[index]) return [];
30
+ // Exactly the nth zone sibling: the positive chain matches n onward and the
31
+ // negated chain excludes n+1 onward. Unlike nth-child, DropLine siblings do not count.
32
+ const selector = `li[data-puck-layer-tree-id="${CSS.escape(id)}"] > ${chain(index + 1)}:not(${chain(index + 2)})`;
33
+ return [
34
+ `${selector} { display: none; }`
35
+ ];
36
+ }).join('\n');
37
+ return /*#__PURE__*/ _jsx("style", {
38
+ "data-xp-outline": id,
39
+ children: rules
40
+ });
41
+ }
42
+ function ExperimentOutline({ children }) {
43
+ // Public selector API in Puck 0.24, rather than the deprecated usePuck hook.
44
+ const key = usePuckSelector((state)=>{
45
+ const { data } = state.appState;
46
+ const items = new Map();
47
+ const collect = (content)=>{
48
+ for (const item of content)if (item.type === EXPERIMENT_SLOT_TYPE) {
49
+ const testId = item.props.experiment?.id;
50
+ items.set(item.props.id, [
51
+ item.props.id,
52
+ testId === undefined ? null : String(testId),
53
+ slots.map((slot)=>{
54
+ const content = item.props[slot];
55
+ return !Array.isArray(content) || content.length === 0;
56
+ })
57
+ ]);
58
+ }
59
+ };
60
+ walkTree(data, state.config, (content)=>{
61
+ collect(content);
62
+ });
63
+ // walkTree visits slots inside legacy zones, but does not call back on the zone itself.
64
+ for (const content of Object.values(data.zones ?? {}))collect(content);
65
+ return JSON.stringify([
66
+ ...items.values()
67
+ ]);
68
+ });
69
+ // Only binding/emptiness changes rebuild the rules, not edits to other page fields.
70
+ const rules = useMemo(()=>JSON.parse(key).map((item)=>/*#__PURE__*/ _jsx(SlotRules, {
71
+ item: item
72
+ }, item[0])), [
73
+ key
74
+ ]);
75
+ return /*#__PURE__*/ _jsxs(_Fragment, {
76
+ children: [
77
+ children,
78
+ rules
79
+ ]
80
+ });
81
+ }
82
+ /** Add to Puck's plugins so its outline wrapper composes with the host's overrides. */ export function experimentOutlinePlugin() {
83
+ return {
84
+ name: 'experiment-outline',
85
+ overrides: {
86
+ outline: ExperimentOutline
87
+ }
88
+ };
89
+ }
@@ -0,0 +1,10 @@
1
+ import { type ExperimentsPuckIntegration } from '../../types/puck.js';
2
+ import type { PuckRoutes } from '../routes.js';
3
+ /**
4
+ * Call once at module scope in a 'use client' module. A new object per render
5
+ * rebuilds Puck's config and plugin list.
6
+ */
7
+ export declare function experimentsPuckEditor({ routes, accent }?: {
8
+ routes?: PuckRoutes;
9
+ accent?: string;
10
+ }): ExperimentsPuckIntegration;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { EXPERIMENT_SLOT_TYPE } from '../../types/puck.js';
3
+ import { categoryTitle } from '../copy.js';
4
+ import { createExperimentSlotEditor } from './ExperimentSlotEditor.js';
5
+ import { experimentOutlinePlugin } from './experimentOutlinePlugin.js';
6
+ /**
7
+ * Call once at module scope in a 'use client' module. A new object per render
8
+ * rebuilds Puck's config and plugin list.
9
+ */ export function experimentsPuckEditor({ routes, accent } = {}) {
10
+ return {
11
+ name: 'experiments',
12
+ components: {
13
+ [EXPERIMENT_SLOT_TYPE]: createExperimentSlotEditor({
14
+ routes,
15
+ accent
16
+ })
17
+ },
18
+ categories: {
19
+ experiments: {
20
+ title: categoryTitle,
21
+ components: [
22
+ EXPERIMENT_SLOT_TYPE
23
+ ]
24
+ }
25
+ },
26
+ plugins: [
27
+ experimentOutlinePlugin()
28
+ ]
29
+ };
30
+ }
@@ -0,0 +1,7 @@
1
+ import type { CustomField } from '@puckeditor/core';
2
+ import { type PuckRoutes } from '../routes.js';
3
+ import './editor.css';
4
+ /** Editor config only. The render config keeps xpGoalPuckField() from /puck. */
5
+ export declare function xpGoalPuckEditorField(options?: {
6
+ routes?: PuckRoutes;
7
+ }): CustomField<string>;
@@ -0,0 +1,134 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useId } from 'react';
4
+ import { useClickGoalPicker } from '../../goals/picker/useClickGoalPicker.js';
5
+ import { CLICK_GOAL_COPY as C } from '../../goals/picker/copy.js';
6
+ import { TextInput, Select } from '../../ui/Inputs.js';
7
+ import { Button } from '../../ui/Button.js';
8
+ import { resolvePuckRoutes } from '../routes.js';
9
+ import './editor.css';
10
+ function XpGoalPuckEditor({ value, onChange, api, readOnly }) {
11
+ const picker = useClickGoalPicker(api, {
12
+ onSelect: onChange
13
+ });
14
+ const id = useId();
15
+ const stopEnter = (event)=>{
16
+ if (event.key === 'Enter') {
17
+ event.preventDefault();
18
+ event.stopPropagation();
19
+ }
20
+ };
21
+ return /*#__PURE__*/ _jsxs("div", {
22
+ className: "xp-slot-root xp-puck-field",
23
+ children: [
24
+ /*#__PURE__*/ _jsx(TextInput, {
25
+ id: `${id}-value`,
26
+ label: C.label,
27
+ value: value ?? '',
28
+ readOnly: readOnly,
29
+ onChange: (event)=>onChange(event.currentTarget.value)
30
+ }),
31
+ picker.canManage && !readOnly ? /*#__PURE__*/ _jsxs(_Fragment, {
32
+ children: [
33
+ /*#__PURE__*/ _jsxs(Select, {
34
+ id: `${id}-picker`,
35
+ label: C.choose,
36
+ value: picker.goals.some((goal)=>goal.slug === value) ? value : '',
37
+ onChange: (event)=>{
38
+ if (event.currentTarget.value) onChange(event.currentTarget.value);
39
+ },
40
+ children: [
41
+ /*#__PURE__*/ _jsx("option", {
42
+ value: "",
43
+ children: C.choose
44
+ }),
45
+ picker.goals.map((goal)=>/*#__PURE__*/ _jsx("option", {
46
+ value: goal.slug,
47
+ children: goal.name
48
+ }, goal.id))
49
+ ]
50
+ }),
51
+ picker.undefinedKey(value ?? '') ? /*#__PURE__*/ _jsx("p", {
52
+ className: "xp-ui-help",
53
+ role: "status",
54
+ children: C.undefinedKey
55
+ }) : null,
56
+ !picker.panel.open ? /*#__PURE__*/ _jsx(Button, {
57
+ type: "button",
58
+ onClick: picker.open,
59
+ children: C.newGoal
60
+ }) : /*#__PURE__*/ _jsxs("section", {
61
+ "aria-labelledby": `${id}-title`,
62
+ children: [
63
+ /*#__PURE__*/ _jsx("h3", {
64
+ id: `${id}-title`,
65
+ children: C.title
66
+ }),
67
+ /*#__PURE__*/ _jsx(TextInput, {
68
+ id: `${id}-name`,
69
+ label: C.name,
70
+ value: picker.panel.name,
71
+ disabled: picker.panel.pending,
72
+ onKeyDown: stopEnter,
73
+ onChange: (event)=>picker.setName(event.currentTarget.value)
74
+ }),
75
+ /*#__PURE__*/ _jsxs("details", {
76
+ open: picker.panel.advanced,
77
+ onToggle: (event)=>picker.setAdvanced(event.currentTarget.open),
78
+ children: [
79
+ /*#__PURE__*/ _jsx("summary", {
80
+ children: C.advanced
81
+ }),
82
+ /*#__PURE__*/ _jsx(TextInput, {
83
+ id: `${id}-slug`,
84
+ label: C.slug,
85
+ value: picker.panel.slug,
86
+ disabled: picker.panel.pending,
87
+ onKeyDown: stopEnter,
88
+ error: picker.panel.errors.slug,
89
+ onChange: (event)=>picker.setSlug(event.currentTarget.value)
90
+ })
91
+ ]
92
+ }),
93
+ picker.panel.errors.general ? /*#__PURE__*/ _jsx("p", {
94
+ className: "xp-ui-error",
95
+ role: "alert",
96
+ children: picker.panel.errors.general
97
+ }) : null,
98
+ /*#__PURE__*/ _jsxs("div", {
99
+ className: "xp-ui-button-group",
100
+ children: [
101
+ /*#__PURE__*/ _jsx(Button, {
102
+ type: "button",
103
+ disabled: picker.panel.pending || !picker.panel.name.trim(),
104
+ onClick: ()=>void picker.create(),
105
+ children: C.createGoal
106
+ }),
107
+ /*#__PURE__*/ _jsx(Button, {
108
+ type: "button",
109
+ disabled: picker.panel.pending,
110
+ onClick: picker.cancel,
111
+ children: C.cancel
112
+ })
113
+ ]
114
+ })
115
+ ]
116
+ })
117
+ ]
118
+ }) : null
119
+ ]
120
+ });
121
+ }
122
+ /** Editor config only. The render config keeps xpGoalPuckField() from /puck. */ export function xpGoalPuckEditorField(options) {
123
+ const routes = resolvePuckRoutes(options?.routes);
124
+ return {
125
+ type: 'custom',
126
+ label: C.label,
127
+ render: ({ value, onChange, readOnly })=>/*#__PURE__*/ _jsx(XpGoalPuckEditor, {
128
+ value: value,
129
+ onChange: onChange,
130
+ api: routes.api,
131
+ readOnly: readOnly
132
+ })
133
+ };
134
+ }
@@ -1,5 +1,7 @@
1
1
  import type { CustomField } from '@puckeditor/core';
2
2
  import type { ExperimentRef } from '../types/puck.js';
3
+ import { type PuckRoutes } from './routes.js';
3
4
  export declare function createExperimentSlotField(opts?: {
4
5
  label?: string;
6
+ routes?: PuckRoutes;
5
7
  }): CustomField<ExperimentRef | null>;
@@ -1,13 +1,21 @@
1
1
  import { createElement } from 'react';
2
2
  import { ExperimentPicker } from './ExperimentPicker.js';
3
+ import { resolvePuckRoutes } from './routes.js';
4
+ import { PuckHostProvider } from './hostContext.js';
3
5
  import { pickerCopy } from './copy.js';
4
6
  export function createExperimentSlotField(opts) {
7
+ const routes = resolvePuckRoutes(opts?.routes);
5
8
  return {
6
9
  type: 'custom',
7
10
  label: opts?.label ?? pickerCopy.label,
8
- render: ({ value, onChange })=>createElement(ExperimentPicker, {
9
- value,
10
- onChange
11
+ render: ({ value, onChange })=>createElement(PuckHostProvider, {
12
+ routes,
13
+ children: createElement('div', {
14
+ className: 'xp-puck-field'
15
+ }, createElement(ExperimentPicker, {
16
+ value,
17
+ onChange
18
+ }))
11
19
  })
12
20
  };
13
21
  }
@@ -0,0 +1,10 @@
1
+ import { resolvePuckRoutes } from './routes.js';
2
+ import type { ReactNode } from 'react';
3
+ export declare const usePuckRoutes: () => {
4
+ admin: string;
5
+ api: string;
6
+ };
7
+ export declare function PuckHostProvider({ routes, children }: {
8
+ routes: ReturnType<typeof resolvePuckRoutes>;
9
+ children: ReactNode;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from 'react';
4
+ import { resolvePuckRoutes } from './routes.js';
5
+ const PuckHostContext = /*#__PURE__*/ createContext(resolvePuckRoutes());
6
+ export const usePuckRoutes = ()=>useContext(PuckHostContext);
7
+ export function PuckHostProvider({ routes, children }) {
8
+ return /*#__PURE__*/ _jsx(PuckHostContext.Provider, {
9
+ value: routes,
10
+ children: children
11
+ });
12
+ }
@@ -0,0 +1,6 @@
1
+ import { type ExperimentsPuckIntegration } from '../types/puck.js';
2
+ import type { PuckRoutes } from './routes.js';
3
+ /** Bundle the test block and its sidebar category for a host render config. */
4
+ export declare function experimentsPuck({ routes }?: {
5
+ routes?: PuckRoutes;
6
+ }): ExperimentsPuckIntegration;
@@ -0,0 +1,21 @@
1
+ import { EXPERIMENT_SLOT_TYPE } from '../types/puck.js';
2
+ import { createExperimentSlot } from './ExperimentSlot.js';
3
+ import { categoryTitle } from './copy.js';
4
+ /** Bundle the test block and its sidebar category for a host render config. */ export function experimentsPuck({ routes } = {}) {
5
+ return {
6
+ name: 'experiments',
7
+ components: {
8
+ [EXPERIMENT_SLOT_TYPE]: createExperimentSlot({
9
+ routes
10
+ })
11
+ },
12
+ categories: {
13
+ experiments: {
14
+ title: categoryTitle,
15
+ components: [
16
+ EXPERIMENT_SLOT_TYPE
17
+ ]
18
+ }
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,12 @@
1
+ export interface PuckRoutes {
2
+ admin?: string;
3
+ api?: string;
4
+ }
5
+ export declare const DEFAULT_PUCK_ROUTES: {
6
+ readonly admin: "/admin";
7
+ readonly api: "/api";
8
+ };
9
+ export declare function resolvePuckRoutes(routes?: PuckRoutes): {
10
+ admin: string;
11
+ api: string;
12
+ };
@@ -0,0 +1,11 @@
1
+ // Payload's documented route defaults; hosts pass config.routes when customized.
2
+ export const DEFAULT_PUCK_ROUTES = {
3
+ admin: '/admin',
4
+ api: '/api'
5
+ };
6
+ export function resolvePuckRoutes(routes) {
7
+ return {
8
+ admin: (routes?.admin ?? DEFAULT_PUCK_ROUTES.admin).replace(/\/$/, ''),
9
+ api: (routes?.api ?? DEFAULT_PUCK_ROUTES.api).replace(/\/$/, '')
10
+ };
11
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ComponentConfig } from '@puckeditor/core';
2
2
  import type { ExperimentSlotProps } from '../types/puck.js';
3
- export declare const experimentSlotLabel: "Experiment";
3
+ export declare const experimentSlotLabel = "Experiment block";
4
4
  export declare const experimentSlotFields: NonNullable<ComponentConfig<ExperimentSlotProps>['fields']>;
5
5
  export declare const experimentSlotDefaultProps: NonNullable<ComponentConfig<ExperimentSlotProps>['defaultProps']>;
@@ -1,6 +1,6 @@
1
1
  import { createExperimentSlotField } from './experimentSlotField.js';
2
- import { pickerCopy, slotLabels } from './copy.js';
3
- export const experimentSlotLabel = pickerCopy.label;
2
+ import { slotLabels } from './copy.js';
3
+ export const experimentSlotLabel = 'Experiment block';
4
4
  export const experimentSlotFields = {
5
5
  experiment: createExperimentSlotField(),
6
6
  // Puck 0.24's defaultSlots prepends each slot, reversing declaration order in the outline.
@@ -0,0 +1,3 @@
1
+ import type { TextField } from '@puckeditor/core';
2
+ /** Prop config only. Components render xpGoal as data-xp-goal. */
3
+ export declare function xpGoalPuckField(): TextField;
@@ -0,0 +1,6 @@
1
+ /** Prop config only. Components render xpGoal as data-xp-goal. */ export function xpGoalPuckField() {
2
+ return {
3
+ type: 'text',
4
+ label: 'Click goal'
5
+ };
6
+ }
@@ -0,0 +1,4 @@
1
+ import type { Snapshot, SnapshotHealth } from '../types/snapshot.js';
2
+ /** Refresh page health while retaining health produced by other checks. */
3
+ export declare function mergePageHealth(previous: readonly SnapshotHealth[], pages: readonly SnapshotHealth[]): SnapshotHealth[];
4
+ export declare function canonicalHealth(snapshot: Pick<Snapshot, 'health' | 'goalFairnessNotice'>, ignoreUncheckedPlacements?: boolean): string;
@@ -0,0 +1,27 @@
1
+ const healthKey = (row)=>JSON.stringify(row.code === 'placement-unchecked' ? [
2
+ row.code,
3
+ row.detail.collection ?? '',
4
+ row.detail.id ?? '',
5
+ row.detail.path ?? ''
6
+ ] : [
7
+ row.code,
8
+ 'path' in row ? row.path : '',
9
+ 'metricId' in row ? row.metricId : ''
10
+ ]);
11
+ /** Refresh page health while retaining health produced by other checks. */ export function mergePageHealth(previous, pages) {
12
+ return [
13
+ ...new Map([
14
+ ...previous.filter((row)=>row.code === 'goal-in-some-versions' || row.code === 'placement-unchecked'),
15
+ ...pages
16
+ ].map((row)=>[
17
+ healthKey(row),
18
+ row
19
+ ])).values()
20
+ ];
21
+ }
22
+ export function canonicalHealth(snapshot, ignoreUncheckedPlacements = false) {
23
+ return JSON.stringify([
24
+ snapshot.goalFairnessNotice ?? '',
25
+ ...(snapshot.health ?? []).filter((row)=>!ignoreUncheckedPlacements || row.code !== 'placement-unchecked').map(healthKey).sort()
26
+ ]);
27
+ }
@@ -7,6 +7,7 @@ const count = (value)=>number(value) && Number.isInteger(value) && value >= 0;
7
7
  const string = (value)=>typeof value === 'string';
8
8
  const boolean = (value)=>typeof value === 'boolean';
9
9
  const nullable = (check)=>(value)=>value === null || check(value);
10
+ const union = (...checks)=>(value)=>checks.some((check)=>check(value));
10
11
  const list = (check)=>(value)=>Array.isArray(value) && value.every(check);
11
12
  const oneOf = (...values)=>(value)=>values.includes(value);
12
13
  const isObject = (value)=>value !== null && typeof value === 'object' && !Array.isArray(value);
@@ -140,10 +141,22 @@ const shape = object({
140
141
  by: nullable(id)
141
142
  }))
142
143
  }, {
143
- health: list(object({
144
+ goalFairnessNotice: string,
145
+ health: list(union(object({
144
146
  code: oneOf('version-page-unavailable', 'version-page-unchecked'),
145
147
  path: string
146
- }))
148
+ }), object({
149
+ code: oneOf('goal-in-some-versions'),
150
+ metricId: id,
151
+ name: string
152
+ }), object({
153
+ code: oneOf('placement-unchecked'),
154
+ detail: object({}, {
155
+ collection: string,
156
+ id,
157
+ path: string
158
+ })
159
+ })))
147
160
  });
148
161
  /** Validate the frozen JSON shape and headline/rollout invariants before storage.
149
162
  * This also accepts the shared CORE-13/admin fixtures without any I/O.
@@ -8,6 +8,7 @@ const arm = (value)=>typeof value === 'string' && [
8
8
  'variantC',
9
9
  'holdout'
10
10
  ].includes(value);
11
+ const path = (value)=>typeof value === 'string' && value.length <= 128 && value.startsWith('/') && !value.includes('//') && !/[^A-Za-z0-9_/-]/.test(value);
11
12
  const failure = (value)=>value === 'placements-failed' || value === 'forbidden' || value === 'not-found';
12
13
  /** Summary details contain identifiers only. Project known shapes rather than
13
14
  * recursively copying host objects: titles, messages and placement detail are text.
@@ -15,15 +16,64 @@ const failure = (value)=>value === 'placements-failed' || value === 'forbidden'
15
16
  * counts and layer capacity, which are not identifiers).
16
17
  */ export function sanitizeReadinessDetail(code, detail) {
17
18
  switch(code){
19
+ case 'goal-in-some-versions':
20
+ {
21
+ const rows = Array.isArray(detail) ? detail : [
22
+ detail
23
+ ];
24
+ return rows.filter(object).filter((row)=>key(row.metricId) || typeof row.metricId === 'number' && Number.isSafeInteger(row.metricId)).map((row)=>({
25
+ metricId: row.metricId
26
+ }));
27
+ }
28
+ case 'placement-unchecked':
29
+ return object(detail) ? Object.fromEntries(Object.entries(detail).filter(([k, v])=>k === 'path' ? path(v) : [
30
+ 'collection',
31
+ 'id'
32
+ ].includes(k) && (key(v) || typeof v === 'number' && Number.isSafeInteger(v)))) : undefined;
33
+ case 'placement-unpublished':
34
+ case 'duplicate-version-row':
35
+ case 'interacting-tests':
36
+ return Array.isArray(detail) ? detail.filter(object).map((row)=>Object.fromEntries(Object.entries(row).filter(([k, v])=>[
37
+ 'collection',
38
+ 'id',
39
+ 'experimentId',
40
+ 'slug'
41
+ ].includes(k) ? key(v) || typeof v === 'number' && Number.isSafeInteger(v) : k === 'path' ? path(v) : k === 'status' ? [
42
+ 'running',
43
+ 'decided'
44
+ ].includes(String(v)) : k === 'version' && typeof v === 'boolean'))) : undefined;
45
+ case 'version-same-address':
46
+ return object(detail) && arm(detail.arm) && arm(detail.otherArm) && path(detail.path) ? {
47
+ arm: detail.arm,
48
+ otherArm: detail.otherArm,
49
+ path: detail.path
50
+ } : undefined;
51
+ case 'version-identical':
52
+ return object(detail) && arm(detail.arm) && key(detail.collection) && (key(detail.id) || typeof detail.id === 'number' && Number.isSafeInteger(detail.id)) ? {
53
+ arm: detail.arm,
54
+ collection: detail.collection,
55
+ id: detail.id,
56
+ ...path(detail.path) ? {
57
+ path: detail.path
58
+ } : {}
59
+ } : undefined;
18
60
  case 'block-path-rule':
19
61
  return object(detail) && Array.isArray(detail.rules) && detail.rules.length && detail.rules.every((index)=>Number.isSafeInteger(index) && index >= 0) ? {
20
62
  rules: detail.rules
21
63
  } : undefined;
22
64
  case 'no-placements':
23
65
  return failure(detail) ? detail : undefined;
66
+ case 'page-in-use':
67
+ {
68
+ if (!object(detail) || !Array.isArray(detail.paths)) return undefined;
69
+ const paths = detail.paths.filter(path);
70
+ return paths.length ? {
71
+ paths
72
+ } : undefined;
73
+ }
24
74
  case 'page-target-unpublished':
25
75
  // Paths only: no query, fragment, whitespace, URL authority or encoded text.
26
- return typeof detail === 'string' && detail.length <= 128 && detail.startsWith('/') && !detail.includes('//') && !/[^A-Za-z0-9_/-]/.test(detail) ? detail : undefined;
76
+ return path(detail) ? detail : undefined;
27
77
  case 'flag-params-invalid':
28
78
  {
29
79
  if (!Array.isArray(detail) || !detail.length || !detail.every((item)=>object(item) && arm(item.arm) && key(item.field))) return undefined;
@@ -13,14 +13,21 @@ import { serverManifest } from './context.js';
13
13
  const live = manifest?.experiments.find((e)=>e.kind === 'page' && e.arms.some((a)=>a.pageTarget !== undefined && normalisePath(a.pageTarget) === normalisePath(path)));
14
14
  if (live) {
15
15
  const controlPath = live.arms.find((a)=>a.kind === 'control')?.pageTarget;
16
- return controlPath && normalisePath(path) !== normalisePath(controlPath) ? {
16
+ if (!controlPath) return {};
17
+ // The Original's URL may be served with a variant's route: its canonical must stay on the Original,
18
+ // never the (noindexed) variant route the host would otherwise derive.
19
+ return normalisePath(path) !== normalisePath(controlPath) ? {
17
20
  robots: {
18
21
  index: false
19
22
  },
20
23
  alternates: {
21
24
  canonical: controlPath
22
25
  }
23
- } : {};
26
+ } : {
27
+ alternates: {
28
+ canonical: controlPath
29
+ }
30
+ };
24
31
  }
25
32
  const archivedPath = manifest ? findNormalisedPathKey(manifest.archivedPages, path) : undefined;
26
33
  const archived = archivedPath === undefined ? undefined : manifest?.archivedPages[archivedPath];
@@ -0,0 +1,7 @@
1
+ import type { Block, Field } from 'payload';
2
+ /** Undo depth expansion only where the collection schema declares relationships.
3
+ * Hidden fields and field-level afterRead hooks are unaffected by depth, so
4
+ * relationship expansion is the only depth-dependent transformation to undo.
5
+ * Other objects (especially JSON content with its own ids) must stay intact.
6
+ */
7
+ export declare function normaliseContentRelationships(data: unknown, fields: Field[], blocks?: Block[], locales?: readonly string[]): unknown;