@delmaredigital/payload-experiments 0.1.0-rc.7 → 0.2.0-rc.2

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 (207) hide show
  1. package/README.md +28 -0
  2. package/dist/admin/LayerUsage.d.ts +1 -1
  3. package/dist/admin/LayerUsage.js +23 -11
  4. package/dist/admin/MetricSourceSelect.js +2 -1
  5. package/dist/admin/TestsNavLink.d.ts +1 -0
  6. package/dist/admin/TestsNavLink.js +30 -0
  7. package/dist/admin/allocation.model.js +1 -1
  8. package/dist/admin/copy/actions.d.ts +0 -2
  9. package/dist/admin/copy/actions.js +0 -2
  10. package/dist/admin/copy/advanced.d.ts +55 -0
  11. package/dist/admin/copy/advanced.js +55 -0
  12. package/dist/admin/copy/armSelect.js +1 -1
  13. package/dist/admin/copy/arms.d.ts +0 -13
  14. package/dist/admin/copy/arms.js +0 -13
  15. package/dist/admin/copy/{who.d.ts → audience.d.ts} +1 -9
  16. package/dist/admin/copy/{who.js → audience.js} +1 -9
  17. package/dist/admin/copy/history.d.ts +0 -4
  18. package/dist/admin/copy/history.js +3 -7
  19. package/dist/admin/copy/metrics.d.ts +2 -2
  20. package/dist/admin/copy/metrics.js +2 -2
  21. package/dist/admin/copy/newTest.d.ts +32 -0
  22. package/dist/admin/copy/newTest.js +32 -0
  23. package/dist/admin/copy/readiness.d.ts +45 -0
  24. package/dist/admin/copy/readiness.js +50 -0
  25. package/dist/admin/copy/recommendation.d.ts +26 -0
  26. package/dist/admin/copy/recommendation.js +25 -0
  27. package/dist/admin/copy/results.d.ts +0 -1
  28. package/dist/admin/copy/results.js +0 -1
  29. package/dist/admin/copy/review.d.ts +0 -17
  30. package/dist/admin/copy/review.js +1 -112
  31. package/dist/admin/copy/setting.d.ts +10 -0
  32. package/dist/admin/copy/setting.js +10 -0
  33. package/dist/admin/copy/surface.d.ts +36 -0
  34. package/dist/admin/copy/surface.js +47 -0
  35. package/dist/admin/copy/testPage.d.ts +88 -0
  36. package/dist/admin/copy/testPage.js +88 -0
  37. package/dist/admin/paths.d.ts +5 -13
  38. package/dist/admin/paths.js +5 -13
  39. package/dist/admin/review/review.model.d.ts +3 -26
  40. package/dist/admin/review/review.model.js +3 -30
  41. package/dist/admin/surface/ResultsRoute.d.ts +7 -0
  42. package/dist/admin/surface/ResultsRoute.js +58 -0
  43. package/dist/admin/surface/SurfaceShell.d.ts +6 -0
  44. package/dist/admin/surface/SurfaceShell.js +35 -0
  45. package/dist/admin/surface/TestsHome.d.ts +4 -0
  46. package/dist/admin/surface/TestsHome.js +212 -0
  47. package/dist/admin/surface/TestsSurface.d.ts +2 -0
  48. package/dist/admin/surface/TestsSurface.js +36 -0
  49. package/dist/admin/surface/contracts.d.ts +48 -0
  50. package/dist/admin/surface/contracts.js +8 -0
  51. package/dist/admin/surface/home.model.d.ts +41 -0
  52. package/dist/admin/surface/home.model.js +59 -0
  53. package/dist/admin/surface/newTest/NewTest.d.ts +7 -0
  54. package/dist/admin/surface/newTest/NewTest.js +355 -0
  55. package/dist/admin/surface/newTest/newTest.css +9 -0
  56. package/dist/admin/surface/pageBlock/pageBlock.model.d.ts +6 -0
  57. package/dist/admin/surface/pageBlock/pageBlock.model.js +8 -0
  58. package/dist/admin/surface/pageBlock/parts.d.ts +2 -0
  59. package/dist/admin/surface/pageBlock/parts.js +64 -0
  60. package/dist/admin/surface/readiness/ActionDialogs.d.ts +15 -0
  61. package/dist/admin/surface/readiness/ActionDialogs.js +274 -0
  62. package/dist/admin/surface/readiness/Banner.d.ts +2 -0
  63. package/dist/admin/surface/readiness/Banner.js +138 -0
  64. package/dist/admin/surface/readiness/SaveStatus.d.ts +4 -0
  65. package/dist/admin/surface/readiness/SaveStatus.js +50 -0
  66. package/dist/admin/surface/readiness/parts.d.ts +2 -0
  67. package/dist/admin/surface/readiness/parts.js +9 -0
  68. package/dist/admin/surface/readiness/readiness.model.d.ts +22 -0
  69. package/dist/admin/surface/readiness/readiness.model.js +107 -0
  70. package/dist/admin/surface/readiness/useTransition.d.ts +10 -0
  71. package/dist/admin/surface/readiness/useTransition.js +34 -0
  72. package/dist/admin/surface/results/ResultsBody.d.ts +7 -0
  73. package/dist/admin/surface/results/ResultsBody.js +262 -0
  74. package/dist/admin/surface/results/recommendation.model.d.ts +12 -0
  75. package/dist/admin/surface/results/recommendation.model.js +59 -0
  76. package/dist/admin/surface/results/useResults.d.ts +56 -0
  77. package/dist/admin/surface/results/useResults.js +183 -0
  78. package/dist/admin/surface/setting/parts.d.ts +3 -0
  79. package/dist/admin/surface/setting/parts.js +263 -0
  80. package/dist/admin/surface/setting/setting.css +29 -0
  81. package/dist/admin/surface/setting/setting.model.d.ts +8 -0
  82. package/dist/admin/surface/setting/setting.model.js +25 -0
  83. package/dist/admin/surface/surface.css +50 -0
  84. package/dist/admin/surface/test/AdvancedSection.d.ts +2 -0
  85. package/dist/admin/surface/test/AdvancedSection.js +153 -0
  86. package/dist/admin/surface/test/GoalSection.d.ts +2 -0
  87. package/dist/admin/surface/test/GoalSection.js +198 -0
  88. package/dist/admin/surface/test/LengthSection.d.ts +2 -0
  89. package/dist/admin/surface/test/LengthSection.js +87 -0
  90. package/dist/admin/surface/test/Section.d.ts +7 -0
  91. package/dist/admin/surface/test/Section.js +27 -0
  92. package/dist/admin/surface/test/TargetingSection.d.ts +2 -0
  93. package/dist/admin/surface/test/TargetingSection.js +164 -0
  94. package/dist/admin/surface/test/TestHeader.d.ts +4 -0
  95. package/dist/admin/surface/test/TestHeader.js +69 -0
  96. package/dist/admin/surface/test/TestPage.d.ts +7 -0
  97. package/dist/admin/surface/test/TestPage.js +84 -0
  98. package/dist/admin/surface/test/VersionsSection.d.ts +4 -0
  99. package/dist/admin/surface/test/VersionsSection.js +208 -0
  100. package/dist/admin/surface/test/fields.d.ts +55 -0
  101. package/dist/admin/surface/test/fields.js +185 -0
  102. package/dist/admin/surface/test/pageParts.d.ts +2 -0
  103. package/dist/admin/surface/test/pageParts.js +39 -0
  104. package/dist/admin/surface/test/testPage.css +23 -0
  105. package/dist/admin/surface/test/testPage.model.d.ts +17 -0
  106. package/dist/admin/surface/test/testPage.model.js +53 -0
  107. package/dist/admin/surface/testDocument.model.d.ts +39 -0
  108. package/dist/admin/surface/testDocument.model.js +61 -0
  109. package/dist/admin/surface/useTestDocument.d.ts +22 -0
  110. package/dist/admin/surface/useTestDocument.js +345 -0
  111. package/dist/admin/targeting/targeting.model.js +2 -2
  112. package/dist/admin/useLayerUsage.d.ts +0 -5
  113. package/dist/admin/useLayerUsage.js +1 -39
  114. package/dist/admin/views/CollectionEditRedirect.d.ts +4 -0
  115. package/dist/admin/views/CollectionEditRedirect.js +14 -0
  116. package/dist/admin/views/CollectionListRedirect.d.ts +2 -0
  117. package/dist/admin/views/CollectionListRedirect.js +8 -0
  118. package/dist/admin/views/ExperimentsRootView.d.ts +2 -0
  119. package/dist/admin/views/ExperimentsRootView.js +175 -0
  120. package/dist/admin/views/access.model.d.ts +7 -0
  121. package/dist/admin/views/access.model.js +5 -0
  122. package/dist/collections/experiments/config.js +18 -125
  123. package/dist/collections/experiments/createDefaults.js +6 -0
  124. package/dist/collections/layers.js +43 -12
  125. package/dist/collections/metrics.js +70 -14
  126. package/dist/collections/serialize.js +4 -1
  127. package/dist/db/snapshots.d.ts +2 -1
  128. package/dist/db/snapshots.js +2 -2
  129. package/dist/endpoints/results.js +7 -0
  130. package/dist/endpoints/summary.d.ts +1 -1
  131. package/dist/endpoints/summary.js +37 -1
  132. package/dist/exports/adminClient.d.ts +2 -13
  133. package/dist/exports/adminClient.js +2 -13
  134. package/dist/exports/adminRsc.d.ts +3 -0
  135. package/dist/exports/adminRsc.js +3 -0
  136. package/dist/exports/index.d.ts +1 -1
  137. package/dist/options.js +29 -0
  138. package/dist/plugin.js +25 -0
  139. package/dist/puck/editor/CreateExperimentDrawer.d.ts +2 -1
  140. package/dist/puck/editor/CreateExperimentDrawer.js +17 -57
  141. package/dist/puck/editor/ExperimentSlotEditor.js +6 -0
  142. package/dist/puck/editor/copy.d.ts +10 -9
  143. package/dist/puck/editor/copy.js +11 -10
  144. package/dist/puck/editor/createTest.d.ts +7 -0
  145. package/dist/puck/editor/createTest.js +50 -0
  146. package/dist/puck/editor/editor.model.d.ts +28 -1
  147. package/dist/puck/editor/editor.model.js +61 -0
  148. package/dist/results/analyse.js +4 -1
  149. package/dist/results/verdict.d.ts +1 -0
  150. package/dist/results/verdict.js +7 -4
  151. package/dist/transitions/checkStart.d.ts +1 -0
  152. package/dist/transitions/placements.d.ts +5 -1
  153. package/dist/transitions/placements.js +14 -7
  154. package/dist/transitions/preflightData.d.ts +7 -1
  155. package/dist/transitions/preflightData.js +199 -148
  156. package/dist/transitions/start.js +2 -2
  157. package/dist/types/admin.d.ts +1 -0
  158. package/dist/types/contracts.d.ts +17 -0
  159. package/dist/types/endpoints.d.ts +11 -1
  160. package/dist/types/snapshot.d.ts +1 -0
  161. package/dist/types/surface.d.ts +29 -0
  162. package/dist/types/surface.js +1 -0
  163. package/package.json +7 -1
  164. package/dist/access.d.ts +0 -1
  165. package/dist/access.js +0 -2
  166. package/dist/admin/AllocationField.d.ts +0 -2
  167. package/dist/admin/AllocationField.js +0 -80
  168. package/dist/admin/FlagPointSelect.d.ts +0 -2
  169. package/dist/admin/FlagPointSelect.js +0 -54
  170. package/dist/admin/LockedFieldNotice.d.ts +0 -2
  171. package/dist/admin/LockedFieldNotice.js +0 -26
  172. package/dist/admin/PhaseHistory.d.ts +0 -2
  173. package/dist/admin/PhaseHistory.js +0 -76
  174. package/dist/admin/StatusCell.d.ts +0 -3
  175. package/dist/admin/StatusCell.js +0 -19
  176. package/dist/admin/SummaryCell.d.ts +0 -2
  177. package/dist/admin/SummaryCell.js +0 -32
  178. package/dist/admin/actions/AddMetricDialog.d.ts +0 -6
  179. package/dist/admin/actions/AddMetricDialog.js +0 -138
  180. package/dist/admin/actions/AllocateDialog.d.ts +0 -6
  181. package/dist/admin/actions/AllocateDialog.js +0 -81
  182. package/dist/admin/actions/ExperimentActions.d.ts +0 -3
  183. package/dist/admin/actions/ExperimentActions.js +0 -110
  184. package/dist/admin/arms/ArmsField.d.ts +0 -2
  185. package/dist/admin/arms/ArmsField.js +0 -310
  186. package/dist/admin/arms/ParamsEditor.d.ts +0 -16
  187. package/dist/admin/arms/ParamsEditor.js +0 -79
  188. package/dist/admin/copy/tabs.d.ts +0 -7
  189. package/dist/admin/copy/tabs.js +0 -7
  190. package/dist/admin/permissions.model.d.ts +0 -10
  191. package/dist/admin/permissions.model.js +0 -4
  192. package/dist/admin/phaseHistory.model.d.ts +0 -19
  193. package/dist/admin/phaseHistory.model.js +0 -19
  194. package/dist/admin/results/ResultsView.d.ts +0 -3
  195. package/dist/admin/results/ResultsView.js +0 -312
  196. package/dist/admin/review/ReviewPanel.d.ts +0 -2
  197. package/dist/admin/review/ReviewPanel.js +0 -268
  198. package/dist/admin/summaryBatcher.d.ts +0 -8
  199. package/dist/admin/summaryBatcher.js +0 -33
  200. package/dist/admin/targeting/TargetingAttributeField.d.ts +0 -2
  201. package/dist/admin/targeting/TargetingAttributeField.js +0 -31
  202. package/dist/admin/targeting/TargetingOpField.d.ts +0 -2
  203. package/dist/admin/targeting/TargetingOpField.js +0 -45
  204. package/dist/admin/targeting/TargetingValueField.d.ts +0 -2
  205. package/dist/admin/targeting/TargetingValueField.js +0 -142
  206. package/dist/index.d.ts +0 -1
  207. package/dist/index.js +0 -4
