@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,209 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { useRouter } from 'next/navigation.js';
5
+ import { formatAdminURL } from 'payload/shared';
6
+ import { useTestDocument } from '../useTestDocument.js';
7
+ import { CopyRoute } from '../pagePicker/CopyRoute.js';
8
+ import { PagePicker } from '../pagePicker/PagePicker.js';
9
+ import { testPageCollection, controlFirst } from '../pagePicker/pagePicker.model.js';
10
+ import { NEW_TEST_COPY as C } from '../../copy/newTest.js';
11
+ import { READINESS_COPY } from '../../copy/readiness.js';
12
+ import { TEST_PAGE } from '../../copy/testPage.js';
13
+ import { SURFACE_COPY } from '../../copy/surface.js';
14
+ import { pageStepCopy } from '../../copy/pages.js';
15
+ import { READINESS, nextStep, pageReadinessContext, readinessArm } from '../readiness/readiness.model.js';
16
+ import { SaveStatus } from '../readiness/SaveStatus.js';
17
+ import { createApi } from '../../api.js';
18
+ import { useTransition } from '../readiness/useTransition.js';
19
+ /** One session stays mounted across Collection, all page steps and Review. */ export function PageWizard({ id, surface }) {
20
+ const router = useRouter();
21
+ const collections = surface.pageCollections ?? [];
22
+ const [step, setStep] = useState(collections.length > 1 ? 'collection' : 'control');
23
+ const test = useTestDocument(id, {
24
+ apiRoute: surface.apiRoute,
25
+ refetchPreflightOnFocus: step === 'review'
26
+ });
27
+ const testUrl = formatAdminURL({
28
+ adminRoute: surface.adminRoute,
29
+ path: `/experiments/${encodeURIComponent(String(id))}`
30
+ });
31
+ const transition = useTransition(test, ()=>router.push(testUrl));
32
+ useEffect(()=>{
33
+ if (step === 'review') void test.refreshPreflight();
34
+ }, [
35
+ step,
36
+ test.refreshPreflight
37
+ ]);
38
+ const leave = ()=>router.push(testUrl);
39
+ if (test.loadState === 'loading') return /*#__PURE__*/ _jsx("p", {
40
+ role: "status",
41
+ children: TEST_PAGE.loading
42
+ });
43
+ if (!test.doc || test.loadState !== 'ready') return /*#__PURE__*/ _jsxs("div", {
44
+ children: [
45
+ /*#__PURE__*/ _jsx("p", {
46
+ role: "alert",
47
+ children: test.loadState === 'forbidden' ? TEST_PAGE.forbidden : test.loadState === 'missing' ? TEST_PAGE.missing : TEST_PAGE.error
48
+ }),
49
+ /*#__PURE__*/ _jsx("button", {
50
+ type: "button",
51
+ onClick: ()=>void test.reload(),
52
+ children: TEST_PAGE.retry
53
+ }),
54
+ /*#__PURE__*/ _jsx("button", {
55
+ type: "button",
56
+ onClick: leave,
57
+ children: C.backToTest
58
+ })
59
+ ]
60
+ });
61
+ const doc = test.doc;
62
+ const collection = testPageCollection(collections, doc, test.preflight?.pagePlacements);
63
+ const arms = controlFirst(doc.arms).filter((arm)=>arm.kind !== 'holdout');
64
+ const steps = [
65
+ ...collections.length > 1 ? [
66
+ 'collection'
67
+ ] : [],
68
+ ...arms.map((arm)=>arm.key),
69
+ 'review'
70
+ ];
71
+ const arm = arms.find((item)=>item.key === step);
72
+ const ctx = pageReadinessContext(doc, test.preflight, surface);
73
+ const next = nextStep(test.preflight, ctx);
74
+ const ready = Boolean(test.preflight && !test.preflightPending && !test.preflightError && test.preflight.checks.every((check)=>check.ok));
75
+ const busy = test.hasUnsaved || test.saving === 'saving' || transition.busy;
76
+ const jump = (section, key)=>{
77
+ if (section === 'versions') setStep(key ?? arms.find((item)=>!item.pageTarget)?.key ?? 'control');
78
+ else router.push(`${testUrl}#section-${section}`);
79
+ };
80
+ const picker = arm && collection ? /*#__PURE__*/ _jsx(PagePicker, {
81
+ collection: collection,
82
+ test: test,
83
+ armKey: arm.key,
84
+ apiRoute: surface.apiRoute
85
+ }, `${collection.slug}/${arm.key}`) : null;
86
+ return /*#__PURE__*/ _jsxs("div", {
87
+ className: "xp-new-test xp-page-wizard",
88
+ children: [
89
+ /*#__PURE__*/ _jsx("h2", {
90
+ children: step === 'collection' ? C.collection : step === 'review' ? C.review : arm?.kind === 'control' ? C.original : pageStepCopy(arm?.label ?? '')
91
+ }),
92
+ /*#__PURE__*/ _jsx(SaveStatus, {
93
+ test: test
94
+ }),
95
+ test.errors._section ? /*#__PURE__*/ _jsx("p", {
96
+ role: "alert",
97
+ children: test.errors._section
98
+ }) : null,
99
+ step === 'collection' ? /*#__PURE__*/ _jsxs("fieldset", {
100
+ children: [
101
+ /*#__PURE__*/ _jsx("legend", {
102
+ children: C.collection
103
+ }),
104
+ collections.map((entry)=>/*#__PURE__*/ _jsxs("label", {
105
+ children: [
106
+ /*#__PURE__*/ _jsx("input", {
107
+ type: "radio",
108
+ name: "page-collection",
109
+ checked: doc.pageCollection === entry.slug,
110
+ disabled: busy,
111
+ onChange: ()=>{
112
+ test.stage({
113
+ pageCollection: entry.slug
114
+ });
115
+ void test.commit();
116
+ }
117
+ }),
118
+ entry.label
119
+ ]
120
+ }, entry.slug))
121
+ ]
122
+ }) : step === 'review' ? /*#__PURE__*/ _jsxs(_Fragment, {
123
+ children: [
124
+ test.preflightError ? /*#__PURE__*/ _jsx("p", {
125
+ role: "alert",
126
+ children: test.preflightError
127
+ }) : null,
128
+ /*#__PURE__*/ _jsx("ul", {
129
+ children: test.preflight?.checks.map((check, index)=>{
130
+ if (!Object.hasOwn(READINESS, check.code)) return check.ok ? null : /*#__PURE__*/ _jsx("li", {
131
+ children: SURFACE_COPY.next
132
+ }, `${check.code}/${index}`);
133
+ const row = READINESS[check.code];
134
+ const sentence = row.next(check.detail, ctx);
135
+ const label = typeof row.nextStep === 'function' ? row.nextStep(check.detail, ctx) : row.nextStep;
136
+ return sentence && !check.ok ? /*#__PURE__*/ _jsxs("li", {
137
+ children: [
138
+ sentence,
139
+ label ? /*#__PURE__*/ _jsx("button", {
140
+ type: "button",
141
+ onClick: ()=>jump(row.section, readinessArm(check.code, check.detail, ctx)),
142
+ children: label
143
+ }) : null
144
+ ]
145
+ }, `${check.code}/${index}`) : null;
146
+ })
147
+ }),
148
+ ready ? /*#__PURE__*/ _jsx("p", {
149
+ children: READINESS_COPY.ready
150
+ }) : null,
151
+ /*#__PURE__*/ _jsx("button", {
152
+ type: "button",
153
+ disabled: busy || test.preflightPending || !test.preflight,
154
+ onClick: ()=>{
155
+ if (ready) void transition.run(()=>createApi(surface.apiRoute).start(id));
156
+ else if (next) jump(next.section, next.arm);
157
+ },
158
+ children: ready ? C.start : C.finishSetup
159
+ }),
160
+ transition.error ? /*#__PURE__*/ _jsx("p", {
161
+ role: "alert",
162
+ children: transition.error
163
+ }) : null
164
+ ]
165
+ }) : arm?.kind === 'control' ? /*#__PURE__*/ _jsxs(_Fragment, {
166
+ children: [
167
+ /*#__PURE__*/ _jsx("p", {
168
+ children: C.originalHelp
169
+ }),
170
+ picker
171
+ ]
172
+ }) : collection ? /*#__PURE__*/ _jsx(CopyRoute, {
173
+ collection: collection,
174
+ children: picker
175
+ }) : null,
176
+ /*#__PURE__*/ _jsxs("div", {
177
+ className: "xp-new-test-actions",
178
+ children: [
179
+ /*#__PURE__*/ _jsx("button", {
180
+ type: "button",
181
+ disabled: busy,
182
+ onClick: ()=>{
183
+ const previous = steps[steps.indexOf(step) - 1];
184
+ if (previous) setStep(previous);
185
+ else leave();
186
+ },
187
+ children: C.back
188
+ }),
189
+ /*#__PURE__*/ _jsx("button", {
190
+ type: "button",
191
+ disabled: busy,
192
+ onClick: leave,
193
+ children: C.backToTest
194
+ }),
195
+ step !== 'review' ? /*#__PURE__*/ _jsx("button", {
196
+ type: "button",
197
+ disabled: busy || step === 'collection' && !collection,
198
+ onClick: ()=>setStep(steps[steps.indexOf(step) + 1] ?? 'review'),
199
+ children: C.next
200
+ }) : null
201
+ ]
202
+ }),
203
+ test.errors.pageCollection ? /*#__PURE__*/ _jsx("p", {
204
+ role: "alert",
205
+ children: test.errors.pageCollection
206
+ }) : null
207
+ ]
208
+ });
209
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { PageCollection } from './pagePicker.model.js';
3
+ export declare function CopyRoute({ collection, children }: {
4
+ collection: PageCollection;
5
+ children: ReactNode;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { PAGE_COPY as C, openCollectionCopy, openOriginalCopy } from '../../copy/pages.js';
4
+ export function CopyRoute({ collection, children }) {
5
+ const link = /*#__PURE__*/ _jsx("a", {
6
+ href: collection.copyUrl,
7
+ target: "_blank",
8
+ rel: "noopener",
9
+ children: collection.copyLabel ?? openCollectionCopy(collection.label)
10
+ });
11
+ return /*#__PURE__*/ _jsxs("div", {
12
+ className: "xp-copy-route",
13
+ children: [
14
+ /*#__PURE__*/ _jsx("div", {
15
+ className: "xp-copy-route-action"
16
+ }),
17
+ collection.copyHint ? /*#__PURE__*/ _jsxs(_Fragment, {
18
+ children: [
19
+ /*#__PURE__*/ _jsxs("p", {
20
+ children: [
21
+ collection.copyHint,
22
+ ' ',
23
+ link
24
+ ]
25
+ }),
26
+ /*#__PURE__*/ _jsx("p", {
27
+ children: C.ownAddress
28
+ })
29
+ ]
30
+ }) : /*#__PURE__*/ _jsxs("ol", {
31
+ children: [
32
+ /*#__PURE__*/ _jsxs("li", {
33
+ children: [
34
+ openOriginalCopy(collection.label),
35
+ ' ',
36
+ link
37
+ ]
38
+ }),
39
+ /*#__PURE__*/ _jsx("li", {
40
+ children: C.duplicateStep
41
+ }),
42
+ /*#__PURE__*/ _jsx("li", {
43
+ children: C.ownAddress
44
+ })
45
+ ]
46
+ }),
47
+ /*#__PURE__*/ _jsx("p", {
48
+ children: C.thenChoose
49
+ }),
50
+ children
51
+ ]
52
+ });
53
+ }
@@ -0,0 +1,10 @@
1
+ import type { TestDocApi } from '../contracts.js';
2
+ import type { PageCollection } from './pagePicker.model.js';
3
+ /** Mount on open; every reopen and return from another tab refreshes the current query. */
4
+ export declare function PagePicker({ collection, test, armKey, apiRoute, onChoose }: {
5
+ collection: PageCollection;
6
+ test: TestDocApi;
7
+ armKey: string;
8
+ apiRoute: string;
9
+ onChoose?: () => void;
10
+ }): import("react").JSX.Element;
@@ -0,0 +1,172 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useId, useMemo, useRef, useState } from 'react';
4
+ import { createApi } from '../../api.js';
5
+ import { PAGE_COPY as C } from '../../copy/pages.js';
6
+ import { reasonFor } from '../test/fields.js';
7
+ import { disabledPageReason, isLatestPageRequest } from './pagePicker.model.js';
8
+ /** Mount on open; every reopen and return from another tab refreshes the current query. */ export function PagePicker({ collection, test, armKey, apiRoute, onChoose }) {
9
+ const api = useMemo(()=>createApi(apiRoute), [
10
+ apiRoute
11
+ ]);
12
+ const id = useId();
13
+ const [search, setSearch] = useState('');
14
+ const [q, setQuery] = useState('');
15
+ const [page, setPage] = useState(1);
16
+ const [refresh, setRefresh] = useState(0);
17
+ const [rows, setRows] = useState([]);
18
+ const [more, setMore] = useState(false);
19
+ const [loading, setLoading] = useState(true);
20
+ const [error, setError] = useState('');
21
+ const revision = useRef(0);
22
+ const reload = ()=>{
23
+ setPage(1);
24
+ setRefresh((value)=>value + 1);
25
+ };
26
+ useEffect(()=>{
27
+ if (search === q) return;
28
+ const timer = setTimeout(()=>{
29
+ setQuery(search);
30
+ setPage(1);
31
+ setRows([]);
32
+ setMore(false);
33
+ setLoading(true);
34
+ setError('');
35
+ }, 250);
36
+ return ()=>clearTimeout(timer);
37
+ }, [
38
+ search,
39
+ q
40
+ ]);
41
+ useEffect(()=>{
42
+ let lastReturn = -Infinity;
43
+ const focus = ()=>{
44
+ if (document.visibilityState !== 'visible' || Date.now() - lastReturn < 100) return;
45
+ lastReturn = Date.now();
46
+ setPage(1);
47
+ setRefresh((value)=>value + 1);
48
+ };
49
+ const visible = ()=>{
50
+ if (document.visibilityState === 'visible') focus();
51
+ };
52
+ window.addEventListener('focus', focus);
53
+ document.addEventListener('visibilitychange', visible);
54
+ return ()=>{
55
+ window.removeEventListener('focus', focus);
56
+ document.removeEventListener('visibilitychange', visible);
57
+ };
58
+ }, []);
59
+ useEffect(()=>{
60
+ const controller = new AbortController();
61
+ const request = ++revision.current;
62
+ setLoading(true);
63
+ setError('');
64
+ void api.pages({
65
+ collection: collection.slug,
66
+ q,
67
+ page
68
+ }, controller.signal).then((result)=>{
69
+ if (controller.signal.aborted || !isLatestPageRequest(request, revision.current)) return;
70
+ if (result.ok && result.data) {
71
+ const docs = result.data.docs;
72
+ setRows((current)=>{
73
+ const combined = page === 1 ? docs : [
74
+ ...current,
75
+ ...docs
76
+ ];
77
+ return combined.filter((row, index)=>combined.findIndex((item)=>item.id === row.id) === index);
78
+ });
79
+ setMore(result.data.hasNextPage);
80
+ } else {
81
+ setMore(false);
82
+ setError(C.error);
83
+ }
84
+ setLoading(false);
85
+ });
86
+ return ()=>controller.abort();
87
+ }, [
88
+ api,
89
+ collection.slug,
90
+ q,
91
+ page,
92
+ refresh
93
+ ]);
94
+ const reason = reasonFor(test, 'arms');
95
+ return /*#__PURE__*/ _jsxs("div", {
96
+ className: "xp-page-picker",
97
+ children: [
98
+ /*#__PURE__*/ _jsx("label", {
99
+ htmlFor: id,
100
+ children: C.search
101
+ }),
102
+ /*#__PURE__*/ _jsx("input", {
103
+ id: id,
104
+ type: "search",
105
+ value: search,
106
+ onChange: (event)=>setSearch(event.currentTarget.value)
107
+ }),
108
+ loading && !rows.length ? /*#__PURE__*/ _jsx("p", {
109
+ role: "status",
110
+ children: C.loading
111
+ }) : null,
112
+ error ? /*#__PURE__*/ _jsxs("p", {
113
+ role: "alert",
114
+ children: [
115
+ error,
116
+ " ",
117
+ /*#__PURE__*/ _jsx("button", {
118
+ type: "button",
119
+ onClick: reload,
120
+ children: C.retry
121
+ })
122
+ ]
123
+ }) : null,
124
+ !rows.length ? !loading && !error ? /*#__PURE__*/ _jsx("p", {
125
+ children: C.empty
126
+ }) : null : /*#__PURE__*/ _jsx("ul", {
127
+ children: rows.map((row)=>{
128
+ const disabled = reason ?? disabledPageReason(row, armKey, test.doc?.arms ?? [], test.preflight?.pagePlacements, test.chosenPages, collection.label, collection.slug);
129
+ return /*#__PURE__*/ _jsxs("li", {
130
+ children: [
131
+ /*#__PURE__*/ _jsxs("button", {
132
+ type: "button",
133
+ disabled: !!disabled || test.saving === 'saving',
134
+ onClick: ()=>{
135
+ const arm = test.doc?.arms.find((item)=>item.key === armKey);
136
+ if (!arm || row.path === null || disabled) return;
137
+ void test.choosePage(arm.key, {
138
+ id: row.id,
139
+ path: row.path
140
+ });
141
+ onChoose?.();
142
+ },
143
+ children: [
144
+ /*#__PURE__*/ _jsx("strong", {
145
+ children: row.title
146
+ }),
147
+ ' ',
148
+ row.path,
149
+ !row.published ? /*#__PURE__*/ _jsxs(_Fragment, {
150
+ children: [
151
+ " · ",
152
+ C.draft
153
+ ]
154
+ }) : null
155
+ ]
156
+ }),
157
+ disabled ? /*#__PURE__*/ _jsx("p", {
158
+ children: disabled
159
+ }) : null
160
+ ]
161
+ }, row.id);
162
+ })
163
+ }),
164
+ more ? /*#__PURE__*/ _jsx("button", {
165
+ type: "button",
166
+ disabled: loading,
167
+ onClick: ()=>setPage((value)=>value + 1),
168
+ children: C.more
169
+ }) : null
170
+ ]
171
+ });
172
+ }
@@ -0,0 +1,25 @@
1
+ import type { Arm, DocId } from '../../../types/contracts.js';
2
+ import type { PagesResult, PreflightResult } from '../../../types/endpoints.js';
3
+ import type { TestsSurfaceProps } from '../../../types/surface.js';
4
+ export type PageRow = PagesResult['docs'][number];
5
+ export type PageCollection = NonNullable<TestsSurfaceProps['pageCollections']>[number];
6
+ export type PageSelections = Partial<Record<Arm['key'], {
7
+ id: DocId;
8
+ path: string;
9
+ }>>;
10
+ /** The server resolves legacy collections; the client consumes its placements verbatim. */
11
+ export declare function testPageCollection(collections: readonly PageCollection[] | undefined, doc: {
12
+ pageCollection?: string | null;
13
+ }, placements: PreflightResult['pagePlacements']): {
14
+ slug: string;
15
+ label: string;
16
+ contentChecks: boolean;
17
+ copyHint?: string;
18
+ copyUrl?: string;
19
+ copyLabel?: string;
20
+ } | undefined;
21
+ export declare const controlFirst: (arms: readonly Arm[]) => Arm[];
22
+ /** Identity wins over address, so distinct documents sharing an address get actionable copy. */
23
+ export declare function disabledPageReason(row: PageRow, ownKey: string, arms: readonly Arm[], placements: PreflightResult['pagePlacements'], chosen: PageSelections, collection: string, collectionSlug?: string): string | null;
24
+ /** Pure request revision comparison, also used after an aborted transport resolves. */
25
+ export declare const isLatestPageRequest: (response: number, latest: number) => boolean;
@@ -0,0 +1,25 @@
1
+ import { normalisePath } from '../../../runtime/normalisePath.js';
2
+ import { PAGE_COPY, usedAddressCopy, usedPageCopy } from '../../copy/pages.js';
3
+ import { actionGroupLabel } from '../../actions/actions.model.js';
4
+ /** The server resolves legacy collections; the client consumes its placements verbatim. */ export function testPageCollection(collections, doc, placements) {
5
+ const slug = doc.pageCollection || placements?.find((entry)=>entry.collection)?.collection;
6
+ return collections?.find((entry)=>entry.slug === slug);
7
+ }
8
+ export const controlFirst = (arms)=>[
9
+ ...arms
10
+ ].sort((a, b)=>Number(b.kind === 'control') - Number(a.kind === 'control'));
11
+ /** Identity wins over address, so distinct documents sharing an address get actionable copy. */ export function disabledPageReason(row, ownKey, arms, placements, chosen, collection, collectionSlug) {
12
+ if (row.path === null || row.pathKey === null) return PAGE_COPY.noPath;
13
+ const others = controlFirst(arms).filter((arm)=>arm.key !== ownKey && arm.kind !== 'holdout');
14
+ for (const arm of others){
15
+ const selection = chosen[arm.key];
16
+ const remembered = selection && arm.pageTarget && normalisePath(selection.path) === normalisePath(arm.pageTarget) ? selection.id : undefined;
17
+ const id = remembered ?? placements?.find((entry)=>entry.arm === arm.key && (!collectionSlug || entry.collection === collectionSlug))?.id;
18
+ if (id != null ? String(id) === String(row.id) : Boolean(arm.pageTarget && normalisePath(arm.pageTarget) === row.pathKey)) {
19
+ return usedPageCopy(actionGroupLabel(arm.key, arm.label));
20
+ }
21
+ }
22
+ const same = others.find((arm)=>arm.pageTarget && normalisePath(arm.pageTarget) === row.pathKey);
23
+ return same ? usedAddressCopy(actionGroupLabel(same.key, same.label), row.path, collection) : null;
24
+ }
25
+ /** Pure request revision comparison, also used after an aborted transport resolves. */ export const isLatestPageRequest = (response, latest)=>response === latest;