@delmaredigital/payload-experiments 0.3.0-rc.1 → 0.4.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 (338) hide show
  1. package/README.md +172 -21
  2. package/dist/admin/MetricSourceSelect.js +3 -3
  3. package/dist/admin/PlacementStatus.d.ts +3 -0
  4. package/dist/admin/PlacementStatus.js +54 -0
  5. package/dist/admin/PreviewLinks.d.ts +2 -1
  6. package/dist/admin/PreviewLinks.js +47 -26
  7. package/dist/admin/TestsNavLink.d.ts +3 -1
  8. package/dist/admin/TestsNavLink.js +2 -2
  9. package/dist/admin/actions/ConfirmDialog.d.ts +2 -1
  10. package/dist/admin/actions/ConfirmDialog.js +2 -2
  11. package/dist/admin/actions/DecideDialog.d.ts +2 -1
  12. package/dist/admin/actions/DecideDialog.js +2 -2
  13. package/dist/admin/api.d.ts +11 -2
  14. package/dist/admin/api.js +20 -2
  15. package/dist/admin/armSelect/ArmSelectField.js +14 -22
  16. package/dist/admin/arms/arms.model.d.ts +1 -21
  17. package/dist/admin/arms/arms.model.js +1 -153
  18. package/dist/admin/blocks/BlockPanel.d.ts +4 -0
  19. package/dist/admin/blocks/BlockPanel.js +153 -0
  20. package/dist/admin/blocks/TestBlockField.d.ts +5 -0
  21. package/dist/admin/blocks/TestBlockField.js +163 -0
  22. package/dist/admin/blocks/formApi.d.ts +43 -0
  23. package/dist/admin/blocks/formApi.js +153 -0
  24. package/dist/admin/blocks/inject.d.ts +7 -0
  25. package/dist/admin/blocks/inject.js +41 -0
  26. package/dist/admin/blocks/model.d.ts +17 -0
  27. package/dist/admin/blocks/model.js +45 -0
  28. package/dist/admin/blocks/statusCache.d.ts +7 -0
  29. package/dist/admin/blocks/statusCache.js +83 -0
  30. package/dist/admin/blocks/useBlockForm.d.ts +17 -0
  31. package/dist/admin/blocks/useBlockForm.js +46 -0
  32. package/dist/admin/copy/actions.d.ts +2 -0
  33. package/dist/admin/copy/actions.js +9 -0
  34. package/dist/admin/copy/arms.d.ts +1 -6
  35. package/dist/admin/copy/arms.js +1 -16
  36. package/dist/admin/copy/blocks.d.ts +27 -0
  37. package/dist/admin/copy/blocks.js +38 -0
  38. package/dist/admin/copy/goals.d.ts +2 -0
  39. package/dist/admin/copy/goals.js +4 -1
  40. package/dist/admin/copy/newTest.d.ts +10 -0
  41. package/dist/admin/copy/newTest.js +10 -0
  42. package/dist/admin/copy/pages.d.ts +26 -0
  43. package/dist/admin/copy/pages.js +26 -0
  44. package/dist/admin/copy/readiness.d.ts +25 -3
  45. package/dist/admin/copy/readiness.js +25 -3
  46. package/dist/admin/copy/results.d.ts +3 -0
  47. package/dist/admin/copy/results.js +4 -1
  48. package/dist/admin/copy/review.d.ts +1 -1
  49. package/dist/admin/copy/review.js +1 -1
  50. package/dist/admin/copy/surface.d.ts +0 -1
  51. package/dist/admin/copy/surface.js +0 -1
  52. package/dist/admin/copy/testPage.d.ts +4 -0
  53. package/dist/admin/copy/testPage.js +4 -0
  54. package/dist/admin/goals/XpGoalField.d.ts +2 -0
  55. package/dist/admin/goals/XpGoalField.js +152 -0
  56. package/dist/admin/metricSource.model.d.ts +2 -0
  57. package/dist/admin/metricSource.model.js +4 -0
  58. package/dist/admin/pageCollections.d.ts +10 -0
  59. package/dist/admin/pageCollections.js +20 -0
  60. package/dist/admin/paths.d.ts +8 -1
  61. package/dist/admin/paths.js +8 -1
  62. package/dist/admin/placementStatus.model.d.ts +2 -0
  63. package/dist/admin/placementStatus.model.js +19 -0
  64. package/dist/admin/placements/placementLabels.d.ts +11 -0
  65. package/dist/admin/placements/placementLabels.js +21 -0
  66. package/dist/admin/placements/placementLabels.model.d.ts +13 -0
  67. package/dist/admin/placements/placementLabels.model.js +12 -0
  68. package/dist/admin/placements/usePlacementLabels.d.ts +12 -0
  69. package/dist/admin/placements/usePlacementLabels.js +102 -0
  70. package/dist/admin/previewLinks.model.d.ts +2 -0
  71. package/dist/admin/previewLinks.model.js +3 -0
  72. package/dist/admin/results/DataQualityPanel.js +4 -1
  73. package/dist/admin/results/legacy.d.ts +16 -0
  74. package/dist/admin/results/legacy.js +27 -0
  75. package/dist/admin/results/results.model.d.ts +9 -6
  76. package/dist/admin/results/results.model.js +22 -9
  77. package/dist/admin/results/snapshot.d.ts +14 -0
  78. package/dist/admin/results/snapshot.js +3 -0
  79. package/dist/admin/surface/TestsHome.js +43 -15
  80. package/dist/admin/surface/TestsSurface.js +5 -1
  81. package/dist/admin/surface/contracts.d.ts +6 -5
  82. package/dist/admin/surface/goals/GoalForm.js +67 -34
  83. package/dist/admin/surface/goals/GoalsList.js +3 -3
  84. package/dist/admin/surface/goals/data.js +19 -16
  85. package/dist/admin/surface/goals/goals.model.d.ts +1 -2
  86. package/dist/admin/surface/goals/goals.model.js +1 -3
  87. package/dist/admin/surface/home.model.d.ts +2 -1
  88. package/dist/admin/surface/home.model.js +8 -4
  89. package/dist/admin/surface/newTest/NewTest.js +58 -17
  90. package/dist/admin/surface/newTest/PageWizard.d.ts +7 -0
  91. package/dist/admin/surface/newTest/PageWizard.js +209 -0
  92. package/dist/admin/surface/pagePicker/CopyRoute.d.ts +6 -0
  93. package/dist/admin/surface/pagePicker/CopyRoute.js +53 -0
  94. package/dist/admin/surface/pagePicker/PagePicker.d.ts +10 -0
  95. package/dist/admin/surface/pagePicker/PagePicker.js +172 -0
  96. package/dist/admin/surface/pagePicker/pagePicker.model.d.ts +25 -0
  97. package/dist/admin/surface/pagePicker/pagePicker.model.js +25 -0
  98. package/dist/admin/surface/readiness/ActionDialogs.js +103 -69
  99. package/dist/admin/surface/readiness/Banner.js +30 -6
  100. package/dist/admin/surface/readiness/readiness.model.d.ts +8 -1
  101. package/dist/admin/surface/readiness/readiness.model.js +95 -6
  102. package/dist/admin/surface/results/ReleasePages.d.ts +3 -1
  103. package/dist/admin/surface/results/ReleasePages.js +3 -7
  104. package/dist/admin/surface/results/ResultsBody.js +23 -3
  105. package/dist/admin/surface/results/ResultsHeader.js +2 -5
  106. package/dist/admin/surface/results/recommendation.model.d.ts +3 -2
  107. package/dist/admin/surface/results/recommendation.model.js +10 -3
  108. package/dist/admin/surface/results/useResults.d.ts +10 -1
  109. package/dist/admin/surface/surface.css +0 -1
  110. package/dist/admin/surface/test/AdvancedSection.js +111 -104
  111. package/dist/admin/surface/test/GoalSection.js +97 -22
  112. package/dist/admin/surface/test/LengthSection.js +1 -1
  113. package/dist/admin/surface/test/PageHoldout.d.ts +3 -0
  114. package/dist/admin/surface/test/PageHoldout.js +67 -0
  115. package/dist/admin/surface/test/TestPage.js +2 -1
  116. package/dist/admin/surface/test/VersionsSection.js +12 -5
  117. package/dist/admin/surface/test/fields.d.ts +13 -4
  118. package/dist/admin/surface/test/fields.js +46 -14
  119. package/dist/admin/surface/test/jumpToSection.d.ts +1 -1
  120. package/dist/admin/surface/test/jumpToSection.js +4 -3
  121. package/dist/admin/surface/test/pageParts.d.ts +4 -1
  122. package/dist/admin/surface/test/pageParts.js +79 -2
  123. package/dist/admin/surface/test/testPage.css +8 -1
  124. package/dist/admin/surface/test/testPage.model.d.ts +2 -3
  125. package/dist/admin/surface/test/testPage.model.js +1 -21
  126. package/dist/admin/surface/testDocument.model.d.ts +1 -0
  127. package/dist/admin/surface/testDocument.model.js +2 -1
  128. package/dist/admin/surface/useTestDocument.d.ts +11 -2
  129. package/dist/admin/surface/useTestDocument.js +55 -16
  130. package/dist/admin/useApi.d.ts +11 -2
  131. package/dist/admin/useApi.js +3 -5
  132. package/dist/admin/useLayerUsage.js +14 -23
  133. package/dist/admin/views/ExperimentsRootView.js +58 -16
  134. package/dist/admin/views/TestsNavLinkServer.d.ts +2 -0
  135. package/dist/admin/views/TestsNavLinkServer.js +43 -0
  136. package/dist/arms/copy.d.ts +6 -0
  137. package/dist/arms/copy.js +16 -0
  138. package/dist/arms/model.d.ts +23 -0
  139. package/dist/arms/model.js +173 -0
  140. package/dist/blocks/bindings.d.ts +3 -0
  141. package/dist/blocks/bindings.js +34 -0
  142. package/dist/blocks/createExperimentBlock.d.ts +6 -0
  143. package/dist/blocks/createExperimentBlock.js +77 -0
  144. package/dist/blocks/formState.d.ts +31 -0
  145. package/dist/blocks/formState.js +146 -0
  146. package/dist/blocks/model.d.ts +41 -0
  147. package/dist/blocks/model.js +34 -0
  148. package/dist/client/ExperimentGroup.d.ts +6 -0
  149. package/dist/client/ExperimentGroup.js +9 -0
  150. package/dist/client/ExperimentsProvider.js +20 -4
  151. package/dist/client/clickGoals.d.ts +4 -0
  152. package/dist/client/clickGoals.js +43 -0
  153. package/dist/collections/experiments/config.js +12 -2
  154. package/dist/collections/experiments/deleteGuard.d.ts +2 -1
  155. package/dist/collections/experiments/deleteGuard.js +12 -1
  156. package/dist/collections/experiments/editability.js +4 -2
  157. package/dist/collections/experiments/pickSlug.d.ts +2 -0
  158. package/dist/collections/experiments/pickSlug.js +8 -0
  159. package/dist/collections/labels.d.ts +3 -0
  160. package/dist/collections/labels.js +20 -0
  161. package/dist/collections/metrics.js +12 -2
  162. package/dist/collections/phases.js +1 -0
  163. package/dist/collections/serialize.js +7 -0
  164. package/dist/db/appends.d.ts +6 -2
  165. package/dist/db/appends.js +10 -4
  166. package/dist/db/clickGoals.d.ts +9 -0
  167. package/dist/db/clickGoals.js +22 -0
  168. package/dist/db/delete.d.ts +1 -0
  169. package/dist/db/delete.js +1 -0
  170. package/dist/db/locks.d.ts +14 -0
  171. package/dist/db/locks.js +36 -1
  172. package/dist/db/names.d.ts +4 -0
  173. package/dist/db/names.js +4 -0
  174. package/dist/db/pageBindings.d.ts +4 -0
  175. package/dist/db/pageBindings.js +17 -0
  176. package/dist/db/pageBusy.d.ts +2 -0
  177. package/dist/db/pageBusy.js +10 -0
  178. package/dist/db/pageContent.d.ts +10 -0
  179. package/dist/db/pageContent.js +79 -0
  180. package/dist/db/pageOccupancy.d.ts +12 -0
  181. package/dist/db/pageOccupancy.js +45 -0
  182. package/dist/db/placements.d.ts +33 -0
  183. package/dist/db/placements.js +54 -0
  184. package/dist/db/slugs.d.ts +8 -0
  185. package/dist/db/slugs.js +40 -0
  186. package/dist/db/snapshots.d.ts +6 -0
  187. package/dist/db/snapshots.js +9 -5
  188. package/dist/endpoints/clickKeys.d.ts +5 -0
  189. package/dist/endpoints/clickKeys.js +17 -0
  190. package/dist/endpoints/events.js +4 -1
  191. package/dist/endpoints/index.js +28 -0
  192. package/dist/endpoints/pages.d.ts +5 -0
  193. package/dist/endpoints/pages.js +120 -0
  194. package/dist/endpoints/placementStatus.d.ts +6 -0
  195. package/dist/endpoints/placementStatus.js +238 -0
  196. package/dist/endpoints/readBody.d.ts +2 -0
  197. package/dist/endpoints/readBody.js +24 -0
  198. package/dist/endpoints/results.js +5 -1
  199. package/dist/endpoints/summary.js +11 -4
  200. package/dist/endpoints/wrapBlock.d.ts +6 -0
  201. package/dist/endpoints/wrapBlock.js +76 -0
  202. package/dist/errors.d.ts +3 -3
  203. package/dist/errors.js +4 -4
  204. package/dist/events/rows.d.ts +1 -1
  205. package/dist/events/rows.js +5 -3
  206. package/dist/exports/adminClient.d.ts +5 -0
  207. package/dist/exports/adminClient.js +5 -0
  208. package/dist/exports/adminRsc.d.ts +1 -0
  209. package/dist/exports/adminRsc.js +1 -0
  210. package/dist/exports/blocks.d.ts +2 -0
  211. package/dist/exports/blocks.js +1 -0
  212. package/dist/exports/client.d.ts +1 -0
  213. package/dist/exports/client.js +1 -0
  214. package/dist/exports/index.d.ts +6 -1
  215. package/dist/exports/index.js +3 -0
  216. package/dist/exports/puck.d.ts +5 -2
  217. package/dist/exports/puck.js +3 -1
  218. package/dist/exports/puckEditor.d.ts +6 -1
  219. package/dist/exports/puckEditor.js +4 -1
  220. package/dist/exports/rsc.d.ts +4 -0
  221. package/dist/exports/rsc.js +2 -0
  222. package/dist/fields/xpGoal.d.ts +3 -0
  223. package/dist/fields/xpGoal.js +28 -0
  224. package/dist/globals/state.js +10 -0
  225. package/dist/goals/cache.d.ts +3 -0
  226. package/dist/goals/cache.js +35 -0
  227. package/dist/goals/fairness.d.ts +40 -0
  228. package/dist/goals/fairness.js +200 -0
  229. package/dist/goals/key.d.ts +4 -0
  230. package/dist/goals/key.js +3 -0
  231. package/dist/goals/picker/clickGoalPicker.model.d.ts +29 -0
  232. package/dist/goals/picker/clickGoalPicker.model.js +45 -0
  233. package/dist/goals/picker/copy.d.ts +16 -0
  234. package/dist/goals/picker/copy.js +15 -0
  235. package/dist/goals/picker/useClickGoalPicker.d.ts +30 -0
  236. package/dist/goals/picker/useClickGoalPicker.js +220 -0
  237. package/dist/hooks/pageGuard.d.ts +5 -1
  238. package/dist/hooks/pageGuard.js +160 -80
  239. package/dist/lib/fetchAllDocs.d.ts +16 -0
  240. package/dist/lib/fetchAllDocs.js +56 -0
  241. package/dist/onInit.js +29 -0
  242. package/dist/options.d.ts +2 -0
  243. package/dist/options.js +43 -10
  244. package/dist/plugin.js +45 -28
  245. package/dist/puck/ExperimentPicker.js +47 -16
  246. package/dist/puck/ExperimentSlot.d.ts +51 -1
  247. package/dist/puck/ExperimentSlot.js +23 -12
  248. package/dist/puck/copy.d.ts +6 -4
  249. package/dist/puck/copy.js +6 -4
  250. package/dist/puck/editor/CreateExperimentDrawer.js +46 -56
  251. package/dist/puck/editor/ExperimentSlotEditor.d.ts +52 -1
  252. package/dist/puck/editor/ExperimentSlotEditor.js +292 -137
  253. package/dist/puck/editor/ResultsReadout.d.ts +6 -0
  254. package/dist/puck/editor/ResultsReadout.js +99 -0
  255. package/dist/puck/editor/VersionBuilder.d.ts +34 -0
  256. package/dist/puck/editor/VersionBuilder.js +317 -0
  257. package/dist/puck/editor/armWrites.d.ts +7 -0
  258. package/dist/puck/editor/armWrites.js +71 -0
  259. package/dist/puck/editor/armsCache.d.ts +14 -0
  260. package/dist/puck/editor/armsCache.js +78 -0
  261. package/dist/puck/editor/copy.d.ts +33 -1
  262. package/dist/puck/editor/copy.js +45 -8
  263. package/dist/puck/editor/createTest.d.ts +1 -1
  264. package/dist/puck/editor/createTest.js +25 -14
  265. package/dist/puck/editor/editor.chrome.css +48 -161
  266. package/dist/puck/editor/editor.css +48 -161
  267. package/dist/puck/editor/editor.model.d.ts +8 -3
  268. package/dist/puck/editor/editor.model.js +16 -10
  269. package/dist/puck/editor/experimentOutlinePlugin.d.ts +3 -0
  270. package/dist/puck/editor/experimentOutlinePlugin.js +89 -0
  271. package/dist/puck/editor/integration.d.ts +10 -0
  272. package/dist/puck/editor/integration.js +30 -0
  273. package/dist/puck/editor/xpGoalPuckEditorField.d.ts +7 -0
  274. package/dist/puck/editor/xpGoalPuckEditorField.js +134 -0
  275. package/dist/puck/experimentSlotField.d.ts +2 -0
  276. package/dist/puck/experimentSlotField.js +11 -3
  277. package/dist/puck/hostContext.d.ts +10 -0
  278. package/dist/puck/hostContext.js +12 -0
  279. package/dist/puck/integration.d.ts +6 -0
  280. package/dist/puck/integration.js +21 -0
  281. package/dist/puck/routes.d.ts +12 -0
  282. package/dist/puck/routes.js +11 -0
  283. package/dist/puck/slotConfig.d.ts +1 -1
  284. package/dist/puck/slotConfig.js +2 -2
  285. package/dist/puck/xpGoalField.d.ts +3 -0
  286. package/dist/puck/xpGoalField.js +6 -0
  287. package/dist/results/health.d.ts +4 -0
  288. package/dist/results/health.js +27 -0
  289. package/dist/results/snapshot.js +15 -2
  290. package/dist/sanitizeReadinessDetail.js +51 -1
  291. package/dist/server/metadata.js +9 -2
  292. package/dist/server/normaliseContentRelationships.d.ts +7 -0
  293. package/dist/server/normaliseContentRelationships.js +62 -0
  294. package/dist/server/pageKindSeoContract.d.ts +27 -0
  295. package/dist/server/pageKindSeoContract.js +80 -0
  296. package/dist/server/recordContentChange.d.ts +1 -1
  297. package/dist/server/recordContentChange.js +72 -13
  298. package/dist/server/resolveExperimentBlocks.d.ts +5 -0
  299. package/dist/server/resolveExperimentBlocks.js +54 -0
  300. package/dist/server/runResults.js +92 -19
  301. package/dist/server/toClientAssignments.js +2 -1
  302. package/dist/transitions/addMetric.d.ts +2 -1
  303. package/dist/transitions/addMetric.js +28 -1
  304. package/dist/transitions/checkStart.d.ts +17 -1
  305. package/dist/transitions/checkStart.js +99 -7
  306. package/dist/transitions/decide.js +40 -1
  307. package/dist/transitions/deleteExperiment.d.ts +3 -1
  308. package/dist/transitions/deleteExperiment.js +27 -1
  309. package/dist/transitions/kernel.js +4 -0
  310. package/dist/transitions/makeContent.d.ts +5 -0
  311. package/dist/transitions/makeContent.js +39 -11
  312. package/dist/transitions/pageCollection.d.ts +10 -0
  313. package/dist/transitions/pageCollection.js +20 -0
  314. package/dist/transitions/pageVersions.d.ts +10 -0
  315. package/dist/transitions/pageVersions.js +44 -0
  316. package/dist/transitions/phaseRecord.d.ts +1 -0
  317. package/dist/transitions/placementInteractions.d.ts +40 -0
  318. package/dist/transitions/placementInteractions.js +94 -0
  319. package/dist/transitions/placements.d.ts +10 -6
  320. package/dist/transitions/placements.js +156 -2
  321. package/dist/transitions/preflightData.d.ts +8 -0
  322. package/dist/transitions/preflightData.js +248 -36
  323. package/dist/transitions/resolveCaps.d.ts +1 -0
  324. package/dist/transitions/resolveCaps.js +6 -2
  325. package/dist/transitions/start.js +77 -3
  326. package/dist/types/admin.d.ts +1 -1
  327. package/dist/types/assignments.d.ts +1 -0
  328. package/dist/types/contracts.d.ts +31 -10
  329. package/dist/types/endpoints.d.ts +92 -5
  330. package/dist/types/index.d.ts +3 -0
  331. package/dist/types/puck.d.ts +7 -1
  332. package/dist/types/snapshot.d.ts +17 -4
  333. package/dist/types/surface.d.ts +13 -0
  334. package/dist/ui/Tabs.d.ts +4 -2
  335. package/dist/ui/Tabs.js +33 -26
  336. package/dist/ui/accent.d.ts +22 -3
  337. package/dist/ui/accent.js +74 -16
  338. package/package.json +12 -2
