@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,6 +1,8 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { theme } from '../../theme.js';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId } from 'react';
4
+ import { Button } from '../../../ui/Button.js';
5
+ import { Range } from '../../../ui/Inputs.js';
4
6
  import { useEffect, useRef, useState } from 'react';
5
7
  import { TEST_PAGE as C } from '../../copy/testPage.js';
6
8
  import { ARMS_COPY, GROUP_LABELS, issueMessage } from '../../copy/arms.js';
@@ -9,14 +11,15 @@ import { allocationSentence } from '../../allocation.model.js';
9
11
  import { balanceWeight, orderedVersions, trafficShares } from './testPage.model.js';
10
12
  import { FieldErrors, ReadOnly, reasonFor, save, TextEdit } from './fields.js';
11
13
  import { Section } from './Section.js';
12
- const COLOURS = [
13
- theme.color.success,
14
- theme.color.accent,
15
- theme.color.warning,
16
- theme.color.error,
17
- theme.color.inputBorder
18
- ];
14
+ const COLOURS = {
15
+ control: 'var(--xp-series-muted)',
16
+ variantA: 'var(--xp-series-a)',
17
+ variantB: 'var(--xp-series-b)',
18
+ variantC: 'var(--xp-series-c)',
19
+ holdout: 'var(--xp-series-muted)'
20
+ };
19
21
  export function VersionsSection({ test, surface, parts }) {
22
+ const id = useId();
20
23
  const doc = test.doc;
21
24
  const [weightEdit, setWeightEdit] = useState(null);
22
25
  const releasedAllocation = useRef(null);
@@ -46,66 +49,85 @@ export function VersionsSection({ test, surface, parts }) {
46
49
  test: test,
47
50
  sectionId: "versions",
48
51
  children: [
49
- /*#__PURE__*/ _jsx("div", {
52
+ reason ? /*#__PURE__*/ _jsx("p", {
53
+ className: "xp-ui-help",
54
+ children: reason
55
+ }) : null,
56
+ /*#__PURE__*/ _jsxs("div", {
50
57
  className: "xp-version-rows",
51
- children: ordered.map(({ arm, index }, position)=>/*#__PURE__*/ _jsxs("article", {
52
- className: "xp-version-row",
58
+ children: [
59
+ /*#__PURE__*/ _jsxs("div", {
60
+ className: "xp-version-grid-head",
61
+ "aria-hidden": "true",
53
62
  children: [
54
- /*#__PURE__*/ _jsxs("h3", {
55
- children: [
56
- /*#__PURE__*/ _jsx("span", {
57
- style: {
58
- background: COLOURS[position]
59
- },
60
- className: "xp-version-colour"
61
- }),
62
- GROUP_LABELS[arm.key]
63
- ]
64
- }),
65
- /*#__PURE__*/ _jsx(TextEdit, {
66
- label: `${GROUP_LABELS[arm.key]}: ${C.label}`,
67
- value: arm.label,
68
- reason: reason,
69
- onCommit: (label)=>update((arms)=>arms.map((current)=>current.key === arm.key ? {
70
- ...current,
71
- label
72
- } : current))
73
- }),
74
- /*#__PURE__*/ _jsx(TextEdit, {
75
- label: `${GROUP_LABELS[arm.key]}: ${C.weight}`,
76
- type: "number",
77
- min: 0,
78
- max: 100,
79
- value: doc.arms[index].weight,
80
- reason: reason,
81
- validate: (raw)=>raw.trim() !== '' && Number.isInteger(Number(raw)) && Number(raw) >= 0 && Number(raw) <= 100,
82
- invalidMessage: C.invalidShare,
83
- onDraft: (value)=>setWeightEdit({
84
- key: arm.key,
85
- weight: Number(value)
86
- }),
87
- onCommit: (value)=>update((arms)=>balanceWeight(arms, arm.key, Number(value)))
88
- }),
89
- /*#__PURE__*/ _jsx(parts.VersionContent, {
90
- test: test,
91
- surface: surface,
92
- armIndex: index
63
+ /*#__PURE__*/ _jsx("span", {
64
+ children: C.label
93
65
  }),
94
- issues.rows[index]?.map((issue, i)=>/*#__PURE__*/ _jsx("p", {
95
- role: "alert",
96
- children: issueMessage(issue, totalWeight(live))
97
- }, i)),
98
- /*#__PURE__*/ _jsx(FieldErrors, {
99
- test: test,
100
- path: `arms.${index}`
66
+ /*#__PURE__*/ _jsx("span", {
67
+ children: C.weight
101
68
  }),
102
- !reason && arm.kind !== 'control' ? /*#__PURE__*/ _jsx("button", {
103
- type: "button",
104
- onClick: ()=>update((arms)=>splitEvenly(removeArm(arms, arm.key))),
105
- children: C.remove
106
- }) : null
69
+ /*#__PURE__*/ _jsx("span", {}),
70
+ /*#__PURE__*/ _jsx("span", {})
107
71
  ]
108
- }, arm.key))
72
+ }),
73
+ ordered.map(({ arm, index })=>/*#__PURE__*/ _jsxs("article", {
74
+ className: "xp-version-row",
75
+ children: [
76
+ /*#__PURE__*/ _jsx(TextEdit, {
77
+ label: `${GROUP_LABELS[arm.key]}: ${C.label}`,
78
+ value: arm.label,
79
+ reason: reason,
80
+ hideLabel: true,
81
+ disabled: Boolean(reason),
82
+ onCommit: (label)=>update((arms)=>arms.map((current)=>current.key === arm.key ? {
83
+ ...current,
84
+ label
85
+ } : current))
86
+ }),
87
+ /*#__PURE__*/ _jsx(TextEdit, {
88
+ label: `${GROUP_LABELS[arm.key]}: ${C.weight}`,
89
+ type: "number",
90
+ min: 0,
91
+ max: 100,
92
+ value: doc.arms[index].weight,
93
+ reason: reason,
94
+ hideLabel: true,
95
+ disabled: Boolean(reason),
96
+ validate: (raw)=>raw.trim() !== '' && Number.isInteger(Number(raw)) && Number(raw) >= 0 && Number(raw) <= 100,
97
+ invalidMessage: C.invalidShare,
98
+ onDraft: (value)=>setWeightEdit({
99
+ key: arm.key,
100
+ weight: Number(value)
101
+ }),
102
+ onCommit: (value)=>update((arms)=>balanceWeight(arms, arm.key, Number(value)))
103
+ }),
104
+ /*#__PURE__*/ _jsx(parts.VersionContent, {
105
+ test: test,
106
+ surface: surface,
107
+ armIndex: index
108
+ }),
109
+ /*#__PURE__*/ _jsx("div", {
110
+ className: "xp-ui-button-group xp-version-remove",
111
+ children: arm.kind !== 'control' ? /*#__PURE__*/ _jsx(Button, {
112
+ variant: "quiet",
113
+ size: "sm",
114
+ disabled: Boolean(reason),
115
+ title: reason ?? undefined,
116
+ onClick: ()=>update((arms)=>splitEvenly(removeArm(arms, arm.key))),
117
+ children: C.remove
118
+ }) : null
119
+ }),
120
+ issues.rows[index]?.map((issue, i)=>/*#__PURE__*/ _jsx("p", {
121
+ role: "alert",
122
+ children: issueMessage(issue, totalWeight(live))
123
+ }, i)),
124
+ /*#__PURE__*/ _jsx(FieldErrors, {
125
+ test: test,
126
+ path: `arms.${index}`
127
+ })
128
+ ]
129
+ }, arm.key))
130
+ ]
109
131
  }),
110
132
  /*#__PURE__*/ _jsx("div", {
111
133
  className: "xp-traffic-bar",
@@ -114,10 +136,30 @@ export function VersionsSection({ test, surface, parts }) {
114
136
  children: ordered.map(({ arm }, i)=>/*#__PURE__*/ _jsx("span", {
115
137
  style: {
116
138
  width: `${shares[i]}%`,
117
- background: COLOURS[i]
139
+ background: COLOURS[arm.key]
118
140
  }
119
141
  }, arm.key))
120
142
  }),
143
+ /*#__PURE__*/ _jsx("div", {
144
+ className: "xp-split-legend",
145
+ children: ordered.map(({ arm })=>/*#__PURE__*/ _jsxs("span", {
146
+ children: [
147
+ /*#__PURE__*/ _jsx("i", {
148
+ style: {
149
+ background: COLOURS[arm.key]
150
+ }
151
+ }),
152
+ arm.label,
153
+ " ",
154
+ arm.weight,
155
+ "%"
156
+ ]
157
+ }, arm.key))
158
+ }),
159
+ /*#__PURE__*/ _jsx("p", {
160
+ className: "xp-ui-help",
161
+ children: C.balanceHelp
162
+ }),
121
163
  issues.form.map((issue, i)=>/*#__PURE__*/ _jsx("p", {
122
164
  role: "alert",
123
165
  children: issueMessage(issue, totalWeight(live))
@@ -126,71 +168,60 @@ export function VersionsSection({ test, surface, parts }) {
126
168
  role: "alert",
127
169
  children: test.errors.arms
128
170
  }) : null,
129
- !reason ? /*#__PURE__*/ _jsxs(_Fragment, {
171
+ /*#__PURE__*/ _jsxs("div", {
172
+ className: "xp-section-actions xp-ui-button-group",
173
+ title: reason ?? undefined,
130
174
  children: [
131
- /*#__PURE__*/ _jsxs("div", {
132
- className: "xp-section-actions",
133
- children: [
134
- /*#__PURE__*/ _jsx("button", {
135
- type: "button",
136
- disabled: doc.arms.filter((arm)=>arm.kind === 'variant').length >= 3,
137
- onClick: ()=>update((arms)=>addVariant(arms, doc.kind)),
138
- children: C.addVersion
139
- }),
140
- /*#__PURE__*/ _jsx("button", {
141
- type: "button",
142
- disabled: doc.arms.some((arm)=>arm.kind === 'holdout'),
143
- onClick: ()=>update(addHoldoutArm),
144
- children: C.addHoldout
145
- }),
146
- /*#__PURE__*/ _jsx("button", {
147
- type: "button",
148
- onClick: ()=>update(splitEvenly),
149
- children: C.split
150
- })
151
- ]
175
+ /*#__PURE__*/ _jsx(Button, {
176
+ disabled: Boolean(reason) || doc.arms.filter((arm)=>arm.kind === 'variant').length >= 3,
177
+ onClick: ()=>update((arms)=>addVariant(arms, doc.kind)),
178
+ children: C.addVersion
152
179
  }),
153
- /*#__PURE__*/ _jsx("p", {
154
- children: ARMS_COPY.holdout
180
+ /*#__PURE__*/ _jsx(Button, {
181
+ disabled: Boolean(reason) || doc.arms.some((arm)=>arm.kind === 'holdout'),
182
+ onClick: ()=>update(addHoldoutArm),
183
+ children: C.addHoldout
155
184
  }),
156
- /*#__PURE__*/ _jsx("p", {
157
- children: C.balanceHelp
185
+ /*#__PURE__*/ _jsx(Button, {
186
+ disabled: Boolean(reason),
187
+ onClick: ()=>update(splitEvenly),
188
+ children: C.split
158
189
  })
159
190
  ]
191
+ }),
192
+ doc.arms.some((arm)=>arm.kind === 'holdout') ? /*#__PURE__*/ _jsx("p", {
193
+ className: "xp-ui-help",
194
+ children: ARMS_COPY.holdout
160
195
  }) : null,
161
196
  reasonFor(test, 'allocation') ? /*#__PURE__*/ _jsx(ReadOnly, {
162
197
  label: C.allocation,
163
198
  value: `${doc.allocation}%`,
164
199
  reason: reasonFor(test, 'allocation')
165
- }) : /*#__PURE__*/ _jsxs("label", {
166
- className: "xp-field",
167
- children: [
168
- C.allocation,
169
- /*#__PURE__*/ _jsx("input", {
170
- type: "range",
171
- min: 1,
172
- max: 100,
173
- value: allocation,
174
- onChange: (e)=>{
175
- releasedAllocation.current = null;
176
- setAllocation(Number(e.target.value));
177
- },
178
- onPointerUp: commitAllocation,
179
- onKeyUp: (e)=>{
180
- if ([
181
- 'ArrowLeft',
182
- 'ArrowRight',
183
- 'ArrowUp',
184
- 'ArrowDown',
185
- 'Home',
186
- 'End',
187
- 'PageUp',
188
- 'PageDown'
189
- ].includes(e.key)) commitAllocation();
190
- },
191
- onBlur: commitAllocation
192
- })
193
- ]
200
+ }) : /*#__PURE__*/ _jsx(Range, {
201
+ id: id,
202
+ label: C.allocation,
203
+ help: `${allocation}%`,
204
+ min: 1,
205
+ max: 100,
206
+ value: allocation,
207
+ onChange: (e)=>{
208
+ releasedAllocation.current = null;
209
+ setAllocation(Number(e.target.value));
210
+ },
211
+ onPointerUp: commitAllocation,
212
+ onKeyUp: (e)=>{
213
+ if ([
214
+ 'ArrowLeft',
215
+ 'ArrowRight',
216
+ 'ArrowUp',
217
+ 'ArrowDown',
218
+ 'Home',
219
+ 'End',
220
+ 'PageUp',
221
+ 'PageDown'
222
+ ].includes(e.key)) commitAllocation();
223
+ },
224
+ onBlur: commitAllocation
194
225
  }),
195
226
  /*#__PURE__*/ _jsx("p", {
196
227
  children: allocationSentence({
@@ -8,13 +8,14 @@ export declare function FieldErrors({ test, path }: {
8
8
  test: TestDocApi;
9
9
  path: string;
10
10
  }): import("react").JSX.Element;
11
- export declare function ReadOnly({ label, value, reason }: {
11
+ export declare function ReadOnly({ label, value, reason, labelAction }: {
12
12
  label: string;
13
13
  value: ReactNode;
14
14
  reason: string | null;
15
+ labelAction?: ReactNode;
15
16
  }): import("react").JSX.Element;
16
17
  /** Local typing is intentionally separate from staging: build the current group on commit. */
17
- export declare function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type, min, max, required, onDraft, validate, invalidMessage }: {
18
+ export declare function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type, min, max, required, onDraft, validate, invalidMessage, hideLabel, disabled, help }: {
18
19
  label: string;
19
20
  value: string | number;
20
21
  onCommit: (value: string) => void;
@@ -28,9 +29,12 @@ export declare function TextEdit({ label, value, onCommit, reason, multiline, pl
28
29
  required?: boolean;
29
30
  validate?: (value: string) => boolean;
30
31
  invalidMessage?: string;
32
+ hideLabel?: boolean;
33
+ disabled?: boolean;
34
+ help?: string;
31
35
  }): import("react").JSX.Element;
32
36
  export declare const positiveInteger: (raw: string) => boolean;
33
- export declare function SelectEdit({ label, value, options, reason, onCommit, readOnlyValue }: {
37
+ export declare function SelectEdit({ label, value, options, reason, onCommit, readOnlyValue, labelAction }: {
34
38
  label: string;
35
39
  value: string;
36
40
  options: readonly {
@@ -40,8 +44,9 @@ export declare function SelectEdit({ label, value, options, reason, onCommit, re
40
44
  reason?: string | null;
41
45
  onCommit: (value: string) => void;
42
46
  readOnlyValue?: string;
47
+ labelAction?: ReactNode;
43
48
  }): import("react").JSX.Element;
44
- export declare function CheckboxList({ label, value, options, reason, onToggle, readOnlyValue }: {
49
+ export declare function CheckboxList({ label, value, options, reason, onToggle, readOnlyValue, searchable }: {
45
50
  label: string;
46
51
  value: string[];
47
52
  options: readonly {
@@ -51,5 +56,7 @@ export declare function CheckboxList({ label, value, options, reason, onToggle,
51
56
  reason?: string | null;
52
57
  onToggle: (value: string, checked: boolean) => void;
53
58
  readOnlyValue?: string;
59
+ searchable?: boolean;
54
60
  }): import("react").JSX.Element;
61
+ export declare function TestName({ test }: SectionProps): import("react").JSX.Element;
55
62
  export declare function Hypothesis({ test }: SectionProps): import("react").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useState } from 'react';
4
+ import { TextInput, NumberInput, Textarea, Select, SearchInput, CheckboxList as UICheckboxList } from '../../../ui/Inputs.js';
4
5
  import { TEST_PAGE as C } from '../../copy/testPage.js';
5
6
  import { EDITABLE_BY_STATUS } from '../../../collections/experiments/editability.js';
6
7
  import { lockedReason } from '../../lockedNotice.model.js';
@@ -21,13 +22,19 @@ export function FieldErrors({ test, path }) {
21
22
  }, key))
22
23
  });
23
24
  }
24
- export function ReadOnly({ label, value, reason }) {
25
+ export function ReadOnly({ label, value, reason, labelAction }) {
25
26
  return /*#__PURE__*/ _jsxs("div", {
26
27
  className: "xp-read-only",
27
28
  title: reason ?? undefined,
28
29
  children: [
29
- /*#__PURE__*/ _jsx("strong", {
30
- children: label
30
+ /*#__PURE__*/ _jsxs("div", {
31
+ className: "xp-ui-field-label",
32
+ children: [
33
+ /*#__PURE__*/ _jsx("strong", {
34
+ children: label
35
+ }),
36
+ labelAction
37
+ ]
31
38
  }),
32
39
  /*#__PURE__*/ _jsx("p", {
33
40
  children: value
@@ -38,7 +45,7 @@ export function ReadOnly({ label, value, reason }) {
38
45
  ]
39
46
  });
40
47
  }
41
- /** Local typing is intentionally separate from staging: build the current group on commit. */ export function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type = 'text', min, max, required, onDraft, validate, invalidMessage }) {
48
+ /** Local typing is intentionally separate from staging: build the current group on commit. */ export function TextEdit({ label, value, onCommit, reason, multiline, placeholder, type = 'text', min, max, required, onDraft, validate, invalidMessage, hideLabel = false, disabled = false, help }) {
42
49
  const id = useId();
43
50
  const [draft, setDraft] = useState(String(value));
44
51
  const [error, setError] = useState(null);
@@ -55,111 +62,103 @@ export function ReadOnly({ label, value, reason }) {
55
62
  }
56
63
  onCommit(draft);
57
64
  };
58
- if (reason) return /*#__PURE__*/ _jsx(ReadOnly, {
65
+ if (reason && !disabled) return /*#__PURE__*/ _jsx(ReadOnly, {
59
66
  label: label,
60
67
  value: value === '' && placeholder ? placeholder : value,
61
68
  reason: reason
62
69
  });
63
- return /*#__PURE__*/ _jsxs(_Fragment, {
64
- children: [
65
- /*#__PURE__*/ _jsxs("label", {
66
- className: "xp-field",
67
- htmlFor: id,
68
- children: [
69
- /*#__PURE__*/ _jsx("span", {
70
- children: label
71
- }),
72
- multiline ? /*#__PURE__*/ _jsx("textarea", {
73
- id: id,
74
- value: draft,
75
- placeholder: placeholder,
76
- onChange: (e)=>{
77
- setDraft(e.target.value);
78
- onDraft?.(e.target.value);
79
- },
80
- onBlur: commit
81
- }) : /*#__PURE__*/ _jsx("input", {
82
- id: id,
83
- type: type,
84
- value: draft,
85
- min: min,
86
- max: max,
87
- required: required,
88
- placeholder: placeholder,
89
- "aria-invalid": error ? true : undefined,
90
- "aria-describedby": error ? `${id}-error` : undefined,
91
- onChange: (e)=>{
92
- setError(null);
93
- setDraft(e.target.value);
94
- onDraft?.(e.target.value);
95
- },
96
- onBlur: commit,
97
- onKeyDown: (e)=>{
98
- if (e.key === 'Enter') e.currentTarget.blur();
99
- }
100
- })
101
- ]
102
- }),
103
- error ? /*#__PURE__*/ _jsx("p", {
104
- className: "xp-field-error",
105
- id: `${id}-error`,
106
- role: "alert",
107
- children: error
108
- }) : null
109
- ]
70
+ const Input = multiline ? Textarea : type === 'number' ? NumberInput : TextInput;
71
+ return /*#__PURE__*/ _jsx("div", {
72
+ className: hideLabel ? 'xp-grid-field' : undefined,
73
+ title: reason ?? undefined,
74
+ children: /*#__PURE__*/ _jsx(Input, {
75
+ id: id,
76
+ label: label,
77
+ "aria-label": label,
78
+ value: draft,
79
+ disabled: disabled,
80
+ placeholder: placeholder,
81
+ min: min,
82
+ max: max,
83
+ required: required,
84
+ help: help,
85
+ error: error ?? undefined,
86
+ onChange: (e)=>{
87
+ setError(null);
88
+ setDraft(e.target.value);
89
+ onDraft?.(e.target.value);
90
+ },
91
+ onBlur: commit,
92
+ onKeyDown: (e)=>{
93
+ if (!multiline && e.key === 'Enter') e.currentTarget.blur();
94
+ }
95
+ })
110
96
  });
111
97
  }
112
98
  export const positiveInteger = (raw)=>raw.trim() !== '' && Number.isInteger(Number(raw)) && Number(raw) > 0;
113
- export function SelectEdit({ label, value, options, reason, onCommit, readOnlyValue }) {
99
+ export function SelectEdit({ label, value, options, reason, onCommit, readOnlyValue, labelAction }) {
114
100
  const id = useId();
115
101
  if (reason) {
116
102
  return /*#__PURE__*/ _jsx(ReadOnly, {
117
103
  label: label,
118
104
  reason: reason,
105
+ labelAction: labelAction,
119
106
  value: readOnlyValue ?? options.find((o)=>o.value === value)?.label ?? value
120
107
  });
121
108
  }
122
- return /*#__PURE__*/ _jsxs("label", {
123
- className: "xp-field",
124
- htmlFor: id,
125
- children: [
126
- /*#__PURE__*/ _jsx("span", {
127
- children: label
128
- }),
129
- /*#__PURE__*/ _jsx("select", {
130
- id: id,
131
- value: value,
132
- onChange: (e)=>onCommit(e.target.value),
133
- children: options.map((option)=>/*#__PURE__*/ _jsx("option", {
134
- value: option.value,
135
- children: option.label
136
- }, option.value))
137
- })
138
- ]
109
+ return /*#__PURE__*/ _jsx(Select, {
110
+ id: id,
111
+ label: label,
112
+ labelAction: labelAction,
113
+ value: value,
114
+ onChange: (e)=>onCommit(e.target.value),
115
+ children: options.map((option)=>/*#__PURE__*/ _jsx("option", {
116
+ value: option.value,
117
+ children: option.label
118
+ }, option.value))
139
119
  });
140
120
  }
141
- export function CheckboxList({ label, value, options, reason, onToggle, readOnlyValue }) {
121
+ export function CheckboxList({ label, value, options, reason, onToggle, readOnlyValue, searchable = false }) {
122
+ const id = useId();
123
+ const [query, setQuery] = useState('');
124
+ const visible = searchable ? options.filter((option)=>value.includes(option.value) || option.label.toLowerCase().includes(query.toLowerCase())) : options;
142
125
  if (reason) return /*#__PURE__*/ _jsx(ReadOnly, {
143
126
  label: label,
144
127
  reason: reason,
145
128
  value: readOnlyValue ?? value.map((v)=>options.find((o)=>o.value === v)?.label ?? v).join(', ')
146
129
  });
147
- return /*#__PURE__*/ _jsxs("fieldset", {
148
- className: "xp-field",
130
+ return /*#__PURE__*/ _jsx(UICheckboxList, {
131
+ id: id,
132
+ label: label,
133
+ value: value,
134
+ options: visible,
135
+ onChange: (next)=>{
136
+ const changed = options.find((option)=>next.includes(option.value) !== value.includes(option.value));
137
+ if (changed) onToggle(changed.value, next.includes(changed.value));
138
+ },
139
+ children: searchable ? /*#__PURE__*/ _jsx(SearchInput, {
140
+ id: `${id}-search`,
141
+ label: C.searchGoals,
142
+ value: query,
143
+ onChange: (event)=>setQuery(event.target.value)
144
+ }) : null
145
+ });
146
+ }
147
+ export function TestName({ test }) {
148
+ return /*#__PURE__*/ _jsxs(_Fragment, {
149
149
  children: [
150
- /*#__PURE__*/ _jsx("legend", {
151
- children: label
150
+ /*#__PURE__*/ _jsx(TextEdit, {
151
+ label: C.name,
152
+ value: test.doc?.name ?? '',
153
+ reason: reasonFor(test, 'name'),
154
+ onCommit: (value)=>save(test, 'name', ()=>({
155
+ name: value
156
+ }))
152
157
  }),
153
- options.map((option)=>/*#__PURE__*/ _jsxs("label", {
154
- children: [
155
- /*#__PURE__*/ _jsx("input", {
156
- type: "checkbox",
157
- checked: value.includes(option.value),
158
- onChange: (e)=>onToggle(option.value, e.target.checked)
159
- }),
160
- option.label
161
- ]
162
- }, option.value))
158
+ /*#__PURE__*/ _jsx(FieldErrors, {
159
+ test: test,
160
+ path: "name"
161
+ })
163
162
  ]
164
163
  });
165
164
  }
@@ -0,0 +1,3 @@
1
+ import type { SectionId } from '../contracts.js';
2
+ /** Open disclosures before scrolling, then give keyboard users a meaningful landing. */
3
+ export declare function jumpToSection(section: SectionId): void;
@@ -0,0 +1,10 @@
1
+ /** Open disclosures before scrolling, then give keyboard users a meaningful landing. */ export function jumpToSection(section) {
2
+ const target = document.getElementById(`section-${section}`);
3
+ if (target instanceof HTMLDetailsElement) target.open = true;
4
+ target?.scrollIntoView?.({
5
+ block: 'start'
6
+ });
7
+ document.getElementById(`title-${section}`)?.focus({
8
+ preventScroll: true
9
+ });
10
+ }
@@ -1,17 +1,23 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { TEST_PAGE as C } from '../../copy/testPage.js';
4
4
  import { pageTargetIssue, setPageTarget, showsPageTarget } from '../../arms/pageTarget.model.js';
5
5
  import { FieldErrors, reasonFor, save, TextEdit } from './fields.js';
6
6
  function VersionContent({ test, armIndex }) {
7
7
  const arm = test.doc?.arms[armIndex];
8
8
  if (!arm || !test.doc) return null;
9
- if (!showsPageTarget(test.doc.kind, arm.kind)) return /*#__PURE__*/ _jsx("p", {
9
+ if (!showsPageTarget(test.doc.kind, arm.kind)) return /*#__PURE__*/ _jsx("span", {
10
+ className: "xp-content-chip",
10
11
  children: C.holdoutContent
11
12
  });
12
13
  const issue = pageTargetIssue(arm.pageTarget, arm.kind);
13
- return /*#__PURE__*/ _jsxs(_Fragment, {
14
+ return /*#__PURE__*/ _jsxs("div", {
15
+ className: "xp-version-content",
14
16
  children: [
17
+ /*#__PURE__*/ _jsx("span", {
18
+ className: `xp-content-chip ${issue ? 'xp-content-empty' : 'xp-content-ready'}`,
19
+ children: issue ? C.addPageAddress : C.pageReady
20
+ }),
15
21
  /*#__PURE__*/ _jsx(TextEdit, {
16
22
  label: `${arm.label}: ${C.pageAddress}`,
17
23
  value: arm.pageTarget ?? '',