@delmaredigital/payload-experiments 0.2.0-rc.6 → 0.3.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 (291) hide show
  1. package/README.md +129 -11
  2. package/dist/admin/PreviewLinks.d.ts +2 -1
  3. package/dist/admin/PreviewLinks.js +24 -16
  4. package/dist/admin/actions/ConfirmDialog.d.ts +7 -4
  5. package/dist/admin/actions/ConfirmDialog.js +37 -82
  6. package/dist/admin/actions/DecideDialog.d.ts +3 -1
  7. package/dist/admin/actions/DecideDialog.js +30 -13
  8. package/dist/admin/actions/MakeContentResult.d.ts +13 -2
  9. package/dist/admin/actions/MakeContentResult.js +115 -16
  10. package/dist/admin/actions/actions.model.d.ts +2 -4
  11. package/dist/admin/actions/actions.model.js +2 -3
  12. package/dist/admin/allocation.model.js +3 -5
  13. package/dist/admin/api.d.ts +10 -4
  14. package/dist/admin/api.js +11 -7
  15. package/dist/admin/copy/actions.d.ts +39 -3
  16. package/dist/admin/copy/actions.js +40 -4
  17. package/dist/admin/copy/armSelect.js +5 -5
  18. package/dist/admin/copy/arms.d.ts +1 -7
  19. package/dist/admin/copy/arms.js +1 -7
  20. package/dist/admin/copy/audience.d.ts +2 -1
  21. package/dist/admin/copy/audience.js +3 -2
  22. package/dist/admin/copy/common.d.ts +13 -6
  23. package/dist/admin/copy/common.js +14 -8
  24. package/dist/admin/copy/goals.d.ts +21 -6
  25. package/dist/admin/copy/goals.js +27 -6
  26. package/dist/admin/copy/readiness.d.ts +19 -0
  27. package/dist/admin/copy/readiness.js +19 -0
  28. package/dist/admin/copy/recommendation.d.ts +0 -3
  29. package/dist/admin/copy/recommendation.js +0 -3
  30. package/dist/admin/copy/results.d.ts +41 -2
  31. package/dist/admin/copy/results.js +42 -3
  32. package/dist/admin/copy/review.d.ts +1 -1
  33. package/dist/admin/copy/review.js +1 -1
  34. package/dist/admin/copy/setting.d.ts +2 -0
  35. package/dist/admin/copy/setting.js +2 -0
  36. package/dist/admin/copy/surface.d.ts +7 -4
  37. package/dist/admin/copy/surface.js +9 -4
  38. package/dist/admin/copy/testPage.d.ts +12 -0
  39. package/dist/admin/copy/testPage.js +12 -0
  40. package/dist/admin/format/date.d.ts +2 -0
  41. package/dist/admin/format/date.js +10 -0
  42. package/dist/admin/labels.d.ts +2 -7
  43. package/dist/admin/labels.js +2 -7
  44. package/dist/admin/previewLinks.model.d.ts +2 -2
  45. package/dist/admin/previewLinks.model.js +5 -7
  46. package/dist/admin/results/CiChart.js +11 -21
  47. package/dist/admin/results/DataQualityPanel.d.ts +4 -2
  48. package/dist/admin/results/DataQualityPanel.js +30 -17
  49. package/dist/admin/results/DataTable.d.ts +16 -0
  50. package/dist/admin/results/DataTable.js +152 -0
  51. package/dist/admin/results/StatTile.d.ts +7 -0
  52. package/dist/admin/results/StatTile.js +22 -0
  53. package/dist/admin/results/VerdictHero.d.ts +13 -0
  54. package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
  55. package/dist/admin/results/releasePages.model.d.ts +17 -0
  56. package/dist/admin/results/releasePages.model.js +10 -0
  57. package/dist/admin/results/results.css +64 -0
  58. package/dist/admin/results/results.model.d.ts +6 -2
  59. package/dist/admin/results/results.model.js +12 -5
  60. package/dist/admin/surface/ResultsRoute.d.ts +1 -1
  61. package/dist/admin/surface/ResultsRoute.js +60 -31
  62. package/dist/admin/surface/SurfaceShell.d.ts +11 -2
  63. package/dist/admin/surface/SurfaceShell.js +49 -14
  64. package/dist/admin/surface/TestsHome.d.ts +6 -1
  65. package/dist/admin/surface/TestsHome.js +183 -112
  66. package/dist/admin/surface/TestsSurface.js +56 -16
  67. package/dist/admin/surface/contracts.d.ts +1 -0
  68. package/dist/admin/surface/goals/GoalForm.js +341 -308
  69. package/dist/admin/surface/goals/GoalsList.js +123 -89
  70. package/dist/admin/surface/goals/data.d.ts +8 -2
  71. package/dist/admin/surface/goals/data.js +11 -3
  72. package/dist/admin/surface/goals/goals.css +10 -13
  73. package/dist/admin/surface/home.model.d.ts +7 -8
  74. package/dist/admin/surface/home.model.js +23 -12
  75. package/dist/admin/surface/newTest/NewTest.js +5 -5
  76. package/dist/admin/surface/pageBlock/parts.js +31 -26
  77. package/dist/admin/surface/readiness/ActionDialogs.js +43 -14
  78. package/dist/admin/surface/readiness/Banner.d.ts +3 -0
  79. package/dist/admin/surface/readiness/Banner.js +63 -36
  80. package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
  81. package/dist/admin/surface/readiness/SaveStatus.js +6 -3
  82. package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
  83. package/dist/admin/surface/readiness/readiness.model.js +18 -9
  84. package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
  85. package/dist/admin/surface/readiness/useTransition.js +2 -1
  86. package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
  87. package/dist/admin/surface/results/ReleasePages.js +116 -0
  88. package/dist/admin/surface/results/ResultsBody.d.ts +15 -2
  89. package/dist/admin/surface/results/ResultsBody.js +281 -107
  90. package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
  91. package/dist/admin/surface/results/ResultsHeader.js +177 -0
  92. package/dist/admin/surface/results/recommendation.model.js +2 -6
  93. package/dist/admin/surface/results/useResults.d.ts +21 -3
  94. package/dist/admin/surface/results/useResults.js +2 -1
  95. package/dist/admin/surface/setting/parts.js +75 -88
  96. package/dist/admin/surface/setting/setting.css +3 -15
  97. package/dist/admin/surface/surface.css +62 -47
  98. package/dist/admin/surface/test/AdvancedSection.js +55 -48
  99. package/dist/admin/surface/test/GoalSection.js +39 -32
  100. package/dist/admin/surface/test/LengthSection.js +46 -34
  101. package/dist/admin/surface/test/Rail.d.ts +4 -0
  102. package/dist/admin/surface/test/Rail.js +83 -0
  103. package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
  104. package/dist/admin/surface/test/ReadinessBar.js +33 -0
  105. package/dist/admin/surface/test/Section.d.ts +1 -1
  106. package/dist/admin/surface/test/Section.js +36 -16
  107. package/dist/admin/surface/test/TargetingSection.js +168 -142
  108. package/dist/admin/surface/test/TestPage.d.ts +2 -1
  109. package/dist/admin/surface/test/TestPage.js +146 -59
  110. package/dist/admin/surface/test/VersionsSection.js +151 -120
  111. package/dist/admin/surface/test/fields.d.ts +11 -4
  112. package/dist/admin/surface/test/fields.js +84 -85
  113. package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
  114. package/dist/admin/surface/test/jumpToSection.js +10 -0
  115. package/dist/admin/surface/test/pageParts.js +9 -3
  116. package/dist/admin/surface/test/testPage.css +81 -14
  117. package/dist/admin/surface/useTestDocument.d.ts +1 -0
  118. package/dist/admin/surface/useTestDocument.js +40 -9
  119. package/dist/admin/targeting/targeting.model.d.ts +1 -3
  120. package/dist/admin/targeting/targeting.model.js +3 -8
  121. package/dist/admin/theme.css +115 -36
  122. package/dist/admin/theme.d.ts +38 -0
  123. package/dist/admin/theme.js +38 -1
  124. package/dist/admin/useApi.d.ts +17 -2
  125. package/dist/admin/views/ExperimentsRootView.js +12 -2
  126. package/dist/collections/experiments/config.js +44 -1
  127. package/dist/collections/experiments/lockHook.js +16 -2
  128. package/dist/collections/identities.js +6 -1
  129. package/dist/collections/metrics.js +7 -0
  130. package/dist/collections/metricsGuards.d.ts +9 -0
  131. package/dist/collections/metricsGuards.js +33 -0
  132. package/dist/constants.d.ts +1 -0
  133. package/dist/constants.js +1 -0
  134. package/dist/contentDone.d.ts +4 -0
  135. package/dist/contentDone.js +5 -0
  136. package/dist/db/identities.d.ts +8 -7
  137. package/dist/db/identities.js +9 -6
  138. package/dist/db/metricUsage.d.ts +19 -0
  139. package/dist/db/metricUsage.js +54 -0
  140. package/dist/db/names.d.ts +5 -0
  141. package/dist/db/names.js +5 -0
  142. package/dist/db/publishedContent.d.ts +10 -0
  143. package/dist/db/publishedContent.js +43 -0
  144. package/dist/db/results.d.ts +2 -0
  145. package/dist/db/results.js +6 -2
  146. package/dist/db/snapshots.d.ts +18 -6
  147. package/dist/db/snapshots.js +23 -7
  148. package/dist/db/staleEdit.d.ts +6 -0
  149. package/dist/db/staleEdit.js +10 -0
  150. package/dist/db/sweep.js +11 -6
  151. package/dist/endpoints/events.js +3 -3
  152. package/dist/endpoints/index.js +19 -1
  153. package/dist/endpoints/recompute.js +2 -1
  154. package/dist/endpoints/results.js +5 -0
  155. package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
  156. package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
  157. package/dist/endpoints/summary.js +20 -2
  158. package/dist/endpoints/transitions.d.ts +15 -0
  159. package/dist/endpoints/transitions.js +36 -0
  160. package/dist/errors.d.ts +8 -0
  161. package/dist/errors.js +28 -0
  162. package/dist/exports/adminClient.d.ts +1 -0
  163. package/dist/exports/adminClient.js +1 -0
  164. package/dist/exports/index.d.ts +2 -1
  165. package/dist/exports/index.js +1 -0
  166. package/dist/exports/puckEditor.d.ts +1 -0
  167. package/dist/exports/puckEditor.js +1 -0
  168. package/dist/exports/rsc.d.ts +5 -1
  169. package/dist/exports/rsc.js +2 -0
  170. package/dist/hooks/hostHook.js +3 -2
  171. package/dist/hooks/pageGuard.d.ts +5 -0
  172. package/dist/hooks/pageGuard.js +121 -0
  173. package/dist/labels/arms.d.ts +7 -0
  174. package/dist/labels/arms.js +7 -0
  175. package/dist/log.d.ts +7 -0
  176. package/dist/log.js +38 -0
  177. package/dist/manifest/compile.d.ts +5 -0
  178. package/dist/manifest/compile.js +5 -1
  179. package/dist/options.js +23 -0
  180. package/dist/plugin.js +26 -2
  181. package/dist/puck/copy.d.ts +1 -1
  182. package/dist/puck/copy.js +6 -6
  183. package/dist/puck/editor/editor.chrome.css +235 -0
  184. package/dist/puck/editor/editor.css +241 -43
  185. package/dist/results/aggregate.js +4 -3
  186. package/dist/results/analyse.d.ts +4 -1
  187. package/dist/results/analyse.js +27 -11
  188. package/dist/results/cuped.js +3 -2
  189. package/dist/results/identityCredit.d.ts +15 -7
  190. package/dist/results/identityCredit.js +32 -33
  191. package/dist/results/rolloutUnits.js +2 -1
  192. package/dist/results/snapshot.js +8 -2
  193. package/dist/results/srm.js +2 -1
  194. package/dist/results/verdict.d.ts +2 -0
  195. package/dist/results/verdict.js +18 -1
  196. package/dist/sanitizeReadinessDetail.d.ts +7 -0
  197. package/dist/sanitizeReadinessDetail.js +54 -0
  198. package/dist/server/linkIdentity.d.ts +10 -1
  199. package/dist/server/linkIdentity.js +20 -18
  200. package/dist/server/metadata.d.ts +5 -0
  201. package/dist/server/metadata.js +9 -1
  202. package/dist/server/recordContentChange.js +2 -1
  203. package/dist/server/recordExposures.js +3 -3
  204. package/dist/server/runResults.d.ts +3 -2
  205. package/dist/server/runResults.js +125 -34
  206. package/dist/server/trackConversion.js +5 -6
  207. package/dist/targeting/values.d.ts +4 -0
  208. package/dist/targeting/values.js +10 -0
  209. package/dist/transitions/addMetric.js +8 -0
  210. package/dist/transitions/archive.js +2 -3
  211. package/dist/transitions/checkStart.d.ts +6 -3
  212. package/dist/transitions/checkStart.js +15 -0
  213. package/dist/transitions/contentFields.d.ts +7 -0
  214. package/dist/transitions/contentFields.js +20 -0
  215. package/dist/transitions/decide.js +6 -1
  216. package/dist/transitions/deleteExperiment.js +2 -1
  217. package/dist/transitions/dismissContentEntry.d.ts +8 -0
  218. package/dist/transitions/dismissContentEntry.js +45 -0
  219. package/dist/transitions/makeContent.d.ts +5 -2
  220. package/dist/transitions/makeContent.js +217 -118
  221. package/dist/transitions/phaseRecord.d.ts +8 -2
  222. package/dist/transitions/placements.d.ts +1 -0
  223. package/dist/transitions/placements.js +4 -0
  224. package/dist/transitions/preflightData.d.ts +1 -1
  225. package/dist/transitions/preflightData.js +42 -4
  226. package/dist/transitions/releasePages.d.ts +8 -0
  227. package/dist/transitions/releasePages.js +26 -0
  228. package/dist/transitions/restoreContentEntry.d.ts +8 -0
  229. package/dist/transitions/restoreContentEntry.js +47 -0
  230. package/dist/transitions/start.js +30 -5
  231. package/dist/types/contracts.d.ts +14 -0
  232. package/dist/types/endpoints.d.ts +34 -3
  233. package/dist/types/index.d.ts +1 -0
  234. package/dist/types/index.js +1 -0
  235. package/dist/types/reasons.d.ts +3 -0
  236. package/dist/types/reasons.js +1 -0
  237. package/dist/types/snapshot.d.ts +6 -1
  238. package/dist/ui/Banner.d.ts +2 -0
  239. package/dist/ui/Banner.js +2 -0
  240. package/dist/ui/Breadcrumb.d.ts +7 -0
  241. package/dist/ui/Breadcrumb.js +25 -0
  242. package/dist/ui/Button.d.ts +15 -0
  243. package/dist/ui/Button.js +32 -0
  244. package/dist/ui/Card.d.ts +12 -0
  245. package/dist/ui/Card.js +40 -0
  246. package/dist/ui/Checkbox.d.ts +2 -0
  247. package/dist/ui/Checkbox.js +2 -0
  248. package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
  249. package/dist/{admin/components → ui}/ChipsInput.js +20 -12
  250. package/dist/ui/Dialog.d.ts +13 -0
  251. package/dist/ui/Dialog.js +85 -0
  252. package/dist/ui/EmptyState.d.ts +2 -0
  253. package/dist/ui/EmptyState.js +2 -0
  254. package/dist/ui/Feedback.d.ts +24 -0
  255. package/dist/ui/Feedback.js +40 -0
  256. package/dist/ui/Inputs.d.ts +34 -0
  257. package/dist/ui/Inputs.js +225 -0
  258. package/dist/ui/NumberInput.d.ts +2 -0
  259. package/dist/ui/NumberInput.js +2 -0
  260. package/dist/ui/Range.d.ts +2 -0
  261. package/dist/ui/Range.js +2 -0
  262. package/dist/ui/SearchInput.d.ts +2 -0
  263. package/dist/ui/SearchInput.js +2 -0
  264. package/dist/ui/SectionCard.d.ts +2 -0
  265. package/dist/ui/SectionCard.js +2 -0
  266. package/dist/ui/Select.d.ts +2 -0
  267. package/dist/ui/Select.js +2 -0
  268. package/dist/ui/StatusChip.d.ts +2 -0
  269. package/dist/ui/StatusChip.js +2 -0
  270. package/dist/ui/Tabs.d.ts +14 -0
  271. package/dist/ui/Tabs.js +54 -0
  272. package/dist/ui/TextInput.d.ts +2 -0
  273. package/dist/ui/TextInput.js +2 -0
  274. package/dist/ui/Textarea.d.ts +2 -0
  275. package/dist/ui/Textarea.js +2 -0
  276. package/dist/ui/Toggle.d.ts +2 -0
  277. package/dist/ui/Toggle.js +2 -0
  278. package/dist/ui/accent.d.ts +21 -0
  279. package/dist/ui/accent.js +81 -0
  280. package/dist/ui/index.d.ts +9 -0
  281. package/dist/ui/index.js +9 -0
  282. package/dist/ui/ui.css +92 -0
  283. package/package.json +1 -1
  284. package/dist/admin/components/chipsInput.css +0 -11
  285. package/dist/admin/results/ArmTable.d.ts +0 -8
  286. package/dist/admin/results/ArmTable.js +0 -113
  287. package/dist/admin/results/VerdictCard.d.ts +0 -9
  288. package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
  289. package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
  290. package/dist/admin/surface/test/TestHeader.d.ts +0 -4
  291. package/dist/admin/surface/test/TestHeader.js +0 -69
