@aglyn/plugins-marketing 1.0.0-beta.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +56 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/announcement-bar-card.component.d.ts +20 -0
  8. package/src/lib/components/announcement-bar-card.component.js +292 -0
  9. package/src/lib/components/announcement-bar-card.component.js.map +1 -0
  10. package/src/lib/components/campaign-composer.d.ts +84 -0
  11. package/src/lib/components/campaign-composer.js +1643 -0
  12. package/src/lib/components/campaign-composer.js.map +1 -0
  13. package/src/lib/components/campaign-conversions-card.d.ts +22 -0
  14. package/src/lib/components/campaign-conversions-card.js +642 -0
  15. package/src/lib/components/campaign-conversions-card.js.map +1 -0
  16. package/src/lib/components/campaign-detail-card.d.ts +55 -0
  17. package/src/lib/components/campaign-detail-card.js +771 -0
  18. package/src/lib/components/campaign-detail-card.js.map +1 -0
  19. package/src/lib/components/campaign-edit-drawer.d.ts +60 -0
  20. package/src/lib/components/campaign-edit-drawer.js +252 -0
  21. package/src/lib/components/campaign-edit-drawer.js.map +1 -0
  22. package/src/lib/components/campaign-email-zones.d.ts +111 -0
  23. package/src/lib/components/campaign-email-zones.js +54 -0
  24. package/src/lib/components/campaign-email-zones.js.map +1 -0
  25. package/src/lib/components/campaign-glance-card.component.d.ts +18 -0
  26. package/src/lib/components/campaign-glance-card.component.js +151 -0
  27. package/src/lib/components/campaign-glance-card.component.js.map +1 -0
  28. package/src/lib/components/campaign-members-section.d.ts +87 -0
  29. package/src/lib/components/campaign-members-section.js +499 -0
  30. package/src/lib/components/campaign-members-section.js.map +1 -0
  31. package/src/lib/components/campaign-reach-sections.d.ts +137 -0
  32. package/src/lib/components/campaign-reach-sections.js +694 -0
  33. package/src/lib/components/campaign-reach-sections.js.map +1 -0
  34. package/src/lib/components/campaign-report-card.d.ts +91 -0
  35. package/src/lib/components/campaign-report-card.js +667 -0
  36. package/src/lib/components/campaign-report-card.js.map +1 -0
  37. package/src/lib/components/campaign-test-send-drawer.d.ts +45 -0
  38. package/src/lib/components/campaign-test-send-drawer.js +238 -0
  39. package/src/lib/components/campaign-test-send-drawer.js.map +1 -0
  40. package/src/lib/components/campaigns-card.d.ts +31 -0
  41. package/src/lib/components/campaigns-card.js +667 -0
  42. package/src/lib/components/campaigns-card.js.map +1 -0
  43. package/src/lib/components/conversion-attribution.component.d.ts +62 -0
  44. package/src/lib/components/conversion-attribution.component.js +127 -0
  45. package/src/lib/components/conversion-attribution.component.js.map +1 -0
  46. package/src/lib/components/email-compose-card.d.ts +12 -0
  47. package/src/lib/components/email-compose-card.js +157 -0
  48. package/src/lib/components/email-compose-card.js.map +1 -0
  49. package/src/lib/components/email-detail.d.ts +40 -0
  50. package/src/lib/components/email-detail.js +1180 -0
  51. package/src/lib/components/email-detail.js.map +1 -0
  52. package/src/lib/components/email-edit-drawer.d.ts +54 -0
  53. package/src/lib/components/email-edit-drawer.js +172 -0
  54. package/src/lib/components/email-edit-drawer.js.map +1 -0
  55. package/src/lib/components/email-messages-widget.d.ts +22 -0
  56. package/src/lib/components/email-messages-widget.js +49 -0
  57. package/src/lib/components/email-messages-widget.js.map +1 -0
  58. package/src/lib/components/email-recipients-card.d.ts +50 -0
  59. package/src/lib/components/email-recipients-card.js +338 -0
  60. package/src/lib/components/email-recipients-card.js.map +1 -0
  61. package/src/lib/components/emails-list-card.d.ts +36 -0
  62. package/src/lib/components/emails-list-card.js +544 -0
  63. package/src/lib/components/emails-list-card.js.map +1 -0
  64. package/src/lib/components/experiment-zones.d.ts +78 -0
  65. package/src/lib/components/experiment-zones.js +65 -0
  66. package/src/lib/components/experiment-zones.js.map +1 -0
  67. package/src/lib/components/host-experiments-card.component.d.ts +17 -0
  68. package/src/lib/components/host-experiments-card.component.js +887 -0
  69. package/src/lib/components/host-experiments-card.component.js.map +1 -0
  70. package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
  71. package/src/lib/components/host-marketing-summary-card.component.js +161 -0
  72. package/src/lib/components/host-marketing-summary-card.component.js.map +1 -0
  73. package/src/lib/components/host-overlays-card.component.d.ts +19 -0
  74. package/src/lib/components/host-overlays-card.component.js +783 -0
  75. package/src/lib/components/host-overlays-card.component.js.map +1 -0
  76. package/src/lib/components/marketing-console-page.d.ts +28 -0
  77. package/src/lib/components/marketing-console-page.js +166 -0
  78. package/src/lib/components/marketing-console-page.js.map +1 -0
  79. package/src/lib/components/marketing-console-sections.d.ts +34 -0
  80. package/src/lib/components/marketing-console-sections.js +77 -0
  81. package/src/lib/components/marketing-console-sections.js.map +1 -0
  82. package/src/lib/components/overlay-stats-row.component.d.ts +15 -0
  83. package/src/lib/components/overlay-stats-row.component.js +78 -0
  84. package/src/lib/components/overlay-stats-row.component.js.map +1 -0
  85. package/src/lib/components/popup-card.component.d.ts +18 -0
  86. package/src/lib/components/popup-card.component.js +462 -0
  87. package/src/lib/components/popup-card.component.js.map +1 -0
  88. package/src/lib/components/record-attribution-widget.d.ts +35 -0
  89. package/src/lib/components/record-attribution-widget.js +33 -0
  90. package/src/lib/components/record-attribution-widget.js.map +1 -0
  91. package/src/lib/components/site-runtime.d.ts +9 -0
  92. package/src/lib/components/site-runtime.js +1081 -0
  93. package/src/lib/components/site-runtime.js.map +1 -0
  94. package/src/lib/components/use-campaign-send-api.d.ts +35 -0
  95. package/src/lib/components/use-campaign-send-api.js +99 -0
  96. package/src/lib/components/use-campaign-send-api.js.map +1 -0
  97. package/src/lib/components/use-campaign-topic-options.d.ts +20 -0
  98. package/src/lib/components/use-campaign-topic-options.js +44 -0
  99. package/src/lib/components/use-campaign-topic-options.js.map +1 -0
  100. package/src/lib/components/use-emails-hub-path.d.ts +39 -0
  101. package/src/lib/components/use-emails-hub-path.js +70 -0
  102. package/src/lib/components/use-emails-hub-path.js.map +1 -0
  103. package/src/lib/components/use-marketing-hub-path.d.ts +21 -0
  104. package/src/lib/components/use-marketing-hub-path.js +50 -0
  105. package/src/lib/components/use-marketing-hub-path.js.map +1 -0
  106. package/src/lib/components/use-overlay-copy-editor.d.ts +79 -0
  107. package/src/lib/components/use-overlay-copy-editor.js +110 -0
  108. package/src/lib/components/use-overlay-copy-editor.js.map +1 -0
  109. package/src/lib/constants/bundle-common.d.ts +8 -0
  110. package/src/lib/constants/bundle-common.js +9 -0
  111. package/src/lib/constants/bundle-common.js.map +1 -0
  112. package/src/lib/model/campaign-membership-figures.d.ts +109 -0
  113. package/src/lib/model/campaign-membership-figures.js +113 -0
  114. package/src/lib/model/campaign-membership-figures.js.map +1 -0
  115. package/src/lib/model/compile-client-automations.d.ts +65 -0
  116. package/src/lib/model/compile-client-automations.js +95 -0
  117. package/src/lib/model/compile-client-automations.js.map +1 -0
  118. package/src/lib/model/experiments.d.ts +130 -0
  119. package/src/lib/model/experiments.js +186 -0
  120. package/src/lib/model/experiments.js.map +1 -0
  121. package/src/lib/model/index.d.ts +26 -0
  122. package/src/lib/model/index.js +26 -0
  123. package/src/lib/model/index.js.map +1 -0
  124. package/src/lib/model/marketing-record-routes.d.ts +6 -0
  125. package/src/lib/model/marketing-record-routes.js +46 -0
  126. package/src/lib/model/marketing-record-routes.js.map +1 -0
  127. package/src/lib/model/overlay-copy.d.ts +62 -0
  128. package/src/lib/model/overlay-copy.js +78 -0
  129. package/src/lib/model/overlay-copy.js.map +1 -0
  130. package/src/lib/model/overlays.d.ts +113 -0
  131. package/src/lib/model/overlays.js +93 -0
  132. package/src/lib/model/overlays.js.map +1 -0
  133. package/src/lib/model/site-contract.d.ts +100 -0
  134. package/src/lib/model/site-contract.js +23 -0
  135. package/src/lib/model/site-contract.js.map +1 -0
  136. package/src/lib/plugin.d.ts +27 -0
  137. package/src/lib/plugin.js +168 -0
  138. package/src/lib/plugin.js.map +1 -0
  139. package/src/lib/preview-props.d.ts +31 -0
  140. package/src/lib/preview-props.js +54 -0
  141. package/src/lib/preview-props.js.map +1 -0
  142. package/src/lib/server/campaign-manage.d.ts +62 -0
  143. package/src/lib/server/campaign-manage.js +829 -0
  144. package/src/lib/server/campaign-manage.js.map +1 -0
  145. package/src/lib/server/campaign-process-scheduled.d.ts +26 -0
  146. package/src/lib/server/campaign-process-scheduled.js +198 -0
  147. package/src/lib/server/campaign-process-scheduled.js.map +1 -0
  148. package/src/lib/server/campaign-recipients.d.ts +55 -0
  149. package/src/lib/server/campaign-recipients.js +219 -0
  150. package/src/lib/server/campaign-recipients.js.map +1 -0
  151. package/src/lib/server/campaign-send.d.ts +487 -0
  152. package/src/lib/server/campaign-send.js +3157 -0
  153. package/src/lib/server/campaign-send.js.map +1 -0
  154. package/src/lib/server/email-campaign-reach.d.ts +174 -0
  155. package/src/lib/server/email-campaign-reach.js +254 -0
  156. package/src/lib/server/email-campaign-reach.js.map +1 -0
  157. package/src/lib/server/email-events.d.ts +27 -0
  158. package/src/lib/server/email-events.js +714 -0
  159. package/src/lib/server/email-events.js.map +1 -0
  160. package/src/lib/server/get-client-automations.d.ts +60 -0
  161. package/src/lib/server/get-client-automations.js +87 -0
  162. package/src/lib/server/get-client-automations.js.map +1 -0
  163. package/src/lib/server/get-overlays.d.ts +28 -0
  164. package/src/lib/server/get-overlays.js +65 -0
  165. package/src/lib/server/get-overlays.js.map +1 -0
  166. package/src/lib/server/get-screen-experiments.d.ts +42 -0
  167. package/src/lib/server/get-screen-experiments.js +82 -0
  168. package/src/lib/server/get-screen-experiments.js.map +1 -0
  169. package/src/lib/server/lists-materialize.d.ts +18 -0
  170. package/src/lib/server/lists-materialize.js +130 -0
  171. package/src/lib/server/lists-materialize.js.map +1 -0
  172. package/src/lib/server/marketing-figures.d.ts +37 -0
  173. package/src/lib/server/marketing-figures.js +248 -0
  174. package/src/lib/server/marketing-figures.js.map +1 -0
  175. package/src/lib/server/site-page-enricher.d.ts +26 -0
  176. package/src/lib/server/site-page-enricher.js +210 -0
  177. package/src/lib/server/site-page-enricher.js.map +1 -0
  178. package/src/lib/server.d.ts +20 -0
  179. package/src/lib/server.js +152 -0
  180. package/src/lib/server.js.map +1 -0
  181. package/src/lib/site.d.ts +22 -0
  182. package/src/lib/site.js +48 -0
  183. package/src/lib/site.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/compile-client-automations.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\n// Import the entitlement/event predicates from the context-free actions\n// subpath, NOT the `@aglyn/aglyn` barrel: this file is in the marketing model,\n// which the plugin's SERVER graph re-exports, and the full barrel pulls client\n// React contexts (createContext) that break the RSC server build (AGL-830).\nimport {\n hostActionStepsForClient,\n isClientActionStep,\n isClientStepEntitled,\n isSiteEventType,\n type HostAction,\n} from '@aglyn/aglyn/app-utils/actions'\nimport { overlayMatchesPath } from './overlays'\nimport type { ClientAutomation } from './site-contract'\n\n/** A raw `hosts/{hostId}/actions` doc paired with its id. */\nexport interface RawHostAction {\n id: string\n action: HostAction\n}\n\nexport interface CompileClientAutomationsOptions {\n /** Leading-slash page path for pathPattern targeting. */\n path: string\n /**\n * `actions` entitlement (AGL-577). Basic presentational steps\n * (menu/drawer/show-hide/class/nav/alert) always load; advanced client\n * steps (overlay/showHtml/analytics) and server steps drop when false.\n */\n actionsEntitled: boolean\n /** Business-tier gate for `runJs` steps (dropped when false). */\n allowJs: boolean\n /**\n * Ignore each action's `pathPattern` (AGL-830). The besigner Preview shows\n * a single composed screen and scopes relevance by selector match, so it\n * skips path filtering rather than resolve the composed path.\n */\n matchAllPaths?: boolean\n /**\n * Drop every automation that names a `pathPattern` (AGL-2511), for a\n * surface whose own path is unknown — the designed 404 body, served from a\n * per-host cache that no request path belongs to.\n *\n * The opposite of `matchAllPaths` and deliberately so: Preview knows the\n * page and only lacks its composed address, so it keeps path-scoped\n * automations; this caller cannot know the page at all, and running an\n * automation scoped to `/pricing` on whatever URL 404'd would be a guess\n * with a side effect. Interactions authored on nodes carry no pattern, so\n * a nav's hover choreography — the reason this surface is enriched — is\n * unaffected.\n */\n dropPathScoped?: boolean\n}\n\n/**\n * Pure action→client-automation mapping (AGL-256/577, extracted AGL-830):\n * the single place the trigger config + entitlement-trimmed client steps are\n * shaped. Both the server page-enricher (admin fetch) and the editor Preview\n * (client fetch) feed their loaded action docs through this, so live and\n * preview stay byte-for-byte consistent. No I/O, no firebase — safe on the\n * client. Malformed entries are skipped, never thrown.\n */\nexport function compileClientAutomations(\n actions: RawHostAction[],\n options: CompileClientAutomationsOptions,\n): ClientAutomation[] {\n const automations: ClientAutomation[] = []\n for (const { id, action } of actions) {\n if (\n (action as { deletedAt?: unknown }).deletedAt ||\n action.enabled === false\n ) {\n continue\n }\n const event = String(action.trigger?.event ?? '')\n if (!isSiteEventType(event)) continue\n const pathPattern = action.trigger?.pathPattern?.trim()\n if (pathPattern && options.dropPathScoped) continue\n if (\n !options.matchAllPaths &&\n pathPattern &&\n !overlayMatchesPath({ pathPatterns: [pathPattern] }, options.path)\n ) {\n continue\n }\n /*\n * TRUNCATED AT THE FIRST WAIT, before anything else is decided.\n *\n * The browser runs its slice of the list the moment the trigger fires, so\n * a client step sitting after a `wait` would execute at once — the popup\n * an author scheduled for three days later would appear immediately, the\n * delay would look correct on the server, and the two halves of one\n * authored flow would disagree about when they happened. Everything past\n * the first wait belongs to a run the job beat has not started yet.\n */\n const reachableSteps = hostActionStepsForClient(action.steps)\n // Step-level entitlement tiering (AGL-577): basic presentational steps\n // load on every plan; advanced client steps need `actions` and runJs\n // needs `webhooks` — see isClientStepEntitled.\n const clientSteps = reachableSteps.filter((step) =>\n isClientStepEntitled(step, {\n actionsEntitled: options.actionsEntitled,\n allowJs: options.allowJs,\n }),\n )\n // Server steps only dispatch (and are only re-authorized) when the org is\n // actions-entitled — never advertise them otherwise, so basic-tier pages\n // don't POST to /api/events/dispatch for a no-op.\n const hasServerSteps =\n options.actionsEntitled &&\n (action.steps ?? []).some((step) => !isClientActionStep(step))\n if (!clientSteps.length && !hasServerSteps) continue\n automations.push({\n id,\n event,\n ...(action.trigger?.selector\n ? { selector: action.trigger.selector }\n : {}),\n ...(Number(action.trigger?.threshold) > 0\n ? { threshold: Number(action.trigger.threshold) }\n : {}),\n ...(action.trigger?.oncePerVisitor === true\n ? { oncePerVisitor: true }\n : {}),\n ...(action.trigger?.oncePerSession === true\n ? { oncePerSession: true }\n : {}),\n ...(Number(action.trigger?.cooldownMinutes) >= 1\n ? { cooldownMinutes: Number(action.trigger?.cooldownMinutes) }\n : {}),\n // Repeatable UI choreography (AGL-562): menu/drawer interactions fire on\n // every occurrence instead of once per pageview.\n ...(action.trigger?.everyTime === true ? { everyTime: true } : {}),\n steps: clientSteps,\n hasServerSteps,\n })\n }\n return automations\n}\n\nexport default compileClientAutomations\n"],"names":["hostActionStepsForClient","isClientActionStep","isClientStepEntitled","isSiteEventType","overlayMatchesPath","compileClientAutomations","actions","options","automations","id","action","deletedAt","enabled","event","String","trigger","pathPattern","trim","dropPathScoped","matchAllPaths","pathPatterns","path","reachableSteps","steps","clientSteps","filter","step","actionsEntitled","allowJs","hasServerSteps","some","length","push","selector","Number","threshold","oncePerVisitor","oncePerSession","cooldownMinutes","everyTime"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,wEAAwE;AACxE,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,SACEA,wBAAwB,EACxBC,kBAAkB,EAClBC,oBAAoB,EACpBC,eAAe,QAEV,iCAAgC;AACvC,SAASC,kBAAkB,QAAQ,gBAAY;AA0C/C;;;;;;;CAOC,GACD,OAAO,SAASC,yBACdC,OAAwB,EACxBC,OAAwC;IAExC,MAAMC,cAAkC,EAAE;IAC1C,KAAK,MAAM,EAAEC,EAAE,EAAEC,MAAM,EAAE,IAAIJ,QAAS;kBA2CjCI;YApCkBA,iBAEDA,6BAAAA,kBAuCdA,kBAGOA,kBAGPA,kBAGAA,kBAGOA,kBACmBA,kBAI1BA;QAhEN,IACE,AAACA,OAAmCC,SAAS,IAC7CD,OAAOE,OAAO,KAAK,OACnB;YACA;QACF;QACA,MAAMC,QAAQC,gBAAOJ,kBAAAA,OAAOK,OAAO,qBAAdL,gBAAgBG,KAAK,mBAAI;QAC9C,IAAI,CAACV,gBAAgBU,QAAQ;QAC7B,MAAMG,eAAcN,mBAAAA,OAAOK,OAAO,sBAAdL,8BAAAA,iBAAgBM,WAAW,qBAA3BN,4BAA6BO,IAAI;QACrD,IAAID,eAAeT,QAAQW,cAAc,EAAE;QAC3C,IACE,CAACX,QAAQY,aAAa,IACtBH,eACA,CAACZ,mBAAmB;YAAEgB,cAAc;gBAACJ;aAAY;QAAC,GAAGT,QAAQc,IAAI,GACjE;YACA;QACF;QACA;;;;;;;;;KASC,GACD,MAAMC,iBAAiBtB,yBAAyBU,OAAOa,KAAK;QAC5D,uEAAuE;QACvE,qEAAqE;QACrE,+CAA+C;QAC/C,MAAMC,cAAcF,eAAeG,MAAM,CAAC,CAACC,OACzCxB,qBAAqBwB,MAAM;gBACzBC,iBAAiBpB,QAAQoB,eAAe;gBACxCC,SAASrB,QAAQqB,OAAO;YAC1B;QAEF,0EAA0E;QAC1E,yEAAyE;QACzE,kDAAkD;QAClD,MAAMC,iBACJtB,QAAQoB,eAAe,IACvB,EAACjB,gBAAAA,OAAOa,KAAK,YAAZb,gBAAgB,EAAE,EAAEoB,IAAI,CAAC,CAACJ,OAAS,CAACzB,mBAAmByB;QAC1D,IAAI,CAACF,YAAYO,MAAM,IAAI,CAACF,gBAAgB;QAC5CrB,YAAYwB,IAAI,CAAC;YACfvB;YACAI;WACIH,EAAAA,mBAAAA,OAAOK,OAAO,qBAAdL,iBAAgBuB,QAAQ,IACxB;YAAEA,UAAUvB,OAAOK,OAAO,CAACkB,QAAQ;QAAC,IACpC,CAAC,GACDC,QAAOxB,mBAAAA,OAAOK,OAAO,qBAAdL,iBAAgByB,SAAS,IAAI,IACpC;YAAEA,WAAWD,OAAOxB,OAAOK,OAAO,CAACoB,SAAS;QAAE,IAC9C,CAAC,GACDzB,EAAAA,mBAAAA,OAAOK,OAAO,qBAAdL,iBAAgB0B,cAAc,MAAK,OACnC;YAAEA,gBAAgB;QAAK,IACvB,CAAC,GACD1B,EAAAA,mBAAAA,OAAOK,OAAO,qBAAdL,iBAAgB2B,cAAc,MAAK,OACnC;YAAEA,gBAAgB;QAAK,IACvB,CAAC,GACDH,QAAOxB,mBAAAA,OAAOK,OAAO,qBAAdL,iBAAgB4B,eAAe,KAAK,IAC3C;YAAEA,iBAAiBJ,QAAOxB,mBAAAA,OAAOK,OAAO,qBAAdL,iBAAgB4B,eAAe;QAAE,IAC3D,CAAC,GAGD5B,EAAAA,mBAAAA,OAAOK,OAAO,qBAAdL,iBAAgB6B,SAAS,MAAK,OAAO;YAAEA,WAAW;QAAK,IAAI,CAAC;YAChEhB,OAAOC;YACPK;;IAEJ;IACA,OAAOrB;AACT;AAEA,eAAeH,yBAAwB"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * A/B experiments (AGL-252): docs at `hosts/{hostId}/experiments/{id}`.