@@ -0,0 +1,102 @@
1
+ 'use client';
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { createApi } from '../api.js';
4
+ import { placementDetail, placementDetailKey, preflightPlacementLabel, resolvedPlacementLabel } from './placementLabels.js';
5
+ export { placementDetailKey } from './placementLabels.js';
6
+ /** The view owns a cache, including misses, so repeated warnings never repeat reads. */ export function usePlacementLabels({ details, preflight, apiRoute, adminRoute, canManage, collectionTitles }) {
7
+ const titlesSignature = JSON.stringify(collectionTitles);
8
+ const cache = useMemo(()=>new Map(), [
9
+ apiRoute,
10
+ adminRoute,
11
+ titlesSignature
12
+ ]);
13
+ const mounted = useRef(true);
14
+ useEffect(()=>{
15
+ mounted.current = true;
16
+ return ()=>{
17
+ mounted.current = false;
18
+ };
19
+ }, []);
20
+ const [, refresh] = useState(0);
21
+ const signature = JSON.stringify(details.map(placementDetail));
22
+ useEffect(()=>{
23
+ if (!canManage || preflight) return;
24
+ const pending = [
25
+ ...new Map(JSON.parse(signature).map((detail)=>[
26
+ placementDetailKey(detail),
27
+ detail
28
+ ])).values()
29
+ ].filter((detail)=>!cache.has(placementDetailKey(detail)));
30
+ const blocks = new Map();
31
+ const jobs = [];
32
+ const save = (value, label)=>{
33
+ cache.set(placementDetailKey(value), label);
34
+ };
35
+ for (const value of pending){
36
+ const detail = placementDetail(value);
37
+ // Reserve immediately: repeated renders do not issue duplicate requests.
38
+ save(value, resolvedPlacementLabel(value, undefined, adminRoute));
39
+ if (!detail.collection) continue;
40
+ if (detail.id != null) {
41
+ blocks.set(detail.collection, [
42
+ ...blocks.get(detail.collection) ?? [],
43
+ value
44
+ ]);
45
+ continue;
46
+ }
47
+ if (!detail.path) continue;
48
+ jobs.push(createApi(apiRoute).pages({
49
+ collection: detail.collection,
50
+ path: detail.path
51
+ }).then((result)=>{
52
+ if (result.ok) save(value, resolvedPlacementLabel(value, result.data?.docs[0], adminRoute));
53
+ }));
54
+ }
55
+ for (const [collection, values] of blocks)jobs.push((async ()=>{
56
+ try {
57
+ const ids = [
58
+ ...new Set(values.map((value)=>String(placementDetail(value).id)))
59
+ ];
60
+ const query = new URLSearchParams({
61
+ 'where[id][in]': ids.join(','),
62
+ depth: '0',
63
+ limit: String(ids.length)
64
+ });
65
+ const response = await fetch(`${apiRoute.replace(/\/$/, '')}/${encodeURIComponent(collection)}?${query}`, {
66
+ credentials: 'include'
67
+ });
68
+ if (!response.ok) return;
69
+ const data = await response.json();
70
+ const titleField = collectionTitles?.[collection];
71
+ for (const value of values){
72
+ const doc = data.docs?.find((doc)=>String(doc.id) === String(placementDetail(value).id));
73
+ if (doc) save(value, resolvedPlacementLabel(value, {
74
+ id: placementDetail(value).id,
75
+ title: titleField && typeof doc[titleField] === 'string' ? doc[titleField] : undefined
76
+ }, adminRoute));
77
+ }
78
+ } catch {}
79
+ })());
80
+ void Promise.all(jobs).then(()=>{
81
+ if (mounted.current && jobs.length) refresh((value)=>value + 1);
82
+ });
83
+ }, [
84
+ signature,
85
+ preflight,
86
+ apiRoute,
87
+ adminRoute,
88
+ canManage,
89
+ collectionTitles,
90
+ cache
91
+ ]);
92
+ return new Map(details.map((detail)=>{
93
+ const label = preflight ? preflightPlacementLabel(detail, preflight, adminRoute) : cache.get(placementDetailKey(detail)) ?? resolvedPlacementLabel(detail, undefined, adminRoute);
94
+ // Results-only readers may not be able to open the collection, so they get the name without a link.
95
+ return [
96
+ placementDetailKey(detail),
97
+ canManage ? label : {
98
+ title: label.title
99
+ }
100
+ ];
101
+ }));
102
+ }
@@ -6,4 +6,6 @@ type PreviewPage = Placement & {
6
6
  };
