@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
@@ -3,16 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { theme } from '../theme.js';
4
4
  import { RESULTS_COPY as C, chartSummary, contentChangeTitle } from '../copy/results.js';
5
5
  import { chartModel } from './chart.model.js';
6
+ import { formatDate } from '../format/date.js';
6
7
  import { formatPercent } from './format.js';
7
- const colors = [
8
- theme.color.textStrong,
9
- theme.color.textMuted,
10
- theme.color.borderAccent
11
- ];
12
- const date = (t)=>new Intl.DateTimeFormat('en-US', {
13
- dateStyle: 'medium',
14
- timeZone: 'UTC'
15
- }).format(t);
8
+ import { seriesColor } from './DataTable.js';
16
9
  export function CiChart({ series, metricId, metricName, phase, contentChanges }) {
17
10
  const model = chartModel({
18
11
  series,
@@ -32,9 +25,6 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
32
25
  color: theme.color.text
33
26
  },
34
27
  children: [
35
- /*#__PURE__*/ _jsx("h2", {
36
- children: C.chart
37
- }),
38
28
  /*#__PURE__*/ _jsxs("svg", {
39
29
  viewBox: "0 0 760 280",
40
30
  style: {
@@ -42,8 +32,8 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
42
32
  height: 'auto'
43
33
  },
44
34
  role: "img",
45
- "aria-label": chartSummary(metricName, model.variants.map((variant)=>variant.label).join(', '), date(from), date(to), [
46
- ...new Set(model.markers.map((marker)=>date(marker.t)))
35
+ "aria-label": chartSummary(metricName, model.variants.map((variant)=>variant.label).join(', '), formatDate(from), formatDate(to), [
36
+ ...new Set(model.markers.map((marker)=>formatDate(marker.t)))
47
37
  ]),
48
38
  children: [
49
39
  /*#__PURE__*/ _jsx("line", {
@@ -70,7 +60,7 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
70
60
  y: 260,
71
61
  fill: "currentColor",
72
62
  fontSize: 12,
73
- children: date(from)
63
+ children: formatDate(from)
74
64
  }),
75
65
  /*#__PURE__*/ _jsx("text", {
76
66
  x: 680,
@@ -78,9 +68,9 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
78
68
  textAnchor: "end",
79
69
  fill: "currentColor",
80
70
  fontSize: 12,
81
- children: date(to)
71
+ children: formatDate(to)
82
72
  }),
83
- model.variants.map((variant, index)=>{
73
+ model.variants.map((variant)=>{
84
74
  const path = (points)=>{
85
75
  let connected = false;
86
76
  return points.map((point)=>{
@@ -93,7 +83,7 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
93
83
  return `${command}${x(point.t)},${y(point.value)}`;
94
84
  }).join(' ');
95
85
  };
96
- const color = colors[index % colors.length];
86
+ const color = seriesColor(variant.arm);
97
87
  const upper = variant.points.map((point)=>`${x(point.t)},${y(point.hi ?? high)}`);
98
88
  const lower = [
99
89
  ...variant.points
@@ -140,7 +130,7 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
140
130
  model.markers.map((marker, index)=>/*#__PURE__*/ _jsxs("g", {
141
131
  children: [
142
132
  /*#__PURE__*/ _jsx("title", {
143
- children: contentChangeTitle(date(marker.t))
133
+ children: contentChangeTitle(formatDate(marker.t))
144
134
  }),
145
135
  /*#__PURE__*/ _jsx("line", {
146
136
  x1: x(marker.t),
@@ -155,7 +145,7 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
155
145
  ]
156
146
  }),
157
147
  /*#__PURE__*/ _jsx("ul", {
158
- children: model.variants.map((variant, index)=>/*#__PURE__*/ _jsxs("li", {
148
+ children: model.variants.map((variant)=>/*#__PURE__*/ _jsxs("li", {
159
149
  style: {
160
150
  color: theme.color.text
161
151
  },
@@ -167,7 +157,7 @@ export function CiChart({ series, metricId, metricName, phase, contentChanges })
167
157
  width: '1em',
168
158
  height: '0.5em',
169
159
  marginRight: '0.5em',
170
- backgroundColor: colors[index % colors.length]
160
+ backgroundColor: seriesColor(variant.arm)
171
161
  }
172
162
  }),
173
163
  variant.label
@@ -1,4 +1,6 @@
1
1
  import type { DataQualityItem } from './results.model.js';
2
- export declare function DataQualityPanel({ items }: {
2
+ import './results.css';
3
+ export declare function DataQualityPanel({ items, problem }: {
3
4
  items: DataQualityItem[];
4
- }): import("react").JSX.Element | null;
5
+ problem?: boolean;
6
+ }): import("react").JSX.Element;
@@ -1,28 +1,37 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { theme } from '../theme.js';
4
- import { RESULTS_COPY } from '../copy/results.js';
5
- export function DataQualityPanel({ items }) {
6
- if (items.length === 0) return null;
7
- return /*#__PURE__*/ _jsxs("section", {
8
- className: "xp-data-quality",
9
- "aria-label": RESULTS_COPY.quality,
10
- style: {
11
- color: theme.color.text,
12
- background: theme.color.surface,
13
- padding: '1rem'
14
- },
3
+ import { RESULTS_COPY as C } from '../copy/results.js';
4
+ import './results.css';
5
+ const quality = (item)=>item.id === 'srm' || item.id.startsWith('version-page-unavailable') ? 'problem' : 'watch';
6
+ export function DataQualityPanel({ items, problem = false }) {
7
+ const overall = problem || items.some((item)=>quality(item) === 'problem') ? 'problem' : items.length ? 'watch' : 'ok';
8
+ return /*#__PURE__*/ _jsxs("details", {
9
+ className: "xp-results-panel xp-data-quality",
10
+ open: overall !== 'ok',
15
11
  children: [
16
- /*#__PURE__*/ _jsx("h2", {
17
- children: RESULTS_COPY.quality
12
+ /*#__PURE__*/ _jsxs("summary", {
13
+ children: [
14
+ C.quality,
15
+ /*#__PURE__*/ _jsx("span", {
16
+ className: `xp-quality-chip xp-quality-chip--${overall}`,
17
+ children: C.qualityStates[overall]
18
+ })
19
+ ]
18
20
  }),
19
- /*#__PURE__*/ _jsx("ul", {
20
- children: items.map((item)=>/*#__PURE__*/ _jsxs("li", {
21
+ /*#__PURE__*/ _jsx("section", {
22
+ "aria-label": C.quality,
23
+ children: items.length ? items.map((item)=>/*#__PURE__*/ _jsxs("div", {
24
+ className: "xp-quality-row",
21
25
  children: [
26
+ /*#__PURE__*/ _jsx("span", {
27
+ className: `xp-quality-chip xp-quality-chip--${quality(item)}`,
28
+ children: C.qualityStates[quality(item)]
29
+ }),
22
30
  /*#__PURE__*/ _jsx("p", {
23
31
  children: item.message
24
32
  }),
25
33
  item.detail ? /*#__PURE__*/ _jsx("p", {
34
+ className: "xp-quality-detail",
26
35
  children: item.detail
27
36
  }) : null,
28
37
  item.shares ? /*#__PURE__*/ _jsx("ul", {
@@ -31,10 +40,14 @@ export function DataQualityPanel({ items }) {
31
40
  }, index))
32
41
  }) : null,
33
42
  /*#__PURE__*/ _jsx("p", {
43
+ className: "xp-quality-next",
34
44
  children: item.nextStep
35
45
  })
36
46
  ]
37
- }, item.id))
47
+ }, item.id)) : /*#__PURE__*/ _jsx("p", {
48
+ className: "xp-quality-row",
49
+ children: C.qualityOK
50
+ })
38
51
  })
39
52
  ]
40
53
  });
@@ -0,0 +1,16 @@
1
+ import type { ArmRow } from './results.model.js';
2
+ export interface DataTableProps {
3
+ rows: ArmRow[];
4
+ caption: string;
5
+ method: 'sequential' | 'bayesian';
6
+ metricKind: 'binomial' | 'count' | 'revenue';
7
+ }
8
+ import './results.css';
9
+ export declare const seriesColor: (key: string) => "var(--xp-series-a)" | "var(--xp-series-b)" | "var(--xp-series-muted)" | "var(--xp-series-c)";
10
+ export declare function Swatch({ arm }: {
11
+ arm: string;
12
+ }): import("react").JSX.Element;
13
+ export declare function DataTable({ rows, caption, metricKind, leader, primary }: DataTableProps & {
14
+ leader?: string | null;
15
+ primary?: boolean;
16
+ }): import("react").JSX.Element;
@@ -0,0 +1,152 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from 'react';
4
+ import { RESULTS_COPY as C } from '../copy/results.js';
5
+ import { CappedCell } from './CappedCell.js';
6
+ import './results.css';
7
+ export const seriesColor = (key)=>key === 'variantA' ? 'var(--xp-series-a)' : key === 'variantB' ? 'var(--xp-series-b)' : key === 'variantC' ? 'var(--xp-series-c)' : 'var(--xp-series-muted)';
8
+ export function Swatch({ arm }) {
9
+ return /*#__PURE__*/ _jsx("span", {
10
+ className: "xp-results-swatch",
11
+ style: {
12
+ background: seriesColor(arm)
13
+ },
14
+ "aria-hidden": "true"
15
+ });
16
+ }
17
+ function updateScrollCues(wrap) {
18
+ wrap.parentElement?.classList.toggle('has-left', wrap.scrollLeft > 0);
19
+ wrap.parentElement?.classList.toggle('has-right', wrap.scrollLeft + wrap.clientWidth < wrap.scrollWidth - 1);
20
+ }
21
+ export function DataTable({ rows, caption, metricKind, leader, primary = false }) {
22
+ const scrollRef = useRef(null);
23
+ useEffect(()=>{
24
+ const wrap = scrollRef.current;
25
+ if (!wrap) return;
26
+ const update = ()=>updateScrollCues(wrap);
27
+ update();
28
+ const observer = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(update);
29
+ observer?.observe(wrap);
30
+ if (wrap.firstElementChild) observer?.observe(wrap.firstElementChild);
31
+ window.addEventListener('resize', update);
32
+ return ()=>{
33
+ observer?.disconnect();
34
+ window.removeEventListener('resize', update);
35
+ };
36
+ }, [
37
+ rows
38
+ ]);
39
+ return /*#__PURE__*/ _jsx("div", {
40
+ className: "xp-data-table-frame",
41
+ children: /*#__PURE__*/ _jsx("div", {
42
+ ref: scrollRef,
43
+ className: "xp-data-table-wrap",
44
+ tabIndex: 0,
45
+ role: "region",
46
+ "aria-label": caption,
47
+ onScroll: (event)=>updateScrollCues(event.currentTarget),
48
+ children: /*#__PURE__*/ _jsxs("table", {
49
+ className: "xp-data-table",
50
+ children: [
51
+ /*#__PURE__*/ _jsx("caption", {
52
+ children: caption
53
+ }),
54
+ /*#__PURE__*/ _jsx("thead", {
55
+ children: /*#__PURE__*/ _jsxs("tr", {
56
+ children: [
57
+ /*#__PURE__*/ _jsx("th", {
58
+ scope: "col",
59
+ children: C.group
60
+ }),
61
+ /*#__PURE__*/ _jsx("th", {
62
+ scope: "col",
63
+ className: "num",
64
+ children: C.people
65
+ }),
66
+ primary && metricKind === 'binomial' ? /*#__PURE__*/ _jsx("th", {
67
+ scope: "col",
68
+ className: "num",
69
+ children: C.conversions
70
+ }) : null,
71
+ /*#__PURE__*/ _jsx("th", {
72
+ scope: "col",
73
+ className: "num",
74
+ children: metricKind === 'binomial' ? C.rate : C.average
75
+ }),
76
+ /*#__PURE__*/ _jsx("th", {
77
+ scope: "col",
78
+ className: "num",
79
+ children: C.lift
80
+ }),
81
+ /*#__PURE__*/ _jsx("th", {
82
+ scope: "col",
83
+ className: "num",
84
+ children: C.ci
85
+ }),
86
+ primary ? /*#__PURE__*/ _jsx("th", {
87
+ scope: "col",
88
+ className: "num",
89
+ children: C.chanceToWin
90
+ }) : null
91
+ ]
92
+ })
93
+ }),
94
+ /*#__PURE__*/ _jsx("tbody", {
95
+ children: rows.map((row)=>/*#__PURE__*/ _jsxs("tr", {
96
+ className: primary && leader && row.key === leader ? 'is-lead' : undefined,
97
+ children: [
98
+ /*#__PURE__*/ _jsxs("th", {
99
+ scope: "row",
100
+ children: [
101
+ /*#__PURE__*/ _jsxs("span", {
102
+ className: "xp-results-group",
103
+ children: [
104
+ /*#__PURE__*/ _jsx(Swatch, {
105
+ arm: row.key
106
+ }),
107
+ row.label
108
+ ]
109
+ }),
110
+ row.key === 'holdout' && row.baseline ? /*#__PURE__*/ _jsx("div", {
111
+ className: "muted",
112
+ children: row.baseline
113
+ }) : null
114
+ ]
115
+ }),
116
+ /*#__PURE__*/ _jsx("td", {
117
+ className: "num",
118
+ children: row.n
119
+ }),
120
+ primary && metricKind === 'binomial' ? /*#__PURE__*/ _jsx("td", {
121
+ className: "num",
122
+ children: row.value
123
+ }) : null,
124
+ /*#__PURE__*/ _jsx("td", {
125
+ className: "num",
126
+ children: metricKind === 'revenue' ? /*#__PURE__*/ _jsx(CappedCell, {
127
+ model: {
128
+ ...row.capped,
129
+ notice: undefined
130
+ }
131
+ }) : row.rate ?? row.capped.statistic
132
+ }),
133
+ /*#__PURE__*/ _jsx("td", {
134
+ className: `num${row.baseline ? ' muted' : ''}`,
135
+ children: row.baseline ? '—' : row.lift ?? '—'
136
+ }),
137
+ /*#__PURE__*/ _jsx("td", {
138
+ className: `num${row.baseline ? ' muted' : ''}`,
139
+ children: row.baseline ? '—' : row.comparison?.method === 'sequential' ? row.comparison.adjustedCI ?? row.comparison.ci : row.comparison?.ci ?? '—'
140
+ }),
141
+ primary ? /*#__PURE__*/ _jsx("td", {
142
+ className: "num",
143
+ children: row.baseline ? '—' : row.chanceToWin ?? '—'
144
+ }) : null
145
+ ]
146
+ }, row.key))
147
+ })
148
+ ]
149
+ })
150
+ })
151
+ });
152
+ }
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from 'react';
2
+ import './results.css';
3
+ export declare function StatTile({ label, value, detail }: {
4
+ label: string;
5
+ value: ReactNode;
6
+ detail?: ReactNode;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import './results.css';
4
+ export function StatTile({ label, value, detail }) {
5
+ return /*#__PURE__*/ _jsxs("div", {
6
+ className: "xp-stat-tile",
7
+ children: [
8
+ /*#__PURE__*/ _jsx("div", {
9
+ className: "xp-stat-tile-label",
10
+ children: label
11
+ }),
12
+ /*#__PURE__*/ _jsx("div", {
13
+ className: "xp-stat-tile-value",
14
+ children: value
15
+ }),
16
+ detail ? /*#__PURE__*/ _jsx("div", {
17
+ className: "xp-stat-tile-detail",
18
+ children: detail
19
+ }) : null
20
+ ]
21
+ });
22
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { VerdictCardModel, guardrailChips } from './results.model.js';
3
+ export interface VerdictHeroProps {
4
+ model: VerdictCardModel;
5
+ guardrails?: ReturnType<typeof guardrailChips>;
6
+ power?: string | null;
7
+ collecting?: string | null;
8
+ sinceShipping?: string | null;
9
+ meta?: ReactNode;
10
+ recommendation?: string;
11
+ }
12
+ import './results.css';
13
+ export declare function VerdictHero({ model, guardrails, power, collecting, sinceShipping, meta, recommendation }: VerdictHeroProps): import("react").JSX.Element;
@@ -1,14 +1,9 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { theme } from '../theme.js';
4
- export function VerdictCard({ model, guardrails = [], power, collecting, sinceShipping }) {
3
+ import './results.css';
4
+ export function VerdictHero({ model, guardrails = [], power, collecting, sinceShipping, meta, recommendation }) {
5
5
  return /*#__PURE__*/ _jsxs("section", {
6
- className: "xp-verdict-card",
7
- style: {
8
- color: theme.color.text,
9
- background: theme.color.surface,
10
- padding: '1rem'
11
- },
6
+ className: "xp-verdict-hero",
12
7
  children: [
13
8
  sinceShipping ? /*#__PURE__*/ _jsx("p", {
14
9
  className: "xp-since-shipping",
@@ -30,10 +25,14 @@ export function VerdictCard({ model, guardrails = [], power, collecting, sinceSh
30
25
  className: "xp-collecting",
31
26
  children: collecting
32
27
  }) : null,
33
- guardrails.length > 0 ? /*#__PURE__*/ _jsx("ul", {
34
- className: "xp-guardrails",
28
+ recommendation ? /*#__PURE__*/ _jsx("p", {
29
+ className: "xp-recommendation",
30
+ children: recommendation
31
+ }) : null,
32
+ guardrails.length ? /*#__PURE__*/ _jsx("ul", {
33
+ className: "xp-results-chips",
35
34
  children: guardrails.map((item, index)=>/*#__PURE__*/ _jsxs("li", {
36
- className: "xp-chip",
35
+ className: "xp-tests-kind",
37
36
  children: [
38
37
  item.name,
39
38
  ": ",
@@ -44,7 +43,11 @@ export function VerdictCard({ model, guardrails = [], power, collecting, sinceSh
44
43
  !('title' in model) && power ? /*#__PURE__*/ _jsx("p", {
45
44
  className: "xp-power",
46
45
  children: power
47
- }) : null
46
+ }) : null,
47
+ /*#__PURE__*/ _jsx("div", {
48
+ className: "xp-verdict-meta",
49
+ children: meta
50
+ })
48
51
  ]
49
52
  });
50
53
  }
@@ -0,0 +1,17 @@
1
+ import type { DocId } from '../../types/contracts.js';
2
+ export interface PageReleaseDecision {
3
+ winnerArm?: string | null;
4
+ discardedAt?: string | null;
5
+ pagesReleasedAt?: string | null;
6
+ pagesReleasedBy?: string | null;
7
+ }
8
+ /** Mirrors the release-pages transition's gate; permission is checked by the caller. */
9
+ export declare function canReleasePages(doc: {
10
+ kind?: string;
11
+ status: string;
12
+ decision?: PageReleaseDecision | null;
13
+ }): boolean;
14
+ export declare function pagesReleasedBy(by: string | null | undefined, currentUserId?: DocId | null, author?: {
15
+ name?: string | null;
16
+ email?: string | null;
17
+ } | null): string | undefined;
@@ -0,0 +1,10 @@
1
+ import { RESULTS_COPY } from '../copy/results.js';
2
+ /** Mirrors the release-pages transition's gate; permission is checked by the caller. */ export function canReleasePages(doc) {
3
+ const decision = doc.decision;
4
+ return doc.kind === 'page' && doc.status === 'archived' && Boolean(decision?.winnerArm) && !decision?.discardedAt && !decision?.pagesReleasedAt;
5
+ }
6
+ export function pagesReleasedBy(by, currentUserId, author) {
7
+ if (by == null) return undefined;
8
+ if (currentUserId != null && by === String(currentUserId)) return RESULTS_COPY.you;
9
+ return author?.name ?? author?.email ?? undefined;
10
+ }
@@ -0,0 +1,64 @@
1
+ .xp-results { max-width: 1120px; display: grid; gap: var(--xp-space-8); min-width: 0; }
2
+ .xp-results h2, .xp-results h3, .xp-results p { margin: 0; }
3
+ .xp-verdict-hero { background: var(--xp-surface); border: 1px solid var(--xp-border); border-radius: var(--xp-radius-card); padding: 28px; min-width: 0; }
4
+ .xp-verdict-hero h2 { font: var(--xp-text-display); letter-spacing: -0.01em; max-width: 36ch; }
5
+ .xp-verdict-hero p { margin-top: var(--xp-space-2); color: var(--xp-text-2); }
6
+ .xp-verdict-meta { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--xp-space-3); margin-top: var(--xp-space-5); padding-top: var(--xp-space-4); border-top: 1px solid var(--xp-border); color: var(--xp-text-3); font: var(--xp-text-small); }
7
+ .xp-round-meta { display: flex; align-items: center; flex-wrap: wrap; gap: var(--xp-space-2); min-width: 0; }
8
+ .xp-round-meta .xp-ui-field { display: flex; align-items: center; gap: var(--xp-space-2); margin: 0; min-width: 0; }
9
+ .xp-round-meta select { width: auto; max-width: 100%; }
10
+ .xp-results-chips { display: flex; flex-wrap: wrap; gap: var(--xp-space-2); list-style: none; padding: 0; margin-bottom: 0; }
11
+ .xp-stat-tiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border: 1px solid var(--xp-border); border-radius: var(--xp-radius-card); background: var(--xp-surface); overflow: hidden; }
12
+ .xp-stat-tile { padding: 20px; min-width: 0; }
13
+ .xp-stat-tile + .xp-stat-tile { border-left: 1px solid var(--xp-border); }
14
+ .xp-stat-tile-label, .xp-stat-tile-detail { font: var(--xp-text-small); color: var(--xp-text-2); }
15
+ .xp-stat-tile-value { margin-top: 6px; font: 600 32px/36px var(--xp-font); font-variant-numeric: tabular-nums; }
16
+ .xp-stat-tile-detail { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
17
+ .xp-results-block { min-width: 0; }
18
+ .xp-results-block-head { display: flex; align-items: baseline; flex-wrap: wrap; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
19
+ .xp-results-block-head h2 { font: var(--xp-text-title); }
20
+ .xp-results-block-meta { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 8px; color: var(--xp-text-2); }
21
+ .xp-data-table-frame { position: relative; min-width: 0; border-radius: var(--xp-radius-card); }
22
+ .xp-data-table-wrap { overflow-x: auto; overflow-y: auto; max-height: 70vh; border: 1px solid var(--xp-border); border-radius: var(--xp-radius-card); background: var(--xp-surface); }
23
+ .xp-data-table-wrap:focus-visible { outline: 2px solid var(--xp-focus); outline-offset: 2px; }
24
+ /* Overlays stay visible above opaque sticky headers and the highlighted row. */
25
+ .xp-data-table-frame::before, .xp-data-table-frame::after { content: ''; position: absolute; top: 1px; bottom: 1px; width: var(--xp-space-6); z-index: 3; pointer-events: none; opacity: 0; }
26
+ .xp-data-table-frame::before { left: 1px; border-radius: var(--xp-radius-card) 0 0 var(--xp-radius-card); background: linear-gradient(to right, var(--xp-border-strong), transparent); }
27
+ .xp-data-table-frame::after { right: 1px; border-radius: 0 var(--xp-radius-card) var(--xp-radius-card) 0; background: linear-gradient(to left, var(--xp-border-strong), transparent); }
28
+ .xp-data-table-frame.has-left::before, .xp-data-table-frame.has-right::after { opacity: 1; }
29
+ .xp-data-table { width: 100%; border-collapse: separate; border-spacing: 0; font: 400 14px/20px var(--xp-font); }
30
+ .xp-data-table caption { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
31
+ .xp-data-table th, .xp-data-table td { padding: 12px 14px; white-space: nowrap; border-bottom: 1px solid var(--xp-border); text-align: left; }
32
+ .xp-data-table thead th { position: sticky; top: 0; z-index: 1; background: var(--xp-surface); font: var(--xp-text-small); color: var(--xp-text-2); }
33
+ .xp-data-table tr > :first-child { position: sticky; left: 0; z-index: 1; background: var(--xp-surface); }
34
+ .xp-data-table thead tr > :first-child { z-index: 2; }
35
+ .xp-data-table tbody th { font-weight: 400; }
36
+ .xp-data-table .num { text-align: right; font-variant-numeric: tabular-nums; }
37
+ .xp-data-table .muted { color: var(--xp-text-3); }
38
+ .xp-data-table tr:last-child > * { border-bottom: 0; }
39
+ .xp-data-table tr.is-lead > * { background: var(--xp-surface-2); font-weight: 600; }
40
+ .xp-results-group { display: inline-flex; align-items: center; gap: 8px; }
41
+ .xp-results-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 3px; flex: none; }
42
+ .xp-results-panel { border: 1px solid var(--xp-border); border-radius: var(--xp-radius-card); background: var(--xp-surface); min-width: 0; }
43
+ .xp-results-panel > summary { padding: 16px 20px; cursor: pointer; font: var(--xp-text-title); }
44
+ .xp-results-panel > summary:focus-visible { outline: 2px solid var(--xp-focus); outline-offset: 2px; }
45
+ .xp-results-panel > summary .xp-quality-chip { margin-left: 12px; }
46
+ .xp-quality-row { padding: 14px 20px; border-top: 1px solid var(--xp-border); display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 14px; }
47
+ .xp-quality-row > :not(:first-child) { grid-column: 2; }
48
+ .xp-quality-row ul { margin: 0; padding-left: 20px; }
49
+ .xp-quality-detail, .xp-quality-next, .xp-results-cap-help { color: var(--xp-text-2); font: var(--xp-text-small); }
50
+ .xp-quality-chip { display: inline-flex; align-self: start; padding: 2px 8px; border-radius: var(--xp-radius-chip); font: var(--xp-text-small); color: var(--xp-success); background: var(--xp-success-bg); }
51
+ .xp-quality-chip--watch { color: var(--xp-warning); background: var(--xp-warning-bg); }
52
+ .xp-quality-chip--problem { color: var(--xp-error); background: var(--xp-error-bg); }
53
+ .xp-ci-chart { padding: 20px; overflow-x: auto; }
54
+ .xp-ci-chart svg { display: block; min-width: 560px; }
55
+ .xp-ci-chart ul { display: flex; flex-wrap: wrap; gap: 16px; list-style: none; margin: 0; padding: 0; font: var(--xp-text-small); }
56
+ .xp-preview-links { display: grid; gap: 12px; }
57
+ .xp-preview-row { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--xp-border); padding-bottom: 12px; }
58
+ @media (max-width: 720px) {
59
+ .xp-stat-tiles { grid-template-columns: minmax(0, 1fr); }
60
+ .xp-stat-tile + .xp-stat-tile { border-left: 0; border-top: 1px solid var(--xp-border); }
61
+ .xp-verdict-hero { padding: 20px; }
62
+ .xp-verdict-hero h2 { font-size: 24px; line-height: 30px; }
63
+ }
64
+ .xp-results-as-of { color: var(--xp-text-2); }
@@ -66,6 +66,9 @@ export interface ArmRow {
66
66
  n: string;
67
67
  value: string;
68
68
  capped: CappedCellModel;
69
+ rate?: string;
70
+ lift?: string;
71
+ chanceToWin?: string;
69
72
  baseline?: string;
70
73
  comparison?: ComparisonModel;
71
74
  }
@@ -76,7 +79,7 @@ export declare function armRows({ snapshot, phase, metricId }: {
76
79
  }): ArmRow[];
77
80
  export declare function guardrailChips(snapshot: Snapshot): {
78
81
  name: string;
79
- status: "Failing" | "OK" | "Watch" | "Too little data";
82
+ status: "OK" | "Watch" | "Failing" | "Too little data";
80
83
  }[];
81
84
  interface RoundLineInput {
82
85
  snapshot: DisplaySnapshot;
@@ -85,6 +88,7 @@ interface RoundLineInput {
85
88
  }
86
89
  export declare function powerLine({ snapshot, phaseKind, status }: RoundLineInput): string | null;
87
90
  export interface ResultsContext {
91
+ status?: string;
88
92
  phase?: PhaseSummary;
89
93
  minExposuresPerArm?: number;
90
94
  inLayer?: boolean;
@@ -1,8 +1,9 @@
1
+ import { READINESS_COPY, versionPageUnavailableCopy, versionPageUncheckedCopy } from '../copy/readiness.js';
1
2
  import { armLabel, phaseKindLabel, verdictCodeLabel } from '../labels.js';
2
3
  import { RESULTS_COPY, cappedCount, collectingCopy, conflictCopy, decidedTitle, exclusionCopy, ignoredCopy, metricUnavailable, moreDays, roundLabel, shareCopy, shippingTitle, srmP, uncappedTotal } from '../copy/results.js';
3
4
  import { formatCI, formatCount, formatMetric, formatPercent, formatProbability } from './format.js';
4
5
  export function defaultRound({ phases, decision, status }) {
5
- if (status === 'decided' && decision) return decision.phase;
6
+ if ((status === 'decided' || status === 'archived') && decision) return decision.phase;
6
7
  return phases.reduce((latest, phase)=>latest === null || phase.phase > latest ? phase.phase : latest, null);
7
8
  }
8
9
  export function roundOptions(phases) {
@@ -61,6 +62,7 @@ export function armRows({ snapshot, phase, metricId }) {
61
62
  key: group.key,
62
63
  label: group.label,
63
64
  n: formatCount(arm?.n ?? snapshot.exposures.perArm[group.key]),
65
+ rate: cell.statistic,
64
66
  value: metric?.kind === 'binomial' ? formatCount(arm?.converters) : cell.statistic,
65
67
  capped: cell
66
68
  };
@@ -74,6 +76,8 @@ export function armRows({ snapshot, phase, metricId }) {
74
76
  };
75
77
  const comparison = metric?.comparisons.find((comparison)=>comparison.arm === group.key);
76
78
  if (!comparison) return row;
79
+ row.lift = formatPercent(comparison.lift, true);
80
+ row.chanceToWin = snapshot.method === 'bayesian' ? formatPercent(comparison.chanceToWin) : '—';
77
81
  const common = {
78
82
  ci: formatCI(comparison.ci),
79
83
  significance: RESULTS_COPY.significance[comparison.significant]
@@ -124,7 +128,11 @@ function smallest(values) {
124
128
  return collectingCopy(formatCount(smallest(keys.map((key)=>snapshot.exposures.perArm[key]))), formatCount(snapshot.minExposuresPerArm ?? ctx.minExposuresPerArm), formatCount(smallest(keys.map((key)=>primary?.perArm.find((arm)=>arm.arm === key)?.converters))), formatCount(primary ? snapshot.params.minSampleSizes[primary.metricId] : undefined));
125
129
  }
126
130
  export function dataQuality(snapshot, ctx = {}) {
127
- const items = [];
131
+ const items = (snapshot.health ?? []).map((item)=>({
132
+ id: `${item.code}:${item.path}`,
133
+ message: READINESS_COPY.needsAttention,
134
+ nextStep: item.code === 'version-page-unavailable' ? versionPageUnavailableCopy(item.path, ctx.status) : versionPageUncheckedCopy(item.path, ctx.status)
135
+ }));
128
136
  const srmTotal = Object.keys(snapshot.srm.expected).reduce((total, key)=>total + (snapshot.exposures.perArm[key] ?? 0), 0);
129
137
  if (snapshot.srm.flagged) items.push({
130
138
  id: 'srm',
@@ -1,7 +1,7 @@
1
1
  import type { DocId } from '../../types/contracts.js';
2
2
  import type { SurfaceProps } from './contracts.js';
3
- import './test/testPage.css';
4
3
  export declare function ResultsRoute(props: {
5
4
  id: DocId;
6
5
  surface: SurfaceProps;
6
+ onTitleChange?: (title: string) => void;
7
7
  }): import("react").JSX.Element;