@delmaredigital/payload-experiments 0.2.0-rc.7 → 0.3.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/README.md +129 -11
  2. package/dist/admin/PreviewLinks.d.ts +2 -1
  3. package/dist/admin/PreviewLinks.js +24 -16
  4. package/dist/admin/actions/ConfirmDialog.d.ts +7 -4
  5. package/dist/admin/actions/ConfirmDialog.js +37 -82
  6. package/dist/admin/actions/DecideDialog.d.ts +3 -1
  7. package/dist/admin/actions/DecideDialog.js +30 -13
  8. package/dist/admin/actions/MakeContentResult.d.ts +13 -2
  9. package/dist/admin/actions/MakeContentResult.js +115 -16
  10. package/dist/admin/actions/actions.model.d.ts +2 -4
  11. package/dist/admin/actions/actions.model.js +2 -3
  12. package/dist/admin/allocation.model.js +3 -5
  13. package/dist/admin/api.d.ts +10 -4
  14. package/dist/admin/api.js +11 -7
  15. package/dist/admin/copy/actions.d.ts +39 -3
  16. package/dist/admin/copy/actions.js +40 -4
  17. package/dist/admin/copy/armSelect.js +5 -5
  18. package/dist/admin/copy/audience.d.ts +2 -1
  19. package/dist/admin/copy/audience.js +3 -2
  20. package/dist/admin/copy/common.d.ts +13 -6
  21. package/dist/admin/copy/common.js +14 -8
  22. package/dist/admin/copy/goals.d.ts +21 -6
  23. package/dist/admin/copy/goals.js +27 -6
  24. package/dist/admin/copy/readiness.d.ts +19 -0
  25. package/dist/admin/copy/readiness.js +19 -0
  26. package/dist/admin/copy/recommendation.d.ts +0 -3
  27. package/dist/admin/copy/recommendation.js +0 -3
  28. package/dist/admin/copy/results.d.ts +41 -2
  29. package/dist/admin/copy/results.js +42 -3
  30. package/dist/admin/copy/review.d.ts +1 -1
  31. package/dist/admin/copy/review.js +1 -1
  32. package/dist/admin/copy/setting.d.ts +2 -0
  33. package/dist/admin/copy/setting.js +2 -0
  34. package/dist/admin/copy/surface.d.ts +7 -4
  35. package/dist/admin/copy/surface.js +9 -4
  36. package/dist/admin/copy/testPage.d.ts +10 -0
  37. package/dist/admin/copy/testPage.js +10 -0
  38. package/dist/admin/format/date.d.ts +2 -0
  39. package/dist/admin/format/date.js +10 -0
  40. package/dist/admin/previewLinks.model.d.ts +2 -2
  41. package/dist/admin/previewLinks.model.js +5 -7
  42. package/dist/admin/results/CiChart.js +11 -21
  43. package/dist/admin/results/DataQualityPanel.d.ts +4 -2
  44. package/dist/admin/results/DataQualityPanel.js +30 -17
  45. package/dist/admin/results/DataTable.d.ts +16 -0
  46. package/dist/admin/results/DataTable.js +152 -0
  47. package/dist/admin/results/StatTile.d.ts +7 -0
  48. package/dist/admin/results/StatTile.js +22 -0
  49. package/dist/admin/results/VerdictHero.d.ts +13 -0
  50. package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
  51. package/dist/admin/results/releasePages.model.d.ts +17 -0
  52. package/dist/admin/results/releasePages.model.js +10 -0
  53. package/dist/admin/results/results.css +64 -0
  54. package/dist/admin/results/results.model.d.ts +5 -1
  55. package/dist/admin/results/results.model.js +10 -2
  56. package/dist/admin/surface/ResultsRoute.d.ts +1 -1
  57. package/dist/admin/surface/ResultsRoute.js +59 -34
  58. package/dist/admin/surface/SurfaceShell.d.ts +11 -2
  59. package/dist/admin/surface/SurfaceShell.js +49 -14
  60. package/dist/admin/surface/TestsHome.d.ts +6 -1
  61. package/dist/admin/surface/TestsHome.js +183 -112
  62. package/dist/admin/surface/TestsSurface.js +56 -16
  63. package/dist/admin/surface/goals/GoalForm.js +341 -308
  64. package/dist/admin/surface/goals/GoalsList.js +123 -89
  65. package/dist/admin/surface/goals/data.d.ts +8 -2
  66. package/dist/admin/surface/goals/data.js +11 -3
  67. package/dist/admin/surface/goals/goals.css +10 -13
  68. package/dist/admin/surface/home.model.d.ts +7 -8
  69. package/dist/admin/surface/home.model.js +23 -12
  70. package/dist/admin/surface/newTest/NewTest.js +5 -5
  71. package/dist/admin/surface/pageBlock/parts.js +31 -26
  72. package/dist/admin/surface/readiness/ActionDialogs.js +43 -14
  73. package/dist/admin/surface/readiness/Banner.d.ts +3 -0
  74. package/dist/admin/surface/readiness/Banner.js +58 -33
  75. package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
  76. package/dist/admin/surface/readiness/SaveStatus.js +6 -3
  77. package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
  78. package/dist/admin/surface/readiness/readiness.model.js +18 -9
  79. package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
  80. package/dist/admin/surface/readiness/useTransition.js +2 -1
  81. package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
  82. package/dist/admin/surface/results/ReleasePages.js +116 -0
  83. package/dist/admin/surface/results/ResultsBody.d.ts +11 -1
  84. package/dist/admin/surface/results/ResultsBody.js +276 -105
  85. package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
  86. package/dist/admin/surface/results/ResultsHeader.js +177 -0
  87. package/dist/admin/surface/results/recommendation.model.js +2 -6
  88. package/dist/admin/surface/results/useResults.d.ts +21 -3
  89. package/dist/admin/surface/results/useResults.js +2 -1
  90. package/dist/admin/surface/setting/parts.js +75 -88
  91. package/dist/admin/surface/setting/setting.css +3 -15
  92. package/dist/admin/surface/surface.css +62 -47
  93. package/dist/admin/surface/test/AdvancedSection.js +55 -48
  94. package/dist/admin/surface/test/GoalSection.js +39 -32
  95. package/dist/admin/surface/test/LengthSection.js +46 -34
  96. package/dist/admin/surface/test/Rail.d.ts +4 -0
  97. package/dist/admin/surface/test/Rail.js +83 -0
  98. package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
  99. package/dist/admin/surface/test/ReadinessBar.js +33 -0
  100. package/dist/admin/surface/test/Section.d.ts +1 -1
  101. package/dist/admin/surface/test/Section.js +36 -16
  102. package/dist/admin/surface/test/TargetingSection.js +168 -142
  103. package/dist/admin/surface/test/TestPage.d.ts +2 -1
  104. package/dist/admin/surface/test/TestPage.js +146 -68
  105. package/dist/admin/surface/test/VersionsSection.js +151 -120
  106. package/dist/admin/surface/test/fields.d.ts +11 -4
  107. package/dist/admin/surface/test/fields.js +84 -85
  108. package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
  109. package/dist/admin/surface/test/jumpToSection.js +10 -0
  110. package/dist/admin/surface/test/pageParts.js +9 -3
  111. package/dist/admin/surface/test/testPage.css +81 -14
  112. package/dist/admin/surface/useTestDocument.js +10 -4
  113. package/dist/admin/targeting/targeting.model.d.ts +1 -3
  114. package/dist/admin/targeting/targeting.model.js +3 -8
  115. package/dist/admin/theme.css +115 -36
  116. package/dist/admin/theme.d.ts +38 -0
  117. package/dist/admin/theme.js +38 -1
  118. package/dist/admin/useApi.d.ts +17 -2
  119. package/dist/admin/views/ExperimentsRootView.js +12 -2
  120. package/dist/collections/experiments/config.js +25 -0
  121. package/dist/collections/experiments/lockHook.js +3 -1
  122. package/dist/collections/identities.js +6 -1
  123. package/dist/collections/metrics.js +7 -0
  124. package/dist/collections/metricsGuards.d.ts +9 -0
  125. package/dist/collections/metricsGuards.js +33 -0
  126. package/dist/contentDone.d.ts +4 -0
  127. package/dist/contentDone.js +5 -0
  128. package/dist/db/identities.d.ts +8 -7
  129. package/dist/db/identities.js +9 -6
  130. package/dist/db/metricUsage.d.ts +19 -0
  131. package/dist/db/metricUsage.js +54 -0
  132. package/dist/db/names.d.ts +5 -0
  133. package/dist/db/names.js +5 -0
  134. package/dist/db/publishedContent.d.ts +10 -0
  135. package/dist/db/publishedContent.js +43 -0
  136. package/dist/db/results.d.ts +2 -0
  137. package/dist/db/results.js +6 -2
  138. package/dist/db/snapshots.d.ts +18 -6
  139. package/dist/db/snapshots.js +23 -7
  140. package/dist/db/sweep.js +11 -6
  141. package/dist/endpoints/index.js +19 -1
  142. package/dist/endpoints/results.js +5 -0
  143. package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
  144. package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
  145. package/dist/endpoints/summary.js +20 -2
  146. package/dist/endpoints/transitions.d.ts +15 -0
  147. package/dist/endpoints/transitions.js +36 -0
  148. package/dist/errors.d.ts +8 -0
  149. package/dist/errors.js +28 -0
  150. package/dist/exports/index.d.ts +2 -1
  151. package/dist/exports/index.js +1 -0
  152. package/dist/exports/puckEditor.d.ts +1 -0
  153. package/dist/exports/puckEditor.js +1 -0
  154. package/dist/exports/rsc.d.ts +5 -1
  155. package/dist/exports/rsc.js +2 -0
  156. package/dist/hooks/pageGuard.d.ts +5 -0
  157. package/dist/hooks/pageGuard.js +121 -0
  158. package/dist/log.js +6 -3
  159. package/dist/manifest/compile.d.ts +5 -0
  160. package/dist/manifest/compile.js +5 -1
  161. package/dist/options.js +23 -0
  162. package/dist/plugin.js +26 -2
  163. package/dist/puck/copy.d.ts +1 -1
  164. package/dist/puck/copy.js +1 -1
  165. package/dist/puck/editor/editor.chrome.css +235 -0
  166. package/dist/puck/editor/editor.css +241 -43
  167. package/dist/results/aggregate.js +4 -3
  168. package/dist/results/analyse.d.ts +4 -1
  169. package/dist/results/analyse.js +27 -11
  170. package/dist/results/cuped.js +3 -2
  171. package/dist/results/identityCredit.d.ts +15 -7
  172. package/dist/results/identityCredit.js +32 -33
  173. package/dist/results/rolloutUnits.js +2 -1
  174. package/dist/results/snapshot.js +8 -2
  175. package/dist/results/srm.js +2 -1
  176. package/dist/sanitizeReadinessDetail.d.ts +7 -0
  177. package/dist/sanitizeReadinessDetail.js +54 -0
  178. package/dist/server/linkIdentity.d.ts +10 -1
  179. package/dist/server/linkIdentity.js +17 -15
  180. package/dist/server/metadata.d.ts +5 -0
  181. package/dist/server/metadata.js +9 -1
  182. package/dist/server/runResults.d.ts +3 -2
  183. package/dist/server/runResults.js +121 -31
  184. package/dist/targeting/values.d.ts +4 -0
  185. package/dist/targeting/values.js +10 -0
  186. package/dist/transitions/addMetric.js +8 -0
  187. package/dist/transitions/archive.js +2 -3
  188. package/dist/transitions/checkStart.d.ts +6 -3
  189. package/dist/transitions/checkStart.js +15 -0
  190. package/dist/transitions/contentFields.d.ts +7 -0
  191. package/dist/transitions/contentFields.js +20 -0
  192. package/dist/transitions/decide.js +6 -1
  193. package/dist/transitions/dismissContentEntry.d.ts +8 -0
  194. package/dist/transitions/dismissContentEntry.js +45 -0
  195. package/dist/transitions/makeContent.d.ts +5 -2
  196. package/dist/transitions/makeContent.js +217 -118
  197. package/dist/transitions/phaseRecord.d.ts +8 -2
  198. package/dist/transitions/placements.d.ts +1 -0
  199. package/dist/transitions/placements.js +4 -0
  200. package/dist/transitions/preflightData.d.ts +1 -1
  201. package/dist/transitions/preflightData.js +42 -4
  202. package/dist/transitions/releasePages.d.ts +8 -0
  203. package/dist/transitions/releasePages.js +26 -0
  204. package/dist/transitions/restoreContentEntry.d.ts +8 -0
  205. package/dist/transitions/restoreContentEntry.js +47 -0
  206. package/dist/transitions/start.js +30 -5
  207. package/dist/types/contracts.d.ts +14 -0
  208. package/dist/types/endpoints.d.ts +34 -3
  209. package/dist/types/index.d.ts +1 -0
  210. package/dist/types/index.js +1 -0
  211. package/dist/types/reasons.d.ts +3 -0
  212. package/dist/types/reasons.js +1 -0
  213. package/dist/types/snapshot.d.ts +6 -1
  214. package/dist/ui/Banner.d.ts +2 -0
  215. package/dist/ui/Banner.js +2 -0
  216. package/dist/ui/Breadcrumb.d.ts +7 -0
  217. package/dist/ui/Breadcrumb.js +25 -0
  218. package/dist/ui/Button.d.ts +15 -0
  219. package/dist/ui/Button.js +32 -0
  220. package/dist/ui/Card.d.ts +12 -0
  221. package/dist/ui/Card.js +40 -0
  222. package/dist/ui/Checkbox.d.ts +2 -0
  223. package/dist/ui/Checkbox.js +2 -0
  224. package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
  225. package/dist/{admin/components → ui}/ChipsInput.js +20 -12
  226. package/dist/ui/Dialog.d.ts +13 -0
  227. package/dist/ui/Dialog.js +85 -0
  228. package/dist/ui/EmptyState.d.ts +2 -0
  229. package/dist/ui/EmptyState.js +2 -0
  230. package/dist/ui/Feedback.d.ts +24 -0
  231. package/dist/ui/Feedback.js +40 -0
  232. package/dist/ui/Inputs.d.ts +34 -0
  233. package/dist/ui/Inputs.js +225 -0
  234. package/dist/ui/NumberInput.d.ts +2 -0
  235. package/dist/ui/NumberInput.js +2 -0
  236. package/dist/ui/Range.d.ts +2 -0
  237. package/dist/ui/Range.js +2 -0
  238. package/dist/ui/SearchInput.d.ts +2 -0
  239. package/dist/ui/SearchInput.js +2 -0
  240. package/dist/ui/SectionCard.d.ts +2 -0
  241. package/dist/ui/SectionCard.js +2 -0
  242. package/dist/ui/Select.d.ts +2 -0
  243. package/dist/ui/Select.js +2 -0
  244. package/dist/ui/StatusChip.d.ts +2 -0
  245. package/dist/ui/StatusChip.js +2 -0
  246. package/dist/ui/Tabs.d.ts +14 -0
  247. package/dist/ui/Tabs.js +54 -0
  248. package/dist/ui/TextInput.d.ts +2 -0
  249. package/dist/ui/TextInput.js +2 -0
  250. package/dist/ui/Textarea.d.ts +2 -0
  251. package/dist/ui/Textarea.js +2 -0
  252. package/dist/ui/Toggle.d.ts +2 -0
  253. package/dist/ui/Toggle.js +2 -0
  254. package/dist/ui/accent.d.ts +21 -0
  255. package/dist/ui/accent.js +81 -0
  256. package/dist/ui/index.d.ts +9 -0
  257. package/dist/ui/index.js +9 -0
  258. package/dist/ui/ui.css +92 -0
  259. package/package.json +1 -1
  260. package/dist/admin/components/chipsInput.css +0 -11
  261. package/dist/admin/results/ArmTable.d.ts +0 -8
  262. package/dist/admin/results/ArmTable.js +0 -113
  263. package/dist/admin/results/VerdictCard.d.ts +0 -9
  264. package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
  265. package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
  266. package/dist/admin/surface/test/TestHeader.d.ts +0 -4
  267. package/dist/admin/surface/test/TestHeader.js +0 -70
