@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,6 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { TEST_PAGE } from '../../copy/testPage.js';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
3
  import { ADVANCED_COPY as C } from '../../copy/advanced.js';
5
4
  import { exposureOptions } from './testPage.model.js';
6
5
  import { FieldErrors, positiveInteger, ReadOnly, reasonFor, save, SelectEdit, TextEdit } from './fields.js';
@@ -40,31 +39,33 @@ export function AdvancedSection({ test, surface }) {
40
39
  return /*#__PURE__*/ _jsx(Section, {
41
40
  test: test,
42
41
  sectionId: "advanced",
43
- children: /*#__PURE__*/ _jsxs("details", {
42
+ children: /*#__PURE__*/ _jsxs("div", {
43
+ className: "xp-field-grid",
44
44
  children: [
45
- /*#__PURE__*/ _jsx("summary", {
46
- children: TEST_PAGE.sections.advanced
47
- }),
48
- /*#__PURE__*/ _jsx(SelectEdit, {
49
- label: C.exposurePolicy.label,
50
- value: doc.exposurePolicy,
51
- reason: reasonFor(test, 'exposurePolicy'),
52
- options: exposureOptions(doc.kind).map((value)=>({
53
- value,
54
- label: C.policies[value]
55
- })),
56
- onCommit: (value)=>save(test, 'exposurePolicy', ()=>({
57
- exposurePolicy: value
58
- }))
59
- }),
60
- /*#__PURE__*/ _jsx("p", {
61
- children: C.exposurePolicy.help
62
- }),
63
- /*#__PURE__*/ _jsx(FieldErrors, {
64
- test: test,
65
- path: "exposurePolicy"
45
+ /*#__PURE__*/ _jsxs("div", {
46
+ children: [
47
+ /*#__PURE__*/ _jsx(SelectEdit, {
48
+ label: C.exposurePolicy.label,
49
+ value: doc.exposurePolicy,
50
+ reason: reasonFor(test, 'exposurePolicy'),
51
+ options: exposureOptions(doc.kind).map((value)=>({
52
+ value,
53
+ label: C.policies[value]
54
+ })),
55
+ onCommit: (value)=>save(test, 'exposurePolicy', ()=>({
56
+ exposurePolicy: value
57
+ }))
58
+ }),
59
+ /*#__PURE__*/ _jsx("p", {
60
+ children: C.exposurePolicy.help
61
+ }),
62
+ /*#__PURE__*/ _jsx(FieldErrors, {
63
+ test: test,
64
+ path: "exposurePolicy"
65
+ })
66
+ ]
66
67
  }),
67
- surface.isAdmin ? /*#__PURE__*/ _jsxs(_Fragment, {
68
+ surface.isAdmin ? /*#__PURE__*/ _jsxs("div", {
68
69
  children: [
69
70
  /*#__PURE__*/ _jsx(SelectEdit, {
70
71
  label: C.method.label,
@@ -91,28 +92,32 @@ export function AdvancedSection({ test, surface }) {
91
92
  })
92
93
  ]
93
94
  }) : null,
94
- /*#__PURE__*/ _jsx(SelectEdit, {
95
- label: C.cuped.label,
96
- value: doc.analysis?.cuped ?? 'auto',
97
- reason: reason,
98
- options: [
99
- {
100
- value: 'auto',
101
- label: C.auto
102
- },
103
- {
104
- value: 'off',
105
- label: C.off
106
- }
107
- ],
108
- onCommit: (value)=>change('cuped', value)
109
- }),
110
- /*#__PURE__*/ _jsx("p", {
111
- children: C.cuped.help
112
- }),
113
- /*#__PURE__*/ _jsx(FieldErrors, {
114
- test: test,
115
- path: "analysis.cuped"
95
+ /*#__PURE__*/ _jsxs("div", {
96
+ children: [
97
+ /*#__PURE__*/ _jsx(SelectEdit, {
98
+ label: C.cuped.label,
99
+ value: doc.analysis?.cuped ?? 'auto',
100
+ reason: reason,
101
+ options: [
102
+ {
103
+ value: 'auto',
104
+ label: C.auto
105
+ },
106
+ {
107
+ value: 'off',
108
+ label: C.off
109
+ }
110
+ ],
111
+ onCommit: (value)=>change('cuped', value)
112
+ }),
113
+ /*#__PURE__*/ _jsx("p", {
114
+ children: C.cuped.help
115
+ }),
116
+ /*#__PURE__*/ _jsx(FieldErrors, {
117
+ test: test,
118
+ path: "analysis.cuped"
119
+ })
120
+ ]
116
121
  }),
117
122
  numbers.map(([field, fallback, min])=>/*#__PURE__*/ _jsxs("div", {
118
123
  children: [
@@ -139,7 +144,9 @@ export function AdvancedSection({ test, surface }) {
139
144
  children: [
140
145
  /*#__PURE__*/ _jsx(ReadOnly, {
141
146
  label: C[field].label,
142
- value: typeof doc[field] === 'string' || typeof doc[field] === 'number' ? String(doc[field]) : C.none,
147
+ value: field === 'slug' ? /*#__PURE__*/ _jsx("code", {
148
+ children: String(doc.slug)
149
+ }) : typeof doc[field] === 'string' || typeof doc[field] === 'number' ? String(doc[field]) : C.none,
143
150
  reason: reasonFor(test, field)
144
151
  }),
145
152
  /*#__PURE__*/ _jsx("p", {
@@ -1,15 +1,15 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button } from '../../../ui/Button.js';
3
4
  import { useEffect, useState } from 'react';
4
5
  import { formatAdminURL } from 'payload/shared';
5
6
  import { TEST_PAGE as C } from '../../copy/testPage.js';
6
7
  import { metricSourceOptions } from '../../metricSource.model.js';
7
8
  import { reviewView } from '../../review/review.model.js';
8
- import { CheckboxList, FieldErrors, reasonFor, save, SelectEdit, TextEdit } from './fields.js';
9
+ import { CheckboxList, FieldErrors, reasonFor, save, SelectEdit } from './fields.js';
9
10
  import { Section } from './Section.js';
10
11
  export function GoalSection({ test, surface }) {
11
12
  const [metrics, setMetrics] = useState([]);
12
- const [query, setQuery] = useState('');
13
13
  const [failed, setFailed] = useState(false);
14
14
  const [loaded, setLoaded] = useState(false);
15
15
  const [attempt, setAttempt] = useState(0);
@@ -17,6 +17,8 @@ export function GoalSection({ test, surface }) {
17
17
  const controller = new AbortController();
18
18
  void (async ()=>{
19
19
  try {
20
+ await Promise.resolve();
21
+ if (controller.signal.aborted) return;
20
22
  const all = [];
21
23
  let page = 1;
22
24
  for(;;){
@@ -55,13 +57,13 @@ export function GoalSection({ test, surface }) {
55
57
  url.searchParams.delete('goal');
56
58
  window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}${url.hash}`);
57
59
  const goal = metrics.find((metric)=>String(metric.id) === goalId);
58
- if (!reason && doc.metrics?.primary == null && goal) {
60
+ if (!reason && goal) {
59
61
  save(test, 'metrics', ()=>({
60
62
  metrics: {
61
63
  ...doc.metrics,
62
64
  primary: goal.id,
63
- guardrails: doc.metrics?.guardrails?.filter((id)=>id !== goal.id),
64
- secondary: doc.metrics?.secondary?.filter((id)=>id !== goal.id)
65
+ guardrails: doc.metrics?.guardrails?.filter((id)=>String(id) !== goalId),
66
+ secondary: doc.metrics?.secondary?.filter((id)=>String(id) !== goalId)
65
67
  }
66
68
  }));
67
69
  }
@@ -83,7 +85,9 @@ export function GoalSection({ test, surface }) {
83
85
  ...doc.metrics?.guardrails ?? [],
84
86
  ...doc.metrics?.secondary ?? []
85
87
  ].filter((id)=>id != null).map(String);
86
- const visible = options.filter((option)=>chosen.includes(option.value) || option.label.toLowerCase().includes(query.toLowerCase()));
88
+ const visible = [
89
+ ...options
90
+ ];
87
91
  for (const id of chosen)if (!visible.some((option)=>option.value === id)) visible.push({
88
92
  value: id,
89
93
  label: unknownGoal
@@ -137,27 +141,37 @@ export function GoalSection({ test, surface }) {
137
141
  test: test,
138
142
  sectionId: "win",
139
143
  children: [
140
- !reason ? /*#__PURE__*/ _jsx(TextEdit, {
141
- label: C.searchGoals,
142
- value: query,
143
- onDraft: setQuery,
144
- onCommit: setQuery
145
- }) : null,
146
144
  failed ? /*#__PURE__*/ _jsxs("p", {
147
145
  role: "alert",
148
146
  children: [
149
147
  C.goalsError,
150
148
  " ",
151
- /*#__PURE__*/ _jsx("button", {
152
- type: "button",
153
- onClick: ()=>setAttempt((n)=>n + 1),
154
- children: C.retry
149
+ /*#__PURE__*/ _jsx("span", {
150
+ className: "xp-ui-button-group",
151
+ children: /*#__PURE__*/ _jsx(Button, {
152
+ onClick: ()=>setAttempt((n)=>n + 1),
153
+ children: C.retry
154
+ })
155
155
  })
156
156
  ]
157
157
  }) : null,
158
158
  /*#__PURE__*/ _jsx(SelectEdit, {
159
+ labelAction: /*#__PURE__*/ _jsx("span", {
160
+ className: "xp-ui-button-group",
161
+ children: /*#__PURE__*/ _jsx(Button, {
162
+ variant: "link",
163
+ size: "sm",
164
+ href: formatAdminURL({
165
+ adminRoute: surface.adminRoute,
166
+ path: `/experiments/goals${!reason ? `?${new URLSearchParams({
167
+ return: String(doc.id)
168
+ })}` : ''}`
169
+ }),
170
+ children: C.manageGoals
171
+ })
172
+ }),
159
173
  label: C.goal,
160
- value: String(doc.metrics?.primary ?? ''),
174
+ value: loaded && !primary ? '' : String(doc.metrics?.primary ?? ''),
161
175
  options: [
162
176
  {
163
177
  value: '',
@@ -174,20 +188,18 @@ export function GoalSection({ test, surface }) {
174
188
  /*#__PURE__*/ _jsx("p", {
175
189
  children: C.goalHelp
176
190
  }),
177
- /*#__PURE__*/ _jsx("a", {
178
- href: formatAdminURL({
179
- adminRoute: surface.adminRoute,
180
- path: `/experiments/goals${!reason ? `?${new URLSearchParams({
181
- return: String(doc.id)
182
- })}` : ''}`
183
- }),
184
- children: C.manageGoals
185
- }),
191
+ baseline ? /*#__PURE__*/ _jsxs("p", {
192
+ children: [
193
+ primary?.name ? `${primary.name}: ` : '',
194
+ baseline
195
+ ]
196
+ }) : null,
186
197
  /*#__PURE__*/ _jsx(FieldErrors, {
187
198
  test: test,
188
199
  path: "metrics.primary"
189
200
  }),
190
201
  /*#__PURE__*/ _jsx(CheckboxList, {
202
+ searchable: metrics.length > 8,
191
203
  label: C.guardrails,
192
204
  value: (doc.metrics?.guardrails ?? []).map(String),
193
205
  options: roleOptions('guardrails'),
@@ -203,6 +215,7 @@ export function GoalSection({ test, surface }) {
203
215
  path: "metrics.guardrails"
204
216
  }),
205
217
  /*#__PURE__*/ _jsx(CheckboxList, {
218
+ searchable: metrics.length > 8,
206
219
  label: C.secondary,
207
220
  value: (doc.metrics?.secondary ?? []).map(String),
208
221
  options: roleOptions('secondary'),
@@ -214,12 +227,6 @@ export function GoalSection({ test, surface }) {
214
227
  test: test,
215
228
  path: "metrics.secondary"
216
229
  }),
217
- baseline ? /*#__PURE__*/ _jsxs("p", {
218
- children: [
219
- primary?.name ? `${primary.name}: ` : '',
220
- baseline
221
- ]
222
- }) : null,
223
230
  test.errors.metrics ? /*#__PURE__*/ _jsx("p", {
224
231
  role: "alert",
225
232
  children: test.errors.metrics
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button } from '../../../ui/Button.js';
3
4
  import { TEST_PAGE as C, suggestedWeeks, weeksHint } from '../../copy/testPage.js';
4
5
  import { durationSuggestion } from './testPage.model.js';
5
6
  import { FieldErrors, positiveInteger, reasonFor, save, TextEdit } from './fields.js';
@@ -22,30 +23,57 @@ export function LengthSection({ test }) {
22
23
  test: test,
23
24
  sectionId: "length",
24
25
  children: [
25
- /*#__PURE__*/ _jsx(TextEdit, {
26
- label: required ? `${C.reach} ${C.required}` : C.reach,
27
- value: doc.analysis?.reachPerDay ?? '',
28
- type: "number",
29
- min: 1,
30
- reason: reason,
31
- required: required,
32
- onCommit: (value)=>change('reachPerDay', value)
33
- }),
34
- /*#__PURE__*/ _jsx("p", {
35
- children: C.reachHelp
36
- }),
37
- /*#__PURE__*/ _jsx(FieldErrors, {
38
- test: test,
39
- path: "analysis.reachPerDay"
26
+ /*#__PURE__*/ _jsxs("div", {
27
+ className: "xp-field-grid",
28
+ children: [
29
+ /*#__PURE__*/ _jsxs("div", {
30
+ children: [
31
+ /*#__PURE__*/ _jsx(TextEdit, {
32
+ label: required ? `${C.reach} ${C.required}` : C.reach,
33
+ value: doc.analysis?.reachPerDay ?? '',
34
+ type: "number",
35
+ min: 1,
36
+ reason: reason,
37
+ required: required,
38
+ onCommit: (value)=>change('reachPerDay', value)
39
+ }),
40
+ /*#__PURE__*/ _jsx("p", {
41
+ className: "xp-ui-help",
42
+ children: C.reachHelp
43
+ }),
44
+ /*#__PURE__*/ _jsx(FieldErrors, {
45
+ test: test,
46
+ path: "analysis.reachPerDay"
47
+ })
48
+ ]
49
+ }),
50
+ /*#__PURE__*/ _jsxs("div", {
51
+ children: [
52
+ /*#__PURE__*/ _jsx(TextEdit, {
53
+ label: C.duration,
54
+ value: doc.analysis?.plannedDurationDays ?? 28,
55
+ type: "number",
56
+ min: 1,
57
+ reason: reason,
58
+ validate: positiveInteger,
59
+ onCommit: (value)=>change('plannedDurationDays', value),
60
+ help: weeksHint(doc.analysis?.plannedDurationDays ?? 28)
61
+ }),
62
+ /*#__PURE__*/ _jsx(FieldErrors, {
63
+ test: test,
64
+ path: "analysis.plannedDurationDays"
65
+ })
66
+ ]
67
+ })
68
+ ]
40
69
  }),
41
70
  suggestion ? /*#__PURE__*/ _jsxs("div", {
42
- className: "xp-duration-suggestion",
71
+ className: "xp-duration-suggestion xp-ui-button-group",
43
72
  children: [
44
73
  /*#__PURE__*/ _jsx("p", {
45
74
  children: suggestedWeeks(suggestion.weeks)
46
75
  }),
47
- !reason ? /*#__PURE__*/ _jsx("button", {
48
- type: "button",
76
+ !reason ? /*#__PURE__*/ _jsx(Button, {
49
77
  onClick: ()=>save(test, 'analysis', ()=>({
50
78
  analysis: {
51
79
  ...test.doc.analysis,
@@ -62,22 +90,6 @@ export function LengthSection({ test }) {
62
90
  }) : !test.preflight?.estimate ? /*#__PURE__*/ _jsx("p", {
63
91
  children: doc.analysis?.reachPerDay == null ? C.noSuggestion : C.chooseGoalSuggestion
64
92
  }) : null,
65
- /*#__PURE__*/ _jsx(TextEdit, {
66
- label: C.duration,
67
- value: doc.analysis?.plannedDurationDays ?? 28,
68
- type: "number",
69
- min: 1,
70
- reason: reason,
71
- validate: positiveInteger,
72
- onCommit: (value)=>change('plannedDurationDays', value)
73
- }),
74
- /*#__PURE__*/ _jsx("p", {
75
- children: weeksHint(doc.analysis?.plannedDurationDays ?? 28)
76
- }),
77
- /*#__PURE__*/ _jsx(FieldErrors, {
78
- test: test,
79
- path: "analysis.plannedDurationDays"
80
- }),
81
93
  test.errors.analysis ? /*#__PURE__*/ _jsx("p", {
82
94
  role: "alert",
83
95
  children: test.errors.analysis
@@ -0,0 +1,4 @@
1
+ import type { TestDocApi } from '../contracts.js';
2
+ export declare function Rail({ test }: {
3
+ test: TestDocApi;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,83 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { SECTION_ORDER } from '../contracts.js';
5
+ import { readinessParts } from '../readiness/parts.js';
6
+ import { TEST_PAGE as C } from '../../copy/testPage.js';
7
+ import { jumpToSection } from './jumpToSection.js';
8
+ export function Rail({ test }) {
9
+ const [active, setActive] = useState('what');
10
+ const ref = useRef(null);
11
+ const done = readinessParts.sectionDone(test);
12
+ useEffect(()=>{
13
+ if (typeof IntersectionObserver === 'undefined') return;
14
+ const visible = new Map();
15
+ const observer = new IntersectionObserver((entries)=>{
16
+ for (const entry of entries)visible.set(entry.target.id.replace('section-', ''), entry.isIntersecting ? entry.intersectionRatio : 0);
17
+ const best = SECTION_ORDER.filter((id)=>(visible.get(id) ?? 0) > 0).sort((a, b)=>(visible.get(b) ?? 0) - (visible.get(a) ?? 0))[0];
18
+ // Retain the last clicked/visible item when nothing intersects.
19
+ if (best) setActive(best);
20
+ }, {
21
+ rootMargin: '-10% 0px -60% 0px',
22
+ threshold: [
23
+ 0,
24
+ .1,
25
+ .25,
26
+ .5,
27
+ .75,
28
+ 1
29
+ ]
30
+ });
31
+ for (const id of SECTION_ORDER){
32
+ const section = document.getElementById(`section-${id}`);
33
+ if (section) observer.observe(section);
34
+ }
35
+ return ()=>observer.disconnect();
36
+ }, []);
37
+ useEffect(()=>{
38
+ if (window.matchMedia?.('(max-width: 960px)').matches) {
39
+ ref.current?.querySelector('[aria-current]')?.scrollIntoView?.({
40
+ block: 'nearest',
41
+ inline: 'nearest'
42
+ });
43
+ }
44
+ }, [
45
+ active
46
+ ]);
47
+ return /*#__PURE__*/ _jsx("nav", {
48
+ ref: ref,
49
+ className: "xp-test-rail",
50
+ "aria-label": C.navigation,
51
+ children: /*#__PURE__*/ _jsx("ol", {
52
+ className: "xp-test-rail-list",
53
+ children: SECTION_ORDER.map((id)=>/*#__PURE__*/ _jsx("li", {
54
+ children: /*#__PURE__*/ _jsxs("a", {
55
+ href: `#section-${id}`,
56
+ className: `xp-test-rail-item${id === 'advanced' ? ' xp-test-rail-item--advanced' : ''}${active === id ? ' is-active' : ''}`,
57
+ "aria-current": active === id ? 'location' : undefined,
58
+ onClick: (event)=>{
59
+ event.preventDefault();
60
+ setActive(id);
61
+ jumpToSection(id);
62
+ },
63
+ children: [
64
+ id !== 'advanced' ? /*#__PURE__*/ _jsxs(_Fragment, {
65
+ children: [
66
+ /*#__PURE__*/ _jsx("span", {
67
+ "aria-hidden": "true",
68
+ className: done[id] ? 'xp-test-rail-mark xp-test-rail-mark--done' : 'xp-test-rail-mark',
69
+ children: done[id] ? C.completeMark : C.incompleteMark
70
+ }),
71
+ /*#__PURE__*/ _jsx("span", {
72
+ className: "xp-test-sr-only",
73
+ children: done[id] ? C.completePrefix : C.incompletePrefix
74
+ })
75
+ ]
76
+ }) : null,
77
+ C.sections[id]
78
+ ]
79
+ })
80
+ }, id))
81
+ })
82
+ });
83
+ }
@@ -0,0 +1,5 @@
1
+ import type { SectionProps } from '../contracts.js';
2
+ /** Measure wrapping actions/copy so the final section always clears the fixed bar. */
3
+ export declare function ReadinessBar({ onHeightChange, ...props }: SectionProps & {
4
+ onHeightChange: (height: number) => void;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useLayoutEffect, useRef } from 'react';
4
+ import { READINESS_COPY as C } from '../../copy/readiness.js';
5
+ import { ReadinessContent } from '../readiness/Banner.js';
6
+ /** Measure wrapping actions/copy so the final section always clears the fixed bar. */ export function ReadinessBar({ onHeightChange, ...props }) {
7
+ const ref = useRef(null);
8
+ useLayoutEffect(()=>{
9
+ const node = ref.current;
10
+ if (!node) return;
11
+ const measure = ()=>onHeightChange(node.getBoundingClientRect().height);
12
+ measure();
13
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);
14
+ observer?.observe(node);
15
+ window.addEventListener('resize', measure);
16
+ return ()=>{
17
+ observer?.disconnect();
18
+ window.removeEventListener('resize', measure);
19
+ };
20
+ }, [
21
+ onHeightChange
22
+ ]);
23
+ return /*#__PURE__*/ _jsx("div", {
24
+ ref: ref,
25
+ className: "xp-test-readiness-bar",
26
+ role: "region",
27
+ "aria-label": C.label,
28
+ children: props.test.doc ? /*#__PURE__*/ _jsx(ReadinessContent, {
29
+ ...props,
30
+ bar: true
31
+ }, `${props.test.doc.id}/${props.test.doc.status}`) : null
32
+ });
33
+ }
@@ -1,4 +1,4 @@
1
- import type { ReactNode } from 'react';
1
+ import { type ReactNode } from 'react';
2
2
  import type { SectionId, TestDocApi } from '../contracts.js';
3
3
  export declare function Section({ test, sectionId, children }: {
4
4
  test: TestDocApi;
@@ -1,27 +1,47 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from 'react';
3
4
  import { readinessParts } from '../readiness/parts.js';
5
+ import { SectionCard } from '../../../ui/SectionCard.js';
6
+ import { SECTION_ORDER } from '../contracts.js';
4
7
  import { TEST_PAGE as C } from '../../copy/testPage.js';
5
8
  export function Section({ test, sectionId, children }) {
9
+ const disclosure = useRef(null);
10
+ const hasAdvancedError = Object.keys(test.errors).some((path)=>path.startsWith('analysis.') && ![
11
+ 'analysis.reachPerDay',
12
+ 'analysis.plannedDurationDays'
13
+ ].includes(path) || path === 'exposurePolicy');
14
+ useEffect(()=>{
15
+ if (hasAdvancedError && disclosure.current) disclosure.current.open = true;
16
+ }, [
17
+ hasAdvancedError
18
+ ]);
6
19
  const done = readinessParts.sectionDone(test)[sectionId];
7
- return /*#__PURE__*/ _jsxs("section", {
8
- className: "xp-test-section",
9
- id: `section-${sectionId}`,
10
- "aria-labelledby": `title-${sectionId}`,
20
+ if (sectionId === 'advanced') return /*#__PURE__*/ _jsxs("details", {
21
+ ref: disclosure,
22
+ className: "xp-test-advanced",
23
+ id: "section-advanced",
11
24
  children: [
12
- /*#__PURE__*/ _jsxs("h2", {
13
- id: `title-${sectionId}`,
14
- children: [
15
- /*#__PURE__*/ _jsx("span", {
16
- className: done ? 'xp-section-check xp-section-check--done' : 'xp-section-check',
17
- "aria-label": done ? C.complete : C.incomplete,
18
- children: done ? C.completeMark : C.incompleteMark
19
- }),
20
- ' ',
21
- C.sections[sectionId]
22
- ]
25
+ /*#__PURE__*/ _jsx("summary", {
26
+ children: /*#__PURE__*/ _jsx("h2", {
27
+ id: "title-advanced",
28
+ tabIndex: -1,
29
+ children: C.sections.advanced
30
+ })
23
31
  }),
24
- children
32
+ /*#__PURE__*/ _jsx("div", {
33
+ className: "xp-ui-section-body",
34
+ children: children
35
+ })
25
36
  ]
26
37
  });
38
+ return /*#__PURE__*/ _jsx(SectionCard, {
39
+ number: SECTION_ORDER.indexOf(sectionId) + 1,
40
+ title: C.sections[sectionId],
41
+ complete: done,
42
+ id: `section-${sectionId}`,
43
+ headingId: `title-${sectionId}`,
44
+ className: "xp-test-section",
45
+ children: children
46
+ });
27
47
  }