@aglyn/aglyn 1.0.0-beta.221 → 1.0.0-beta.222
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/package.json +15 -11
- package/src/lib/app-utils/agent-openapi.js +1 -1
- package/src/lib/app-utils/agent-openapi.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +3 -2
- package/src/lib/app-utils/analytics-events.js +3 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/api-plugins.d.ts +30 -0
- package/src/lib/app-utils/api-plugins.js +24 -1
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/author-html.d.ts +1 -1
- package/src/lib/app-utils/author-html.js.map +1 -1
- package/src/lib/app-utils/bandwidth-cap.d.ts +12 -1
- package/src/lib/app-utils/bandwidth-cap.js +17 -1
- package/src/lib/app-utils/bandwidth-cap.js.map +1 -1
- package/src/lib/app-utils/compose-reusable-components.d.ts +19 -0
- package/src/lib/app-utils/compose-reusable-components.js +116 -26
- package/src/lib/app-utils/compose-reusable-components.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +4 -0
- package/src/lib/app-utils/console-routes.js +3 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +71 -0
- package/src/lib/app-utils/contacts.js +55 -0
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/contexts.d.ts +1 -0
- package/src/lib/app-utils/contexts.js +1 -0
- package/src/lib/app-utils/contexts.js.map +1 -1
- package/src/lib/app-utils/crm-email-templates.js +21 -4
- package/src/lib/app-utils/crm-email-templates.js.map +1 -1
- package/src/lib/app-utils/crm-kinds.d.ts +2 -0
- package/src/lib/app-utils/crm-kinds.js +1 -0
- package/src/lib/app-utils/crm-kinds.js.map +1 -1
- package/src/lib/app-utils/crm.d.ts +1199 -67
- package/src/lib/app-utils/crm.js +1951 -310
- package/src/lib/app-utils/crm.js.map +1 -1
- package/src/lib/app-utils/csv-import.d.ts +10 -38
- package/src/lib/app-utils/csv-import.js +17 -42
- package/src/lib/app-utils/csv-import.js.map +1 -1
- package/src/lib/app-utils/csv.d.ts +32 -11
- package/src/lib/app-utils/csv.js +43 -5
- package/src/lib/app-utils/csv.js.map +1 -1
- package/src/lib/app-utils/default-site.d.ts +116 -0
- package/src/lib/app-utils/default-site.js +1024 -0
- package/src/lib/app-utils/default-site.js.map +1 -0
- package/src/lib/app-utils/docs-help.generated.d.ts +22 -22
- package/src/lib/app-utils/docs-help.generated.js +44 -10
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +738 -150
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/draft-placeholders.d.ts +76 -0
- package/src/lib/app-utils/draft-placeholders.js +115 -0
- package/src/lib/app-utils/draft-placeholders.js.map +1 -0
- package/src/lib/app-utils/entity-picker-context.d.ts +19 -15
- package/src/lib/app-utils/entity-picker-context.js +4 -4
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +66 -10
- package/src/lib/app-utils/expand-repeatables.js +88 -14
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/forms.d.ts +33 -22
- package/src/lib/app-utils/forms.js +47 -28
- package/src/lib/app-utils/forms.js.map +1 -1
- package/src/lib/app-utils/full-use-cost.d.ts +277 -0
- package/src/lib/app-utils/full-use-cost.js +421 -0
- package/src/lib/app-utils/full-use-cost.js.map +1 -0
- package/src/lib/app-utils/health-report.js +11 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/llms-txt.d.ts +20 -0
- package/src/lib/app-utils/llms-txt.js +7 -1
- package/src/lib/app-utils/llms-txt.js.map +1 -1
- package/src/lib/app-utils/media-asset-facts.d.ts +16 -1
- package/src/lib/app-utils/media-asset-facts.js +40 -13
- package/src/lib/app-utils/media-asset-facts.js.map +1 -1
- package/src/lib/app-utils/media-bandwidth.d.ts +52 -0
- package/src/lib/app-utils/media-bandwidth.js +94 -0
- package/src/lib/app-utils/media-bandwidth.js.map +1 -0
- package/src/lib/app-utils/media-ref.d.ts +241 -33
- package/src/lib/app-utils/media-ref.js +324 -43
- package/src/lib/app-utils/media-ref.js.map +1 -1
- package/src/lib/app-utils/media-src.d.ts +8 -8
- package/src/lib/app-utils/media-src.js +27 -11
- package/src/lib/app-utils/media-src.js.map +1 -1
- package/src/lib/app-utils/media-takedown-reach.d.ts +4 -1
- package/src/lib/app-utils/media-takedown-reach.js +6 -3
- package/src/lib/app-utils/media-takedown-reach.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +1 -1
- package/src/lib/app-utils/notifications.js +2 -0
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/organizations.d.ts +51 -2
- package/src/lib/app-utils/organizations.js +56 -3
- package/src/lib/app-utils/organizations.js.map +1 -1
- package/src/lib/app-utils/page-record-sources.d.ts +87 -0
- package/src/lib/app-utils/page-record-sources.js +53 -0
- package/src/lib/app-utils/page-record-sources.js.map +1 -0
- package/src/lib/app-utils/paid-media-source.d.ts +3 -7
- package/src/lib/app-utils/paid-media-source.js +5 -19
- package/src/lib/app-utils/paid-media-source.js.map +1 -1
- package/src/lib/app-utils/person-erasure.d.ts +5 -2
- package/src/lib/app-utils/person-erasure.js.map +1 -1
- package/src/lib/app-utils/picklists.d.ts +187 -0
- package/src/lib/app-utils/picklists.js +320 -0
- package/src/lib/app-utils/picklists.js.map +1 -0
- package/src/lib/app-utils/plan-entitlements.d.ts +220 -50
- package/src/lib/app-utils/plan-entitlements.js +309 -115
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +1 -1
- package/src/lib/app-utils/release-flags.js +11 -0
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/rendered-widths.d.ts +121 -0
- package/src/lib/app-utils/rendered-widths.js +211 -0
- package/src/lib/app-utils/rendered-widths.js.map +1 -0
- package/src/lib/app-utils/repeat-sources.d.ts +2 -1
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +154 -36
- package/src/lib/app-utils/screen-route.js +185 -40
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +11 -1
- package/src/lib/app-utils/seo-audit.js +33 -13
- package/src/lib/app-utils/seo-audit.js.map +1 -1
- package/src/lib/app-utils/seo-keywords.d.ts +11 -0
- package/src/lib/app-utils/seo-keywords.js +19 -5
- package/src/lib/app-utils/seo-keywords.js.map +1 -1
- package/src/lib/app-utils/seo-page-facts.d.ts +47 -4
- package/src/lib/app-utils/seo-page-facts.js +136 -31
- package/src/lib/app-utils/seo-page-facts.js.map +1 -1
- package/src/lib/app-utils/seo-site-scan.d.ts +42 -4
- package/src/lib/app-utils/seo-site-scan.js +113 -12
- package/src/lib/app-utils/seo-site-scan.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +5 -4
- package/src/lib/app-utils/server.js +14 -5
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-icon-set.d.ts +195 -0
- package/src/lib/app-utils/site-icon-set.js +351 -0
- package/src/lib/app-utils/site-icon-set.js.map +1 -0
- package/src/lib/app-utils/site-interactions.d.ts +101 -4
- package/src/lib/app-utils/site-interactions.js +120 -4
- package/src/lib/app-utils/site-interactions.js.map +1 -1
- package/src/lib/app-utils/starter-template-nodes.d.ts +7 -0
- package/src/lib/app-utils/starter-template-nodes.js +3 -0
- package/src/lib/app-utils/starter-template-nodes.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +5 -19
- package/src/lib/app-utils/starter-templates.js +5 -49
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/transfer-launcher-context.d.ts +136 -0
- package/src/lib/app-utils/transfer-launcher-context.js +23 -0
- package/src/lib/app-utils/transfer-launcher-context.js.map +1 -0
- package/src/lib/app-utils/transfer-resume-import.d.ts +39 -0
- package/src/lib/app-utils/transfer-resume-import.js +128 -0
- package/src/lib/app-utils/transfer-resume-import.js.map +1 -0
- package/src/lib/app-utils/upgrade-proposal.d.ts +66 -0
- package/src/lib/app-utils/upgrade-proposal.js +95 -0
- package/src/lib/app-utils/upgrade-proposal.js.map +1 -0
- package/src/lib/app-utils/utc-month.d.ts +33 -0
- package/src/lib/app-utils/utc-month.js +35 -0
- package/src/lib/app-utils/utc-month.js.map +1 -0
- package/src/lib/app-utils/visitor-record-ceiling.d.ts +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js +4 -4
- package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
- package/src/lib/app-utils/wire-nodes.d.ts +39 -19
- package/src/lib/app-utils/wire-nodes.js +120 -34
- package/src/lib/app-utils/wire-nodes.js.map +1 -1
- package/src/lib/data-transfer/access.d.ts +74 -0
- package/src/lib/data-transfer/access.js +93 -0
- package/src/lib/data-transfer/access.js.map +1 -0
- package/src/lib/data-transfer/derive.d.ts +246 -0
- package/src/lib/data-transfer/derive.js +1007 -0
- package/src/lib/data-transfer/derive.js.map +1 -0
- package/src/lib/data-transfer/export-file.d.ts +46 -0
- package/src/lib/data-transfer/export-file.js +146 -0
- package/src/lib/data-transfer/export-file.js.map +1 -0
- package/src/lib/data-transfer/field-catalog.d.ts +134 -0
- package/src/lib/data-transfer/field-catalog.js +278 -0
- package/src/lib/data-transfer/field-catalog.js.map +1 -0
- package/src/lib/data-transfer/header-match.d.ts +129 -0
- package/src/lib/data-transfer/header-match.js +367 -0
- package/src/lib/data-transfer/header-match.js.map +1 -0
- package/src/lib/data-transfer/index.d.ts +34 -0
- package/src/lib/data-transfer/index.js +40 -0
- package/src/lib/data-transfer/index.js.map +1 -0
- package/src/lib/data-transfer/job.d.ts +190 -0
- package/src/lib/data-transfer/job.js +229 -0
- package/src/lib/data-transfer/job.js.map +1 -0
- package/src/lib/data-transfer/lookup.d.ts +45 -0
- package/src/lib/data-transfer/lookup.js +71 -0
- package/src/lib/data-transfer/lookup.js.map +1 -0
- package/src/lib/data-transfer/match.d.ts +99 -0
- package/src/lib/data-transfer/match.js +244 -0
- package/src/lib/data-transfer/match.js.map +1 -0
- package/src/lib/data-transfer/package-plan.d.ts +205 -0
- package/src/lib/data-transfer/package-plan.js +475 -0
- package/src/lib/data-transfer/package-plan.js.map +1 -0
- package/src/lib/data-transfer/package.d.ts +181 -0
- package/src/lib/data-transfer/package.js +426 -0
- package/src/lib/data-transfer/package.js.map +1 -0
- package/src/lib/data-transfer/picklist-map.d.ts +139 -0
- package/src/lib/data-transfer/picklist-map.js +271 -0
- package/src/lib/data-transfer/picklist-map.js.map +1 -0
- package/src/lib/data-transfer/plan.d.ts +176 -0
- package/src/lib/data-transfer/plan.js +451 -0
- package/src/lib/data-transfer/plan.js.map +1 -0
- package/src/lib/data-transfer/policy.d.ts +170 -0
- package/src/lib/data-transfer/policy.js +378 -0
- package/src/lib/data-transfer/policy.js.map +1 -0
- package/src/lib/data-transfer/resource.d.ts +211 -0
- package/src/lib/data-transfer/resource.js +241 -0
- package/src/lib/data-transfer/resource.js.map +1 -0
- package/src/lib/data-transfer/review.d.ts +70 -0
- package/src/lib/data-transfer/review.js +233 -0
- package/src/lib/data-transfer/review.js.map +1 -0
- package/src/lib/data-transfer/similarity.d.ts +40 -0
- package/src/lib/data-transfer/similarity.js +106 -0
- package/src/lib/data-transfer/similarity.js.map +1 -0
- package/src/lib/data-transfer/site-package.d.ts +365 -0
- package/src/lib/data-transfer/site-package.js +1077 -0
- package/src/lib/data-transfer/site-package.js.map +1 -0
- package/src/lib/data-transfer/source.d.ts +74 -0
- package/src/lib/data-transfer/source.js +293 -0
- package/src/lib/data-transfer/source.js.map +1 -0
- package/src/lib/data-transfer/transfer-api.d.ts +898 -0
- package/src/lib/data-transfer/transfer-api.js +107 -0
- package/src/lib/data-transfer/transfer-api.js.map +1 -0
- package/src/lib/foundation/definitions/org-billing.types.d.ts +31 -0
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +42 -1
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +6 -4
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +91 -2
- package/src/lib/plugin-manager/feature-plugins.js +48 -3
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +50 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +1129 -28
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/index.d.ts +0 -1
- package/src/lib/plugin-manager/index.js +0 -1
- package/src/lib/plugin-manager/index.js.map +1 -1
- package/src/lib/plugin-manager/interaction-recipes.d.ts +142 -0
- package/src/lib/plugin-manager/interaction-recipes.js +104 -0
- package/src/lib/plugin-manager/interaction-recipes.js.map +1 -0
- package/src/lib/plugin-manager/interaction-step-checks.d.ts +66 -0
- package/src/lib/plugin-manager/interaction-step-checks.js +86 -0
- package/src/lib/plugin-manager/interaction-step-checks.js.map +1 -0
- package/src/lib/plugin-manager/plugin-artifact-types.d.ts +235 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js +99 -0
- package/src/lib/plugin-manager/plugin-artifact-types.js.map +1 -0
- package/src/lib/plugin-manager/plugin-contact-capture.d.ts +8 -0
- package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
- package/src/lib/plugin-manager/plugin-conversion-credit.d.ts +216 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js +91 -0
- package/src/lib/plugin-manager/plugin-conversion-credit.js.map +1 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.d.ts +83 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js +87 -0
- package/src/lib/plugin-manager/plugin-entity-pickers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +6 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +6 -1
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -1
- package/src/lib/plugin-manager/plugin-intake-gates.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js +49 -0
- package/src/lib/plugin-manager/plugin-intake-gates.js.map +1 -0
- package/src/lib/plugin-manager/plugin-jobs.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-jobs.js +7 -7
- package/src/lib/plugin-manager/plugin-jobs.js.map +1 -1
- package/src/lib/plugin-manager/plugin-live-paths.d.ts +70 -0
- package/src/lib/plugin-manager/plugin-live-paths.js +66 -0
- package/src/lib/plugin-manager/plugin-live-paths.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +3 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +30 -5
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-erasure.d.ts +68 -9
- package/src/lib/plugin-manager/plugin-person-erasure.js +196 -51
- package/src/lib/plugin-manager/plugin-person-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-person-records.d.ts +275 -0
- package/src/lib/plugin-manager/plugin-person-records.js +107 -0
- package/src/lib/plugin-manager/plugin-person-records.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-counts.d.ts +76 -0
- package/src/lib/plugin-manager/plugin-record-counts.js +49 -0
- package/src/lib/plugin-manager/plugin-record-counts.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +26 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js +16 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-index.d.ts +10 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-lists.d.ts +86 -6
- package/src/lib/plugin-manager/plugin-record-lists.js +42 -5
- package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-origin.d.ts +79 -0
- package/src/lib/plugin-manager/plugin-record-origin.js +46 -0
- package/src/lib/plugin-manager/plugin-record-origin.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-pages.d.ts +37 -0
- package/src/lib/plugin-manager/plugin-record-pages.js +52 -0
- package/src/lib/plugin-manager/plugin-record-pages.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-timeline.d.ts +60 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js +16 -0
- package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
- package/src/lib/plugin-manager/plugin-resource-drafts.d.ts +5 -5
- package/src/lib/plugin-manager/plugin-resource-drafts.js.map +1 -1
- package/src/lib/plugin-manager/plugin-revocations.d.ts +64 -0
- package/src/lib/plugin-manager/plugin-revocations.js +47 -0
- package/src/lib/plugin-manager/plugin-revocations.js.map +1 -0
- package/src/lib/plugin-manager/plugin-send-tallies.d.ts +69 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js +45 -0
- package/src/lib/plugin-manager/plugin-send-tallies.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +40 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +4 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +57 -1
- package/src/lib/plugin-manager/plugin-site-export.js +23 -10
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -1
- package/src/lib/plugin-manager/plugin-sitemap-readers.d.ts +146 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js +110 -0
- package/src/lib/plugin-manager/plugin-sitemap-readers.js.map +1 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.d.ts +714 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js +443 -0
- package/src/lib/plugin-manager/plugin-transfer-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +54 -5
- package/src/lib/plugin-manager/plugin-usage-axes.js +11 -3
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js +9 -1
- package/src/lib/plugin-manager/plugin-user-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-visitor-doors.d.ts +104 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js +108 -0
- package/src/lib/plugin-manager/plugin-visitor-doors.js.map +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +5 -6
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -1
- package/src/lib/app-utils/actions.d.ts +0 -310
- package/src/lib/app-utils/actions.js +0 -506
- package/src/lib/app-utils/actions.js.map +0 -1
- package/src/lib/app-utils/automation-placeholders.d.ts +0 -72
- package/src/lib/app-utils/automation-placeholders.js +0 -207
- package/src/lib/app-utils/automation-placeholders.js.map +0 -1
- package/src/lib/app-utils/dataset-models.d.ts +0 -339
- package/src/lib/app-utils/dataset-models.js +0 -722
- package/src/lib/app-utils/dataset-models.js.map +0 -1
- package/src/lib/app-utils/datasets.d.ts +0 -171
- package/src/lib/app-utils/datasets.js +0 -227
- package/src/lib/app-utils/datasets.js.map +0 -1
- package/src/lib/app-utils/form-abuse-ceiling.d.ts +0 -146
- package/src/lib/app-utils/form-abuse-ceiling.js +0 -158
- package/src/lib/app-utils/form-abuse-ceiling.js.map +0 -1
- package/src/lib/plugin-manager/order-fulfilment.d.ts +0 -142
- package/src/lib/plugin-manager/order-fulfilment.js +0 -104
- package/src/lib/plugin-manager/order-fulfilment.js.map +0 -1
|
@@ -292,10 +292,24 @@ export interface SiteInteraction<Step extends InteractionStepBase = ClientIntera
|
|
|
292
292
|
steps: Step[];
|
|
293
293
|
/** A disabled interaction never runs; a new one defaults enabled. */
|
|
294
294
|
enabled?: boolean;
|
|
295
|
+
/**
|
|
296
|
+
* The recipe this interaction was installed from or begun as (AGL-2639) —
|
|
297
|
+
* one a plugin offers through `plugin-manager/interaction-recipes` — or
|
|
298
|
+
* `null` for one begun blank. Absent on a document from before the stamp
|
|
299
|
+
* existed: read it through `interactionRecipeStamp`, which keeps the three
|
|
300
|
+
* cases apart.
|
|
301
|
+
*/
|
|
302
|
+
recipe?: string | null;
|
|
295
303
|
}
|
|
296
304
|
/** Steps the tenant page runtime executes client-side (AGL-257). */
|
|
297
305
|
export declare const CLIENT_ACTION_STEP_TYPES: ReadonlySet<string>;
|
|
298
306
|
export declare function isClientActionStep(step: InteractionStepBase): step is ClientInteractionStep;
|
|
307
|
+
/**
|
|
308
|
+
* How an editor, a run history and a drafter name each of the platform's
|
|
309
|
+
* client steps. Every other step is named by the plugin that declares it
|
|
310
|
+
* ({@link interactionStepLabel}).
|
|
311
|
+
*/
|
|
312
|
+
export declare const CLIENT_INTERACTION_STEP_LABELS: Readonly<Record<ClientInteractionStepType, string>>;
|
|
299
313
|
/**
|
|
300
314
|
* Basic presentational interactions (AGL-577): the subset of client steps
|
|
301
315
|
* that are pure DOM choreography — menu/drawer open-close, element
|
|
@@ -431,11 +445,64 @@ export interface InteractionStepDeclaration {
|
|
|
431
445
|
/** What a step with no pick is told, after its `Step N:` prefix. */
|
|
432
446
|
missing: string;
|
|
433
447
|
};
|
|
448
|
+
/**
|
|
449
|
+
* `false` for a step only an automation holds — one that acts on the server
|
|
450
|
+
* after the page that started it has gone, such as an email or a wait — so
|
|
451
|
+
* the besigner's interaction builder does not offer it. Absent means offered.
|
|
452
|
+
*/
|
|
453
|
+
offered?: false;
|
|
454
|
+
/** For a step that SUSPENDS the run that reaches it: see {@link InteractionStepHolds}. */
|
|
455
|
+
holds?: InteractionStepHolds;
|
|
456
|
+
/** The step's fields a person types words into, in the order a sentence lists them. */
|
|
457
|
+
typedFields?: readonly InteractionStepTypedField[];
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* A step that suspends the run reaching it, to be continued later from a
|
|
461
|
+
* beat: the whole minutes it may hold, and — where a hold can end on the
|
|
462
|
+
* clock rather than on what it waits for — the scope field the continued run
|
|
463
|
+
* carries to say it ran out of time. A visitor's page never runs a step past
|
|
464
|
+
* one ({@link interactionStepsForClient}): everything after it belongs to a
|
|
465
|
+
* run that has not happened yet.
|
|
466
|
+
*/
|
|
467
|
+
export interface InteractionStepHolds {
|
|
468
|
+
minMinutes: number;
|
|
469
|
+
maxMinutes: number;
|
|
470
|
+
timeoutField?: string;
|
|
471
|
+
}
|
|
472
|
+
/** One field of a step a person types words into, and what a sentence calls it: "the list". */
|
|
473
|
+
export interface InteractionStepTypedField {
|
|
474
|
+
key: string;
|
|
475
|
+
names: string;
|
|
434
476
|
}
|
|
435
477
|
/** Every step the first-party plugins declare, in `plugins.config.json` order. */
|
|
436
478
|
export declare function declaredInteractionSteps(): readonly InteractionStepDeclaration[];
|
|
437
479
|
/** The declared step of this type, or `null` for one no plugin declares. */
|
|
438
480
|
export declare function declaredInteractionStep(type: string, declarations?: readonly InteractionStepDeclaration[]): InteractionStepDeclaration | null;
|
|
481
|
+
/**
|
|
482
|
+
* How a step type reads in an editor, a run history and a drafter's words:
|
|
483
|
+
* the platform's own label for a client step, else the label the declaring
|
|
484
|
+
* plugin gave it; `null` for a type nobody declares.
|
|
485
|
+
*/
|
|
486
|
+
export declare function interactionStepLabel(type: string, declarations?: readonly InteractionStepDeclaration[]): string | null;
|
|
487
|
+
/** The hold a step type declares, or `null` for one that never suspends a run. */
|
|
488
|
+
export declare function interactionStepHolds(type: string, declarations?: readonly InteractionStepDeclaration[]): InteractionStepHolds | null;
|
|
489
|
+
/** The fields of a step type a person types words into: the platform's, else the declaring plugin's. */
|
|
490
|
+
export declare function interactionStepTypedFields(type: string, declarations?: readonly InteractionStepDeclaration[]): readonly InteractionStepTypedField[];
|
|
491
|
+
/**
|
|
492
|
+
* The step list as the visitor's browser may see it: cut at the first step
|
|
493
|
+
* that suspends the run.
|
|
494
|
+
*
|
|
495
|
+
* A client step AFTER a hold must never reach the page. The page runs its
|
|
496
|
+
* slice of the list the moment the trigger fires, so shipping the whole list
|
|
497
|
+
* would make "wait three days, then show the popup" show the popup at once —
|
|
498
|
+
* the delay would appear to work on the server, be ignored in the browser,
|
|
499
|
+
* and the two halves of one authored flow would disagree about when it
|
|
500
|
+
* happened.
|
|
501
|
+
*
|
|
502
|
+
* Truncating rather than filtering: everything past the first hold belongs to
|
|
503
|
+
* a run that has not happened yet, whichever side would have executed it.
|
|
504
|
+
*/
|
|
505
|
+
export declare function interactionStepsForClient<Step extends InteractionStepBase>(steps: readonly Step[] | undefined | null, declarations?: readonly InteractionStepDeclaration[]): Step[];
|
|
439
506
|
export interface ValidateInteractionOptions {
|
|
440
507
|
/**
|
|
441
508
|
* One step's checks that belong to its owner — a plugin's own step types.
|
|
@@ -451,12 +518,42 @@ export interface ValidateInteractionOptions {
|
|
|
451
518
|
* Every surface that writes one shares this, so a bad step never persists or
|
|
452
519
|
* runs.
|
|
453
520
|
*
|
|
454
|
-
* The platform checks what is its own: the name, the
|
|
455
|
-
* conditions, the step count, each step's guard, its client
|
|
456
|
-
* pick of every declared step that picks a record. A step it
|
|
457
|
-
* left to its owner, through `validateStep`.
|
|
521
|
+
* The platform checks what is its own: the name, the recipe stamp, the
|
|
522
|
+
* trigger and its conditions, the step count, each step's guard, its client
|
|
523
|
+
* steps, and the pick of every declared step that picks a record. A step it
|
|
524
|
+
* does not know is left to its owner, through `validateStep`.
|
|
458
525
|
*/
|
|
459
526
|
export declare function validateInteraction(interaction: SiteInteraction<InteractionStepBase>, options?: ValidateInteractionOptions): string | null;
|
|
527
|
+
/**
|
|
528
|
+
* An interaction as the document at `hosts/{hostId}/actions/{id}` holds it.
|
|
529
|
+
*
|
|
530
|
+
* Every optional trigger key is written OUT — a boolean cap as `false`, a
|
|
531
|
+
* cleared list as `null` — because the editors save with a merge-set, and a
|
|
532
|
+
* merge keeps whatever key the payload omits: a frequency cap switched off,
|
|
533
|
+
* left out of the payload, would stay on. The legacy single `condition` is
|
|
534
|
+
* always nulled; `conditions` has been canonical since the list shape arrived
|
|
535
|
+
* and a document that carried both would have the reader pick. Everything
|
|
536
|
+
* else is the interaction, unchanged.
|
|
537
|
+
*
|
|
538
|
+
* `recipe` rides along only when the interaction SAYS something about it: an
|
|
539
|
+
* id or `null`. One that carries no stamp (an older document, edited and
|
|
540
|
+
* saved again) keeps carrying none — see `interactionRecipeStamp` for why an
|
|
541
|
+
* absent stamp must not become a `null` one.
|
|
542
|
+
*/
|
|
543
|
+
export type SiteInteractionDocument<Step extends InteractionStepBase = InteractionStepBase> = SiteInteraction<Step> & {
|
|
544
|
+
trigger: InteractionTrigger & {
|
|
545
|
+
oncePerVisitor: boolean;
|
|
546
|
+
oncePerSession: boolean;
|
|
547
|
+
cooldownMinutes: number | null;
|
|
548
|
+
everyTime: boolean;
|
|
549
|
+
condition: null;
|
|
550
|
+
conditions: InteractionCondition[] | null;
|
|
551
|
+
combinator: TriggerCombinator | null;
|
|
552
|
+
};
|
|
553
|
+
enabled: boolean;
|
|
554
|
+
};
|
|
555
|
+
/** The stored shape of an interaction: what every writer of `hosts/{hostId}/actions` saves. */
|
|
556
|
+
export declare function siteInteractionDocument<Step extends InteractionStepBase>(interaction: SiteInteraction<Step>): SiteInteractionDocument<Step>;
|
|
460
557
|
/** Alert produced by a `siteAlert` step, surfaced to the emitting client. */
|
|
461
558
|
export interface SiteAlert {
|
|
462
559
|
message: string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
1
3
|
/**
|
|
2
4
|
* @license
|
|
3
5
|
* Copyright 2026 Aglyn LLC
|
|
@@ -16,6 +18,7 @@
|
|
|
16
18
|
*/ import { resolveAuthoredEventName, sanitizeEventParams } from "./analytics-events.js";
|
|
17
19
|
import { sanitizeAuthorHtml } from "./author-html.js";
|
|
18
20
|
import { PLUGIN_INTERACTION_STEPS_DECLARED } from "../plugin-manager/first-party-plugins.generated.js";
|
|
21
|
+
import { isKnownInteractionRecipe } from "../plugin-manager/interaction-recipes.js";
|
|
19
22
|
/**
|
|
20
23
|
* SITE INTERACTIONS: what a published page does when a visitor does something.
|
|
21
24
|
*
|
|
@@ -160,6 +163,54 @@ export function isSiteEventType(event) {
|
|
|
160
163
|
export function isClientActionStep(step) {
|
|
161
164
|
return CLIENT_ACTION_STEP_TYPES.has(step.type);
|
|
162
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* How an editor, a run history and a drafter name each of the platform's
|
|
168
|
+
* client steps. Every other step is named by the plugin that declares it
|
|
169
|
+
* ({@link interactionStepLabel}).
|
|
170
|
+
*/ export const CLIENT_INTERACTION_STEP_LABELS = {
|
|
171
|
+
siteAlert: 'Show a site alert',
|
|
172
|
+
showOverlay: 'Show a popup or bar',
|
|
173
|
+
stickyNav: 'Make navigation sticky',
|
|
174
|
+
addClass: 'Add a CSS class',
|
|
175
|
+
toggleClass: 'Toggle a CSS class',
|
|
176
|
+
removeClass: 'Remove a CSS class',
|
|
177
|
+
showElement: 'Show an element',
|
|
178
|
+
hideElement: 'Hide an element',
|
|
179
|
+
toggleElement: 'Show/hide an element',
|
|
180
|
+
openDrawer: 'Open a drawer',
|
|
181
|
+
closeDrawer: 'Close a drawer',
|
|
182
|
+
toggleDrawer: 'Open/close a drawer',
|
|
183
|
+
openMenu: 'Open a menu',
|
|
184
|
+
closeMenu: 'Close a menu',
|
|
185
|
+
toggleMenu: 'Open/close a menu',
|
|
186
|
+
setAttribute: 'Set an ARIA or data attribute',
|
|
187
|
+
removeAttribute: 'Remove an ARIA or data attribute',
|
|
188
|
+
scrollTo: 'Scroll to element',
|
|
189
|
+
playVideo: 'Play a video',
|
|
190
|
+
showHtml: 'Show custom HTML',
|
|
191
|
+
runJs: 'Run custom JS (Business)',
|
|
192
|
+
redirect: 'Redirect the visitor',
|
|
193
|
+
trackGaEvent: 'Track an analytics event'
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* The client steps' fields a person types words into, and what a sentence
|
|
197
|
+
* calls each: where a drafted interaction may leave a placeholder
|
|
198
|
+
* (`draft-placeholders.ts`). A client step absent here holds none — a
|
|
199
|
+
* selector, a class name, HTML or a script uses brackets as syntax.
|
|
200
|
+
*/ const CLIENT_INTERACTION_STEP_TYPED_FIELDS = {
|
|
201
|
+
showOverlay: [
|
|
202
|
+
{
|
|
203
|
+
key: 'overlayName',
|
|
204
|
+
names: 'the popup or bar'
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
siteAlert: [
|
|
208
|
+
{
|
|
209
|
+
key: 'message',
|
|
210
|
+
names: 'the message'
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
};
|
|
163
214
|
/**
|
|
164
215
|
* Basic presentational interactions (AGL-577): the subset of client steps
|
|
165
216
|
* that are pure DOM choreography — menu/drawer open-close, element
|
|
@@ -288,19 +339,63 @@ export function isClientActionStep(step) {
|
|
|
288
339
|
var _declarations_find;
|
|
289
340
|
return (_declarations_find = declarations.find((declaration)=>declaration.type === type)) != null ? _declarations_find : null;
|
|
290
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* How a step type reads in an editor, a run history and a drafter's words:
|
|
344
|
+
* the platform's own label for a client step, else the label the declaring
|
|
345
|
+
* plugin gave it; `null` for a type nobody declares.
|
|
346
|
+
*/ export function interactionStepLabel(type, declarations = PLUGIN_INTERACTION_STEPS_DECLARED) {
|
|
347
|
+
var _ref, _CLIENT_INTERACTION_STEP_LABELS_type;
|
|
348
|
+
var _declaredInteractionStep;
|
|
349
|
+
return (_ref = (_CLIENT_INTERACTION_STEP_LABELS_type = CLIENT_INTERACTION_STEP_LABELS[type]) != null ? _CLIENT_INTERACTION_STEP_LABELS_type : (_declaredInteractionStep = declaredInteractionStep(type, declarations)) == null ? void 0 : _declaredInteractionStep.label) != null ? _ref : null;
|
|
350
|
+
}
|
|
351
|
+
/** The hold a step type declares, or `null` for one that never suspends a run. */ export function interactionStepHolds(type, declarations = PLUGIN_INTERACTION_STEPS_DECLARED) {
|
|
352
|
+
var _ref;
|
|
353
|
+
var _declaredInteractionStep;
|
|
354
|
+
return (_ref = (_declaredInteractionStep = declaredInteractionStep(type, declarations)) == null ? void 0 : _declaredInteractionStep.holds) != null ? _ref : null;
|
|
355
|
+
}
|
|
356
|
+
/** The fields of a step type a person types words into: the platform's, else the declaring plugin's. */ export function interactionStepTypedFields(type, declarations = PLUGIN_INTERACTION_STEPS_DECLARED) {
|
|
357
|
+
var _ref, _CLIENT_INTERACTION_STEP_TYPED_FIELDS_type;
|
|
358
|
+
var _declaredInteractionStep;
|
|
359
|
+
return (_ref = (_CLIENT_INTERACTION_STEP_TYPED_FIELDS_type = CLIENT_INTERACTION_STEP_TYPED_FIELDS[type]) != null ? _CLIENT_INTERACTION_STEP_TYPED_FIELDS_type : (_declaredInteractionStep = declaredInteractionStep(type, declarations)) == null ? void 0 : _declaredInteractionStep.typedFields) != null ? _ref : [];
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* The step list as the visitor's browser may see it: cut at the first step
|
|
363
|
+
* that suspends the run.
|
|
364
|
+
*
|
|
365
|
+
* A client step AFTER a hold must never reach the page. The page runs its
|
|
366
|
+
* slice of the list the moment the trigger fires, so shipping the whole list
|
|
367
|
+
* would make "wait three days, then show the popup" show the popup at once —
|
|
368
|
+
* the delay would appear to work on the server, be ignored in the browser,
|
|
369
|
+
* and the two halves of one authored flow would disagree about when it
|
|
370
|
+
* happened.
|
|
371
|
+
*
|
|
372
|
+
* Truncating rather than filtering: everything past the first hold belongs to
|
|
373
|
+
* a run that has not happened yet, whichever side would have executed it.
|
|
374
|
+
*/ export function interactionStepsForClient(steps, declarations = PLUGIN_INTERACTION_STEPS_DECLARED) {
|
|
375
|
+
const list = steps != null ? steps : [];
|
|
376
|
+
const holdAt = list.findIndex((step)=>interactionStepHolds(step.type, declarations) !== null);
|
|
377
|
+
return [
|
|
378
|
+
...holdAt < 0 ? list : list.slice(0, holdAt)
|
|
379
|
+
];
|
|
380
|
+
}
|
|
291
381
|
/**
|
|
292
382
|
* Validates an interaction's shape; returns a human-readable error or null.
|
|
293
383
|
* Every surface that writes one shares this, so a bad step never persists or
|
|
294
384
|
* runs.
|
|
295
385
|
*
|
|
296
|
-
* The platform checks what is its own: the name, the
|
|
297
|
-
* conditions, the step count, each step's guard, its client
|
|
298
|
-
* pick of every declared step that picks a record. A step it
|
|
299
|
-
* left to its owner, through `validateStep`.
|
|
386
|
+
* The platform checks what is its own: the name, the recipe stamp, the
|
|
387
|
+
* trigger and its conditions, the step count, each step's guard, its client
|
|
388
|
+
* steps, and the pick of every declared step that picks a record. A step it
|
|
389
|
+
* does not know is left to its owner, through `validateStep`.
|
|
300
390
|
*/ export function validateInteraction(interaction, options = {}) {
|
|
301
391
|
var _ref, _interaction_steps;
|
|
302
392
|
var _interaction_name, _interaction_trigger_event, _interaction_trigger, _interaction_trigger_selector, _interaction_trigger1, _interaction_trigger2, _interaction_trigger3, _interaction_trigger4;
|
|
303
393
|
if (!((_interaction_name = interaction.name) == null ? void 0 : _interaction_name.trim())) return 'Name the action';
|
|
394
|
+
// A stamp is provenance, and provenance naming a recipe that does not
|
|
395
|
+
// exist is a document nothing can read back; `null` and absent both pass.
|
|
396
|
+
if (interaction.recipe != null && !isKnownInteractionRecipe(interaction.recipe)) {
|
|
397
|
+
return 'Unknown recipe';
|
|
398
|
+
}
|
|
304
399
|
const event = (_ref = (_interaction_trigger = interaction.trigger) == null ? void 0 : (_interaction_trigger_event = _interaction_trigger.event) == null ? void 0 : _interaction_trigger_event.trim()) != null ? _ref : '';
|
|
305
400
|
if (!event) return 'Pick a trigger event';
|
|
306
401
|
// A server event or a custom one fired by another step is, like a site
|
|
@@ -514,6 +609,27 @@ export function isClientActionStep(step) {
|
|
|
514
609
|
}
|
|
515
610
|
return null;
|
|
516
611
|
}
|
|
612
|
+
/** The stored shape of an interaction: what every writer of `hosts/{hostId}/actions` saves. */ export function siteInteractionDocument(interaction) {
|
|
613
|
+
var _trigger_conditions, _trigger_combinator;
|
|
614
|
+
const { recipe } = interaction, rest = _object_without_properties_loose(interaction, [
|
|
615
|
+
"recipe"
|
|
616
|
+
]);
|
|
617
|
+
const trigger = interaction.trigger;
|
|
618
|
+
return _extends({}, rest, {
|
|
619
|
+
trigger: _extends({}, trigger, {
|
|
620
|
+
oncePerVisitor: trigger.oncePerVisitor === true,
|
|
621
|
+
oncePerSession: trigger.oncePerSession === true,
|
|
622
|
+
cooldownMinutes: Number(trigger.cooldownMinutes) >= 1 ? Number(trigger.cooldownMinutes) : null,
|
|
623
|
+
everyTime: trigger.everyTime === true,
|
|
624
|
+
condition: null,
|
|
625
|
+
conditions: (_trigger_conditions = trigger.conditions) != null ? _trigger_conditions : null,
|
|
626
|
+
combinator: (_trigger_combinator = trigger.combinator) != null ? _trigger_combinator : null
|
|
627
|
+
}),
|
|
628
|
+
enabled: interaction.enabled !== false
|
|
629
|
+
}, recipe !== undefined ? {
|
|
630
|
+
recipe
|
|
631
|
+
} : {});
|
|
632
|
+
}
|
|
517
633
|
/**
|
|
518
634
|
* How many LIVE action documents one host may hold (AGL-2266).
|
|
519
635
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-interactions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolveAuthoredEventName, sanitizeEventParams } from './analytics-events'\nimport { type AuthorHtmlRemoval, sanitizeAuthorHtml } from './author-html'\nimport { PLUGIN_INTERACTION_STEPS_DECLARED } from '../plugin-manager/first-party-plugins.generated'\n\n/**\n * SITE INTERACTIONS: what a published page does when a visitor does something.\n *\n * An interaction is a trigger and an ordered step list. The trigger names an\n * event — a SITE event the page runtime watches for itself (a click, a hover,\n * an element scrolled into view, exit intent), or an event raised on the\n * server — narrowed by payload conditions and capped in how often it fires.\n * The steps the platform knows are the ones a page carries out in the\n * visitor's own browser: showing and hiding elements, opening menus and\n * drawers, classes and ARIA attributes, scrolling, playing a video, an alert,\n * a redirect, an analytics event, author HTML and script.\n *\n * An interaction is stored on the element that carries it\n * (`node-interactions.ts`) or as a row of `hosts/{hostId}/actions`, and the\n * same shape holds more than the platform's own steps. A plugin adds steps of\n * its own — the workflows plugin's server steps, which a page hands to\n * `/api/events/dispatch` for the host-event listener that owns them. The\n * platform types only its own steps ({@link ClientInteractionStep}), knows any\n * other step only by its `type`, and treats every step that is not one of its\n * client steps as the server's to run. A plugin that wants one of its steps\n * offered in the interaction builder DECLARES it, under `interactionSteps` in\n * `plugins.config.json` ({@link InteractionStepDeclaration}); nothing here\n * names a plugin.\n */\n\n/**\n * Client-side site events (AGL-256) the tenant page runtime emits:\n * element interactions, scroll behavior, dwell time, and exit intent.\n * Trigger config (selector/threshold/path) rides the interaction's trigger.\n */\nexport const SITE_EVENT_TYPES = [\n 'scrollDepth',\n 'scrollToElement',\n 'elementClick',\n 'elementVisible',\n // Hover choreography (AGL-562): delegated enter/leave on the trigger\n // selector — the nav-menu/drawer show-hide building blocks.\n 'elementHoverEnter',\n 'elementHoverLeave',\n 'exitIntent',\n 'timeOnPage',\n 'pageVisit',\n] as const\n\n/** Site events that watch a specific element and need a selector. */\nexport const ELEMENT_SCOPED_SITE_EVENTS = [\n 'scrollToElement',\n 'elementClick',\n 'elementVisible',\n 'elementHoverEnter',\n 'elementHoverLeave',\n] as const\n\nexport type SiteEventType = (typeof SITE_EVENT_TYPES)[number]\n\nexport function isSiteEventType(event: string): event is SiteEventType {\n return SITE_EVENT_TYPES.includes(event as SiteEventType)\n}\n\n/** Structured condition operators (AGL-557). */\nexport const TRIGGER_CONDITION_OPS = [\n 'equals',\n 'contains',\n 'notEmpty',\n] as const\n\nexport type TriggerConditionOp = (typeof TRIGGER_CONDITION_OPS)[number]\n\n/**\n * Structured per-interaction condition over the event payload (AGL-557): the\n * no-code sibling of the free-text `filter` expression, e.g. \"only run\n * when the `subscribe` field is not empty\". Evaluated against the same\n * scope as the filter (event name + payload fields); the interaction is\n * skipped when unmet.\n */\nexport interface InteractionCondition {\n /** Payload field the condition reads (a submitted form field name). */\n field: string\n op: TriggerConditionOp\n /** Comparison value for `equals`/`contains`; unused by `notEmpty`. */\n value?: string\n}\n\n/**\n * Evaluates a structured trigger condition (AGL-557). String-coerces the\n * payload value; `equals`/`contains` compare trimmed + case-insensitive\n * so checkbox values like \"Yes\" match an author-typed \"yes\". A missing\n * field never matches (and is \"empty\" for `notEmpty`); an absent or\n * field-less condition always passes, mirroring the filter's default.\n */\nexport function evaluateTriggerCondition(\n condition: InteractionCondition | undefined | null,\n payload: Record<string, unknown>,\n): boolean {\n const field = condition?.field?.trim()\n if (!condition || !field) return true\n const raw = (payload ?? {})[field]\n const actual = (raw == null ? '' : String(raw)).trim().toLowerCase()\n if (condition.op === 'notEmpty') return actual.length > 0\n const expected = String(condition.value ?? '')\n .trim()\n .toLowerCase()\n if (condition.op === 'equals') return actual === expected\n if (condition.op === 'contains') {\n return expected.length > 0 && actual.includes(expected)\n }\n return false\n}\n\n/** How chained trigger conditions combine (AGL-565). */\nexport const TRIGGER_COMBINATORS = ['and', 'or'] as const\n\nexport type TriggerCombinator = (typeof TRIGGER_COMBINATORS)[number]\n\n/** Cap on chained conditions per trigger (AGL-565). */\nexport const ACTION_MAX_CONDITIONS = 5\n\n/**\n * Normalizes a trigger's condition clauses to a list (AGL-565): the\n * `conditions` array wins when present; a legacy single `condition`\n * (AGL-557) becomes a one-element list. Read-time only — persisted docs\n * are never migrated, so pre-565 single-condition docs keep working\n * unchanged.\n */\nexport function normalizeTriggerConditions(\n trigger:\n | Pick<InteractionTrigger, 'condition' | 'conditions'>\n | undefined\n | null,\n): InteractionCondition[] {\n const conditions = trigger?.conditions\n if (Array.isArray(conditions)) return conditions.filter(Boolean)\n return trigger?.condition ? [trigger.condition] : []\n}\n\n/**\n * Evaluates a trigger's condition list with its combinator (AGL-565):\n * `and` (the default) requires every condition to pass, `or` any one.\n * Per-condition semantics are unchanged from AGL-557\n * (`evaluateTriggerCondition`), and an empty list always passes — which\n * covers every pre-565 doc without conditions.\n */\nexport function evaluateTriggerConditions(\n trigger:\n | Pick<InteractionTrigger, 'condition' | 'conditions' | 'combinator'>\n | undefined\n | null,\n payload: Record<string, unknown>,\n): boolean {\n const conditions = normalizeTriggerConditions(trigger)\n if (!conditions.length) return true\n return trigger?.combinator === 'or'\n ? conditions.some((condition) =>\n evaluateTriggerCondition(condition, payload),\n )\n : conditions.every((condition) =>\n evaluateTriggerCondition(condition, payload),\n )\n}\n\nexport interface InteractionTrigger {\n /**\n * Event the interaction listens for: a HOST_EVENT_TYPE (server-emitted), a\n * SITE_EVENT_TYPE (client-emitted, AGL-256), or a custom name fired by\n * another automation's `customEvent` step.\n */\n event: string\n /** Optional expression over the payload; runs only when truthy. */\n filter?: string\n /** Optional structured payload condition (AGL-557); null clears it. */\n condition?: InteractionCondition | null\n /**\n * Chained payload conditions (AGL-565), combined with `combinator`.\n * When present this list wins over the legacy single `condition`\n * (see `normalizeTriggerConditions`); null clears it.\n */\n conditions?: InteractionCondition[] | null\n /** How the chained conditions combine (AGL-565); default `and`. */\n combinator?: TriggerCombinator | null\n /** CSS selector for element-scoped site events (click/visible/scroll-to). */\n selector?: string\n /** Percent 0-100 (scrollDepth) or seconds (timeOnPage). */\n threshold?: number\n /** Path pattern the site event listens on (overlay glob rules); empty = all. */\n pathPattern?: string\n /**\n * Fire at most once per visitor (AGL-266, localStorage-keyed) instead\n * of once per pageview. Site events only.\n */\n oncePerVisitor?: boolean\n /**\n * Fire at most once per browser session (AGL-274,\n * sessionStorage-keyed). Site events only.\n */\n oncePerSession?: boolean\n /**\n * Minimum minutes between fires for the same visitor (AGL-274,\n * localStorage timestamp). Site events only; ignored when\n * `oncePerVisitor` is set.\n */\n cooldownMinutes?: number\n /**\n * Fire on EVERY occurrence instead of once per pageview (AGL-562) —\n * required for repeatable UI choreography (menu toggles, drawer\n * open/close, hover show/hide). Site events only; the explicit\n * per-session/visitor/cooldown caps above still win when set.\n */\n everyTime?: boolean\n}\n\n/**\n * A per-STEP condition, and the difference between an automation and a flow.\n *\n * `InteractionTrigger.conditions` gates the whole interaction: every step runs or\n * none does. That is enough for \"when a form is submitted, do these three\n * things\" and not enough for anything with a shape — \"wait three days, then,\n * only if they have not ordered, send the reminder\" needs the condition to\n * belong to the step rather than to the run.\n *\n * Same clause type and the same combinator as the trigger's, evaluated\n * against the same scope, so an author learns one condition editor and the\n * two can never disagree about what `contains` means.\n */\nexport interface InteractionStepGuard {\n conditions: InteractionCondition[]\n /** How the clauses combine; default `and`, as on the trigger. */\n combinator?: TriggerCombinator | null\n}\n\n/**\n * Evaluates a step's own guard, with the trigger's semantics.\n *\n * An absent or clause-less guard passes, exactly as an absent trigger\n * condition does — so every step written before guards existed keeps running.\n */\nexport function evaluateStepGuard(\n guard: InteractionStepGuard | undefined | null,\n scope: Record<string, unknown>,\n): boolean {\n const conditions = (guard?.conditions ?? []).filter(Boolean)\n if (!conditions.length) return true\n return guard?.combinator === 'or'\n ? conditions.some((condition) => evaluateTriggerCondition(condition, scope))\n : conditions.every((condition) => evaluateTriggerCondition(condition, scope))\n}\n\n/**\n * The steps the platform itself carries out, in the visitor's browser (AGL-257).\n *\n * Every other step an interaction may hold is a plugin's, known here only as\n * {@link InteractionStepBase}.\n */\nexport type ClientInteractionStep = (\n | {\n type: 'siteAlert'\n message: string\n severity?: 'info' | 'success' | 'warning' | 'error'\n }\n // Client-side UI steps (AGL-257): run in the visitor's page by the\n // tenant automations engine; the server executor skips them.\n | { type: 'showOverlay'; overlayId?: string; overlayName?: string }\n | { type: 'stickyNav'; selector?: string }\n // Selectors accept CSS or a node id via [data-node-id=\"…\"] — the\n // besigner element picker emits the latter (AGL-314/319).\n | { type: 'addClass'; selector: string; className: string }\n | { type: 'removeClass'; selector: string; className: string }\n | { type: 'toggleClass'; selector: string; className: string }\n // Element show/hide choreography (AGL-562): sugar over the shared\n // hidden class (see element-ui.ts) so authors never type class names.\n // The besigner target picker emits the node's stable data-aglyn\n // selector; any CSS selector works.\n // Element visibility (AGL-562) with menu-grade choreography (AGL-589):\n // `delayMs` defers the change and a later visibility step on the same\n // selector cancels the pending one (the hover grace period), and\n // `dismissOn` self-dismisses a shown element on Escape / outside click.\n | {\n type: 'showElement'\n selector: string\n delayMs?: number\n dismissOn?: ElementDismissOption[]\n }\n | { type: 'hideElement'; selector: string; delayMs?: number }\n | {\n type: 'toggleElement'\n selector: string\n delayMs?: number\n dismissOn?: ElementDismissOption[]\n }\n // Drawer commands (AGL-562): delivered to muiDrawer instances over the\n // window event bus keyed by node id; an empty target addresses the\n // page's first drawer.\n // Attribute steps (AGL-2546): the semantics half of a hand-built\n // disclosure. `showElement` opens the panel; these are what let it say so.\n //\n // `selector`, deliberately, and NOT a new targeting field. Two mechanisms\n // key off that exact name: `regraftStepSelectors` rewrites it to the\n // grafted `cmp__{instance}__{id}` when an interaction is authored inside a\n // reusable component, and the runtime wraps it in `expandLeafSelector`.\n // The menu steps took `menuNodeId` instead, get neither, and compensate at\n // run time — one concept should not have three targeting models.\n //\n // Two verbs rather than one with an empty value: `\"\"` does not mean\n // \"absent\" uniformly. `hidden=\"\"` is true, `aria-expanded=\"\"` is invalid\n // and discarded, `data-x=\"\"` is a real value an attribute selector still\n // matches. Overloading it would make the step's meaning depend on which\n // attribute the author picked.\n | { type: 'setAttribute'; selector: string; name: string; value: string }\n | { type: 'removeAttribute'; selector: string; name: string }\n // Steps that act on ONE element (AGL-2867): the first rendered match of\n // `selector`, which is spelled `selector` for the reason the attribute\n // steps give above. `scrollTo` rather than `scrollToElement`, because that\n // name is already a TRIGGER — \"the visitor scrolled to the element\" — and\n // one word meaning both directions would be read wrong by every reader of a\n // stored document.\n | {\n type: 'scrollTo'\n selector: string\n /** Absent means `smooth`. A visitor who asks for reduced motion is always scrolled instantly. */\n behavior?: ScrollToBehavior\n /** Pixels left above the target, so a sticky header does not cover it. Absent means 0. */\n offsetPx?: number\n }\n // Presses a Video element's poster from outside it: the same press, so\n // the lightbox opens and plays, or the film plays in place of the poster.\n | { type: 'playVideo'; selector: string }\n | { type: 'openDrawer'; drawerNodeId?: string }\n | { type: 'closeDrawer'; drawerNodeId?: string }\n | { type: 'toggleDrawer'; drawerNodeId?: string }\n // Menu commands (AGL-568): the drawer pattern for Dropdown/Mega Menu\n // elements — delivered over their own window event bus keyed by node\n // id; an empty target addresses the page's first menu. Hover-triggered\n // opens carry a hover flag so the menu closes on pointer leave.\n | { type: 'openMenu'; menuNodeId?: string }\n | { type: 'closeMenu'; menuNodeId?: string }\n | { type: 'toggleMenu'; menuNodeId?: string }\n | { type: 'showHtml'; html: string }\n | { type: 'runJs'; code: string }\n // Screen targets are rename-safe (AGL-339): the tenant resolves the\n // screen id against the host routing map at run time; `url` is the\n // external/manual fallback.\n | { type: 'redirect'; url?: string; screenId?: string }\n | {\n type: 'trackGaEvent'\n eventName: string\n params?: Record<string, string>\n }\n) & {\n /** See {@link InteractionStepGuard}. Absent means the step always runs. */\n when?: InteractionStepGuard | null\n}\n\nexport type ClientInteractionStepType = ClientInteractionStep['type']\n\n/**\n * Any step, as the platform holds it: a type, the guard every step may carry,\n * and fields the platform does not read. A plugin's own step types its other\n * fields itself.\n */\nexport interface InteractionStepBase {\n type: string\n when?: InteractionStepGuard | null\n [field: string]: unknown\n}\n\n/**\n * An interaction, as `hosts/{hostId}/actions/{id}` and an element's own\n * `interactions` hold it.\n *\n * `Step` is the platform's client vocabulary unless a reader names more: the\n * workflows plugin reads the same documents with its server steps added, and\n * a step from a plugin this reader does not know is still carried, untyped,\n * in the list.\n */\nexport interface SiteInteraction<Step extends InteractionStepBase = ClientInteractionStep> {\n name: string\n trigger: InteractionTrigger\n steps: Step[]\n /** A disabled interaction never runs; a new one defaults enabled. */\n enabled?: boolean\n}\n\n/** Steps the tenant page runtime executes client-side (AGL-257). */\nexport const CLIENT_ACTION_STEP_TYPES: ReadonlySet<string> =\n new Set<ClientInteractionStepType>([\n 'showOverlay',\n 'stickyNav',\n 'addClass',\n 'removeClass',\n 'toggleClass',\n 'showElement',\n 'hideElement',\n 'toggleElement',\n 'openDrawer',\n 'closeDrawer',\n 'toggleDrawer',\n 'openMenu',\n 'closeMenu',\n 'toggleMenu',\n 'setAttribute',\n 'removeAttribute',\n 'scrollTo',\n 'playVideo',\n 'showHtml',\n 'runJs',\n 'redirect',\n 'trackGaEvent',\n 'siteAlert',\n ] as const)\n\nexport function isClientActionStep(step: InteractionStepBase): step is ClientInteractionStep {\n return CLIENT_ACTION_STEP_TYPES.has(step.type)\n}\n\n/**\n * Basic presentational interactions (AGL-577): the subset of client steps\n * that are pure DOM choreography — menu/drawer open-close, element\n * show/hide, class toggles, sticky nav, navigation, and client-only\n * alerts. They carry NO server cost and touch NO data, so they run on\n * every plan (the `interactions` feature, on by default everywhere).\n *\n * The powerful client steps stay behind the `actions` entitlement:\n * `showOverlay` (marketing overlays), `showHtml` (arbitrary HTML\n * injection), and `trackGaEvent` (analytics). `runJs` keeps its own\n * higher `webhooks` (Business) gate. A step that is not a client step is\n * the server's, and is re-checked against `actions` where it runs.\n */\nexport const BASIC_CLIENT_ACTION_STEP_TYPES: ReadonlySet<string> =\n new Set<ClientInteractionStepType>([\n 'stickyNav',\n 'addClass',\n 'removeClass',\n 'toggleClass',\n 'showElement',\n 'hideElement',\n 'toggleElement',\n 'openDrawer',\n 'closeDrawer',\n 'toggleDrawer',\n 'openMenu',\n 'closeMenu',\n 'toggleMenu',\n 'setAttribute',\n 'removeAttribute',\n // Moving the page and pressing a player the author placed: no data, no\n // server, and nothing a Video element's own poster does not already do\n // on every plan.\n 'scrollTo',\n 'playVideo',\n 'redirect',\n 'siteAlert',\n ] as const)\n\n/** A client step available on all plans (no `actions` entitlement). */\nexport function isBasicClientActionStep(step: InteractionStepBase): boolean {\n return BASIC_CLIENT_ACTION_STEP_TYPES.has(step.type)\n}\n\n/**\n * Whether a client step may run for the given entitlement tier (AGL-577).\n * This is the single source of truth the page enricher uses to trim the\n * client-automation payload per plan:\n *\n * - Basic presentational steps → always (every plan).\n * - `runJs` → `allowJs` (Business `webhooks` tier).\n * - Remaining advanced client steps (overlay / showHtml / analytics) →\n * `actionsEntitled` (`actions` tier).\n *\n * Server steps are not client steps and always return false here; they\n * are dispatched and re-authorized server-side.\n */\nexport function isClientStepEntitled(\n step: InteractionStepBase,\n entitlements: { actionsEntitled: boolean; allowJs: boolean },\n): boolean {\n if (!isClientActionStep(step)) return false\n if (isBasicClientActionStep(step)) return true\n if (step.type === 'runJs') return entitlements.allowJs\n return entitlements.actionsEntitled\n}\n\n/** The longest step list an interaction may hold. */\nexport const ACTION_MAX_STEPS = 10\n\n/**\n * Cap on the parameters one `trackGaEvent` step may carry (AGL-1587).\n *\n * Not a storage concern. These pairs are the least trustworthy analytics\n * input on the platform — both halves are typed by a site author and land in\n * that site's own GA4 property — and a bounded list is one an author can read\n * back in full before publishing, which is the only review a parameter ever\n * gets. GA4 caps custom parameters per event as well and drops the overflow\n * without saying so, so an unbounded editor would let an author configure\n * parameters that are never delivered and never explained. Ten, the same\n * ceiling {@link ACTION_MAX_STEPS} puts on the step list itself.\n */\nexport const ACTION_MAX_EVENT_PARAMS = 10\n\n/**\n * Longest analytics parameter NAME GA4 accepts; it drops a longer one on\n * arrival, so a name over this is silence rather than a truncated dimension.\n * The value half is capped by `ANALYTICS_PARAM_MAX_LENGTH`, which truncates\n * instead of dropping.\n */\nexport const ACTION_EVENT_PARAM_NAME_MAX_LENGTH = 40\n\n/** Self-dismiss triggers for shown elements (AGL-589). */\nexport const ELEMENT_DISMISS_OPTIONS = ['escape', 'outsideClick'] as const\nexport type ElementDismissOption = (typeof ELEMENT_DISMISS_OPTIONS)[number]\n/** Visibility grace-delay ceiling — enough for hover travel, no dead UIs. */\nexport const ELEMENT_VISIBILITY_MAX_DELAY_MS = 5000\n\n/**\n * How a `scrollTo` step moves the page. Stored in documents; never rename a\n * value. `instant` rather than the DOM's `auto`, because `auto` defers to the\n * site's CSS `scroll-behavior` — a site that sets `smooth` there would animate\n * a scroll the author asked to be instant.\n */\nexport const SCROLL_TO_BEHAVIORS = ['smooth', 'instant'] as const\nexport type ScrollToBehavior = (typeof SCROLL_TO_BEHAVIORS)[number]\n\n/**\n * The largest offset a `scrollTo` step may leave above its target.\n *\n * The offset exists to clear a sticky header, which is tens of pixels and\n * rarely more than a few hundred with an announcement bar stacked on it. A\n * thousand leaves room for any header while still refusing the extra digit\n * that would park the target off the bottom of the screen.\n */\nexport const SCROLL_TO_MAX_OFFSET_PX = 1000\n\n/** Custom event names: short, no collision with built-ins. */\nexport const CUSTOM_EVENT_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{1,39}$/\n\n/**\n * The attribute names an interaction may write (AGL-2546).\n *\n * An unrestricted attribute setter is a self-XSS vector authored through the\n * interactions UI: it reaches `href`, `src`, `onclick`, `formaction` and\n * `style`, and on a site with collaborators the author and the victim need\n * not be the same person. `aria-*` and `data-*` carry the entire\n * accessibility use case and none of that exposure.\n *\n * Deliberately NOT solved by an entitlement gate. `runJs` is Business-gated\n * because it is dangerous at any tier; this has to work on every plan,\n * because a screen-reader user's access to a menu is not a paid feature.\n */\nexport const INTERACTION_ATTRIBUTE_NAME_PATTERN = /^(?:aria|data)-[a-z][a-z0-9-]*$/\n\n/**\n * Whether an interaction is allowed to write this attribute name.\n *\n * Case-insensitive because HTML attribute names are, and a rejected name is\n * a no-op rather than an error: a step that cannot run must not break the\n * steps after it in the same interaction.\n */\nexport function isInteractionAttributeAllowed(name: unknown): boolean {\n return (\n typeof name === 'string' &&\n INTERACTION_ATTRIBUTE_NAME_PATTERN.test(name.trim().toLowerCase())\n )\n}\n\n/**\n * An interaction step a plugin declares under `interactionSteps` in\n * `plugins.config.json`, compiled into the first-party catalog, so the\n * interaction builder can offer it and every validator can check it without\n * the plugin loaded.\n *\n * A step may be the plugin's own (a server step the plugin runs) or a\n * platform client step whose target is a record the plugin keeps. Either way\n * the declaration says how the builder names it and, for a step that PICKS a\n * record, where the builder lists the records from and which of the step's\n * fields hold the pick.\n */\nexport interface InteractionStepDeclaration {\n /** The plugin that declared it. */\n pluginId: string\n /** The stored step `type`. */\n type: string\n /** How the builder's step picker names it: \"Run a workflow\". */\n label: string\n /** The record this step acts on, picked in the builder. */\n picks?: {\n /** The site collection the records are listed from — one the plugin declares it owns. */\n collection: string\n /** How many records the builder lists. */\n limit: number\n /** The step field holding the picked record's id (rename-safe). */\n idField: string\n /** The step field holding its name at the time it was picked (display only). */\n nameField: string\n /** The picker's label: \"Workflow\". */\n label: string\n /** What a step with no pick is told, after its `Step N:` prefix. */\n missing: string\n }\n}\n\n/** Every step the first-party plugins declare, in `plugins.config.json` order. */\nexport function declaredInteractionSteps(): readonly InteractionStepDeclaration[] {\n return PLUGIN_INTERACTION_STEPS_DECLARED\n}\n\n/** The declared step of this type, or `null` for one no plugin declares. */\nexport function declaredInteractionStep(\n type: string,\n declarations: readonly InteractionStepDeclaration[] = PLUGIN_INTERACTION_STEPS_DECLARED,\n): InteractionStepDeclaration | null {\n return declarations.find((declaration) => declaration.type === type) ?? null\n}\n\nexport interface ValidateInteractionOptions {\n /**\n * One step's checks that belong to its owner — a plugin's own step types.\n * Runs after the step's guard is checked; its answer, when not `null`, is\n * the interaction's.\n */\n validateStep?: (step: InteractionStepBase, label: string) => string | null\n /** The declared steps; the compiled first-party catalog unless a spec says otherwise. */\n declarations?: readonly InteractionStepDeclaration[]\n}\n\n/**\n * Validates an interaction's shape; returns a human-readable error or null.\n * Every surface that writes one shares this, so a bad step never persists or\n * runs.\n *\n * The platform checks what is its own: the name, the trigger and its\n * conditions, the step count, each step's guard, its client steps, and the\n * pick of every declared step that picks a record. A step it does not know is\n * left to its owner, through `validateStep`.\n */\nexport function validateInteraction(\n interaction: SiteInteraction<InteractionStepBase>,\n options: ValidateInteractionOptions = {},\n): string | null {\n if (!interaction.name?.trim()) return 'Name the action'\n const event = interaction.trigger?.event?.trim() ?? ''\n if (!event) return 'Pick a trigger event'\n // A server event or a custom one fired by another step is, like a site\n // event, a name; every built-in server event fits the custom pattern.\n if (!isSiteEventType(event) && !CUSTOM_EVENT_PATTERN.test(event)) {\n return 'Custom event names are 2–40 letters, digits, dashes'\n }\n // Site-event trigger config (AGL-256; hover events AGL-562).\n if (\n (ELEMENT_SCOPED_SITE_EVENTS as readonly string[]).includes(event) &&\n !interaction.trigger?.selector?.trim()\n ) {\n return 'This trigger needs a CSS selector'\n }\n if (\n ['scrollDepth', 'timeOnPage'].includes(event) &&\n !(Number(interaction.trigger?.threshold) > 0)\n ) {\n return event === 'scrollDepth'\n ? 'Set the scroll percentage (1–100)'\n : 'Set the seconds on page'\n }\n // Frequency caps (AGL-274).\n if (\n interaction.trigger?.cooldownMinutes != null &&\n !(Number(interaction.trigger.cooldownMinutes) >= 1)\n ) {\n return 'Cooldown must be at least 1 minute'\n }\n // Structured payload conditions (AGL-557; chained AGL-565).\n const combinator = interaction.trigger?.combinator\n if (combinator != null && !TRIGGER_COMBINATORS.includes(combinator)) {\n return 'Combine conditions with AND or OR'\n }\n const conditions = normalizeTriggerConditions(interaction.trigger)\n if (conditions.length > ACTION_MAX_CONDITIONS) {\n return `Conditions are capped at ${ACTION_MAX_CONDITIONS}`\n }\n for (const [index, condition] of conditions.entries()) {\n // Single-condition messages stay exactly as AGL-557 shipped them;\n // the row marker only appears once there are rows to tell apart.\n const where = conditions.length > 1 ? ` (condition ${index + 1})` : ''\n if (!TRIGGER_CONDITION_OPS.includes(condition.op)) {\n return `Pick a condition operator${where}`\n }\n if (!condition.field?.trim()) {\n return `Name the field the condition checks${where}`\n }\n if (condition.op !== 'notEmpty' && !condition.value?.trim()) {\n return `Enter the value the condition compares against${where}`\n }\n }\n const steps = interaction.steps ?? []\n if (!steps.length) return 'Add at least one step'\n if (steps.length > ACTION_MAX_STEPS) {\n return `Actions are capped at ${ACTION_MAX_STEPS} steps`\n }\n for (const [index, step] of steps.entries()) {\n const label = `Step ${index + 1}`\n // A step's own guard, checked with the same messages the trigger's\n // clauses get — one condition editor, one set of complaints about it.\n const guardClauses = (step.when?.conditions ?? []).filter(Boolean)\n if (guardClauses.length > ACTION_MAX_CONDITIONS) {\n return `${label}: conditions are capped at ${ACTION_MAX_CONDITIONS}`\n }\n if (\n step.when?.combinator != null &&\n !TRIGGER_COMBINATORS.includes(step.when.combinator)\n ) {\n return `${label}: combine conditions with AND or OR`\n }\n for (const clause of guardClauses) {\n if (!TRIGGER_CONDITION_OPS.includes(clause.op)) {\n return `${label}: pick a condition operator`\n }\n if (!clause.field?.trim()) {\n return `${label}: name the field the condition checks`\n }\n if (clause.op !== 'notEmpty' && !clause.value?.trim()) {\n return `${label}: enter the value the condition compares against`\n }\n }\n const owned = options.validateStep?.(step, label)\n if (owned) return owned\n const declared = declaredInteractionStep(step.type, options.declarations)\n if (declared?.picks) {\n const id = step[declared.picks.idField]\n const name = step[declared.picks.nameField]\n if (\n !(typeof id === 'string' && id.trim()) &&\n !(typeof name === 'string' && name.trim())\n ) {\n return `${label}: ${declared.picks.missing}`\n }\n }\n if (isClientActionStep(step)) {\n const problem = clientStepProblem(step, label)\n if (problem) return problem\n }\n }\n return null\n}\n\n/** A client step's own complaint, or null. The step's guard is checked by the caller. */\nfunction clientStepProblem(step: ClientInteractionStep, label: string): string | null {\n if (step.type === 'siteAlert' && !step.message?.trim()) {\n return `${label}: enter the alert message`\n }\n if (\n step.type === 'addClass' ||\n step.type === 'removeClass' ||\n step.type === 'toggleClass'\n ) {\n if (!step.selector?.trim()) return `${label}: enter a CSS selector`\n if (!step.className?.trim()) return `${label}: enter the class name`\n }\n // Element show/hide steps (AGL-562) always target a selector; drawer\n // (AGL-562) and menu (AGL-568) commands may omit the target (the\n // page's first drawer/menu answers).\n if (\n (step.type === 'showElement' ||\n step.type === 'hideElement' ||\n step.type === 'toggleElement') &&\n !step.selector?.trim()\n ) {\n return `${label}: pick the element to show or hide`\n }\n if (\n step.type === 'showElement' ||\n step.type === 'hideElement' ||\n step.type === 'toggleElement'\n ) {\n // Choreography options (AGL-589).\n const delay = (step as { delayMs?: unknown }).delayMs\n if (\n delay != null &&\n !(\n Number.isInteger(delay) &&\n (delay as number) >= 0 &&\n (delay as number) <= ELEMENT_VISIBILITY_MAX_DELAY_MS\n )\n ) {\n return `${label}: delay must be 0–${ELEMENT_VISIBILITY_MAX_DELAY_MS}ms`\n }\n const dismiss = (step as { dismissOn?: unknown }).dismissOn\n if (\n dismiss != null &&\n !(\n Array.isArray(dismiss) &&\n dismiss.every((option) =>\n (ELEMENT_DISMISS_OPTIONS as readonly string[]).includes(\n option as string,\n ),\n )\n )\n ) {\n return `${label}: dismiss options are escape and outsideClick`\n }\n }\n // One-target steps (AGL-2867). The runtime does nothing for a target it\n // cannot find, so a missing one is refused here, where it can be fixed.\n if (step.type === 'scrollTo') {\n if (!step.selector?.trim()) {\n return `${label}: pick the element to scroll to`\n }\n const behavior = (step as { behavior?: unknown }).behavior\n if (\n behavior != null &&\n !(SCROLL_TO_BEHAVIORS as readonly unknown[]).includes(behavior)\n ) {\n return `${label}: scroll smoothly or instantly`\n }\n const offset = (step as { offsetPx?: unknown }).offsetPx\n if (\n offset != null &&\n !(\n Number.isInteger(offset) &&\n (offset as number) >= 0 &&\n (offset as number) <= SCROLL_TO_MAX_OFFSET_PX\n )\n ) {\n return `${label}: the offset must be 0–${SCROLL_TO_MAX_OFFSET_PX}px`\n }\n }\n if (step.type === 'playVideo' && !step.selector?.trim()) {\n return `${label}: pick the video to play`\n }\n if (step.type === 'showHtml') {\n if (!step.html?.trim()) return `${label}: enter the HTML`\n // AGL-2486: the author-facing half of the runtime sanitizer, exactly as\n // the reserved analytics name below is the author-facing half of the\n // runtime refusal. `site-runtime.tsx` runs this HTML through\n // `sanitizeAuthorHtml` for a VISITOR, where the only thing it can do\n // with markup it refuses is drop it and say nothing — so an author who\n // pastes an embed snippet gets a step that runs, reports success, and\n // shows nothing. The refusal is reported HERE, where the person who\n // can fix it is looking, and it is the SAME function that will run at\n // render rather than a second description of its rules.\n const removals: AuthorHtmlRemoval[] = []\n const safe = sanitizeAuthorHtml(step.html, removals)\n if (!safe.trim()) {\n return `${label}: none of this HTML can be shown on a published page — ${removals[0]?.message ?? 'it is removed by the sanitizer.'}`\n }\n if (removals.length) {\n return `${label}: ${removals[0].message}${removals.length > 1 ? ` (+${removals.length - 1} more)` : ''}`\n }\n }\n if (step.type === 'runJs' && !step.code?.trim()) {\n return `${label}: enter the JavaScript`\n }\n if (step.type === 'redirect' && !step.url?.trim() && !step.screenId) {\n return `${label}: pick a page or enter the destination URL`\n }\n if (step.type === 'trackGaEvent') {\n if (!step.eventName?.trim()) {\n return `${label}: name the analytics event`\n }\n // AGL-1587: the author-facing half of the runtime refusal. The runtime\n // can only drop a bad name silently — it is executing for a VISITOR —\n // so the name is checked HERE, where the person who can fix it is.\n const resolved = resolveAuthoredEventName(step.eventName)\n if (resolved.reason === 'reserved') {\n return `${label}: \"${step.eventName.trim()}\" is a reserved analytics event name — pick another`\n }\n if (!resolved.name) {\n return `${label}: the analytics event name must start with a letter`\n }\n const params = Object.entries(step.params ?? {})\n if (params.length > ACTION_MAX_EVENT_PARAMS) {\n return `${label}: analytics parameters are capped at ${ACTION_MAX_EVENT_PARAMS}`\n }\n for (const [key, value] of params) {\n if (!key.trim()) return `${label}: name every analytics parameter`\n if (key.trim().length > ACTION_EVENT_PARAM_NAME_MAX_LENGTH) {\n return `${label}: the \"${key.trim().slice(0, 16)}…\" parameter name is over ${ACTION_EVENT_PARAM_NAME_MAX_LENGTH} characters, which GA4 drops`\n }\n if (!String(value ?? '').trim()) {\n return `${label}: enter a value for the \"${key.trim()}\" parameter`\n }\n }\n // The author-facing half of the runtime sanitizer, the same shape the\n // reserved name above and the `showHtml` check take. `trackAuthoredEvent`\n // strips a parameter that can carry personal data and says nothing about\n // it, because it is executing for a VISITOR — so an author who binds a\n // form field into a parameter gets a step that runs, reports success,\n // and delivers an event missing the dimension it was created for. The\n // strip is reported HERE, by running the REAL `sanitizeEventParams`\n // rather than a second description of its rules, so the two can never\n // disagree about which parameter survives.\n const kept = sanitizeEventParams(step.params ?? undefined)\n const stripped = params.find(([key]) => !(key in kept))\n if (stripped) {\n return `${label}: the \"${stripped[0]}\" parameter is never sent — an analytics parameter must not carry a visitor's own details`\n }\n }\n return null\n}\n\n/** Alert produced by a `siteAlert` step, surfaced to the emitting client. */\nexport interface SiteAlert {\n message: string\n severity: 'info' | 'success' | 'warning' | 'error'\n}\n\n/**\n * How many LIVE action documents one host may hold (AGL-2266).\n *\n * `hosts/{hostId}/actions` was in NO exclusion list in\n * `cloud/firebase-firestore.rules`, so the host catch-all's `allow create`\n * granted it to any editor, client-direct, on any plan — and the import\n * route's own table said so in as many words: *\"no `RESOURCES` entry and no\n * quota key anywhere; all three creators write the document client-direct.\"*\n * A free org could therefore mint unbounded Firestore documents from the\n * browser against a $0 subscription. Unbounded infrastructure, not a bypass of\n * anything we sell, which is `WEBHOOK_MAX_PER_HOST`'s (AGL-1360) and\n * `NON_PAGE_SCREEN_MAX_PER_HOST`'s (AGL-1399) shape exactly: a flat PLATFORM\n * cap with no `OrgEntitlements` key, no variation by plan, and nothing on the\n * price list to explain.\n *\n * ## Not gated on the `actions` entitlement, deliberately\n *\n * `actions` is a Pro feature and `interactions` is free, and BOTH write this\n * collection — the interaction builder and the besigner's preset wiring create\n * the same document type the Pro actions card does. Gating creation on the\n * paid flag would take element interactions away from every free and starter\n * site, which is a pricing change wearing a cap's clothes. The runtime already\n * checks the entitlement where it decides what may RUN\n * (`run-event-actions.ts`), which is the right place for it.\n *\n * ## Why 500\n *\n * Sized to the busiest plausible site rather than to today's data. An action\n * is authored per interactive element, so a heavy marketing site with dropdown\n * choreography on every section reaches tens; presets wire a handful at a\n * time. 500 is far past anything authoring produces and well short of what a\n * script in a loop wants, which is the only property a flat cap needs. Live\n * documents only — the interactions provider soft-deletes with `deletedAt`, so\n * counting tombstones would be AGL-1173's bug one collection over, where\n * removing an interaction never frees its slot.\n */\nexport const ACTIONS_MAX_PER_HOST = 500\n"],"names":["resolveAuthoredEventName","sanitizeEventParams","sanitizeAuthorHtml","PLUGIN_INTERACTION_STEPS_DECLARED","SITE_EVENT_TYPES","ELEMENT_SCOPED_SITE_EVENTS","isSiteEventType","event","includes","TRIGGER_CONDITION_OPS","evaluateTriggerCondition","condition","payload","field","trim","raw","actual","String","toLowerCase","op","length","expected","value","TRIGGER_COMBINATORS","ACTION_MAX_CONDITIONS","normalizeTriggerConditions","trigger","conditions","Array","isArray","filter","Boolean","evaluateTriggerConditions","combinator","some","every","evaluateStepGuard","guard","scope","CLIENT_ACTION_STEP_TYPES","Set","isClientActionStep","step","has","type","BASIC_CLIENT_ACTION_STEP_TYPES","isBasicClientActionStep","isClientStepEntitled","entitlements","allowJs","actionsEntitled","ACTION_MAX_STEPS","ACTION_MAX_EVENT_PARAMS","ACTION_EVENT_PARAM_NAME_MAX_LENGTH","ELEMENT_DISMISS_OPTIONS","ELEMENT_VISIBILITY_MAX_DELAY_MS","SCROLL_TO_BEHAVIORS","SCROLL_TO_MAX_OFFSET_PX","CUSTOM_EVENT_PATTERN","INTERACTION_ATTRIBUTE_NAME_PATTERN","isInteractionAttributeAllowed","name","test","declaredInteractionSteps","declaredInteractionStep","declarations","find","declaration","validateInteraction","interaction","options","selector","Number","threshold","cooldownMinutes","index","entries","where","steps","label","guardClauses","when","clause","owned","validateStep","declared","picks","id","idField","nameField","missing","problem","clientStepProblem","message","className","delay","delayMs","isInteger","dismiss","dismissOn","option","behavior","offset","offsetPx","html","removals","safe","code","url","screenId","eventName","resolved","reason","params","Object","key","slice","kept","undefined","stripped","ACTIONS_MAX_PER_HOST"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-interactions.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { resolveAuthoredEventName, sanitizeEventParams } from './analytics-events'\nimport { type AuthorHtmlRemoval, sanitizeAuthorHtml } from './author-html'\nimport { PLUGIN_INTERACTION_STEPS_DECLARED } from '../plugin-manager/first-party-plugins.generated'\nimport { isKnownInteractionRecipe } from '../plugin-manager/interaction-recipes'\n\n/**\n * SITE INTERACTIONS: what a published page does when a visitor does something.\n *\n * An interaction is a trigger and an ordered step list. The trigger names an\n * event — a SITE event the page runtime watches for itself (a click, a hover,\n * an element scrolled into view, exit intent), or an event raised on the\n * server — narrowed by payload conditions and capped in how often it fires.\n * The steps the platform knows are the ones a page carries out in the\n * visitor's own browser: showing and hiding elements, opening menus and\n * drawers, classes and ARIA attributes, scrolling, playing a video, an alert,\n * a redirect, an analytics event, author HTML and script.\n *\n * An interaction is stored on the element that carries it\n * (`node-interactions.ts`) or as a row of `hosts/{hostId}/actions`, and the\n * same shape holds more than the platform's own steps. A plugin adds steps of\n * its own — the workflows plugin's server steps, which a page hands to\n * `/api/events/dispatch` for the host-event listener that owns them. The\n * platform types only its own steps ({@link ClientInteractionStep}), knows any\n * other step only by its `type`, and treats every step that is not one of its\n * client steps as the server's to run. A plugin that wants one of its steps\n * offered in the interaction builder DECLARES it, under `interactionSteps` in\n * `plugins.config.json` ({@link InteractionStepDeclaration}); nothing here\n * names a plugin.\n */\n\n/**\n * Client-side site events (AGL-256) the tenant page runtime emits:\n * element interactions, scroll behavior, dwell time, and exit intent.\n * Trigger config (selector/threshold/path) rides the interaction's trigger.\n */\nexport const SITE_EVENT_TYPES = [\n 'scrollDepth',\n 'scrollToElement',\n 'elementClick',\n 'elementVisible',\n // Hover choreography (AGL-562): delegated enter/leave on the trigger\n // selector — the nav-menu/drawer show-hide building blocks.\n 'elementHoverEnter',\n 'elementHoverLeave',\n 'exitIntent',\n 'timeOnPage',\n 'pageVisit',\n] as const\n\n/** Site events that watch a specific element and need a selector. */\nexport const ELEMENT_SCOPED_SITE_EVENTS = [\n 'scrollToElement',\n 'elementClick',\n 'elementVisible',\n 'elementHoverEnter',\n 'elementHoverLeave',\n] as const\n\nexport type SiteEventType = (typeof SITE_EVENT_TYPES)[number]\n\nexport function isSiteEventType(event: string): event is SiteEventType {\n return SITE_EVENT_TYPES.includes(event as SiteEventType)\n}\n\n/** Structured condition operators (AGL-557). */\nexport const TRIGGER_CONDITION_OPS = [\n 'equals',\n 'contains',\n 'notEmpty',\n] as const\n\nexport type TriggerConditionOp = (typeof TRIGGER_CONDITION_OPS)[number]\n\n/**\n * Structured per-interaction condition over the event payload (AGL-557): the\n * no-code sibling of the free-text `filter` expression, e.g. \"only run\n * when the `subscribe` field is not empty\". Evaluated against the same\n * scope as the filter (event name + payload fields); the interaction is\n * skipped when unmet.\n */\nexport interface InteractionCondition {\n /** Payload field the condition reads (a submitted form field name). */\n field: string\n op: TriggerConditionOp\n /** Comparison value for `equals`/`contains`; unused by `notEmpty`. */\n value?: string\n}\n\n/**\n * Evaluates a structured trigger condition (AGL-557). String-coerces the\n * payload value; `equals`/`contains` compare trimmed + case-insensitive\n * so checkbox values like \"Yes\" match an author-typed \"yes\". A missing\n * field never matches (and is \"empty\" for `notEmpty`); an absent or\n * field-less condition always passes, mirroring the filter's default.\n */\nexport function evaluateTriggerCondition(\n condition: InteractionCondition | undefined | null,\n payload: Record<string, unknown>,\n): boolean {\n const field = condition?.field?.trim()\n if (!condition || !field) return true\n const raw = (payload ?? {})[field]\n const actual = (raw == null ? '' : String(raw)).trim().toLowerCase()\n if (condition.op === 'notEmpty') return actual.length > 0\n const expected = String(condition.value ?? '')\n .trim()\n .toLowerCase()\n if (condition.op === 'equals') return actual === expected\n if (condition.op === 'contains') {\n return expected.length > 0 && actual.includes(expected)\n }\n return false\n}\n\n/** How chained trigger conditions combine (AGL-565). */\nexport const TRIGGER_COMBINATORS = ['and', 'or'] as const\n\nexport type TriggerCombinator = (typeof TRIGGER_COMBINATORS)[number]\n\n/** Cap on chained conditions per trigger (AGL-565). */\nexport const ACTION_MAX_CONDITIONS = 5\n\n/**\n * Normalizes a trigger's condition clauses to a list (AGL-565): the\n * `conditions` array wins when present; a legacy single `condition`\n * (AGL-557) becomes a one-element list. Read-time only — persisted docs\n * are never migrated, so pre-565 single-condition docs keep working\n * unchanged.\n */\nexport function normalizeTriggerConditions(\n trigger:\n | Pick<InteractionTrigger, 'condition' | 'conditions'>\n | undefined\n | null,\n): InteractionCondition[] {\n const conditions = trigger?.conditions\n if (Array.isArray(conditions)) return conditions.filter(Boolean)\n return trigger?.condition ? [trigger.condition] : []\n}\n\n/**\n * Evaluates a trigger's condition list with its combinator (AGL-565):\n * `and` (the default) requires every condition to pass, `or` any one.\n * Per-condition semantics are unchanged from AGL-557\n * (`evaluateTriggerCondition`), and an empty list always passes — which\n * covers every pre-565 doc without conditions.\n */\nexport function evaluateTriggerConditions(\n trigger:\n | Pick<InteractionTrigger, 'condition' | 'conditions' | 'combinator'>\n | undefined\n | null,\n payload: Record<string, unknown>,\n): boolean {\n const conditions = normalizeTriggerConditions(trigger)\n if (!conditions.length) return true\n return trigger?.combinator === 'or'\n ? conditions.some((condition) =>\n evaluateTriggerCondition(condition, payload),\n )\n : conditions.every((condition) =>\n evaluateTriggerCondition(condition, payload),\n )\n}\n\nexport interface InteractionTrigger {\n /**\n * Event the interaction listens for: a HOST_EVENT_TYPE (server-emitted), a\n * SITE_EVENT_TYPE (client-emitted, AGL-256), or a custom name fired by\n * another automation's `customEvent` step.\n */\n event: string\n /** Optional expression over the payload; runs only when truthy. */\n filter?: string\n /** Optional structured payload condition (AGL-557); null clears it. */\n condition?: InteractionCondition | null\n /**\n * Chained payload conditions (AGL-565), combined with `combinator`.\n * When present this list wins over the legacy single `condition`\n * (see `normalizeTriggerConditions`); null clears it.\n */\n conditions?: InteractionCondition[] | null\n /** How the chained conditions combine (AGL-565); default `and`. */\n combinator?: TriggerCombinator | null\n /** CSS selector for element-scoped site events (click/visible/scroll-to). */\n selector?: string\n /** Percent 0-100 (scrollDepth) or seconds (timeOnPage). */\n threshold?: number\n /** Path pattern the site event listens on (overlay glob rules); empty = all. */\n pathPattern?: string\n /**\n * Fire at most once per visitor (AGL-266, localStorage-keyed) instead\n * of once per pageview. Site events only.\n */\n oncePerVisitor?: boolean\n /**\n * Fire at most once per browser session (AGL-274,\n * sessionStorage-keyed). Site events only.\n */\n oncePerSession?: boolean\n /**\n * Minimum minutes between fires for the same visitor (AGL-274,\n * localStorage timestamp). Site events only; ignored when\n * `oncePerVisitor` is set.\n */\n cooldownMinutes?: number\n /**\n * Fire on EVERY occurrence instead of once per pageview (AGL-562) —\n * required for repeatable UI choreography (menu toggles, drawer\n * open/close, hover show/hide). Site events only; the explicit\n * per-session/visitor/cooldown caps above still win when set.\n */\n everyTime?: boolean\n}\n\n/**\n * A per-STEP condition, and the difference between an automation and a flow.\n *\n * `InteractionTrigger.conditions` gates the whole interaction: every step runs or\n * none does. That is enough for \"when a form is submitted, do these three\n * things\" and not enough for anything with a shape — \"wait three days, then,\n * only if they have not ordered, send the reminder\" needs the condition to\n * belong to the step rather than to the run.\n *\n * Same clause type and the same combinator as the trigger's, evaluated\n * against the same scope, so an author learns one condition editor and the\n * two can never disagree about what `contains` means.\n */\nexport interface InteractionStepGuard {\n conditions: InteractionCondition[]\n /** How the clauses combine; default `and`, as on the trigger. */\n combinator?: TriggerCombinator | null\n}\n\n/**\n * Evaluates a step's own guard, with the trigger's semantics.\n *\n * An absent or clause-less guard passes, exactly as an absent trigger\n * condition does — so every step written before guards existed keeps running.\n */\nexport function evaluateStepGuard(\n guard: InteractionStepGuard | undefined | null,\n scope: Record<string, unknown>,\n): boolean {\n const conditions = (guard?.conditions ?? []).filter(Boolean)\n if (!conditions.length) return true\n return guard?.combinator === 'or'\n ? conditions.some((condition) => evaluateTriggerCondition(condition, scope))\n : conditions.every((condition) => evaluateTriggerCondition(condition, scope))\n}\n\n/**\n * The steps the platform itself carries out, in the visitor's browser (AGL-257).\n *\n * Every other step an interaction may hold is a plugin's, known here only as\n * {@link InteractionStepBase}.\n */\nexport type ClientInteractionStep = (\n | {\n type: 'siteAlert'\n message: string\n severity?: 'info' | 'success' | 'warning' | 'error'\n }\n // Client-side UI steps (AGL-257): run in the visitor's page by the\n // tenant automations engine; the server executor skips them.\n | { type: 'showOverlay'; overlayId?: string; overlayName?: string }\n | { type: 'stickyNav'; selector?: string }\n // Selectors accept CSS or a node id via [data-node-id=\"…\"] — the\n // besigner element picker emits the latter (AGL-314/319).\n | { type: 'addClass'; selector: string; className: string }\n | { type: 'removeClass'; selector: string; className: string }\n | { type: 'toggleClass'; selector: string; className: string }\n // Element show/hide choreography (AGL-562): sugar over the shared\n // hidden class (see element-ui.ts) so authors never type class names.\n // The besigner target picker emits the node's stable data-aglyn\n // selector; any CSS selector works.\n // Element visibility (AGL-562) with menu-grade choreography (AGL-589):\n // `delayMs` defers the change and a later visibility step on the same\n // selector cancels the pending one (the hover grace period), and\n // `dismissOn` self-dismisses a shown element on Escape / outside click.\n | {\n type: 'showElement'\n selector: string\n delayMs?: number\n dismissOn?: ElementDismissOption[]\n }\n | { type: 'hideElement'; selector: string; delayMs?: number }\n | {\n type: 'toggleElement'\n selector: string\n delayMs?: number\n dismissOn?: ElementDismissOption[]\n }\n // Drawer commands (AGL-562): delivered to muiDrawer instances over the\n // window event bus keyed by node id; an empty target addresses the\n // page's first drawer.\n // Attribute steps (AGL-2546): the semantics half of a hand-built\n // disclosure. `showElement` opens the panel; these are what let it say so.\n //\n // `selector`, deliberately, and NOT a new targeting field. Two mechanisms\n // key off that exact name: `regraftStepSelectors` rewrites it to the\n // grafted `cmp__{instance}__{id}` when an interaction is authored inside a\n // reusable component, and the runtime wraps it in `expandLeafSelector`.\n // The menu steps took `menuNodeId` instead, get neither, and compensate at\n // run time — one concept should not have three targeting models.\n //\n // Two verbs rather than one with an empty value: `\"\"` does not mean\n // \"absent\" uniformly. `hidden=\"\"` is true, `aria-expanded=\"\"` is invalid\n // and discarded, `data-x=\"\"` is a real value an attribute selector still\n // matches. Overloading it would make the step's meaning depend on which\n // attribute the author picked.\n | { type: 'setAttribute'; selector: string; name: string; value: string }\n | { type: 'removeAttribute'; selector: string; name: string }\n // Steps that act on ONE element (AGL-2867): the first rendered match of\n // `selector`, which is spelled `selector` for the reason the attribute\n // steps give above. `scrollTo` rather than `scrollToElement`, because that\n // name is already a TRIGGER — \"the visitor scrolled to the element\" — and\n // one word meaning both directions would be read wrong by every reader of a\n // stored document.\n | {\n type: 'scrollTo'\n selector: string\n /** Absent means `smooth`. A visitor who asks for reduced motion is always scrolled instantly. */\n behavior?: ScrollToBehavior\n /** Pixels left above the target, so a sticky header does not cover it. Absent means 0. */\n offsetPx?: number\n }\n // Presses a Video element's poster from outside it: the same press, so\n // the lightbox opens and plays, or the film plays in place of the poster.\n | { type: 'playVideo'; selector: string }\n | { type: 'openDrawer'; drawerNodeId?: string }\n | { type: 'closeDrawer'; drawerNodeId?: string }\n | { type: 'toggleDrawer'; drawerNodeId?: string }\n // Menu commands (AGL-568): the drawer pattern for Dropdown/Mega Menu\n // elements — delivered over their own window event bus keyed by node\n // id; an empty target addresses the page's first menu. Hover-triggered\n // opens carry a hover flag so the menu closes on pointer leave.\n | { type: 'openMenu'; menuNodeId?: string }\n | { type: 'closeMenu'; menuNodeId?: string }\n | { type: 'toggleMenu'; menuNodeId?: string }\n | { type: 'showHtml'; html: string }\n | { type: 'runJs'; code: string }\n // Screen targets are rename-safe (AGL-339): the tenant resolves the\n // screen id against the host routing map at run time; `url` is the\n // external/manual fallback.\n | { type: 'redirect'; url?: string; screenId?: string }\n | {\n type: 'trackGaEvent'\n eventName: string\n params?: Record<string, string>\n }\n) & {\n /** See {@link InteractionStepGuard}. Absent means the step always runs. */\n when?: InteractionStepGuard | null\n}\n\nexport type ClientInteractionStepType = ClientInteractionStep['type']\n\n/**\n * Any step, as the platform holds it: a type, the guard every step may carry,\n * and fields the platform does not read. A plugin's own step types its other\n * fields itself.\n */\nexport interface InteractionStepBase {\n type: string\n when?: InteractionStepGuard | null\n [field: string]: unknown\n}\n\n/**\n * An interaction, as `hosts/{hostId}/actions/{id}` and an element's own\n * `interactions` hold it.\n *\n * `Step` is the platform's client vocabulary unless a reader names more: the\n * workflows plugin reads the same documents with its server steps added, and\n * a step from a plugin this reader does not know is still carried, untyped,\n * in the list.\n */\nexport interface SiteInteraction<Step extends InteractionStepBase = ClientInteractionStep> {\n name: string\n trigger: InteractionTrigger\n steps: Step[]\n /** A disabled interaction never runs; a new one defaults enabled. */\n enabled?: boolean\n /**\n * The recipe this interaction was installed from or begun as (AGL-2639) —\n * one a plugin offers through `plugin-manager/interaction-recipes` — or\n * `null` for one begun blank. Absent on a document from before the stamp\n * existed: read it through `interactionRecipeStamp`, which keeps the three\n * cases apart.\n */\n recipe?: string | null\n}\n\n/** Steps the tenant page runtime executes client-side (AGL-257). */\nexport const CLIENT_ACTION_STEP_TYPES: ReadonlySet<string> =\n new Set<ClientInteractionStepType>([\n 'showOverlay',\n 'stickyNav',\n 'addClass',\n 'removeClass',\n 'toggleClass',\n 'showElement',\n 'hideElement',\n 'toggleElement',\n 'openDrawer',\n 'closeDrawer',\n 'toggleDrawer',\n 'openMenu',\n 'closeMenu',\n 'toggleMenu',\n 'setAttribute',\n 'removeAttribute',\n 'scrollTo',\n 'playVideo',\n 'showHtml',\n 'runJs',\n 'redirect',\n 'trackGaEvent',\n 'siteAlert',\n ] as const)\n\nexport function isClientActionStep(step: InteractionStepBase): step is ClientInteractionStep {\n return CLIENT_ACTION_STEP_TYPES.has(step.type)\n}\n\n/**\n * How an editor, a run history and a drafter name each of the platform's\n * client steps. Every other step is named by the plugin that declares it\n * ({@link interactionStepLabel}).\n */\nexport const CLIENT_INTERACTION_STEP_LABELS: Readonly<Record<ClientInteractionStepType, string>> = {\n siteAlert: 'Show a site alert',\n showOverlay: 'Show a popup or bar',\n stickyNav: 'Make navigation sticky',\n addClass: 'Add a CSS class',\n toggleClass: 'Toggle a CSS class',\n removeClass: 'Remove a CSS class',\n showElement: 'Show an element',\n hideElement: 'Hide an element',\n toggleElement: 'Show/hide an element',\n openDrawer: 'Open a drawer',\n closeDrawer: 'Close a drawer',\n toggleDrawer: 'Open/close a drawer',\n openMenu: 'Open a menu',\n closeMenu: 'Close a menu',\n toggleMenu: 'Open/close a menu',\n setAttribute: 'Set an ARIA or data attribute',\n removeAttribute: 'Remove an ARIA or data attribute',\n scrollTo: 'Scroll to element',\n playVideo: 'Play a video',\n showHtml: 'Show custom HTML',\n runJs: 'Run custom JS (Business)',\n redirect: 'Redirect the visitor',\n trackGaEvent: 'Track an analytics event',\n}\n\n/**\n * The client steps' fields a person types words into, and what a sentence\n * calls each: where a drafted interaction may leave a placeholder\n * (`draft-placeholders.ts`). A client step absent here holds none — a\n * selector, a class name, HTML or a script uses brackets as syntax.\n */\nconst CLIENT_INTERACTION_STEP_TYPED_FIELDS: Readonly<\n Partial<Record<ClientInteractionStepType, readonly InteractionStepTypedField[]>>\n> = {\n showOverlay: [{ key: 'overlayName', names: 'the popup or bar' }],\n siteAlert: [{ key: 'message', names: 'the message' }],\n}\n\n/**\n * Basic presentational interactions (AGL-577): the subset of client steps\n * that are pure DOM choreography — menu/drawer open-close, element\n * show/hide, class toggles, sticky nav, navigation, and client-only\n * alerts. They carry NO server cost and touch NO data, so they run on\n * every plan (the `interactions` feature, on by default everywhere).\n *\n * The powerful client steps stay behind the `actions` entitlement:\n * `showOverlay` (marketing overlays), `showHtml` (arbitrary HTML\n * injection), and `trackGaEvent` (analytics). `runJs` keeps its own\n * higher `webhooks` (Business) gate. A step that is not a client step is\n * the server's, and is re-checked against `actions` where it runs.\n */\nexport const BASIC_CLIENT_ACTION_STEP_TYPES: ReadonlySet<string> =\n new Set<ClientInteractionStepType>([\n 'stickyNav',\n 'addClass',\n 'removeClass',\n 'toggleClass',\n 'showElement',\n 'hideElement',\n 'toggleElement',\n 'openDrawer',\n 'closeDrawer',\n 'toggleDrawer',\n 'openMenu',\n 'closeMenu',\n 'toggleMenu',\n 'setAttribute',\n 'removeAttribute',\n // Moving the page and pressing a player the author placed: no data, no\n // server, and nothing a Video element's own poster does not already do\n // on every plan.\n 'scrollTo',\n 'playVideo',\n 'redirect',\n 'siteAlert',\n ] as const)\n\n/** A client step available on all plans (no `actions` entitlement). */\nexport function isBasicClientActionStep(step: InteractionStepBase): boolean {\n return BASIC_CLIENT_ACTION_STEP_TYPES.has(step.type)\n}\n\n/**\n * Whether a client step may run for the given entitlement tier (AGL-577).\n * This is the single source of truth the page enricher uses to trim the\n * client-automation payload per plan:\n *\n * - Basic presentational steps → always (every plan).\n * - `runJs` → `allowJs` (Business `webhooks` tier).\n * - Remaining advanced client steps (overlay / showHtml / analytics) →\n * `actionsEntitled` (`actions` tier).\n *\n * Server steps are not client steps and always return false here; they\n * are dispatched and re-authorized server-side.\n */\nexport function isClientStepEntitled(\n step: InteractionStepBase,\n entitlements: { actionsEntitled: boolean; allowJs: boolean },\n): boolean {\n if (!isClientActionStep(step)) return false\n if (isBasicClientActionStep(step)) return true\n if (step.type === 'runJs') return entitlements.allowJs\n return entitlements.actionsEntitled\n}\n\n/** The longest step list an interaction may hold. */\nexport const ACTION_MAX_STEPS = 10\n\n/**\n * Cap on the parameters one `trackGaEvent` step may carry (AGL-1587).\n *\n * Not a storage concern. These pairs are the least trustworthy analytics\n * input on the platform — both halves are typed by a site author and land in\n * that site's own GA4 property — and a bounded list is one an author can read\n * back in full before publishing, which is the only review a parameter ever\n * gets. GA4 caps custom parameters per event as well and drops the overflow\n * without saying so, so an unbounded editor would let an author configure\n * parameters that are never delivered and never explained. Ten, the same\n * ceiling {@link ACTION_MAX_STEPS} puts on the step list itself.\n */\nexport const ACTION_MAX_EVENT_PARAMS = 10\n\n/**\n * Longest analytics parameter NAME GA4 accepts; it drops a longer one on\n * arrival, so a name over this is silence rather than a truncated dimension.\n * The value half is capped by `ANALYTICS_PARAM_MAX_LENGTH`, which truncates\n * instead of dropping.\n */\nexport const ACTION_EVENT_PARAM_NAME_MAX_LENGTH = 40\n\n/** Self-dismiss triggers for shown elements (AGL-589). */\nexport const ELEMENT_DISMISS_OPTIONS = ['escape', 'outsideClick'] as const\nexport type ElementDismissOption = (typeof ELEMENT_DISMISS_OPTIONS)[number]\n/** Visibility grace-delay ceiling — enough for hover travel, no dead UIs. */\nexport const ELEMENT_VISIBILITY_MAX_DELAY_MS = 5000\n\n/**\n * How a `scrollTo` step moves the page. Stored in documents; never rename a\n * value. `instant` rather than the DOM's `auto`, because `auto` defers to the\n * site's CSS `scroll-behavior` — a site that sets `smooth` there would animate\n * a scroll the author asked to be instant.\n */\nexport const SCROLL_TO_BEHAVIORS = ['smooth', 'instant'] as const\nexport type ScrollToBehavior = (typeof SCROLL_TO_BEHAVIORS)[number]\n\n/**\n * The largest offset a `scrollTo` step may leave above its target.\n *\n * The offset exists to clear a sticky header, which is tens of pixels and\n * rarely more than a few hundred with an announcement bar stacked on it. A\n * thousand leaves room for any header while still refusing the extra digit\n * that would park the target off the bottom of the screen.\n */\nexport const SCROLL_TO_MAX_OFFSET_PX = 1000\n\n/** Custom event names: short, no collision with built-ins. */\nexport const CUSTOM_EVENT_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{1,39}$/\n\n/**\n * The attribute names an interaction may write (AGL-2546).\n *\n * An unrestricted attribute setter is a self-XSS vector authored through the\n * interactions UI: it reaches `href`, `src`, `onclick`, `formaction` and\n * `style`, and on a site with collaborators the author and the victim need\n * not be the same person. `aria-*` and `data-*` carry the entire\n * accessibility use case and none of that exposure.\n *\n * Deliberately NOT solved by an entitlement gate. `runJs` is Business-gated\n * because it is dangerous at any tier; this has to work on every plan,\n * because a screen-reader user's access to a menu is not a paid feature.\n */\nexport const INTERACTION_ATTRIBUTE_NAME_PATTERN = /^(?:aria|data)-[a-z][a-z0-9-]*$/\n\n/**\n * Whether an interaction is allowed to write this attribute name.\n *\n * Case-insensitive because HTML attribute names are, and a rejected name is\n * a no-op rather than an error: a step that cannot run must not break the\n * steps after it in the same interaction.\n */\nexport function isInteractionAttributeAllowed(name: unknown): boolean {\n return (\n typeof name === 'string' &&\n INTERACTION_ATTRIBUTE_NAME_PATTERN.test(name.trim().toLowerCase())\n )\n}\n\n/**\n * An interaction step a plugin declares under `interactionSteps` in\n * `plugins.config.json`, compiled into the first-party catalog, so the\n * interaction builder can offer it and every validator can check it without\n * the plugin loaded.\n *\n * A step may be the plugin's own (a server step the plugin runs) or a\n * platform client step whose target is a record the plugin keeps. Either way\n * the declaration says how the builder names it and, for a step that PICKS a\n * record, where the builder lists the records from and which of the step's\n * fields hold the pick.\n */\nexport interface InteractionStepDeclaration {\n /** The plugin that declared it. */\n pluginId: string\n /** The stored step `type`. */\n type: string\n /** How the builder's step picker names it: \"Run a workflow\". */\n label: string\n /** The record this step acts on, picked in the builder. */\n picks?: {\n /** The site collection the records are listed from — one the plugin declares it owns. */\n collection: string\n /** How many records the builder lists. */\n limit: number\n /** The step field holding the picked record's id (rename-safe). */\n idField: string\n /** The step field holding its name at the time it was picked (display only). */\n nameField: string\n /** The picker's label: \"Workflow\". */\n label: string\n /** What a step with no pick is told, after its `Step N:` prefix. */\n missing: string\n }\n /**\n * `false` for a step only an automation holds — one that acts on the server\n * after the page that started it has gone, such as an email or a wait — so\n * the besigner's interaction builder does not offer it. Absent means offered.\n */\n offered?: false\n /** For a step that SUSPENDS the run that reaches it: see {@link InteractionStepHolds}. */\n holds?: InteractionStepHolds\n /** The step's fields a person types words into, in the order a sentence lists them. */\n typedFields?: readonly InteractionStepTypedField[]\n}\n\n/**\n * A step that suspends the run reaching it, to be continued later from a\n * beat: the whole minutes it may hold, and — where a hold can end on the\n * clock rather than on what it waits for — the scope field the continued run\n * carries to say it ran out of time. A visitor's page never runs a step past\n * one ({@link interactionStepsForClient}): everything after it belongs to a\n * run that has not happened yet.\n */\nexport interface InteractionStepHolds {\n minMinutes: number\n maxMinutes: number\n timeoutField?: string\n}\n\n/** One field of a step a person types words into, and what a sentence calls it: \"the list\". */\nexport interface InteractionStepTypedField {\n key: string\n names: string\n}\n\n/** Every step the first-party plugins declare, in `plugins.config.json` order. */\nexport function declaredInteractionSteps(): readonly InteractionStepDeclaration[] {\n return PLUGIN_INTERACTION_STEPS_DECLARED\n}\n\n/** The declared step of this type, or `null` for one no plugin declares. */\nexport function declaredInteractionStep(\n type: string,\n declarations: readonly InteractionStepDeclaration[] = PLUGIN_INTERACTION_STEPS_DECLARED,\n): InteractionStepDeclaration | null {\n return declarations.find((declaration) => declaration.type === type) ?? null\n}\n\n/**\n * How a step type reads in an editor, a run history and a drafter's words:\n * the platform's own label for a client step, else the label the declaring\n * plugin gave it; `null` for a type nobody declares.\n */\nexport function interactionStepLabel(\n type: string,\n declarations: readonly InteractionStepDeclaration[] = PLUGIN_INTERACTION_STEPS_DECLARED,\n): string | null {\n return (\n CLIENT_INTERACTION_STEP_LABELS[type as ClientInteractionStepType] ??\n declaredInteractionStep(type, declarations)?.label ??\n null\n )\n}\n\n/** The hold a step type declares, or `null` for one that never suspends a run. */\nexport function interactionStepHolds(\n type: string,\n declarations: readonly InteractionStepDeclaration[] = PLUGIN_INTERACTION_STEPS_DECLARED,\n): InteractionStepHolds | null {\n return declaredInteractionStep(type, declarations)?.holds ?? null\n}\n\n/** The fields of a step type a person types words into: the platform's, else the declaring plugin's. */\nexport function interactionStepTypedFields(\n type: string,\n declarations: readonly InteractionStepDeclaration[] = PLUGIN_INTERACTION_STEPS_DECLARED,\n): readonly InteractionStepTypedField[] {\n return (\n CLIENT_INTERACTION_STEP_TYPED_FIELDS[type as ClientInteractionStepType] ??\n declaredInteractionStep(type, declarations)?.typedFields ??\n []\n )\n}\n\n/**\n * The step list as the visitor's browser may see it: cut at the first step\n * that suspends the run.\n *\n * A client step AFTER a hold must never reach the page. The page runs its\n * slice of the list the moment the trigger fires, so shipping the whole list\n * would make \"wait three days, then show the popup\" show the popup at once —\n * the delay would appear to work on the server, be ignored in the browser,\n * and the two halves of one authored flow would disagree about when it\n * happened.\n *\n * Truncating rather than filtering: everything past the first hold belongs to\n * a run that has not happened yet, whichever side would have executed it.\n */\nexport function interactionStepsForClient<Step extends InteractionStepBase>(\n steps: readonly Step[] | undefined | null,\n declarations: readonly InteractionStepDeclaration[] = PLUGIN_INTERACTION_STEPS_DECLARED,\n): Step[] {\n const list = steps ?? []\n const holdAt = list.findIndex((step) => interactionStepHolds(step.type, declarations) !== null)\n return [...(holdAt < 0 ? list : list.slice(0, holdAt))]\n}\n\nexport interface ValidateInteractionOptions {\n /**\n * One step's checks that belong to its owner — a plugin's own step types.\n * Runs after the step's guard is checked; its answer, when not `null`, is\n * the interaction's.\n */\n validateStep?: (step: InteractionStepBase, label: string) => string | null\n /** The declared steps; the compiled first-party catalog unless a spec says otherwise. */\n declarations?: readonly InteractionStepDeclaration[]\n}\n\n/**\n * Validates an interaction's shape; returns a human-readable error or null.\n * Every surface that writes one shares this, so a bad step never persists or\n * runs.\n *\n * The platform checks what is its own: the name, the recipe stamp, the\n * trigger and its conditions, the step count, each step's guard, its client\n * steps, and the pick of every declared step that picks a record. A step it\n * does not know is left to its owner, through `validateStep`.\n */\nexport function validateInteraction(\n interaction: SiteInteraction<InteractionStepBase>,\n options: ValidateInteractionOptions = {},\n): string | null {\n if (!interaction.name?.trim()) return 'Name the action'\n // A stamp is provenance, and provenance naming a recipe that does not\n // exist is a document nothing can read back; `null` and absent both pass.\n if (interaction.recipe != null && !isKnownInteractionRecipe(interaction.recipe)) {\n return 'Unknown recipe'\n }\n const event = interaction.trigger?.event?.trim() ?? ''\n if (!event) return 'Pick a trigger event'\n // A server event or a custom one fired by another step is, like a site\n // event, a name; every built-in server event fits the custom pattern.\n if (!isSiteEventType(event) && !CUSTOM_EVENT_PATTERN.test(event)) {\n return 'Custom event names are 2–40 letters, digits, dashes'\n }\n // Site-event trigger config (AGL-256; hover events AGL-562).\n if (\n (ELEMENT_SCOPED_SITE_EVENTS as readonly string[]).includes(event) &&\n !interaction.trigger?.selector?.trim()\n ) {\n return 'This trigger needs a CSS selector'\n }\n if (\n ['scrollDepth', 'timeOnPage'].includes(event) &&\n !(Number(interaction.trigger?.threshold) > 0)\n ) {\n return event === 'scrollDepth'\n ? 'Set the scroll percentage (1–100)'\n : 'Set the seconds on page'\n }\n // Frequency caps (AGL-274).\n if (\n interaction.trigger?.cooldownMinutes != null &&\n !(Number(interaction.trigger.cooldownMinutes) >= 1)\n ) {\n return 'Cooldown must be at least 1 minute'\n }\n // Structured payload conditions (AGL-557; chained AGL-565).\n const combinator = interaction.trigger?.combinator\n if (combinator != null && !TRIGGER_COMBINATORS.includes(combinator)) {\n return 'Combine conditions with AND or OR'\n }\n const conditions = normalizeTriggerConditions(interaction.trigger)\n if (conditions.length > ACTION_MAX_CONDITIONS) {\n return `Conditions are capped at ${ACTION_MAX_CONDITIONS}`\n }\n for (const [index, condition] of conditions.entries()) {\n // Single-condition messages stay exactly as AGL-557 shipped them;\n // the row marker only appears once there are rows to tell apart.\n const where = conditions.length > 1 ? ` (condition ${index + 1})` : ''\n if (!TRIGGER_CONDITION_OPS.includes(condition.op)) {\n return `Pick a condition operator${where}`\n }\n if (!condition.field?.trim()) {\n return `Name the field the condition checks${where}`\n }\n if (condition.op !== 'notEmpty' && !condition.value?.trim()) {\n return `Enter the value the condition compares against${where}`\n }\n }\n const steps = interaction.steps ?? []\n if (!steps.length) return 'Add at least one step'\n if (steps.length > ACTION_MAX_STEPS) {\n return `Actions are capped at ${ACTION_MAX_STEPS} steps`\n }\n for (const [index, step] of steps.entries()) {\n const label = `Step ${index + 1}`\n // A step's own guard, checked with the same messages the trigger's\n // clauses get — one condition editor, one set of complaints about it.\n const guardClauses = (step.when?.conditions ?? []).filter(Boolean)\n if (guardClauses.length > ACTION_MAX_CONDITIONS) {\n return `${label}: conditions are capped at ${ACTION_MAX_CONDITIONS}`\n }\n if (\n step.when?.combinator != null &&\n !TRIGGER_COMBINATORS.includes(step.when.combinator)\n ) {\n return `${label}: combine conditions with AND or OR`\n }\n for (const clause of guardClauses) {\n if (!TRIGGER_CONDITION_OPS.includes(clause.op)) {\n return `${label}: pick a condition operator`\n }\n if (!clause.field?.trim()) {\n return `${label}: name the field the condition checks`\n }\n if (clause.op !== 'notEmpty' && !clause.value?.trim()) {\n return `${label}: enter the value the condition compares against`\n }\n }\n const owned = options.validateStep?.(step, label)\n if (owned) return owned\n const declared = declaredInteractionStep(step.type, options.declarations)\n if (declared?.picks) {\n const id = step[declared.picks.idField]\n const name = step[declared.picks.nameField]\n if (\n !(typeof id === 'string' && id.trim()) &&\n !(typeof name === 'string' && name.trim())\n ) {\n return `${label}: ${declared.picks.missing}`\n }\n }\n if (isClientActionStep(step)) {\n const problem = clientStepProblem(step, label)\n if (problem) return problem\n }\n }\n return null\n}\n\n/** A client step's own complaint, or null. The step's guard is checked by the caller. */\nfunction clientStepProblem(step: ClientInteractionStep, label: string): string | null {\n if (step.type === 'siteAlert' && !step.message?.trim()) {\n return `${label}: enter the alert message`\n }\n if (\n step.type === 'addClass' ||\n step.type === 'removeClass' ||\n step.type === 'toggleClass'\n ) {\n if (!step.selector?.trim()) return `${label}: enter a CSS selector`\n if (!step.className?.trim()) return `${label}: enter the class name`\n }\n // Element show/hide steps (AGL-562) always target a selector; drawer\n // (AGL-562) and menu (AGL-568) commands may omit the target (the\n // page's first drawer/menu answers).\n if (\n (step.type === 'showElement' ||\n step.type === 'hideElement' ||\n step.type === 'toggleElement') &&\n !step.selector?.trim()\n ) {\n return `${label}: pick the element to show or hide`\n }\n if (\n step.type === 'showElement' ||\n step.type === 'hideElement' ||\n step.type === 'toggleElement'\n ) {\n // Choreography options (AGL-589).\n const delay = (step as { delayMs?: unknown }).delayMs\n if (\n delay != null &&\n !(\n Number.isInteger(delay) &&\n (delay as number) >= 0 &&\n (delay as number) <= ELEMENT_VISIBILITY_MAX_DELAY_MS\n )\n ) {\n return `${label}: delay must be 0–${ELEMENT_VISIBILITY_MAX_DELAY_MS}ms`\n }\n const dismiss = (step as { dismissOn?: unknown }).dismissOn\n if (\n dismiss != null &&\n !(\n Array.isArray(dismiss) &&\n dismiss.every((option) =>\n (ELEMENT_DISMISS_OPTIONS as readonly string[]).includes(\n option as string,\n ),\n )\n )\n ) {\n return `${label}: dismiss options are escape and outsideClick`\n }\n }\n // One-target steps (AGL-2867). The runtime does nothing for a target it\n // cannot find, so a missing one is refused here, where it can be fixed.\n if (step.type === 'scrollTo') {\n if (!step.selector?.trim()) {\n return `${label}: pick the element to scroll to`\n }\n const behavior = (step as { behavior?: unknown }).behavior\n if (\n behavior != null &&\n !(SCROLL_TO_BEHAVIORS as readonly unknown[]).includes(behavior)\n ) {\n return `${label}: scroll smoothly or instantly`\n }\n const offset = (step as { offsetPx?: unknown }).offsetPx\n if (\n offset != null &&\n !(\n Number.isInteger(offset) &&\n (offset as number) >= 0 &&\n (offset as number) <= SCROLL_TO_MAX_OFFSET_PX\n )\n ) {\n return `${label}: the offset must be 0–${SCROLL_TO_MAX_OFFSET_PX}px`\n }\n }\n if (step.type === 'playVideo' && !step.selector?.trim()) {\n return `${label}: pick the video to play`\n }\n if (step.type === 'showHtml') {\n if (!step.html?.trim()) return `${label}: enter the HTML`\n // AGL-2486: the author-facing half of the runtime sanitizer, exactly as\n // the reserved analytics name below is the author-facing half of the\n // runtime refusal. `site-runtime.tsx` runs this HTML through\n // `sanitizeAuthorHtml` for a VISITOR, where the only thing it can do\n // with markup it refuses is drop it and say nothing — so an author who\n // pastes an embed snippet gets a step that runs, reports success, and\n // shows nothing. The refusal is reported HERE, where the person who\n // can fix it is looking, and it is the SAME function that will run at\n // render rather than a second description of its rules.\n const removals: AuthorHtmlRemoval[] = []\n const safe = sanitizeAuthorHtml(step.html, removals)\n if (!safe.trim()) {\n return `${label}: none of this HTML can be shown on a published page — ${removals[0]?.message ?? 'it is removed by the sanitizer.'}`\n }\n if (removals.length) {\n return `${label}: ${removals[0].message}${removals.length > 1 ? ` (+${removals.length - 1} more)` : ''}`\n }\n }\n if (step.type === 'runJs' && !step.code?.trim()) {\n return `${label}: enter the JavaScript`\n }\n if (step.type === 'redirect' && !step.url?.trim() && !step.screenId) {\n return `${label}: pick a page or enter the destination URL`\n }\n if (step.type === 'trackGaEvent') {\n if (!step.eventName?.trim()) {\n return `${label}: name the analytics event`\n }\n // AGL-1587: the author-facing half of the runtime refusal. The runtime\n // can only drop a bad name silently — it is executing for a VISITOR —\n // so the name is checked HERE, where the person who can fix it is.\n const resolved = resolveAuthoredEventName(step.eventName)\n if (resolved.reason === 'reserved') {\n return `${label}: \"${step.eventName.trim()}\" is a reserved analytics event name — pick another`\n }\n if (!resolved.name) {\n return `${label}: the analytics event name must start with a letter`\n }\n const params = Object.entries(step.params ?? {})\n if (params.length > ACTION_MAX_EVENT_PARAMS) {\n return `${label}: analytics parameters are capped at ${ACTION_MAX_EVENT_PARAMS}`\n }\n for (const [key, value] of params) {\n if (!key.trim()) return `${label}: name every analytics parameter`\n if (key.trim().length > ACTION_EVENT_PARAM_NAME_MAX_LENGTH) {\n return `${label}: the \"${key.trim().slice(0, 16)}…\" parameter name is over ${ACTION_EVENT_PARAM_NAME_MAX_LENGTH} characters, which GA4 drops`\n }\n if (!String(value ?? '').trim()) {\n return `${label}: enter a value for the \"${key.trim()}\" parameter`\n }\n }\n // The author-facing half of the runtime sanitizer, the same shape the\n // reserved name above and the `showHtml` check take. `trackAuthoredEvent`\n // strips a parameter that can carry personal data and says nothing about\n // it, because it is executing for a VISITOR — so an author who binds a\n // form field into a parameter gets a step that runs, reports success,\n // and delivers an event missing the dimension it was created for. The\n // strip is reported HERE, by running the REAL `sanitizeEventParams`\n // rather than a second description of its rules, so the two can never\n // disagree about which parameter survives.\n const kept = sanitizeEventParams(step.params ?? undefined)\n const stripped = params.find(([key]) => !(key in kept))\n if (stripped) {\n return `${label}: the \"${stripped[0]}\" parameter is never sent — an analytics parameter must not carry a visitor's own details`\n }\n }\n return null\n}\n\n/**\n * An interaction as the document at `hosts/{hostId}/actions/{id}` holds it.\n *\n * Every optional trigger key is written OUT — a boolean cap as `false`, a\n * cleared list as `null` — because the editors save with a merge-set, and a\n * merge keeps whatever key the payload omits: a frequency cap switched off,\n * left out of the payload, would stay on. The legacy single `condition` is\n * always nulled; `conditions` has been canonical since the list shape arrived\n * and a document that carried both would have the reader pick. Everything\n * else is the interaction, unchanged.\n *\n * `recipe` rides along only when the interaction SAYS something about it: an\n * id or `null`. One that carries no stamp (an older document, edited and\n * saved again) keeps carrying none — see `interactionRecipeStamp` for why an\n * absent stamp must not become a `null` one.\n */\nexport type SiteInteractionDocument<Step extends InteractionStepBase = InteractionStepBase> =\n SiteInteraction<Step> & {\n trigger: InteractionTrigger & {\n oncePerVisitor: boolean\n oncePerSession: boolean\n cooldownMinutes: number | null\n everyTime: boolean\n condition: null\n conditions: InteractionCondition[] | null\n combinator: TriggerCombinator | null\n }\n enabled: boolean\n }\n\n/** The stored shape of an interaction: what every writer of `hosts/{hostId}/actions` saves. */\nexport function siteInteractionDocument<Step extends InteractionStepBase>(\n interaction: SiteInteraction<Step>,\n): SiteInteractionDocument<Step> {\n const { recipe, ...rest } = interaction\n const trigger = interaction.trigger\n return {\n ...rest,\n trigger: {\n ...trigger,\n oncePerVisitor: trigger.oncePerVisitor === true,\n oncePerSession: trigger.oncePerSession === true,\n cooldownMinutes:\n Number(trigger.cooldownMinutes) >= 1 ? Number(trigger.cooldownMinutes) : null,\n everyTime: trigger.everyTime === true,\n condition: null,\n conditions: trigger.conditions ?? null,\n combinator: trigger.combinator ?? null,\n },\n enabled: interaction.enabled !== false,\n ...(recipe !== undefined ? { recipe } : {}),\n }\n}\n\n/** Alert produced by a `siteAlert` step, surfaced to the emitting client. */\nexport interface SiteAlert {\n message: string\n severity: 'info' | 'success' | 'warning' | 'error'\n}\n\n/**\n * How many LIVE action documents one host may hold (AGL-2266).\n *\n * `hosts/{hostId}/actions` was in NO exclusion list in\n * `cloud/firebase-firestore.rules`, so the host catch-all's `allow create`\n * granted it to any editor, client-direct, on any plan — and the import\n * route's own table said so in as many words: *\"no `RESOURCES` entry and no\n * quota key anywhere; all three creators write the document client-direct.\"*\n * A free org could therefore mint unbounded Firestore documents from the\n * browser against a $0 subscription. Unbounded infrastructure, not a bypass of\n * anything we sell, which is `WEBHOOK_MAX_PER_HOST`'s (AGL-1360) and\n * `NON_PAGE_SCREEN_MAX_PER_HOST`'s (AGL-1399) shape exactly: a flat PLATFORM\n * cap with no `OrgEntitlements` key, no variation by plan, and nothing on the\n * price list to explain.\n *\n * ## Not gated on the `actions` entitlement, deliberately\n *\n * `actions` is a Pro feature and `interactions` is free, and BOTH write this\n * collection — the interaction builder and the besigner's preset wiring create\n * the same document type the Pro actions card does. Gating creation on the\n * paid flag would take element interactions away from every free and starter\n * site, which is a pricing change wearing a cap's clothes. The runtime already\n * checks the entitlement where it decides what may RUN\n * (`run-event-actions.ts`), which is the right place for it.\n *\n * ## Why 500\n *\n * Sized to the busiest plausible site rather than to today's data. An action\n * is authored per interactive element, so a heavy marketing site with dropdown\n * choreography on every section reaches tens; presets wire a handful at a\n * time. 500 is far past anything authoring produces and well short of what a\n * script in a loop wants, which is the only property a flat cap needs. Live\n * documents only — the interactions provider soft-deletes with `deletedAt`, so\n * counting tombstones would be AGL-1173's bug one collection over, where\n * removing an interaction never frees its slot.\n */\nexport const ACTIONS_MAX_PER_HOST = 500\n"],"names":["resolveAuthoredEventName","sanitizeEventParams","sanitizeAuthorHtml","PLUGIN_INTERACTION_STEPS_DECLARED","isKnownInteractionRecipe","SITE_EVENT_TYPES","ELEMENT_SCOPED_SITE_EVENTS","isSiteEventType","event","includes","TRIGGER_CONDITION_OPS","evaluateTriggerCondition","condition","payload","field","trim","raw","actual","String","toLowerCase","op","length","expected","value","TRIGGER_COMBINATORS","ACTION_MAX_CONDITIONS","normalizeTriggerConditions","trigger","conditions","Array","isArray","filter","Boolean","evaluateTriggerConditions","combinator","some","every","evaluateStepGuard","guard","scope","CLIENT_ACTION_STEP_TYPES","Set","isClientActionStep","step","has","type","CLIENT_INTERACTION_STEP_LABELS","siteAlert","showOverlay","stickyNav","addClass","toggleClass","removeClass","showElement","hideElement","toggleElement","openDrawer","closeDrawer","toggleDrawer","openMenu","closeMenu","toggleMenu","setAttribute","removeAttribute","scrollTo","playVideo","showHtml","runJs","redirect","trackGaEvent","CLIENT_INTERACTION_STEP_TYPED_FIELDS","key","names","BASIC_CLIENT_ACTION_STEP_TYPES","isBasicClientActionStep","isClientStepEntitled","entitlements","allowJs","actionsEntitled","ACTION_MAX_STEPS","ACTION_MAX_EVENT_PARAMS","ACTION_EVENT_PARAM_NAME_MAX_LENGTH","ELEMENT_DISMISS_OPTIONS","ELEMENT_VISIBILITY_MAX_DELAY_MS","SCROLL_TO_BEHAVIORS","SCROLL_TO_MAX_OFFSET_PX","CUSTOM_EVENT_PATTERN","INTERACTION_ATTRIBUTE_NAME_PATTERN","isInteractionAttributeAllowed","name","test","declaredInteractionSteps","declaredInteractionStep","declarations","find","declaration","interactionStepLabel","label","interactionStepHolds","holds","interactionStepTypedFields","typedFields","interactionStepsForClient","steps","list","holdAt","findIndex","slice","validateInteraction","interaction","options","recipe","selector","Number","threshold","cooldownMinutes","index","entries","where","guardClauses","when","clause","owned","validateStep","declared","picks","id","idField","nameField","missing","problem","clientStepProblem","message","className","delay","delayMs","isInteger","dismiss","dismissOn","option","behavior","offset","offsetPx","html","removals","safe","code","url","screenId","eventName","resolved","reason","params","Object","kept","undefined","stripped","siteInteractionDocument","rest","oncePerVisitor","oncePerSession","everyTime","enabled","ACTIONS_MAX_PER_HOST"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import type { NodeInteraction } from './node-interactions';
|
|
17
18
|
/**
|
|
18
19
|
* What a starter site is, and the kit its pages are built with (AGL-687,
|
|
19
20
|
* AGL-3080).
|
|
@@ -84,6 +85,12 @@ export type StarterNodeSpec = {
|
|
|
84
85
|
* the panel could show but no click could change or clear.
|
|
85
86
|
*/
|
|
86
87
|
sx?: Record<string, unknown>;
|
|
88
|
+
/**
|
|
89
|
+
* What the element does when a visitor acts on it, stored on the node as
|
|
90
|
+
* the interactions editor stores it (AGL-2867) — a button that scrolls to
|
|
91
|
+
* a section of its own page, for one.
|
|
92
|
+
*/
|
|
93
|
+
interactions?: NodeInteraction[];
|
|
87
94
|
children?: StarterNodeSpec[];
|
|
88
95
|
};
|
|
89
96
|
/** Builds the flat, persisted node map from a nested spec. */
|