@@ -1,23 +1,90 @@
1
1
  .xp-test-page { color: var(--xp-text); background: var(--xp-bg); max-width: 90rem; margin: 0 auto; padding: 1.5rem; }
2
+ .xp-test-page--setup { min-width: 0; padding: 0 0 160px; }
2
3
  .xp-test-header { position: sticky; top: 0; z-index: 10; background: var(--xp-bg); padding: 1rem 0; border-bottom: 1px solid var(--xp-border); }
3
4
  .xp-header-meta, .xp-section-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-block: 1rem; }
4
- .xp-kind-chip, .xp-status-pill { border: 1px solid var(--xp-border-control); border-radius: 1rem; padding: .25rem .75rem; }
5
+ .xp-kind-chip, .xp-status-pill { border: 1px solid var(--xp-border-strong); border-radius: 1rem; padding: .25rem .75rem; }
5
6
  .xp-status-pill--success, .xp-content-ready, .xp-section-check--done { color: var(--xp-success-text); }
6
7
  .xp-content-empty, .xp-status-pill--warning { color: var(--xp-warning-text); }
7
- .xp-test-section { padding-block: 1.5rem; border-bottom: 1px solid var(--xp-border); scroll-margin-top: 15rem; }
8
- .xp-version-rows { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr)); }
9
- .xp-version-row { border: 1px solid var(--xp-border); border-radius: var(--xp-radius); padding: 1rem; }
10
- .xp-version-colour { display: inline-block; width: .75rem; height: .75rem; margin-right: .5rem; border-radius: 50%; }
11
- .xp-traffic-bar { display: flex; height: 1.25rem; background: var(--xp-surface-raised); margin-block: 1.5rem; overflow: hidden; border-radius: var(--xp-radius); }
8
+ .xp-test-section, .xp-test-advanced { scroll-margin-top: var(--xp-space-6); }
9
+ .xp-version-rows { display: grid; grid-template-columns: minmax(0, 1.3fr) var(--xp-version-share-width) minmax(var(--xp-version-state-width), 1fr) var(--xp-version-remove-width); gap: var(--xp-space-3); align-items: center; }
10
+ .xp-version-row, .xp-version-grid-head { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; gap: var(--xp-space-3); align-items: center; }
11
+ .xp-version-grid-head { margin-bottom: var(--xp-space-2); font: var(--xp-text-small); color: var(--xp-text-2); }
12
+ .xp-version-row > .xp-field-error, .xp-version-row > [role=alert] { grid-column: 1 / -1; }
13
+ .xp-version-remove { grid-column: 4; }
14
+ .xp-grid-field .xp-ui-field { margin: 0; }
15
+ .xp-grid-field .xp-ui-field > label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
16
+ .xp-version-content { display: flex; flex-wrap: nowrap; gap: var(--xp-space-2); align-items: center; min-width: 0; }
17
+ .xp-version-content .xp-ui-button { white-space: nowrap; }
18
+ .xp-content-chip { flex-shrink: 0; display: inline-flex; width: fit-content; border-radius: var(--xp-radius-chip); padding: var(--xp-space-half) var(--xp-space-2); font: var(--xp-text-small); background: var(--xp-surface-2); color: var(--xp-text-2); }
19
+ .xp-content-chip.xp-content-ready { background: var(--xp-success-bg); color: var(--xp-success); }
20
+ .xp-content-chip.xp-content-empty { background: var(--xp-warning-bg); color: var(--xp-warning); }
21
+ .xp-traffic-bar { display: flex; height: var(--xp-space-2); gap: var(--xp-space-half); margin-block: var(--xp-space-5) var(--xp-space-3); overflow: hidden; border-radius: var(--xp-space-1); }
12
22
  .xp-traffic-bar > span { display: block; }