@@ -0,0 +1,263 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useState } from 'react';
4
+ import { CheckboxInput, TextareaInput, TextInput } from '@payloadcms/ui';
5
+ import { EDITABLE_BY_STATUS, isEditable } from '../../../collections/experiments/editability.js';
6
+ import { coerceParam, paramIssue, paramsForArm, setParam } from '../../arms/params.model.js';
7
+ import { SETTING_COPY } from '../../copy/setting.js';
8
+ import { lockedReason } from '../../lockedNotice.model.js';
9
+ import { bindSetting, displayParam, paramCheckMessage, paramControl } from './setting.model.js';
10
+ import './setting.css';
11
+ function Binding({ test, surface }) {
12
+ const id = useId();
13
+ const doc = test.doc;
14
+ if (!doc) return null;
15
+ const point = surface.flagPoints.find((point)=>point.id === doc.flagPoint);
16
+ const editable = surface.canManage && isEditable(doc.status, 'flagPoint');
17
+ const reason = lockedReason(doc.status, 'flagPoint', EDITABLE_BY_STATUS);
18
+ return /*#__PURE__*/ _jsxs("div", {
19
+ className: "xp-setting-binding",
20
+ children: [
21
+ editable ? /*#__PURE__*/ _jsxs("label", {
22
+ htmlFor: id,
23
+ children: [
24
+ SETTING_COPY.setting,
25
+ /*#__PURE__*/ _jsxs("select", {
26
+ id: id,
27
+ value: point?.id ?? '',
28
+ "aria-describedby": point ? `${id}-description` : undefined,
29
+ onChange: (event)=>{
30
+ const current = test.doc;
31
+ if (!current || !surface.canManage || !isEditable(current.status, 'flagPoint') || event.target.value === current.flagPoint) return;
32
+ const next = surface.flagPoints.find((point)=>point.id === event.target.value);
33
+ if (!next) return;
34
+ test.stage({
35
+ flagPoint: next.id,
36
+ arms: bindSetting(current.arms, next)
37
+ });
38
+ void test.commit();
39
+ },
40
+ children: [
41
+ !point ? /*#__PURE__*/ _jsx("option", {
42
+ value: "",
43
+ children: SETTING_COPY.choose
44
+ }) : null,
45
+ surface.flagPoints.map((point)=>/*#__PURE__*/ _jsx("option", {
46
+ value: point.id,
47
+ children: point.label
48
+ }, point.id))
49
+ ]
50
+ })
51
+ ]
52
+ }) : /*#__PURE__*/ _jsxs("p", {
53
+ title: reason ?? undefined,
54
+ children: [
55
+ SETTING_COPY.setting,
56
+ ": ",
57
+ point?.label ?? SETTING_COPY.choose
58
+ ]
59
+ }),
60
+ point ? /*#__PURE__*/ _jsx("p", {
61
+ id: `${id}-description`,
62
+ children: point.description ?? point.label
63
+ }) : null,
64
+ reason ? /*#__PURE__*/ _jsx("p", {
65
+ className: "xp-setting-lock",
66
+ children: reason
67
+ }) : null,
68
+ test.errors.flagPoint ? /*#__PURE__*/ _jsx("p", {
69
+ role: "alert",
70
+ children: test.errors.flagPoint
71
+ }) : null
72
+ ]
73
+ });
74
+ }
75
+ function Parameter({ test, surface, armIndex, paramKey, field }) {
76
+ const id = useId();
77
+ // Only the edited scalar stays local; the complete group is rebuilt on commit.
78
+ const [draft, setDraft] = useState(null);
79
+ const [touched, setTouched] = useState(false);
80
+ const doc = test.doc;
81
+ const arm = doc?.arms[armIndex];
82
+ if (!doc || !arm) return null;
83
+ const point = surface.flagPoints.find((point)=>point.id === doc.flagPoint);
84
+ const value = draft ? draft.value : paramsForArm(arm, point)[paramKey];
85
+ const path = `arms.${armIndex}.params.${paramKey}`;
86
+ const editable = surface.canManage && isEditable(doc.status, 'arms');
87
+ const reason = lockedReason(doc.status, 'arms', EDITABLE_BY_STATUS);
88
+ const failed = test.preflight?.checks.find((check)=>check.code === 'flag-params-invalid' && !check.ok);
89
+ const error = test.errors[path] ?? test.errors[`arms.${armIndex}.params`];
90
+ const preflightIssue = paramCheckMessage(failed?.detail, arm.key, paramKey, field);
91
+ const issue = error ?? (touched || preflightIssue ? paramIssue(field, draft ? coerceParam(field, value) : value) : null) ?? preflightIssue;
92
+ const save = (next)=>{
93
+ const current = test.doc;
94
+ const currentArm = current?.arms[armIndex];
95
+ if (!current || currentArm?.kind !== 'variant' || currentArm.key !== arm.key || !surface.canManage || !isEditable(current.status, 'arms') || current.flagPoint !== point?.id) return;
96
+ setTouched(true);
97
+ const arms = current.arms.map((item, index)=>index === armIndex ? {
98
+ ...item,
99
+ params: setParam(paramsForArm(item, point), paramKey, field, next)
100
+ } : item);
101
+ test.stage({
102
+ arms
103
+ });
104
+ void test.commit();
105
+ };
106
+ const blur = ()=>{
107
+ if (draft) {
108
+ save(draft.value);
109
+ setDraft(null);
110
+ }
111
+ };
112
+ const control = paramControl(field);
113
+ const shared = {
114
+ path: id,
115
+ label: field.label,
116
+ required: field.required
117
+ };
118
+ const inlineError = issue ? /*#__PURE__*/ _jsx("p", {
119
+ id: `${id}-error`,
120
+ role: "alert",
121
+ children: issue
122
+ }) : null;
123
+ const editDraft = (value)=>{
124
+ setTouched(true);
125
+ setDraft({
126
+ value
127
+ });
128
+ };
129
+ const text = typeof value === 'string' ? value : '';
130
+ return /*#__PURE__*/ _jsxs("div", {
131
+ className: "xp-setting-parameter",
132
+ onBlur: blur,
133
+ children: [
134
+ arm.kind === 'control' || !editable ? /*#__PURE__*/ _jsxs("div", {
135
+ title: reason ?? undefined,
136
+ children: [
137
+ /*#__PURE__*/ _jsx("span", {
138
+ children: field.label
139
+ }),
140
+ /*#__PURE__*/ _jsx("p", {
141
+ className: "xp-setting-value",
142
+ children: arm.kind === 'control' ? SETTING_COPY.current.replace('{value}', displayParam(field, point?.effectiveDefaults[paramKey])) : displayParam(field, value)
143
+ }),
144
+ reason ? /*#__PURE__*/ _jsx("p", {
145
+ className: "xp-setting-lock",
146
+ children: reason
147
+ }) : null
148
+ ]
149
+ }) : control === 'checkbox' ? /*#__PURE__*/ _jsx(CheckboxInput, {
150
+ ...shared,
151
+ id: id,
152
+ name: id,
153
+ checked: value === true,
154
+ onToggle: (event)=>save(event.target.checked)
155
+ }) : control === 'segmented' ? /*#__PURE__*/ _jsxs("fieldset", {
156
+ "aria-describedby": issue ? `${id}-error` : undefined,
157
+ children: [
158
+ /*#__PURE__*/ _jsx("legend", {
159
+ children: field.label
160
+ }),
161
+ /*#__PURE__*/ _jsx("div", {
162
+ className: "xp-setting-segments",
163
+ children: field.options?.map((option)=>/*#__PURE__*/ _jsxs("label", {
164
+ children: [
165
+ /*#__PURE__*/ _jsx("input", {
166
+ type: "radio",
167
+ name: id,
168
+ value: option.value,
169
+ checked: value === option.value,
170
+ required: field.required,
171
+ onChange: ()=>save(option.value)
172
+ }),
173
+ /*#__PURE__*/ _jsx("span", {
174
+ children: option.label
175
+ })
176
+ ]
177
+ }, option.value))
178
+ })
179
+ ]
180
+ }) : control === 'select' ? /*#__PURE__*/ _jsxs("label", {
181
+ htmlFor: id,
182
+ children: [
183
+ field.label,
184
+ /*#__PURE__*/ _jsxs("select", {
185
+ id: id,
186
+ value: text,
187
+ required: field.required,
188
+ "aria-invalid": !!issue,
189
+ "aria-describedby": issue ? `${id}-error` : undefined,
190
+ onChange: (event)=>save(event.target.value),
191
+ children: [
192
+ /*#__PURE__*/ _jsx("option", {
193
+ value: "",
194
+ children: SETTING_COPY.empty
195
+ }),
196
+ field.options?.map((option)=>/*#__PURE__*/ _jsx("option", {
197
+ value: option.value,
198
+ children: option.label
199
+ }, option.value))
200
+ ]
201
+ })
202
+ ]
203
+ }) : control === 'number' ? /*#__PURE__*/ _jsxs("label", {
204
+ htmlFor: id,
205
+ children: [
206
+ field.label,
207
+ /*#__PURE__*/ _jsx("input", {
208
+ id: id,
209
+ type: "number",
210
+ min: field.min,
211
+ max: field.max,
212
+ step: "any",
213
+ required: field.required,
214
+ value: typeof value === 'number' && Number.isFinite(value) || typeof value === 'string' ? value : '',
215
+ "aria-invalid": !!issue,
216
+ "aria-describedby": issue ? `${id}-error` : undefined,
217
+ onChange: (event)=>editDraft(event.target.value)
218
+ })
219
+ ]
220
+ }) : control === 'textarea' ? /*#__PURE__*/ _jsx(TextareaInput, {
221
+ ...shared,
222
+ Error: inlineError,
223
+ showError: !!issue,
224
+ value: text,
225
+ onChange: (event)=>editDraft(event.target.value)
226
+ }) : /*#__PURE__*/ _jsx(TextInput, {
227
+ ...shared,
228
+ Error: inlineError,
229
+ showError: !!issue,
230
+ hasMany: false,
231
+ value: text,
232
+ onChange: (event)=>editDraft(event.target.value)
233
+ }),
234
+ arm.kind === 'control' || !editable || control !== 'text' && control !== 'textarea' ? inlineError : null
235
+ ]
236
+ });
237
+ }
238
+ function VersionContent({ test, surface, armIndex }) {
239
+ const doc = test.doc;
240
+ const arm = doc?.arms[armIndex];
241
+ if (!doc || !arm) return null;
242
+ if (arm.kind === 'holdout') return /*#__PURE__*/ _jsx("p", {
243
+ children: SETTING_COPY.holdout
244
+ });
245
+ const point = surface.flagPoints.find((point)=>point.id === doc.flagPoint);
246
+ if (!point) return /*#__PURE__*/ _jsx("p", {
247
+ children: SETTING_COPY.choose
248
+ });
249
+ return /*#__PURE__*/ _jsx("div", {
250
+ className: "xp-setting-values",
251
+ children: Object.entries(point.params).map(([key, field])=>/*#__PURE__*/ _jsx(Parameter, {
252
+ test: test,
253
+ surface: surface,
254
+ armIndex: armIndex,
255
+ paramKey: key,
256
+ field: field
257
+ }, `${doc.id}:${point.id}:${arm.key}:${key}:${doc.status}:${surface.canManage}`))
258
+ });
259
+ }
260
+ export const settingParts = {
261
+ Binding,
262
+ VersionContent
263
+ };
@@ -0,0 +1,29 @@
1
+ .xp-setting-binding select,
2
+ .xp-setting-parameter select,
3
+ .xp-setting-parameter input[type='number'] {
4
+ display: block;
5
+ width: 100%;
6
+ padding: 0.65rem;
7
+ margin-top: 0.4rem;
8
+ border: 1px solid var(--theme-elevation-150);
9
+ border-radius: var(--style-radius-s);
10
+ background: var(--theme-input-bg);
11
+ color: var(--theme-text);
12
+ }
13
+ .xp-setting-values { display: grid; gap: 1rem; }
14
+ .xp-setting-value { white-space: pre-wrap; }
15
+ .xp-setting-lock { color: var(--theme-elevation-600); }
16
+ .xp-setting-parameter fieldset { border: 0; padding: 0; margin: 0; }
17
+ .xp-setting-segments { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: 0.4rem; }
18
+ .xp-setting-segments label { position: relative; }
19
+ .xp-setting-segments input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
20
+ .xp-setting-segments span {
21
+ display: block;
22
+ padding: 0.5rem 0.8rem;
23
+ border: 1px solid var(--theme-elevation-150);
24
+ border-radius: var(--style-radius-s);
25
+ background: var(--theme-input-bg);
26
+ color: var(--theme-text);
27
+ }
28
+ .xp-setting-segments input:checked + span { background: var(--theme-elevation-150); border-color: var(--theme-text); }
29
+ .xp-setting-segments input:focus-visible + span { outline: 2px solid var(--theme-text); outline-offset: 2px; }
@@ -0,0 +1,8 @@
1
+ import type { Arm, ParamField } from '../../../types/contracts.js';
2
+ import type { SerializedFlagPoint } from '../../../types/admin.js';
3
+ export declare function paramControl(field: ParamField): ParamField['type'] | 'segmented';
4
+ export declare function displayParam(field: ParamField, value: unknown): string;
5
+ /** Bind against the current document, preserving every unrelated version field. */
6
+ export declare function bindSetting(arms: Arm[], point?: SerializedFlagPoint): Arm[];
7
+ /** Preflight identifies invalid values with { arm, field }, never editor-facing codes. */
8
+ export declare function paramCheckMessage(detail: unknown, armKey: string, key: string, field: ParamField): string | null;
@@ -0,0 +1,25 @@
1
+ import { SETTING_COPY } from '../../copy/setting.js';
2
+ export function paramControl(field) {
3
+ return field.type === 'select' && field.options?.length && field.options.length <= 4 ? 'segmented' : field.type;
4
+ }
5
+ export function displayParam(field, value) {
6
+ if (value == null || value === '') return SETTING_COPY.empty;
7
+ if (field.type === 'checkbox') return value === true ? SETTING_COPY.yes : value === false ? SETTING_COPY.no : SETTING_COPY.empty;
8
+ if (field.type === 'select') return field.options?.find((option)=>option.value === value)?.label ?? SETTING_COPY.empty;
9
+ if (field.type === 'number') return typeof value === 'number' && Number.isFinite(value) ? String(value) : SETTING_COPY.empty;
10
+ return typeof value === 'string' ? value : SETTING_COPY.empty;
11
+ }
12
+ /** Bind against the current document, preserving every unrelated version field. */ export function bindSetting(arms, point) {
13
+ return arms.map((arm)=>{
14
+ const { params: _params, ...rest } = arm;
15
+ return arm.kind === 'holdout' ? rest : {
16
+ ...rest,
17
+ params: arm.kind === 'control' ? {
18
+ ...point?.effectiveDefaults
19
+ } : {}
20
+ };
21
+ });
22
+ }
23
+ /** Preflight identifies invalid values with { arm, field }, never editor-facing codes. */ export function paramCheckMessage(detail, armKey, key, field) {
24
+ return Array.isArray(detail) && detail.some((item)=>item && item.arm === armKey && item.field === key) ? SETTING_COPY.invalid.replace('{label}', field.label) : null;
25
+ }
@@ -0,0 +1,50 @@
1
+ .xp-tests-surface {
2
+ min-height: 100dvh;
3
+ width: 100%;
4
+ color: var(--theme-text);
5
+ background: var(--theme-bg);
6
+ }
7
+ .xp-tests-shell-header {
8
+ display: flex;
9
+ align-items: center;
10
+ gap: var(--base);
11
+ padding: calc(var(--base) * 1.5) calc(var(--base) * 2);
12
+ border-bottom: 1px solid var(--theme-elevation-150);
13
+ }
14
+ .xp-tests-main { padding: calc(var(--base) * 2); }
15
+ .xp-tests-surface a { color: var(--theme-text); }
16
+ .xp-tests-toolbar, .xp-tests-card-heading {
17
+ display: flex;
18
+ flex-wrap: wrap;
19
+ align-items: center;
20
+ gap: var(--base);
21
+ }
22
+ .xp-tests-toolbar { margin-bottom: calc(var(--base) * 2); }
23
+ .xp-tests-toolbar h1 { margin: 0; margin-right: auto; }
24
+ .xp-tests-toolbar label { display: grid; gap: calc(var(--base) / 4); }
25
+ .xp-tests-toolbar input, .xp-tests-surface button {
26
+ color: var(--theme-text);
27
+ background: var(--theme-input-bg);
28
+ border: 1px solid var(--theme-elevation-300);
29
+ border-radius: var(--style-radius-s);
30
+ padding: calc(var(--base) / 2);
31
+ }
32
+ .xp-tests-primary {
33
+ padding: calc(var(--base) / 2) var(--base);
34
+ border: 1px solid var(--theme-elevation-500);
35
+ border-radius: var(--style-radius-s);
36
+ }
37
+ .xp-tests-group { margin-bottom: calc(var(--base) * 2); }
38
+ .xp-tests-group summary { cursor: pointer; padding: var(--base) 0; }
39
+ .xp-tests-cards { display: grid; gap: var(--base); }
40
+ .xp-tests-card {
41
+ border: 1px solid var(--theme-elevation-150);
42
+ border-radius: var(--style-radius-m);
43
+ padding: calc(var(--base) * 1.5);
44
+ background: var(--theme-elevation-50);
45
+ }
46
+ .xp-tests-card h3 { margin: 0; margin-right: auto; }
47
+ .xp-tests-health { width: calc(var(--base) / 2); height: calc(var(--base) / 2); border-radius: 50%; }
48
+ .xp-tests-health--red { background: var(--theme-error-500); }
49
+ .xp-tests-health--amber { background: var(--theme-warning-500); }
50
+ .xp-tests-health--green { background: var(--theme-success-500); }
@@ -0,0 +1,2 @@
1
+ import type { SectionProps } from '../contracts.js';
2
+ export declare function AdvancedSection({ test, surface }: SectionProps): import("react").JSX.Element;
@@ -0,0 +1,153 @@
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';
4
+ import { ADVANCED_COPY as C } from '../../copy/advanced.js';
5
+ import { exposureOptions } from './testPage.model.js';
6
+ import { FieldErrors, positiveInteger, ReadOnly, reasonFor, save, SelectEdit, TextEdit } from './fields.js';
7
+ import { Section } from './Section.js';
8
+ const readOnlyFields = [
9
+ 'layer',
10
+ 'slug',
11
+ 'kind',
12
+ 'unitType'
13
+ ];
14
+ export function AdvancedSection({ test, surface }) {
15
+ const doc = test.doc;
16
+ const reason = reasonFor(test, 'analysis');
17
+ const change = (field, value)=>save(test, 'analysis', ()=>({
18
+ analysis: {
19
+ ...test.doc.analysis,
20
+ [field]: value
21
+ }
22
+ }));
23
+ const numbers = [
24
+ [
25
+ 'minExposuresPerArm',
26
+ 200,
27
+ 1
28
+ ],
29
+ [
30
+ 'attributionWindowHours',
31
+ '',
32
+ 1
33
+ ],
34
+ [
35
+ 'sequentialTuningParameter',
36
+ '',
37
+ 0
38
+ ]
39
+ ];
40
+ return /*#__PURE__*/ _jsx(Section, {
41
+ test: test,
42
+ sectionId: "advanced",
43
+ children: /*#__PURE__*/ _jsxs("details", {
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"
66
+ }),
67
+ surface.isAdmin ? /*#__PURE__*/ _jsxs(_Fragment, {
68
+ children: [
69
+ /*#__PURE__*/ _jsx(SelectEdit, {
70
+ label: C.method.label,
71
+ value: doc.analysis?.method ?? 'sequential',
72
+ reason: reason,
73
+ options: [
74
+ {
75
+ value: 'sequential',
76
+ label: C.sequential
77
+ },
78
+ {
79
+ value: 'bayesian',
80
+ label: C.bayesian
81
+ }
82
+ ],
83
+ onCommit: (value)=>change('method', value)
84
+ }),
85
+ /*#__PURE__*/ _jsx("p", {
86
+ children: C.method.help
87
+ }),
88
+ /*#__PURE__*/ _jsx(FieldErrors, {
89
+ test: test,
90
+ path: "analysis.method"
91
+ })
92
+ ]
93
+ }) : 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"
116
+ }),
117
+ numbers.map(([field, fallback, min])=>/*#__PURE__*/ _jsxs("div", {
118
+ children: [
119
+ /*#__PURE__*/ _jsx(TextEdit, {
120
+ label: C[field].label,
121
+ value: doc.analysis?.[field] ?? fallback,
122
+ placeholder: field === 'attributionWindowHours' ? C.inheritedWindow : field === 'sequentialTuningParameter' ? C.automaticTuning : undefined,
123
+ type: "number",
124
+ min: min,
125
+ reason: reason,
126
+ validate: field === 'minExposuresPerArm' ? positiveInteger : undefined,
127
+ onCommit: (raw)=>change(field, raw.trim() ? Number(raw) : null)
128
+ }),
129
+ /*#__PURE__*/ _jsx("p", {
130
+ children: C[field].help
131
+ }),
132
+ /*#__PURE__*/ _jsx(FieldErrors, {
133
+ test: test,
134
+ path: `analysis.${field}`
135
+ })
136
+ ]
137
+ }, field)),
138
+ readOnlyFields.map((field)=>/*#__PURE__*/ _jsxs("div", {
139
+ children: [
140
+ /*#__PURE__*/ _jsx(ReadOnly, {
141
+ label: C[field].label,
142
+ value: typeof doc[field] === 'string' || typeof doc[field] === 'number' ? String(doc[field]) : C.none,
143
+ reason: reasonFor(test, field)
144
+ }),
145
+ /*#__PURE__*/ _jsx("p", {
146
+ children: C[field].help
147
+ })
148
+ ]
149
+ }, field))
150
+ ]
151
+ })
152
+ });
153
+ }
@@ -0,0 +1,2 @@
1
+ import type { SectionProps } from '../contracts.js';
2
+ export declare function GoalSection({ test, surface }: SectionProps): import("react").JSX.Element;