@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
@@ -0,0 +1,177 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useState } from 'react';
4
+ import { formatAdminURL } from 'payload/shared';
5
+ import { SurfaceShell } from '../SurfaceShell.js';
6
+ import { Button } from '../../../ui/Button.js';
7
+ import { Banner, StatusChip } from '../../../ui/Feedback.js';
8
+ import { createApi } from '../../api.js';
9
+ import { availableActions, actionGroupLabel } from '../../actions/actions.model.js';
10
+ import { AllocateAction, ConfirmAction } from '../readiness/ActionDialogs.js';
11
+ import { READINESS, daysRemaining, displayDate, nextStep } from '../readiness/readiness.model.js';
12
+ import { READINESS_COPY as R, runningCopy, stoppedCopy, decidedCopy } from '../../copy/readiness.js';
13
+ import { RESULTS_COPY as C } from '../../copy/results.js';
14
+ import { TEST_PAGE } from '../../copy/testPage.js';
15
+ export function ResultsHeader({ test, surface, resultsVersion, children, actions, released }) {
16
+ const doc = test.doc;
17
+ const [selected, setSelected] = useState(null);
18
+ const [people, setPeople] = useState(null);
19
+ useEffect(()=>{
20
+ let active = true;
21
+ if (surface.canReadResults && doc.status === 'running') {
22
+ void createApi(surface.apiRoute).summary([
23
+ doc.id
24
+ ]).then((result)=>{
25
+ const count = result.ok ? result.data?.[doc.id]?.exposures : undefined;
26
+ if (active && typeof count === 'number' && Number.isFinite(count)) setPeople(count);
27
+ }).catch(()=>{});
28
+ }
29
+ return ()=>{
30
+ active = false;
31
+ };
32
+ }, [
33
+ doc.id,
34
+ doc.status,
35
+ surface.apiRoute,
36
+ surface.canReadResults,
37
+ resultsVersion
38
+ ]);
39
+ const decision = doc.decision;
40
+ const winner = actionGroupLabel(decision?.winnerArm ?? 'control', doc.arms.find((item)=>item.key === decision?.winnerArm)?.label);
41
+ const available = availableActions({
42
+ status: doc.status,
43
+ kind: doc.kind,
44
+ arms: doc.arms,
45
+ decision,
46
+ canManage: surface.canManage
47
+ }).filter((action)=>doc.status === 'running' ? [
48
+ 'allocate',
49
+ 'stop'
50
+ ].includes(action.id) : action.id !== 'decide').sort((a, b)=>doc.status === 'running' ? [
51
+ 'allocate',
52
+ 'stop'
53
+ ].indexOf(a.id) - [
54
+ 'allocate',
55
+ 'stop'
56
+ ].indexOf(b.id) : 0);
57
+ const next = nextStep(test.preflight, {
58
+ ...doc,
59
+ flagPoints: surface.flagPoints
60
+ });
61
+ const ready = Boolean(test.preflight && !test.preflightPending && !test.preflightError && test.preflight.checks.every((check)=>check.ok));
62
+ const startReason = next?.sentence ?? (!ready ? test.preflightError ? R.checkError : R.checking : undefined);
63
+ const action = available.find((item)=>item.id === selected);
64
+ const sentence = doc.status === 'running' ? runningCopy(displayDate(doc.startedAt), people, daysRemaining(doc, Date.now())) : doc.status === 'stopped' ? stoppedCopy(displayDate(doc.stoppedAt)) : doc.status === 'decided' ? decidedCopy(displayDate(doc.decidedAt), winner) : doc.status === 'archived' ? R.archived : null;
65
+ const testURL = formatAdminURL({
66
+ adminRoute: surface.adminRoute,
67
+ path: `/experiments/${encodeURIComponent(String(doc.id))}`
68
+ });
69
+ return /*#__PURE__*/ _jsxs(SurfaceShell, {
70
+ adminRoute: surface.adminRoute,
71
+ title: doc.name,
72
+ crumbs: [
73
+ {
74
+ label: C.tests,
75
+ href: formatAdminURL({
76
+ adminRoute: surface.adminRoute,
77
+ path: '/experiments'
78
+ })
79
+ },
80
+ {
81
+ label: doc.name,
82
+ href: testURL
83
+ },
84
+ {
85
+ label: C.results
86
+ }
87
+ ],
88
+ status: /*#__PURE__*/ _jsxs(_Fragment, {
89
+ children: [
90
+ /*#__PURE__*/ _jsx("span", {
91
+ className: "xp-tests-kind",
92
+ children: TEST_PAGE.kinds[doc.kind]
93
+ }),
94
+ /*#__PURE__*/ _jsx(StatusChip, {
95
+ status: doc.status
96
+ })
97
+ ]
98
+ }),
99
+ subtitle: /*#__PURE__*/ _jsxs(_Fragment, {
100
+ children: [
101
+ sentence ? /*#__PURE__*/ _jsx("p", {
102
+ role: "status",
103
+ children: sentence
104
+ }) : null,
105
+ released,
106
+ surface.canManage ? test.preflight?.checks.filter((check)=>check.code === 'make-content-unsupported' && check.warning).map((check, index)=>/*#__PURE__*/ _jsx(Banner, {
107
+ tone: "warning",
108
+ children: READINESS[check.code].next(check.detail, doc)
109
+ }, `${check.code}/${index}`)) : null
110
+ ]
111
+ }),
112
+ actions: /*#__PURE__*/ _jsxs("div", {
113
+ className: "xp-ui-button-group",
114
+ children: [
115
+ /*#__PURE__*/ _jsx(Button, {
116
+ variant: "quiet",
117
+ href: testURL,
118
+ children: C.backToSetup
119
+ }),
120
+ available.map((item)=>/*#__PURE__*/ _jsx(Button, {
121
+ variant: item.tone === 'danger' ? 'danger' : 'secondary',
122
+ disabled: item.id === 'start' && !!startReason || !!selected || test.hasUnsaved || test.saving === 'saving' || item.id === 'allocate' && doc.allocation >= 100,
123
+ title: item.id === 'start' ? startReason : item.id === 'allocate' && doc.allocation >= 100 ? R.everyone : undefined,
124
+ onClick: ()=>setSelected(item.id),
125
+ children: item.id === 'allocate' ? R.allocate : item.label
126
+ }, item.id)),
127
+ actions
128
+ ]
129
+ }),
130
+ children: [
131
+ test.errors._section ? /*#__PURE__*/ _jsx(Banner, {
132
+ tone: "error",
133
+ children: test.errors._section
134
+ }) : null,
135
+ test.preflightError ? /*#__PURE__*/ _jsx(Banner, {
136
+ tone: "error",
137
+ children: test.preflightError
138
+ }) : null,
139
+ children,
140
+ action?.id === 'allocate' ? /*#__PURE__*/ _jsx(AllocateAction, {
141
+ test: test,
142
+ surface: surface,
143
+ onClose: ()=>setSelected(null)
144
+ }) : action ? /*#__PURE__*/ _jsx(ConfirmAction, {
145
+ test: test,
146
+ surface: surface,
147
+ action: action,
148
+ winnerLabel: winner,
149
+ onClose: ()=>setSelected(null)
150
+ }) : null
151
+ ]
152
+ });
153
+ }
154
+ export function ReaderResultsShell({ surface, children, data }) {
155
+ // CORE-48 sends a missing name as an empty string.
156
+ const title = data?.experiment?.name?.trim() || C.test;
157
+ return /*#__PURE__*/ _jsx(SurfaceShell, {
158
+ adminRoute: surface.adminRoute,
159
+ title: title,
160
+ crumbs: [
161
+ {
162
+ label: C.tests,
163
+ href: formatAdminURL({
164
+ adminRoute: surface.adminRoute,
165
+ path: '/experiments'
166
+ })
167
+ },
168
+ {
169
+ label: title
170
+ },
171
+ {
172
+ label: C.results
173
+ }
174
+ ],
175
+ children: children
176
+ });
177
+ }
@@ -1,3 +1,4 @@
1
+ import { formatDate } from '../../format/date.js';
1
2
  import { armLabel } from '../../labels.js';
