@delmaredigital/payload-experiments 0.2.0-rc.7 → 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 (267) 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/audience.d.ts +2 -1
  19. package/dist/admin/copy/audience.js +3 -2
  20. package/dist/admin/copy/common.d.ts +13 -6
  21. package/dist/admin/copy/common.js +14 -8
  22. package/dist/admin/copy/goals.d.ts +21 -6
  23. package/dist/admin/copy/goals.js +27 -6
  24. package/dist/admin/copy/readiness.d.ts +19 -0
  25. package/dist/admin/copy/readiness.js +19 -0
  26. package/dist/admin/copy/recommendation.d.ts +0 -3
  27. package/dist/admin/copy/recommendation.js +0 -3
  28. package/dist/admin/copy/results.d.ts +41 -2
  29. package/dist/admin/copy/results.js +42 -3
  30. package/dist/admin/copy/review.d.ts +1 -1
  31. package/dist/admin/copy/review.js +1 -1
  32. package/dist/admin/copy/setting.d.ts +2 -0
  33. package/dist/admin/copy/setting.js +2 -0
  34. package/dist/admin/copy/surface.d.ts +7 -4
  35. package/dist/admin/copy/surface.js +9 -4
  36. package/dist/admin/copy/testPage.d.ts +10 -0
  37. package/dist/admin/copy/testPage.js +10 -0
  38. package/dist/admin/format/date.d.ts +2 -0
  39. package/dist/admin/format/date.js +10 -0
  40. package/dist/admin/previewLinks.model.d.ts +2 -2
  41. package/dist/admin/previewLinks.model.js +5 -7
  42. package/dist/admin/results/CiChart.js +11 -21
  43. package/dist/admin/results/DataQualityPanel.d.ts +4 -2
  44. package/dist/admin/results/DataQualityPanel.js +30 -17
  45. package/dist/admin/results/DataTable.d.ts +16 -0
  46. package/dist/admin/results/DataTable.js +152 -0
  47. package/dist/admin/results/StatTile.d.ts +7 -0
  48. package/dist/admin/results/StatTile.js +22 -0
  49. package/dist/admin/results/VerdictHero.d.ts +13 -0
  50. package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
  51. package/dist/admin/results/releasePages.model.d.ts +17 -0
  52. package/dist/admin/results/releasePages.model.js +10 -0
  53. package/dist/admin/results/results.css +64 -0
  54. package/dist/admin/results/results.model.d.ts +5 -1
  55. package/dist/admin/results/results.model.js +10 -2
  56. package/dist/admin/surface/ResultsRoute.d.ts +1 -1
  57. package/dist/admin/surface/ResultsRoute.js +59 -34
  58. package/dist/admin/surface/SurfaceShell.d.ts +11 -2
  59. package/dist/admin/surface/SurfaceShell.js +49 -14
  60. package/dist/admin/surface/TestsHome.d.ts +6 -1
  61. package/dist/admin/surface/TestsHome.js +183 -112
  62. package/dist/admin/surface/TestsSurface.js +56 -16
  63. package/dist/admin/surface/goals/GoalForm.js +341 -308
  64. package/dist/admin/surface/goals/GoalsList.js +123 -89
  65. package/dist/admin/surface/goals/data.d.ts +8 -2
  66. package/dist/admin/surface/goals/data.js +11 -3
  67. package/dist/admin/surface/goals/goals.css +10 -13
  68. package/dist/admin/surface/home.model.d.ts +7 -8
  69. package/dist/admin/surface/home.model.js +23 -12
  70. package/dist/admin/surface/newTest/NewTest.js +5 -5
  71. package/dist/admin/surface/pageBlock/parts.js +31 -26
  72. package/dist/admin/surface/readiness/ActionDialogs.js +43 -14
  73. package/dist/admin/surface/readiness/Banner.d.ts +3 -0
  74. package/dist/admin/surface/readiness/Banner.js +58 -33
  75. package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
  76. package/dist/admin/surface/readiness/SaveStatus.js +6 -3
  77. package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
  78. package/dist/admin/surface/readiness/readiness.model.js +18 -9
  79. package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
  80. package/dist/admin/surface/readiness/useTransition.js +2 -1
  81. package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
  82. package/dist/admin/surface/results/ReleasePages.js +116 -0
  83. package/dist/admin/surface/results/ResultsBody.d.ts +11 -1
  84. package/dist/admin/surface/results/ResultsBody.js +276 -105
  85. package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
  86. package/dist/admin/surface/results/ResultsHeader.js +177 -0
  87. package/dist/admin/surface/results/recommendation.model.js +2 -6
  88. package/dist/admin/surface/results/useResults.d.ts +21 -3
  89. package/dist/admin/surface/results/useResults.js +2 -1
  90. package/dist/admin/surface/setting/parts.js +75 -88
  91. package/dist/admin/surface/setting/setting.css +3 -15
  92. package/dist/admin/surface/surface.css +62 -47
  93. package/dist/admin/surface/test/AdvancedSection.js +55 -48
  94. package/dist/admin/surface/test/GoalSection.js +39 -32
  95. package/dist/admin/surface/test/LengthSection.js +46 -34
  96. package/dist/admin/surface/test/Rail.d.ts +4 -0
  97. package/dist/admin/surface/test/Rail.js +83 -0
  98. package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
  99. package/dist/admin/surface/test/ReadinessBar.js +33 -0
  100. package/dist/admin/surface/test/Section.d.ts +1 -1
  101. package/dist/admin/surface/test/Section.js +36 -16
  102. package/dist/admin/surface/test/TargetingSection.js +168 -142
  103. package/dist/admin/surface/test/TestPage.d.ts +2 -1
  104. package/dist/admin/surface/test/TestPage.js +146 -68
  105. package/dist/admin/surface/test/VersionsSection.js +151 -120
  106. package/dist/admin/surface/test/fields.d.ts +11 -4
  107. package/dist/admin/surface/test/fields.js +84 -85
  108. package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
  109. package/dist/admin/surface/test/jumpToSection.js +10 -0
  110. package/dist/admin/surface/test/pageParts.js +9 -3
  111. package/dist/admin/surface/test/testPage.css +81 -14
  112. package/dist/admin/surface/useTestDocument.js +10 -4
  113. package/dist/admin/targeting/targeting.model.d.ts +1 -3
  114. package/dist/admin/targeting/targeting.model.js +3 -8
  115. package/dist/admin/theme.css +115 -36
  116. package/dist/admin/theme.d.ts +38 -0
  117. package/dist/admin/theme.js +38 -1
  118. package/dist/admin/useApi.d.ts +17 -2
  119. package/dist/admin/views/ExperimentsRootView.js +12 -2
  120. package/dist/collections/experiments/config.js +25 -0
  121. package/dist/collections/experiments/lockHook.js +3 -1
  122. package/dist/collections/identities.js +6 -1
  123. package/dist/collections/metrics.js +7 -0
  124. package/dist/collections/metricsGuards.d.ts +9 -0
  125. package/dist/collections/metricsGuards.js +33 -0
  126. package/dist/contentDone.d.ts +4 -0
  127. package/dist/contentDone.js +5 -0
  128. package/dist/db/identities.d.ts +8 -7
  129. package/dist/db/identities.js +9 -6
  130. package/dist/db/metricUsage.d.ts +19 -0
  131. package/dist/db/metricUsage.js +54 -0
  132. package/dist/db/names.d.ts +5 -0
  133. package/dist/db/names.js +5 -0
  134. package/dist/db/publishedContent.d.ts +10 -0
  135. package/dist/db/publishedContent.js +43 -0
  136. package/dist/db/results.d.ts +2 -0
  137. package/dist/db/results.js +6 -2
  138. package/dist/db/snapshots.d.ts +18 -6
  139. package/dist/db/snapshots.js +23 -7
  140. package/dist/db/sweep.js +11 -6
  141. package/dist/endpoints/index.js +19 -1
  142. package/dist/endpoints/results.js +5 -0
  143. package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
  144. package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
  145. package/dist/endpoints/summary.js +20 -2
  146. package/dist/endpoints/transitions.d.ts +15 -0
  147. package/dist/endpoints/transitions.js +36 -0
  148. package/dist/errors.d.ts +8 -0
  149. package/dist/errors.js +28 -0
  150. package/dist/exports/index.d.ts +2 -1
  151. package/dist/exports/index.js +1 -0
  152. package/dist/exports/puckEditor.d.ts +1 -0
  153. package/dist/exports/puckEditor.js +1 -0
  154. package/dist/exports/rsc.d.ts +5 -1
  155. package/dist/exports/rsc.js +2 -0
  156. package/dist/hooks/pageGuard.d.ts +5 -0
  157. package/dist/hooks/pageGuard.js +121 -0
  158. package/dist/log.js +6 -3
  159. package/dist/manifest/compile.d.ts +5 -0
  160. package/dist/manifest/compile.js +5 -1
  161. package/dist/options.js +23 -0
  162. package/dist/plugin.js +26 -2
  163. package/dist/puck/copy.d.ts +1 -1
  164. package/dist/puck/copy.js +1 -1
  165. package/dist/puck/editor/editor.chrome.css +235 -0
  166. package/dist/puck/editor/editor.css +241 -43
  167. package/dist/results/aggregate.js +4 -3
  168. package/dist/results/analyse.d.ts +4 -1
  169. package/dist/results/analyse.js +27 -11
  170. package/dist/results/cuped.js +3 -2
  171. package/dist/results/identityCredit.d.ts +15 -7
  172. package/dist/results/identityCredit.js +32 -33
  173. package/dist/results/rolloutUnits.js +2 -1
  174. package/dist/results/snapshot.js +8 -2
  175. package/dist/results/srm.js +2 -1
  176. package/dist/sanitizeReadinessDetail.d.ts +7 -0
  177. package/dist/sanitizeReadinessDetail.js +54 -0
  178. package/dist/server/linkIdentity.d.ts +10 -1
  179. package/dist/server/linkIdentity.js +17 -15
  180. package/dist/server/metadata.d.ts +5 -0
  181. package/dist/server/metadata.js +9 -1
  182. package/dist/server/runResults.d.ts +3 -2
  183. package/dist/server/runResults.js +121 -31
  184. package/dist/targeting/values.d.ts +4 -0
  185. package/dist/targeting/values.js +10 -0
  186. package/dist/transitions/addMetric.js +8 -0
  187. package/dist/transitions/archive.js +2 -3
  188. package/dist/transitions/checkStart.d.ts +6 -3
  189. package/dist/transitions/checkStart.js +15 -0
  190. package/dist/transitions/contentFields.d.ts +7 -0
  191. package/dist/transitions/contentFields.js +20 -0
  192. package/dist/transitions/decide.js +6 -1
  193. package/dist/transitions/dismissContentEntry.d.ts +8 -0
  194. package/dist/transitions/dismissContentEntry.js +45 -0
  195. package/dist/transitions/makeContent.d.ts +5 -2
  196. package/dist/transitions/makeContent.js +217 -118
  197. package/dist/transitions/phaseRecord.d.ts +8 -2
  198. package/dist/transitions/placements.d.ts +1 -0
  199. package/dist/transitions/placements.js +4 -0
  200. package/dist/transitions/preflightData.d.ts +1 -1
  201. package/dist/transitions/preflightData.js +42 -4
  202. package/dist/transitions/releasePages.d.ts +8 -0
  203. package/dist/transitions/releasePages.js +26 -0
  204. package/dist/transitions/restoreContentEntry.d.ts +8 -0
  205. package/dist/transitions/restoreContentEntry.js +47 -0
  206. package/dist/transitions/start.js +30 -5
  207. package/dist/types/contracts.d.ts +14 -0
  208. package/dist/types/endpoints.d.ts +34 -3
  209. package/dist/types/index.d.ts +1 -0
  210. package/dist/types/index.js +1 -0
  211. package/dist/types/reasons.d.ts +3 -0
  212. package/dist/types/reasons.js +1 -0
  213. package/dist/types/snapshot.d.ts +6 -1
  214. package/dist/ui/Banner.d.ts +2 -0
  215. package/dist/ui/Banner.js +2 -0
  216. package/dist/ui/Breadcrumb.d.ts +7 -0
  217. package/dist/ui/Breadcrumb.js +25 -0
  218. package/dist/ui/Button.d.ts +15 -0
  219. package/dist/ui/Button.js +32 -0
  220. package/dist/ui/Card.d.ts +12 -0
  221. package/dist/ui/Card.js +40 -0
  222. package/dist/ui/Checkbox.d.ts +2 -0
  223. package/dist/ui/Checkbox.js +2 -0
  224. package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
  225. package/dist/{admin/components → ui}/ChipsInput.js +20 -12
  226. package/dist/ui/Dialog.d.ts +13 -0
  227. package/dist/ui/Dialog.js +85 -0
  228. package/dist/ui/EmptyState.d.ts +2 -0
  229. package/dist/ui/EmptyState.js +2 -0
  230. package/dist/ui/Feedback.d.ts +24 -0
  231. package/dist/ui/Feedback.js +40 -0
  232. package/dist/ui/Inputs.d.ts +34 -0
  233. package/dist/ui/Inputs.js +225 -0
  234. package/dist/ui/NumberInput.d.ts +2 -0
  235. package/dist/ui/NumberInput.js +2 -0
  236. package/dist/ui/Range.d.ts +2 -0
  237. package/dist/ui/Range.js +2 -0
  238. package/dist/ui/SearchInput.d.ts +2 -0
  239. package/dist/ui/SearchInput.js +2 -0
  240. package/dist/ui/SectionCard.d.ts +2 -0
  241. package/dist/ui/SectionCard.js +2 -0
  242. package/dist/ui/Select.d.ts +2 -0
  243. package/dist/ui/Select.js +2 -0
  244. package/dist/ui/StatusChip.d.ts +2 -0
  245. package/dist/ui/StatusChip.js +2 -0
  246. package/dist/ui/Tabs.d.ts +14 -0
  247. package/dist/ui/Tabs.js +54 -0
  248. package/dist/ui/TextInput.d.ts +2 -0
  249. package/dist/ui/TextInput.js +2 -0
  250. package/dist/ui/Textarea.d.ts +2 -0
  251. package/dist/ui/Textarea.js +2 -0
  252. package/dist/ui/Toggle.d.ts +2 -0
  253. package/dist/ui/Toggle.js +2 -0
  254. package/dist/ui/accent.d.ts +21 -0
  255. package/dist/ui/accent.js +81 -0
  256. package/dist/ui/index.d.ts +9 -0
  257. package/dist/ui/index.js +9 -0
  258. package/dist/ui/ui.css +92 -0
  259. package/package.json +1 -1
  260. package/dist/admin/components/chipsInput.css +0 -11
  261. package/dist/admin/results/ArmTable.d.ts +0 -8
  262. package/dist/admin/results/ArmTable.js +0 -113
  263. package/dist/admin/results/VerdictCard.d.ts +0 -9
  264. package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
  265. package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
  266. package/dist/admin/surface/test/TestHeader.d.ts +0 -4
  267. package/dist/admin/surface/test/TestHeader.js +0 -70
@@ -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 = {