13
- .xp-field { display: flex; flex-direction: column; gap: .5rem; margin-block: 1rem; }
14
- .xp-field input, .xp-field textarea, .xp-field select { font: inherit; color: var(--xp-text); background: var(--xp-input-bg); border: 1px solid var(--xp-border-control); border-radius: var(--xp-radius); padding: .65rem; }
15
- .xp-field textarea { min-height: 6rem; resize: vertical; }
16
- .xp-field input[type=range] { padding: 0; }
23
+ .xp-split-legend { display: flex; flex-wrap: wrap; gap: var(--xp-space-4); font: var(--xp-text-small); color: var(--xp-text-2); }
24
+ .xp-split-legend i { display: inline-block; width: var(--xp-legend-key-size); height: var(--xp-legend-key-size); border-radius: var(--xp-legend-key-radius); margin-right: var(--xp-space-one-half); }
25
+ .xp-field-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--xp-space-4); }
26
+ .xp-test-advanced .xp-field-grid { padding-top: var(--xp-space-4); }
27
+ .xp-field-grid > div { min-width: 0; }
28
+ .xp-field-grid p, .xp-page-binding p { color: var(--xp-text-2); font: var(--xp-text-small); }
29
+ .xp-read-only code { font-family: var(--xp-mono); overflow-wrap: anywhere; }
30
+ .xp-ui-checkbox-list { display: flex; flex-wrap: wrap; gap: var(--xp-space-2) var(--xp-space-4); }
31
+ .xp-duration-suggestion { align-items: center; margin-top: var(--xp-space-4); }
17
32
  .xp-read-only { padding-block: .5rem; }