2
3
  import { formatCount, formatPercent } from '../../results/format.js';
3
4
  import { RECOMMENDATION_COPY as C, collectingRecommendation, guardrailRecommendation, mixedRecommendation, rolloutCollectingRecommendation, rolloutReadoutRecommendation, runningRecommendation, winnerRecommendation, worseRecommendation, storedGuardrailFinding } from '../../copy/recommendation.js';
@@ -42,12 +43,7 @@ const winner = (ctx)=>label(ctx, ctx.snapshot.winnerArm ?? ctx.snapshot.verdict.
42
43
  const days = ctx.plannedDurationDays;
43
44
  const start = Date.parse(ctx.phase?.startedAt ?? '');
44
45
  const end = days != null && Number.isFinite(days) && days > 0 ? start + days * 86_400_000 : NaN;
45
- return Number.isFinite(end) ? runningRecommendation(new Intl.DateTimeFormat('en-US', {
46
- month: 'short',
47
- day: 'numeric',
48
- year: 'numeric',
49
- timeZone: 'UTC'
50
- }).format(end)) : C.keepRunning;
46
+ return Number.isFinite(end) ? runningRecommendation(formatDate(end)) : C.keepRunning;
51
47
  },
52
48
  'no-difference': ()=>C.noDifference,
53
49
  'rollout-collecting': (ctx)=>rolloutCollectingRecommendation(winner(ctx)),