19
+ * Assignment is deterministic — hash(visitorId + experimentId) buckets a
20
+ * visitor into a weighted variant, so the same visitor always sees the
21
+ * same variant with no server state. Exposures/conversions accumulate on
22
+ * per-variant stats counters via the track API. Business tier
23
+ * (`abTesting` flag).
24
+ */
25
+ export type ExperimentStatus = 'draft' | 'running' | 'paused' | 'done';
26
+ /** What the experiment varies. */
27
+ export type ExperimentTarget = 'screen' | 'section' | 'email';
28
+ export interface ExperimentVariant {
29
+ id: string;
30
+ name?: string;
31
+ /** Relative traffic weight; defaults to 1. */
32
+ weight?: number;
33
+ /** Screen experiments: the screen version this variant serves. */
34
+ versionId?: string;
35
+ /** Email experiments (AGL-255): subject/body overrides. */
36
+ subject?: string;
37
+ body?: string;
38
+ }
39
+ export interface HostExperiment {
40
+ name: string;
41
+ status: ExperimentStatus;
42
+ target: ExperimentTarget;
43
+ /** Screen/section experiments: the screen under test. */
44
+ screenId?: string;
45
+ /** Section experiments: canvas node id the variants swap. */
46
+ nodeId?: string;
47
+ variants: ExperimentVariant[];
48
+ /** Conversion goal: a host/site event, optionally filtered. */
49
+ goal?: {
50
+ event: string;
51
+ filter?: string;
52
+ };
53
+ /** Set when finished; the renderer then serves only the winner. */
54
+ winnerVariantId?: string;
55
+ /**
56
+ * Epoch millis (AGL-273): past this a running experiment stops serving
57
+ * variants (visitors get the default) until someone declares a winner.
58
+ */
59
+ endAtMs?: number;
60
+ /**
61
+ * Opt-in auto-completion (AGL-273): once every variant has
62
+ * `minExposures` and a challenger clears `confidence` (0..1) against
63
+ * the control, the track pipeline promotes it and finishes the
64
+ * experiment. Control wins when every challenger is confidently worse.
65
+ */
66
+ autoWinner?: {
67
+ minExposures: number;
68
+ confidence: number;
69
+ };
70
+ /** Set by the pipeline when `autoWinner` decided the experiment. */
71
+ autoCompleted?: boolean;
72
+ }
73
+ export declare const EXPERIMENT_MAX_VARIANTS = 4;
74
+ /**
75
+ * Deterministic weighted assignment: the same visitor always lands in the
76
+ * same variant of an experiment. Finished experiments return the winner;
77
+ * non-running experiments without a winner return null (serve default).
78
+ */
79
+ export declare function assignExperimentVariant(experiment: Pick<HostExperiment, 'status' | 'variants' | 'winnerVariantId' | 'endAtMs'> & {
80
+ $id?: string;
81
+ }, experimentId: string, visitorId: string, nowMs?: number): ExperimentVariant | null;
82
+ /** Validates an experiment doc shape; human-readable error or null. */
83
+ export declare function validateExperiment(experiment: HostExperiment): string | null;
84
+ /** Naive conversion-rate summary the results table renders. */
85
+ export declare function summarizeVariantStats(stats: {
86
+ exposures?: number;
87
+ conversions?: number;
88
+ }): {
89
+ exposures: number;
90
+ conversions: number;
91
+ rate: number;
92
+ };
93
+ export interface VariantComparison {
94
+ /** Relative conversion-rate lift vs the control; null when unmeasurable. */
95
+ lift: number | null;
96
+ /**
97
+ * One-sided confidence (0..1) that the challenger genuinely beats the
98
+ * control — a two-proportion z-test through the normal CDF. Null until
99
+ * both variants have exposures and at least one conversion exists.
100
+ */
101
+ confidence: number | null;
102
+ }
103
+ /**
104
+ * Compares a challenger variant against the control (AGL-265): lift =
105
+ * relative rate change; confidence = P(challenger > control) from a
106
+ * pooled two-proportion z-test. Small samples return null confidence
107
+ * rather than a misleading number.
108
+ */
109
+ export declare function compareVariants(control: {
110
+ exposures?: number;
111
+ conversions?: number;
112
+ }, challenger: {
113
+ exposures?: number;
114
+ conversions?: number;
115
+ }): VariantComparison;
116
+ /**
117
+ * Auto-winner evaluation (AGL-273): decides a running experiment from
118
+ * per-variant stats once thresholds are met. The first variant is the
119
+ * control. Undecidable states return null: any arm still under
120
+ * `minExposures`, or no challenger confidently better while some remain
121
+ * in the uncertain middle. Control wins only when every challenger is
122
+ * confidently worse (its win probability is below `1 - confidence`).
123
+ */
124
+ export declare function evaluateAutoWinner(experiment: Pick<HostExperiment, 'status' | 'variants' | 'autoWinner'>, statsByVariant: Record<string, {
125
+ exposures?: number;
126
+ conversions?: number;
127
+ } | undefined>): {
128
+ variantId: string;
129
+ confidence: number;
130
+ } | null;
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * A/B experiments (AGL-252): docs at `hosts/{hostId}/experiments/{id}`.
18
+ * Assignment is deterministic — hash(visitorId + experimentId) buckets a
19
+ * visitor into a weighted variant, so the same visitor always sees the
20
+ * same variant with no server state. Exposures/conversions accumulate on
21
+ * per-variant stats counters via the track API. Business tier
22
+ * (`abTesting` flag).
23
+ */ export const EXPERIMENT_MAX_VARIANTS = 4;
24
+ /** FNV-1a over the visitor+experiment key — stable, dependency-free. */ function bucketHash(value) {
25
+ let hash = 0x811c9dc5;
26
+ for(let index = 0; index < value.length; index += 1){
27
+ hash ^= value.charCodeAt(index);
28
+ hash = Math.imul(hash, 0x01000193);
29
+ }
30
+ return hash >>> 0;
31
+ }
32
+ /**
33
+ * Deterministic weighted assignment: the same visitor always lands in the
34
+ * same variant of an experiment. Finished experiments return the winner;
35
+ * non-running experiments without a winner return null (serve default).
36
+ */ export function assignExperimentVariant(experiment, experimentId, visitorId, nowMs = Date.now()) {
37
+ var _experiment_variants;
38
+ const variants = ((_experiment_variants = experiment.variants) != null ? _experiment_variants : []).slice(0, EXPERIMENT_MAX_VARIANTS);
39
+ if (!variants.length) return null;
40
+ if (experiment.winnerVariantId) {
41
+ var _variants_find;
42
+ return (_variants_find = variants.find((variant)=>variant.id === experiment.winnerVariantId)) != null ? _variants_find : null;
43
+ }
44
+ if (experiment.status !== 'running') return null;
45
+ // Past the end date (AGL-273) visitors get the default until a winner
46
+ // is declared — stats stop accruing rather than drifting post-test.
47
+ if (experiment.endAtMs && nowMs > experiment.endAtMs) return null;
48
+ const totalWeight = variants.reduce((sum, variant)=>{
49
+ var _variant_weight;
50
+ return sum + Math.max(0, (_variant_weight = variant.weight) != null ? _variant_weight : 1);
51
+ }, 0);
52
+ if (totalWeight <= 0) return null;
53
+ const bucket = bucketHash(`${visitorId}:${experimentId}`) % totalWeight;
54
+ let cursor = 0;
55
+ for (const variant of variants){
56
+ var _variant_weight;
57
+ cursor += Math.max(0, (_variant_weight = variant.weight) != null ? _variant_weight : 1);
58
+ if (bucket < cursor) return variant;
59
+ }
60
+ return variants[variants.length - 1];
61
+ }
62
+ /** Validates an experiment doc shape; human-readable error or null. */ export function validateExperiment(experiment) {
63
+ var _experiment_variants;
64
+ var _experiment_name, _experiment_nodeId, _experiment_screenId;
65
+ if (!((_experiment_name = experiment.name) == null ? void 0 : _experiment_name.trim())) return 'Name the experiment';
66
+ if (![
67
+ 'screen',
68
+ 'section',
69
+ 'email'
70
+ ].includes(experiment.target)) {
71
+ return 'Pick what the experiment tests';
72
+ }
73
+ const variants = (_experiment_variants = experiment.variants) != null ? _experiment_variants : [];
74
+ if (variants.length < 2) return 'Add at least two variants';
75
+ if (variants.length > EXPERIMENT_MAX_VARIANTS) {
76
+ return `Experiments are capped at ${EXPERIMENT_MAX_VARIANTS} variants`;
77
+ }
78
+ const ids = new Set(variants.map((variant)=>variant.id));
79
+ if (ids.size !== variants.length) return 'Variant ids must be unique';
80
+ if (experiment.target === 'section' && !((_experiment_nodeId = experiment.nodeId) == null ? void 0 : _experiment_nodeId.trim())) {
81
+ return 'Section experiments need the canvas element id';
82
+ }
83
+ if ((experiment.target === 'screen' || experiment.target === 'section') && !((_experiment_screenId = experiment.screenId) == null ? void 0 : _experiment_screenId.trim())) {
84
+ return 'Pick the screen under test';
85
+ }
86
+ if (experiment.autoWinner) {
87
+ const { minExposures, confidence } = experiment.autoWinner;
88
+ if (!Number.isFinite(minExposures) || minExposures < 1) {
89
+ return 'Auto-winner needs a minimum exposure count of at least 1';
90
+ }
91
+ if (!Number.isFinite(confidence) || confidence <= 0.5 || confidence >= 1) {
92
+ return 'Auto-winner confidence must be between 0.5 and 1';
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+ /** Naive conversion-rate summary the results table renders. */ export function summarizeVariantStats(stats) {
98
+ var _stats_exposures, _stats_conversions;
99
+ const exposures = Math.max(0, (_stats_exposures = stats.exposures) != null ? _stats_exposures : 0);
100
+ const conversions = Math.max(0, (_stats_conversions = stats.conversions) != null ? _stats_conversions : 0);
101
+ return {
102
+ exposures,
103
+ conversions,
104
+ rate: exposures > 0 ? conversions / exposures : 0
105
+ };
106
+ }
107
+ /** Abramowitz–Stegun erf approximation (max error ~1.5e-7). */ function erf(x) {
108
+ const sign = x < 0 ? -1 : 1;
109
+ const t = 1 / (1 + 0.3275911 * Math.abs(x));
110
+ const y = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * Math.exp(-x * x);
111
+ return sign * y;
112
+ }
113
+ /** Standard normal CDF. */ function normalCdf(z) {
114
+ return 0.5 * (1 + erf(z / Math.SQRT2));
115
+ }
116
+ /**
117
+ * Compares a challenger variant against the control (AGL-265): lift =
118
+ * relative rate change; confidence = P(challenger > control) from a
119
+ * pooled two-proportion z-test. Small samples return null confidence
120
+ * rather than a misleading number.
121
+ */ export function compareVariants(control, challenger) {
122
+ const a = summarizeVariantStats(control);
123
+ const b = summarizeVariantStats(challenger);
124
+ const lift = a.rate > 0 ? (b.rate - a.rate) / a.rate : null;
125
+ if (a.exposures < 1 || b.exposures < 1 || a.conversions + b.conversions < 1) {
126
+ return {
127
+ lift,
128
+ confidence: null
129
+ };
130
+ }
131
+ const pooled = (a.conversions + b.conversions) / (a.exposures + b.exposures);
132
+ const standardError = Math.sqrt(pooled * (1 - pooled) * (1 / a.exposures + 1 / b.exposures));
133
+ if (!Number.isFinite(standardError) || standardError === 0) {
134
+ return {
135
+ lift,
136
+ confidence: null
137
+ };
138
+ }
139
+ const z = (b.rate - a.rate) / standardError;
140
+ return {
141
+ lift,
142
+ confidence: normalCdf(z)
143
+ };
144
+ }
145
+ /**
146
+ * Auto-winner evaluation (AGL-273): decides a running experiment from
147
+ * per-variant stats once thresholds are met. The first variant is the
148
+ * control. Undecidable states return null: any arm still under
149
+ * `minExposures`, or no challenger confidently better while some remain
150
+ * in the uncertain middle. Control wins only when every challenger is
151
+ * confidently worse (its win probability is below `1 - confidence`).
152
+ */ export function evaluateAutoWinner(experiment, statsByVariant) {
153
+ var _experiment_variants, _statsByVariant_variants__id, _controlStats_exposures;
154
+ const config = experiment.autoWinner;
155
+ if (!config || experiment.status !== 'running') return null;
156
+ const variants = ((_experiment_variants = experiment.variants) != null ? _experiment_variants : []).slice(0, EXPERIMENT_MAX_VARIANTS);
157
+ if (variants.length < 2) return null;
158
+ const controlStats = (_statsByVariant_variants__id = statsByVariant[variants[0].id]) != null ? _statsByVariant_variants__id : {};
159
+ if (((_controlStats_exposures = controlStats.exposures) != null ? _controlStats_exposures : 0) < config.minExposures) return null;
160
+ let best = null;
161
+ let everyLoses = true;
162
+ for (const challenger of variants.slice(1)){
163
+ var _statsByVariant_challenger_id, _challengerStats_exposures;
164
+ const challengerStats = (_statsByVariant_challenger_id = statsByVariant[challenger.id]) != null ? _statsByVariant_challenger_id : {};
165
+ if (((_challengerStats_exposures = challengerStats.exposures) != null ? _challengerStats_exposures : 0) < config.minExposures) return null;
166
+ const { confidence } = compareVariants(controlStats, challengerStats);
167
+ if (confidence === null) return null;
168
+ if (confidence >= config.confidence && (!best || confidence > best.confidence)) {
169
+ best = {
170
+ variantId: challenger.id,
171
+ confidence
172
+ };
173
+ }
174
+ if (confidence > 1 - config.confidence) everyLoses = false;
175
+ }
176
+ if (best) return best;
177
+ if (everyLoses) {
178
+ return {
179
+ variantId: variants[0].id,
180
+ confidence: config.confidence
181
+ };
182
+ }
183
+ return null;
184
+ }
185
+
186
+ //# sourceMappingURL=experiments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/experiments.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\n/**\n * A/B experiments (AGL-252): docs at `hosts/{hostId}/experiments/{id}`.\n * Assignment is deterministic — hash(visitorId + experimentId) buckets a\n * visitor into a weighted variant, so the same visitor always sees the\n * same variant with no server state. Exposures/conversions accumulate on\n * per-variant stats counters via the track API. Business tier\n * (`abTesting` flag).\n */\nexport type ExperimentStatus = 'draft' | 'running' | 'paused' | 'done'\n\n/** What the experiment varies. */\nexport type ExperimentTarget = 'screen' | 'section' | 'email'\n\nexport interface ExperimentVariant {\n id: string\n name?: string\n /** Relative traffic weight; defaults to 1. */\n weight?: number\n /** Screen experiments: the screen version this variant serves. */\n versionId?: string\n /** Email experiments (AGL-255): subject/body overrides. */\n subject?: string\n body?: string\n}\n\nexport interface HostExperiment {\n name: string\n status: ExperimentStatus\n target: ExperimentTarget\n /** Screen/section experiments: the screen under test. */\n screenId?: string\n /** Section experiments: canvas node id the variants swap. */\n nodeId?: string\n variants: ExperimentVariant[]\n /** Conversion goal: a host/site event, optionally filtered. */\n goal?: { event: string; filter?: string }\n /** Set when finished; the renderer then serves only the winner. */\n winnerVariantId?: string\n /**\n * Epoch millis (AGL-273): past this a running experiment stops serving\n * variants (visitors get the default) until someone declares a winner.\n */\n endAtMs?: number\n /**\n * Opt-in auto-completion (AGL-273): once every variant has\n * `minExposures` and a challenger clears `confidence` (0..1) against\n * the control, the track pipeline promotes it and finishes the\n * experiment. Control wins when every challenger is confidently worse.\n */\n autoWinner?: { minExposures: number; confidence: number }\n /** Set by the pipeline when `autoWinner` decided the experiment. */\n autoCompleted?: boolean\n}\n\nexport const EXPERIMENT_MAX_VARIANTS = 4\n\n/** FNV-1a over the visitor+experiment key — stable, dependency-free. */\nfunction bucketHash(value: string): number {\n let hash = 0x811c9dc5\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index)\n hash = Math.imul(hash, 0x01000193)\n }\n return hash >>> 0\n}\n\n/**\n * Deterministic weighted assignment: the same visitor always lands in the\n * same variant of an experiment. Finished experiments return the winner;\n * non-running experiments without a winner return null (serve default).\n */\nexport function assignExperimentVariant(\n experiment: Pick<\n HostExperiment,\n 'status' | 'variants' | 'winnerVariantId' | 'endAtMs'\n > & {\n $id?: string\n },\n experimentId: string,\n visitorId: string,\n nowMs: number = Date.now(),\n): ExperimentVariant | null {\n const variants = (experiment.variants ?? []).slice(\n 0,\n EXPERIMENT_MAX_VARIANTS,\n )\n if (!variants.length) return null\n if (experiment.winnerVariantId) {\n return (\n variants.find((variant) => variant.id === experiment.winnerVariantId) ??\n null\n )\n }\n if (experiment.status !== 'running') return null\n // Past the end date (AGL-273) visitors get the default until a winner\n // is declared — stats stop accruing rather than drifting post-test.\n if (experiment.endAtMs && nowMs > experiment.endAtMs) return null\n const totalWeight = variants.reduce(\n (sum, variant) => sum + Math.max(0, variant.weight ?? 1),\n 0,\n )\n if (totalWeight <= 0) return null\n const bucket = bucketHash(`${visitorId}:${experimentId}`) % totalWeight\n let cursor = 0\n for (const variant of variants) {\n cursor += Math.max(0, variant.weight ?? 1)\n if (bucket < cursor) return variant\n }\n return variants[variants.length - 1]\n}\n\n/** Validates an experiment doc shape; human-readable error or null. */\nexport function validateExperiment(\n experiment: HostExperiment,\n): string | null {\n if (!experiment.name?.trim()) return 'Name the experiment'\n if (!['screen', 'section', 'email'].includes(experiment.target)) {\n return 'Pick what the experiment tests'\n }\n const variants = experiment.variants ?? []\n if (variants.length < 2) return 'Add at least two variants'\n if (variants.length > EXPERIMENT_MAX_VARIANTS) {\n return `Experiments are capped at ${EXPERIMENT_MAX_VARIANTS} variants`\n }\n const ids = new Set(variants.map((variant) => variant.id))\n if (ids.size !== variants.length) return 'Variant ids must be unique'\n if (experiment.target === 'section' && !experiment.nodeId?.trim()) {\n return 'Section experiments need the canvas element id'\n }\n if (\n (experiment.target === 'screen' || experiment.target === 'section') &&\n !experiment.screenId?.trim()\n ) {\n return 'Pick the screen under test'\n }\n if (experiment.autoWinner) {\n const { minExposures, confidence } = experiment.autoWinner\n if (!Number.isFinite(minExposures) || minExposures < 1) {\n return 'Auto-winner needs a minimum exposure count of at least 1'\n }\n if (!Number.isFinite(confidence) || confidence <= 0.5 || confidence >= 1) {\n return 'Auto-winner confidence must be between 0.5 and 1'\n }\n }\n return null\n}\n\n/** Naive conversion-rate summary the results table renders. */\nexport function summarizeVariantStats(stats: {\n exposures?: number\n conversions?: number\n}): { exposures: number; conversions: number; rate: number } {\n const exposures = Math.max(0, stats.exposures ?? 0)\n const conversions = Math.max(0, stats.conversions ?? 0)\n return {\n exposures,\n conversions,\n rate: exposures > 0 ? conversions / exposures : 0,\n }\n}\n\nexport interface VariantComparison {\n /** Relative conversion-rate lift vs the control; null when unmeasurable. */\n lift: number | null\n /**\n * One-sided confidence (0..1) that the challenger genuinely beats the\n * control — a two-proportion z-test through the normal CDF. Null until\n * both variants have exposures and at least one conversion exists.\n */\n confidence: number | null\n}\n\n/** Abramowitz–Stegun erf approximation (max error ~1.5e-7). */\nfunction erf(x: number): number {\n const sign = x < 0 ? -1 : 1\n const t = 1 / (1 + 0.3275911 * Math.abs(x))\n const y =\n 1 -\n (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t -\n 0.284496736) *\n t +\n 0.254829592) *\n t *\n Math.exp(-x * x)\n return sign * y\n}\n\n/** Standard normal CDF. */\nfunction normalCdf(z: number): number {\n return 0.5 * (1 + erf(z / Math.SQRT2))\n}\n\n/**\n * Compares a challenger variant against the control (AGL-265): lift =\n * relative rate change; confidence = P(challenger > control) from a\n * pooled two-proportion z-test. Small samples return null confidence\n * rather than a misleading number.\n */\nexport function compareVariants(\n control: { exposures?: number; conversions?: number },\n challenger: { exposures?: number; conversions?: number },\n): VariantComparison {\n const a = summarizeVariantStats(control)\n const b = summarizeVariantStats(challenger)\n const lift = a.rate > 0 ? (b.rate - a.rate) / a.rate : null\n if (\n a.exposures < 1 ||\n b.exposures < 1 ||\n a.conversions + b.conversions < 1\n ) {\n return { lift, confidence: null }\n }\n const pooled =\n (a.conversions + b.conversions) / (a.exposures + b.exposures)\n const standardError = Math.sqrt(\n pooled * (1 - pooled) * (1 / a.exposures + 1 / b.exposures),\n )\n if (!Number.isFinite(standardError) || standardError === 0) {\n return { lift, confidence: null }\n }\n const z = (b.rate - a.rate) / standardError\n return { lift, confidence: normalCdf(z) }\n}\n\n/**\n * Auto-winner evaluation (AGL-273): decides a running experiment from\n * per-variant stats once thresholds are met. The first variant is the\n * control. Undecidable states return null: any arm still under\n * `minExposures`, or no challenger confidently better while some remain\n * in the uncertain middle. Control wins only when every challenger is\n * confidently worse (its win probability is below `1 - confidence`).\n */\nexport function evaluateAutoWinner(\n experiment: Pick<HostExperiment, 'status' | 'variants' | 'autoWinner'>,\n statsByVariant: Record<\n string,\n { exposures?: number; conversions?: number } | undefined\n >,\n): { variantId: string; confidence: number } | null {\n const config = experiment.autoWinner\n if (!config || experiment.status !== 'running') return null\n const variants = (experiment.variants ?? []).slice(\n 0,\n EXPERIMENT_MAX_VARIANTS,\n )\n if (variants.length < 2) return null\n const controlStats = statsByVariant[variants[0].id] ?? {}\n if ((controlStats.exposures ?? 0) < config.minExposures) return null\n let best: { variantId: string; confidence: number } | null = null\n let everyLoses = true\n for (const challenger of variants.slice(1)) {\n const challengerStats = statsByVariant[challenger.id] ?? {}\n if ((challengerStats.exposures ?? 0) < config.minExposures) return null\n const { confidence } = compareVariants(controlStats, challengerStats)\n if (confidence === null) return null\n if (\n confidence >= config.confidence &&\n (!best || confidence > best.confidence)\n ) {\n best = { variantId: challenger.id, confidence }\n }\n if (confidence > 1 - config.confidence) everyLoses = false\n }\n if (best) return best\n if (everyLoses) {\n return { variantId: variants[0].id, confidence: config.confidence }\n }\n return null\n}\n"],"names":["EXPERIMENT_MAX_VARIANTS","bucketHash","value","hash","index","length","charCodeAt","Math","imul","assignExperimentVariant","experiment","experimentId","visitorId","nowMs","Date","now","variants","slice","winnerVariantId","find","variant","id","status","endAtMs","totalWeight","reduce","sum","max","weight","bucket","cursor","validateExperiment","name","trim","includes","target","ids","Set","map","size","nodeId","screenId","autoWinner","minExposures","confidence","Number","isFinite","summarizeVariantStats","stats","exposures","conversions","rate","erf","x","sign","t","abs","y","exp","normalCdf","z","SQRT2","compareVariants","control","challenger","a","b","lift","pooled","standardError","sqrt","evaluateAutoWinner","statsByVariant","controlStats","config","best","everyLoses","challengerStats","variantId"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Marketing domain model (AGL-412): overlays + experiments relocated from core app-utils.
19
+ * Context-free — importable by client components, /server handlers, and
20
+ * other plugins/apps via `@aglyn/plugins-marketing/model`.
21
+ */
22
+ export * from './campaign-membership-figures';
23
+ export * from './overlays';
24
+ export * from './experiments';
25
+ export * from './site-contract';
26
+ export * from './compile-client-automations';
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * Marketing domain model (AGL-412): overlays + experiments relocated from core app-utils.
18
+ * Context-free — importable by client components, /server handlers, and
19
+ * other plugins/apps via `@aglyn/plugins-marketing/model`.
20
+ */ export * from "./campaign-membership-figures.js";
21
+ export * from "./overlays.js";
22
+ export * from "./experiments.js";
23
+ export * from "./site-contract.js";
24
+ export * from "./compile-client-automations.js";
25
+
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/index.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\n/**\n * Marketing domain model (AGL-412): overlays + experiments relocated from core app-utils.\n * Context-free — importable by client components, /server handlers, and\n * other plugins/apps via `@aglyn/plugins-marketing/model`.\n */\nexport * from './campaign-membership-figures'\nexport * from './overlays'\nexport * from './experiments'\nexport * from './site-contract'\nexport * from './compile-client-automations'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,cAAc,mCAA+B;AAC7C,cAAc,gBAAY;AAC1B,cAAc,mBAAe;AAC7B,cAAc,qBAAiB;AAC/B,cAAc,kCAA8B"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Called from the console registrar. The owner is named rather than read off
3
+ * the loader's marker, so the route registers the same way under a spec that
4
+ * calls the registrar directly.
5
+ */
6
+ export declare function registerMarketingRecordRoutes(): void;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ * Unless required by applicable law or agreed to in writing, software
9
+ * distributed under the License is distributed on an "AS IS" BASIS,
10
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ * See the License for the specific language governing permissions and
12
+ * limitations under the License.
13
+ */ import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
14
+ import { registerPluginRecordRoute } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
15
+ import { BUNDLE_ID } from "../constants/bundle-common.js";
16
+ /**
17
+ * Where a campaign is read, published for every other surface.
18
+ *
19
+ * A message's template lists the campaigns its sends belonged to, and a
20
+ * campaign's pages are this plugin's. A surface elsewhere asks the
21
+ * record-route registry for `campaign` rather than spelling this plugin's nav
22
+ * slug, and gets `null` — text instead of a link — where this plugin is not
23
+ * loaded.
24
+ */ /** The nav slug the shell resolves this plugin's hub by. */ const MARKETING_SLUG = 'marketing';
25
+ /**
26
+ * Called from the console registrar. The owner is named rather than read off
27
+ * the loader's marker, so the route registers the same way under a spec that
28
+ * calls the registrar directly.
29
+ */ export function registerMarketingRecordRoutes() {
30
+ registerPluginRecordRoute('campaign', {
31
+ // Campaigns belong to one site, so the organization has no address.
32
+ list: (context)=>context.host ? `${hub(context.orgSlug, context.host)}/campaigns` : null,
33
+ record: (context, id)=>context.host ? `${hub(context.orgSlug, context.host)}/campaigns/${id}` : null
34
+ }, {
35
+ pluginId: BUNDLE_ID
36
+ });
37
+ }
38
+ function hub(orgSlug, host) {
39
+ return buildRoute(Route.HOST_PLUGIN, {
40
+ orgSlug,
41
+ host,
42
+ pluginSlug: MARKETING_SLUG
43
+ });
44
+ }
45
+
46
+ //# sourceMappingURL=marketing-record-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/marketing-record-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\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 * http://www.apache.org/licenses/LICENSE-2.0\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 */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { registerPluginRecordRoute } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * Where a campaign is read, published for every other surface.\n *\n * A message's template lists the campaigns its sends belonged to, and a\n * campaign's pages are this plugin's. A surface elsewhere asks the\n * record-route registry for `campaign` rather than spelling this plugin's nav\n * slug, and gets `null` — text instead of a link — where this plugin is not\n * loaded.\n */\n\n/** The nav slug the shell resolves this plugin's hub by. */\nconst MARKETING_SLUG = 'marketing'\n\n/**\n * Called from the console registrar. The owner is named rather than read off\n * the loader's marker, so the route registers the same way under a spec that\n * calls the registrar directly.\n */\nexport function registerMarketingRecordRoutes(): void {\n registerPluginRecordRoute(\n 'campaign',\n {\n // Campaigns belong to one site, so the organization has no address.\n list: (context) =>\n context.host\n ? `${hub(context.orgSlug, context.host)}/campaigns`\n : null,\n record: (context, id) =>\n context.host\n ? `${hub(context.orgSlug, context.host)}/campaigns/${id}`\n : null,\n },\n { pluginId: BUNDLE_ID },\n )\n}\n\nfunction hub(orgSlug: string, host: string): string {\n return buildRoute(Route.HOST_PLUGIN, {\n orgSlug,\n host,\n pluginSlug: MARKETING_SLUG,\n })\n}\n"],"names":["buildRoute","Route","registerPluginRecordRoute","BUNDLE_ID","MARKETING_SLUG","registerMarketingRecordRoutes","list","context","host","hub","orgSlug","record","id","pluginId","HOST_PLUGIN","pluginSlug"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GACD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SAASC,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,SAAS,QAAQ,gCAA4B;AAEtD;;;;;;;;CAQC,GAED,0DAA0D,GAC1D,MAAMC,iBAAiB;AAEvB;;;;CAIC,GACD,OAAO,SAASC;IACdH,0BACE,YACA;QACE,oEAAoE;QACpEI,MAAM,CAACC,UACLA,QAAQC,IAAI,GACR,GAAGC,IAAIF,QAAQG,OAAO,EAAEH,QAAQC,IAAI,EAAE,UAAU,CAAC,GACjD;QACNG,QAAQ,CAACJ,SAASK,KAChBL,QAAQC,IAAI,GACR,GAAGC,IAAIF,QAAQG,OAAO,EAAEH,QAAQC,IAAI,EAAE,WAAW,EAAEI,IAAI,GACvD;IACR,GACA;QAAEC,UAAUV;IAAU;AAE1B;AAEA,SAASM,IAAIC,OAAe,EAAEF,IAAY;IACxC,OAAOR,WAAWC,MAAMa,WAAW,EAAE;QACnCJ;QACAF;QACAO,YAAYX;IACd;AACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type HostTokenSource } from '@aglyn/aglyn/app-utils/host-tokens';
18
+ import { type HostVariable } from '@aglyn/aglyn/app-utils/variables';
19
+ /**
20
+ * The fields of a bar or popup that hold copy: a bar's `text`, a popup's
21
+ * `headline` and `body`. Links, labels and colors are not copy and are never
22
+ * resolved.
23
+ */
24
+ export declare const OVERLAY_COPY_FIELDS: readonly ["text", "headline", "body"];
25
+ /**
26
+ * Overlay copy as a visitor reads it: an announcement bar's text, and a
27
+ * popup's headline and body.
28
+ *
29
+ * Every path that puts overlay copy in front of a visitor goes through this
30
+ * one function — the bar and popup a page shows on load, and a bar or popup an
31
+ * automation shows later — so the same overlay cannot read one way when it
32
+ * matches the page and another way when an automation opens it.
33
+ *
34
+ * The order is the one a page's own copy is resolved in: site variables
35
+ * (`{{var:id}}`) first, then the site's details (`{{host.*}}`). A variable
36
+ * whose document is gone renders as nothing, and so does a detail the site has
37
+ * not set; neither ever reaches a visitor as the token.
38
+ *
39
+ * Deep imports rather than either `@aglyn/aglyn` barrel: the server enricher
40
+ * and the console editors both import this module, and the client barrel
41
+ * carries React contexts a server graph must not load.
42
+ *
43
+ * @param variables - the site's variables keyed by document id, as
44
+ * `getVariables` returns them.
45
+ * @param host - the site document the copy is published on.
46
+ */
47
+ export declare function resolveOverlayCopy(text: string, variables: Record<string, HostVariable>, host: HostTokenSource | null | undefined): string;
48
+ /**
49
+ * The tokens in stored overlay copy that a visitor would read exactly as
50
+ * typed: every token {@link resolveOverlayCopy} leaves in place.
51
+ *
52
+ * A bare `{{name}}` is one — nothing on a published page resolves that form,
53
+ * which is why an editor rewrites a name it recognizes to `{{var:id}}` before
54
+ * saving — and so is any token overlay copy has no source for, such as a
55
+ * function call or an entry field.
56
+ *
57
+ * Each token is put through the published page's own resolution rather than
58
+ * matched against a list of forms, so what an editor warns about cannot drift
59
+ * from what the page does. A variable whose document is gone and a detail the
60
+ * site has not set both render as nothing, and neither is listed.
61
+ */
62
+ export declare function overlayCopyTokensShownAsTyped(text: string | undefined, variables: Record<string, HostVariable>, host: HostTokenSource | null | undefined): string[];
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { resolveHostTokens } from "@aglyn/aglyn/app-utils/host-tokens";
17
+ import { resolveBindings } from "@aglyn/aglyn/app-utils/variables";
18
+ /**
19
+ * The fields of a bar or popup that hold copy: a bar's `text`, a popup's
20
+ * `headline` and `body`. Links, labels and colors are not copy and are never
21
+ * resolved.
22
+ */ export const OVERLAY_COPY_FIELDS = [
23
+ 'text',
24
+ 'headline',
25
+ 'body'
26
+ ];
27
+ /**
28
+ * Overlay copy as a visitor reads it: an announcement bar's text, and a
29
+ * popup's headline and body.
30
+ *
31
+ * Every path that puts overlay copy in front of a visitor goes through this
32
+ * one function — the bar and popup a page shows on load, and a bar or popup an
33
+ * automation shows later — so the same overlay cannot read one way when it
34
+ * matches the page and another way when an automation opens it.
35
+ *
36
+ * The order is the one a page's own copy is resolved in: site variables
37
+ * (`{{var:id}}`) first, then the site's details (`{{host.*}}`). A variable
38
+ * whose document is gone renders as nothing, and so does a detail the site has
39
+ * not set; neither ever reaches a visitor as the token.
40
+ *
41
+ * Deep imports rather than either `@aglyn/aglyn` barrel: the server enricher
42
+ * and the console editors both import this module, and the client barrel
43
+ * carries React contexts a server graph must not load.
44
+ *
45
+ * @param variables - the site's variables keyed by document id, as
46
+ * `getVariables` returns them.
47
+ * @param host - the site document the copy is published on.
48
+ */ export function resolveOverlayCopy(text, variables, host) {
49
+ if (typeof text !== 'string' || !text.includes('{{')) return text;
50
+ return resolveHostTokens(resolveBindings(text, variables), host);
51
+ }
52
+ /** Any `{{…}}` token, whatever it names. */ const ANY_TOKEN_PATTERN = /\{\{[^{}]*\}\}/g;
53
+ /**
54
+ * The tokens in stored overlay copy that a visitor would read exactly as
55
+ * typed: every token {@link resolveOverlayCopy} leaves in place.
56
+ *
57
+ * A bare `{{name}}` is one — nothing on a published page resolves that form,
58
+ * which is why an editor rewrites a name it recognizes to `{{var:id}}` before
59
+ * saving — and so is any token overlay copy has no source for, such as a
60
+ * function call or an entry field.
61
+ *
62
+ * Each token is put through the published page's own resolution rather than
63
+ * matched against a list of forms, so what an editor warns about cannot drift
64
+ * from what the page does. A variable whose document is gone and a detail the
65
+ * site has not set both render as nothing, and neither is listed.
66
+ */ export function overlayCopyTokensShownAsTyped(text, variables, host) {
67
+ var _text_match;
68
+ if (typeof text !== 'string' || !text.includes('{{')) return [];
69
+ const shown = new Set();
70
+ for (const token of (_text_match = text.match(ANY_TOKEN_PATTERN)) != null ? _text_match : []){
71
+ if (resolveOverlayCopy(token, variables, host) === token) shown.add(token);
72
+ }
73
+ return [
74
+ ...shown
75
+ ];
76
+ }
77
+
78
+ //# sourceMappingURL=overlay-copy.js.map