@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
@@ -1,12 +1,15 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { DocsResponseError, fetchAllDocs } from '../../../lib/fetchAllDocs.js';
4
+ import { usePlacementLabels, placementDetailKey } from '../../placements/usePlacementLabels.js';
5
+ import { placementUncheckedCopy } from '../../copy/readiness.js';
3
6
  import { useEffect, useState } from 'react';
4
7
  import { Button } from '../../../ui/Button.js';
5
8
  import { TextInput } from '../../../ui/Inputs.js';
6
9
  import { Dialog } from '../../../ui/Dialog.js';
7
10
  import { MakeContentResult } from '../../actions/MakeContentResult.js';
8
11
  import { expectedConfirm, matchesConfirm } from '../../../transitions/confirmPhrases.js';
9
- import { ACTIONS_COPY as A } from '../../copy/actions.js';
12
+ import { ACTIONS_COPY as A, addMetricWarningCopy } from '../../copy/actions.js';
10
13
  import { REVIEW_COPY as R } from '../../copy/review.js';
11
14
  import { READINESS_COPY as C } from '../../copy/readiness.js';
12
15
  import { API_MESSAGES, refusalMessage } from '../../copy/common.js';
@@ -186,22 +189,26 @@ import { useTransition } from './useTransition.js';
186
189
  const [loading, setLoading] = useState(true);
187
190
  const [loadError, setLoadError] = useState(null);
188
191
  const [metric, setMetric] = useState('');
192
+ const [warnings, setWarnings] = useState(null);
193
+ const placementLabels = usePlacementLabels({
194
+ details: (warnings ?? []).filter((warning)=>warning.code === 'placement-unchecked').map((warning)=>warning.detail),
195
+ preflight: test.preflight,
196
+ ...surface
197
+ });
189
198
  const [role, setRole] = useState('secondary');
190
199
  const transition = useTransition(test, onClose);