@@ -1,14 +1,17 @@
1
1
  import type { DocId, Status } from '../../../types/contracts.js';
2
2
  import type { PhaseSummary, ResultsResponse } from '../../../types/endpoints.js';
3
3
  import { type ApiResult } from '../../api.js';
4
+ import type { PageReleaseDecision } from '../../results/releasePages.model.js';
4
5
  export interface ResultsDocument {
6
+ flagPoint?: string;
7
+ kind?: string;
5
8
  status: Status;
6
9
  arms: Array<{
7
10
  key: string;
8
11
  kind?: string;
9
12
  label?: string;
10
13
  }>;
11
- decision?: {
14
+ decision?: PageReleaseDecision & {
12
15
  phase: number;
13
16
  winnerArm?: string;
14
17
  } | null;
@@ -27,18 +30,33 @@ export declare function useResults(id: DocId, { apiRoute, canReadResults, canMan
27
30
  }): {
28
31
  api: {
29
32
  start: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
30
- allocate: (id: DocId, body: import("../../../types/endpoints.js").AllocateBody) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
31
- addMetric: (id: DocId, body: import("../../../types/endpoints.js").AddMetricBody) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
33
+ allocate: (id: DocId, body: import("../../../types/endpoints.js").AllocateBody, options?: {
34
+ notFound?: string;
35
+ codes?: Readonly<Record<string, string>>;
36
+ fallback?: string;
37
+ }) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
38
+ addMetric: (id: DocId, body: import("../../../types/endpoints.js").AddMetricBody, options?: {
39
+ notFound?: string;
40
+ codes?: Readonly<Record<string, string>>;
41
+ fallback?: string;
42
+ }) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
32
43
  stop: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
33
44
  decide: (id: DocId, body: import("../../../types/endpoints.js").DecideBody) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
34
45
  releaseHoldout: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
46
+ releasePages: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
35
47
  makeContent: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").MakeContentResult>>;
48
+ dismissContentEntry: (id: DocId, body: import("../../../types/endpoints.js").DismissContentEntryBody) => Promise<ApiResult<import("../../../types/endpoints.js").DismissContentEntryResult>>;
49
+ restoreContentEntry: (id: DocId, body: import("../../../types/endpoints.js").RestoreContentEntryBody) => Promise<ApiResult<import("../../../types/endpoints.js").RestoreContentEntryResult>>;
36
50
  archive: (id: DocId, body: import("../../../types/endpoints.js").ArchiveBody) => Promise<ApiResult<import("../../../types/endpoints.js").TransitionSuccess>>;
37
51
  preflight: (id: DocId) => Promise<ApiResult<import("../../../types/endpoints.js").PreflightResult>>;
38
52
  previewToken: (id: DocId, arm: string) => Promise<ApiResult<import("../../../types/endpoints.js").PreviewTokenResult>>;
39
53
  results: (id: DocId, options?: {
40
54
  phase?: number;
41
55
  series?: boolean;
56
+ } & {
57
+ notFound?: string;
58
+ codes?: Readonly<Record<string, string>>;
59
+ fallback?: string;
42
60
  }) => Promise<ApiResult<ResultsResponse>>;
43
61
  recompute: (id: DocId) => Promise<ApiResult<{
44
62
  snapshotId: string;
@@ -85,7 +85,8 @@ export function useResults(id, { apiRoute, canReadResults, canManage = true }) {
85
85
  }),
86
86
  api.results(id, {
87
87
  phase: round ?? undefined,
88
- series: true
88
+ series: true,
89
+ notFound: round === null ? undefined : API_MESSAGES.roundNotFound
89
90
  })
90
91
  ]).then(([document, result])=>{
91
92
  if (signal.aborted) return;
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useId, useState } from 'react';
4
- import { CheckboxInput, TextareaInput, TextInput } from '@payloadcms/ui';
4
+ import { Checkbox, Textarea, TextInput, NumberInput, Select } from '../../../ui/Inputs.js';
5
5
  import { EDITABLE_BY_STATUS, isEditable } from '../../../collections/experiments/editability.js';
6
6
  import { coerceParam, paramIssue, paramsForArm, setParam } from '../../arms/params.model.js';
7
7
  import { SETTING_COPY } from '../../copy/setting.js';
@@ -18,36 +18,31 @@ function Binding({ test, surface }) {
18
18
  return /*#__PURE__*/ _jsxs("div", {
19
19
  className: "xp-setting-binding",
20
20
  children: [
21
- editable ? /*#__PURE__*/ _jsxs("label", {
22
- htmlFor: id,
21
+ editable ? /*#__PURE__*/ _jsxs(Select, {
22
+ id: id,
23
+ label: SETTING_COPY.setting,
24
+ value: point?.id ?? '',
25
+ "aria-describedby": point ? `${id}-description` : undefined,
26
+ onChange: (event)=>{
27
+ const current = test.doc;
28
+ if (!current || !surface.canManage || !isEditable(current.status, 'flagPoint') || event.target.value === current.flagPoint) return;
29
+ const next = surface.flagPoints.find((point)=>point.id === event.target.value);
30
+ if (!next) return;
31
+ test.stage({
32
+ flagPoint: next.id,
33
+ arms: bindSetting(current.arms, next)
34
+ });
35
+ void test.commit();
36
+ },
23
37
  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
- })
38
+ !point ? /*#__PURE__*/ _jsx("option", {
39
+ value: "",
40
+ children: SETTING_COPY.choose
41
+ }) : null,
42
+ surface.flagPoints.map((point)=>/*#__PURE__*/ _jsx("option", {
43
+ value: point.id,
44
+ children: point.label
45
+ }, point.id))
51
46
  ]
52
47
  }) : /*#__PURE__*/ _jsxs("p", {
53
48
  title: reason ?? undefined,
@@ -111,7 +106,7 @@ function Parameter({ test, surface, armIndex, paramKey, field }) {
111
106
  };
112
107
  const control = paramControl(field);
113
108
  const shared = {
114
- path: id,
109
+ id,
115
110
  label: field.label,
116
111
  required: field.required
117
112
  };
@@ -146,12 +141,12 @@ function Parameter({ test, surface, armIndex, paramKey, field }) {
146
141
  children: reason
147
142
  }) : null
148
143
  ]
149
- }) : control === 'checkbox' ? /*#__PURE__*/ _jsx(CheckboxInput, {
144
+ }) : control === 'checkbox' ? /*#__PURE__*/ _jsx(Checkbox, {
150
145
  ...shared,
151
- id: id,
146
+ error: issue ?? undefined,
152
147
  name: id,
153
148
  checked: value === true,
154
- onToggle: (event)=>save(event.target.checked)
149
+ onChange: (event)=>save(event.target.checked)
155
150
  }) : control === 'segmented' ? /*#__PURE__*/ _jsxs("fieldset", {
156
151
  "aria-describedby": issue ? `${id}-error` : undefined,
157
152
  children: [
@@ -177,61 +172,45 @@ function Parameter({ test, surface, armIndex, paramKey, field }) {
177
172
  }, option.value))
178
173
  })
179
174
  ]
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,
175
+ }) : control === 'select' ? /*#__PURE__*/ _jsxs(Select, {
176
+ id: id,
177
+ label: field.label,
178
+ value: text,
179
+ required: field.required,
180
+ error: issue ?? undefined,
181
+ onChange: (event)=>save(event.target.value),
205
182
  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
- })
183
+ /*#__PURE__*/ _jsx("option", {
184
+ value: "",
185
+ children: SETTING_COPY.empty
186
+ }),
187
+ field.options?.map((option)=>/*#__PURE__*/ _jsx("option", {
188
+ value: option.value,
189
+ children: option.label
190
+ }, option.value))
219
191
  ]