7
7
  /** Preview URLs identify pages; counts use the most placements in any one version. */
8
8
  export declare function previewPages(placements: readonly Placement[], flagPoint?: string): PreviewPage[];
9
+ /** Pages that offer something to do: a preview or an editor link. The section is hidden when none do. */
10
+ export declare function actionablePreviewPages(placements: readonly Placement[], flagPoint?: string): PreviewPage[];
9
11
  export {};
@@ -30,3 +30,6 @@
30
30
  ...pages.values()
31
31
  ].map(({ page })=>page);
32
32
  }
33
+ /** Pages that offer something to do: a preview or an editor link. The section is hidden when none do. */ export function actionablePreviewPages(placements, flagPoint) {
34
+ return previewPages(placements, flagPoint).filter((page)=>page.url || page.editorUrl);
35
+ }
@@ -41,7 +41,10 @@ export function DataQualityPanel({ items, problem = false }) {
41
41
  }) : null,
42
42
  /*#__PURE__*/ _jsx("p", {
43
43
  className: "xp-quality-next",
44
- children: item.nextStep
44
+ children: item.href ? /*#__PURE__*/ _jsx("a", {
45
+ href: item.href,
46
+ children: item.nextStep
47
+ }) : item.nextStep
45
48
  })
46
49
  ]