191
200
  useEffect(()=>{
192
201
  const controller = new AbortController();
193
202
  void (async ()=>{
194
203
  try {
195
- const response = await fetch(`${surface.apiRoute.replace(/\/$/, '')}/xp-metrics?depth=0&limit=100`, {
204
+ const response = await fetchAllDocs(`${surface.apiRoute.replace(/\/$/, '')}/xp-metrics?depth=0`, {
196
205
  credentials: 'include',
197
206
  signal: controller.signal
198
207
  });
199
208
  if (!response.ok) throw new Error(refusalMessage(undefined, response.status));
200
- const data = await response.json();
201
- if (!Array.isArray(data.docs)) throw new Error(API_MESSAGES.unreadable);
202
- if (!controller.signal.aborted) setMetrics(data.docs);
209
+ if (!controller.signal.aborted) setMetrics(response.docs);
203
210
  } catch (cause) {
204
- if (!controller.signal.aborted) setLoadError(cause instanceof Error ? cause.message : API_MESSAGES.network);
211
+ if (!controller.signal.aborted) setLoadError(cause instanceof DocsResponseError ? API_MESSAGES.unreadable : cause instanceof Error ? cause.message : API_MESSAGES.network);
205
212
  } finally{
206
213
  if (!controller.signal.aborted) setLoading(false);
207
214
  }
@@ -218,85 +225,112 @@ import { useTransition } from './useTransition.js';
218
225
  const available = metrics.filter((item)=>!attached.has(String(item.id)));
219
226
  const selected = available.find((item)=>String(item.id) === metric);
220
227
  const disabled = transition.busy || test.hasUnsaved || test.saving === 'saving';
228
+ function close() {
229
+ if (transition.busy) return;
230
+ if (warnings) void test.reload();
231
+ onClose();
232
+ }
221
233
  return /*#__PURE__*/ _jsxs(Dialog, {
222
234
  open: true,
223
235
  dismissible: !transition.busy,
224
236
  title: C.addGoal,
225
- onClose: ()=>{
226
- if (!transition.busy) onClose();
227
- },
237
+ onClose: close,
228
238
  children: [
229
- /*#__PURE__*/ _jsx("p", {
230
- children: C.addMetric
231
- }),
232
- loading ? /*#__PURE__*/ _jsx("p", {
233
- role: "status",
234
- children: R.loadingMetrics
235
- }) : null,
236
- /*#__PURE__*/ _jsxs("label", {
239
+ warnings ? warnings.map((warning, index)=>{
240
+ const placement = warning.code === 'placement-unchecked' ? placementLabels.get(placementDetailKey(warning.detail)) : undefined;
241
+ return /*#__PURE__*/ _jsx("p", {
242
+ children: placement ? placement.href ? /*#__PURE__*/ _jsx("a", {
243
+ href: placement.href,
244
+ children: placementUncheckedCopy(placement.title)
245
+ }) : placementUncheckedCopy(placement.title) : addMetricWarningCopy(warning)
246
+ }, index);
247
+ }) : /*#__PURE__*/ _jsxs(_Fragment, {
237
248
  children: [
238
- R.role,
239
- /*#__PURE__*/ _jsxs("select", {
240
- value: role,
241
- disabled: disabled,
242
- onChange: (event)=>setRole(event.target.value),
249
+ /*#__PURE__*/ _jsx("p", {
250
+ children: C.addMetric
251
+ }),
252
+ loading ? /*#__PURE__*/ _jsx("p", {
253
+ role: "status",
254
+ children: R.loadingMetrics
255
+ }) : null,
256
+ /*#__PURE__*/ _jsxs("label", {
243
257
  children: [
244
- /*#__PURE__*/ _jsx("option", {
245
- value: "secondary",
246
- children: R.supporting
247
- }),
248
- /*#__PURE__*/ _jsx("option", {
249
- value: "guardrail",
250
- children: R.guardrail
258
+ R.role,
259
+ /*#__PURE__*/ _jsxs("select", {
260
+ value: role,
261
+ disabled: disabled,
262
+ onChange: (event)=>setRole(event.target.value),
263
+ children: [
264
+ /*#__PURE__*/ _jsx("option", {
265
+ value: "secondary",
266
+ children: R.supporting
267
+ }),
268
+ /*#__PURE__*/ _jsx("option", {
269
+ value: "guardrail",
270
+ children: R.guardrail
271
+ })
272
+ ]
251
273
  })
252
274
  ]
253
- })
254
- ]
255
- }),
256
- /*#__PURE__*/ _jsxs("label", {
257
- children: [
258
- R.metric,
259
- /*#__PURE__*/ _jsxs("select", {
260
- value: metric,
261
- disabled: disabled || loading,
262
- onChange: (event)=>setMetric(event.target.value),
275
+ }),
276
+ /*#__PURE__*/ _jsxs("label", {
263
277
  children: [
264
- /*#__PURE__*/ _jsx("option", {
265
- value: "",
266
- children: R.chooseMetric
267
- }),
268
- available.map((item)=>/*#__PURE__*/ _jsx("option", {
269
- value: String(item.id),
270
- children: item.name
271
- }, item.id))
278
+ R.metric,
279
+ /*#__PURE__*/ _jsxs("select", {
280
+ value: metric,
281
+ disabled: disabled || loading,
282
+ onChange: (event)=>setMetric(event.target.value),
283
+ children: [
284
+ /*#__PURE__*/ _jsx("option", {
285
+ value: "",
286
+ children: R.chooseMetric
287
+ }),
288
+ available.map((item)=>/*#__PURE__*/ _jsx("option", {
289
+ value: String(item.id),
290
+ children: item.name
291
+ }, item.id))
292
+ ]
293
+ })
272
294
  ]
295
+ }),
296
+ !loading && !loadError && !available.length ? /*#__PURE__*/ _jsx("p", {
297
+ children: R.noMetrics
298
+ }) : null,
299
+ loadError || transition.error ? /*#__PURE__*/ _jsx("p", {
300
+ role: "alert",
301
+ children: loadError ?? transition.error
302
+ }) : null,
303
+ /*#__PURE__*/ _jsx(Button, {
304
+ disabled: disabled || loading || !!loadError || !selected,
305
+ onClick: ()=>{
306
+ // Goal and phase lookups share not-found; prefer the more likely missing goal without reading server text.
307
+ if (selected && test.doc) void transition.run(async ()=>{
308
+ const result = await createApi(surface.apiRoute).addMetric(test.doc.id, {
309
+ metric: selected.id,
310
+ role
311
+ }, {
312
+ notFound: API_MESSAGES.goalNotFound
313
+ });
314
+ if (result.ok) {
315
+ if (result.data?.warnings?.length) setWarnings(result.data.warnings);
316
+ else {
317
+ await test.reload();
318
+ onClose();
319
+ }
320
+ }
321
+ return result;
322
+ }, {
323
+ reloadOnSuccess: false
324
+ });
325
+ },
326
+ children: C.addGoal
273
327
  })
274
328
  ]
275
329
  }),
276
- !loading && !loadError && !available.length ? /*#__PURE__*/ _jsx("p", {
277
- children: R.noMetrics
278
- }) : null,
279
- loadError || transition.error ? /*#__PURE__*/ _jsx("p", {
280
- role: "alert",
281
- children: loadError ?? transition.error
282
- }) : null,
283
- /*#__PURE__*/ _jsx(Button, {
284
- disabled: disabled || loading || !!loadError || !selected,
285
- onClick: ()=>{
286
- // Goal and phase lookups share not-found; prefer the more likely missing goal without reading server text.
287
- if (selected && test.doc) void transition.run(()=>createApi(surface.apiRoute).addMetric(test.doc.id, {
288
- metric: selected.id,
289
- role
290
- }, {
291
- notFound: API_MESSAGES.goalNotFound
292
- }));
293
- },
294
- children: C.addGoal
295
- }),
296
330
  /*#__PURE__*/ _jsx(Button, {
297
331
  disabled: transition.busy,
298
- onClick: onClose,
299
- children: A.cancel
332
+ onClick: close,
333
+ children: warnings ? A.done : A.cancel
300
334
  })
301
335
  ]
302
336
  });
@@ -7,8 +7,10 @@ import { jumpToSection } from '../test/jumpToSection.js';
7
7
  import { actionGroupLabel, availableActions } from '../../actions/actions.model.js';
8
8
  import { createApi } from '../../api.js';
9
9
  import { READINESS_COPY as C, decidedCopy, nextCopy, runningCopy, stoppedCopy } from '../../copy/readiness.js';
10
- import { READINESS, daysRemaining, displayDate, nextStep } from './readiness.model.js';
10
+ import { READINESS, daysRemaining, displayDate, nextStep, pageReadinessContext } from './readiness.model.js';
11
11
  import { AddMetricAction, AllocateAction, ConfirmAction } from './ActionDialogs.js';
12
+ import { preflightPlacementLabel } from '../../placements/placementLabels.js';
13
+ import { placementUncheckedCopy } from '../../copy/readiness.js';
12
14
  import { useTransition } from './useTransition.js';
13
15
  const RUNNING_ACTIONS = [
14
16
  'stop',
@@ -49,10 +51,17 @@ export function ReadinessContent({ test, surface, resultsVersion, bar = false })
49
51
  ]);
50
52
  const decision = doc.decision;
51
53
  const winner = actionGroupLabel(decision?.winnerArm ?? 'control', doc.arms.find((item)=>item.key === decision?.winnerArm)?.label);
52
- const next = nextStep(test.preflight, {
53
- ...doc,
54
- flagPoints: surface.flagPoints
55
- });
54
+ const labelledPreflight = test.preflight ? {
55
+ ...test.preflight,
56
+ checks: test.preflight.checks.map((check)=>check.code === 'placement-unchecked' ? {
57
+ ...check,
58
+ detail: {
59
+ ...check.detail,
60
+ title: preflightPlacementLabel(check.detail, test.preflight, surface.adminRoute).title
61
+ }
62
+ } : check)
63
+ } : null;
64
+ const next = nextStep(labelledPreflight, pageReadinessContext(doc, test.preflight, surface));
56
65
  const setup = doc.status === 'draft' || doc.status === 'stopped';
57
66
  const ready = Boolean(test.preflight && !test.preflightPending && !test.preflightError && test.preflight.checks.every((check)=>check.ok));
58
67
  const unsaved = test.hasUnsaved || test.saving === 'saving';
@@ -96,7 +105,7 @@ export function ReadinessContent({ test, surface, resultsVersion, bar = false })
96
105
  href: `#section-${next.section}`,
97
106
  onClick: (event)=>{
98
107
  event.preventDefault();
99
- if (next.section !== 'header') jumpToSection(next.section);
108
+ if (next.section !== 'header') jumpToSection(next.section, next.arm);
100
109
  },
101
110
  children: [
102
111
  sentence,
@@ -119,6 +128,21 @@ export function ReadinessContent({ test, surface, resultsVersion, bar = false })
119
128
  test.preflight?.checks.filter((check)=>check.code === 'make-content-unsupported' && check.warning).map((check)=>/*#__PURE__*/ _jsx("p", {
120
129
  children: READINESS[check.code].next(check.detail, doc)
121
130
  }, check.code)),
131
+ test.preflight?.checks.filter((check)=>check.warning && [
132
+ 'goal-in-some-versions',
133
+ 'placement-unchecked'
134
+ ].includes(check.code)).map((check, index)=>{
135
+ const placement = check.code === 'placement-unchecked' ? preflightPlacementLabel(check.detail, test.preflight, surface.adminRoute) : undefined;
136
+ return /*#__PURE__*/ _jsx("p", {
137
+ children: placement ? placement.href ? /*#__PURE__*/ _jsx("a", {
138
+ href: placement.href,
139
+ children: placementUncheckedCopy(placement.title)
140
+ }) : placementUncheckedCopy(placement.title) : READINESS[check.code].next(check.detail, doc)
141
+ }, `${check.code}:${index}`);
142
+ }),
143
+ test.preflight?.goalNotes?.map((note)=>/*#__PURE__*/ _jsx("p", {
144
+ children: note.message
145
+ }, note.metricId)),
122
146
  doc.status === 'stopped' ? /*#__PURE__*/ _jsx("p", {
123
147
  children: stoppedCopy(displayDate(doc.stoppedAt))
124
148
  }) : null,
@@ -1,18 +1,22 @@
1
1
  import type { ReadinessCode, PreflightResult } from '../../../types/endpoints.js';
2
2
  import type { SerializedFlagPoint } from '../../../types/admin.js';
3
3
  import type { TestDocument } from '../testDocument.model.js';
4
+ import type { SurfaceProps } from '../contracts.js';
4
5
  import { type SectionId } from '../contracts.js';
5
6
  export type ReadinessContext = Pick<TestDocument, 'arms' | 'unitType' | 'flagPoint'> & {
7
+ collectionLabel?: string;
8
+ pagePlacements?: PreflightResult['pagePlacements'];
6
9
  flagPoints?: readonly Pick<SerializedFlagPoint, 'id' | 'params'>[];
7
10
  };
8
11
  /** Exhaustive mapping of the checkStart ReadinessCode union, including all ten invariant checks. */
9
12
  export declare const READINESS: Record<ReadinessCode, {
10
13
  section: SectionId | 'header';
11
14
  next(detail: unknown, ctx: ReadinessContext): string | null;
12
- nextStep?: string;
15
+ nextStep?: string | ((detail: unknown, ctx: ReadinessContext) => string);
13
16
  }>;
14
17
  export declare function nextStep(preflight: PreflightResult | null, ctx: ReadinessContext): {
15
18
  nextStep?: string | undefined;
19
+ arm?: import("../../../types/contracts.js").ArmKey | undefined;
16
20
  section: "header" | SectionId;
17
21
  sentence: string;
18
22
  code: ReadinessCode;
@@ -22,3 +26,6 @@ export declare function sectionDone(test: {
22
26
  }): Record<SectionId, boolean>;
23
27
  export declare function displayDate(value: unknown): string | null;
24
28
  export declare function daysRemaining(doc: TestDocument, now: number): number | null;
29
+ export declare function pageReadinessContext(doc: TestDocument, preflight: PreflightResult | null, surface: SurfaceProps): ReadinessContext;
30
+ /** First affected row in arm order; shared by the wizard and the test-page jump. */
31
+ export declare function readinessArm(code: ReadinessCode, detail: unknown, ctx: ReadinessContext): import("../../../types/contracts.js").ArmKey | undefined;
@@ -1,6 +1,9 @@
1
+ import { SURFACE_COPY } from '../../copy/surface.js';
1
2
  import { formatDate } from '../../format/date.js';
2
3
  import { actionGroupLabel } from '../../actions/actions.model.js';
3
- import { unsupportedContentCopy, versionPageUnavailableCopy, versionPageUncheckedCopy, placementFailureCopy, unpublishedPageCopy, READINESS_COPY as C, attributeNext, contentNext, goalPeopleNext, parameterNext, placementNext, ruleNext, versionsNext } from '../../copy/readiness.js';
4
+ import { asymmetricGoalsCopy, placementUncheckedCopy, interactingTestsCopy, sameAddressCopy, identicalVersionCopy, unsupportedContentCopy, versionPageUnavailableCopy, versionPageUncheckedCopy, placementFailureCopy, pageInUseCopy, unpublishedPageCopy, READINESS_COPY as C, attributeNext, contentNext, goalPeopleNext, parameterNext, placementNext, ruleNext, versionsNext } from '../../copy/readiness.js';
5
+ import { normalisePath } from '../../../runtime/normalisePath.js';
6
+ import { testPageCollection } from '../pagePicker/pagePicker.model.js';
4
7
  import { SECTION_ORDER } from '../contracts.js';
5
8
  const object = (value)=>value && typeof value === 'object' && !Array.isArray(value) ? value : {};
6
9
  const first = (value)=>object(Array.isArray(value) ? value[0] : value);
@@ -17,8 +20,30 @@ const row = (section, next)=>({
17
20
  const versions = row('versions', (detail)=>versionsNext(detailText(detail, C.versionFallback)));
18
21
  /** Exhaustive mapping of the checkStart ReadinessCode union, including all ten invariant checks. */ export const READINESS = {
19
22
  'make-content-unsupported': row('header', (detail)=>unsupportedContentCopy(text(detail, 'This page'))),
23
+ 'goal-in-some-versions': {
24
+ ...row('win', (detail)=>asymmetricGoalsCopy(detail)),
25
+ nextStep: 'Edit the versions'
26
+ },
27
+ 'placement-unchecked': {
28
+ ...row('versions', (detail)=>placementUncheckedCopy(text(object(detail).title ?? object(detail).path, 'This page'))),
29
+ nextStep: (detail)=>`Open ${text(object(detail).title ?? object(detail).path, 'the page')}`
30
+ },
20
31
  'invalid-status': row('header', ()=>null),
21
32
  'no-placements': row('what', (detail)=>detail === undefined ? C.noPlacements : placementFailureCopy(detail)),
33
+ 'placement-unpublished': {
34
+ ...row('versions', (detail)=>(Array.isArray(detail) && detail.length ? detail : [
35
+ detail
36
+ ]).map((placement)=>`The block you're testing is only on a draft of ${text(object(placement).title, 'this page')}. Publish the page before you start.`).join('\n')),
37
+ nextStep: 'Publish the page'
38
+ },
39
+ 'duplicate-version-row': {
40
+ ...row('versions', (detail)=>`The Experiment block on ${text(first(detail).title, 'this page')} has the same version twice. Remove one of them, then save.`),
41
+ nextStep: (detail)=>`Edit ${text(first(detail).title, 'this page')}`
42
+ },
43
+ 'interacting-tests': {
44
+ ...row('versions', (detail)=>interactingTestsCopy(text(first(detail).path, 'this page'), text(first(detail).status, 'running'), text(first(detail).name, 'another test'), first(detail).version === true)),
45
+ nextStep: (detail)=>`Open ${text(first(detail).name, 'the test')}${first(detail).version === true ? `\nEdit ${text(first(detail).path, 'this page')}` : ''}`
46
+ },
22
47
  'empty-slot': row('versions', (detail, ctx)=>{
23
48
  const placements = Array.isArray(detail) ? detail : [];
24
49
  const missing = ctx.arms.find((item)=>item.kind !== 'holdout' && placements.some((placement)=>{
@@ -49,8 +74,40 @@ const versions = row('versions', (detail)=>versionsNext(detailText(detail, C.ver
49
74
  'no-reach-per-day': row('length', ()=>C.noReach),
50
75
  'layer-full': row('versions', ()=>C.full),
51
76
  'layer-unit-mismatch': row('header', ()=>C.differentPeople),
52
- 'page-no-targets': row('versions', ()=>C.noTargets),
53
- 'page-target-unpublished': row('versions', (detail)=>unpublishedPageCopy(text(detail, 'This page'))),
77
+ 'page-in-use': {
78
+ ...row('versions', (detail, ctx)=>{
79
+ const paths = object(detail).paths;
80
+ const path = text(Array.isArray(paths) ? paths[0] : undefined, 'this page');
81
+ return pageInUseCopy(path, pageArm(path, ctx)?.kind === 'variant');
82
+ }),
83
+ nextStep: C.choosePages
84
+ },
85
+ 'page-no-targets': {
86
+ ...row('versions', ()=>C.noTargets),
87
+ nextStep: 'Choose pages'
88
+ },
89
+ 'unknown-collection': row('versions', ()=>"That page type isn't set up for tests."),
90
+ 'version-identical': {
91
+ ...row('versions', (detail, ctx)=>identicalVersionCopy(text(object(detail).path, object(detail).title ? `${group(object(detail).arm, ctx)} on ${text(object(detail).title, 'this page')}` : group(object(detail).arm, ctx)))),
92
+ nextStep: (detail, ctx)=>`Edit ${text(object(detail).path, group(object(detail).arm, ctx))}`
93
+ },
94
+ 'version-same-address': {
95
+ ...row('versions', (detail, ctx)=>sameAddressCopy(group(object(detail).arm, ctx), group(object(detail).otherArm, ctx), text(object(detail).path, 'this address'), ctx.collectionLabel)),
96
+ nextStep: 'Choose pages'
97
+ },
98
+ 'page-target-unpublished': {
99
+ ...row('versions', (detail, ctx)=>{
100
+ const path = text(typeof detail === 'string' ? detail : object(detail).path, 'This page');
101
+ const arm = ctx.arms.find((item)=>item.key === object(detail).arm) ?? pageArm(path, ctx);
102
+ const placement = (arm ? ctx.pagePlacements?.find((entry)=>entry.arm === arm.key) : undefined) ?? ctx.pagePlacements?.find((entry)=>normalisePath(entry.path) === normalisePath(path));
103
+ return unpublishedPageCopy(path, ctx.pagePlacements && ctx.collectionLabel ? {
104
+ original: arm?.kind === 'control',
105
+ found: placement?.id != null && placement.published === false,
106
+ collection: ctx.collectionLabel
107
+ } : undefined);
108
+ }),
109
+ nextStep: C.choosePages
110
+ },
54
111
  'page-targets-unchecked': row('versions', ()=>C.uncheckedPages),
55
112
  'version-page-unavailable': row('versions', (detail)=>versionPageUnavailableCopy(text(detail, 'This page'))),
56
113
  'version-page-unchecked': row('versions', (detail)=>versionPageUncheckedCopy(text(detail, 'This page'))),
@@ -84,15 +141,28 @@ export function nextStep(preflight, ctx) {
84
141
  ...SECTION_ORDER
85
142
  ]){
86
143
  for (const check of preflight?.checks ?? []){
144
+ if (!Object.hasOwn(READINESS, check.code)) {
145
+ if (!check.ok && section === SECTION_ORDER[0]) return {
146
+ section,
147
+ sentence: SURFACE_COPY.next,
148
+ code: check.code
149
+ };
150
+ continue;
151
+ }
87
152
  const mapping = READINESS[check.code];
88
153
  if (check.ok || mapping.section !== section) continue;
89
154
  const sentence = mapping.next(check.detail, ctx);
155
+ const nextStep = typeof mapping.nextStep === 'function' ? mapping.nextStep(check.detail, ctx) : mapping.nextStep;
156
+ const arm = readinessArm(check.code, check.detail, ctx);
90
157
  if (sentence) return {
91
158
  section,
92
159
  sentence,
93
160
  code: check.code,
94
- ...mapping.nextStep ? {
95
- nextStep: mapping.nextStep
161
+ ...arm ? {
162
+ arm
163
+ } : {},
164
+ ...nextStep ? {
165
+ nextStep
96
166
  } : {}
97
167
  };
98
168
  }
@@ -102,7 +172,7 @@ export function nextStep(preflight, ctx) {
102
172
  export function sectionDone(test) {
103
173
  return Object.fromEntries(SECTION_ORDER.map((section)=>[
104
174
  section,
105
- test.preflight ? !test.preflight.checks.some((check)=>!check.ok && READINESS[check.code].section === section) : section === 'who' || section === 'advanced'
175
+ test.preflight ? !test.preflight.checks.some((check)=>!check.ok && (Object.hasOwn(READINESS, check.code) ? READINESS[check.code].section : SECTION_ORDER[0]) === section) : section === 'who' || section === 'advanced'
106
176
  ]));
107
177
  }
108
178
  export function displayDate(value) {
@@ -114,3 +184,22 @@ export function daysRemaining(doc, now) {
114
184
  const start = typeof doc.startedAt === 'string' ? Date.parse(doc.startedAt) : NaN;
115
185
  return typeof days === 'number' && days > 0 && Number.isFinite(start) ? Math.max(0, Math.ceil(days - (now - start) / 86_400_000)) : null;
116
186
  }
187
+ function pageArm(path, ctx) {
188
+ return ctx.arms.find((arm)=>arm.pageTarget && normalisePath(arm.pageTarget) === normalisePath(path));
189
+ }
190
+ export function pageReadinessContext(doc, preflight, surface) {
191
+ return {
192
+ ...doc,
193
+ flagPoints: surface.flagPoints,
194
+ pagePlacements: preflight?.pagePlacements,
195
+ collectionLabel: testPageCollection(surface.pageCollections, doc, preflight?.pagePlacements)?.label
196
+ };
197
+ }
198
+ /** First affected row in arm order; shared by the wizard and the test-page jump. */ export function readinessArm(code, detail, ctx) {
199
+ if (code === 'version-same-address') return ctx.arms.find((arm)=>arm.key === object(detail).arm)?.key;
200
+ if (code === 'page-no-targets') return ctx.arms.find((arm)=>arm.kind !== 'holdout' && !arm.pageTarget)?.key;
201
+ const paths = code === 'page-in-use' ? object(detail).paths : code === 'page-target-unpublished' ? [
202
+ detail
203
+ ] : [];
204
+ return ctx.arms.find((arm)=>arm.kind !== 'holdout' && arm.pageTarget && Array.isArray(paths) && paths.some((path)=>typeof path === 'string' && normalisePath(path) === normalisePath(arm.pageTarget)))?.key;
205
+ }
@@ -8,7 +8,9 @@ export declare function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, o
8
8
  onRefusal(message: string): void;
9
9
  }): import("react").JSX.Element;
10
10
  /** An inaccessible or deleted author never hides the recorded release date. */
11
- export declare function PagesReleased({ decision, apiRoute }: {
11
+ export declare function PagesReleased({ decision, apiRoute, userId, userCollection: collection }: {
12
12
  decision: PageReleaseDecision;
13
13
  apiRoute: string;
14
+ userId?: DocId;
15
+ userCollection?: string;
14
16
  }): import("react").JSX.Element;
@@ -1,7 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from 'react';
4
- import { useAuth, useConfig } from '@payloadcms/ui';
5
4
  import { Button } from '../../../ui/Button.js';
6
5
  import { Dialog } from '../../../ui/Dialog.js';
7
6
  import { createApi } from '../../api.js';
@@ -63,10 +62,7 @@ export function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, onRefusal
63
62
  ]
64
63
  });
65
64
  }
66
- /** An inaccessible or deleted author never hides the recorded release date. */ export function PagesReleased({ decision, apiRoute }) {
67
- const { user } = useAuth();
68
- const { config } = useConfig();
69
- const collection = config.admin.user;
65
+ /** An inaccessible or deleted author never hides the recorded release date. */ export function PagesReleased({ decision, apiRoute, userId, userCollection: collection }) {
70
66
  const by = decision.pagesReleasedBy;
71
67
  const [resolved, setResolved] = useState(null);
72
68
  const key = JSON.stringify([
@@ -74,7 +70,7 @@ export function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, onRefusal
74
70
  collection,
75
71
  by
76
72
  ]);
77
- const own = pagesReleasedBy(by, user?.id);
73
+ const own = pagesReleasedBy(by, userId);
78
74
  useEffect(()=>{
79
75
  if (!by || own || !collection) return;
80
76
  const controller = new AbortController();
@@ -109,7 +105,7 @@ export function ReleasePagesDialog({ id, apiRoute, onClose, onSuccess, onRefusal
109
105
  key,
110
106
  own
111
107
  ]);
112
- const name = own ?? pagesReleasedBy(by, user?.id, resolved?.key === key ? resolved.author : null);
108
+ const name = own ?? pagesReleasedBy(by, userId, resolved?.key === key ? resolved.author : null);
113
109
  return /*#__PURE__*/ _jsx("p", {
114
110
  children: pagesReleasedCopy(formatDate(decision.pagesReleasedAt), name)
115
111
  });
@@ -12,6 +12,7 @@ import { METRICS_COPY } from '../../copy/metrics.js';
12
12
  import { API_MESSAGES } from '../../copy/common.js';
13
13
  import { armLabel } from '../../labels.js';
14
14
  import { PreviewLinks } from '../../PreviewLinks.js';
15
+ import { actionablePreviewPages } from '../../previewLinks.model.js';
15
16
  import { VerdictHero } from '../../results/VerdictHero.js';
16
17
  import { StatTile } from '../../results/StatTile.js';
17
18
  import { DataTable, Swatch } from '../../results/DataTable.js';
@@ -26,6 +27,7 @@ import { DecideDialog } from '../../actions/DecideDialog.js';
26
27
  import { Banner } from '../../../ui/Banner.js';
27
28
  import { canReleasePages } from '../../results/releasePages.model.js';
28
29
  import { PagesReleased, ReleasePagesDialog } from './ReleasePages.js';
30
+ import { usePlacementLabels } from '../../placements/usePlacementLabels.js';
29
31
  export function ResultsBody({ id, surface, onRecomputed, onPagesReleased, renderPage }) {
30
32
  // A different test owns an independent selection and action lifecycle.
31
33
  return /*#__PURE__*/ _jsx(ResultsContent, {
@@ -115,12 +117,20 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
115
117
  isDecidedTestRound,
116
118
  winnerLabel: winner
117
119
  }) : null;
120
+ const placementLabels = usePlacementLabels({
121
+ details: shown?.health?.filter((item)=>item.code === 'placement-unchecked').map((item)=>item.detail) ?? [],
122
+ apiRoute: surface.apiRoute,
123
+ adminRoute: surface.adminRoute,
124
+ canManage: manage,
125
+ collectionTitles: surface.collectionTitles
126
+ });
118
127
  const context = {
119
128
  status,
120
129
  phase,
121
130
  inLayer: doc?.layer != null,
122
131
  layerSlices: doc?.layerSlices,
123
- allocation: doc?.allocation
132
+ allocation: doc?.allocation,
133
+ placementLabels
124
134
  };
125
135
  const primary = phase?.metrics.find((metric)=>metric.role === 'primary');
126
136
  // The endpoint decides the current round; historical rounds are read-only.
@@ -146,7 +156,9 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
146
156
  // Release details come only from the manager's own document, never the results response.
147
157
  const released = manage && doc?.decision?.pagesReleasedAt ? /*#__PURE__*/ _jsx(PagesReleased, {
148
158
  decision: doc.decision,
149
- apiRoute: surface.apiRoute
159
+ apiRoute: surface.apiRoute,
160
+ userId: surface.userId,
161
+ userCollection: surface.userCollection
150
162
  }) : null;
151
163
  const meta = /*#__PURE__*/ _jsxs(_Fragment, {
152
164
  children: [
@@ -262,6 +274,9 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
262
274
  }, context),
263
275
  sinceShipping: rolloutLine(shown, phase, phases, decision ?? undefined)
264
276
  }),
277
+ primary?.clickGoal ? /*#__PURE__*/ _jsx("p", {
278
+ children: C.clickPrimary
279
+ }) : null,
265
280
  /*#__PURE__*/ _jsxs("div", {
266
281
  className: "xp-stat-tiles",
267
282
  children: [
@@ -308,6 +323,9 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
308
323
  })
309
324
  ]
310
325
  }) : null,
326
+ shown.goalFairnessNotice ? /*#__PURE__*/ _jsx("p", {
327
+ children: shown.goalFairnessNotice
328
+ }) : null,
311
329
  /*#__PURE__*/ _jsx(DataQualityPanel, {
312
330
  problem: shown.verdict.code === 'data-problem',
313
331
  items: dataQuality(shown, context)
@@ -329,7 +347,7 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
329
347
  }) : null
330
348
  ]
331
349
  }),
332
- manage && placements && placements.length > 0 ? /*#__PURE__*/ _jsxs("section", {
350
+ manage && placements && actionablePreviewPages(placements, doc?.flagPoint).length > 0 ? /*#__PURE__*/ _jsxs("section", {
333
351
  className: "xp-results-block",
334
352
  children: [
335
353
  /*#__PURE__*/ _jsx("div", {
@@ -339,6 +357,7 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
339
357
  })
340
358
  }),
341
359
  /*#__PURE__*/ _jsx(PreviewLinks, {
360
+ apiRoute: surface.apiRoute,
342
361
  experimentId: id,
343
362
  flagPoint: doc?.flagPoint,
344
363
  placements: placements,
@@ -359,6 +378,7 @@ function ResultsContent({ id, surface, onRecomputed, onPagesReleased, renderPage
359
378
  ]
360
379
  }) : null,
361
380
  deciding && manage && phase ? /*#__PURE__*/ _jsx(DecideDialog, {
381
+ apiRoute: surface.apiRoute,
362
382
  id: id,
363
383
  arms: phase.arms,
364
384
  checks: checks,
@@ -8,7 +8,7 @@ import { Banner, StatusChip } from '../../../ui/Feedback.js';
8
8
  import { createApi } from '../../api.js';
9
9
  import { availableActions, actionGroupLabel } from '../../actions/actions.model.js';
10
10
  import { AllocateAction, ConfirmAction } from '../readiness/ActionDialogs.js';
11
- import { READINESS, daysRemaining, displayDate, nextStep } from '../readiness/readiness.model.js';
11
+ import { READINESS, daysRemaining, displayDate, nextStep, pageReadinessContext } from '../readiness/readiness.model.js';
12
12
  import { READINESS_COPY as R, runningCopy, stoppedCopy, decidedCopy } from '../../copy/readiness.js';
13
13
  import { RESULTS_COPY as C } from '../../copy/results.js';
14
14
  import { TEST_PAGE } from '../../copy/testPage.js';
@@ -54,10 +54,7 @@ export function ResultsHeader({ test, surface, resultsVersion, children, actions
54
54
  'allocate',
55
55
  'stop'
56
56
  ].indexOf(b.id) : 0);
57
- const next = nextStep(test.preflight, {
58
- ...doc,
59
- flagPoints: surface.flagPoints
60
- });
57
+ const next = nextStep(test.preflight, pageReadinessContext(doc, test.preflight, surface));
61
58
  const ready = Boolean(test.preflight && !test.preflightPending && !test.preflightError && test.preflight.checks.every((check)=>check.ok));
62
59
  const startReason = next?.sentence ?? (!ready ? test.preflightError ? R.checkError : R.checking : undefined);
63
60
  const action = available.find((item)=>item.id === selected);
@@ -1,8 +1,9 @@
1
1
  import type { PhaseSummary } from '../../../types/endpoints.js';
2
2
  import type { Status } from '../../../types/contracts.js';
3
- import type { Snapshot, VerdictCode } from '../../../types/snapshot.js';
3
+ import type { VerdictCode } from '../../../types/snapshot.js';
4
+ import { type DisplaySnapshot } from '../../results/snapshot.js';
4
5
  export interface RecommendationContext {
5
- snapshot: Snapshot;
6
+ snapshot: DisplaySnapshot;
6
7
  phase?: PhaseSummary;
7
8
  plannedDurationDays?: number | null;
8
9
  status?: Status;