@aglyn/aglyn 1.0.0-beta.217 → 1.0.0-beta.219

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 (348) hide show
  1. package/package.json +14 -14
  2. package/src/lib/app-utils/actions.d.ts +25 -374
  3. package/src/lib/app-utils/actions.js +101 -561
  4. package/src/lib/app-utils/actions.js.map +1 -1
  5. package/src/lib/app-utils/activity-presenter.d.ts +3 -3
  6. package/src/lib/app-utils/activity-presenter.js +36 -52
  7. package/src/lib/app-utils/activity-presenter.js.map +1 -1
  8. package/src/lib/app-utils/admin-audit-index.js +2 -2
  9. package/src/lib/app-utils/admin-audit-index.js.map +1 -1
  10. package/src/lib/app-utils/analytics-events.d.ts +11 -2
  11. package/src/lib/app-utils/analytics-events.js +11 -2
  12. package/src/lib/app-utils/analytics-events.js.map +1 -1
  13. package/src/lib/app-utils/analytics-provider.d.ts +182 -0
  14. package/src/lib/app-utils/analytics-provider.js +133 -0
  15. package/src/lib/app-utils/analytics-provider.js.map +1 -0
  16. package/src/lib/app-utils/api-plugins.d.ts +46 -0
  17. package/src/lib/app-utils/api-plugins.js +19 -0
  18. package/src/lib/app-utils/api-plugins.js.map +1 -1
  19. package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
  20. package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
  21. package/src/lib/app-utils/artifact-overrides.js.map +1 -0
  22. package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
  23. package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
  24. package/src/lib/app-utils/artifact-provenance.js.map +1 -0
  25. package/src/lib/app-utils/assist-credits.js +2 -1
  26. package/src/lib/app-utils/assist-credits.js.map +1 -1
  27. package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
  28. package/src/lib/app-utils/binding-token-catalog.js +17 -41
  29. package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
  30. package/src/lib/app-utils/binding-tokens.js +1 -1
  31. package/src/lib/app-utils/binding-tokens.js.map +1 -1
  32. package/src/lib/app-utils/brand-support-line.d.ts +36 -0
  33. package/src/lib/app-utils/brand-support-line.js +38 -0
  34. package/src/lib/app-utils/brand-support-line.js.map +1 -0
  35. package/src/lib/app-utils/campaign-touch.d.ts +5 -5
  36. package/src/lib/app-utils/campaign-touch.js +8 -8
  37. package/src/lib/app-utils/campaign-touch.js.map +1 -1
  38. package/src/lib/app-utils/collection-entries.d.ts +2 -2
  39. package/src/lib/app-utils/collection-entries.js.map +1 -1
  40. package/src/lib/app-utils/consent-group-change.d.ts +4 -0
  41. package/src/lib/app-utils/consent-group-change.js.map +1 -1
  42. package/src/lib/app-utils/console-record-links.d.ts +10 -89
  43. package/src/lib/app-utils/console-record-links.js +3 -107
  44. package/src/lib/app-utils/console-record-links.js.map +1 -1
  45. package/src/lib/app-utils/console-routes.d.ts +16 -7
  46. package/src/lib/app-utils/console-routes.js +9 -3
  47. package/src/lib/app-utils/console-routes.js.map +1 -1
  48. package/src/lib/app-utils/contacts.d.ts +23 -1
  49. package/src/lib/app-utils/contacts.js +21 -5
  50. package/src/lib/app-utils/contacts.js.map +1 -1
  51. package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
  52. package/src/lib/app-utils/cross-pool-desync.js +47 -0
  53. package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
  54. package/src/lib/app-utils/csv-upload.d.ts +29 -0
  55. package/src/lib/app-utils/csv-upload.js +29 -0
  56. package/src/lib/app-utils/csv-upload.js.map +1 -0
  57. package/src/lib/app-utils/dataset-models.d.ts +5 -5
  58. package/src/lib/app-utils/dataset-models.js +19 -2
  59. package/src/lib/app-utils/dataset-models.js.map +1 -1
  60. package/src/lib/app-utils/datasets.d.ts +65 -11
  61. package/src/lib/app-utils/datasets.js +74 -11
  62. package/src/lib/app-utils/datasets.js.map +1 -1
  63. package/src/lib/app-utils/docs-deflection.d.ts +6 -6
  64. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  65. package/src/lib/app-utils/docs-help.generated.d.ts +12 -6
  66. package/src/lib/app-utils/docs-help.generated.js +21 -6
  67. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  68. package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
  69. package/src/lib/app-utils/docs-index.generated.js +514 -318
  70. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  71. package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
  72. package/src/lib/app-utils/docs-retrieval.js +14 -10
  73. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  74. package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
  75. package/src/lib/app-utils/duplicate-resource.js +7 -4
  76. package/src/lib/app-utils/duplicate-resource.js.map +1 -1
  77. package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
  78. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  79. package/src/lib/app-utils/element-ui.d.ts +1 -1
  80. package/src/lib/app-utils/element-ui.js +1 -1
  81. package/src/lib/app-utils/element-ui.js.map +1 -1
  82. package/src/lib/app-utils/email-starter-policy.js +3 -3
  83. package/src/lib/app-utils/email-starter-policy.js.map +1 -1
  84. package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
  85. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  86. package/src/lib/app-utils/error-beacon.d.ts +75 -6
  87. package/src/lib/app-utils/error-beacon.js +160 -21
  88. package/src/lib/app-utils/error-beacon.js.map +1 -1
  89. package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
  90. package/src/lib/app-utils/expand-repeatables.js +10 -10
  91. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  92. package/src/lib/app-utils/host-events.d.ts +82 -0
  93. package/src/lib/app-utils/host-events.js +62 -0
  94. package/src/lib/app-utils/host-events.js.map +1 -0
  95. package/src/lib/app-utils/lockdown.d.ts +33 -1
  96. package/src/lib/app-utils/lockdown.js +45 -4
  97. package/src/lib/app-utils/lockdown.js.map +1 -1
  98. package/src/lib/app-utils/margin-utilization.d.ts +4 -69
  99. package/src/lib/app-utils/margin-utilization.js +82 -69
  100. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  101. package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
  102. package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
  103. package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
  104. package/src/lib/app-utils/node-interactions.d.ts +7 -7
  105. package/src/lib/app-utils/node-interactions.js.map +1 -1
  106. package/src/lib/app-utils/notifications.d.ts +78 -47
  107. package/src/lib/app-utils/notifications.js +63 -44
  108. package/src/lib/app-utils/notifications.js.map +1 -1
  109. package/src/lib/app-utils/payments-configured.d.ts +8 -21
  110. package/src/lib/app-utils/payments-configured.js +8 -23
  111. package/src/lib/app-utils/payments-configured.js.map +1 -1
  112. package/src/lib/app-utils/plan-entitlements.d.ts +43 -128
  113. package/src/lib/app-utils/plan-entitlements.js +145 -304
  114. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  115. package/src/lib/app-utils/platform-brand.d.ts +10 -0
  116. package/src/lib/app-utils/platform-brand.js +14 -3
  117. package/src/lib/app-utils/platform-brand.js.map +1 -1
  118. package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
  119. package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
  120. package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
  121. package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
  122. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  123. package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
  124. package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
  125. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
  126. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
  127. package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
  128. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
  129. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  130. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  131. package/src/lib/app-utils/release-flags.d.ts +14 -5
  132. package/src/lib/app-utils/release-flags.js +21 -125
  133. package/src/lib/app-utils/release-flags.js.map +1 -1
  134. package/src/lib/app-utils/repeat-sources.d.ts +9 -0
  135. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  136. package/src/lib/app-utils/retained-refusals.d.ts +73 -0
  137. package/src/lib/app-utils/retained-refusals.js +186 -0
  138. package/src/lib/app-utils/retained-refusals.js.map +1 -0
  139. package/src/lib/app-utils/scope-tokens.d.ts +27 -0
  140. package/src/lib/app-utils/scope-tokens.js +61 -0
  141. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  142. package/src/lib/app-utils/screen-link-context.js +3 -3
  143. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  144. package/src/lib/app-utils/screen-route.d.ts +15 -0
  145. package/src/lib/app-utils/screen-route.js +21 -1
  146. package/src/lib/app-utils/screen-route.js.map +1 -1
  147. package/src/lib/app-utils/seo-audit.d.ts +132 -0
  148. package/src/lib/app-utils/seo-audit.js +246 -0
  149. package/src/lib/app-utils/seo-audit.js.map +1 -0
  150. package/src/lib/app-utils/seo-keywords.d.ts +48 -0
  151. package/src/lib/app-utils/seo-keywords.js +60 -0
  152. package/src/lib/app-utils/seo-keywords.js.map +1 -0
  153. package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
  154. package/src/lib/app-utils/seo-page-facts.js +155 -0
  155. package/src/lib/app-utils/seo-page-facts.js.map +1 -0
  156. package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
  157. package/src/lib/app-utils/seo-site-scan.js +162 -0
  158. package/src/lib/app-utils/seo-site-scan.js.map +1 -0
  159. package/src/lib/app-utils/seo-title-variables.js +1 -1
  160. package/src/lib/app-utils/seo-title-variables.js.map +1 -1
  161. package/src/lib/app-utils/server.d.ts +6 -9
  162. package/src/lib/app-utils/server.js +17 -27
  163. package/src/lib/app-utils/server.js.map +1 -1
  164. package/src/lib/app-utils/site-interactions.d.ts +501 -0
  165. package/src/lib/app-utils/site-interactions.js +554 -0
  166. package/src/lib/app-utils/site-interactions.js.map +1 -0
  167. package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
  168. package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +5 -2
  169. package/src/lib/app-utils/site-theme.js.map +1 -0
  170. package/src/lib/app-utils/sitemap.d.ts +4 -3
  171. package/src/lib/app-utils/sitemap.js +9 -7
  172. package/src/lib/app-utils/sitemap.js.map +1 -1
  173. package/src/lib/app-utils/stale-build-error.d.ts +51 -0
  174. package/src/lib/app-utils/stale-build-error.js +100 -1
  175. package/src/lib/app-utils/stale-build-error.js.map +1 -1
  176. package/src/lib/app-utils/starter-templates.d.ts +20 -0
  177. package/src/lib/app-utils/starter-templates.js +49 -0
  178. package/src/lib/app-utils/starter-templates.js.map +1 -1
  179. package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
  180. package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
  181. package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
  182. package/src/lib/app-utils/theme-library.d.ts +10 -3
  183. package/src/lib/app-utils/theme-library.js +13 -4
  184. package/src/lib/app-utils/theme-library.js.map +1 -1
  185. package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
  186. package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
  187. package/src/lib/app-utils/utm-attribution.js.map +1 -0
  188. package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
  189. package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
  190. package/src/lib/app-utils/utm-forwarding.js.map +1 -0
  191. package/src/lib/app-utils/video-object.js +6 -5
  192. package/src/lib/app-utils/video-object.js.map +1 -1
  193. package/src/lib/app-utils/visitor-consent.d.ts +41 -164
  194. package/src/lib/app-utils/visitor-consent.js +66 -277
  195. package/src/lib/app-utils/visitor-consent.js.map +1 -1
  196. package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
  197. package/src/lib/app-utils/web-vitals-rum.js +31 -63
  198. package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
  199. package/src/lib/app-utils/where-used.d.ts +11 -6
  200. package/src/lib/app-utils/where-used.js +3 -8
  201. package/src/lib/app-utils/where-used.js.map +1 -1
  202. package/src/lib/canvas-manager/canvas-manager.js +3 -3
  203. package/src/lib/canvas-manager/canvas-manager.js.map +1 -1
  204. package/src/lib/foundation/definitions/components.types.d.ts +1 -1
  205. package/src/lib/foundation/definitions/components.types.js +1 -1
  206. package/src/lib/foundation/definitions/components.types.js.map +1 -1
  207. package/src/lib/foundation/definitions/org-billing.types.d.ts +18 -135
  208. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  209. package/src/lib/foundation/definitions/organization.types.d.ts +6 -3
  210. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  211. package/src/lib/foundation/definitions/platform.types.d.ts +27 -5
  212. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  213. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
  214. package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
  215. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  216. package/src/lib/foundation/index.d.ts +1 -1
  217. package/src/lib/foundation/index.js.map +1 -1
  218. package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
  219. package/src/lib/plugin-manager/besigner-documents.js +61 -0
  220. package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
  221. package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
  222. package/src/lib/plugin-manager/computed-variables.js +74 -0
  223. package/src/lib/plugin-manager/computed-variables.js.map +1 -0
  224. package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
  225. package/src/lib/plugin-manager/custom-fields.js +47 -1
  226. package/src/lib/plugin-manager/custom-fields.js.map +1 -1
  227. package/src/lib/plugin-manager/feature-plugins.d.ts +292 -20
  228. package/src/lib/plugin-manager/feature-plugins.js +105 -17
  229. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  230. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
  231. package/src/lib/plugin-manager/first-party-plugins.generated.js +785 -14
  232. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  233. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
  234. package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
  235. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
  236. package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
  237. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  238. package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
  239. package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
  240. package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
  241. package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
  242. package/src/lib/plugin-manager/plugin-dependents.js +75 -0
  243. package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
  244. package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
  245. package/src/lib/plugin-manager/plugin-distribution.js +45 -0
  246. package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
  247. package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
  248. package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
  249. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
  251. package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
  252. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
  254. package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
  255. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  256. package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
  257. package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
  258. package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
  259. package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
  260. package/src/lib/plugin-manager/plugin-record-index.js +51 -0
  261. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
  263. package/src/lib/plugin-manager/plugin-record-written.js +42 -0
  264. package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
  265. package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
  266. package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
  267. package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
  269. package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
  270. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
  271. package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
  272. package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
  273. package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
  274. package/src/lib/plugin-manager/plugin-site-export.js +40 -0
  275. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
  276. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
  277. package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
  278. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
  279. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
  280. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  281. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
  282. package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
  283. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
  284. package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
  285. package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
  286. package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
  287. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +107 -0
  288. package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
  289. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
  290. package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
  291. package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
  292. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  293. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  294. package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
  295. package/src/lib/plugin-manager/record-captured-contact.js +10 -32
  296. package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
  297. package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
  298. package/src/lib/plugin-manager/repeat-rows.js +108 -0
  299. package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
  300. package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
  301. package/src/lib/plugin-manager/submission-record-target.js +125 -0
  302. package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
  303. package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
  304. package/src/lib/plugin-manager/video-embed-provider.js +165 -0
  305. package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
  306. package/src/lib/types/nodes.d.ts +2 -2
  307. package/src/lib/types/nodes.js.map +1 -1
  308. package/src/lib/app-utils/campaign-attribution.js.map +0 -1
  309. package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
  310. package/src/lib/app-utils/contact-holder.d.ts +0 -68
  311. package/src/lib/app-utils/contact-holder.js +0 -129
  312. package/src/lib/app-utils/contact-holder.js.map +0 -1
  313. package/src/lib/app-utils/contact-merge.d.ts +0 -136
  314. package/src/lib/app-utils/contact-merge.js +0 -412
  315. package/src/lib/app-utils/contact-merge.js.map +0 -1
  316. package/src/lib/app-utils/crm-csv.d.ts +0 -239
  317. package/src/lib/app-utils/crm-csv.js +0 -467
  318. package/src/lib/app-utils/crm-csv.js.map +0 -1
  319. package/src/lib/app-utils/crm-digest.d.ts +0 -185
  320. package/src/lib/app-utils/crm-digest.js +0 -272
  321. package/src/lib/app-utils/crm-digest.js.map +0 -1
  322. package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
  323. package/src/lib/app-utils/form-dataset-binding.js +0 -74
  324. package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
  325. package/src/lib/app-utils/list-import.d.ts +0 -158
  326. package/src/lib/app-utils/list-import.js +0 -332
  327. package/src/lib/app-utils/list-import.js.map +0 -1
  328. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
  329. package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
  330. package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
  331. package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
  332. package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
  333. package/src/lib/app-utils/marketplace-theme.js.map +0 -1
  334. package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
  335. package/src/lib/app-utils/marketplace-update-state.js +0 -132
  336. package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
  337. package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
  338. package/src/lib/app-utils/publisher-agreement.js +0 -321
  339. package/src/lib/app-utils/publisher-agreement.js.map +0 -1
  340. package/src/lib/app-utils/wistia-embed.d.ts +0 -104
  341. package/src/lib/app-utils/wistia-embed.js +0 -138
  342. package/src/lib/app-utils/wistia-embed.js.map +0 -1
  343. package/src/lib/app-utils/workflows.d.ts +0 -136
  344. package/src/lib/app-utils/workflows.js +0 -346
  345. package/src/lib/app-utils/workflows.js.map +0 -1
  346. package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
  347. package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
  348. package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/binding-tokens.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 * Binding token grammar (AGL-185) — the single source of truth for what a\n * binding token looks like, shared by the besigner editor, the console\n * binding picker, and the tenant renderer.\n *\n * Two generations coexist:\n * - id tokens: `{{var:variableId}}` and `{{fn:functionId(args)}}` — the\n * reference survives renames because only the display name changes.\n * - legacy name tokens: `{{name}}` and `{{fn:name(args)}}` — resolved as a\n * fallback until AGL-188 migrates stored content.\n *\n * Resolution maps are id-keyed (AGL-194); the editor keeps name-capable\n * lookups only for typed-name normalization and friendly display.\n */\n\n/** Firestore doc ids and legacy binding names both fit this shape. */\nconst REF = '[a-zA-Z0-9_-]{1,64}'\n\n/** `{{var:variableId}}` — capture group 1 is the variable's doc id. */\nexport const VARIABLE_ID_TOKEN_PATTERN = new RegExp(\n `\\\\{\\\\{\\\\s*var:(${REF})\\\\s*\\\\}\\\\}`,\n 'g',\n)\n\n/**\n * `{{fn:ref(args)}}` — capture 1 is a function doc id OR legacy name,\n * capture 2 the raw argument list. One pattern serves both generations;\n * lookup order (id first, then name) decides what the ref means.\n */\nexport const FUNCTION_TOKEN_PATTERN = new RegExp(\n `\\\\{\\\\{\\\\s*fn:(${REF}(?: ${REF})*)\\\\s*\\\\(([^)]*)\\\\)\\\\s*\\\\}\\\\}`,\n 'g',\n)\n\n/** Legacy `{{name}}` — names never contain `:`, so `var:`/`fn:` can't match. */\nexport const NAME_TOKEN_PATTERN = /\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_]{0,39})\\s*\\}\\}/g\n\n/** Renders the id-form variable token the picker inserts (AGL-186). */\nexport function formatVariableIdToken(variableId: string): string {\n return `{{var:${variableId}}}`\n}\n\n/** Renders the id-form function token with its parameter placeholders. */\nexport function formatFunctionIdToken(\n functionId: string,\n parameterNames: string[] = [],\n): string {\n return `{{fn:${functionId}(${parameterNames.join(', ')})}}`\n}\n\n/**\n * Keys resource docs by doc id — the only key resolution uses since the\n * legacy name fallback retired (AGL-194). Editor-side lookups that need\n * name access (typed-name normalization, friendly display) read the\n * `name` field off the values instead of relying on map keys.\n */\nexport function keyById<T extends { name?: string }>(\n docs: Array<T & { id: string }>,\n): Record<string, T> {\n const map: Record<string, T> = {}\n for (const { id, ...doc } of docs) {\n map[id] = doc as unknown as T\n }\n return map\n}\n\n/**\n * Doc shape the editor-side helpers need: consoles pass Firestore docs\n * that carry their id as `$id` (reactfire idField) beside the name.\n */\nexport interface BindingDocRef {\n name?: string\n $id?: string\n parameters?: Array<{ name: string }>\n}\n\n/**\n * Rewrites legacy name tokens to their id forms when the name matches a\n * known doc (AGL-186 typing normalization): hand-typed `{{Message}}`\n * becomes `{{var:abc123}}` at save so it survives renames. Unknown names\n * are left alone — the variable may be created later, and legacy\n * resolution still covers them meanwhile.\n */\nexport function normalizeBindingTokens(\n text: string,\n variables: Record<string, BindingDocRef> = {},\n functions: Record<string, BindingDocRef> = {},\n): string {\n const withFunctions = text.replace(\n FUNCTION_TOKEN_PATTERN,\n (token, ref, rawArgs) => {\n const definition = functions[String(ref).trim()]\n // Already an id (the ref maps to a doc whose $id is the ref itself)\n // or unknown: leave as-is.\n if (!definition?.$id || definition.$id === String(ref).trim()) {\n return token\n }\n return `{{fn:${definition.$id}(${String(rawArgs)})}}`\n },\n )\n return withFunctions.replace(NAME_TOKEN_PATTERN, (token, name) => {\n const variable = variables[name]\n return variable?.$id ? formatVariableIdToken(variable.$id) : token\n })\n}\n\n/** A whole `{{name}}` token, for asking whether a name can be typed as one. */\nconst TYPED_NAME_TOKEN = new RegExp(`^${NAME_TOKEN_PATTERN.source}$`)\n\n/**\n * Rewrites id-form variable tokens to the `{{name}}` an author types — the\n * inverse of {@link normalizeBindingTokens}, for an editor that shows stored\n * copy in a plain text field and normalizes it again when it saves.\n *\n * Unlike {@link displayBindingTokens}, whose result must never be persisted,\n * this one survives the round trip: normalizing its output restores the\n * input. So a token is rewritten only when that holds — its variable exists,\n * its name is one a typed token can carry, and looking that name up leads back\n * to this id and no other. Every other token keeps its id form. That includes\n * a deleted variable's token, which renders as nothing on a published page,\n * where `{{missing binding}}` would render as typed.\n *\n * @param variables - the lookup `normalizeBindingTokens` takes: the site's\n * variables keyed by name, each carrying its document id as `$id`.\n */\nexport function editableBindingTokens(\n text: string,\n variables: Record<string, BindingDocRef> = {},\n): string {\n if (!text.includes('{{')) return text\n return text.replace(VARIABLE_ID_TOKEN_PATTERN, (token, rawId) => {\n const id = String(rawId)\n const name = Object.values(variables).find(\n (variable) => variable?.$id === id,\n )?.name\n if (!name || variables[name]?.$id !== id) return token\n const typed = `{{${name}}}`\n return TYPED_NAME_TOKEN.test(typed) ? typed : token\n })\n}\n\n/** A variable/function referent to search for in stored content. */\nexport interface BindingRef {\n kind: 'variable' | 'function'\n /** Firestore doc id — matches id-form tokens. */\n id?: string\n /** Current name — matches legacy name-form tokens. */\n name?: string\n}\n\n/** How a dependent references the binding: rename-safe id or legacy name. */\nexport type BindingRefVia = 'id' | 'name'\n\nconst escapeRegExp = (value: string) =>\n value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Which token generation(s) in `text` reference the given binding\n * (AGL-187 where-used). Returns matches so callers can distinguish\n * rename-safe id references from legacy name references — a rename only\n * endangers the latter.\n */\nexport function textReferencesBinding(\n text: string,\n ref: BindingRef,\n): BindingRefVia[] {\n const via: BindingRefVia[] = []\n if (ref.kind === 'variable') {\n if (\n ref.id &&\n new RegExp(`\\\\{\\\\{\\\\s*var:${escapeRegExp(ref.id)}\\\\s*\\\\}\\\\}`).test(text)\n ) {\n via.push('id')\n }\n if (\n ref.name &&\n new RegExp(`\\\\{\\\\{\\\\s*${escapeRegExp(ref.name)}\\\\s*\\\\}\\\\}`).test(text)\n ) {\n via.push('name')\n }\n } else {\n if (\n ref.id &&\n new RegExp(`\\\\{\\\\{\\\\s*fn:${escapeRegExp(ref.id)}\\\\s*\\\\(`).test(text)\n ) {\n via.push('id')\n }\n if (\n ref.name &&\n new RegExp(`\\\\{\\\\{\\\\s*fn:${escapeRegExp(ref.name)}\\\\s*\\\\(`).test(text)\n ) {\n via.push('name')\n }\n }\n return via\n}\n\n/**\n * Scans a normalized nodes map's string props for references to the\n * binding; returns the union of match kinds across all nodes.\n */\nexport function nodesReferenceBinding(\n nodes: Record<string, { props?: Record<string, unknown> } | undefined>,\n ref: BindingRef,\n): BindingRefVia[] {\n const via = new Set<BindingRefVia>()\n for (const node of Object.values(nodes ?? {})) {\n for (const value of Object.values(node?.props ?? {})) {\n if (typeof value !== 'string' || !value.includes('{{')) continue\n for (const match of textReferencesBinding(value, ref)) via.add(match)\n }\n if (via.size === 2) break\n }\n return [...via]\n}\n\n/**\n * Deep-walks a JSON-ish value, applying `normalizeBindingTokens` to every\n * string containing a token opener (AGL-188 migration / host import).\n * Arrays and plain objects recurse; other values pass through untouched.\n * Returns the (possibly new) value and whether anything changed.\n */\nexport function rewriteBindingTokensDeep<T>(\n value: T,\n variables: Record<string, BindingDocRef> = {},\n functions: Record<string, BindingDocRef> = {},\n): { value: T; changed: boolean } {\n if (typeof value === 'string') {\n if (!value.includes('{{')) return { value, changed: false }\n const next = normalizeBindingTokens(value, variables, functions)\n return { value: next as T, changed: next !== value }\n }\n if (Array.isArray(value)) {\n let changed = false\n const next = value.map((item) => {\n const result = rewriteBindingTokensDeep(item, variables, functions)\n changed = changed || result.changed\n return result.value\n })\n return { value: (changed ? next : value) as T, changed }\n }\n if (value && typeof value === 'object') {\n let changed = false\n const next: Record<string, unknown> = {}\n for (const [key, item] of Object.entries(value)) {\n const result = rewriteBindingTokensDeep(item, variables, functions)\n changed = changed || result.changed\n next[key] = result.value\n }\n return { value: (changed ? next : value) as T, changed }\n }\n return { value, changed: false }\n}\n\n/**\n * Which binding lookups a value's tokens would actually need (AGL-703).\n *\n * A read-only twin of {@link rewriteBindingTokensDeep}, and it exists for\n * cost. Publish-time normalization used to fetch EVERY variable and EVERY\n * function on the site — two `limit(1000)` gets — before looking at whether\n * the version being published contained a single token to rewrite. Almost\n * none do: AGL-188 migrated the corpus years ago and the picker has written\n * id-form tokens ever since, so the overwhelming majority of publishes paid\n * for two collection reads to discover there was nothing to do.\n *\n * The two flags are separate because the two rewrites are:\n *\n * - a legacy `{{name}}` token is resolved against VARIABLES and nothing else\n * — {@link normalizeBindingTokens} only consults `variables` for it;\n * - a `{{fn:ref(...)}}` token is resolved against FUNCTIONS.\n *\n * So a page holding only function tokens never reads the variables.\n *\n * `{{var:id}}` deliberately sets NEITHER. It is already the rename-safe form,\n * nothing rewrites it, and it cannot match `NAME_TOKEN_PATTERN` because a\n * name may not contain `:`. A page whose tokens are all id-form — which is\n * what a page authored through the picker looks like — costs nothing.\n *\n * A `{{fn:}}` token sets `functions` even when its ref is ALREADY an id,\n * because telling an id from a legacy name is exactly what the lookup is for.\n * Erring that way costs one read on a page that has function tokens; erring\n * the other way would skip a rewrite that was due.\n */\nexport interface BindingTokenNeeds {\n /** A legacy `{{name}}` token is present — needs the variables lookup. */\n variables: boolean\n /** A `{{fn:…}}` token is present — needs the functions lookup. */\n functions: boolean\n}\n\nexport function bindingTokenNeedsDeep(value: unknown): BindingTokenNeeds {\n const needs: BindingTokenNeeds = { variables: false, functions: false }\n /*\n Fresh, NON-GLOBAL copies. The exported patterns carry the `g` flag, and a\n global regex's `.test()` advances its own `lastIndex` — so testing the\n same shared object twice in a walk skips matches, and does it in a way\n that depends on the order the tree happened to be visited in.\n */\n const nameToken = new RegExp(NAME_TOKEN_PATTERN.source)\n const functionToken = new RegExp(FUNCTION_TOKEN_PATTERN.source)\n\n const walk = (current: unknown): void => {\n if (needs.variables && needs.functions) return\n if (typeof current === 'string') {\n if (!current.includes('{{')) return\n if (!needs.variables && nameToken.test(current)) needs.variables = true\n if (!needs.functions && functionToken.test(current)) {\n needs.functions = true\n }\n return\n }\n if (Array.isArray(current)) {\n for (const item of current) walk(item)\n return\n }\n if (current && typeof current === 'object') {\n for (const item of Object.values(current)) walk(item)\n }\n }\n walk(value)\n return needs\n}\n\n/** Editor display text for a token whose referent no longer exists. */\nexport const MISSING_BINDING_LABEL = 'missing binding'\n\n/**\n * Rewrites id tokens to friendly name forms for editor display (AGL-186):\n * `{{var:abc123}}` shows as `{{Message}}` (the CURRENT name — renames\n * reflect immediately), and tokens whose doc was deleted show as\n * `{{missing binding}}` instead of leaking raw ids. Display only — never\n * persist the result.\n */\nexport function displayBindingTokens(\n text: string,\n variables: Record<string, BindingDocRef> = {},\n functions: Record<string, BindingDocRef> = {},\n): string {\n const withVariables = text.replace(\n VARIABLE_ID_TOKEN_PATTERN,\n (_token, id) => {\n const variable = variables[String(id)]\n return `{{${variable?.name ?? MISSING_BINDING_LABEL}}}`\n },\n )\n return withVariables.replace(\n FUNCTION_TOKEN_PATTERN,\n (token, ref, rawArgs) => {\n const definition = functions[String(ref).trim()]\n // Name-form refs already read fine; only id-form refs get mapped.\n if (!definition || definition.name === String(ref).trim()) return token\n return `{{fn:${definition.name ?? MISSING_BINDING_LABEL}(${String(rawArgs)})}}`\n },\n )\n}\n"],"names":["REF","VARIABLE_ID_TOKEN_PATTERN","RegExp","FUNCTION_TOKEN_PATTERN","NAME_TOKEN_PATTERN","formatVariableIdToken","variableId","formatFunctionIdToken","functionId","parameterNames","join","keyById","docs","map","id","doc","normalizeBindingTokens","text","variables","functions","withFunctions","replace","token","ref","rawArgs","definition","String","trim","$id","name","variable","TYPED_NAME_TOKEN","source","editableBindingTokens","includes","rawId","Object","values","find","typed","test","escapeRegExp","value","textReferencesBinding","via","kind","push","nodesReferenceBinding","nodes","Set","node","props","match","add","size","rewriteBindingTokensDeep","changed","next","Array","isArray","item","result","key","entries","bindingTokenNeedsDeep","needs","nameToken","functionToken","walk","current","MISSING_BINDING_LABEL","displayBindingTokens","withVariables","_token"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/binding-tokens.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 * Binding token grammar (AGL-185) — the single source of truth for what a\n * binding token looks like, shared by the besigner editor, the console\n * binding picker, and the tenant renderer.\n *\n * Two generations coexist:\n * - id tokens: `{{var:variableId}}` and `{{fn:functionId(args)}}` — the\n * reference survives renames because only the display name changes.\n * - legacy name tokens: `{{name}}` and `{{fn:name(args)}}` — resolved as a\n * fallback until AGL-188 migrates stored content.\n *\n * Resolution maps are id-keyed (AGL-194); the editor keeps name-capable\n * lookups only for typed-name normalization and friendly display.\n */\n\n/** Firestore doc ids and legacy binding names both fit this shape. */\nconst REF = '[a-zA-Z0-9_-]{1,64}'\n\n/** `{{var:variableId}}` — capture group 1 is the variable's doc id. */\nexport const VARIABLE_ID_TOKEN_PATTERN = new RegExp(\n `\\\\{\\\\{\\\\s*var:(${REF})\\\\s*\\\\}\\\\}`,\n 'g',\n)\n\n/**\n * `{{fn:ref(args)}}` — capture 1 is a function doc id OR legacy name,\n * capture 2 the raw argument list. One pattern serves both generations;\n * lookup order (id first, then name) decides what the ref means.\n */\nexport const FUNCTION_TOKEN_PATTERN = new RegExp(\n `\\\\{\\\\{\\\\s*fn:(${REF}(?: ${REF})*)\\\\s*\\\\(([^)]*)\\\\)\\\\s*\\\\}\\\\}`,\n 'g',\n)\n\n/** Legacy `{{name}}` — names never contain `:`, so `var:`/`fn:` can't match. */\nexport const NAME_TOKEN_PATTERN = /\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_]{0,39})\\s*\\}\\}/g\n\n/** Renders the id-form variable token the picker inserts (AGL-186). */\nexport function formatVariableIdToken(variableId: string): string {\n return `{{var:${variableId}}}`\n}\n\n/** Renders the id-form function token with its parameter placeholders. */\nexport function formatFunctionIdToken(\n functionId: string,\n parameterNames: string[] = [],\n): string {\n return `{{fn:${functionId}(${parameterNames.join(', ')})}}`\n}\n\n/**\n * Keys resource docs by doc id — the only key resolution uses since the\n * legacy name fallback retired (AGL-194). Editor-side lookups that need\n * name access (typed-name normalization, friendly display) read the\n * `name` field off the values instead of relying on map keys.\n */\nexport function keyById<T extends { name?: string }>(\n docs: Array<T & { id: string }>,\n): Record<string, T> {\n const map: Record<string, T> = {}\n for (const { id, ...doc } of docs) {\n map[id] = doc as unknown as T\n }\n return map\n}\n\n/**\n * Doc shape the editor-side helpers need: consoles pass Firestore docs\n * that carry their id as `$id` (reactfire idField) beside the name.\n */\nexport interface BindingDocRef {\n name?: string\n $id?: string\n parameters?: Array<{ name: string }>\n}\n\n/**\n * Rewrites legacy name tokens to their id forms when the name matches a\n * known doc (AGL-186 typing normalization): hand-typed `{{Message}}`\n * becomes `{{var:abc123}}` at save so it survives renames. Unknown names\n * are left alone — the variable may be created later, and legacy\n * resolution still covers them meanwhile.\n */\nexport function normalizeBindingTokens(\n text: string,\n variables: Record<string, BindingDocRef> = {},\n functions: Record<string, BindingDocRef> = {},\n): string {\n const withFunctions = text.replace(\n FUNCTION_TOKEN_PATTERN,\n (token, ref, rawArgs) => {\n const definition = functions[String(ref).trim()]\n // Already an id (the ref maps to a doc whose $id is the ref itself)\n // or unknown: leave as-is.\n if (!definition?.$id || definition.$id === String(ref).trim()) {\n return token\n }\n return `{{fn:${definition.$id}(${String(rawArgs)})}}`\n },\n )\n return withFunctions.replace(NAME_TOKEN_PATTERN, (token, name) => {\n const variable = variables[name]\n return variable?.$id ? formatVariableIdToken(variable.$id) : token\n })\n}\n\n/** A whole `{{name}}` token, for asking whether a name can be typed as one. */\nconst TYPED_NAME_TOKEN = new RegExp(`^${NAME_TOKEN_PATTERN.source}$`)\n\n/**\n * Rewrites id-form variable tokens to the `{{name}}` an author types — the\n * inverse of {@link normalizeBindingTokens}, for an editor that shows stored\n * copy in a plain text field and normalizes it again when it saves.\n *\n * Unlike {@link displayBindingTokens}, whose result must never be persisted,\n * this one survives the round trip: normalizing its output restores the\n * input. So a token is rewritten only when that holds — its variable exists,\n * its name is one a typed token can carry, and looking that name up leads back\n * to this id and no other. Every other token keeps its id form. That includes\n * a deleted variable's token, which renders as nothing on a published page,\n * where `{{missing binding}}` would render as typed.\n *\n * @param variables - the lookup `normalizeBindingTokens` takes: the site's\n * variables keyed by name, each carrying its document id as `$id`.\n */\nexport function editableBindingTokens(\n text: string,\n variables: Record<string, BindingDocRef> = {},\n): string {\n if (!text.includes('{{')) return text\n return text.replace(VARIABLE_ID_TOKEN_PATTERN, (token, rawId) => {\n const id = String(rawId)\n const name = Object.values(variables).find(\n (variable) => variable?.$id === id,\n )?.name\n if (!name || variables[name]?.$id !== id) return token\n const typed = `{{${name}}}`\n return TYPED_NAME_TOKEN.test(typed) ? typed : token\n })\n}\n\n/** A variable/function referent to search for in stored content. */\nexport interface BindingRef {\n kind: 'variable' | 'function'\n /** Firestore doc id — matches id-form tokens. */\n id?: string\n /** Current name — matches legacy name-form tokens. */\n name?: string\n}\n\n/** How a dependent references the binding: rename-safe id or legacy name. */\nexport type BindingRefVia = 'id' | 'name'\n\nconst escapeRegExp = (value: string) =>\n value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Which token generation(s) in `text` reference the given binding\n * (AGL-187 where-used). Returns matches so callers can distinguish\n * rename-safe id references from legacy name references — a rename only\n * endangers the latter.\n */\nexport function textReferencesBinding(\n text: string,\n ref: BindingRef,\n): BindingRefVia[] {\n const via: BindingRefVia[] = []\n if (ref.kind === 'variable') {\n if (\n ref.id &&\n new RegExp(`\\\\{\\\\{\\\\s*var:${escapeRegExp(ref.id)}\\\\s*\\\\}\\\\}`).test(text)\n ) {\n via.push('id')\n }\n if (\n ref.name &&\n new RegExp(`\\\\{\\\\{\\\\s*${escapeRegExp(ref.name)}\\\\s*\\\\}\\\\}`).test(text)\n ) {\n via.push('name')\n }\n } else {\n if (\n ref.id &&\n new RegExp(`\\\\{\\\\{\\\\s*fn:${escapeRegExp(ref.id)}\\\\s*\\\\(`).test(text)\n ) {\n via.push('id')\n }\n if (\n ref.name &&\n new RegExp(`\\\\{\\\\{\\\\s*fn:${escapeRegExp(ref.name)}\\\\s*\\\\(`).test(text)\n ) {\n via.push('name')\n }\n }\n return via\n}\n\n/**\n * Scans a normalized nodes map's string props for references to the\n * binding; returns the union of match kinds across all nodes.\n */\nexport function nodesReferenceBinding(\n nodes: Record<string, { props?: Record<string, unknown> } | undefined>,\n ref: BindingRef,\n): BindingRefVia[] {\n const via = new Set<BindingRefVia>()\n for (const node of Object.values(nodes ?? {})) {\n for (const value of Object.values(node?.props ?? {})) {\n if (typeof value !== 'string' || !value.includes('{{')) continue\n for (const match of textReferencesBinding(value, ref)) via.add(match)\n }\n if (via.size === 2) break\n }\n return [...via]\n}\n\n/**\n * Deep-walks a JSON-ish value, applying `normalizeBindingTokens` to every\n * string containing a token opener (AGL-188 migration / host import).\n * Arrays and plain objects recurse; other values pass through untouched.\n * Returns the (possibly new) value and whether anything changed.\n */\nexport function rewriteBindingTokensDeep<T>(\n value: T,\n variables: Record<string, BindingDocRef> = {},\n functions: Record<string, BindingDocRef> = {},\n): { value: T; changed: boolean } {\n if (typeof value === 'string') {\n if (!value.includes('{{')) return { value, changed: false }\n const next = normalizeBindingTokens(value, variables, functions)\n return { value: next as T, changed: next !== value }\n }\n if (Array.isArray(value)) {\n let changed = false\n const next = value.map((item) => {\n const result = rewriteBindingTokensDeep(item, variables, functions)\n changed = changed || result.changed\n return result.value\n })\n return { value: (changed ? next : value) as T, changed }\n }\n if (value && typeof value === 'object') {\n let changed = false\n const next: Record<string, unknown> = {}\n for (const [key, item] of Object.entries(value)) {\n const result = rewriteBindingTokensDeep(item, variables, functions)\n changed = changed || result.changed\n next[key] = result.value\n }\n return { value: (changed ? next : value) as T, changed }\n }\n return { value, changed: false }\n}\n\n/**\n * Which binding lookups a value's tokens would actually need (AGL-703).\n *\n * A read-only twin of {@link rewriteBindingTokensDeep}, and it exists for\n * cost. Publish-time normalization used to fetch EVERY variable and EVERY\n * function on the site — two `limit(1000)` gets — before looking at whether\n * the version being published contained a single token to rewrite. Almost\n * none do: AGL-188 migrated the corpus years ago and the picker has written\n * id-form tokens ever since, so the overwhelming majority of publishes paid\n * for two collection reads to discover there was nothing to do.\n *\n * The two flags are separate because the two rewrites are:\n *\n * - a legacy `{{name}}` token is resolved against VARIABLES and nothing else\n * — {@link normalizeBindingTokens} only consults `variables` for it;\n * - a `{{fn:ref(...)}}` token is resolved against FUNCTIONS.\n *\n * So a page holding only function tokens never reads the variables.\n *\n * `{{var:id}}` deliberately sets NEITHER. It is already the rename-safe form,\n * nothing rewrites it, and it cannot match `NAME_TOKEN_PATTERN` because a\n * name may not contain `:`. A page whose tokens are all id-form — which is\n * what a page authored through the picker looks like — costs nothing.\n *\n * A `{{fn:}}` token sets `functions` even when its ref is ALREADY an id,\n * because telling an id from a legacy name is exactly what the lookup is for.\n * Erring that way costs one read on a page that has function tokens; erring\n * the other way would skip a rewrite that was due.\n */\nexport interface BindingTokenNeeds {\n /** A legacy `{{name}}` token is present — needs the variables lookup. */\n variables: boolean\n /** A `{{fn:…}}` token is present — needs the functions lookup. */\n functions: boolean\n}\n\nexport function bindingTokenNeedsDeep(value: unknown): BindingTokenNeeds {\n const needs: BindingTokenNeeds = { variables: false, functions: false }\n /*\n Fresh, NON-GLOBAL copies. The exported patterns carry the `g` flag, and a\n global regex's `.test()` advances its own `lastIndex` — so testing the\n same shared object twice in a walk skips matches, and does it in a way\n that depends on the order the tree happened to be visited in.\n */\n const nameToken = new RegExp(NAME_TOKEN_PATTERN.source)\n const functionToken = new RegExp(FUNCTION_TOKEN_PATTERN.source)\n\n const walk = (current: unknown): void => {\n if (needs.variables && needs.functions) return\n if (typeof current === 'string') {\n if (!current.includes('{{')) return\n if (!needs.variables && nameToken.test(current)) needs.variables = true\n if (!needs.functions && functionToken.test(current)) {\n needs.functions = true\n }\n return\n }\n if (Array.isArray(current)) {\n for (const item of current) walk(item)\n return\n }\n if (current && typeof current === 'object') {\n for (const item of Object.values(current)) walk(item)\n }\n }\n walk(value)\n return needs\n}\n\n/** Editor display text for a token whose referent no longer exists. */\nexport const MISSING_BINDING_LABEL = 'missing binding'\n\n/**\n * Rewrites id tokens to friendly name forms for editor display (AGL-186):\n * `{{var:abc123}}` shows as `{{Message}}` (the CURRENT name — renames\n * reflect immediately), and tokens whose doc was deleted show as\n * `{{missing binding}}` instead of leaking raw ids. Display only — never\n * persist the result.\n */\nexport function displayBindingTokens(\n text: string,\n variables: Record<string, BindingDocRef> = {},\n functions: Record<string, BindingDocRef> = {},\n): string {\n const withVariables = text.replace(\n VARIABLE_ID_TOKEN_PATTERN,\n (_token, id) => {\n const variable = variables[String(id)]\n return `{{${variable?.name ?? MISSING_BINDING_LABEL}}}`\n },\n )\n return withVariables.replace(\n FUNCTION_TOKEN_PATTERN,\n (token, ref, rawArgs) => {\n const definition = functions[String(ref).trim()]\n // Name-form refs already read fine; only id-form refs get mapped.\n if (!definition || definition.name === String(ref).trim()) return token\n return `{{fn:${definition.name ?? MISSING_BINDING_LABEL}(${String(rawArgs)})}}`\n },\n )\n}\n"],"names":["REF","VARIABLE_ID_TOKEN_PATTERN","RegExp","FUNCTION_TOKEN_PATTERN","NAME_TOKEN_PATTERN","formatVariableIdToken","variableId","formatFunctionIdToken","functionId","parameterNames","join","keyById","docs","map","id","doc","normalizeBindingTokens","text","variables","functions","withFunctions","replace","token","ref","rawArgs","definition","String","trim","$id","name","variable","TYPED_NAME_TOKEN","source","editableBindingTokens","includes","rawId","Object","values","find","typed","test","escapeRegExp","value","textReferencesBinding","via","kind","push","nodesReferenceBinding","nodes","Set","node","props","match","add","size","rewriteBindingTokensDeep","changed","next","Array","isArray","item","result","key","entries","bindingTokenNeedsDeep","needs","nameToken","functionToken","walk","current","MISSING_BINDING_LABEL","displayBindingTokens","withVariables","_token"],"mappings":"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"}
@@ -0,0 +1,36 @@
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 { ResolvedBrandingProfile } from './platform-brand';
18
+ /**
19
+ * The "Need help?" footer of a built-in transactional email — **or nothing**
20
+ * (AGL-2428).
21
+ *
22
+ * A white-label organization that left its Support URL blank resolves
23
+ * `supportUrl` to null, and this returns the empty string rather than a line
24
+ * pointing at Aglyn. The recipient of that mail is the organization's own
25
+ * customer: our desk cannot help them, and the link would name their vendor
26
+ * to somebody who was never told one exists.
27
+ *
28
+ * It returns the SEPARATOR too, not just the sentence. A caller that
29
+ * concatenated `\n\n` itself would leave two blank lines at the end of the
30
+ * message for exactly the orgs this exists for — the "gap where something
31
+ * should be" that `emailLogoUrl` already avoids one field over.
32
+ *
33
+ * Every org WITHOUT the concealment entitlement resolves to the platform
34
+ * profile, whose support URL is real, so those emails are unchanged.
35
+ */
36
+ export declare function brandSupportLine(branding: Pick<ResolvedBrandingProfile, 'supportUrl'>): string;
@@ -0,0 +1,38 @@
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
+ * The "Need help?" footer of a built-in transactional email — **or nothing**
18
+ * (AGL-2428).
19
+ *
20
+ * A white-label organization that left its Support URL blank resolves
21
+ * `supportUrl` to null, and this returns the empty string rather than a line
22
+ * pointing at Aglyn. The recipient of that mail is the organization's own
23
+ * customer: our desk cannot help them, and the link would name their vendor
24
+ * to somebody who was never told one exists.
25
+ *
26
+ * It returns the SEPARATOR too, not just the sentence. A caller that
27
+ * concatenated `\n\n` itself would leave two blank lines at the end of the
28
+ * message for exactly the orgs this exists for — the "gap where something
29
+ * should be" that `emailLogoUrl` already avoids one field over.
30
+ *
31
+ * Every org WITHOUT the concealment entitlement resolves to the platform
32
+ * profile, whose support URL is real, so those emails are unchanged.
33
+ */ export function brandSupportLine(branding) {
34
+ const url = typeof branding.supportUrl === 'string' ? branding.supportUrl.trim() : '';
35
+ return url ? `\n\nNeed help? ${url}` : '';
36
+ }
37
+
38
+ //# sourceMappingURL=brand-support-line.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/brand-support-line.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 type { ResolvedBrandingProfile } from './platform-brand'\n\n/**\n * The \"Need help?\" footer of a built-in transactional email — **or nothing**\n * (AGL-2428).\n *\n * A white-label organization that left its Support URL blank resolves\n * `supportUrl` to null, and this returns the empty string rather than a line\n * pointing at Aglyn. The recipient of that mail is the organization's own\n * customer: our desk cannot help them, and the link would name their vendor\n * to somebody who was never told one exists.\n *\n * It returns the SEPARATOR too, not just the sentence. A caller that\n * concatenated `\\n\\n` itself would leave two blank lines at the end of the\n * message for exactly the orgs this exists for — the \"gap where something\n * should be\" that `emailLogoUrl` already avoids one field over.\n *\n * Every org WITHOUT the concealment entitlement resolves to the platform\n * profile, whose support URL is real, so those emails are unchanged.\n */\nexport function brandSupportLine(\n branding: Pick<ResolvedBrandingProfile, 'supportUrl'>,\n): string {\n const url = typeof branding.supportUrl === 'string' ? branding.supportUrl.trim() : ''\n return url ? `\\n\\nNeed help? ${url}` : ''\n}\n"],"names":["brandSupportLine","branding","url","supportUrl","trim"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAID;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASA,iBACdC,QAAqD;IAErD,MAAMC,MAAM,OAAOD,SAASE,UAAU,KAAK,WAAWF,SAASE,UAAU,CAACC,IAAI,KAAK;IACnF,OAAOF,MAAM,CAAC,eAAe,EAAEA,KAAK,GAAG;AACzC"}
@@ -42,7 +42,7 @@
42
42
  *
43
43
  * `localStorage`, expiring at {@link ATTRIBUTION_WINDOW_DAYS}.
44
44
  *
45
- * `campaign-forwarding.ts` holds its own campaign in `sessionStorage` and
45
+ * `utm-forwarding.ts` holds its own campaign in `sessionStorage` and
46
46
  * argues for it: a first touch that outlived the visit would start
47
47
  * attributing next week's organic return to this week's ad. That argument is
48
48
  * correct for the question it answers — which campaign produced THIS SIGNUP,
@@ -64,7 +64,7 @@
64
64
  * The revenue join credits the LAST click, and a product where a lead and an
65
65
  * order attribute by different rules is worse than one that is uniformly
66
66
  * approximate. So a new campaign arrival OVERWRITES the stored one. This is
67
- * the deliberate opposite of {@link rememberVisitCampaign}, which keeps the
67
+ * the deliberate opposite of {@link rememberVisitUtm}, which keeps the
68
68
  * first — that one describes a single visit's origin and this one describes
69
69
  * which campaign most recently brought a person back.
70
70
  *
@@ -104,7 +104,7 @@
104
104
  * ## What is never carried
105
105
  *
106
106
  * Only the three allowlisted `utm_` labels, scrubbed by
107
- * {@link parseCampaignAttribution} — which refuses an email-shaped value
107
+ * {@link parseUtmAttribution} — which refuses an email-shaped value
108
108
  * outright. No address, no person's identifier, no click id, nothing that
109
109
  * names the visitor. A campaign link is exactly where putting a recipient in
110
110
  * a query string would be tempting, and the parser is what makes it
@@ -120,7 +120,7 @@
120
120
  * are the WEB channel's touch, and a campaign that carries none is joined by
121
121
  * address instead.
122
122
  */
123
- import { type CampaignAttribution } from './campaign-attribution';
123
+ import { type UtmAttribution } from './utm-attribution';
124
124
  /** Days between a campaign touch and a conversion it may be credited with. */
125
125
  export declare const ATTRIBUTION_WINDOW_DAYS = 7;
126
126
  /** The same window in milliseconds. */
@@ -137,7 +137,7 @@ export declare const CAMPAIGN_TOUCH_STORAGE_KEY = "aglyn:campaign-touch";
137
137
  */
138
138
  export declare const CAMPAIGN_TOUCH_TIME_KEY = "t";
139
139
  /** A campaign the visitor arrived from, and when they arrived from it. */
140
- export interface CampaignTouch extends CampaignAttribution {
140
+ export interface CampaignTouch extends UtmAttribution {
141
141
  /** When the visitor followed the campaign link, epoch ms. */
142
142
  atMs: number;
143
143
  }
@@ -42,7 +42,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
42
42
  *
43
43
  * `localStorage`, expiring at {@link ATTRIBUTION_WINDOW_DAYS}.
44
44
  *
45
- * `campaign-forwarding.ts` holds its own campaign in `sessionStorage` and
45
+ * `utm-forwarding.ts` holds its own campaign in `sessionStorage` and
46
46
  * argues for it: a first touch that outlived the visit would start
47
47
  * attributing next week's organic return to this week's ad. That argument is
48
48
  * correct for the question it answers — which campaign produced THIS SIGNUP,
@@ -64,7 +64,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
64
64
  * The revenue join credits the LAST click, and a product where a lead and an
65
65
  * order attribute by different rules is worse than one that is uniformly
66
66
  * approximate. So a new campaign arrival OVERWRITES the stored one. This is
67
- * the deliberate opposite of {@link rememberVisitCampaign}, which keeps the
67
+ * the deliberate opposite of {@link rememberVisitUtm}, which keeps the
68
68
  * first — that one describes a single visit's origin and this one describes
69
69
  * which campaign most recently brought a person back.
70
70
  *
@@ -104,7 +104,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
104
104
  * ## What is never carried
105
105
  *
106
106
  * Only the three allowlisted `utm_` labels, scrubbed by
107
- * {@link parseCampaignAttribution} — which refuses an email-shaped value
107
+ * {@link parseUtmAttribution} — which refuses an email-shaped value
108
108
  * outright. No address, no person's identifier, no click id, nothing that
109
109
  * names the visitor. A campaign link is exactly where putting a recipient in
110
110
  * a query string would be tempting, and the parser is what makes it
@@ -119,7 +119,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
119
119
  * recorded server-side against the recipient's address hash. The labels below
120
120
  * are the WEB channel's touch, and a campaign that carries none is joined by
121
121
  * address instead.
122
- */ import { campaignAttributionQuery, parseCampaignAttribution } from "./campaign-attribution.js";
122
+ */ import { utmAttributionQuery, parseUtmAttribution } from "./utm-attribution.js";
123
123
  /*==========================================
124
124
  * THE WINDOW, AND WHY THIS IS THE SECOND COPY OF A NUMBER.
125
125
  *
@@ -183,7 +183,7 @@ function localStore() {
183
183
  * cannot drift from what {@link parseCampaignTouch} reads back.
184
184
  */ export function campaignTouchWire(touch) {
185
185
  if (!touch) return '';
186
- const labels = campaignAttributionQuery(touch);
186
+ const labels = utmAttributionQuery(touch);
187
187
  if (!labels) return '';
188
188
  const atMs = Math.round(Number(touch.atMs));
189
189
  if (!Number.isFinite(atMs) || atMs <= 0) return '';
@@ -205,7 +205,7 @@ function localStore() {
205
205
  } catch (unused) {
206
206
  return null;
207
207
  }
208
- const campaign = parseCampaignAttribution(params);
208
+ const campaign = parseUtmAttribution(params);
209
209
  if (!campaign) return null;
210
210
  const atMs = Number(params.get(CAMPAIGN_TOUCH_TIME_KEY));
211
211
  if (!touchIsInWindow(atMs, nowMs)) return null;
@@ -249,7 +249,7 @@ function localStore() {
249
249
  const store = localStore();
250
250
  if (!store) return null;
251
251
  const source = typeof search === 'string' ? search : typeof window === 'undefined' ? '' : window.location.search;
252
- const campaign = parseCampaignAttribution(new URLSearchParams(source));
252
+ const campaign = parseUtmAttribution(new URLSearchParams(source));
253
253
  if (!campaign) return null;
254
254
  const touch = _extends({}, campaign, {
255
255
  atMs: nowMs
@@ -282,7 +282,7 @@ function localStore() {
282
282
  * device would be keeping a record of where somebody came from for no purpose
283
283
  * anything reads. The read that finds it removes it.
284
284
  */ export function readCampaignTouch(nowMs = Date.now()) {
285
- const live = typeof window === 'undefined' ? null : parseCampaignAttribution(new URLSearchParams(window.location.search));
285
+ const live = typeof window === 'undefined' ? null : parseUtmAttribution(new URLSearchParams(window.location.search));
286
286
  if (live) return _extends({}, live, {
287
287
  atMs: nowMs
288
288
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/campaign-touch.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 * THE VISITOR'S HALF OF CAMPAIGN ATTRIBUTION — the touch that has to survive\n * the gap between arriving from a campaign and becoming somebody.\n *\n * ## The gap this exists to cross\n *\n * A known recipient can be joined on their address: they clicked a campaign's\n * mail, the delivery webhook stamped the click on their person document, and\n * `email-revenue-attribution.ts` reads it back when their order settles. An\n * ANONYMOUS visitor has no such handle. They arrive from an ad, a partner\n * link or a social post, browse for a while, and only become identifiable at\n * the moment they submit a form, sign up, book or check out. Between the\n * arrival and that moment there is nothing on the server that connects them.\n *\n * The site's own collector already reads `utm_source`/`utm_medium`/\n * `utm_campaign` off the landing URL and increments a per-day label counter\n * with them. That answers \"how many views did this campaign send us\" and\n * stops there — the labels are never carried to the moment the visitor says\n * who they are, so every form, lead, contact and booking a campaign caused is\n * recorded as though nobody caused it.\n *\n * This carries them. The touch is held on the VISITOR, and every identify\n * moment attaches it.\n *\n * ## What holds it, and why it outlives the tab\n *\n * `localStorage`, expiring at {@link ATTRIBUTION_WINDOW_DAYS}.\n *\n * `campaign-forwarding.ts` holds its own campaign in `sessionStorage` and\n * argues for it: a first touch that outlived the visit would start\n * attributing next week's organic return to this week's ad. That argument is\n * correct for the question it answers — which campaign produced THIS SIGNUP,\n * asked of one uninterrupted visit to the marketing site — and it is the\n * wrong shape here, because this question already has a stated answer to\n * \"how long may a touch be credited\": the attribution window, seven days,\n * stamped onto every record so the rule is readable off the data.\n *\n * A store that dies with the tab cannot express a seven-day window. It would\n * credit only the visitors who convert without ever closing the tab and\n * silently record every other conversion as organic — a measured zero, which\n * is the failure mode this whole area exists to end. So the touch lives as\n * long as the window says it may and not one millisecond longer: an expired\n * entry is DELETED on the read that finds it, rather than ignored, so a\n * device never holds a campaign it can no longer be credited with.\n *\n * ## Last touch, not first\n *\n * The revenue join credits the LAST click, and a product where a lead and an\n * order attribute by different rules is worse than one that is uniformly\n * approximate. So a new campaign arrival OVERWRITES the stored one. This is\n * the deliberate opposite of {@link rememberVisitCampaign}, which keeps the\n * first — that one describes a single visit's origin and this one describes\n * which campaign most recently brought a person back.\n *\n * ## Consent\n *\n * Two tiers, exactly as the console hop splits them, and the split is the\n * same because the acts are the same:\n *\n * - **The live URL at the moment of conversion** touches no storage. The\n * parameters are already in the page the visitor asked for, nothing is\n * written to their device, and there is accordingly nothing to consent to.\n * A visitor who converts on the page the ad landed them on is attributed\n * with no grant of any kind.\n * - **The remembered touch** is written to the visitor's device for an\n * analytics purpose, which is `analytics_storage`, and it waits for the\n * same grant the analytics tag waits for. The gate is not re-derived here:\n * the caller already computed it for the tag and hands the same boolean\n * down, so there is one gate and it cannot drift from itself.\n *\n * `null` is UNRESOLVED and is not `false`. With `strictNullChecks` off\n * repo-wide that distinction evaporates unless it is carried explicitly, so\n * it is: until the visitor's state is actually settled, nothing is read and\n * nothing is written. Failing closed costs an attribution; failing open\n * writes to a device before the visitor answered.\n *\n * On a WITHDRAWAL the stored touch is removed rather than merely ignored.\n * Somebody who changes their mind should not leave the thing they withdrew\n * consent for sitting on their device.\n *\n * ⛔ **A host whose posture requires prior consent and whose visitor has not\n * given it produces no remembered touch at all.** Their conversion attributes\n * to nothing, exactly as direct traffic does. That is the degradation, stated\n * rather than worked around: there is no fallback identifier, no fingerprint\n * and no server-side cookie, because every one of those is the durable\n * identifier the analytics posture on this runtime deliberately does not set.\n *\n * ## What is never carried\n *\n * Only the three allowlisted `utm_` labels, scrubbed by\n * {@link parseCampaignAttribution} — which refuses an email-shaped value\n * outright. No address, no person's identifier, no click id, nothing that\n * names the visitor. A campaign link is exactly where putting a recipient in\n * a query string would be tempting, and the parser is what makes it\n * impossible rather than merely discouraged: the stored string is re-parsed\n * through the same allowlist that wrote it, so a hand-edited `localStorage`\n * entry can claim no more than a hand-edited URL could.\n *\n * ⛔ **Nothing here depends on a parameter surviving the mail provider's\n * click wrapper.** A campaign link is rewritten by the provider and redirects\n * to the authored URL, so whatever the marketer put on it arrives — but the\n * email channel does not rely on that, because an email click is already\n * recorded server-side against the recipient's address hash. The labels below\n * are the WEB channel's touch, and a campaign that carries none is joined by\n * address instead.\n */\n\nimport {\n campaignAttributionQuery,\n parseCampaignAttribution,\n type CampaignAttribution,\n} from './campaign-attribution'\n\n/*==========================================\n * THE WINDOW, AND WHY THIS IS THE SECOND COPY OF A NUMBER.\n *\n * `email-revenue-window.ts` in `shared-util-email` declares the same seven\n * days, and its docblock is right that a window defined twice is a window\n * that drifts. Importing it here is not available: `shared-util-email` is\n * tagged `scope:shared` and reaches back into `@aglyn/aglyn`, so an edge in\n * this direction closes a project cycle and the module-boundary rule refuses\n * it. That is the same wall `email-media-src.ts` hit, and the answer is the\n * same one it took — a deliberate copy, pinned by a drift guard in an app\n * spec that can import both sides (`attribution-window-drift.spec.ts`).\n *\n * The copy is the WINDOW only. The model name is stamped onto records, which\n * happens server-side, so it stays in one place and never comes near this\n * file.\n *=========================================*/\n\n/** Days between a campaign touch and a conversion it may be credited with. */\nexport const ATTRIBUTION_WINDOW_DAYS = 7\n\n/** The same window in milliseconds. */\nexport const ATTRIBUTION_WINDOW_MS =\n ATTRIBUTION_WINDOW_DAYS * 24 * 60 * 60 * 1000\n\n/**\n * Whether a touch may be credited with a conversion at `convertedAtMs`.\n *\n * Both bounds matter and they fail differently, exactly as the revenue join\n * describes them: a touch AFTER the conversion is the receipt rather than the\n * cause, and a touch older than the window is one nobody can argue caused\n * anything. Inclusive at both ends.\n */\nfunction touchIsInWindow(touchedAtMs: number, convertedAtMs: number): boolean {\n if (!Number.isFinite(touchedAtMs) || !Number.isFinite(convertedAtMs)) {\n return false\n }\n if (touchedAtMs <= 0 || convertedAtMs <= 0) return false\n const age = convertedAtMs - touchedAtMs\n return age >= 0 && age <= ATTRIBUTION_WINDOW_MS\n}\n\n/** Where the touch is held. Namespaced like every other key this app sets. */\nexport const CAMPAIGN_TOUCH_STORAGE_KEY = 'aglyn:campaign-touch'\n\n/**\n * The parameter the touch's instant rides under inside the wire form.\n *\n * Deliberately not a `utm_` name: the wire form is parsed by the same\n * allowlist that reads a URL, and a fourth `utm_` key would either be dropped\n * by it or have to widen it. `t` is outside the allowlist and read\n * separately.\n */\nexport const CAMPAIGN_TOUCH_TIME_KEY = 't'\n\n/** A campaign the visitor arrived from, and when they arrived from it. */\nexport interface CampaignTouch extends CampaignAttribution {\n /** When the visitor followed the campaign link, epoch ms. */\n atMs: number\n}\n\n/**\n * Whether the visitor has granted analytics storage.\n *\n * `null` means NOT YET RESOLVED and is not the same as `false` — see the\n * consent section above.\n */\nlet storageConsent: boolean | null = null\n\nfunction localStore(): Storage | null {\n if (typeof window === 'undefined') return null\n // Touching the property itself throws in some privacy modes, not just its\n // methods.\n try {\n return window.localStorage ?? null\n } catch {\n return null\n }\n}\n\n/**\n * The canonical stored form of a touch — the ONLY place it is written, so it\n * cannot drift from what {@link parseCampaignTouch} reads back.\n */\nexport function campaignTouchWire(touch: CampaignTouch | null | undefined): string {\n if (!touch) return ''\n const labels = campaignAttributionQuery(touch)\n if (!labels) return ''\n const atMs = Math.round(Number(touch.atMs))\n if (!Number.isFinite(atMs) || atMs <= 0) return ''\n return `${labels}&${CAMPAIGN_TOUCH_TIME_KEY}=${atMs}`\n}\n\n/**\n * Read a stored or transmitted touch back, or `null`.\n *\n * The window is enforced HERE rather than at the call sites, so every reader\n * — the browser deciding what to send, the server deciding what to credit —\n * agrees about what an expired touch is: nothing at all. A touch dated in the\n * future is refused for the reason the revenue join refuses one: a click\n * after the conversion is the receipt, not the cause.\n */\nexport function parseCampaignTouch(\n wire: unknown,\n nowMs: number = Date.now(),\n): CampaignTouch | null {\n if (typeof wire !== 'string' || !wire) return null\n let params: URLSearchParams\n try {\n params = new URLSearchParams(wire)\n } catch {\n return null\n }\n const campaign = parseCampaignAttribution(params)\n if (!campaign) return null\n const atMs = Number(params.get(CAMPAIGN_TOUCH_TIME_KEY))\n if (!touchIsInWindow(atMs, nowMs)) return null\n return { ...campaign, atMs }\n}\n\n/**\n * Tell this module what the visitor's analytics consent state is.\n *\n * Called by whoever already computed it for the tag, on every render, so a\n * grant remembers the arrival the moment it is given and a withdrawal drops\n * the stored touch immediately.\n */\nexport function setCampaignTouchConsent(allowed: boolean | null): void {\n storageConsent = allowed === true ? true : allowed === false ? false : null\n if (storageConsent === true) {\n rememberCampaignTouch()\n return\n }\n if (storageConsent === false) {\n const store = localStore()\n if (!store) return\n try {\n store.removeItem(CAMPAIGN_TOUCH_STORAGE_KEY)\n } catch {\n // Nothing else to try, and a failed cleanup must not break the page.\n }\n }\n}\n\n/**\n * Remember the campaign on the current URL as this visitor's latest touch.\n *\n * Overwrites, because the model is last touch. A URL naming no campaign\n * writes NOTHING and clears nothing: a visitor who arrives from an ad and\n * then browses ten organic pages has one touch, not one touch erased by the\n * second pageview.\n *\n * @returns what is now remembered, or `null`.\n */\nexport function rememberCampaignTouch(\n search?: string,\n nowMs: number = Date.now(),\n): CampaignTouch | null {\n if (storageConsent !== true) return null\n const store = localStore()\n if (!store) return null\n const source =\n typeof search === 'string'\n ? search\n : typeof window === 'undefined'\n ? ''\n : window.location.search\n const campaign = parseCampaignAttribution(new URLSearchParams(source))\n if (!campaign) return null\n const touch: CampaignTouch = { ...campaign, atMs: nowMs }\n const wire = campaignTouchWire(touch)\n if (!wire) return null\n try {\n store.setItem(CAMPAIGN_TOUCH_STORAGE_KEY, wire)\n } catch {\n // A store that refuses the write costs the walk from the landing page to\n // the conversion, never a conversion on the landing page itself — the\n // live URL below still carries that visitor.\n return null\n }\n return touch\n}\n\n/**\n * The touch to credit a conversion happening right now, or `null`.\n *\n * ## The order is the model\n *\n * The live URL is consulted FIRST and wins whenever it names a campaign,\n * because a campaign on the address bar at the moment of conversion is by\n * definition the most recent touch there is. Only when the current page names\n * none does this fall back to what was remembered.\n *\n * ## An expired entry is deleted, not skipped\n *\n * A touch past the window can never be credited again, so leaving it on the\n * device would be keeping a record of where somebody came from for no purpose\n * anything reads. The read that finds it removes it.\n */\nexport function readCampaignTouch(\n nowMs: number = Date.now(),\n): CampaignTouch | null {\n const live =\n typeof window === 'undefined'\n ? null\n : parseCampaignAttribution(new URLSearchParams(window.location.search))\n if (live) return { ...live, atMs: nowMs }\n\n if (storageConsent !== true) return null\n const store = localStore()\n if (!store) return null\n let raw: string | null\n try {\n raw = store.getItem(CAMPAIGN_TOUCH_STORAGE_KEY)\n } catch {\n return null\n }\n if (!raw) return null\n const touch = parseCampaignTouch(raw, nowMs)\n if (!touch) {\n try {\n store.removeItem(CAMPAIGN_TOUCH_STORAGE_KEY)\n } catch {\n // The entry stays until the next read finds it again; it is already\n // uncreditable, so nothing downstream is affected.\n }\n return null\n }\n return touch\n}\n\n/**\n * The fragment a conversion request spreads into its body, or `{}`.\n *\n * One helper rather than four hand-written spreads: a door that forgets it\n * reports its conversions as organic, which looks exactly like a campaign\n * that produced none. Empty when there is no touch — never a placeholder,\n * never `campaignTouch: undefined`, so that \"arrived from nowhere\" and \"this\n * door does not report\" stay distinguishable on the wire.\n */\nexport function campaignTouchField(\n nowMs: number = Date.now(),\n): { campaignTouch?: string } {\n const wire = campaignTouchWire(readCampaignTouch(nowMs))\n return wire ? { campaignTouch: wire } : {}\n}\n"],"names":["campaignAttributionQuery","parseCampaignAttribution","ATTRIBUTION_WINDOW_DAYS","ATTRIBUTION_WINDOW_MS","touchIsInWindow","touchedAtMs","convertedAtMs","Number","isFinite","age","CAMPAIGN_TOUCH_STORAGE_KEY","CAMPAIGN_TOUCH_TIME_KEY","storageConsent","localStore","window","localStorage","campaignTouchWire","touch","labels","atMs","Math","round","parseCampaignTouch","wire","nowMs","Date","now","params","URLSearchParams","campaign","get","setCampaignTouchConsent","allowed","rememberCampaignTouch","store","removeItem","search","source","location","setItem","readCampaignTouch","live","raw","getItem","campaignTouchField","campaignTouch"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/campaign-touch.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 * THE VISITOR'S HALF OF CAMPAIGN ATTRIBUTION — the touch that has to survive\n * the gap between arriving from a campaign and becoming somebody.\n *\n * ## The gap this exists to cross\n *\n * A known recipient can be joined on their address: they clicked a campaign's\n * mail, the delivery webhook stamped the click on their person document, and\n * `email-revenue-attribution.ts` reads it back when their order settles. An\n * ANONYMOUS visitor has no such handle. They arrive from an ad, a partner\n * link or a social post, browse for a while, and only become identifiable at\n * the moment they submit a form, sign up, book or check out. Between the\n * arrival and that moment there is nothing on the server that connects them.\n *\n * The site's own collector already reads `utm_source`/`utm_medium`/\n * `utm_campaign` off the landing URL and increments a per-day label counter\n * with them. That answers \"how many views did this campaign send us\" and\n * stops there — the labels are never carried to the moment the visitor says\n * who they are, so every form, lead, contact and booking a campaign caused is\n * recorded as though nobody caused it.\n *\n * This carries them. The touch is held on the VISITOR, and every identify\n * moment attaches it.\n *\n * ## What holds it, and why it outlives the tab\n *\n * `localStorage`, expiring at {@link ATTRIBUTION_WINDOW_DAYS}.\n *\n * `utm-forwarding.ts` holds its own campaign in `sessionStorage` and\n * argues for it: a first touch that outlived the visit would start\n * attributing next week's organic return to this week's ad. That argument is\n * correct for the question it answers — which campaign produced THIS SIGNUP,\n * asked of one uninterrupted visit to the marketing site — and it is the\n * wrong shape here, because this question already has a stated answer to\n * \"how long may a touch be credited\": the attribution window, seven days,\n * stamped onto every record so the rule is readable off the data.\n *\n * A store that dies with the tab cannot express a seven-day window. It would\n * credit only the visitors who convert without ever closing the tab and\n * silently record every other conversion as organic — a measured zero, which\n * is the failure mode this whole area exists to end. So the touch lives as\n * long as the window says it may and not one millisecond longer: an expired\n * entry is DELETED on the read that finds it, rather than ignored, so a\n * device never holds a campaign it can no longer be credited with.\n *\n * ## Last touch, not first\n *\n * The revenue join credits the LAST click, and a product where a lead and an\n * order attribute by different rules is worse than one that is uniformly\n * approximate. So a new campaign arrival OVERWRITES the stored one. This is\n * the deliberate opposite of {@link rememberVisitUtm}, which keeps the\n * first — that one describes a single visit's origin and this one describes\n * which campaign most recently brought a person back.\n *\n * ## Consent\n *\n * Two tiers, exactly as the console hop splits them, and the split is the\n * same because the acts are the same:\n *\n * - **The live URL at the moment of conversion** touches no storage. The\n * parameters are already in the page the visitor asked for, nothing is\n * written to their device, and there is accordingly nothing to consent to.\n * A visitor who converts on the page the ad landed them on is attributed\n * with no grant of any kind.\n * - **The remembered touch** is written to the visitor's device for an\n * analytics purpose, which is `analytics_storage`, and it waits for the\n * same grant the analytics tag waits for. The gate is not re-derived here:\n * the caller already computed it for the tag and hands the same boolean\n * down, so there is one gate and it cannot drift from itself.\n *\n * `null` is UNRESOLVED and is not `false`. With `strictNullChecks` off\n * repo-wide that distinction evaporates unless it is carried explicitly, so\n * it is: until the visitor's state is actually settled, nothing is read and\n * nothing is written. Failing closed costs an attribution; failing open\n * writes to a device before the visitor answered.\n *\n * On a WITHDRAWAL the stored touch is removed rather than merely ignored.\n * Somebody who changes their mind should not leave the thing they withdrew\n * consent for sitting on their device.\n *\n * ⛔ **A host whose posture requires prior consent and whose visitor has not\n * given it produces no remembered touch at all.** Their conversion attributes\n * to nothing, exactly as direct traffic does. That is the degradation, stated\n * rather than worked around: there is no fallback identifier, no fingerprint\n * and no server-side cookie, because every one of those is the durable\n * identifier the analytics posture on this runtime deliberately does not set.\n *\n * ## What is never carried\n *\n * Only the three allowlisted `utm_` labels, scrubbed by\n * {@link parseUtmAttribution} — which refuses an email-shaped value\n * outright. No address, no person's identifier, no click id, nothing that\n * names the visitor. A campaign link is exactly where putting a recipient in\n * a query string would be tempting, and the parser is what makes it\n * impossible rather than merely discouraged: the stored string is re-parsed\n * through the same allowlist that wrote it, so a hand-edited `localStorage`\n * entry can claim no more than a hand-edited URL could.\n *\n * ⛔ **Nothing here depends on a parameter surviving the mail provider's\n * click wrapper.** A campaign link is rewritten by the provider and redirects\n * to the authored URL, so whatever the marketer put on it arrives — but the\n * email channel does not rely on that, because an email click is already\n * recorded server-side against the recipient's address hash. The labels below\n * are the WEB channel's touch, and a campaign that carries none is joined by\n * address instead.\n */\n\nimport {\n utmAttributionQuery,\n parseUtmAttribution,\n type UtmAttribution,\n} from './utm-attribution'\n\n/*==========================================\n * THE WINDOW, AND WHY THIS IS THE SECOND COPY OF A NUMBER.\n *\n * `email-revenue-window.ts` in `shared-util-email` declares the same seven\n * days, and its docblock is right that a window defined twice is a window\n * that drifts. Importing it here is not available: `shared-util-email` is\n * tagged `scope:shared` and reaches back into `@aglyn/aglyn`, so an edge in\n * this direction closes a project cycle and the module-boundary rule refuses\n * it. That is the same wall `email-media-src.ts` hit, and the answer is the\n * same one it took — a deliberate copy, pinned by a drift guard in an app\n * spec that can import both sides (`attribution-window-drift.spec.ts`).\n *\n * The copy is the WINDOW only. The model name is stamped onto records, which\n * happens server-side, so it stays in one place and never comes near this\n * file.\n *=========================================*/\n\n/** Days between a campaign touch and a conversion it may be credited with. */\nexport const ATTRIBUTION_WINDOW_DAYS = 7\n\n/** The same window in milliseconds. */\nexport const ATTRIBUTION_WINDOW_MS =\n ATTRIBUTION_WINDOW_DAYS * 24 * 60 * 60 * 1000\n\n/**\n * Whether a touch may be credited with a conversion at `convertedAtMs`.\n *\n * Both bounds matter and they fail differently, exactly as the revenue join\n * describes them: a touch AFTER the conversion is the receipt rather than the\n * cause, and a touch older than the window is one nobody can argue caused\n * anything. Inclusive at both ends.\n */\nfunction touchIsInWindow(touchedAtMs: number, convertedAtMs: number): boolean {\n if (!Number.isFinite(touchedAtMs) || !Number.isFinite(convertedAtMs)) {\n return false\n }\n if (touchedAtMs <= 0 || convertedAtMs <= 0) return false\n const age = convertedAtMs - touchedAtMs\n return age >= 0 && age <= ATTRIBUTION_WINDOW_MS\n}\n\n/** Where the touch is held. Namespaced like every other key this app sets. */\nexport const CAMPAIGN_TOUCH_STORAGE_KEY = 'aglyn:campaign-touch'\n\n/**\n * The parameter the touch's instant rides under inside the wire form.\n *\n * Deliberately not a `utm_` name: the wire form is parsed by the same\n * allowlist that reads a URL, and a fourth `utm_` key would either be dropped\n * by it or have to widen it. `t` is outside the allowlist and read\n * separately.\n */\nexport const CAMPAIGN_TOUCH_TIME_KEY = 't'\n\n/** A campaign the visitor arrived from, and when they arrived from it. */\nexport interface CampaignTouch extends UtmAttribution {\n /** When the visitor followed the campaign link, epoch ms. */\n atMs: number\n}\n\n/**\n * Whether the visitor has granted analytics storage.\n *\n * `null` means NOT YET RESOLVED and is not the same as `false` — see the\n * consent section above.\n */\nlet storageConsent: boolean | null = null\n\nfunction localStore(): Storage | null {\n if (typeof window === 'undefined') return null\n // Touching the property itself throws in some privacy modes, not just its\n // methods.\n try {\n return window.localStorage ?? null\n } catch {\n return null\n }\n}\n\n/**\n * The canonical stored form of a touch — the ONLY place it is written, so it\n * cannot drift from what {@link parseCampaignTouch} reads back.\n */\nexport function campaignTouchWire(touch: CampaignTouch | null | undefined): string {\n if (!touch) return ''\n const labels = utmAttributionQuery(touch)\n if (!labels) return ''\n const atMs = Math.round(Number(touch.atMs))\n if (!Number.isFinite(atMs) || atMs <= 0) return ''\n return `${labels}&${CAMPAIGN_TOUCH_TIME_KEY}=${atMs}`\n}\n\n/**\n * Read a stored or transmitted touch back, or `null`.\n *\n * The window is enforced HERE rather than at the call sites, so every reader\n * — the browser deciding what to send, the server deciding what to credit —\n * agrees about what an expired touch is: nothing at all. A touch dated in the\n * future is refused for the reason the revenue join refuses one: a click\n * after the conversion is the receipt, not the cause.\n */\nexport function parseCampaignTouch(\n wire: unknown,\n nowMs: number = Date.now(),\n): CampaignTouch | null {\n if (typeof wire !== 'string' || !wire) return null\n let params: URLSearchParams\n try {\n params = new URLSearchParams(wire)\n } catch {\n return null\n }\n const campaign = parseUtmAttribution(params)\n if (!campaign) return null\n const atMs = Number(params.get(CAMPAIGN_TOUCH_TIME_KEY))\n if (!touchIsInWindow(atMs, nowMs)) return null\n return { ...campaign, atMs }\n}\n\n/**\n * Tell this module what the visitor's analytics consent state is.\n *\n * Called by whoever already computed it for the tag, on every render, so a\n * grant remembers the arrival the moment it is given and a withdrawal drops\n * the stored touch immediately.\n */\nexport function setCampaignTouchConsent(allowed: boolean | null): void {\n storageConsent = allowed === true ? true : allowed === false ? false : null\n if (storageConsent === true) {\n rememberCampaignTouch()\n return\n }\n if (storageConsent === false) {\n const store = localStore()\n if (!store) return\n try {\n store.removeItem(CAMPAIGN_TOUCH_STORAGE_KEY)\n } catch {\n // Nothing else to try, and a failed cleanup must not break the page.\n }\n }\n}\n\n/**\n * Remember the campaign on the current URL as this visitor's latest touch.\n *\n * Overwrites, because the model is last touch. A URL naming no campaign\n * writes NOTHING and clears nothing: a visitor who arrives from an ad and\n * then browses ten organic pages has one touch, not one touch erased by the\n * second pageview.\n *\n * @returns what is now remembered, or `null`.\n */\nexport function rememberCampaignTouch(\n search?: string,\n nowMs: number = Date.now(),\n): CampaignTouch | null {\n if (storageConsent !== true) return null\n const store = localStore()\n if (!store) return null\n const source =\n typeof search === 'string'\n ? search\n : typeof window === 'undefined'\n ? ''\n : window.location.search\n const campaign = parseUtmAttribution(new URLSearchParams(source))\n if (!campaign) return null\n const touch: CampaignTouch = { ...campaign, atMs: nowMs }\n const wire = campaignTouchWire(touch)\n if (!wire) return null\n try {\n store.setItem(CAMPAIGN_TOUCH_STORAGE_KEY, wire)\n } catch {\n // A store that refuses the write costs the walk from the landing page to\n // the conversion, never a conversion on the landing page itself — the\n // live URL below still carries that visitor.\n return null\n }\n return touch\n}\n\n/**\n * The touch to credit a conversion happening right now, or `null`.\n *\n * ## The order is the model\n *\n * The live URL is consulted FIRST and wins whenever it names a campaign,\n * because a campaign on the address bar at the moment of conversion is by\n * definition the most recent touch there is. Only when the current page names\n * none does this fall back to what was remembered.\n *\n * ## An expired entry is deleted, not skipped\n *\n * A touch past the window can never be credited again, so leaving it on the\n * device would be keeping a record of where somebody came from for no purpose\n * anything reads. The read that finds it removes it.\n */\nexport function readCampaignTouch(\n nowMs: number = Date.now(),\n): CampaignTouch | null {\n const live =\n typeof window === 'undefined'\n ? null\n : parseUtmAttribution(new URLSearchParams(window.location.search))\n if (live) return { ...live, atMs: nowMs }\n\n if (storageConsent !== true) return null\n const store = localStore()\n if (!store) return null\n let raw: string | null\n try {\n raw = store.getItem(CAMPAIGN_TOUCH_STORAGE_KEY)\n } catch {\n return null\n }\n if (!raw) return null\n const touch = parseCampaignTouch(raw, nowMs)\n if (!touch) {\n try {\n store.removeItem(CAMPAIGN_TOUCH_STORAGE_KEY)\n } catch {\n // The entry stays until the next read finds it again; it is already\n // uncreditable, so nothing downstream is affected.\n }\n return null\n }\n return touch\n}\n\n/**\n * The fragment a conversion request spreads into its body, or `{}`.\n *\n * One helper rather than four hand-written spreads: a door that forgets it\n * reports its conversions as organic, which looks exactly like a campaign\n * that produced none. Empty when there is no touch — never a placeholder,\n * never `campaignTouch: undefined`, so that \"arrived from nowhere\" and \"this\n * door does not report\" stay distinguishable on the wire.\n */\nexport function campaignTouchField(\n nowMs: number = Date.now(),\n): { campaignTouch?: string } {\n const wire = campaignTouchWire(readCampaignTouch(nowMs))\n return wire ? { campaignTouch: wire } : {}\n}\n"],"names":["utmAttributionQuery","parseUtmAttribution","ATTRIBUTION_WINDOW_DAYS","ATTRIBUTION_WINDOW_MS","touchIsInWindow","touchedAtMs","convertedAtMs","Number","isFinite","age","CAMPAIGN_TOUCH_STORAGE_KEY","CAMPAIGN_TOUCH_TIME_KEY","storageConsent","localStore","window","localStorage","campaignTouchWire","touch","labels","atMs","Math","round","parseCampaignTouch","wire","nowMs","Date","now","params","URLSearchParams","campaign","get","setCampaignTouchConsent","allowed","rememberCampaignTouch","store","removeItem","search","source","location","setItem","readCampaignTouch","live","raw","getItem","campaignTouchField","campaignTouch"],"mappings":"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"}
@@ -306,8 +306,8 @@ export interface CollectionEntryRecord {
306
306
  coverImage?: string;
307
307
  /**
308
308
  * The entry's featured video (AGL-2956), stored in the shape
309
- * {@link coverImage} is: a `media:{scope}/{id}` reference or a URL, a Wistia
310
- * media link included. Raw here for the reason every image field in this
309
+ * {@link coverImage} is: a `media:{scope}/{id}` reference or a URL, a hosted
310
+ * player's media link included (`video-embed-provider.ts`). Raw here for the reason every image field in this
311
311
  * file is raw — the resolver needs the rendering host, which this record
312
312
  * does not carry.
313
313
  *