220
- }) : control === 'textarea' ? /*#__PURE__*/ _jsx(TextareaInput, {
192
+ }) : control === 'number' ? /*#__PURE__*/ _jsx(NumberInput, {
193
+ id: id,
194
+ label: field.label,
195
+ min: field.min,
196
+ max: field.max,
197
+ step: "any",
198
+ required: field.required,
199
+ value: typeof value === 'number' && Number.isFinite(value) || typeof value === 'string' ? value : '',
200
+ error: issue ?? undefined,
201
+ onChange: (event)=>editDraft(event.target.value)
202
+ }) : control === 'textarea' ? /*#__PURE__*/ _jsx(Textarea, {
221
203
  ...shared,
222
- Error: inlineError,
223
- showError: !!issue,
204
+ error: issue ?? undefined,
224
205
  value: text,
225
206
  onChange: (event)=>editDraft(event.target.value)
226
207
  }) : /*#__PURE__*/ _jsx(TextInput, {
227
208
  ...shared,
228
- Error: inlineError,
229
- showError: !!issue,
230
- hasMany: false,
209
+ error: issue ?? undefined,
231
210
  value: text,
232
211
  onChange: (event)=>editDraft(event.target.value)
233
212
  }),
234
- arm.kind === 'control' || !editable || control !== 'text' && control !== 'textarea' ? inlineError : null
213
+ arm.kind === 'control' || !editable || control === 'segmented' ? inlineError : null
235
214
  ]