18
33
  .xp-read-only p { margin-block: .5rem; }
19
34
  .xp-field-error { color: var(--xp-error-text); }
20
- .xp-test-page button { cursor: pointer; color: var(--xp-text); background: var(--xp-surface-raised); border: 1px solid var(--xp-border-control); padding: .65rem 1rem; border-radius: var(--xp-radius); font: inherit; }
21
- .xp-test-page button:disabled { opacity: .5; cursor: default; }
22
- .xp-test-page summary { cursor: pointer; padding-block: .75rem; }
23
- .xp-targeting-row { border-bottom: 1px solid var(--xp-border); padding-bottom: 1rem; }
35
+
36
+ .xp-targeting-row { display: grid; grid-template-columns: var(--xp-targeting-attribute-width) var(--xp-targeting-comparison-width) minmax(0, 1fr); align-items: start; gap: var(--xp-space-3); border: 1px solid var(--xp-border); border-radius: var(--xp-radius-control); padding: var(--xp-space-4); margin-bottom: var(--xp-space-4); }
37
+ .xp-targeting-row > .xp-ui-button-group, .xp-targeting-row > p { grid-column: 1 / -1; }
38
+ @media (max-width: 720px) {
39
+ .xp-version-rows { grid-template-columns: minmax(0, 1fr) var(--xp-version-share-width-mobile); }
40
+ .xp-version-content { flex-wrap: wrap; }
41
+ .xp-version-remove:empty { display: none; }
42
+ .xp-version-content, .xp-setting-values, .xp-version-remove { grid-column: 1 / -1; }
43
+ .xp-version-grid-head > :nth-child(n+3) { display: none; }
44
+ .xp-targeting-row, .xp-field-grid { grid-template-columns: 1fr; }
45
+ }
46
+
47
+ .xp-test-layout { display: grid; grid-template-columns: 220px minmax(0, 1fr); align-items: start; gap: var(--xp-space-8); }
48
+ .xp-test-content { display: grid; gap: var(--xp-space-8); min-width: 0; max-width: 760px; }
49
+ .xp-test-rail { position: sticky; top: var(--xp-space-6); min-width: 0; }
50
+ .xp-test-rail-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--xp-space-1); }
51
+ .xp-test-rail-item { display: flex; align-items: center; gap: var(--xp-space-2); padding: var(--xp-space-2); border-radius: var(--xp-radius-control); color: var(--xp-text-2); text-decoration: none; font: var(--xp-text-small); }
52
+ .xp-test-rail-item:hover, .xp-test-rail-item.is-active { background: var(--xp-surface-2); color: var(--xp-text); }
53
+ .xp-test-rail-item.is-active { font-weight: 600; }
54
+ .xp-test-rail-item--advanced { margin-top: var(--xp-space-3); color: var(--xp-text-3); }
55
+ .xp-test-rail-mark { flex: none; color: var(--xp-text-3); }
56
+ .xp-test-rail-mark--done { color: var(--xp-success); }
57
+ .xp-test-sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
58
+ .xp-test-rail-item:focus-visible, .xp-test-advanced > summary:focus-visible, .xp-test-content h2:focus { outline: 2px solid var(--xp-focus); outline-offset: 2px; }
59
+ .xp-test-advanced { border: 1px solid var(--xp-border); border-radius: var(--xp-radius-card); background: var(--xp-surface); }
60
+ .xp-test-advanced > summary { cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: var(--xp-space-3); padding: var(--xp-space-4) var(--xp-space-5); color: var(--xp-text-2); }
61
+ .xp-test-advanced > summary h2 { margin: 0; font: var(--xp-text-body); }
62
+ .xp-test-advanced[open] > summary { border-bottom: 1px solid var(--xp-border); }
63
+ .xp-test-readiness-bar { position: fixed; bottom: 0; inset-inline: 0; z-index: 20; background: var(--xp-surface); border-top: 1px solid var(--xp-border); }
64
+ .xp-readiness { color: var(--xp-text); background: var(--xp-surface); padding: var(--xp-space-4); border: 1px solid var(--xp-border-strong); border-radius: var(--xp-radius-control); }
65
+ .xp-readiness--ready:not(.xp-readiness--bar) { background: var(--xp-success-bg); }
66
+ .xp-readiness--bar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--xp-space-2) var(--xp-space-4); padding-block: var(--xp-space-3); min-height: 64px; border: 0; border-radius: 0; }
67
+ .xp-readiness__state { display: flex; align-items: center; flex-wrap: wrap; gap: var(--xp-space-2); min-width: 0; }
68
+ .xp-readiness__state p, .xp-readiness--bar > p { margin: 0; }
69
+ .xp-readiness__state > p { font-weight: 500; }
70
+ .xp-readiness--bar .xp-save-status, .xp-readiness__separator { color: var(--xp-text-3); font: var(--xp-text-small); }
71
+ .xp-readiness__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--xp-space-2); }
72
+ .xp-readiness--bar > .xp-readiness__actions { grid-column: 2; grid-row: 1 / span 3; justify-content: flex-end; }
73
+ @media (max-width: 960px) {
74
+ .xp-test-layout { grid-template-columns: minmax(0, 1fr); gap: var(--xp-space-5); }
75
+ .xp-test-rail { top: 0; z-index: 2; background: var(--xp-bg); padding-block: var(--xp-space-2); border-bottom: 1px solid var(--xp-border); }
76
+ .xp-test-rail-list { flex-direction: row; overflow-x: auto; }
77
+ .xp-test-rail-item { white-space: nowrap; }
78
+ .xp-test-rail-item--advanced { margin-top: 0; }
79
+ .xp-test-content { max-width: none; }
80
+ .xp-test-section, .xp-test-advanced { scroll-margin-top: 64px; }
81
+ }
82
+ @media (max-width: 720px) {
83
+ .xp-readiness--bar { grid-template-columns: minmax(0, 1fr); }
84
+ /* The sentence wraps under the save state here, so a trailing separator would dangle. */
85
+ .xp-readiness--bar .xp-readiness__separator { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
86
+ .xp-readiness--bar > .xp-readiness__actions { grid-column: 1; grid-row: auto; justify-content: flex-start; }
87
+ }
88
+
89
+ .xp-ui-checkbox-list > .xp-ui-field:has(input[type="search"]) { flex-basis: 100%; }
90
+ .xp-targeting-row .xp-ui-chips { margin-block: 0; }
@@ -155,11 +155,13 @@ function stage(session, partial) {
155
155
  }
156
156
  session.publish();
157
157
  }