@@ -1,20 +1,30 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from 'react';
4
- import { Link, Pill } from '@payloadcms/ui';
4
+ import { Link } from '@payloadcms/ui';
5
5
  import { formatAdminURL } from 'payload/shared';
6
- import { GOALS_COPY as C } from '../../copy/goals.js';
6
+ import { Banner, EmptyState } from '../../../ui/Feedback.js';
7
+ import { Button } from '../../../ui/Button.js';
8
+ import { SearchInput } from '../../../ui/Inputs.js';
9
+ import { SurfaceShell } from '../SurfaceShell.js';
10
+ import { SURFACE_COPY } from '../../copy/surface.js';
11
+ import { GOALS_COPY as C, goalsNoMatches, goalUsedBy } from '../../copy/goals.js';
12
+ import { METRIC_KIND_LABELS } from '../../copy/metrics.js';
7
13
  import { goalUsage } from './goals.model.js';
8
14
  import { goalPages } from './data.js';
9
- import { GoalsBreadcrumb } from './GoalsBreadcrumb.js';
10
15
  import './goals.css';
11
16
  export function GoalsList({ surface, returnToTest }) {
12
17
  const [data, setData] = useState();
13
18
  const [error, setError] = useState('');
14
19
  const [attempt, setAttempt] = useState(0);
20
+ const [search, setSearch] = useState('');
15
21
  const returnQuery = returnToTest ? `?${new URLSearchParams({
16
22
  return: returnToTest
17
23
  })}` : '';
24
+ const url = (path)=>formatAdminURL({
25
+ adminRoute: surface.adminRoute,
26
+ path
27
+ });
18
28
  useEffect(()=>{
19
29
  const controller = new AbortController();
20
30
  setData(undefined);
@@ -37,99 +47,123 @@ export function GoalsList({ surface, returnToTest }) {
37
47
  attempt
38
48
  ]);
39
49
  const usage = goalUsage(data?.tests ?? []);
40
- const newGoal = /*#__PURE__*/ _jsx(Link, {
41
- className: "xp-tests-primary",
42
- href: formatAdminURL({
43
- adminRoute: surface.adminRoute,
44
- path: `/experiments/goals/new${returnQuery}`
45
- }),
50
+ const showSearch = (data?.goals.length ?? 0) > 8;
51
+ const goals = data?.goals.filter((goal)=>!showSearch || goal.name.toLocaleLowerCase().includes(search.trim().toLocaleLowerCase())) ?? [];
52
+ const newGoal = surface.canManage ? /*#__PURE__*/ _jsx(Button, {
53
+ variant: "primary",
54
+ href: url(`/experiments/goals/new${returnQuery}`),
46
55
  children: C.newGoal
47
- });
48
- return /*#__PURE__*/ _jsxs("div", {
49
- className: "xp-goals",
50
- children: [
51
- /*#__PURE__*/ _jsx(GoalsBreadcrumb, {
52
- adminRoute: surface.adminRoute
53
- }),
54
- /*#__PURE__*/ _jsxs("div", {
55
- className: "xp-tests-toolbar",
56
- children: [
57
- /*#__PURE__*/ _jsx("h1", {
58
- children: C.title
59
- }),
60
- newGoal
61
- ]
62
- }),
63
- error ? /*#__PURE__*/ _jsxs("p", {
64
- role: "alert",
65
- children: [
66
- error,
67
- ' ',
68
- /*#__PURE__*/ _jsx("button", {
69
- type: "button",
56
+ }) : null;
57
+ return /*#__PURE__*/ _jsx(SurfaceShell, {
58
+ adminRoute: surface.adminRoute,
59
+ title: C.title,
60
+ crumbs: [
61
+ {
62
+ label: SURFACE_COPY.title,
63
+ href: url('/experiments')
64
+ },
65
+ {
66
+ label: C.title
67
+ }
68
+ ],
69
+ actions: /*#__PURE__*/ _jsxs(_Fragment, {
70
+ children: [
71
+ returnToTest ? /*#__PURE__*/ _jsx(Button, {
72
+ variant: "quiet",
73
+ href: url(`/experiments/${encodeURIComponent(returnToTest)}`),
74
+ children: C.backToTest
75
+ }) : null,
76
+ newGoal
77
+ ]
78
+ }),
79
+ children: /*#__PURE__*/ _jsxs("div", {
80
+ className: "xp-goals",
81
+ children: [
82
+ showSearch ? /*#__PURE__*/ _jsx(SearchInput, {
83
+ id: "xp-goals-search",
84
+ label: C.search,
85
+ placeholder: C.search,
86
+ value: search,
87
+ onChange: (event)=>setSearch(event.target.value)
88
+ }) : null,
89
+ error ? /*#__PURE__*/ _jsx(Banner, {
90
+ tone: "error",
91
+ action: /*#__PURE__*/ _jsx(Button, {
92
+ size: "sm",
70
93
  onClick: ()=>setAttempt((n)=>n + 1),
71
94
  children: C.retry
72
- })
73
- ]
74
- }) : !data ? /*#__PURE__*/ _jsx("p", {
75
- role: "status",
76
- children: C.loading
77
- }) : !data.goals.length ? /*#__PURE__*/ _jsx("p", {
78
- children: C.empty
79
- }) : /*#__PURE__*/ _jsx("div", {
80
- className: "xp-goals-table",
81
- children: /*#__PURE__*/ _jsxs("table", {
82
- children: [
83
- /*#__PURE__*/ _jsx("thead", {
84
- children: /*#__PURE__*/ _jsx("tr", {
85
- children: [
86
- C.name,
87
- C.source,
88
- C.directionColumn,
89
- C.usedBy,
90
- C.status
91
- ].map((label)=>/*#__PURE__*/ _jsx("th", {
92
- scope: "col",
93
- children: label
94
- }, label))
95
- })
96
- }),
97
- /*#__PURE__*/ _jsx("tbody", {
98
- children: data.goals.map((goal)=>{
99
- const count = usage.get(goal.id) ?? 0;
100
- return /*#__PURE__*/ _jsxs("tr", {
95
+ }),
96
+ children: error
97
+ }) : !data ? /*#__PURE__*/ _jsx("p", {
98
+ role: "status",
99
+ children: C.loading
100
+ }) : !data.goals.length ? /*#__PURE__*/ _jsx(EmptyState, {
101
+ action: newGoal,
102
+ children: C.empty
103
+ }) : !goals.length ? /*#__PURE__*/ _jsx(EmptyState, {
104
+ action: /*#__PURE__*/ _jsx(Button, {
105
+ variant: "quiet",
106
+ onClick: ()=>setSearch(''),
107
+ children: C.clearSearch
108
+ }),
109
+ children: goalsNoMatches(search)
110
+ }) : /*#__PURE__*/ _jsx("div", {
111
+ className: "xp-tests-list",
112
+ children: goals.map((goal)=>{
113
+ const count = usage.get(goal.id) ?? 0;
114
+ const kind = surface.metricSources.find((source)=>source.id === goal.source)?.kind;
115
+ const label = kind && Object.hasOwn(METRIC_KIND_LABELS, kind) ? METRIC_KIND_LABELS[kind] : undefined;
116
+ const href = url(`/experiments/goals/${encodeURIComponent(String(goal.id))}${returnQuery}`);
117
+ return /*#__PURE__*/ _jsxs("article", {
118
+ className: "xp-tests-row",
119
+ children: [
120
+ /*#__PURE__*/ _jsxs("div", {
121
+ className: "xp-tests-row-main",
101
122
  children: [
102
- /*#__PURE__*/ _jsx("th", {
103
- scope: "row",
104
- children: /*#__PURE__*/ _jsx(Link, {
105
- href: formatAdminURL({
106
- adminRoute: surface.adminRoute,
107
- path: `/experiments/goals/${encodeURIComponent(String(goal.id))}${returnQuery}`
123
+ /*#__PURE__*/ _jsxs("div", {
124
+ className: "xp-tests-row-name",
125
+ children: [
126
+ /*#__PURE__*/ _jsx(Link, {
127
+ href: href,
128
+ children: goal.name
108
129
  }),
109
- children: goal.name
110
- })
111
- }),
112
- /*#__PURE__*/ _jsx("td", {
113
- children: surface.metricSources.find((source)=>source.id === goal.source)?.label ?? goal.source
114
- }),
115
- /*#__PURE__*/ _jsx("td", {
116
- children: goal.direction === 'decrease' ? C.decrease : C.increase
130
+ label ? /*#__PURE__*/ _jsx("span", {
131
+ className: "xp-tests-kind",
132
+ children: label[0].toUpperCase() + label.slice(1)
133
+ }) : null
134
+ ]
117
135
  }),
118
- /*#__PURE__*/ _jsx("td", {
119
- children: count
120
- }),
121
- /*#__PURE__*/ _jsx("td", {
122
- children: /*#__PURE__*/ _jsx(Pill, {
123
- children: count ? C.inUse : C.unused
124
- })
136
+ /*#__PURE__*/ _jsxs("div", {
137
+ className: "xp-tests-row-line",
138
+ children: [
139
+ /*#__PURE__*/ _jsx("span", {
140
+ children: goal.direction === 'decrease' ? C.decrease : C.increase
141
+ }),
142
+ /*#__PURE__*/ _jsx("span", {
143
+ "aria-hidden": "true",
144
+ children: "·"
145
+ }),
146
+ /*#__PURE__*/ _jsx("span", {
147
+ children: count ? goalUsedBy(count) : C.unused
148
+ })
149
+ ]
125
150
  })
126
151
  ]
127
- }, `${typeof goal.id}:${goal.id}`);
128
- })
129
- })
130
- ]
152
+ }),
153
+ /*#__PURE__*/ _jsx(Button, {
154
+ className: "xp-tests-row-action",
155
+ variant: returnToTest ? 'primary' : 'secondary',
156
+ size: "sm",
157
+ href: returnToTest ? url(`/experiments/${encodeURIComponent(returnToTest)}?${new URLSearchParams({
158
+ goal: String(goal.id)
159
+ })}`) : href,
160
+ children: returnToTest ? C.useGoal : C.open
161
+ })
162
+ ]
163
+ }, `${typeof goal.id}:${goal.id}`);
164
+ })
131
165
  })
132
- })
133
- ]
166
+ ]
167
+ })
134
168
  });
135
169
  }
@@ -1,3 +1,9 @@
1
- /** REST refusals remain the server's words, including nested field validation errors. */
2
- export declare function goalResponse(response: Response, fallback: string): Promise<unknown>;
1
+ export declare class GoalResponseError extends Error {
2
+ readonly fields: Record<string, string>;
3
+ constructor(message: string, fields: Record<string, string>);
4
+ }
5
+ /** Map REST refusals while retaining Payload's field validation details. */
6
+ export declare function goalResponse(response: Response, fallback: string, options?: {
7
+ notFound?: string;
8
+ }): Promise<unknown>;
3
9
  export declare function goalPages<T>(base: string, collection: 'xp-metrics' | 'xp-experiments', signal: AbortSignal): Promise<T[]>;
@@ -1,10 +1,18 @@
1
+ import { refusalMessage } from '../../copy/common.js';
1
2
  import { GOALS_COPY as C } from '../../copy/goals.js';
2
3
  import { payloadFieldErrors } from '../testDocument.model.js';
3
- /** REST refusals remain the server's words, including nested field validation errors. */ export async function goalResponse(response, fallback) {
4
+ export class GoalResponseError extends Error {
5
+ fields;
6
+ constructor(message, fields){
7
+ super(message), this.fields = fields;
8
+ }
9
+ }
10
+ /** Map REST refusals while retaining Payload's field validation details. */ export async function goalResponse(response, fallback, options) {
4
11
  const body = await response.json().catch(()=>undefined);
5
12
  if (!response.ok) {
6
- const messages = Object.values(payloadFieldErrors(body, '_section', fallback));
7
- throw new Error(messages.length ? messages.join(' ') : fallback);
13
+ const fields = payloadFieldErrors(body, '_section', fallback);
14
+ const first = body?.errors?.[0];
15
+ throw new GoalResponseError(refusalMessage(first?.code, response.status, Object.values(fields).join(' ') || fallback, options), fields);
8
16
  }
9
17
  if (body == null) throw new Error(fallback);
10
18
  return body;
@@ -1,15 +1,12 @@
1
+ /* Compact rows reuse surface.css's xp-tests-list / xp-tests-row classes. */
1
2
  .xp-goals { display: grid; gap: var(--xp-space-4); }
2
- .xp-goals-breadcrumb { display: flex; flex-wrap: wrap; gap: var(--xp-space-2); align-items: center; }
3
- .xp-goals-table { overflow-x: auto; }
4
- .xp-goals-table table { width: 100%; border-collapse: collapse; text-align: left; }
5
- .xp-goals-table th, .xp-goals-table td { padding: var(--xp-space-3); border-bottom: 1px solid var(--xp-border); }
3
+ .xp-goals > .xp-ui-field { max-width: 20rem; }
6
4
  .xp-goal-form { max-width: 48rem; display: grid; gap: var(--xp-space-4); }
7
- .xp-goal-form fieldset { border: 0; margin: 0; padding: 0; min-width: 0; display: grid; gap: var(--xp-space-3); }
8
- .xp-goal-form .xp-field { display: grid; gap: var(--xp-space-2); }
9
- .xp-goal-form input, .xp-goal-form select {
10
- padding: var(--xp-space-3); color: var(--xp-text); background: var(--xp-input-bg); border: 1px solid var(--xp-input-border);
11
- }
12
- .xp-goal-form input[readonly] { background: var(--xp-surface); }
13
- .xp-goal-form details { padding: var(--xp-space-4); border: 1px solid var(--xp-border); }
14
- .xp-goal-form summary { cursor: pointer; margin-bottom: var(--xp-space-3); }
15
- .xp-goal-cancel { padding: var(--xp-space-2) var(--xp-space-4); color: var(--xp-text); }
5
+ .xp-goal-card { display: grid; gap: var(--xp-space-4); }
6
+ .xp-goal-card > h2 { margin: 0; font: var(--xp-text-title); }
7
+ .xp-goal-form fieldset { border: 0; margin: 0; padding: 0; min-width: 0; display: grid; gap: var(--xp-space-4); }
8
+ .xp-goal-form input[readonly] { background: var(--xp-surface-2); }
9
+ .xp-goal-advanced { border: 1px solid var(--xp-border); border-radius: var(--xp-radius); }
10
+ .xp-goal-advanced > summary { padding: var(--xp-space-4); cursor: pointer; font: var(--xp-text-body); }
11
+ .xp-goal-advanced > summary:focus-visible { outline: 2px solid var(--xp-focus); outline-offset: 2px; }
12
+ .xp-goal-advanced-fields { display: grid; gap: var(--xp-space-4); padding: 0 var(--xp-space-4) var(--xp-space-4); }
@@ -1,17 +1,17 @@
1
- import type { DocId, ExperimentKind, Status } from '../../types/contracts.js';
1
+ import type { Arm, DocId, ExperimentKind, Status, UnitType } from '../../types/contracts.js';
2
2
  import type { SummaryResponse } from '../../types/endpoints.js';
3
+ import type { ReadinessContext } from './readiness/readiness.model.js';
3
4
  export interface HomeTest {
4
5
  id: DocId;
5
6
  name: string;
6
7
  kind: ExperimentKind;
7
8
  status: Status;
9
+ unitType: UnitType;
10
+ flagPoint?: string | null;
8
11
  analysis?: {
9
12
  plannedDurationDays?: number | null;
10
13
  } | null;
11
- arms?: Array<{
12
- key: string;
13
- label?: string;
14
- }> | null;
14
+ arms?: Arm[] | null;
15
15
  decidedAt?: string | null;
16
16
  decision?: {
17
17
  winnerArm?: string | null;
@@ -22,10 +22,10 @@ type Summary = SummaryResponse[DocId] | undefined;
22
22
  export type Health = 'red' | 'amber' | 'green';
23
23
  export declare function healthDot(test: HomeTest, summary: Summary): Health | undefined;
24
24
  export declare function groupTests(tests: readonly HomeTest[], summaries: SummaryResponse, search?: string): {
25
- label: "Draft" | "Running" | "Decided" | "Archived" | "Needs attention" | "Ready to start";
25
+ label: "Needs attention" | "Running" | "Decided" | "Draft" | "Archived" | "Ready to start";
26
26
  tests: HomeTest[];
27
27
  }[];
28
- export declare function homeCard(test: HomeTest, summary: Summary, adminRoute: string): {
28
+ export declare function homeCard(test: HomeTest, summary: Summary, adminRoute: string, flagPoints?: ReadinessContext['flagPoints']): {
29
29
  kind: "Whole page" | "Page block" | "Setting";
30
30
  pill: {
31
31
  label: string;
@@ -33,7 +33,6 @@ export declare function homeCard(test: HomeTest, summary: Summary, adminRoute: s
33
33
  tooltip: string;
34
34
  };
35
35
  line: string;
36
- headline: string | undefined;
37
36
  health: Health | undefined;
38
37
  href: string;
39
38
  action: "Continue setup" | "View results" | "Open";
@@ -1,13 +1,16 @@
1
1
  import { formatAdminURL } from 'payload/shared';
2
+ import { formatDate } from '../format/date.js';
2
3
  import { armLabel } from '../labels.js';
3
4
  import { statusPill } from '../status.model.js';
4
5
  import { decidedLine, runningLine, SURFACE_COPY } from '../copy/surface.js';
6
+ import { nextCopy } from '../copy/readiness.js';
7
+ import { READINESS } from './readiness/readiness.model.js';
8
+ import { sanitizeReadinessDetail } from '../../sanitizeReadinessDetail.js';
5
9
  export function healthDot(test, summary) {
6
10
  const code = summary?.verdict?.code;
7
- if (!code) return undefined;
8
- if (code === 'data-problem' || code === 'guardrail-failing') return 'red';
9
- if (code === 'unavailable' || code === 'collecting' && test.analysis?.plannedDurationDays != null && summary.days > test.analysis.plannedDurationDays) return 'amber';
10
- return 'green';
11
+ if (summary?.health?.some((item)=>item.code === 'version-page-unavailable') || code === 'data-problem' || code === 'guardrail-failing') return 'red';
12
+ if (summary?.health?.length || code === 'unavailable' || code === 'collecting' && test.analysis?.plannedDurationDays != null && summary.days > test.analysis.plannedDurationDays) return 'amber';
13
+ return code ? 'green' : undefined;
11
14
  }
12
15
  export function groupTests(tests, summaries, search = '') {
13
16
  const groups = SURFACE_COPY.groups.map((label)=>({
@@ -27,31 +30,39 @@ export function groupTests(tests, summaries, search = '') {
27
30
  const number = (value)=>value != null && Number.isFinite(value) ? new Intl.NumberFormat('en-US', {
28
31
  maximumFractionDigits: 0
29
32
  }).format(value) : '—';
30
- export function homeCard(test, summary, adminRoute) {
33
+ export function homeCard(test, summary, adminRoute, flagPoints) {
31
34
  const results = [
32
35
  'running',
33
36
  'stopped',
34
- 'decided'
37
+ 'decided',
38
+ 'archived'
35
39
  ].includes(test.status);
36
40
  const href = formatAdminURL({
37
41
  adminRoute,
38
42
  path: `/experiments/${encodeURIComponent(test.id)}${results ? '/results' : ''}`
39
43
  });
40
- let line = summary?.ready ? SURFACE_COPY.ready : SURFACE_COPY.next;
44
+ let line = summary?.ready === true ? SURFACE_COPY.ready : nextCopy(SURFACE_COPY.next);
45
+ const next = summary?.next;
46
+ if ((test.status === 'draft' || test.status === 'stopped') && summary?.ready !== true && next && Object.hasOwn(READINESS, next.code)) {
47
+ // Keep unexpected text out of authored copy, including responses from older servers.
48
+ const sentence = READINESS[next.code].next(sanitizeReadinessDetail(next.code, next.detail), {
49
+ ...test,
50
+ arms: test.arms ?? [],
51
+ flagPoints
52
+ });
53
+ if (sentence) line = nextCopy(sentence);
54
+ }
41
55
  if (test.status === 'running') line = runningLine(number(summary?.days), number(test.analysis?.plannedDurationDays), number(summary?.exposures));
42
56
  if (test.status === 'decided') {
43
- const date = test.decidedAt ? new Date(test.decidedAt) : undefined;
44
57
  const winner = test.decision?.winnerArm;
45
- line = decidedLine(date && Number.isFinite(date.valueOf()) ? date.toLocaleDateString('en-US', {
46
- timeZone: 'UTC'
47
- }) : '—', winner ? armLabel(winner, test.arms?.find((arm)=>arm.key === winner)?.label) : '—');
58
+ const winnerLabel = test.arms?.find((arm)=>arm.key === winner)?.label?.trim();
59
+ line = decidedLine(formatDate(test.decidedAt), winner ? winnerLabel || armLabel(winner) : '—');
48
60
  }
49
61
  if (test.status === 'archived') line = statusPill(test).label;
50
62
  return {
51
63
  kind: SURFACE_COPY.kinds[test.kind],
52
64
  pill: statusPill(test),
53
65
  line,
54
- headline: test.status === 'running' ? summary?.verdict?.headline : undefined,
55
66
  health: healthDot(test, summary),
56
67
  href,
57
68
  action: results ? SURFACE_COPY.results : test.status === 'draft' || test.status === 'stopped' ? SURFACE_COPY.continue : SURFACE_COPY.open
@@ -80,12 +80,12 @@ export function NewTest({ surface, pages }) {
80
80
  const collections = [
81
81
  ...new Set(pages?.map((page)=>page.collection))
82
82
  ];
83
- return /*#__PURE__*/ _jsxs("main", {
83
+ return /*#__PURE__*/ _jsxs("div", {
84
84
  className: "xp-new-test",
85
85
  children: [
86
86
  step === 'kind' ? /*#__PURE__*/ _jsxs(_Fragment, {
87
87
  children: [
88
- /*#__PURE__*/ _jsx("h1", {
88
+ /*#__PURE__*/ _jsx("h2", {
89
89
  children: C.question
90
90
  }),
91
91
  /*#__PURE__*/ _jsxs("div", {
@@ -131,7 +131,7 @@ export function NewTest({ surface, pages }) {
131
131
  ]
132
132
  }) : step === 'block' ? /*#__PURE__*/ _jsxs(_Fragment, {
133
133
  children: [
134
- /*#__PURE__*/ _jsx("h1", {
134
+ /*#__PURE__*/ _jsx("h2", {
135
135
  children: C.block
136
136
  }),
137
137
  /*#__PURE__*/ _jsx("p", {
@@ -169,7 +169,7 @@ export function NewTest({ surface, pages }) {
169
169
  ]
170
170
  }) : step === 'setting' ? /*#__PURE__*/ _jsxs(_Fragment, {
171
171
  children: [
172
- /*#__PURE__*/ _jsx("h1", {
172
+ /*#__PURE__*/ _jsx("h2", {
173
173
  children: C.chooseSetting
174
174
  }),
175
175
  /*#__PURE__*/ _jsxs("fieldset", {
@@ -289,7 +289,7 @@ export function NewTest({ surface, pages }) {
289
289
  void submit();
290
290
  },
291
291
  children: [
292
- /*#__PURE__*/ _jsx("h1", {
292
+ /*#__PURE__*/ _jsx("h2", {
293
293
  children: C.nameQuestion
294
294
  }),
295
295
  /*#__PURE__*/ _jsxs("label", {
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { TEST_PAGE as C } from '../../copy/testPage.js';
3
+ import { Button } from '../../../ui/Button.js';
4
+ import { previewPages } from '../../previewLinks.model.js';
5
+ import { TEST_PAGE as C, placementCount } from '../../copy/testPage.js';
4
6
  import { placementLine, slotReady } from './pageBlock.model.js';
5
7
  function Binding({ test }) {
6
8
  const placements = test.preflight?.placements ?? [];
@@ -8,16 +10,20 @@ function Binding({ test }) {
8
10
  className: "xp-page-binding",
9
11
  children: placements.length ? /*#__PURE__*/ _jsxs(_Fragment, {
10
12
  children: [
11
- placements.map((placement, index)=>/*#__PURE__*/ _jsxs("p", {
13
+ previewPages(placements).map((placement)=>/*#__PURE__*/ _jsxs("p", {
14
+ className: "xp-ui-button-group",
12
15
  children: [
13
16
  placementLine(placement),
17
+ placement.slotCount > 1 ? placementCount(placement.slotCount) : null,
14
18
  ' ',
15
- placement.editorUrl ? /*#__PURE__*/ _jsx("a", {
19
+ placement.editorUrl ? /*#__PURE__*/ _jsx(Button, {
20
+ variant: "link",
21
+ size: "sm",
16
22
  href: placement.editorUrl,
17
23
  children: C.openPage
18
24
  }) : null
19
25
  ]
20
- }, index)),
26
+ }, placement.key)),
21
27
  /*#__PURE__*/ _jsx("p", {
22
28
  children: C.editedOnPage
23
29
  })
@@ -30,32 +36,31 @@ function Binding({ test }) {
30
36
  function VersionContent({ test, armIndex }) {
31
37
  const arm = test.doc?.arms[armIndex];
32
38
  if (!arm) return null;
33
- if (arm.kind === 'holdout') return /*#__PURE__*/ _jsx("p", {
39
+ if (arm.kind === 'holdout') return /*#__PURE__*/ _jsx("span", {
40
+ className: "xp-content-chip",
34
41
  children: C.holdoutContent
35
42
  });
36
43
  const placements = test.preflight?.placements ?? [];
37
- return /*#__PURE__*/ _jsx("div", {
38
- children: placements.length ? placements.map((placement, index)=>/*#__PURE__*/ _jsxs("p", {
39
- children: [
40
- /*#__PURE__*/ _jsxs("span", {
41
- children: [
42
- placement.title,
43
- ": "
44
- ]
45
- }),
46
- /*#__PURE__*/ _jsx("span", {
47
- className: slotReady(placement, arm) ? 'xp-content-ready' : 'xp-content-empty',
48
- children: slotReady(placement, arm) ? C.ready : C.emptySlot
49
- }),
50
- ' ',
51
- placement.editorUrl ? /*#__PURE__*/ _jsx("a", {
52
- href: placement.editorUrl,
44
+ const pages = previewPages(placements);
45
+ const ready = placements.length > 0 && placements.every((placement)=>slotReady(placement, arm));
46
+ return /*#__PURE__*/ _jsxs("div", {
47
+ className: "xp-version-content",
48
+ children: [
49
+ /*#__PURE__*/ _jsx("span", {
50
+ className: `xp-content-chip ${ready ? 'xp-content-ready' : 'xp-content-empty'}`,
51
+ children: ready ? C.ready : C.empty
52
+ }),
53
+ /*#__PURE__*/ _jsx("div", {
54
+ className: "xp-ui-button-group",
55
+ children: pages.filter((page)=>page.editorUrl).map((page)=>/*#__PURE__*/ _jsx(Button, {
56
+ variant: "link",
57
+ size: "sm",
58
+ href: page.editorUrl,
59
+ "aria-label": pages.length > 1 ? `${C.editOnPage}: ${page.title}` : undefined,
53
60
  children: C.editOnPage
54
- }) : null
55
- ]
56
- }, index)) : /*#__PURE__*/ _jsx("p", {
57
- children: C.emptySlot
58
- })
61
+ }, page.key))
62
+ })
63
+ ]
59
64
  });
60
65
  }
61
66
  export const pageBlockParts = {