236
215
  });
237
216
  }
@@ -239,22 +218,30 @@ function VersionContent({ test, surface, armIndex }) {
239
218
  const doc = test.doc;
240
219
  const arm = doc?.arms[armIndex];
241
220
  if (!doc || !arm) return null;
242
- if (arm.kind === 'holdout') return /*#__PURE__*/ _jsx("p", {
221
+ if (arm.kind === 'holdout') return /*#__PURE__*/ _jsx("span", {
222
+ className: "xp-content-chip",
243
223
  children: SETTING_COPY.holdout
244
224
  });
245
225
  const point = surface.flagPoints.find((point)=>point.id === doc.flagPoint);
246
226
  if (!point) return /*#__PURE__*/ _jsx("p", {
247
227
  children: SETTING_COPY.choose
248
228
  });
249
- return /*#__PURE__*/ _jsx("div", {
229
+ const ready = Object.entries(point.params).every(([key, field])=>!paramIssue(field, paramsForArm(arm, point)[key]));
230
+ return /*#__PURE__*/ _jsxs("div", {
250
231
  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}`))
232
+ children: [
233
+ /*#__PURE__*/ _jsx("span", {
234
+ className: `xp-content-chip ${ready ? 'xp-content-ready' : 'xp-content-empty'}`,
235
+ children: ready ? SETTING_COPY.valuesReady : SETTING_COPY.setValues
236
+ }),
237
+ Object.entries(point.params).map(([key, field])=>/*#__PURE__*/ _jsx(Parameter, {
238
+ test: test,
239
+ surface: surface,
240
+ armIndex: armIndex,
241
+ paramKey: key,
242
+ field: field
243
+ }, `${doc.id}:${point.id}:${arm.key}:${key}:${doc.status}:${surface.canManage}`))
244
+ ]
258
245
  });
259
246
  }
260
247
  export const settingParts = {
@@ -1,16 +1,4 @@
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(--xp-border);
9
- border-radius: var(--xp-radius);
10
- background: var(--xp-input-bg);
11
- color: var(--xp-text);
12
- }
13
- .xp-setting-values { display: grid; gap: 1rem; }
1
+ .xp-setting-values { display: grid; gap: var(--xp-space-3); }
14
2
  .xp-setting-value { white-space: pre-wrap; }
15
3
  .xp-setting-lock { color: var(--xp-text-muted); }
16
4
  .xp-setting-parameter fieldset { border: 0; padding: 0; margin: 0; }
@@ -25,5 +13,5 @@
25
13
  background: var(--xp-input-bg);
26
14
  color: var(--xp-text);
27
15
  }
28
- .xp-setting-segments input:checked + span { background: var(--xp-border); border-color: var(--xp-text); }
29
- .xp-setting-segments input:focus-visible + span { outline: 2px solid var(--xp-text); outline-offset: 2px; }
16
+ .xp-setting-segments input:checked + span { background: var(--xp-accent-subtle); border-color: var(--xp-accent); }
17
+ .xp-setting-segments input:focus-visible + span { outline: 2px solid var(--xp-focus); outline-offset: 2px; }
@@ -1,50 +1,65 @@
1
- .xp-tests-surface {
2
- min-height: 100dvh;
3
- width: 100%;
4
- color: var(--xp-text);
5
- background: var(--xp-bg);
1
+ /* Full-screen shell shared by every Tests surface. */
2
+ .xp-tests-surface { min-height: 100dvh; width: 100%; color: var(--xp-text); background: var(--xp-bg); font: var(--xp-text-body); }
3
+ .xp-tests-surface a:not(.xp-ui-button) { color: var(--xp-text); }
4
+ .xp-tests-surface a:not(.xp-ui-button):hover { text-decoration: underline; text-underline-offset: 3px; }
5
+ .xp-tests-container { box-sizing: border-box; width: 100%; max-width: 1120px; margin-inline: auto; padding-inline: var(--xp-space-8); }
6
+ .xp-tests-shell-header { background: var(--xp-surface); border-bottom: 1px solid var(--xp-border); }
7
+ .xp-tests-shell-top { display: flex; align-items: center; justify-content: space-between; gap: var(--xp-space-4); min-height: 48px; }
8
+ .xp-tests-shell-top .xp-ui-breadcrumb { min-width: 0; overflow-wrap: anywhere; }
9
+ .xp-tests-shell-top > .xp-ui-button { flex-shrink: 0; }
10
+ .xp-tests-shell-main { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--xp-space-6); padding-top: var(--xp-space-3); padding-bottom: var(--xp-space-6); }
11
+ .xp-tests-shell-title { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; min-width: 0; overflow-wrap: anywhere; }
12
+ .xp-tests-shell-title h1 { margin: 0; font: var(--xp-text-display); letter-spacing: -0.01em; }
13
+ .xp-tests-shell-actions { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: var(--xp-space-2); min-width: 0; }
14
+ .xp-tests-main { padding-block: var(--xp-space-8) 48px; }
15
+ .xp-tests-search { position: relative; width: 260px; max-width: 100%; }
16
+ .xp-tests-search label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
17
+ .xp-tests-search svg { position: absolute; top: 10px; left: 10px; width: 16px; height: 16px; color: var(--xp-text-3); pointer-events: none; }
18
+ .xp-tests-search input { padding-left: var(--xp-space-8); }
19
+ .xp-tests-group { margin-top: var(--xp-space-8); }
20
+ .xp-tests-group:first-child { margin-top: 0; }
21
+ .xp-tests-group-head { display: flex; align-items: baseline; gap: var(--xp-space-2); padding-inline: 2px; margin-bottom: 10px; }
22
+ .xp-tests-group-head h2, h2.xp-tests-group-head { margin: 0; font: 600 15px/22px var(--xp-font); }
23
+ .xp-tests-group-head > span { color: var(--xp-text-3); font: var(--xp-text-small); font-variant-numeric: tabular-nums; }
24
+ .xp-tests-list { border: 1px solid var(--xp-border); border-radius: var(--xp-radius-card); background: var(--xp-surface); }
25
+ .xp-tests-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--xp-space-2) var(--xp-space-6); padding: var(--xp-space-4) var(--xp-space-5); }
26
+ .xp-tests-row + .xp-tests-row { border-top: 1px solid var(--xp-border); }
27
+ .xp-tests-row-main { min-width: 0; overflow-wrap: anywhere; }
28
+ .xp-tests-row-name { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
29
+ .xp-tests-row-name > a { color: var(--xp-text); font: 500 15px/22px var(--xp-font); text-decoration: none; border-radius: var(--xp-radius-chip); }
30
+ .xp-tests-row-name > a:hover { text-decoration: underline; text-underline-offset: 3px; }
31
+ .xp-tests-row-name > a:focus-visible, .xp-tests-group > summary:focus-visible { outline: 2px solid var(--xp-focus); outline-offset: 2px; }
32
+ .xp-tests-kind { display: inline-flex; align-items: center; height: 22px; padding-inline: var(--xp-space-2); border: 1px solid var(--xp-border); border-radius: var(--xp-radius-chip); color: var(--xp-text-2); font: 500 12px/18px var(--xp-font); white-space: nowrap; }
33
+ .xp-tests-row-line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--xp-space-1) 14px; margin-top: var(--xp-space-1); color: var(--xp-text-2); font: 400 14px/20px var(--xp-font); }
34
+ .xp-tests-row-action { justify-self: end; }
35
+ .xp-tests-health { display: inline-flex; align-items: center; gap: 7px; font: 500 13px/18px var(--xp-font); }
36
+ .xp-tests-health::before { content: ''; width: var(--xp-space-2); height: var(--xp-space-2); border-radius: 50%; flex: none; }
37
+ .xp-tests-health--red::before { background: var(--xp-error); }
38
+ .xp-tests-health--amber::before { background: var(--xp-warning); }
39
+ .xp-tests-health--green::before { background: var(--xp-success); }
40
+ .xp-tests-group > summary { display: inline-flex; align-items: baseline; gap: var(--xp-space-2); cursor: pointer; list-style: none; border-radius: var(--xp-radius-chip); }
41
+ .xp-tests-group > summary::-webkit-details-marker { display: none; }
42
+ .xp-tests-group > summary .xp-tests-group-head { margin-bottom: 0; }
43
+ .xp-tests-group-chevron { align-self: center; color: var(--xp-text-3); }
44
+ .xp-tests-group[open] .xp-tests-group-chevron { transform: rotate(90deg); }
45
+ .xp-tests-group > summary + .xp-tests-list { margin-top: 10px; }
46
+ .xp-tests-limited { margin-top: var(--xp-space-4); color: var(--xp-text-2); font: var(--xp-text-small); }
47
+ .xp-tests-no-matches { display: flex; flex-wrap: wrap; align-items: center; gap: var(--xp-space-2); }
48
+ .xp-tests-no-matches p { margin: 0; }
49
+ @media (max-width: 720px) {
50
+ .xp-tests-container { padding-inline: var(--xp-space-4); }
51
+ .xp-tests-shell-main { flex-direction: column; align-items: stretch; gap: var(--xp-space-4); }
52
+ .xp-tests-shell-actions { justify-content: flex-start; }
53
+ .xp-tests-search { width: 100%; }
54
+ .xp-tests-row { grid-template-columns: minmax(0, 1fr); }
55
+ .xp-tests-row-action { justify-self: start; }
6
56
  }
7
- .xp-tests-shell-header {
8
- display: flex;
9
- align-items: center;
10
- gap: var(--xp-space-4);
11
- padding: var(--xp-space-5) var(--xp-space-6);
12
- border-bottom: 1px solid var(--xp-border);
13
- }
14
- .xp-tests-main { padding: var(--xp-space-6); }
15
- .xp-tests-surface a { color: var(--xp-text); }
16
- .xp-tests-toolbar, .xp-tests-card-heading {
17
- display: flex;
18
- flex-wrap: wrap;
19
- align-items: center;
20
- gap: var(--xp-space-4);
21
- }
22
- .xp-tests-toolbar { margin-bottom: var(--xp-space-6); }
57
+ /* Existing screens retain their controls until their design-system lanes. */
58
+ .xp-tests-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--xp-space-4); margin-bottom: var(--xp-space-6); }
23
59
  .xp-tests-toolbar h1 { margin: 0; margin-right: auto; }
24
60
  .xp-tests-toolbar label { display: grid; gap: var(--xp-space-1); }
25
- .xp-tests-toolbar input, .xp-tests-surface button {
26
- color: var(--xp-text);
27
- background: var(--xp-input-bg);
28
- border: 1px solid var(--xp-input-border);
29
- border-radius: var(--xp-radius);
30
- padding: var(--xp-space-2);
31
- }
32
- .xp-tests-primary {
33
- padding: var(--xp-space-2) var(--xp-space-4);
34
- border: 1px solid var(--xp-accent);
35
- border-radius: var(--xp-radius);
36
- }
37
- .xp-tests-group { margin-bottom: var(--xp-space-6); }
38
- .xp-tests-group summary { cursor: pointer; padding: var(--xp-space-4) 0; }
39
- .xp-tests-cards { display: grid; gap: var(--xp-space-4); }
40
- .xp-tests-card {
41
- border: 1px solid var(--xp-border);
42
- border-radius: var(--xp-radius-large);
43
- padding: var(--xp-space-5);
44
- background: var(--xp-surface);
45
- }
46
- .xp-tests-card h3 { margin: 0; margin-right: auto; }
47
- .xp-tests-health { width: var(--xp-space-2); height: var(--xp-space-2); border-radius: 50%; }
48
- .xp-tests-health--red { background: var(--xp-error); }
49
- .xp-tests-health--amber { background: var(--xp-warning); }
50
- .xp-tests-health--green { background: var(--xp-success); }
61
+ .xp-tests-toolbar input, .xp-tests-toolbar button:not(.xp-ui-button) { color: var(--xp-text); background: var(--xp-input-bg); border: 1px solid var(--xp-input-border); border-radius: var(--xp-radius); padding: var(--xp-space-2); }
62
+ .xp-tests-primary { color: var(--xp-text); padding: var(--xp-space-2) var(--xp-space-4); border: 1px solid var(--xp-accent); border-radius: var(--xp-radius); }
63
+ .xp-tests-shell-heading { min-width: 0; }
64
+ .xp-tests-shell-subtitle { margin-top: var(--xp-space-2); color: var(--xp-text-2); font: var(--xp-text-small); }
65
+ .xp-tests-shell-subtitle p { margin: 0; }