158
- async function readDocument() {
158
+ async function readDocument(forSave = false) {
159
159
  const response = await fetch(url, {
160
160
  method: 'GET',
161
161
  credentials: 'include',
162
- signal: controller.signal
162
+ ...forSave ? {} : {
163
+ signal: controller.signal
164
+ }
163
165
  });
164
166
  if (!response.ok) throw new Error(API_MESSAGES.unreadable);
165
167
  const doc = await response.json();
@@ -181,6 +183,10 @@ function stage(session, partial) {
181
183
  let failed = false;
182
184
  try {
183
185
  if (!session.saved) return;
186
+ if (typeof session.saved.updatedAt !== 'string' || !session.saved.updatedAt) {
187
+ session.saved = await readDocument(true);
188
+ if (typeof session.saved.updatedAt !== 'string' || !session.saved.updatedAt) throw new Error(API_MESSAGES.unreadable);
189
+ }
184
190
  const patch = changedPatch(session.saved, queued);
185
191
  changed = Object.keys(patch).length > 0;
186
192
  if (changed) {
@@ -221,8 +227,8 @@ function stage(session, partial) {
221
227
  const doc = body?.doc;
222
228
  const readable = doc && doc.id != null && doc.status;
223
229
  // A 2xx has applied even if its response body was lost. Preserve it while re-reading.
224
- // The old timestamp is no longer a valid baseline. Omit the header until a
225
- // readable PATCH or GET restores it, including when this read is aborted.
230
+ // The old timestamp is no longer a valid baseline. The next queued save
231
+ // must recover it with a GET before sending another PATCH.
226
232
  session.saved = readable ? doc : {
227
233
  ...session.saved,
228
234
  ...patch,
@@ -1,6 +1,7 @@
1
1
  import type { TargetingRule } from '../../types/contracts.js';
2
2
  import type { SerializedAttributeKeys } from '../../types/admin.js';
3
3
  import type { TargetingOp } from '../../targeting/ops.js';
4
+ export { readListValue } from '../../targeting/values.js';
4
5
  type Attribute = SerializedAttributeKeys[string];
5
6
  export type AttributeType = Attribute['type'];
6
7
  export declare function opsFor(type?: AttributeType): readonly TargetingOp[];
@@ -13,11 +14,8 @@ export declare function valueInput(attribute?: Attribute, op?: TargetingOp): {
13
14
  }[];
14
15
  };
15
16
  export declare function coerceValue(type: AttributeType, op: TargetingOp, raw: unknown): string | number | boolean | string[] | null;
16
- /** Only the read boundary accepts legacy comma-separated document values. */
17
- export declare function readListValue(raw: unknown): string[];
18
17
  /** Normalize the read boundary before any whole-group edit. */
19
18
  export declare function readTargetingRules(rules: TargetingRule[]): TargetingRule[];
20
19
  export declare function rowPath(path: string): string;
21
20
  export declare function emptyListHint(op: TargetingOp, value: unknown): string | undefined;
22
21
  export declare function humaniseAttribute(key: string): string;
23
- export {};
@@ -1,5 +1,7 @@
1
1
  import { TARGETING_OPS, isOpValid } from '../../targeting/ops.js';
2
+ import { coerceListValue, readListValue } from '../../targeting/values.js';
2
3
  import { NUMBER_OP_LABELS, STRING_OP_LABELS, AUDIENCE_COPY } from '../copy/audience.js';
4
+ export { readListValue } from '../../targeting/values.js';
3
5
  export function opsFor(type) {
4
6
  return type ? TARGETING_OPS[type] : [];
5
7
  }
@@ -27,11 +29,7 @@ export function valueInput(attribute, op = 'eq') {
27
29
  export function coerceValue(type, op, raw) {
28
30
  if (!isOpValid(type, op)) return null;
29
31
  if (type === 'string') {
30
- if (op === 'in' || op === 'nin') return [
31
- ...new Set((Array.isArray(raw) ? raw.map(String) : [
32
- String(raw ?? '')
33
- ]).map((value)=>value.trim()).filter(Boolean))
34
- ];
32
+ if (op === 'in' || op === 'nin') return coerceListValue(raw);
35
33
  return raw == null ? '' : String(raw);
36
34
  }
37
35
  if (type === 'number') {
@@ -41,9 +39,6 @@ export function coerceValue(type, op, raw) {
41
39
  }
42
40
  return raw === true || raw === 'true' ? true : raw === false || raw === 'false' ? false : null;
43
41
  }
44
- /** Only the read boundary accepts legacy comma-separated document values. */ export function readListValue(raw) {
45
- return coerceValue('string', 'in', typeof raw === 'string' ? raw.split(',') : raw);
46
- }
47
42
  /** Normalize the read boundary before any whole-group edit. */ export function readTargetingRules(rules) {
48
43
  return rules.map((rule)=>rule.op === 'in' || rule.op === 'nin' ? {
49
44
  ...rule,
@@ -1,44 +1,123 @@
1
- /* The Payload token boundary. Recompute aliases for either admin theme. */
1
+ /* The only Payload token boundary. Reference: Payload 3.90.2; nearest neutral
2
+ elevation by RGB distance to the approved mockup, advancing toward the mockup
3
+ on collision so all five surface steps stay distinct (light / dark):
4
+ bg 50/0; surface 0/50; surface-2 100/100; surface-3 150/150;
5
+ accent-subtle 200/200;
6
+ border 100/100; border-strong 250/200; text 850/800;
7
+ text-2 650/550; text-3 450/350; accent 800/800; hover 900/900;
8
+ accent-fg 0/0; focus 800/800; series-muted 300/250.
9
+ Semantic and series hues are fixed mockup literals; only neutrals use Payload. */
2
10
  :root, [data-theme] {
3
- --xp-bg: var(--theme-bg);
4
- --xp-text: var(--theme-text);
5
- --xp-surface: var(--theme-elevation-50);
6
- --xp-surface-raised: var(--theme-elevation-100);
7
- --xp-border: var(--theme-elevation-150);
8
- --xp-border-strong: var(--theme-elevation-200);
11
+ --xp-bg: var(--theme-elevation-50);
12
+ --xp-surface: var(--theme-elevation-0);
13
+ --xp-surface-2: var(--theme-elevation-100);
14
+ --xp-surface-3: var(--theme-elevation-150);
15
+ --xp-border: var(--theme-elevation-100);
16
+ --xp-border-strong: var(--theme-elevation-250);
17
+ --xp-text: var(--theme-elevation-850);
18
+ --xp-text-2: var(--theme-elevation-650);
19
+ --xp-text-3: var(--theme-elevation-450);
20
+ --xp-accent: var(--theme-elevation-800);
21
+ --xp-accent-hover: var(--theme-elevation-900);
22
+ --xp-accent-active: var(--theme-elevation-950);
23
+ --xp-accent-subtle: var(--theme-elevation-200);
24
+ --xp-accent-fg: var(--theme-elevation-0);
25
+ --xp-accent-text: var(--xp-text);
26
+ --xp-accent-text-decoration: none;
27
+ --xp-focus: var(--theme-elevation-800);
28
+ --xp-series-muted: var(--theme-elevation-300);
29
+ --xp-success: #1a6a3a;
30
+ --xp-success-bg: #e8f4ec;
31
+ --xp-success-border: #bddfc9;
32
+ --xp-warning: #8a5a0b;
33
+ --xp-warning-bg: #fbf3e1;
34
+ --xp-warning-border: #ecd6a2;
35
+ --xp-error: #a63030;
36
+ --xp-error-bg: #fbeaea;
37
+ --xp-error-border: #efc3c3;
38
+ --xp-series-a: #2a78d6;
39
+ --xp-series-b: #eb6834;
40
+ --xp-series-c: #8056b3;
41
+ --xp-radius-control: 8px;
42
+ --xp-radius-card: 12px;
43
+ --xp-radius-chip: 6px;
44
+ --xp-font: var(--font-body);
45
+ --xp-mono: var(--font-mono);
46
+ --xp-text-display: 600 28px/32px var(--xp-font);
47
+ --xp-text-title: 600 20px/28px var(--xp-font);
48
+ --xp-text-body: 400 15px/22px var(--xp-font);
49
+ --xp-text-small: 400 13px/18px var(--xp-font);
50
+ --xp-motion: 120ms;
51
+ --xp-space-half: 2px;
52
+ --xp-space-one-half: 6px;
53
+ --xp-version-share-width: 96px;
54
+ --xp-version-share-width-mobile: 84px;
55
+ --xp-version-state-width: 192px;
56
+ --xp-version-remove-width: 128px;
57
+ --xp-targeting-attribute-width: 150px;
58
+ --xp-targeting-comparison-width: 140px;
59
+ --xp-legend-key-size: 10px;
60
+ --xp-legend-key-radius: 3px;
61
+ --xp-space-1: 4px;
62
+ --xp-space-2: 8px;
63
+ --xp-space-3: 12px;
64
+ --xp-space-4: 16px;
65
+ --xp-space-5: 20px;
66
+ --xp-space-6: 24px;
67
+ --xp-space-7: 28px;
68
+ --xp-space-8: 32px;
69
+ --xp-surface-raised: var(--xp-surface-2);
9
70
  --xp-border-control: var(--theme-elevation-250);
10
71
  --xp-input-border: var(--theme-elevation-300);
11
72
  --xp-border-accent: var(--theme-elevation-400);
12
- --xp-accent: var(--theme-elevation-500);
13
- --xp-text-muted: var(--theme-elevation-600);
14
- --xp-text-strong: var(--theme-elevation-800);
15
- --xp-text-emphasis: var(--theme-elevation-900);
73
+ --xp-text-muted: var(--xp-text-2);
74
+ --xp-text-strong: var(--xp-text);
75
+ --xp-text-emphasis: var(--xp-text);
16
76
  --xp-input-bg: var(--theme-input-bg);
17
77
  --xp-overlay: var(--theme-overlay);
18
- --xp-success-surface: var(--theme-success-100);
19
- --xp-success-bg: var(--theme-success-150);
20
- --xp-success: var(--theme-success-500);
21
- --xp-success-text: var(--theme-success-600);
22
- --xp-success-text-strong: var(--theme-success-850);
23
- --xp-warning-surface: var(--theme-warning-100);
24
- --xp-warning-bg: var(--theme-warning-150);
25
- --xp-warning: var(--theme-warning-500);
26
- --xp-warning-text: var(--theme-warning-600);
27
- --xp-warning-text-strong: var(--theme-warning-850);
28
- --xp-error-surface: var(--theme-error-100);
29
- --xp-error: var(--theme-error-500);
30
- --xp-error-text: var(--theme-error-600);
31
- --xp-radius: var(--style-radius-s);
32
- --xp-radius-large: var(--style-radius-m);
33
- --xp-space-1: calc(var(--base) * 0.25);
34
- --xp-space-2: calc(var(--base) * 0.5);
35
- --xp-space-3: calc(var(--base) * 0.75);
36
- --xp-space-4: calc(var(--base) * 1);
37
- --xp-space-5: calc(var(--base) * 1.5);
38
- --xp-space-6: calc(var(--base) * 2);
78
+ --xp-success-surface: var(--xp-success-bg);
79
+ --xp-success-text: var(--xp-success);
80
+ --xp-success-text-strong: var(--xp-success);
81
+ --xp-warning-surface: var(--xp-warning-bg);
82
+ --xp-warning-text: var(--xp-warning);
83
+ --xp-warning-text-strong: var(--xp-warning);
84
+ --xp-error-surface: var(--xp-error-bg);
85
+ --xp-error-text: var(--xp-error);
86
+ --xp-radius: var(--xp-radius-control);
87
+ --xp-radius-large: var(--xp-radius-card);
39
88
  --xp-dash-card-gap: 12px;
40
- --xp-dash-card-gap-small: calc(var(--base) * 0.4);
41
- --xp-font-body: var(--font-body);
42
- --xp-font-size-small: calc(var(--base) * 0.65);
43
- --xp-font-size-body: calc(var(--base) * 0.75);
89
+ --xp-dash-card-gap-small: 8px;
90
+ --xp-font-body: var(--xp-font);
91
+ --xp-font-size-small: 13px;
92
+ --xp-font-size-body: 15px;
93
+ }
94
+ [data-theme="dark"] {
95
+ --xp-bg: var(--theme-elevation-0);
96
+ --xp-surface: var(--theme-elevation-50);
97
+ --xp-surface-2: var(--theme-elevation-100);
98
+ --xp-surface-3: var(--theme-elevation-150);
99
+ --xp-border: var(--theme-elevation-100);
100
+ --xp-border-strong: var(--theme-elevation-200);
101
+ --xp-text: var(--theme-elevation-800);
102
+ --xp-text-2: var(--theme-elevation-550);
103
+ --xp-text-3: var(--theme-elevation-350);
104
+ --xp-accent: var(--theme-elevation-800);
105
+ --xp-accent-hover: var(--theme-elevation-900);
106
+ --xp-accent-active: var(--theme-elevation-1000);
107
+ --xp-accent-subtle: var(--theme-elevation-200);
108
+ --xp-accent-fg: var(--theme-elevation-0);
109
+ --xp-focus: var(--theme-elevation-800);
110
+ --xp-series-muted: var(--theme-elevation-250);
111
+ --xp-success: #7fd49c;
112
+ --xp-success-bg: #142b1d;
113
+ --xp-success-border: #23492f;
114
+ --xp-warning: #e7bd5c;
115
+ --xp-warning-bg: #2b2312;
116
+ --xp-warning-border: #4d3d17;
117
+ --xp-error: #f09494;
118
+ --xp-error-bg: #2f1717;
119
+ --xp-error-border: #5a2626;
120
+ --xp-series-a: #3987e5;
121
+ --xp-series-b: #d95926;
122
+ --xp-series-c: #b48ade;
44
123
  }
@@ -1,9 +1,33 @@
1
+ /** Host accent rules (reference surfaces are defined by theme.css):
2
+ * (i) Foreground on accent, hover and active fills reaches 4.5:1 in both themes.
3
+ * (ii) Accent text moves toward the surface's opposite to reach 4.5:1; if the
4
+ * bounded search fails, use the neutral text token and underline it.
5
+ * (iii) Focus reaches 3:1 against the surface: darken on light, lighten on dark.
6
+ */
7
+ export { deriveAccent } from '../ui/accent.js';
1
8
  /** every Payload token reference lives in theme.css; porting to Payload 4 edits that file only */
2
9
  export declare const theme: {
3
10
  readonly color: {
4
11
  readonly bg: "var(--xp-bg)";
5
12
  readonly text: "var(--xp-text)";
6
13
  readonly surface: "var(--xp-surface)";
14
+ readonly surface2: "var(--xp-surface-2)";
15
+ readonly surface3: "var(--xp-surface-3)";
16
+ readonly text2: "var(--xp-text-2)";
17
+ readonly text3: "var(--xp-text-3)";
18
+ readonly accentHover: "var(--xp-accent-hover)";
19
+ readonly accentActive: "var(--xp-accent-active)";
20
+ readonly accentText: "var(--xp-accent-text)";
21
+ readonly accentSubtle: "var(--xp-accent-subtle)";
22
+ readonly accentFg: "var(--xp-accent-fg)";
23
+ readonly focus: "var(--xp-focus)";
24
+ readonly successBorder: "var(--xp-success-border)";
25
+ readonly warningBorder: "var(--xp-warning-border)";
26
+ readonly errorBg: "var(--xp-error-bg)";
27
+ readonly errorBorder: "var(--xp-error-border)";
28
+ readonly seriesA: "var(--xp-series-a)";
29
+ readonly seriesB: "var(--xp-series-b)";
30
+ readonly seriesMuted: "var(--xp-series-muted)";
7
31
  readonly surfaceRaised: "var(--xp-surface-raised)";
8
32
  readonly border: "var(--xp-border)";
9
33
  readonly borderStrong: "var(--xp-border-strong)";
@@ -30,7 +54,17 @@ export declare const theme: {
30
54
  readonly error: "var(--xp-error)";
31
55
  readonly errorText: "var(--xp-error-text)";
32
56
  };
57
+ readonly motion: "var(--xp-motion)";
58
+ readonly type: {
59
+ readonly display: "var(--xp-text-display)";
60
+ readonly title: "var(--xp-text-title)";
61
+ readonly body: "var(--xp-text-body)";
62
+ readonly small: "var(--xp-text-small)";
63
+ };
33
64
  readonly radius: {
65
+ readonly control: "var(--xp-radius-control)";
66
+ readonly card: "var(--xp-radius-card)";
67
+ readonly chip: "var(--xp-radius-chip)";
34
68
  readonly small: "var(--xp-radius)";
35
69
  readonly large: "var(--xp-radius-large)";
36
70
  };
@@ -41,8 +75,12 @@ export declare const theme: {
41
75
  readonly 4: "var(--xp-space-4)";
42
76
  readonly 5: "var(--xp-space-5)";
43
77
  readonly 6: "var(--xp-space-6)";
78
+ readonly 7: "var(--xp-space-7)";
79
+ readonly 8: "var(--xp-space-8)";
44
80
  };
45
81
  readonly font: {
82
+ readonly mono: "var(--xp-mono)";
83
+ readonly family: "var(--xp-font)";
46
84
  readonly body: "var(--xp-font-body)";
47
85
  readonly small: "var(--xp-font-size-small)";
48
86
  readonly size: "var(--xp-font-size-body)";
@@ -1,8 +1,31 @@
1
+ /** Host accent rules (reference surfaces are defined by theme.css):
2
+ * (i) Foreground on accent, hover and active fills reaches 4.5:1 in both themes.
3
+ * (ii) Accent text moves toward the surface's opposite to reach 4.5:1; if the
4
+ * bounded search fails, use the neutral text token and underline it.
5
+ * (iii) Focus reaches 3:1 against the surface: darken on light, lighten on dark.
6
+ */ export { deriveAccent } from '../ui/accent.js';
1
7
  /** every Payload token reference lives in theme.css; porting to Payload 4 edits that file only */ export const theme = {
2
8
  color: {
3
9
  bg: 'var(--xp-bg)',
4
10
  text: 'var(--xp-text)',
5
11
  surface: 'var(--xp-surface)',
12
+ surface2: 'var(--xp-surface-2)',
13
+ surface3: 'var(--xp-surface-3)',
14
+ text2: 'var(--xp-text-2)',
15
+ text3: 'var(--xp-text-3)',
16
+ accentHover: 'var(--xp-accent-hover)',
17
+ accentActive: 'var(--xp-accent-active)',
18
+ accentText: 'var(--xp-accent-text)',
19
+ accentSubtle: 'var(--xp-accent-subtle)',
20
+ accentFg: 'var(--xp-accent-fg)',
21
+ focus: 'var(--xp-focus)',
22
+ successBorder: 'var(--xp-success-border)',
23
+ warningBorder: 'var(--xp-warning-border)',
24
+ errorBg: 'var(--xp-error-bg)',
25
+ errorBorder: 'var(--xp-error-border)',
26
+ seriesA: 'var(--xp-series-a)',
27
+ seriesB: 'var(--xp-series-b)',
28
+ seriesMuted: 'var(--xp-series-muted)',
6
29
  surfaceRaised: 'var(--xp-surface-raised)',
7
30
  border: 'var(--xp-border)',
8
31
  borderStrong: 'var(--xp-border-strong)',
@@ -29,7 +52,17 @@
29
52
  error: 'var(--xp-error)',
30
53
  errorText: 'var(--xp-error-text)'
31
54
  },
55
+ motion: 'var(--xp-motion)',
56
+ type: {
57
+ display: 'var(--xp-text-display)',
58
+ title: 'var(--xp-text-title)',
59
+ body: 'var(--xp-text-body)',
60
+ small: 'var(--xp-text-small)'
61
+ },
32
62
  radius: {
63
+ control: 'var(--xp-radius-control)',
64
+ card: 'var(--xp-radius-card)',
65
+ chip: 'var(--xp-radius-chip)',
33
66
  small: 'var(--xp-radius)',
34
67
  large: 'var(--xp-radius-large)'
35
68
  },
@@ -39,9 +72,13 @@
39
72
  3: 'var(--xp-space-3)',
40
73
  4: 'var(--xp-space-4)',
41
74
  5: 'var(--xp-space-5)',
42
- 6: 'var(--xp-space-6)'
75
+ 6: 'var(--xp-space-6)',
76
+ 7: 'var(--xp-space-7)',
77
+ 8: 'var(--xp-space-8)'
43
78
  },
44
79
  font: {
80
+ mono: 'var(--xp-mono)',
81
+ family: 'var(--xp-font)',
45
82
  body: 'var(--xp-font-body)',
46
83
  small: 'var(--xp-font-size-small)',
47
84
  size: 'var(--xp-font-size-body)'
@@ -1,17 +1,32 @@
1
1
  export declare function useApi(): {
2
2
  start: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
3
- allocate: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").AllocateBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
4
- addMetric: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").AddMetricBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
3
+ allocate: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").AllocateBody, options?: {
4
+ notFound?: string;
5
+ codes?: Readonly<Record<string, string>>;
6
+ fallback?: string;
7
+ }) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
8
+ addMetric: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").AddMetricBody, options?: {
9
+ notFound?: string;
10
+ codes?: Readonly<Record<string, string>>;
11
+ fallback?: string;
12
+ }) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
5
13
  stop: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
6
14
  decide: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").DecideBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
7
15
  releaseHoldout: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
16
+ releasePages: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
8
17
  makeContent: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").MakeContentResult>>;
18
+ dismissContentEntry: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").DismissContentEntryBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").DismissContentEntryResult>>;
19
+ restoreContentEntry: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").RestoreContentEntryBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").RestoreContentEntryResult>>;
9
20
  archive: (id: import("../types/contracts.js").DocId, body: import("../types/endpoints.js").ArchiveBody) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").TransitionSuccess>>;
10
21
  preflight: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").PreflightResult>>;
11
22
  previewToken: (id: import("../types/contracts.js").DocId, arm: string) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").PreviewTokenResult>>;
12
23
  results: (id: import("../types/contracts.js").DocId, options?: {
13
24
  phase?: number;
14
25
  series?: boolean;
26
+ } & {
27
+ notFound?: string;
28
+ codes?: Readonly<Record<string, string>>;
29
+ fallback?: string;
15
30
  }) => Promise<import("./api.js").ApiResult<import("../types/endpoints.js").ResultsResponse>>;
16
31
  recompute: (id: import("../types/contracts.js").DocId) => Promise<import("./api.js").ApiResult<{
17
32
  snapshotId: string;
@@ -1,4 +1,5 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { accentStyles } from '../../ui/accent.js';
2
3
  import { formatAdminURL } from 'payload/shared';
3
4
  import { notFound, redirect } from 'next/navigation.js';
4
5
  import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent';
@@ -166,10 +167,19 @@ export async function ExperimentsRootView({ initPageResult, params, searchParams
166
167
  children: SURFACE_COPY.notInstalled
167
168
  });
168
169
  }
169
- return RenderServerComponent({
170
+ const css = accentStyles(options.theme?.accent);
171
+ const surface = RenderServerComponent({
170
172
  Component: XP_TESTS_SURFACE,
171
173
  Fallback: TestsSurfaceFallback,
172
174
  importMap: req.payload.importMap,
173
175
  clientProps
174
176
  });
177
+ return css ? /*#__PURE__*/ _jsxs(_Fragment, {
178
+ children: [
179
+ /*#__PURE__*/ _jsx("style", {
180
+ children: css
181
+ }),
182
+ surface
183
+ ]
184
+ }) : surface;
175
185
  }
@@ -2,12 +2,14 @@ import * as paths from '../../admin/paths.js';
2
2
  import { managedAccess } from '../access.js';
3
3
  import { serializeAttributeKeys, serializeFlagPoints } from '../serialize.js';
4
4
  import { TARGETING_OPS, isOpValid } from '../../targeting/ops.js';
5
+ import { readListValue } from '../../targeting/values.js';
5
6
  import { createDefaults } from './createDefaults.js';
6
7
  import { flagControlParams } from './flagControlParams.js';
7
8
  import { lockHook } from './lockHook.js';
8
9
  import { deleteGuard, deleteGuardAfter } from './deleteGuard.js';
9
10
  import { checkArms } from './armInvariants.js';
10
11
  import { XpNotImplemented, XpRefusal } from '../../errors.js';
12
+ const targetingValue = (rule)=>rule.op === 'in' || rule.op === 'nin' ? readListValue(rule.value) : rule.value;
11
13
  const positiveInteger = (value)=>value != null && Number.isInteger(value) && value > 0 || 'Enter a positive whole number.';
12
14
  export function experimentsCollection(options) {
13
15
  const flagPoints = serializeFlagPoints(options);
@@ -224,6 +226,21 @@ export function experimentsCollection(options) {
224
226
  {
225
227
  name: 'targeting',
226
228
  type: 'array',
229
+ validate: (value, { data, previousValue })=>{
230
+ // Payload 3.90.2 beforeChange/promise.js passes merged document data
231
+ // and this document's stored array as previousValue, including in bulk updates.
232
+ const status = data?.status ?? 'draft';
233
+ if (data?.kind === 'block' && (status === 'draft' || status === 'stopped')) {
234
+ for (const rule of value ?? []){
235
+ if (rule.attribute !== 'path') continue;
236
+ const previous = rule.id == null ? undefined : previousValue?.find((stored)=>stored.id != null && String(stored.id) === String(rule.id));
237
+ if (!previous || previous.attribute !== rule.attribute || previous.op !== rule.op || JSON.stringify(targetingValue(previous)) !== JSON.stringify(targetingValue(rule))) {
238
+ return 'Page block tests run on their page; remove the page-path rule.';
239
+ }
240
+ }
241
+ }
242
+ return true;
243
+ },
227
244
  fields: [
228
245
  {
229
246
  name: 'attribute',
@@ -477,6 +494,14 @@ export function experimentsCollection(options) {
477
494
  {
478
495
  name: 'holdoutReleasedAt',
479
496
  type: 'date'
497
+ },
498
+ {
499
+ name: 'pagesReleasedAt',
500
+ type: 'date'
501
+ },
502
+ {
503
+ name: 'pagesReleasedBy',
504
+ type: 'text'
480
505
  }
481
506
  ]
482
507
  }
@@ -12,6 +12,7 @@ const relationships = new Set([
12
12
  'metrics.secondary',
13
13
  'metrics.guardrails',
14
14
  'decision.decidedBy',
15
+ 'decision.pagesReleasedBy',
15
16
  'decision.snapshotId'
16
17
  ]);
17
18
  const dates = new Set([
@@ -20,7 +21,8 @@ const dates = new Set([
20
21
  'decidedAt',
21
22
  'archivedAt',
22
23
  'decision.discardedAt',
23
- 'decision.holdoutReleasedAt'
24
+ 'decision.holdoutReleasedAt',
25
+ 'decision.pagesReleasedAt'
24
26
  ]);
25
27
  /** Schema-aware: JSON retains its own ids and date-like strings. */ function normalize(value, path, json = false) {
26
28
  json ||= [