@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.
- package/README.md +129 -11
- package/dist/admin/PreviewLinks.d.ts +2 -1
- package/dist/admin/PreviewLinks.js +24 -16
- package/dist/admin/actions/ConfirmDialog.d.ts +7 -4
- package/dist/admin/actions/ConfirmDialog.js +37 -82
- package/dist/admin/actions/DecideDialog.d.ts +3 -1
- package/dist/admin/actions/DecideDialog.js +30 -13
- package/dist/admin/actions/MakeContentResult.d.ts +13 -2
- package/dist/admin/actions/MakeContentResult.js +115 -16
- package/dist/admin/actions/actions.model.d.ts +2 -4
- package/dist/admin/actions/actions.model.js +2 -3
- package/dist/admin/allocation.model.js +3 -5
- package/dist/admin/api.d.ts +10 -4
- package/dist/admin/api.js +11 -7
- package/dist/admin/copy/actions.d.ts +39 -3
- package/dist/admin/copy/actions.js +40 -4
- package/dist/admin/copy/armSelect.js +5 -5
- package/dist/admin/copy/audience.d.ts +2 -1
- package/dist/admin/copy/audience.js +3 -2
- package/dist/admin/copy/common.d.ts +13 -6
- package/dist/admin/copy/common.js +14 -8
- package/dist/admin/copy/goals.d.ts +21 -6
- package/dist/admin/copy/goals.js +27 -6
- package/dist/admin/copy/readiness.d.ts +19 -0
- package/dist/admin/copy/readiness.js +19 -0
- package/dist/admin/copy/recommendation.d.ts +0 -3
- package/dist/admin/copy/recommendation.js +0 -3
- package/dist/admin/copy/results.d.ts +41 -2
- package/dist/admin/copy/results.js +42 -3
- package/dist/admin/copy/review.d.ts +1 -1
- package/dist/admin/copy/review.js +1 -1
- package/dist/admin/copy/setting.d.ts +2 -0
- package/dist/admin/copy/setting.js +2 -0
- package/dist/admin/copy/surface.d.ts +7 -4
- package/dist/admin/copy/surface.js +9 -4
- package/dist/admin/copy/testPage.d.ts +10 -0
- package/dist/admin/copy/testPage.js +10 -0
- package/dist/admin/format/date.d.ts +2 -0
- package/dist/admin/format/date.js +10 -0
- package/dist/admin/previewLinks.model.d.ts +2 -2
- package/dist/admin/previewLinks.model.js +5 -7
- package/dist/admin/results/CiChart.js +11 -21
- package/dist/admin/results/DataQualityPanel.d.ts +4 -2
- package/dist/admin/results/DataQualityPanel.js +30 -17
- package/dist/admin/results/DataTable.d.ts +16 -0
- package/dist/admin/results/DataTable.js +152 -0
- package/dist/admin/results/StatTile.d.ts +7 -0
- package/dist/admin/results/StatTile.js +22 -0
- package/dist/admin/results/VerdictHero.d.ts +13 -0
- package/dist/admin/results/{VerdictCard.js → VerdictHero.js} +15 -12
- package/dist/admin/results/releasePages.model.d.ts +17 -0
- package/dist/admin/results/releasePages.model.js +10 -0
- package/dist/admin/results/results.css +64 -0
- package/dist/admin/results/results.model.d.ts +5 -1
- package/dist/admin/results/results.model.js +10 -2
- package/dist/admin/surface/ResultsRoute.d.ts +1 -1
- package/dist/admin/surface/ResultsRoute.js +59 -34
- package/dist/admin/surface/SurfaceShell.d.ts +11 -2
- package/dist/admin/surface/SurfaceShell.js +49 -14
- package/dist/admin/surface/TestsHome.d.ts +6 -1
- package/dist/admin/surface/TestsHome.js +183 -112
- package/dist/admin/surface/TestsSurface.js +56 -16
- package/dist/admin/surface/goals/GoalForm.js +341 -308
- package/dist/admin/surface/goals/GoalsList.js +123 -89
- package/dist/admin/surface/goals/data.d.ts +8 -2
- package/dist/admin/surface/goals/data.js +11 -3
- package/dist/admin/surface/goals/goals.css +10 -13
- package/dist/admin/surface/home.model.d.ts +7 -8
- package/dist/admin/surface/home.model.js +23 -12
- package/dist/admin/surface/newTest/NewTest.js +5 -5
- package/dist/admin/surface/pageBlock/parts.js +31 -26
- package/dist/admin/surface/readiness/ActionDialogs.js +43 -14
- package/dist/admin/surface/readiness/Banner.d.ts +3 -0
- package/dist/admin/surface/readiness/Banner.js +58 -33
- package/dist/admin/surface/readiness/SaveStatus.d.ts +1 -0
- package/dist/admin/surface/readiness/SaveStatus.js +6 -3
- package/dist/admin/surface/readiness/readiness.model.d.ts +2 -0
- package/dist/admin/surface/readiness/readiness.model.js +18 -9
- package/dist/admin/surface/readiness/useTransition.d.ts +1 -0
- package/dist/admin/surface/readiness/useTransition.js +2 -1
- package/dist/admin/surface/results/ReleasePages.d.ts +14 -0
- package/dist/admin/surface/results/ReleasePages.js +116 -0
- package/dist/admin/surface/results/ResultsBody.d.ts +11 -1
- package/dist/admin/surface/results/ResultsBody.js +276 -105
- package/dist/admin/surface/results/ResultsHeader.d.ts +10 -0
- package/dist/admin/surface/results/ResultsHeader.js +177 -0
- package/dist/admin/surface/results/recommendation.model.js +2 -6
- package/dist/admin/surface/results/useResults.d.ts +21 -3
- package/dist/admin/surface/results/useResults.js +2 -1
- package/dist/admin/surface/setting/parts.js +75 -88
- package/dist/admin/surface/setting/setting.css +3 -15
- package/dist/admin/surface/surface.css +62 -47
- package/dist/admin/surface/test/AdvancedSection.js +55 -48
- package/dist/admin/surface/test/GoalSection.js +39 -32
- package/dist/admin/surface/test/LengthSection.js +46 -34
- package/dist/admin/surface/test/Rail.d.ts +4 -0
- package/dist/admin/surface/test/Rail.js +83 -0
- package/dist/admin/surface/test/ReadinessBar.d.ts +5 -0
- package/dist/admin/surface/test/ReadinessBar.js +33 -0
- package/dist/admin/surface/test/Section.d.ts +1 -1
- package/dist/admin/surface/test/Section.js +36 -16
- package/dist/admin/surface/test/TargetingSection.js +168 -142
- package/dist/admin/surface/test/TestPage.d.ts +2 -1
- package/dist/admin/surface/test/TestPage.js +146 -68
- package/dist/admin/surface/test/VersionsSection.js +151 -120
- package/dist/admin/surface/test/fields.d.ts +11 -4
- package/dist/admin/surface/test/fields.js +84 -85
- package/dist/admin/surface/test/jumpToSection.d.ts +3 -0
- package/dist/admin/surface/test/jumpToSection.js +10 -0
- package/dist/admin/surface/test/pageParts.js +9 -3
- package/dist/admin/surface/test/testPage.css +81 -14
- package/dist/admin/surface/useTestDocument.js +10 -4
- package/dist/admin/targeting/targeting.model.d.ts +1 -3
- package/dist/admin/targeting/targeting.model.js +3 -8
- package/dist/admin/theme.css +115 -36
- package/dist/admin/theme.d.ts +38 -0
- package/dist/admin/theme.js +38 -1
- package/dist/admin/useApi.d.ts +17 -2
- package/dist/admin/views/ExperimentsRootView.js +12 -2
- package/dist/collections/experiments/config.js +25 -0
- package/dist/collections/experiments/lockHook.js +3 -1
- package/dist/collections/identities.js +6 -1
- package/dist/collections/metrics.js +7 -0
- package/dist/collections/metricsGuards.d.ts +9 -0
- package/dist/collections/metricsGuards.js +33 -0
- package/dist/contentDone.d.ts +4 -0
- package/dist/contentDone.js +5 -0
- package/dist/db/identities.d.ts +8 -7
- package/dist/db/identities.js +9 -6
- package/dist/db/metricUsage.d.ts +19 -0
- package/dist/db/metricUsage.js +54 -0
- package/dist/db/names.d.ts +5 -0
- package/dist/db/names.js +5 -0
- package/dist/db/publishedContent.d.ts +10 -0
- package/dist/db/publishedContent.js +43 -0
- package/dist/db/results.d.ts +2 -0
- package/dist/db/results.js +6 -2
- package/dist/db/snapshots.d.ts +18 -6
- package/dist/db/snapshots.js +23 -7
- package/dist/db/sweep.js +11 -6
- package/dist/endpoints/index.js +19 -1
- package/dist/endpoints/results.js +5 -0
- package/dist/endpoints/sanitizeReadinessDetail.d.ts +1 -0
- package/dist/endpoints/sanitizeReadinessDetail.js +2 -0
- package/dist/endpoints/summary.js +20 -2
- package/dist/endpoints/transitions.d.ts +15 -0
- package/dist/endpoints/transitions.js +36 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +28 -0
- package/dist/exports/index.d.ts +2 -1
- package/dist/exports/index.js +1 -0
- package/dist/exports/puckEditor.d.ts +1 -0
- package/dist/exports/puckEditor.js +1 -0
- package/dist/exports/rsc.d.ts +5 -1
- package/dist/exports/rsc.js +2 -0
- package/dist/hooks/pageGuard.d.ts +5 -0
- package/dist/hooks/pageGuard.js +121 -0
- package/dist/log.js +6 -3
- package/dist/manifest/compile.d.ts +5 -0
- package/dist/manifest/compile.js +5 -1
- package/dist/options.js +23 -0
- package/dist/plugin.js +26 -2
- package/dist/puck/copy.d.ts +1 -1
- package/dist/puck/copy.js +1 -1
- package/dist/puck/editor/editor.chrome.css +235 -0
- package/dist/puck/editor/editor.css +241 -43
- package/dist/results/aggregate.js +4 -3
- package/dist/results/analyse.d.ts +4 -1
- package/dist/results/analyse.js +27 -11
- package/dist/results/cuped.js +3 -2
- package/dist/results/identityCredit.d.ts +15 -7
- package/dist/results/identityCredit.js +32 -33
- package/dist/results/rolloutUnits.js +2 -1
- package/dist/results/snapshot.js +8 -2
- package/dist/results/srm.js +2 -1
- package/dist/sanitizeReadinessDetail.d.ts +7 -0
- package/dist/sanitizeReadinessDetail.js +54 -0
- package/dist/server/linkIdentity.d.ts +10 -1
- package/dist/server/linkIdentity.js +17 -15
- package/dist/server/metadata.d.ts +5 -0
- package/dist/server/metadata.js +9 -1
- package/dist/server/runResults.d.ts +3 -2
- package/dist/server/runResults.js +121 -31
- package/dist/targeting/values.d.ts +4 -0
- package/dist/targeting/values.js +10 -0
- package/dist/transitions/addMetric.js +8 -0
- package/dist/transitions/archive.js +2 -3
- package/dist/transitions/checkStart.d.ts +6 -3
- package/dist/transitions/checkStart.js +15 -0
- package/dist/transitions/contentFields.d.ts +7 -0
- package/dist/transitions/contentFields.js +20 -0
- package/dist/transitions/decide.js +6 -1
- package/dist/transitions/dismissContentEntry.d.ts +8 -0
- package/dist/transitions/dismissContentEntry.js +45 -0
- package/dist/transitions/makeContent.d.ts +5 -2
- package/dist/transitions/makeContent.js +217 -118
- package/dist/transitions/phaseRecord.d.ts +8 -2
- package/dist/transitions/placements.d.ts +1 -0
- package/dist/transitions/placements.js +4 -0
- package/dist/transitions/preflightData.d.ts +1 -1
- package/dist/transitions/preflightData.js +42 -4
- package/dist/transitions/releasePages.d.ts +8 -0
- package/dist/transitions/releasePages.js +26 -0
- package/dist/transitions/restoreContentEntry.d.ts +8 -0
- package/dist/transitions/restoreContentEntry.js +47 -0
- package/dist/transitions/start.js +30 -5
- package/dist/types/contracts.d.ts +14 -0
- package/dist/types/endpoints.d.ts +34 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/reasons.d.ts +3 -0
- package/dist/types/reasons.js +1 -0
- package/dist/types/snapshot.d.ts +6 -1
- package/dist/ui/Banner.d.ts +2 -0
- package/dist/ui/Banner.js +2 -0
- package/dist/ui/Breadcrumb.d.ts +7 -0
- package/dist/ui/Breadcrumb.js +25 -0
- package/dist/ui/Button.d.ts +15 -0
- package/dist/ui/Button.js +32 -0
- package/dist/ui/Card.d.ts +12 -0
- package/dist/ui/Card.js +40 -0
- package/dist/ui/Checkbox.d.ts +2 -0
- package/dist/ui/Checkbox.js +2 -0
- package/dist/{admin/components → ui}/ChipsInput.d.ts +4 -3
- package/dist/{admin/components → ui}/ChipsInput.js +20 -12
- package/dist/ui/Dialog.d.ts +13 -0
- package/dist/ui/Dialog.js +85 -0
- package/dist/ui/EmptyState.d.ts +2 -0
- package/dist/ui/EmptyState.js +2 -0
- package/dist/ui/Feedback.d.ts +24 -0
- package/dist/ui/Feedback.js +40 -0
- package/dist/ui/Inputs.d.ts +34 -0
- package/dist/ui/Inputs.js +225 -0
- package/dist/ui/NumberInput.d.ts +2 -0
- package/dist/ui/NumberInput.js +2 -0
- package/dist/ui/Range.d.ts +2 -0
- package/dist/ui/Range.js +2 -0
- package/dist/ui/SearchInput.d.ts +2 -0
- package/dist/ui/SearchInput.js +2 -0
- package/dist/ui/SectionCard.d.ts +2 -0
- package/dist/ui/SectionCard.js +2 -0
- package/dist/ui/Select.d.ts +2 -0
- package/dist/ui/Select.js +2 -0
- package/dist/ui/StatusChip.d.ts +2 -0
- package/dist/ui/StatusChip.js +2 -0
- package/dist/ui/Tabs.d.ts +14 -0
- package/dist/ui/Tabs.js +54 -0
- package/dist/ui/TextInput.d.ts +2 -0
- package/dist/ui/TextInput.js +2 -0
- package/dist/ui/Textarea.d.ts +2 -0
- package/dist/ui/Textarea.js +2 -0
- package/dist/ui/Toggle.d.ts +2 -0
- package/dist/ui/Toggle.js +2 -0
- package/dist/ui/accent.d.ts +21 -0
- package/dist/ui/accent.js +81 -0
- package/dist/ui/index.d.ts +9 -0
- package/dist/ui/index.js +9 -0
- package/dist/ui/ui.css +92 -0
- package/package.json +1 -1
- package/dist/admin/components/chipsInput.css +0 -11
- package/dist/admin/results/ArmTable.d.ts +0 -8
- package/dist/admin/results/ArmTable.js +0 -113
- package/dist/admin/results/VerdictCard.d.ts +0 -9
- package/dist/admin/surface/goals/GoalsBreadcrumb.d.ts +0 -5
- package/dist/admin/surface/goals/GoalsBreadcrumb.js +0 -47
- package/dist/admin/surface/test/TestHeader.d.ts +0 -4
- package/dist/admin/surface/test/TestHeader.js +0 -70
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @delmaredigital/payload-experiments
|
|
2
2
|
|
|
3
|
+
> ⚠️ Upgrading to 0.3? For 0.3.0-rc.1, every `linkIdentity` call must supply `verified`; it now returns an outcome. Run `payload migrate:create` for the nullable `xp_identities.verified_at` column and `user_id` index, plus the release-pages columns listed in the CHANGELOG (`decision_pages_released_at` / `decision_pages_released_by` on `xp_experiments` and their `version_` counterparts on `_xp_experiments_v`). Existing identity links become unverified; see the CHANGELOG for optional backfill.
|
|
4
|
+
|
|
3
5
|
> ⚠️ Upgrading to 0.2? After upgrading, run `payload generate:importmap`: the Tests surface is registered through admin.dependencies, and without it the Tests routes show a 'not installed' message instead of the Tests screen. In 0.2.0-rc.2, the Tests server views moved from `/rsc` to `/admin/rsc`, so the import map must be regenerated when upgrading from rc.1 too. The old experiments edit-form components are gone from `/admin/client`, and the `xp-experiments` admin list and edit views now redirect to the Tests surface.
|
|
4
6
|
|
|
5
7
|
A/B testing for Payload CMS and Next.js, with results inside the admin and data
|
|
@@ -87,7 +89,7 @@ permission is granted only for `true`. Optional `access.admin` and
|
|
|
87
89
|
`access.readResults` default to `manage`; `access.readIdentities` defaults to
|
|
88
90
|
`admin`. Use `admin` for code experiments and changing the analysis method.
|
|
89
91
|
Other plugin options are `flagPoints`, `metricSources`, `eventSources`,
|
|
90
|
-
`attributeKeys`, `puck` and `events`:
|
|
92
|
+
`attributeKeys`, `pages`, `puck` and `events`:
|
|
91
93
|
|
|
92
94
|
- `attributeKeys` declares targeting attributes as `{ type, values? }`, with
|
|
93
95
|
`type` one of `string`, `number`, `boolean`; optional string `values` supplies
|
|
@@ -261,11 +263,8 @@ Other server integrations:
|
|
|
261
263
|
outside a Next request; a user-only server conversion can supply `userId`.
|
|
262
264
|
Record revenue through trusted server code or a metric source, never a client
|
|
263
265
|
beacon.
|
|
264
|
-
- `linkIdentity(serverRuntime, { headers, userId })` belongs on
|
|
265
|
-
user-creation and sign-in path
|
|
266
|
-
opaque user id using insert-ignore on its own connection and never throws into
|
|
267
|
-
sign-in. For visitor experiments using user-keyed outcomes, credit goes to the
|
|
268
|
-
earliest-exposed linked visitor; cross-group links are reported separately.
|
|
266
|
+
- `linkIdentity(serverRuntime, { headers, userId, verified })` belongs on every
|
|
267
|
+
user-creation and proving sign-in path; see Identity linking below.
|
|
269
268
|
- `eraseUser(payload, { userId })` deletes the user's identities and associated
|
|
270
269
|
events transactionally and returns `{ identities, events }` counts. Call it
|
|
271
270
|
from the host's authorized user-erasure flow.
|
|
@@ -279,11 +278,72 @@ Other server integrations:
|
|
|
279
278
|
failure is logged and the content save proceeds.
|
|
280
279
|
- `experimentMetadata(serverRuntime, path)` returns metadata to merge into the
|
|
281
280
|
page's Next metadata: live variant targets get noindex and an Original
|
|
282
|
-
canonical; archived losing paths point canonically to the winner.
|
|
281
|
+
canonical; archived losing paths point canonically to the winner. It reads
|
|
282
|
+
the signed request context via `headers()`, so it decides from the URL the
|
|
283
|
+
visitor requested, not the rewritten route, and the Original's URL is never
|
|
284
|
+
noindex. Call it only from a dynamic `generateMetadata`, never inside
|
|
285
|
+
`'use cache'`; `path` is the fallback when the proxy did not run.
|
|
283
286
|
`experimentSitemapFilter(path, manifest)` is a synchronous build-time filter:
|
|
284
287
|
pass a privately server-loaded manifest and keep paths for which it returns
|
|
285
288
|
`true`, excluding live variants and archived losing paths.
|
|
286
289
|
|
|
290
|
+
## Identity linking
|
|
291
|
+
|
|
292
|
+
Call `linkIdentity(serverRuntime, { headers, userId, verified })` on every
|
|
293
|
+
user-creation and proving sign-in path. It links the signed anonymous cookie
|
|
294
|
+
to an opaque user id on its own connection and never throws into sign-in.
|
|
295
|
+
`verified` is required. Pass `true` only when **this request proves, in this
|
|
296
|
+
browser, that the person owns the account**: a magic-link or one-time-code
|
|
297
|
+
callback, password sign-in, or OAuth callback. It does not mean "a session
|
|
298
|
+
exists" or "the account's email is verified". Registration alone uses `false`.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
// Registration, before proof of account ownership:
|
|
302
|
+
await linkIdentity(serverRuntime, { headers, userId, verified: false })
|
|
303
|
+
// After successful magic-link/code verification in this browser:
|
|
304
|
+
await linkIdentity(serverRuntime, { headers, userId, verified: true })
|
|
305
|
+
// After a successful password sign-in (or OAuth callback):
|
|
306
|
+
await linkIdentity(serverRuntime, { headers, userId, verified: true })
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
An auth "after" hook that fires whenever a new session cookie is set also
|
|
310
|
+
fires on routine session refreshes; pass `verified: true` only from proving
|
|
311
|
+
sign-in paths, never from a refresh.
|
|
312
|
+
|
|
313
|
+
The result is `'linked'` for an inserted link, `'verified'` for an upgraded
|
|
314
|
+
unverified link, `'skipped'` for no consent, no valid unit cookie or an existing
|
|
315
|
+
link with nothing to change, and `'failed'` for an error (safely logged).
|
|
316
|
+
First link per browser wins; a different account never replaces it.
|
|
317
|
+
|
|
318
|
+
For visitor experiments with user-keyed events, verified links are always
|
|
319
|
+
eligible. Unverified links are eligible only before the first verification
|
|
320
|
+
across all of that user's browsers, or indefinitely if nobody ever verifies.
|
|
321
|
+
Each event goes to the earliest exposed eligible visitor. Eligible links
|
|
322
|
+
spanning groups are reported as a diagnostic. User-keyed CUPED covariates use
|
|
323
|
+
the same eligibility rule at each visitor's first exposure and credit one
|
|
324
|
+
aggregate value to the earliest eligible visitor per user.
|
|
325
|
+
|
|
326
|
+
Accepted limits:
|
|
327
|
+
|
|
328
|
+
- Cross-device: register in A, verify on B, keep using A. A's events after B's
|
|
329
|
+
proof are not credited until a proving sign-in on A upgrades A. Events before
|
|
330
|
+
B's proof, including the registration funnel, remain credited. Once A is
|
|
331
|
+
upgraded, its link is verified and eligible for all its events, so the next
|
|
332
|
+
results run also credits A's events from between B's proof and the upgrade.
|
|
333
|
+
- Password set before verification: a later password sign-in is proof the
|
|
334
|
+
plugin accepts. Whether unverified registration may set a password is a host
|
|
335
|
+
authentication concern.
|
|
336
|
+
- Shared device: a different user's verified sign-in never relinks an already
|
|
337
|
+
linked browser and returns `'skipped'`; that user's events get no credit
|
|
338
|
+
through that browser.
|
|
339
|
+
- If someone registers another person's email from their own browser and that
|
|
340
|
+
person never signs in, a later user-keyed event the host records against that
|
|
341
|
+
account (for example an offline gift) is credited to the registering browser.
|
|
342
|
+
It needs a deliberate registration of someone else's email plus an event
|
|
343
|
+
matched to that account, and it moves one unit's value, never assignment.
|
|
344
|
+
There is no time window, so recurring gifts from never-signed-in donors keep
|
|
345
|
+
counting.
|
|
346
|
+
|
|
287
347
|
## Results
|
|
288
348
|
|
|
289
349
|
The host owns scheduling and authentication. Call
|
|
@@ -368,7 +428,8 @@ a user-keyed source. Visitor experiments can use a user-keyed source through
|
|
|
368
428
|
linked identities.
|
|
369
429
|
|
|
370
430
|
Optional `covariate({ units: [{ unitId, firstExposureTs }], lookbackDays })`
|
|
371
|
-
returns `[{ unitId, value }]
|
|
431
|
+
returns `[{ unitId, value }]`, one aggregate value per unit for pre-exposure
|
|
432
|
+
CUPED adjustment (the plugin asks for a 14-day lookback). Percentile
|
|
372
433
|
revenue capping reuses `query` over the 90 days before Start; the resolved cap
|
|
373
434
|
is frozen for the round. Capping affects decision statistics, while results
|
|
374
435
|
also show uncapped totals. Count and binomial metrics are never capped.
|
|
@@ -389,6 +450,41 @@ are recorded with `trackConversion`. Beacon conversions for a non-writable name
|
|
|
389
450
|
are dropped silently. Only listed names become `events:<name>` sources. They
|
|
390
451
|
support visitor and user units and do not represent counts or revenue in v1.
|
|
391
452
|
|
|
453
|
+
### Whole-page options
|
|
454
|
+
|
|
455
|
+
| Option | Purpose |
|
|
456
|
+
| --- | --- |
|
|
457
|
+
| `pages.collections` | Optional array of `{ slug, pathOf, findByPath }`. `pathOf(doc)` derives a public path from the slug (used to protect live test paths); `findByPath(path, req)` resolves a published page to `{ id, published: true }`, or returns `null`. Start and readiness check every non-holdout version, including the Original. Registered collections must exist when this plugin runs. Localized publication status and field-level `beforeChange` hooks that rewrite paths after the collection guard are unsupported; derive paths from fields finalized before collection hooks. Without this option, readiness warns that pages are unchecked and Start keeps its existing behaviour. |
|
|
458
|
+
|
|
459
|
+
Do not swallow errors from nested updates of guarded page collections inside host hooks: a swallowed error leaves the guard's operation frame in place for the rest of the request because Payload's Local API does not call `afterError`.
|
|
460
|
+
|
|
461
|
+
For a collection with drafts enabled, use a published-only slug lookup with the
|
|
462
|
+
manager's request and `overrideAccess: false`:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
pages: {
|
|
466
|
+
collections: [{
|
|
467
|
+
slug: 'pages',
|
|
468
|
+
pathOf: doc => typeof doc.slug === 'string' ? `/${doc.slug}` : null,
|
|
469
|
+
findByPath: async (path, req) => {
|
|
470
|
+
const { docs } = await req.payload.find({
|
|
471
|
+
collection: 'pages', req, overrideAccess: false, draft: false, depth: 0, limit: 1,
|
|
472
|
+
where: { and: [
|
|
473
|
+
{ slug: { equals: path.slice(1) } },
|
|
474
|
+
{ _status: { equals: 'published' } },
|
|
475
|
+
] },
|
|
476
|
+
})
|
|
477
|
+
return docs[0] ? { id: docs[0].id, published: true } : null
|
|
478
|
+
},
|
|
479
|
+
}],
|
|
480
|
+
},
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Adapt both callbacks for the site's homepage path. These checks run before Start;
|
|
484
|
+
they do not protect pages from later deletion, unpublishing or renaming. Keep an
|
|
485
|
+
archived winner published at the same address: archived test URLs still point to it.
|
|
486
|
+
No migration is needed.
|
|
487
|
+
|
|
392
488
|
### `admin` options
|
|
393
489
|
|
|
394
490
|
Host links and kind descriptions are optional. For a host using payload-puck's
|
|
@@ -632,10 +728,32 @@ changes block actions**; save first so they use the saved experiment.
|
|
|
632
728
|
- The Puck picker lists 100 experiments.
|
|
633
729
|
- The Puck experiment picker always calls `/api`; hosts with a custom API route
|
|
634
730
|
see an empty picker.
|
|
635
|
-
- Making a winner permanent skips pages in collections that are localized,
|
|
636
|
-
have hidden fields, or contain fields the manager cannot read or update. Those pages
|
|
637
|
-
are listed with a note to update them by hand.
|
|
638
731
|
- Content-change markers are recorded on publish only (on save for collections
|
|
639
732
|
without drafts).
|
|
640
733
|
- Results cover the whole round; breakdowns by segment or dimension are not
|
|
641
734
|
available in v1.
|
|
735
|
+
|
|
736
|
+
### Limits
|
|
737
|
+
|
|
738
|
+
| Capability | Limit |
|
|
739
|
+
|---|---|
|
|
740
|
+
| Make it the content: localized collections | not supported yet |
|
|
741
|
+
| Make it the content: field defaults | An update-denied field empty in both versions may publish its default because Payload applies defaults when reading. |
|
|
742
|
+
|
|
743
|
+
## Theme
|
|
744
|
+
|
|
745
|
+
The admin inherits Payload's neutral palette by default. Set one host accent:
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
theme: { accent: '#1f3a68' }
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
`theme.accent` accepts `#rgb` or `#rrggbb`; empty or undefined uses the default.
|
|
752
|
+
The plugin derives darker hover/active fills (clamped to keep text contrast), a
|
|
753
|
+
contrasting focus ring, and an opaque 8% subtle tint.
|
|
754
|
+
In dark mode it raises HSL lightness when needed to reach 3:1 against the
|
|
755
|
+
reference dark surface, then selects white or near-black foreground text.
|
|
756
|
+
The WCAG 2.x gate requires at least 4.5:1 **foreground text on the accent fill**
|
|
757
|
+
in each theme (the accent is used as a fill, not as page text). A failing
|
|
758
|
+
colour is refused with its ratio. Status chips retain Payload semantic colours.
|
|
759
|
+
`deriveAccent` is also exported from `/puck/editor` for editor integrations.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DocId } from '../types/contracts.js';
|
|
2
2
|
import type { PreflightResult } from '../types/endpoints.js';
|
|
3
|
-
export declare function PreviewLinks({ experimentId, placements, arms }: {
|
|
3
|
+
export declare function PreviewLinks({ experimentId, placements, arms, flagPoint }: {
|
|
4
|
+
flagPoint?: string;
|
|
4
5
|
experimentId: DocId;
|
|
5
6
|
placements: PreflightResult['placements'];
|
|
6
7
|
arms: readonly {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useState } from 'react';
|
|
4
|
-
import { Button } from '
|
|
4
|
+
import { Button } from '../ui/Button.js';
|
|
5
5
|
import { armLabel } from './labels.js';
|
|
6
6
|
import { useApi } from './useApi.js';
|
|
7
7
|
import { REVIEW_COPY as C } from './copy/review.js';
|
|
8
8
|
import { API_MESSAGES } from './copy/common.js';
|
|
9
9
|
import { previewPages } from './previewLinks.model.js';
|
|
10
|
-
export function PreviewLinks({ experimentId, placements, arms }) {
|
|
10
|
+
export function PreviewLinks({ experimentId, placements, arms, flagPoint }) {
|
|
11
11
|
const api = useApi();
|
|
12
12
|
const inFlight = useRef(false);
|
|
13
13
|
const [busy, setBusy] = useState(false);
|
|
@@ -40,7 +40,8 @@ export function PreviewLinks({ experimentId, placements, arms }) {
|
|
|
40
40
|
return /*#__PURE__*/ _jsxs("div", {
|
|
41
41
|
className: "xp-preview-links",
|
|
42
42
|
children: [
|
|
43
|
-
previewPages(placements).map((placement)=>/*#__PURE__*/ _jsxs("div", {
|
|
43
|
+
previewPages(placements, flagPoint).map((placement)=>/*#__PURE__*/ _jsxs("div", {
|
|
44
|
+
className: "xp-preview-row",
|
|
44
45
|
children: [
|
|
45
46
|
/*#__PURE__*/ _jsxs("p", {
|
|
46
47
|
children: [
|
|
@@ -48,19 +49,26 @@ export function PreviewLinks({ experimentId, placements, arms }) {
|
|
|
48
49
|
placement.slotCount > 1 ? ` ${C.placedTimes(placement.slotCount)}` : null
|
|
49
50
|
]
|
|
50
51
|
}),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
/*#__PURE__*/ _jsx("div", {
|
|
53
|
+
className: "xp-ui-button-group",
|
|
54
|
+
children: placement.url ? arms.filter((arm)=>arm.kind !== 'holdout' && arm.key !== 'holdout').map((arm)=>/*#__PURE__*/ _jsxs(Button, {
|
|
55
|
+
variant: "secondary",
|
|
56
|
+
size: "sm",
|
|
57
|
+
disabled: busy,
|
|
58
|
+
onClick: ()=>{
|
|
59
|
+
void preview(placement.url, arm.key);
|
|
60
|
+
},
|
|
61
|
+
children: [
|
|
62
|
+
C.preview,
|
|
63
|
+
" ",
|
|
64
|
+
arm.label?.trim() || armLabel(arm.key)
|
|
65
|
+
]
|
|
66
|
+
}, arm.key)) : /*#__PURE__*/ _jsx(Button, {
|
|
67
|
+
variant: "secondary",
|
|
68
|
+
size: "sm",
|
|
69
|
+
disabled: true,
|
|
70
|
+
children: C.noPreview
|
|
71
|
+
})
|
|
64
72
|
})
|
|
65
73
|
]
|
|
66
74
|
}, placement.key)),
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import '../theme.css';
|
|
2
1
|
import type { ReactNode } from 'react';
|
|
3
2
|
import type { DocId } from '../../types/contracts.js';
|
|
4
3
|
import type { ExperimentAction } from './actions.model.js';
|
|
5
|
-
export declare function DialogFrame({ title, onClose, children }: {
|
|
4
|
+
export declare function DialogFrame({ title, onClose, children, initialFocus, dismissible }: {
|
|
5
|
+
dismissible?: boolean;
|
|
6
|
+
initialFocus?: 'cancel';
|
|
6
7
|
title: string;
|
|
7
8
|
onClose: () => void;
|
|
8
9
|
children: ReactNode;
|
|
9
|
-
}): import("react").
|
|
10
|
-
export declare function ConfirmDialog({ id, action, winnerLabel, onClose }: {
|
|
10
|
+
}): import("react").JSX.Element;
|
|
11
|
+
export declare function ConfirmDialog({ id, action, winnerLabel, onClose, canManage, isDecided }: {
|
|
12
|
+
canManage?: boolean;
|
|
13
|
+
isDecided?: boolean;
|
|
11
14
|
id: DocId;
|
|
12
15
|
action: ExperimentAction;
|
|
13
16
|
winnerLabel: string;
|
|
@@ -1,99 +1,45 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import '
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { Button, TextInput } from '@payloadcms/ui';
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import { Button } from '../../ui/Button.js';
|
|
5
|
+
import { TextInput } from '../../ui/Inputs.js';
|
|
6
|
+
import { Dialog } from '../../ui/Dialog.js';
|
|
8
7
|
import { expectedConfirm, matchesConfirm } from '../../transitions/confirmPhrases.js';
|
|
9
8
|
import { useApi } from '../useApi.js';
|
|
10
9
|
import { reloadPage } from '../reload.js';
|
|
11
10
|
import { ACTIONS_COPY as C } from '../copy/actions.js';
|
|
12
11
|
import { API_MESSAGES } from '../copy/common.js';
|
|
13
12
|
import { MakeContentResult } from './MakeContentResult.js';
|
|
14
|
-
export function DialogFrame({ title, onClose, children }) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}, []);
|
|
24
|
-
return /*#__PURE__*/ createPortal(/*#__PURE__*/ _jsx("div", {
|
|
25
|
-
style: {
|
|
26
|
-
position: 'fixed',
|
|
27
|
-
inset: 0,
|
|
28
|
-
zIndex: 1000,
|
|
29
|
-
background: theme.color.overlay,
|
|
30
|
-
display: 'grid',
|
|
31
|
-
placeItems: 'center'
|
|
32
|
-
},
|
|
33
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
34
|
-
ref: ref,
|
|
35
|
-
role: "dialog",
|
|
36
|
-
"aria-modal": "true",
|
|
37
|
-
"aria-labelledby": heading,
|
|
38
|
-
tabIndex: -1,
|
|
39
|
-
style: {
|
|
40
|
-
background: theme.color.bg,
|
|
41
|
-
color: theme.color.text,
|
|
42
|
-
border: `1px solid ${theme.color.borderStrong}`,
|
|
43
|
-
padding: '2rem',
|
|
44
|
-
width: 'min(36rem, 90vw)',
|
|
45
|
-
maxHeight: '90vh',
|
|
46
|
-
overflow: 'auto',
|
|
47
|
-
borderRadius: theme.radius.small
|
|
48
|
-
},
|
|
49
|
-
onKeyDown: (event)=>{
|
|
50
|
-
if (event.defaultPrevented) return;
|
|
51
|
-
if (event.key === 'Escape') {
|
|
52
|
-
event.preventDefault();
|
|
53
|
-
event.stopPropagation();
|
|
54
|
-
onClose();
|
|
55
|
-
}
|
|
56
|
-
if (event.key === 'Tab') {
|
|
57
|
-
const items = Array.from(ref.current?.querySelectorAll('button:not(:disabled), input:not(:disabled), textarea:not(:disabled), select:not(:disabled), [tabindex="0"]') ?? []);
|
|
58
|
-
const first = items[0], last = items.at(-1);
|
|
59
|
-
if (!first) {
|
|
60
|
-
event.preventDefault();
|
|
61
|
-
ref.current?.focus();
|
|
62
|
-
} else if (event.shiftKey && (document.activeElement === first || document.activeElement === ref.current)) {
|
|
63
|
-
event.preventDefault();
|
|
64
|
-
last?.focus();
|
|
65
|
-
} else if (!event.shiftKey && (document.activeElement === last || document.activeElement === ref.current)) {
|
|
66
|
-
event.preventDefault();
|
|
67
|
-
first.focus();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
children: [
|
|
72
|
-
/*#__PURE__*/ _jsx("h2", {
|
|
73
|
-
id: heading,
|
|
74
|
-
children: title
|
|
75
|
-
}),
|
|
76
|
-
children
|
|
77
|
-
]
|
|
78
|
-
})
|
|
79
|
-
}), document.body);
|
|
13
|
+
export function DialogFrame({ title, onClose, children, initialFocus, dismissible = true }) {
|
|
14
|
+
return /*#__PURE__*/ _jsx(Dialog, {
|
|
15
|
+
open: true,
|
|
16
|
+
dismissible: dismissible,
|
|
17
|
+
initialFocus: initialFocus,
|
|
18
|
+
title: title,
|
|
19
|
+
onClose: onClose,
|
|
20
|
+
children: children
|
|
21
|
+
});
|
|
80
22
|
}
|
|
81
|
-
export function ConfirmDialog({ id, action, winnerLabel, onClose }) {
|
|
23
|
+
export function ConfirmDialog({ id, action, winnerLabel, onClose, canManage = false, isDecided = false }) {
|
|
82
24
|
const api = useApi();
|
|
83
25
|
const [typed, setTyped] = useState('');
|
|
84
26
|
const [busy, setBusy] = useState(false);
|
|
27
|
+
const [reportBusy, setReportBusy] = useState(false);
|
|
85
28
|
const inFlight = useRef(false);
|
|
86
29
|
const [error, setError] = useState(null);
|
|
87
30
|
const [content, setContent] = useState(null);
|
|
88
31
|
const phrase = action.confirm.phrase ? expectedConfirm(action.confirm.phrase.action, winnerLabel) : null;
|
|
89
32
|
const enabled = !busy && (!phrase || matchesConfirm(phrase, typed));
|
|
90
|
-
async function submit() {
|
|
33
|
+
async function submit(finish = false) {
|
|
91
34
|
if (inFlight.current || !enabled) return;
|
|
92
35
|
inFlight.current = true;
|
|
93
36
|
setBusy(true);
|
|
94
37
|
setError(null);
|
|
95
38
|
let result;
|
|
96
|
-
if (
|
|
39
|
+
if (finish) result = await api.archive(id, {
|
|
40
|
+
mode: 'after-content'
|
|
41
|
+
});
|
|
42
|
+
else if (action.archive) result = await api.archive(id, {
|
|
97
43
|
...action.archive,
|
|
98
44
|
...phrase ? {
|
|
99
45
|
confirmation: typed.trim()
|
|
@@ -112,7 +58,7 @@ export function ConfirmDialog({ id, action, winnerLabel, onClose }) {
|
|
|
112
58
|
}
|
|
113
59
|
result = response;
|
|
114
60
|
}
|
|
115
|
-
if (result.ok && action.id !== 'make-content') {
|
|
61
|
+
if (result.ok && (finish || action.id !== 'make-content')) {
|
|
116
62
|
reloadPage();
|
|
117
63
|
return;
|
|
118
64
|
}
|
|
@@ -126,18 +72,27 @@ export function ConfirmDialog({ id, action, winnerLabel, onClose }) {
|
|
|
126
72
|
});
|
|
127
73
|
}
|
|
128
74
|
function close() {
|
|
129
|
-
if (busy) return;
|
|
75
|
+
if (busy || reportBusy) return;
|
|
130
76
|
if (content) reloadPage();
|
|
131
77
|
onClose();
|
|
132
78
|
}
|
|
133
79
|
return /*#__PURE__*/ _jsxs(DialogFrame, {
|
|
80
|
+
dismissible: !busy && !reportBusy,
|
|
81
|
+
initialFocus: action.id === 'start' ? undefined : 'cancel',
|
|
134
82
|
title: action.confirm.title,
|
|
135
83
|
onClose: close,
|
|
136
84
|
children: [
|
|
137
85
|
content ? /*#__PURE__*/ _jsx(MakeContentResult, {
|
|
138
86
|
result: content,
|
|
139
|
-
|
|
140
|
-
|
|
87
|
+
id: id,
|
|
88
|
+
api: api,
|
|
89
|
+
canManage: canManage,
|
|
90
|
+
isDecided: content.doc?.status ? content.doc.status === 'decided' : isDecided,
|
|
91
|
+
onRetry: ()=>submit(),
|
|
92
|
+
onFinish: ()=>submit(true),
|
|
93
|
+
busy: busy,
|
|
94
|
+
onBusyChange: setReportBusy,
|
|
95
|
+
onActionStart: ()=>setError(null)
|
|
141
96
|
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
142
97
|
children: [
|
|
143
98
|
/*#__PURE__*/ _jsx("p", {
|
|
@@ -155,8 +110,7 @@ export function ConfirmDialog({ id, action, winnerLabel, onClose }) {
|
|
|
155
110
|
]
|
|
156
111
|
}),
|
|
157
112
|
/*#__PURE__*/ _jsx(TextInput, {
|
|
158
|
-
|
|
159
|
-
hasMany: false,
|
|
113
|
+
id: "xp-confirmation",
|
|
160
114
|
label: C.confirmation,
|
|
161
115
|
value: typed,
|
|
162
116
|
readOnly: busy,
|
|
@@ -177,7 +131,7 @@ export function ConfirmDialog({ id, action, winnerLabel, onClose }) {
|
|
|
177
131
|
},
|
|
178
132
|
children: action.label
|
|
179
133
|
}),
|
|
180
|
-
error?.retryable ? /*#__PURE__*/ _jsx(Button, {
|
|
134
|
+
error?.retryable && !content ? /*#__PURE__*/ _jsx(Button, {
|
|
181
135
|
disabled: !enabled,
|
|
182
136
|
onClick: ()=>{
|
|
183
137
|
void submit();
|
|
@@ -185,7 +139,8 @@ export function ConfirmDialog({ id, action, winnerLabel, onClose }) {
|
|
|
185
139
|
children: C.retry
|
|
186
140
|
}) : null,
|
|
187
141
|
/*#__PURE__*/ _jsx(Button, {
|
|
188
|
-
|
|
142
|
+
"data-dialog-cancel": true,
|
|
143
|
+
disabled: busy || reportBusy,
|
|
189
144
|
onClick: close,
|
|
190
145
|
children: content ? C.done : C.cancel
|
|
191
146
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { DocId } from '../../types/contracts.js';
|
|
2
|
+
import type { PreflightResult } from '../../types/endpoints.js';
|
|
2
3
|
import type { ActionGroup } from './actions.model.js';
|
|
3
|
-
export declare function DecideDialog({ id, arms, onClose }: {
|
|
4
|
+
export declare function DecideDialog({ id, arms, checks, onClose }: {
|
|
4
5
|
id: DocId;
|
|
5
6
|
arms: readonly ActionGroup[];
|
|
7
|
+
checks?: PreflightResult['checks'];
|
|
6
8
|
onClose: () => void;
|
|
7
9
|
}): import("react").JSX.Element;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { Button
|
|
4
|
+
import { Button } from '../../ui/Button.js';
|
|
5
|
+
import { Select, Textarea, TextInput } from '../../ui/Inputs.js';
|
|
6
|
+
import { unsupportedContentCopy } from '../copy/readiness.js';
|
|
5
7
|
import { useApi } from '../useApi.js';
|
|
6
8
|
import { reloadPage } from '../reload.js';
|
|
9
|
+
import { RESULTS_COPY } from '../copy/results.js';
|
|
7
10
|
import { ACTIONS_COPY as C, decideCopy } from '../copy/actions.js';
|
|
8
11
|
import { decideState } from './decide.model.js';
|
|
9
12
|
import { DialogFrame } from './ConfirmDialog.js';
|
|
10
|
-
export function DecideDialog({ id, arms, onClose }) {
|
|
13
|
+
export function DecideDialog({ id, arms, checks = [], onClose }) {
|
|
11
14
|
const api = useApi();
|
|
12
15
|
const [snapshot, setSnapshot] = useState(null);
|
|
13
16
|
const [chosenArm, setChosenArm] = useState(null);
|
|
@@ -85,6 +88,8 @@ export function DecideDialog({ id, arms, onClose }) {
|
|
|
85
88
|
const enabled = !busy && !loading && !loadFailed && state.canSubmit;
|
|
86
89
|
const label = state.options.find((option)=>option.value === chosenArm)?.label;
|
|
87
90
|
return /*#__PURE__*/ _jsxs(DialogFrame, {
|
|
91
|
+
dismissible: !busy,
|
|
92
|
+
initialFocus: "cancel",
|
|
88
93
|
title: C.decide,
|
|
89
94
|
onClose: ()=>{
|
|
90
95
|
if (!busy) onClose();
|
|
@@ -94,27 +99,39 @@ export function DecideDialog({ id, arms, onClose }) {
|
|
|
94
99
|
role: "status",
|
|
95
100
|
children: C.loading
|
|
96
101
|
}) : null,
|
|
97
|
-
/*#__PURE__*/
|
|
98
|
-
|
|
102
|
+
/*#__PURE__*/ _jsxs(Select, {
|
|
103
|
+
id: "xp-winner",
|
|
99
104
|
name: "xp-winner",
|
|
100
105
|
label: C.group,
|
|
101
|
-
options: state.options,
|
|
102
106
|
value: chosenArm ?? '',
|
|
103
|
-
|
|
107
|
+
disabled: busy || loading,
|
|
104
108
|
required: true,
|
|
105
|
-
onChange: (
|
|
106
|
-
setChosenArm(
|
|
109
|
+
onChange: (event)=>{
|
|
110
|
+
setChosenArm(event.target.value || null);
|
|
107
111
|
setTyped('');
|
|
108
|
-
}
|
|
112
|
+
},
|
|
113
|
+
children: [
|
|
114
|
+
/*#__PURE__*/ _jsx("option", {
|
|
115
|
+
value: "",
|
|
116
|
+
children: RESULTS_COPY.chooseGroup
|
|
117
|
+
}),
|
|
118
|
+
state.options.map((option)=>/*#__PURE__*/ _jsx("option", {
|
|
119
|
+
value: option.value,
|
|
120
|
+
children: option.label
|
|
121
|
+
}, option.value))
|
|
122
|
+
]
|
|
109
123
|
}),
|
|
110
124
|
label ? /*#__PURE__*/ _jsx("p", {
|
|
111
125
|
children: decideCopy(label, arms.some((group)=>group.key === 'holdout'), chosenArm === 'control')
|
|
112
126
|
}) : null,
|
|
127
|
+
checks.filter((check)=>check.code === 'make-content-unsupported' && check.warning).map((check)=>/*#__PURE__*/ _jsx("p", {
|
|
128
|
+
children: unsupportedContentCopy(typeof check.detail === 'string' ? check.detail : 'This page')
|
|
129
|
+
}, check.code)),
|
|
113
130
|
state.reason ? /*#__PURE__*/ _jsx("p", {
|
|
114
131
|
children: state.reason
|
|
115
132
|
}) : null,
|
|
116
|
-
/*#__PURE__*/ _jsx(
|
|
117
|
-
|
|
133
|
+
/*#__PURE__*/ _jsx(Textarea, {
|
|
134
|
+
id: "xp-note",
|
|
118
135
|
label: C.note,
|
|
119
136
|
value: note,
|
|
120
137
|
required: state.guarded,
|
|
@@ -133,8 +150,7 @@ export function DecideDialog({ id, arms, onClose }) {
|
|
|
133
150
|
]
|
|
134
151
|
}),
|
|
135
152
|
/*#__PURE__*/ _jsx(TextInput, {
|
|
136
|
-
|
|
137
|
-
hasMany: false,
|
|
153
|
+
id: "xp-decide-confirmation",
|
|
138
154
|
label: C.confirmation,
|
|
139
155
|
value: typed,
|
|
140
156
|
readOnly: busy,
|
|
@@ -164,6 +180,7 @@ export function DecideDialog({ id, arms, onClose }) {
|
|
|
164
180
|
children: C.retry
|
|
165
181
|
}) : null,
|
|
166
182
|
/*#__PURE__*/ _jsx(Button, {
|
|
183
|
+
"data-dialog-cancel": true,
|
|
167
184
|
disabled: busy,
|
|
168
185
|
onClick: onClose,
|
|
169
186
|
children: C.cancel
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
import type { DocId } from '../../types/contracts.js';
|
|
1
2
|
import type { MakeContentResult as Result } from '../../types/endpoints.js';
|
|
2
|
-
|
|
3
|
+
import type { createApi } from '../api.js';
|
|
4
|
+
type Props = {
|
|
3
5
|
result: Result;
|
|
4
6
|
onRetry: () => void | Promise<void>;
|
|
5
7
|
busy?: boolean;
|
|
6
|
-
|
|
8
|
+
id?: DocId;
|
|
9
|
+
api?: ReturnType<typeof createApi>;
|
|
10
|
+
canManage?: boolean;
|
|
11
|
+
isDecided?: boolean;
|
|
12
|
+
onFinish?: () => void | Promise<void>;
|
|
13
|
+
onBusyChange?: (busy: boolean) => void;
|
|
14
|
+
onActionStart?: () => void;
|
|
15
|
+
};
|
|
16
|
+
export declare function MakeContentResult({ result, onRetry, busy, id, api, canManage, isDecided, onFinish, onBusyChange, onActionStart }: Props): import("react").JSX.Element;
|
|
17
|
+
export {};
|