47
50
  }, item.id)) : /*#__PURE__*/ _jsx("p", {
@@ -0,0 +1,16 @@
1
+ import type { PhaseSummary } from '../../types/endpoints.js';
2
+ import type { ResultsContext } from './results.model.js';
3
+ import type { LegacySnapshot } from './snapshot.js';
4
+ /** Stands in for CORE-15a winnerArm, borrowing the live decision only for the newest rollout. */
5
+ export declare function legacyRolloutLine(snapshot: LegacySnapshot, phase: PhaseSummary, phases: PhaseSummary[], decision?: {
6
+ winnerArm?: string;
7
+ }): string;
8
+ /** Stands in for CORE-15a minExposuresPerArm; absent historical minima keep the old dash. */
9
+ export declare function legacyMinimum(snapshot: LegacySnapshot, ctx: ResultsContext): number | undefined;
10
+ /** Stands in for CORE-15a minExposuresPerArm by deriving the old notice from the frozen verdict. */
11
+ export declare function legacyTooFew(snapshot: LegacySnapshot): boolean;
12
+ /** Stands in for CORE-15a winnerArm in rollout recommendations using the old recommended arm and label. */
13
+ export declare function legacyRecommendationWinner(snapshot: LegacySnapshot, phase?: PhaseSummary): {
14
+ key: string | null;
15
+ label: string;
16
+ };
@@ -0,0 +1,27 @@
1
+ import { RESULTS_COPY, shippingTitle } from '../copy/results.js';
2
+ import { armLabel } from '../labels.js';
3
+ /** Stands in for CORE-15a winnerArm, borrowing the live decision only for the newest rollout. */ export function legacyRolloutLine(snapshot, phase, phases, decision) {
4
+ const newestRollout = phases.filter((phase)=>phase.phaseKind === 'rollout').reduce((latest, phase)=>latest === null || phase.phase > latest ? phase.phase : latest, null);
5
+ const winner = snapshot.winnerArm ?? (snapshot.phase === newestRollout ? decision?.winnerArm : undefined);
6
+ return shippingTitle(winner ? phase.arms.find((arm)=>arm.key === winner)?.label ?? armLabel(winner) : RESULTS_COPY.shippedGroup);
7
+ }
8
+ /** Stands in for CORE-15a minExposuresPerArm; absent historical minima keep the old dash. */ export function legacyMinimum(snapshot, ctx) {
9
+ return snapshot.minExposuresPerArm ?? ctx.minExposuresPerArm;
10
+ }
11
+ /** Stands in for CORE-15a minExposuresPerArm by deriving the old notice from the frozen verdict. */ export function legacyTooFew(snapshot) {
12
+ return snapshot.verdict.code === 'collecting' || snapshot.verdict.code === 'rollout-collecting';
13
+ }
14
+ /** Stands in for CORE-15a winnerArm in rollout recommendations using the old recommended arm and label. */ export function legacyRecommendationWinner(snapshot, phase) {
15
+ const key = snapshot.winnerArm ?? snapshot.verdict.recommendedArm;
16
+ const stored = phase?.arms.find((arm)=>arm.key === key)?.label.trim();
17
+ return {
18
+ key,
19
+ label: stored || (key && [
20
+ 'control',
21
+ 'variantA',
22
+ 'variantB',
23
+ 'variantC',
24
+ 'holdout'
25
+ ].includes(key) ? armLabel(key) : RESULTS_COPY.shippedGroup)
26
+ };
27
+ }
@@ -1,13 +1,14 @@
1
1
  import type { DocId, Status } from '../../types/contracts.js';
2
2
  import type { PhaseSummary, ResultsResponse } from '../../types/endpoints.js';
3
3
  import type { Snapshot } from '../../types/snapshot.js';
4
+ import { type DisplaySnapshot } from './snapshot.js';
5
+ import type { PlacementLabel } from '../placements/placementLabels.model.js';
4
6
  type Metric = Snapshot['metrics'][number];
5
7
  type Arm = Metric['perArm'][number];
6
8
  type Decision = {
7
9
  phase: number;
8
10
  winnerArm?: string;
9
11
  };
10
- type DisplaySnapshot = Snapshot;
11
12
  export declare function defaultRound({ phases, decision, status }: Pick<ResultsResponse, 'phases'> & {
12
13
  decision?: Decision | null;
13
14
  status: Status;
@@ -26,8 +27,8 @@ export type VerdictCardModel = {
26
27
  guardrailLine?: string;
27
28
  };
28
29
  export declare function verdictCard({ snapshot, decisionSnapshot, isDecidedTestRound, winnerLabel }: {
29
- snapshot: Snapshot;
30
- decisionSnapshot?: Snapshot;
30
+ snapshot: DisplaySnapshot;
31
+ decisionSnapshot?: DisplaySnapshot;
31
32
  isDecidedTestRound: boolean;
32
33
  winnerLabel: string;
33
34
  }): VerdictCardModel;
@@ -73,11 +74,11 @@ export interface ArmRow {
73
74
  comparison?: ComparisonModel;
74
75
  }
75
76
  export declare function armRows({ snapshot, phase, metricId }: {
76
- snapshot: Snapshot;
77
+ snapshot: DisplaySnapshot;
77
78
  phase: PhaseSummary;
78
79
  metricId: DocId;
79
80
  }): ArmRow[];
80
- export declare function guardrailChips(snapshot: Snapshot): {
81
+ export declare function guardrailChips(snapshot: DisplaySnapshot): {
81
82
  name: string;
82
83
  status: "OK" | "Watch" | "Failing" | "Too little data";
83
84
  }[];
@@ -88,6 +89,7 @@ interface RoundLineInput {
88
89
  }
89
90
  export declare function powerLine({ snapshot, phaseKind, status }: RoundLineInput): string | null;
90
91
  export interface ResultsContext {
92
+ placementLabels?: Map<string, PlacementLabel>;
91
93
  status?: string;
92
94
  phase?: PhaseSummary;
93
95
  minExposuresPerArm?: number;
@@ -102,8 +104,9 @@ export interface DataQualityItem {
102
104
  message: string;
103
105
  detail?: string;
104
106
  nextStep: string;
107
+ href?: string;
105
108
  shares?: string[];
106
109
  }
107
110
  export declare function dataQuality(snapshot: DisplaySnapshot, ctx?: ResultsContext): DataQualityItem[];
108
- export declare function verdictLabel(snapshot: Snapshot): string;
111
+ export declare function verdictLabel(snapshot: DisplaySnapshot): string;
109
112
  export {};
@@ -1,7 +1,10 @@
1
- import { READINESS_COPY, versionPageUnavailableCopy, versionPageUncheckedCopy } from '../copy/readiness.js';
1
+ import { READINESS_COPY, placementUncheckedCopy, versionPageUnavailableCopy, versionPageUncheckedCopy } from '../copy/readiness.js';
2
2
  import { armLabel, phaseKindLabel, verdictCodeLabel } from '../labels.js';
3
3
  import { RESULTS_COPY, cappedCount, collectingCopy, conflictCopy, decidedTitle, exclusionCopy, ignoredCopy, metricUnavailable, moreDays, roundLabel, shareCopy, shippingTitle, srmP, uncappedTotal } from '../copy/results.js';
4
4
  import { formatCI, formatCount, formatMetric, formatPercent, formatProbability } from './format.js';
5
+ import { legacyMinimum, legacyRolloutLine, legacyTooFew } from './legacy.js';
6
+ import { isLegacySnapshot } from './snapshot.js';
7
+ import { placementDetailKey } from '../placements/placementLabels.model.js';
5
8
  export function defaultRound({ phases, decision, status }) {
6
9
  if ((status === 'decided' || status === 'archived') && decision) return decision.phase;
7
10
  return phases.reduce((latest, phase)=>latest === null || phase.phase > latest ? phase.phase : latest, null);
@@ -29,9 +32,10 @@ export function verdictCard({ snapshot, decisionSnapshot, isDecidedTestRound, wi
29
32
  }
30
33
  export function rolloutLine(snapshot, phase, phases, decision) {
31
34
  if (snapshot.phaseKind !== 'rollout') return null;
32
- const newestRollout = phases.filter((phase)=>phase.phaseKind === 'rollout').reduce((latest, phase)=>latest === null || phase.phase > latest ? phase.phase : latest, null);
33
- const winner = snapshot.winnerArm ?? (snapshot.phase === newestRollout ? decision?.winnerArm : undefined);
34
- return shippingTitle(winner ? phase.arms.find((arm)=>arm.key === winner)?.label ?? armLabel(winner) : RESULTS_COPY.shippedGroup);
35
+ return isLegacySnapshot(snapshot) ? legacyRolloutLine(snapshot, phase, phases, decision) : currentRolloutLine(snapshot, phase);
36
+ }
37
+ function currentRolloutLine(snapshot, phase) {
38
+ return snapshot.winnerArm === null ? null : shippingTitle(phase.arms.find((arm)=>arm.key === snapshot.winnerArm)?.label ?? armLabel(snapshot.winnerArm));
35
39
  }
36
40
  export function cappedCell({ metric, arm, caps }) {
37
41
  const cap = caps?.[metric.metricId];
@@ -125,14 +129,23 @@ function smallest(values) {
125
129
  if (phaseKind !== 'rollout' || snapshot.verdict.code !== 'rollout-collecting') return null;
126
130
  const keys = comparedKeys(snapshot, ctx.phase);
127
131
  const primary = snapshot.metrics.find((metric)=>metric.role === 'primary');
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));
132
+ return collectingCopy(formatCount(smallest(keys.map((key)=>snapshot.exposures.perArm[key]))), formatCount(isLegacySnapshot(snapshot) ? legacyMinimum(snapshot, ctx) : snapshot.minExposuresPerArm), formatCount(smallest(keys.map((key)=>primary?.perArm.find((arm)=>arm.arm === key)?.converters))), formatCount(primary ? snapshot.params.minSampleSizes[primary.metricId] : undefined));
129
133
  }
130
134
  export function dataQuality(snapshot, ctx = {}) {
131
135
  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)
136
+ id: item.code === 'placement-unchecked' ? `${item.code}:${JSON.stringify(item.detail)}` : `${item.code}:${'path' in item ? item.path : item.metricId}`,
137
+ message: item.code === 'placement-unchecked' ? placementUncheckedCopy(ctx.placementLabels?.get(placementDetailKey(item.detail))?.title ?? item.detail.path ?? 'This page') : item.code === 'goal-in-some-versions' ? `${item.name} now appears in some versions but not all, so this result is no longer a fair comparison. Add it to every version, or remove it.` : READINESS_COPY.needsAttention,
138
+ ...item.code === 'placement-unchecked' ? {
139
+ href: ctx.placementLabels?.get(placementDetailKey(item.detail))?.href
140
+ } : {},
141
+ nextStep: item.code === 'placement-unchecked' ? `Open ${ctx.placementLabels?.get(placementDetailKey(item.detail))?.title ?? item.detail.path ?? 'the page'}` : item.code === 'goal-in-some-versions' ? 'Edit the versions' : item.code === 'version-page-unavailable' ? versionPageUnavailableCopy(item.path, ctx.status) : versionPageUncheckedCopy(item.path, ctx.status)
135
142
  }));
143
+ // Test snapshots store null by design; only rollout snapshots record the shipped winner.
144
+ if (!isLegacySnapshot(snapshot) && snapshot.phaseKind === 'rollout' && snapshot.winnerArm === null) items.push({
145
+ id: 'missing-winner',
146
+ message: RESULTS_COPY.missingWinner,
147
+ nextStep: RESULTS_COPY.missingWinnerNext
148
+ });
136
149
  const srmTotal = Object.keys(snapshot.srm.expected).reduce((total, key)=>total + (snapshot.exposures.perArm[key] ?? 0), 0);
137
150
  if (snapshot.srm.flagged) items.push({
138
151
  id: 'srm',
@@ -159,7 +172,7 @@ export function dataQuality(snapshot, ctx = {}) {
159
172
  detail: conflictCopy(formatPercent(exposures.identityConflictShare)),
160
173
  nextStep: RESULTS_COPY.identityNext
161
174
  });
162
- if (snapshot.verdict.code === 'collecting' || snapshot.verdict.code === 'rollout-collecting') items.push({
175
+ if (isLegacySnapshot(snapshot) ? legacyTooFew(snapshot) : snapshot.verdict.code === 'collecting' || snapshot.verdict.code === 'rollout-collecting') items.push({
163
176
  id: 'too-few',
164
177
  message: RESULTS_COPY.tooFew,
165
178
  nextStep: RESULTS_COPY.tooFewNext
@@ -0,0 +1,14 @@
1
+ import type { Snapshot } from '../../types/snapshot.js';
2
+ type Core15aFields = Pick<Snapshot, 'winnerArm' | 'minExposuresPerArm'>;
3
+ /** Immutable history written before CORE-15a, with at least one added field absent. */
4
+ export type LegacySnapshot = Omit<Snapshot, keyof Core15aFields> & ({
5
+ winnerArm?: undefined;
6
+ minExposuresPerArm?: number;
7
+ } | {
8
+ winnerArm?: string | null;
9
+ minExposuresPerArm?: undefined;
10
+ });
11
+ export type DisplaySnapshot = Snapshot | LegacySnapshot;
12
+ /** A stored null winner is current data; only missing CORE-15a fields mark history. */
13
+ export declare function isLegacySnapshot(snapshot: DisplaySnapshot): snapshot is LegacySnapshot;
14
+ export {};
@@ -0,0 +1,3 @@
1
+ /** A stored null winner is current data; only missing CORE-15a fields mark history. */ export function isLegacySnapshot(snapshot) {
2
+ return snapshot.winnerArm === undefined || snapshot.minExposuresPerArm === undefined;
3
+ }
@@ -1,14 +1,15 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useState } from 'react';
4
- import { Link } from '@payloadcms/ui';
5
4
  import { formatAdminURL } from 'payload/shared';
5
+ import { fetchAllDocs } from '../../lib/fetchAllDocs.js';
6
6
  import { createApi } from '../api.js';
7
7
  import { noMatchesLine, SURFACE_COPY } from '../copy/surface.js';
8
8
  import { Banner, EmptyState, StatusChip } from '../../ui/Feedback.js';
9
9
  import { Button } from '../../ui/Button.js';
10
10
  import { SearchInput } from '../../ui/Inputs.js';
11
11
  import { SurfaceShell } from './SurfaceShell.js';
12
+ import { sanitizeReadinessDetail } from '../../sanitizeReadinessDetail.js';
12
13
  import { groupTests, homeCard } from './home.model.js';
13
14
  const HOME_FIELDS = [
14
15
  'name',
@@ -19,7 +20,8 @@ const HOME_FIELDS = [
19
20
  'decidedAt',
20
21
  'decision',
21
22
  'unitType',
22
- 'flagPoint'
23
+ 'flagPoint',
24
+ 'updatedAt'
23
25
  ];
24
26
  // Typecheck fails if HomeTest gains a field the list request does not select (Payload always returns id).
25
27
  const homeFieldsComplete = true;
@@ -42,20 +44,48 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
42
44
  });
43
45
  async function load() {
44
46
  try {
45
- const response = await fetch(`${apiRoute.replace(/\/$/, '')}/xp-experiments?depth=0&sort=-updatedAt&limit=200&${HOME_SELECT}`, {
47
+ const response = await fetchAllDocs(`${apiRoute.replace(/\/$/, '')}/xp-experiments?depth=0&sort=id&${HOME_SELECT}`, {
46
48
  credentials: 'include',
47
49
  signal: controller.signal
48
50
  });
49
51
  if (!response.ok) throw new Error('list');
50
- const { docs, totalDocs } = await response.json();
52
+ const docs = response.docs.sort((a, b)=>(Date.parse(b.updatedAt ?? '') || 0) - (Date.parse(a.updatedAt ?? '') || 0));
51
53
  if (!active) return;
52
54
  const summary = await api.summary(docs.map((doc)=>doc.id));
55
+ const summaries = summary.ok ? summary.data ?? {} : {};
56
+ const ids = [
57
+ ...new Set(docs.flatMap((doc)=>{
58
+ const item = summaries[doc.id];
59
+ if (!canManage || ![
60
+ 'draft',
61
+ 'stopped'
62
+ ].includes(doc.status) || item?.ready === true || item?.next?.code !== 'goal-in-some-versions') return [];
63
+ const detail = sanitizeReadinessDetail(item.next.code, item.next.detail);
64
+ return detail?.map((row)=>String(row.metricId)) ?? [];
65
+ }))
66
+ ];
67
+ const goalNames = {};
68
+ if (ids.length) {
69
+ try {
70
+ const query = new URLSearchParams({
71
+ 'where[id][in]': ids.join(','),
72
+ depth: '0',
73
+ limit: String(ids.length)
74
+ });
75
+ const response = await fetch(`${apiRoute.replace(/\/$/, '')}/xp-metrics?${query}`, {
76
+ credentials: 'include',
77
+ signal: controller.signal
78
+ });
79
+ const body = response.ok ? await response.json() : undefined;
80
+ for (const goal of body?.docs ?? [])if (typeof goal.name === 'string' && goal.name.trim() && ids.includes(String(goal.id))) goalNames[String(goal.id)] = goal.name;
81
+ } catch {}
82
+ }
53
83
  if (active) setState({
54
84
  loading: false,
55
85
  error: false,
56
86
  tests: docs,
57
- limited: totalDocs > docs.length,
58
- summaries: summary.ok ? summary.data ?? {} : {},
87
+ goalNames,
88
+ summaries,
59
89
  summaryError: !summary.ok
60
90
  });
61
91
  } catch {
@@ -73,7 +103,8 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
73
103
  }, [
74
104
  api,
75
105
  apiRoute,
76
- attempt
106
+ attempt,
107
+ canManage
77
108
  ]);
78
109
  const retry = /*#__PURE__*/ _jsx(Button, {
79
110
  size: "sm",
@@ -152,20 +183,17 @@ export function TestsHome({ adminRoute, apiRoute, canManage = false, crumbs, fla
152
183
  }) : /*#__PURE__*/ _jsx(TestGroups, {
153
184
  tests: state.tests,
154
185
  summaries: state.summaries,
186
+ goalNames: state.goalNames,
155
187
  adminRoute: adminRoute,
156
188
  flagPoints: flagPoints,
157
189
  search: search,
158
190
  clear: ()=>setSearch('')
159
- }),
160
- state.limited ? /*#__PURE__*/ _jsx("p", {
161
- className: "xp-tests-limited",
162
- children: SURFACE_COPY.limitedList
163
- }) : null
191
+ })
164
192
  ]
165
193
  })
166
194
  });
167
195
  }
168
- function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear }) {
196
+ function TestGroups({ tests, summaries, goalNames, adminRoute, flagPoints, search, clear }) {
169
197
  const groups = groupTests(tests, summaries, search);
170
198
  if (!groups.some((group)=>group.tests.length)) return /*#__PURE__*/ _jsxs("div", {
171
199
  className: "xp-tests-no-matches",
@@ -187,7 +215,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
187
215
  const rows = /*#__PURE__*/ _jsx("div", {
188
216
  className: "xp-tests-list",
189
217
  children: group.tests.map((test)=>{
190
- const card = homeCard(test, summaries[test.id], adminRoute, flagPoints);
218
+ const card = homeCard(test, summaries[test.id], adminRoute, flagPoints, goalNames);
191
219
  return /*#__PURE__*/ _jsxs("article", {
192
220
  className: "xp-tests-row",
193
221
  children: [
@@ -197,7 +225,7 @@ function TestGroups({ tests, summaries, adminRoute, flagPoints, search, clear })
197
225
  /*#__PURE__*/ _jsxs("div", {
198
226
  className: "xp-tests-row-name",
199
227
  children: [
200
- /*#__PURE__*/ _jsx(Link, {
228
+ /*#__PURE__*/ _jsx("a", {
201
229
  href: card.href,
202
230
  children: test.name
203
231
  }),
@@ -28,7 +28,11 @@ function SurfacePage(props) {
28
28
  flagPoints: props.flagPoints,
29
29
  attributeKeys: props.attributeKeys,
30
30
  metricSources: props.metricSources,
31
- descriptions: props.admin?.descriptions
31
+ admin: props.admin,
32
+ pageCollections: props.pageCollections,
33
+ collectionTitles: props.collectionTitles,
34
+ userId: props.userId,
35
+ userCollection: props.userCollection
32
36
  };
33
37
  const crumbs = [
34
38
  {
@@ -1,4 +1,5 @@
1
1
  import type { ComponentType } from 'react';
2
+ import type { TestsSurfaceProps } from '../../types/surface.js';
2
3
  import type { SerializedAttributeKeys, SerializedFlagPoint, SerializedMetricSource } from '../../types/admin.js';
3
4
  import type { useTestDocument } from './useTestDocument.js';
4
5
  /** Sections of the test page, in readiness order (surface spec §6.2, §8). */
@@ -10,17 +11,17 @@ export type TestDocApi = ReturnType<typeof useTestDocument>;
10
11
  export interface SurfaceProps {
11
12
  adminRoute: string;
12
13
  apiRoute: string;
14
+ collectionTitles?: Record<string, string>;
15
+ userId?: string | number;
16
+ userCollection?: string;
13
17
  canManage: boolean;
14
18
  canReadResults: boolean;
15
19
  isAdmin: boolean;
16
20
  flagPoints: SerializedFlagPoint[];
17
21
  attributeKeys: SerializedAttributeKeys;
18
22
  metricSources: SerializedMetricSource[];
19
- descriptions?: {
20
- block?: string;
21
- document?: string;
22
- setting?: string;
23
- };
23
+ pageCollections?: TestsSurfaceProps['pageCollections'];
24
+ admin?: TestsSurfaceProps['admin'];
24
25
  }
25
26
  export interface SectionProps {
26
27
  test: TestDocApi;