@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
@@ -14,9 +14,9 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ /**
17
- * Carry the campaign across the domain hop (AGL-1731).
17
+ * Carry the visit's UTM labels across the domain hop (AGL-1731).
18
18
  *
19
- * `campaign-attribution.ts` is the READER half of this contract and it works.
19
+ * `utm-attribution.ts` is the READER half of this contract and it works.
20
20
  * This is the half that was missing, and without it the reader was fed
21
21
  * nothing: `aglyn.com` is a tenant published site and `app.aglyn.com` is the
22
22
  * console, a real cross-origin hop, and no code anywhere put a `utm_*`
@@ -94,7 +94,7 @@
94
94
  * it. There is no default, no inference from the referrer, and no
95
95
  * `utm_source=direct` — a signup with no campaign must reach the console with
96
96
  * no campaign, so that "arrived from nowhere" and "never asked" stay the
97
- * distinct facts `campaign-attribution.ts` keeps them.
97
+ * distinct facts `utm-attribution.ts` keeps them.
98
98
  *
99
99
  * Only links to OUR OWN console origin are touched, and the origin is supplied
100
100
  * by the caller rather than written here — a self-host install points
@@ -113,13 +113,13 @@
113
113
  * set it would forward NOTHING while looking perfectly healthy. A silent zero
114
114
  * is the exact failure AGL-1731 is about, and it is worse than a few
115
115
  * mislabelled rows that GA's own Hostname dimension can already separate.
116
- */ import { CAMPAIGN_QUERY_KEYS, campaignAttributionQuery, parseCampaignAttribution } from "./campaign-attribution.js";
116
+ */ import { UTM_QUERY_KEYS, utmAttributionQuery, parseUtmAttribution } from "./utm-attribution.js";
117
117
  /**
118
118
  * Where the first touch of this visit is held. `sessionStorage`, so it dies
119
119
  * with the tab: a campaign is a fact about one visit, and a value that
120
120
  * outlived the visit would start attributing next week's organic return to
121
121
  * this week's ad.
122
- */ export const CAMPAIGN_VISIT_STORAGE_KEY = 'aglyn:campaign';
122
+ */ export const UTM_VISIT_STORAGE_KEY = 'aglyn:campaign';
123
123
  /**
124
124
  * Whether the visitor has granted analytics storage.
125
125
  *
@@ -149,7 +149,7 @@ function sessionStore() {
149
149
  * `sessionStorage` is writable by anything running on the page, so the stored
150
150
  * string can claim no more than an inbound URL could — three allowlisted keys,
151
151
  * trimmed, email shapes refused, capped.
152
- */ export function readVisitCampaign() {
152
+ */ export function readVisitUtm() {
153
153
  if (storageConsent !== true) return {
154
154
  status: 'none'
155
155
  };
@@ -159,7 +159,7 @@ function sessionStore() {
159
159
  };
160
160
  let raw;
161
161
  try {
162
- raw = store.getItem(CAMPAIGN_VISIT_STORAGE_KEY);
162
+ raw = store.getItem(UTM_VISIT_STORAGE_KEY);
163
163
  } catch (unused) {
164
164
  return {
165
165
  status: 'unreadable'
@@ -168,7 +168,7 @@ function sessionStore() {
168
168
  if (!raw) return {
169
169
  status: 'none'
170
170
  };
171
- const campaign = parseCampaignAttribution(new URLSearchParams(raw));
171
+ const campaign = parseUtmAttribution(new URLSearchParams(raw));
172
172
  return campaign ? {
173
173
  status: 'campaign',
174
174
  campaign
@@ -185,17 +185,17 @@ function sessionStore() {
185
185
  * quietly turn this into last-touch.
186
186
  *
187
187
  * Returns what is now remembered so a caller can assert on it.
188
- */ export function rememberVisitCampaign(search) {
189
- const existing = readVisitCampaign();
188
+ */ export function rememberVisitUtm(search) {
189
+ const existing = readVisitUtm();
190
190
  if (existing.status === 'campaign') return existing.campaign;
191
191
  if (storageConsent !== true) return null;
192
192
  const store = sessionStore();
193
193
  if (!store) return null;
194
194
  const source = typeof search === 'string' ? search : typeof window === 'undefined' ? '' : window.location.search;
195
- const campaign = parseCampaignAttribution(new URLSearchParams(source));
195
+ const campaign = parseUtmAttribution(new URLSearchParams(source));
196
196
  if (!campaign) return null;
197
197
  try {
198
- store.setItem(CAMPAIGN_VISIT_STORAGE_KEY, campaignAttributionQuery(campaign));
198
+ store.setItem(UTM_VISIT_STORAGE_KEY, utmAttributionQuery(campaign));
199
199
  } catch (unused) {
200
200
  // A store that refuses the write costs the walk from the landing page to
201
201
  // the pricing page, never the click itself — tier 1 still carries a
@@ -212,17 +212,17 @@ function sessionStore() {
212
212
  * effect immediately: the stored value is dropped, not merely ignored. A
213
213
  * visitor who changes their mind mid-visit should not leave the thing they
214
214
  * withdrew consent for sitting on their device.
215
- */ export function setCampaignForwardingConsent(allowed) {
215
+ */ export function setUtmForwardingConsent(allowed) {
216
216
  storageConsent = allowed === true ? true : allowed === false ? false : null;
217
217
  if (storageConsent === true) {
218
- rememberVisitCampaign();
218
+ rememberVisitUtm();
219
219
  return;
220
220
  }
221
221
  if (storageConsent === false) {
222
222
  const store = sessionStore();
223
223
  if (!store) return;
224
224
  try {
225
- store.removeItem(CAMPAIGN_VISIT_STORAGE_KEY);
225
+ store.removeItem(UTM_VISIT_STORAGE_KEY);
226
226
  } catch (unused) {
227
227
  // Nothing else to try, and a failed cleanup must not break the page.
228
228
  }
@@ -234,11 +234,11 @@ function sessionStore() {
234
234
  * The `unreadable` case falls through to the live URL rather than to nothing,
235
235
  * which is the point of carrying that state at all: a broken store degrades
236
236
  * this to tier 1 rather than to silence.
237
- */ export function campaignToForward(search) {
238
- const stored = readVisitCampaign();
237
+ */ export function utmToForward(search) {
238
+ const stored = readVisitUtm();
239
239
  if (stored.status === 'campaign') return stored.campaign;
240
240
  const live = typeof search === 'string' ? search : typeof window === 'undefined' ? '' : window.location.search;
241
- return parseCampaignAttribution(new URLSearchParams(live));
241
+ return parseUtmAttribution(new URLSearchParams(live));
242
242
  }
243
243
  function normalizeOrigin(value) {
244
244
  if (!value) return '';
@@ -261,7 +261,7 @@ function normalizeOrigin(value) {
261
261
  * three must not produce `source=hn` married to the author's `campaign=`: that
262
262
  * row describes an event that never happened. Everything else on the href —
263
263
  * `plan`, `interval`, the AGL-1535 intent — is preserved untouched.
264
- */ export function decorateCampaignHref(href, baseHref, consoleOrigin, campaign) {
264
+ */ export function decorateUtmHref(href, baseHref, consoleOrigin, campaign) {
265
265
  if (!campaign) return null;
266
266
  const origin = normalizeOrigin(consoleOrigin);
267
267
  if (!origin) return null;
@@ -272,8 +272,8 @@ function normalizeOrigin(value) {
272
272
  return null;
273
273
  }
274
274
  if (url.origin !== origin) return null;
275
- for (const key of CAMPAIGN_QUERY_KEYS)url.searchParams.delete(key);
276
- const query = new URLSearchParams(campaignAttributionQuery(campaign));
275
+ for (const key of UTM_QUERY_KEYS)url.searchParams.delete(key);
276
+ const query = new URLSearchParams(utmAttributionQuery(campaign));
277
277
  query.forEach((value, key)=>url.searchParams.set(key, value));
278
278
  const next = url.toString();
279
279
  return next === href ? null : next;
@@ -295,7 +295,7 @@ function normalizeOrigin(value) {
295
295
  *
296
296
  * Returns an uninstall function for tests and for a caller that owns the page
297
297
  * lifecycle.
298
- */ export function installCampaignForwarding(options) {
298
+ */ export function installUtmForwarding(options) {
299
299
  if (typeof document === 'undefined' || typeof window === 'undefined') {
300
300
  return ()=>undefined;
301
301
  }
@@ -308,7 +308,7 @@ function normalizeOrigin(value) {
308
308
  if (!target || typeof target.closest !== 'function') return;
309
309
  const anchor = target.closest('a[href]');
310
310
  if (!anchor) return;
311
- const decorated = decorateCampaignHref(anchor.getAttribute('href') || '', window.location.href, origin, campaignToForward());
311
+ const decorated = decorateUtmHref(anchor.getAttribute('href') || '', window.location.href, origin, utmToForward());
312
312
  if (decorated) anchor.setAttribute('href', decorated);
313
313
  } catch (unused) {
314
314
  // Attribution never breaks a navigation. A link that throws here would
@@ -318,9 +318,9 @@ function normalizeOrigin(value) {
318
318
  installedHandler = onActivate;
319
319
  document.addEventListener('pointerdown', onActivate, true);
320
320
  document.addEventListener('click', onActivate, true);
321
- return resetCampaignForwarding;
321
+ return resetUtmForwarding;
322
322
  }
323
- /** Test seam — removes both listeners, forgets them, and forgets consent. */ export function resetCampaignForwarding() {
323
+ /** Test seam — removes both listeners, forgets them, and forgets consent. */ export function resetUtmForwarding() {
324
324
  if (installedHandler && typeof document !== 'undefined') {
325
325
  document.removeEventListener('pointerdown', installedHandler, true);
326
326
  document.removeEventListener('click', installedHandler, true);
@@ -329,4 +329,4 @@ function normalizeOrigin(value) {
329
329
  storageConsent = null;
330
330
  }
331
331
 
332
- //# sourceMappingURL=campaign-forwarding.js.map
332
+ //# sourceMappingURL=utm-forwarding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/utm-forwarding.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 * Carry the visit's UTM labels across the domain hop (AGL-1731).\n *\n * `utm-attribution.ts` is the READER half of this contract and it works.\n * This is the half that was missing, and without it the reader was fed\n * nothing: `aglyn.com` is a tenant published site and `app.aglyn.com` is the\n * console, a real cross-origin hop, and no code anywhere put a `utm_*`\n * parameter on a console-bound link. So `users/{uid}.signupCampaign` was\n * written for exactly one population — visitors whose CTA href had a campaign\n * typed into it by hand.\n *\n * A hand-typed one is worse than none. It is STATIC: `utm_source=google`\n * authored onto the pricing button attributes every clicker to Google whether\n * they arrived from Google, from Hacker News or from a bookmark. Confidently\n * wrong attribution reaches the same spend decisions as absent attribution and\n * is much harder to notice, because the report looks populated.\n *\n * What is needed instead is PER-VISITOR forwarding: the landing page copies\n * the campaign off its own inbound URL onto the console-bound href, at click\n * time, for that one visitor.\n *\n * ## Why a delegated listener and not `AppLink`\n *\n * The same reason `analytics-link-clicks.ts` gives, and it is the reason that\n * decides this file's shape. The CTAs on `aglyn.com` are authored besigner\n * content, not repo files; rich-text and Custom HTML anchors are written with\n * `dangerouslySetInnerHTML` and are plain DOM anchors with no React handler at\n * all. A prop on `AppLink` would miss them, and reaching them from `AppLink`\n * would mean `useSearchParams` in a component the console renders on every\n * page — a dynamic-rendering hazard on statically generated surfaces, paid on\n * every route to fix a link on one of them. A capture-phase listener on\n * `document` sees every anchor, costs one listener, and renders nothing.\n *\n * ## The two tiers, and why the storage tier is the SECOND one\n *\n * A visitor rarely signs up from the page the ad landed on. They land on\n * `/?utm_source=google&...`, read `/pricing`, then click. By then the campaign\n * is off the address bar, so reading the live URL at click time alone would\n * capture only the minority who convert without navigating.\n *\n * - **Tier 1 — the live URL.** Read `window.location.search` at click time.\n * This touches NO storage: the parameters are already in the page the\n * visitor asked for. Nothing is written to their device, so there is nothing\n * here to consent to, and it works for every visitor including one who has\n * declined analytics.\n * - **Tier 2 — the first touch of the visit**, in `sessionStorage`, which is\n * what survives the walk from the landing page to the pricing page.\n *\n * Tier 2 WINS when both exist, because first touch is the question being\n * asked. Last touch — re-deriving the campaign from whatever URL happens to be\n * current at signup — answers a different question and would disagree with\n * GA4's own session attribution.\n *\n * ## Why tier 2 is gated on analytics consent, and tier 1 is not\n *\n * Tier 2 writes to the visitor's device for an analytics purpose, so it is\n * `analytics_storage` in the consent-mode sense and it waits for the same\n * grant the GA tag waits for (AGL-1498). The gate is not re-implemented here\n * and no consent record is read here: the caller already computes\n * `analyticsAllowed` for the tag and hands the same boolean down, so there is\n * one gate and it cannot drift from itself.\n *\n * It is deliberately NOT gated on advertising storage. Nothing here reads or\n * writes an advertising identifier, sets a cookie, or leaves anything that\n * outlives the tab, and AGL-1649 has advertising denied with no route for a\n * host to grant it — gating on a permission that cannot currently be given\n * would be a feature that is dead by construction rather than one that is off.\n *\n * **Consent unresolved is not consent denied, and both are falsy.** With\n * `strictNullChecks` off repo-wide that distinction is the sort that\n * evaporates, so it is carried as an explicit tri-state: until the caller has\n * actually resolved the visitor's state, tier 2 neither reads nor writes.\n * Failing closed here costs an attribution; failing open would write to a\n * device before the visitor answered.\n *\n * ## What is never done\n *\n * A value is only ever forwarded if this visitor literally arrived carrying\n * it. There is no default, no inference from the referrer, and no\n * `utm_source=direct` — a signup with no campaign must reach the console with\n * no campaign, so that \"arrived from nowhere\" and \"never asked\" stay the\n * distinct facts `utm-attribution.ts` keeps them.\n *\n * Only links to OUR OWN console origin are touched, and the origin is supplied\n * by the caller rather than written here — a self-host install points\n * `NEXT_PUBLIC_CONSOLE_URL` somewhere else entirely. Decorating any other host\n * would post our campaign labels to a third party, which is a data leak dressed\n * up as attribution.\n *\n * ## One accepted consequence\n *\n * The tenant runtime serves `aglyn.com` AND every customer's published site,\n * so a customer site that links to our signup forwards ITS OWN inbound\n * campaign — an agency partner's `utm_source=their-newsletter` would land in our\n * acquisition report describing their spend, not ours. It is not gated on\n * \"is this the operator's marketing host\" deliberately: that gate needs a\n * second configured origin to compare against, and a deployment that forgot to\n * set it would forward NOTHING while looking perfectly healthy. A silent zero\n * is the exact failure AGL-1731 is about, and it is worse than a few\n * mislabelled rows that GA's own Hostname dimension can already separate.\n */\n\nimport {\n UTM_QUERY_KEYS,\n utmAttributionQuery,\n parseUtmAttribution,\n type UtmAttribution,\n} from './utm-attribution'\n\n/**\n * Where the first touch of this visit is held. `sessionStorage`, so it dies\n * with the tab: a campaign is a fact about one visit, and a value that\n * outlived the visit would start attributing next week's organic return to\n * this week's ad.\n */\nexport const UTM_VISIT_STORAGE_KEY = 'aglyn:campaign'\n\n/**\n * What a read of the first-touch store produced.\n *\n * Three states rather than \"a campaign or null\", and the third is the whole\n * point. `sessionStorage` throws outright in some privacy modes, and a\n * `catch` that returned null would report \"this visitor named no campaign\" —\n * indistinguishable from an organic arrival, which is how an unreadable source\n * becomes a measured zero. `unreadable` is a different answer and the caller\n * treats it differently: it falls through to the live URL instead of\n * concluding anything.\n */\nexport type VisitUtmRead =\n | { status: 'campaign'; campaign: UtmAttribution }\n | { status: 'none' }\n | { status: 'unreadable' }\n\n/**\n * Whether the visitor has granted analytics storage.\n *\n * `null` means NOT YET RESOLVED, and is not the same as `false`. The consent\n * state is settled client-side after hydration, so there is a window in every\n * pageview where the answer is genuinely unknown, and treating it as a denial\n * would be as wrong as treating it as a grant. Tier 2 does nothing in it.\n */\nlet storageConsent: boolean | null = null\n\n/** The one listener pair on `document`, or null. Held, not flagged — the same\n * reason `analytics-link-clicks.ts` holds its own: a boolean lets a caller\n * install over a listener that is still attached, and re-entrant decoration is\n * harder to see than a double-counted event. */\nlet installedHandler: ((event: Event) => void) | null = null\n\nfunction sessionStore(): Storage | null {\n if (typeof window === 'undefined') return null\n // Touching the property itself can throw, not just its methods.\n try {\n return window.sessionStorage ?? null\n } catch {\n return null\n }\n}\n\n/**\n * The campaign this visit began with, as far as the store can say.\n *\n * Re-parsed through the same allowlist that wrote it rather than trusted:\n * `sessionStorage` is writable by anything running on the page, so the stored\n * string can claim no more than an inbound URL could — three allowlisted keys,\n * trimmed, email shapes refused, capped.\n */\nexport function readVisitUtm(): VisitUtmRead {\n if (storageConsent !== true) return { status: 'none' }\n const store = sessionStore()\n if (!store) return { status: 'unreadable' }\n let raw: string | null\n try {\n raw = store.getItem(UTM_VISIT_STORAGE_KEY)\n } catch {\n return { status: 'unreadable' }\n }\n if (!raw) return { status: 'none' }\n const campaign = parseUtmAttribution(new URLSearchParams(raw))\n return campaign ? { status: 'campaign', campaign } : { status: 'none' }\n}\n\n/**\n * Remember the campaign on the current URL as this visit's first touch.\n *\n * First touch means FIRST: an existing entry is never replaced. A second\n * campaign arriving later in the same visit is a link the visitor followed\n * after we already knew where they came from, and letting it overwrite would\n * quietly turn this into last-touch.\n *\n * Returns what is now remembered so a caller can assert on it.\n */\nexport function rememberVisitUtm(search?: string): UtmAttribution | null {\n const existing = readVisitUtm()\n if (existing.status === 'campaign') return existing.campaign\n if (storageConsent !== true) return null\n const store = sessionStore()\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 try {\n store.setItem(UTM_VISIT_STORAGE_KEY, utmAttributionQuery(campaign))\n } catch {\n // A store that refuses the write costs the walk from the landing page to\n // the pricing page, never the click itself — tier 1 still carries a\n // visitor who converts without navigating.\n return null\n }\n return campaign\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 seeds the first touch the moment it is given and a WITHDRAWAL takes\n * effect immediately: the stored value is dropped, not merely ignored. A\n * visitor who changes their mind mid-visit should not leave the thing they\n * withdrew consent for sitting on their device.\n */\nexport function setUtmForwardingConsent(allowed: boolean | null): void {\n storageConsent = allowed === true ? true : allowed === false ? false : null\n if (storageConsent === true) {\n rememberVisitUtm()\n return\n }\n if (storageConsent === false) {\n const store = sessionStore()\n if (!store) return\n try {\n store.removeItem(UTM_VISIT_STORAGE_KEY)\n } catch {\n // Nothing else to try, and a failed cleanup must not break the page.\n }\n }\n}\n\n/**\n * The campaign to forward, first touch preferred.\n *\n * The `unreadable` case falls through to the live URL rather than to nothing,\n * which is the point of carrying that state at all: a broken store degrades\n * this to tier 1 rather than to silence.\n */\nexport function utmToForward(search?: string): UtmAttribution | null {\n const stored = readVisitUtm()\n if (stored.status === 'campaign') return stored.campaign\n const live =\n typeof search === 'string'\n ? search\n : typeof window === 'undefined'\n ? ''\n : window.location.search\n return parseUtmAttribution(new URLSearchParams(live))\n}\n\nfunction normalizeOrigin(value: string | null | undefined): string {\n if (!value) return ''\n try {\n return new URL(value).origin\n } catch {\n return ''\n }\n}\n\n/**\n * The href a console-bound link should carry for this visitor, or null to\n * leave it exactly as authored.\n *\n * Null — rather than \"the href unchanged\" — so that a caller cannot write back\n * a value it did not mean to change, and so a test can tell \"decided not to\n * touch this\" from \"touched it and produced the same string\".\n *\n * The three keys are replaced WHOLESALE, never merged key by key. A visitor\n * who arrived with only `utm_source=hn` clicking a button authored with all\n * three must not produce `source=hn` married to the author's `campaign=`: that\n * row describes an event that never happened. Everything else on the href —\n * `plan`, `interval`, the AGL-1535 intent — is preserved untouched.\n */\nexport function decorateUtmHref(\n href: string,\n baseHref: string,\n consoleOrigin: string,\n campaign: UtmAttribution | null,\n): string | null {\n if (!campaign) return null\n const origin = normalizeOrigin(consoleOrigin)\n if (!origin) return null\n let url: URL\n try {\n url = new URL(href, baseHref)\n } catch {\n return null\n }\n if (url.origin !== origin) return null\n for (const key of UTM_QUERY_KEYS) url.searchParams.delete(key)\n const query = new URLSearchParams(utmAttributionQuery(campaign))\n query.forEach((value, key) => url.searchParams.set(key, value))\n const next = url.toString()\n return next === href ? null : next\n}\n\n/**\n * Start forwarding the campaign onto console-bound links on this page.\n *\n * Idempotent and safe to call during render, the same contract as\n * `installLinkClickTracking` and for the same AGL-1550 reason: an effect only\n * runs if React commits, and a page that renders without committing would\n * otherwise ship links with no campaign on them.\n *\n * Both `pointerdown` and `click` are listened for, in capture phase.\n * `pointerdown` is what covers a middle-click, a modifier-click into a new\n * tab, and \"Copy link address\" from the context menu — all of which read the\n * href without ever firing `click`. `click` is what covers keyboard\n * activation, which fires no pointer event at all. Decoration is idempotent,\n * so a plain left click doing both is not a problem.\n *\n * Returns an uninstall function for tests and for a caller that owns the page\n * lifecycle.\n */\nexport function installUtmForwarding(options: {\n consoleOrigin: string\n}): () => void {\n if (typeof document === 'undefined' || typeof window === 'undefined') {\n return () => undefined\n }\n if (installedHandler) return () => undefined\n const origin = normalizeOrigin(options?.consoleOrigin)\n if (!origin) return () => undefined\n\n const onActivate = (event: Event) => {\n try {\n const target = event.target as Element | null\n if (!target || typeof target.closest !== 'function') return\n const anchor = target.closest('a[href]')\n if (!anchor) return\n const decorated = decorateUtmHref(\n anchor.getAttribute('href') || '',\n window.location.href,\n origin,\n utmToForward(),\n )\n if (decorated) anchor.setAttribute('href', decorated)\n } catch {\n // Attribution never breaks a navigation. A link that throws here would\n // be a link that does not work, which costs more than the campaign does.\n }\n }\n\n installedHandler = onActivate\n document.addEventListener('pointerdown', onActivate, true)\n document.addEventListener('click', onActivate, true)\n return resetUtmForwarding\n}\n\n/** Test seam — removes both listeners, forgets them, and forgets consent. */\nexport function resetUtmForwarding(): void {\n if (installedHandler && typeof document !== 'undefined') {\n document.removeEventListener('pointerdown', installedHandler, true)\n document.removeEventListener('click', installedHandler, true)\n }\n installedHandler = null\n storageConsent = null\n}\n"],"names":["UTM_QUERY_KEYS","utmAttributionQuery","parseUtmAttribution","UTM_VISIT_STORAGE_KEY","storageConsent","installedHandler","sessionStore","window","sessionStorage","readVisitUtm","status","store","raw","getItem","campaign","URLSearchParams","rememberVisitUtm","search","existing","source","location","setItem","setUtmForwardingConsent","allowed","removeItem","utmToForward","stored","live","normalizeOrigin","value","URL","origin","decorateUtmHref","href","baseHref","consoleOrigin","url","key","searchParams","delete","query","forEach","set","next","toString","installUtmForwarding","options","document","undefined","onActivate","event","target","closest","anchor","decorated","getAttribute","setAttribute","addEventListener","resetUtmForwarding","removeEventListener"],"mappings":"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"}
@@ -48,10 +48,10 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
48
48
  * eligible. `description` is one Google recommends rather than requires, so a
49
49
  * blank one costs the block that field and nothing more.
50
50
  */ import { NODE_ROOT_ID } from "../canvas-manager/canvas-manager.js";
51
+ import { videoEmbedUrl } from "../plugin-manager/video-embed-provider.js";
51
52
  import { collectDescendantIds } from "./compose-reusable-components.js";
52
53
  import { videoDurationIso8601 } from "./media-metadata.js";
53
54
  import { MEDIA_CDN_POSTER_WIDTH, absoluteMediaSrc, videoPosterSrc } from "./media-ref.js";
54
- import { wistiaEmbedUrl } from "./wistia-embed.js";
55
55
  /** Component id of the Video element. Persisted in documents; never renamed. */ export const VIDEO_COMPONENT_ID = 'video';
56
56
  /** Milliseconds in a second — the unit `videoDurationIso8601` takes. */ const MS_PER_SECOND = 1000;
57
57
  /** A trimmed string, or nothing. Blank is never a value here. */ const text = (value)=>{
@@ -150,10 +150,11 @@ import { wistiaEmbedUrl } from "./wistia-embed.js";
150
150
  // All three, or nothing. See the module note: a block missing one of these
151
151
  // is an error a search console reports, not a smaller win.
152
152
  if (!name || !uploadDate || !thumbnailUrl) return undefined;
153
- // A Wistia link names a player, not a file (AGL-2826), so it is published
154
- // as the player page and never as `contentUrl`: the link an author pasted
155
- // is Wistia's media page, whose bytes are HTML.
156
- const embedUrl = wistiaEmbedUrl(props['src']);
153
+ // A hosted player's link names a player, not a file (AGL-2826), so it is
154
+ // published as the player page and never as `contentUrl`: the link an
155
+ // author pasted is the host's media page, whose bytes are HTML. Which hosts
156
+ // those are is declared by plugins (`video-embed-provider.ts`).
157
+ const embedUrl = videoEmbedUrl(props['src']);
157
158
  const contentUrl = embedUrl ? undefined : absoluteMediaSrc(typeof props['src'] === 'string' ? props['src'] : undefined, {
158
159
  hostId,
159
160
  origin
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/video-object.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 * `VideoObject` structured data for a published page (AGL-2747).\n *\n * ## Why the page's NODES are the input\n *\n * Every other block `buildJsonLd` emits is derived from a screen, a collection\n * entry or the host document — metadata about the page. A video is not\n * metadata about anything: it is a thing an author placed *in* the page, and\n * the only record that it is there is the node.\n *\n * The route already walks that map twice for exactly this kind of question\n * (`pageAnimationAssets`, `deferLazyPanelNodes`) and commerce walks it in a\n * site-page enricher to seed the `Product` block. So this is the same\n * mechanism, not a second one.\n *\n * ⚠️ The composed map is **denormalized and flat**: `Record<string, node>`\n * whose children are id STRINGS under `nodes`, never a nested `children`\n * array. The commerce enricher's first version recursed `children`, matched\n * nothing on any page, and shipped green because its fixture was a tree. This\n * finds Video nodes by walking the values, and then keeps only the ones the\n * page draws — see {@link pageVideoObjects}.\n *\n * ## Why an incomplete block is not emitted\n *\n * Google requires `name`, `thumbnailUrl` and `uploadDate` for a video result. A\n * `VideoObject` missing one of them is not a partial win — it is an invalid\n * rich result that a search console reports as an error against the page. So\n * the block is withheld until all three are there, the way\n * `breadcrumbListJsonLd` declines a single-crumb list. Everything else\n * (`description`, `contentUrl`, `embedUrl`, `duration`) is added when known and\n * omitted when not, because none of those changes whether the page is\n * eligible. `description` is one Google recommends rather than requires, so a\n * blank one costs the block that field and nothing more.\n */\n\nimport { NODE_ROOT_ID } from '../canvas-manager/canvas-manager'\nimport type { AglynNodeSchema, NodeId } from '../foundation'\nimport { collectDescendantIds } from './compose-reusable-components'\nimport { videoDurationIso8601 } from './media-metadata'\nimport {\n MEDIA_CDN_POSTER_WIDTH,\n absoluteMediaSrc,\n videoPosterSrc,\n} from './media-ref'\nimport { wistiaEmbedUrl } from './wistia-embed'\n\n/** Component id of the Video element. Persisted in documents; never renamed. */\nexport const VIDEO_COMPONENT_ID = 'video'\n\n/** Milliseconds in a second — the unit `videoDurationIso8601` takes. */\nconst MS_PER_SECOND = 1000\n\n/** A node as the composed map holds it — flat, children by id. */\ninterface ComposedVideoNode {\n componentId?: string\n props?: Record<string, unknown>\n resolvedProps?: Record<string, unknown>\n}\n\nexport interface VideoObjectContext {\n /** The site's public origin — a relative URL is not a `contentUrl`. */\n origin?: string | null\n /** The site being rendered, which qualifies an org-scoped media scope. */\n hostId?: string\n}\n\n/** A trimmed string, or nothing. Blank is never a value here. */\nconst text = (value: unknown): string | undefined => {\n const trimmed = typeof value === 'string' ? value.trim() : ''\n return trimmed || undefined\n}\n\n/**\n * The UTC hour a bare calendar day is published at. An author names a day,\n * not a moment, and noon UTC is still that same day in every zone from UTC-12\n * through UTC+11. Midnight UTC is the previous evening in every zone behind\n * UTC, so a video result dated from it reads a day early across the Americas.\n */\nconst CALENDAR_DAY_UTC_HOUR = 12\n\n/** A calendar day: the shape the Publication date field asks an author for. */\nconst CALENDAR_DAY = /^(\\d{4})-(\\d{2})-(\\d{2})$/\n\n/**\n * An ISO 8601 date-time that names its zone, as `Z` or an offset — the shape\n * a bound timestamp or a video host's own metadata arrives in.\n */\nconst ZONED_DATE_TIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|[+-](?:0\\d|1[0-4]):[0-5]\\d)$/\n\n/**\n * The instant at `hour` UTC on the day a match's year, month and day groups\n * name, or `undefined` when that day does not exist. `Date.UTC` rolls\n * 2026-02-30 over to March 2 rather than refusing it, so a real day is one\n * whose parts survive the round trip.\n */\nconst utcDay = (match: RegExpExecArray, hour = 0): number | undefined => {\n const year = Number(match[1])\n const month = Number(match[2]) - 1\n const day = Number(match[3])\n const time = Date.UTC(year, month, day, hour)\n const date = new Date(time)\n return date.getUTCFullYear() === year &&\n date.getUTCMonth() === month &&\n date.getUTCDate() === day\n ? time\n : undefined\n}\n\n/**\n * `uploadDate` as Google reads it: an ISO 8601 date AND time, with its zone\n * (AGL-2948).\n *\n * Google types the property as a DateTime. A bare `YYYY-MM-DD` is reported\n * against the page twice, as an invalid datetime and as one missing its\n * timezone, and a time with no zone is read in Googlebot's. Converting where\n * the block is built covers every writer at once: the field, a bound\n * property, an agent, and every document already published with a bare day.\n *\n * - A calendar day becomes that day at {@link CALENDAR_DAY_UTC_HOUR}:00 UTC.\n * - A date-time that names its zone keeps its instant.\n * - Both are spelled by `toISOString()`, as `Article.datePublished` is.\n * - Anything else — a time with no zone, a day that does not exist, free\n * text — is published exactly as typed. Which zone an author meant is a\n * guess, and a confidently wrong date is harder to notice than a warning.\n */\nconst uploadDateTime = (value: string | undefined): string | undefined => {\n if (!value) return undefined\n const day = CALENDAR_DAY.exec(value)\n if (day) {\n const noon = utcDay(day, CALENDAR_DAY_UTC_HOUR)\n return noon === undefined ? value : new Date(noon).toISOString()\n }\n const zoned = ZONED_DATE_TIME.exec(value)\n if (!zoned || utcDay(zoned) === undefined) return value\n // `toISOString` throws on an invalid date, and a page render is no place\n // to find out which fraction lengths this runtime's parser accepts.\n const instant = Date.parse(value)\n return Number.isNaN(instant) ? value : new Date(instant).toISOString()\n}\n\n/**\n * The `VideoObject` for one Video node, or `undefined` when the author has not\n * given it enough to be worth publishing.\n *\n * `props` precedence is `resolvedProps ?? props` — the same rule every other\n * consumer of the composed map uses, because a node inside a repeated\n * collection carries its bound values only in the resolved copy.\n *\n * URLs go through `absoluteMediaSrc` and `mediaVariantSrc`, never through the\n * stored value: a `media:` reference published into structured data is a\n * string no crawler can fetch, which is the AGL-1343 lesson `article-json-ld`\n * pins for cover images.\n */\nexport function videoObjectJsonLd(\n node: ComposedVideoNode | null | undefined,\n context?: VideoObjectContext,\n): Record<string, unknown> | undefined {\n if (!node || node.componentId !== VIDEO_COMPONENT_ID) return undefined\n const props = node.resolvedProps ?? node.props ?? {}\n const origin = context?.origin\n const hostId = context?.hostId\n const name = text(props['title'])\n const description = text(props['description'])\n const uploadDate = uploadDateTime(text(props['uploadDate']))\n // The SAME rule the element renders with, so the thumbnail a crawler\n // fetches is the poster a visitor sees — including the refusal to derive a\n // generated poster's url unless the node records that one exists, which is\n // what keeps a 404 out of the rich result.\n const thumbnailUrl = absoluteMediaSrc(\n videoPosterSrc({\n hostId,\n poster: props['poster'],\n src: props['src'],\n generated: props['posterFromSource'],\n width: MEDIA_CDN_POSTER_WIDTH,\n }),\n { hostId, origin },\n )\n // All three, or nothing. See the module note: a block missing one of these\n // is an error a search console reports, not a smaller win.\n if (!name || !uploadDate || !thumbnailUrl) return undefined\n // A Wistia link names a player, not a file (AGL-2826), so it is published\n // as the player page and never as `contentUrl`: the link an author pasted\n // is Wistia's media page, whose bytes are HTML.\n const embedUrl = wistiaEmbedUrl(props['src'])\n const contentUrl = embedUrl\n ? undefined\n : absoluteMediaSrc(\n typeof props['src'] === 'string' ? props['src'] : undefined,\n { hostId, origin },\n )\n // Through the DAM's own formatter, in ITS unit. The node stores seconds\n // because that is what an author types; `videoDurationIso8601` takes\n // milliseconds and rounds a sub-second clip UP to `PT1S` rather than to a\n // `PT0S` that reads as \"no duration\" — which is why this converts rather\n // than keeping the second implementation it used to have.\n const seconds = Number(props['durationSeconds'])\n const duration =\n Number.isFinite(seconds) && seconds > 0\n ? videoDurationIso8601(seconds * MS_PER_SECOND)\n : undefined\n return {\n '@context': 'https://schema.org',\n '@type': 'VideoObject',\n name,\n ...(description ? { description } : {}),\n thumbnailUrl,\n uploadDate,\n // Exactly one of the two. `contentUrl` is the file, for a film served from\n // this site's media CDN or hotlinked; `embedUrl` is a PLAYER page, which\n // this element renders only for a Wistia video.\n ...(contentUrl ? { contentUrl } : {}),\n ...(embedUrl ? { embedUrl } : {}),\n ...(duration ? { duration } : {}),\n }\n}\n\n/**\n * Every publishable `VideoObject` on a page, in the composed map's own order.\n *\n * Returns `[]` — the common case — for a page with no video, a page whose\n * videos have no SEO fields filled in, and a page with no nodes at all. The\n * route then emits no extra script, which is the same shape\n * `pageAnimationAssets` uses for the same reason.\n *\n * ⚠️ Hand this the map the page actually SHIPS, not the one the loader read.\n * A video inside a withheld lazy tab panel is not in the HTML, and a\n * `VideoObject` describing a player that is not on the page is precisely the\n * mismatch a video rich result is checked for.\n *\n * ## Only the videos the page draws (AGL-2957)\n *\n * Being in the map is not being on the page. The renderer starts at\n * {@link NODE_ROOT_ID} and follows child id lists, and nothing else, so a node\n * no list names is carried in the payload and never drawn. Composition leaves\n * such nodes on purpose: `expandCollectionEntries` and `expandRepeatables`\n * point a block's list at its clones and keep the template in the map. On a\n * list page that template publishes a block named `{{entry.title}}`; on an\n * entry page, where entry tokens are substituted across the whole map, it\n * publishes a copy of the page's own film.\n *\n * So when the map has a root, a Video counts only if a child list reaches it\n * from there. Every stage that moves a subtree does so through those lists —\n * a layout slot adopts the screen's top-level ids, a reusable component or a\n * placed form hands its definition's list to the placement, a tab panel lists\n * its content — so each of those is followed by the same walk, and a panel\n * the route withheld has already lost its list. `parentId` is never followed:\n * a template keeps its parent's id after its parent stops listing it.\n *\n * A map with no root is a fragment with no entry point to measure reach from,\n * and keeps the whole-map walk.\n *\n * One rule of the renderer's is not in the map: a self-closing element draws\n * no children, and which components are self-closing is registry knowledge.\n * The editor refuses a child under one (`nodeAcceptsChildren`), so a Video\n * there is a hand-edited document rather than an authored page.\n *\n * The reach is computed only for a map that holds a Video at all, so a page\n * without one costs a single pass over its values and no walk.\n */\nexport function pageVideoObjects(\n nodes: Record<string, unknown> | null | undefined,\n context?: VideoObjectContext,\n): Record<string, unknown>[] {\n if (!nodes) return []\n const videoIds: string[] = []\n for (const id in nodes) {\n const node = nodes[id] as ComposedVideoNode | null | undefined\n if (node?.componentId === VIDEO_COMPONENT_ID) videoIds.push(id)\n }\n if (!videoIds.length) return []\n const drawn = nodes[NODE_ROOT_ID]\n ? collectDescendantIds(\n nodes as Record<NodeId, AglynNodeSchema>,\n NODE_ROOT_ID,\n )\n : undefined\n const found: Record<string, unknown>[] = []\n for (const id of videoIds) {\n if (drawn && id !== NODE_ROOT_ID && !drawn.has(id)) continue\n const block = videoObjectJsonLd(nodes[id] as ComposedVideoNode, context)\n if (block) found.push(block)\n }\n return found\n}\n"],"names":["NODE_ROOT_ID","collectDescendantIds","videoDurationIso8601","MEDIA_CDN_POSTER_WIDTH","absoluteMediaSrc","videoPosterSrc","wistiaEmbedUrl","VIDEO_COMPONENT_ID","MS_PER_SECOND","text","value","trimmed","trim","undefined","CALENDAR_DAY_UTC_HOUR","CALENDAR_DAY","ZONED_DATE_TIME","utcDay","match","hour","year","Number","month","day","time","Date","UTC","date","getUTCFullYear","getUTCMonth","getUTCDate","uploadDateTime","exec","noon","toISOString","zoned","instant","parse","isNaN","videoObjectJsonLd","node","context","componentId","props","resolvedProps","origin","hostId","name","description","uploadDate","thumbnailUrl","poster","src","generated","width","embedUrl","contentUrl","seconds","duration","isFinite","pageVideoObjects","nodes","videoIds","id","push","length","drawn","found","has","block"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCC,GAED,SAASA,YAAY,QAAQ,sCAAkC;AAE/D,SAASC,oBAAoB,QAAQ,mCAA+B;AACpE,SAASC,oBAAoB,QAAQ,sBAAkB;AACvD,SACEC,sBAAsB,EACtBC,gBAAgB,EAChBC,cAAc,QACT,iBAAa;AACpB,SAASC,cAAc,QAAQ,oBAAgB;AAE/C,8EAA8E,GAC9E,OAAO,MAAMC,qBAAqB,QAAO;AAEzC,sEAAsE,GACtE,MAAMC,gBAAgB;AAgBtB,+DAA+D,GAC/D,MAAMC,OAAO,CAACC;IACZ,MAAMC,UAAU,OAAOD,UAAU,WAAWA,MAAME,IAAI,KAAK;IAC3D,OAAOD,WAAWE;AACpB;AAEA;;;;;CAKC,GACD,MAAMC,wBAAwB;AAE9B,6EAA6E,GAC7E,MAAMC,eAAe;AAErB;;;CAGC,GACD,MAAMC,kBACJ;AAEF;;;;;CAKC,GACD,MAAMC,SAAS,CAACC,OAAwBC,OAAO,CAAC;IAC9C,MAAMC,OAAOC,OAAOH,KAAK,CAAC,EAAE;IAC5B,MAAMI,QAAQD,OAAOH,KAAK,CAAC,EAAE,IAAI;IACjC,MAAMK,MAAMF,OAAOH,KAAK,CAAC,EAAE;IAC3B,MAAMM,OAAOC,KAAKC,GAAG,CAACN,MAAME,OAAOC,KAAKJ;IACxC,MAAMQ,OAAO,IAAIF,KAAKD;IACtB,OAAOG,KAAKC,cAAc,OAAOR,QAC/BO,KAAKE,WAAW,OAAOP,SACvBK,KAAKG,UAAU,OAAOP,MACpBC,OACAX;AACN;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,MAAMkB,iBAAiB,CAACrB;IACtB,IAAI,CAACA,OAAO,OAAOG;IACnB,MAAMU,MAAMR,aAAaiB,IAAI,CAACtB;IAC9B,IAAIa,KAAK;QACP,MAAMU,OAAOhB,OAAOM,KAAKT;QACzB,OAAOmB,SAASpB,YAAYH,QAAQ,IAAIe,KAAKQ,MAAMC,WAAW;IAChE;IACA,MAAMC,QAAQnB,gBAAgBgB,IAAI,CAACtB;IACnC,IAAI,CAACyB,SAASlB,OAAOkB,WAAWtB,WAAW,OAAOH;IAClD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM0B,UAAUX,KAAKY,KAAK,CAAC3B;IAC3B,OAAOW,OAAOiB,KAAK,CAACF,WAAW1B,QAAQ,IAAIe,KAAKW,SAASF,WAAW;AACtE;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASK,kBACdC,IAA0C,EAC1CC,OAA4B;QAGdD,MAAAA;IADd,IAAI,CAACA,QAAQA,KAAKE,WAAW,KAAKnC,oBAAoB,OAAOM;IAC7D,MAAM8B,SAAQH,QAAAA,sBAAAA,KAAKI,aAAa,YAAlBJ,sBAAsBA,KAAKG,KAAK,YAAhCH,OAAoC,CAAC;IACnD,MAAMK,SAASJ,2BAAAA,QAASI,MAAM;IAC9B,MAAMC,SAASL,2BAAAA,QAASK,MAAM;IAC9B,MAAMC,OAAOtC,KAAKkC,KAAK,CAAC,QAAQ;IAChC,MAAMK,cAAcvC,KAAKkC,KAAK,CAAC,cAAc;IAC7C,MAAMM,aAAalB,eAAetB,KAAKkC,KAAK,CAAC,aAAa;IAC1D,qEAAqE;IACrE,2EAA2E;IAC3E,2EAA2E;IAC3E,2CAA2C;IAC3C,MAAMO,eAAe9C,iBACnBC,eAAe;QACbyC;QACAK,QAAQR,KAAK,CAAC,SAAS;QACvBS,KAAKT,KAAK,CAAC,MAAM;QACjBU,WAAWV,KAAK,CAAC,mBAAmB;QACpCW,OAAOnD;IACT,IACA;QAAE2C;QAAQD;IAAO;IAEnB,2EAA2E;IAC3E,2DAA2D;IAC3D,IAAI,CAACE,QAAQ,CAACE,cAAc,CAACC,cAAc,OAAOrC;IAClD,0EAA0E;IAC1E,0EAA0E;IAC1E,gDAAgD;IAChD,MAAM0C,WAAWjD,eAAeqC,KAAK,CAAC,MAAM;IAC5C,MAAMa,aAAaD,WACf1C,YACAT,iBACE,OAAOuC,KAAK,CAAC,MAAM,KAAK,WAAWA,KAAK,CAAC,MAAM,GAAG9B,WAClD;QAAEiC;QAAQD;IAAO;IAEvB,wEAAwE;IACxE,qEAAqE;IACrE,0EAA0E;IAC1E,yEAAyE;IACzE,0DAA0D;IAC1D,MAAMY,UAAUpC,OAAOsB,KAAK,CAAC,kBAAkB;IAC/C,MAAMe,WACJrC,OAAOsC,QAAQ,CAACF,YAAYA,UAAU,IAClCvD,qBAAqBuD,UAAUjD,iBAC/BK;IACN,OAAO;QACL,YAAY;QACZ,SAAS;QACTkC;OACIC,cAAc;QAAEA;IAAY,IAAI,CAAC;QACrCE;QACAD;OAIIO,aAAa;QAAEA;IAAW,IAAI,CAAC,GAC/BD,WAAW;QAAEA;IAAS,IAAI,CAAC,GAC3BG,WAAW;QAAEA;IAAS,IAAI,CAAC;AAEnC;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CC,GACD,OAAO,SAASE,iBACdC,KAAiD,EACjDpB,OAA4B;IAE5B,IAAI,CAACoB,OAAO,OAAO,EAAE;IACrB,MAAMC,WAAqB,EAAE;IAC7B,IAAK,MAAMC,MAAMF,MAAO;QACtB,MAAMrB,OAAOqB,KAAK,CAACE,GAAG;QACtB,IAAIvB,CAAAA,wBAAAA,KAAME,WAAW,MAAKnC,oBAAoBuD,SAASE,IAAI,CAACD;IAC9D;IACA,IAAI,CAACD,SAASG,MAAM,EAAE,OAAO,EAAE;IAC/B,MAAMC,QAAQL,KAAK,CAAC7D,aAAa,GAC7BC,qBACE4D,OACA7D,gBAEFa;IACJ,MAAMsD,QAAmC,EAAE;IAC3C,KAAK,MAAMJ,MAAMD,SAAU;QACzB,IAAII,SAASH,OAAO/D,gBAAgB,CAACkE,MAAME,GAAG,CAACL,KAAK;QACpD,MAAMM,QAAQ9B,kBAAkBsB,KAAK,CAACE,GAAG,EAAuBtB;QAChE,IAAI4B,OAAOF,MAAMH,IAAI,CAACK;IACxB;IACA,OAAOF;AACT"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/video-object.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 * `VideoObject` structured data for a published page (AGL-2747).\n *\n * ## Why the page's NODES are the input\n *\n * Every other block `buildJsonLd` emits is derived from a screen, a collection\n * entry or the host document — metadata about the page. A video is not\n * metadata about anything: it is a thing an author placed *in* the page, and\n * the only record that it is there is the node.\n *\n * The route already walks that map twice for exactly this kind of question\n * (`pageAnimationAssets`, `deferLazyPanelNodes`) and commerce walks it in a\n * site-page enricher to seed the `Product` block. So this is the same\n * mechanism, not a second one.\n *\n * ⚠️ The composed map is **denormalized and flat**: `Record<string, node>`\n * whose children are id STRINGS under `nodes`, never a nested `children`\n * array. The commerce enricher's first version recursed `children`, matched\n * nothing on any page, and shipped green because its fixture was a tree. This\n * finds Video nodes by walking the values, and then keeps only the ones the\n * page draws — see {@link pageVideoObjects}.\n *\n * ## Why an incomplete block is not emitted\n *\n * Google requires `name`, `thumbnailUrl` and `uploadDate` for a video result. A\n * `VideoObject` missing one of them is not a partial win — it is an invalid\n * rich result that a search console reports as an error against the page. So\n * the block is withheld until all three are there, the way\n * `breadcrumbListJsonLd` declines a single-crumb list. Everything else\n * (`description`, `contentUrl`, `embedUrl`, `duration`) is added when known and\n * omitted when not, because none of those changes whether the page is\n * eligible. `description` is one Google recommends rather than requires, so a\n * blank one costs the block that field and nothing more.\n */\n\nimport { NODE_ROOT_ID } from '../canvas-manager/canvas-manager'\nimport type { AglynNodeSchema, NodeId } from '../foundation'\nimport { videoEmbedUrl } from '../plugin-manager/video-embed-provider'\nimport { collectDescendantIds } from './compose-reusable-components'\nimport { videoDurationIso8601 } from './media-metadata'\nimport {\n MEDIA_CDN_POSTER_WIDTH,\n absoluteMediaSrc,\n videoPosterSrc,\n} from './media-ref'\n\n/** Component id of the Video element. Persisted in documents; never renamed. */\nexport const VIDEO_COMPONENT_ID = 'video'\n\n/** Milliseconds in a second — the unit `videoDurationIso8601` takes. */\nconst MS_PER_SECOND = 1000\n\n/** A node as the composed map holds it — flat, children by id. */\ninterface ComposedVideoNode {\n componentId?: string\n props?: Record<string, unknown>\n resolvedProps?: Record<string, unknown>\n}\n\nexport interface VideoObjectContext {\n /** The site's public origin — a relative URL is not a `contentUrl`. */\n origin?: string | null\n /** The site being rendered, which qualifies an org-scoped media scope. */\n hostId?: string\n}\n\n/** A trimmed string, or nothing. Blank is never a value here. */\nconst text = (value: unknown): string | undefined => {\n const trimmed = typeof value === 'string' ? value.trim() : ''\n return trimmed || undefined\n}\n\n/**\n * The UTC hour a bare calendar day is published at. An author names a day,\n * not a moment, and noon UTC is still that same day in every zone from UTC-12\n * through UTC+11. Midnight UTC is the previous evening in every zone behind\n * UTC, so a video result dated from it reads a day early across the Americas.\n */\nconst CALENDAR_DAY_UTC_HOUR = 12\n\n/** A calendar day: the shape the Publication date field asks an author for. */\nconst CALENDAR_DAY = /^(\\d{4})-(\\d{2})-(\\d{2})$/\n\n/**\n * An ISO 8601 date-time that names its zone, as `Z` or an offset — the shape\n * a bound timestamp or a video host's own metadata arrives in.\n */\nconst ZONED_DATE_TIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|[+-](?:0\\d|1[0-4]):[0-5]\\d)$/\n\n/**\n * The instant at `hour` UTC on the day a match's year, month and day groups\n * name, or `undefined` when that day does not exist. `Date.UTC` rolls\n * 2026-02-30 over to March 2 rather than refusing it, so a real day is one\n * whose parts survive the round trip.\n */\nconst utcDay = (match: RegExpExecArray, hour = 0): number | undefined => {\n const year = Number(match[1])\n const month = Number(match[2]) - 1\n const day = Number(match[3])\n const time = Date.UTC(year, month, day, hour)\n const date = new Date(time)\n return date.getUTCFullYear() === year &&\n date.getUTCMonth() === month &&\n date.getUTCDate() === day\n ? time\n : undefined\n}\n\n/**\n * `uploadDate` as Google reads it: an ISO 8601 date AND time, with its zone\n * (AGL-2948).\n *\n * Google types the property as a DateTime. A bare `YYYY-MM-DD` is reported\n * against the page twice, as an invalid datetime and as one missing its\n * timezone, and a time with no zone is read in Googlebot's. Converting where\n * the block is built covers every writer at once: the field, a bound\n * property, an agent, and every document already published with a bare day.\n *\n * - A calendar day becomes that day at {@link CALENDAR_DAY_UTC_HOUR}:00 UTC.\n * - A date-time that names its zone keeps its instant.\n * - Both are spelled by `toISOString()`, as `Article.datePublished` is.\n * - Anything else — a time with no zone, a day that does not exist, free\n * text — is published exactly as typed. Which zone an author meant is a\n * guess, and a confidently wrong date is harder to notice than a warning.\n */\nconst uploadDateTime = (value: string | undefined): string | undefined => {\n if (!value) return undefined\n const day = CALENDAR_DAY.exec(value)\n if (day) {\n const noon = utcDay(day, CALENDAR_DAY_UTC_HOUR)\n return noon === undefined ? value : new Date(noon).toISOString()\n }\n const zoned = ZONED_DATE_TIME.exec(value)\n if (!zoned || utcDay(zoned) === undefined) return value\n // `toISOString` throws on an invalid date, and a page render is no place\n // to find out which fraction lengths this runtime's parser accepts.\n const instant = Date.parse(value)\n return Number.isNaN(instant) ? value : new Date(instant).toISOString()\n}\n\n/**\n * The `VideoObject` for one Video node, or `undefined` when the author has not\n * given it enough to be worth publishing.\n *\n * `props` precedence is `resolvedProps ?? props` — the same rule every other\n * consumer of the composed map uses, because a node inside a repeated\n * collection carries its bound values only in the resolved copy.\n *\n * URLs go through `absoluteMediaSrc` and `mediaVariantSrc`, never through the\n * stored value: a `media:` reference published into structured data is a\n * string no crawler can fetch, which is the AGL-1343 lesson `article-json-ld`\n * pins for cover images.\n */\nexport function videoObjectJsonLd(\n node: ComposedVideoNode | null | undefined,\n context?: VideoObjectContext,\n): Record<string, unknown> | undefined {\n if (!node || node.componentId !== VIDEO_COMPONENT_ID) return undefined\n const props = node.resolvedProps ?? node.props ?? {}\n const origin = context?.origin\n const hostId = context?.hostId\n const name = text(props['title'])\n const description = text(props['description'])\n const uploadDate = uploadDateTime(text(props['uploadDate']))\n // The SAME rule the element renders with, so the thumbnail a crawler\n // fetches is the poster a visitor sees — including the refusal to derive a\n // generated poster's url unless the node records that one exists, which is\n // what keeps a 404 out of the rich result.\n const thumbnailUrl = absoluteMediaSrc(\n videoPosterSrc({\n hostId,\n poster: props['poster'],\n src: props['src'],\n generated: props['posterFromSource'],\n width: MEDIA_CDN_POSTER_WIDTH,\n }),\n { hostId, origin },\n )\n // All three, or nothing. See the module note: a block missing one of these\n // is an error a search console reports, not a smaller win.\n if (!name || !uploadDate || !thumbnailUrl) return undefined\n // A hosted player's link names a player, not a file (AGL-2826), so it is\n // published as the player page and never as `contentUrl`: the link an\n // author pasted is the host's media page, whose bytes are HTML. Which hosts\n // those are is declared by plugins (`video-embed-provider.ts`).\n const embedUrl = videoEmbedUrl(props['src'])\n const contentUrl = embedUrl\n ? undefined\n : absoluteMediaSrc(\n typeof props['src'] === 'string' ? props['src'] : undefined,\n { hostId, origin },\n )\n // Through the DAM's own formatter, in ITS unit. The node stores seconds\n // because that is what an author types; `videoDurationIso8601` takes\n // milliseconds and rounds a sub-second clip UP to `PT1S` rather than to a\n // `PT0S` that reads as \"no duration\" — which is why this converts rather\n // than keeping the second implementation it used to have.\n const seconds = Number(props['durationSeconds'])\n const duration =\n Number.isFinite(seconds) && seconds > 0\n ? videoDurationIso8601(seconds * MS_PER_SECOND)\n : undefined\n return {\n '@context': 'https://schema.org',\n '@type': 'VideoObject',\n name,\n ...(description ? { description } : {}),\n thumbnailUrl,\n uploadDate,\n // Exactly one of the two. `contentUrl` is the file, for a film served from\n // this site's media CDN or hotlinked; `embedUrl` is a PLAYER page, which\n // this element renders only for a declared host's link.\n ...(contentUrl ? { contentUrl } : {}),\n ...(embedUrl ? { embedUrl } : {}),\n ...(duration ? { duration } : {}),\n }\n}\n\n/**\n * Every publishable `VideoObject` on a page, in the composed map's own order.\n *\n * Returns `[]` — the common case — for a page with no video, a page whose\n * videos have no SEO fields filled in, and a page with no nodes at all. The\n * route then emits no extra script, which is the same shape\n * `pageAnimationAssets` uses for the same reason.\n *\n * ⚠️ Hand this the map the page actually SHIPS, not the one the loader read.\n * A video inside a withheld lazy tab panel is not in the HTML, and a\n * `VideoObject` describing a player that is not on the page is precisely the\n * mismatch a video rich result is checked for.\n *\n * ## Only the videos the page draws (AGL-2957)\n *\n * Being in the map is not being on the page. The renderer starts at\n * {@link NODE_ROOT_ID} and follows child id lists, and nothing else, so a node\n * no list names is carried in the payload and never drawn. Composition leaves\n * such nodes on purpose: `expandCollectionEntries` and `expandRepeatables`\n * point a block's list at its clones and keep the template in the map. On a\n * list page that template publishes a block named `{{entry.title}}`; on an\n * entry page, where entry tokens are substituted across the whole map, it\n * publishes a copy of the page's own film.\n *\n * So when the map has a root, a Video counts only if a child list reaches it\n * from there. Every stage that moves a subtree does so through those lists —\n * a layout slot adopts the screen's top-level ids, a reusable component or a\n * placed form hands its definition's list to the placement, a tab panel lists\n * its content — so each of those is followed by the same walk, and a panel\n * the route withheld has already lost its list. `parentId` is never followed:\n * a template keeps its parent's id after its parent stops listing it.\n *\n * A map with no root is a fragment with no entry point to measure reach from,\n * and keeps the whole-map walk.\n *\n * One rule of the renderer's is not in the map: a self-closing element draws\n * no children, and which components are self-closing is registry knowledge.\n * The editor refuses a child under one (`nodeAcceptsChildren`), so a Video\n * there is a hand-edited document rather than an authored page.\n *\n * The reach is computed only for a map that holds a Video at all, so a page\n * without one costs a single pass over its values and no walk.\n */\nexport function pageVideoObjects(\n nodes: Record<string, unknown> | null | undefined,\n context?: VideoObjectContext,\n): Record<string, unknown>[] {\n if (!nodes) return []\n const videoIds: string[] = []\n for (const id in nodes) {\n const node = nodes[id] as ComposedVideoNode | null | undefined\n if (node?.componentId === VIDEO_COMPONENT_ID) videoIds.push(id)\n }\n if (!videoIds.length) return []\n const drawn = nodes[NODE_ROOT_ID]\n ? collectDescendantIds(\n nodes as Record<NodeId, AglynNodeSchema>,\n NODE_ROOT_ID,\n )\n : undefined\n const found: Record<string, unknown>[] = []\n for (const id of videoIds) {\n if (drawn && id !== NODE_ROOT_ID && !drawn.has(id)) continue\n const block = videoObjectJsonLd(nodes[id] as ComposedVideoNode, context)\n if (block) found.push(block)\n }\n return found\n}\n"],"names":["NODE_ROOT_ID","videoEmbedUrl","collectDescendantIds","videoDurationIso8601","MEDIA_CDN_POSTER_WIDTH","absoluteMediaSrc","videoPosterSrc","VIDEO_COMPONENT_ID","MS_PER_SECOND","text","value","trimmed","trim","undefined","CALENDAR_DAY_UTC_HOUR","CALENDAR_DAY","ZONED_DATE_TIME","utcDay","match","hour","year","Number","month","day","time","Date","UTC","date","getUTCFullYear","getUTCMonth","getUTCDate","uploadDateTime","exec","noon","toISOString","zoned","instant","parse","isNaN","videoObjectJsonLd","node","context","componentId","props","resolvedProps","origin","hostId","name","description","uploadDate","thumbnailUrl","poster","src","generated","width","embedUrl","contentUrl","seconds","duration","isFinite","pageVideoObjects","nodes","videoIds","id","push","length","drawn","found","has","block"],"mappings":"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"}
@@ -14,6 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
+ import { type AnalyticsProviderDeclaration } from './analytics-provider';
17
18
  /**
18
19
  * Visitor tracking consent (AGL-1498): the single answer to "may this
19
20
  * tracking feature run for this visitor?", shared by every surface that has
@@ -252,17 +253,32 @@ export declare const GTM_CONTAINER_ID_PATTERN: RegExp;
252
253
  /** The configured GTM container when it is well-formed, else null. */
253
254
  export declare function resolveGtmContainerId(host: VisitorConsentHost | null | undefined): string | null;
254
255
  /**
255
- * Whether the site loads ANY Google tag — a GA property, a GTM container, or
256
- * both (AGL-2486).
256
+ * The site's analytics settings that can configure a tag, and the shape each
257
+ * must have to reach a page — the same patterns the console's Tracking tab
258
+ * validates with. A provider declares which of these it mounts a tag for
259
+ * (`analyticsProvider.settings` in `plugins.config.json`); a declared setting
260
+ * with no shape here configures nothing.
261
+ */
262
+ export declare const ANALYTICS_SETTING_PATTERNS: Readonly<Record<string, RegExp>>;
263
+ /**
264
+ * Whether the site runs ANY analytics tag a provider mounts — a measurement
265
+ * id, a tag container, or both (AGL-2486).
257
266
  *
258
267
  * The one predicate both consent questions are asked through, because the
259
- * alternative is two places that each remember only one of the two. A site
260
- * with a container and no GA id is a site that tracks: it was the shape that
261
- * would have had `consentGatedCategories` return `[]`, no banner rendered, and
262
- * the container loading ungated — which for GTM is worse than for GA, since a
263
- * container is exactly the thing that can carry advertising tags.
264
- */
265
- export declare function hostHasGoogleTag(host: VisitorConsentHost | null | undefined): boolean;
268
+ * alternative is two places that each remember only one of the settings. A
269
+ * site with a container and no measurement id is a site that tracks: it was
270
+ * the shape that would have had `consentGatedCategories` return `[]`, no
271
+ * banner rendered, and the container loading ungated — which for a container
272
+ * is worse than for a single tag, since a container is exactly the thing that
273
+ * can carry advertising tags.
274
+ *
275
+ * Read from the COMPILED provider declarations, never from which adapters
276
+ * have loaded: this is asked during render on the published page and in the
277
+ * console, before any adapter could have arrived, and "no tag" would mean no
278
+ * banner in front of one. With no provider declared, nothing can mount, and
279
+ * the answer is `false`.
280
+ */
281
+ export declare function hostConfiguresAnalyticsTag(host: VisitorConsentHost | null | undefined, declared?: readonly AnalyticsProviderDeclaration[]): boolean;
266
282
  /** Host opt-out of the tool. Absent means ACTIVE — the safe default. */
267
283
  export declare function isConsentToolDisabled(host: VisitorConsentHost | null | undefined): boolean;
268
284
  /** The host's posture choice; anything but `'strict'` reads as `'geo'`. */
@@ -470,15 +486,6 @@ export declare function clearAnalyticsCookies(hostname?: string | null): string[
470
486
  * Meta-specific.
471
487
  */
472
488
  export declare function clearCookiesWithPrefixes(prefixes: readonly string[], hostname?: string | null): string[];
473
- /**
474
- * The `window` flag `gtag.js` consults before every hit: with
475
- * `window['ga-disable-G-XXXX'] === true` the tag for that property sends
476
- * nothing and writes nothing. Google's own documented opt-out mechanism, and
477
- * the one that works on a tag which never received a consent-mode default
478
- * (AGL-1608) — a case that still exists after AGL-1622, because a tag arriving
479
- * through GTM or a host's own CMP is not one this module declared for.
480
- */
481
- export declare const GA_DISABLE_FLAG_PREFIX = "ga-disable-";
482
489
  /** A single consent-mode signal value. */
483
490
  export type AnalyticsConsentSignal = 'granted' | 'denied';
484
491
  /**
@@ -548,154 +555,24 @@ export declare function consentModeSignals(grants: {
548
555
  */
549
556
  export declare function advertisingGrantedByRecord(host: VisitorConsentHost | null | undefined, stored: StoredVisitorConsent | null | undefined): boolean;
550
557
  /**
551
- * The consent-mode `default` declaration that goes in front of the tag
552
- * (AGL-1622), for injection into the same inline block that creates
553
- * `dataLayer` and calls `gtag('config', …)`.
554
- *
555
- * ## What this is, and — load-bearing — what it is NOT
556
- *
557
- * It is NOT a licence to load the tag earlier. It is emitted INSIDE the block
558
- * that only renders when the AGL-1498 gate has already said yes, so it is
559
- * reached only on a pageview where analytics is granted. On every gated
560
- * pageview — an EU/UK/EEA visitor with no explicit accept, an unknown region, a
561
- * declined or opted-out visitor, a GPC browser — nothing here renders, no
562
- * request goes to `googletagmanager.com`, and this constant is never evaluated
563
- * into the page. "The script never LOADS, rather than load-then-suppress" is a
564
- * stated AGL-1498 property and it is intact everywhere the gate applies;
565
- * `consent-mode-default.spec.tsx` fails if that ever stops being true.
566
- *
567
- * the decision, 2026-08-14: load-then-restrict is approved for the UNITED
568
- * STATES, where the implied-consent posture already permits the load and the
569
- * restriction signals act on a tag that is legitimately resident. EU and UK
570
- * keep the prior-consent gate exactly as it is, because loading an analytics
571
- * tag before consent is the specific thing prior-consent law prohibits. So the
572
- * consent-mode mechanism is strictly ADDITIVE to the gate, never a replacement
573
- * for it.
574
- *
575
- * What declaring it actually buys, given the tag is granted by the time it is
576
- * read:
577
- *
578
- * - The advertising signals are denied FROM THE FIRST HIT. Without a default,
579
- * a freshly loaded tag runs with `ad_storage` unrestricted and only becomes
580
- * denied if the visitor happens to withdraw and change their mind again
581
- * (AGL-1608's `update`). The load-time state now matches the tool's actual
582
- * scope instead of being wider than it.
583
- * - A later `update` is a transition from a declared state rather than a tag's
584
- * first-ever consent signal, which is the shape GA4's consent-mode reporting
585
- * and any third-party CMP alongside ours both expect.
586
- *
587
- * The value is a literal, built from {@link analyticsConsentSignals} — no
588
- * interpolated input reaches it, which matters because it lands inside an
589
- * inline script (the AGL-138 concern).
590
- */
591
- export declare const GA_CONSENT_DEFAULT_SNIPPET: string;
592
- /**
593
- * The same declaration for a visitor who granted ADVERTISING too (AGL-1649).
594
- *
595
- * A second CONSTANT rather than a parameterised builder, for the reason the
596
- * first one is a constant: it lands inside an inline script (the AGL-138
597
- * concern), and a constant cannot interpolate anything. Both values are
598
- * literals fixed at module load; the only thing the caller chooses is which
599
- * of the two to emit.
600
- *
601
- * Reached only where {@link advertisingGrantedByRecord} says yes — host
602
- * opted in, explicit accept, explicit yes to this category — which is
603
- * evaluated client-side after hydration, like every other part of this gate.
604
- * The ISR property is unaffected: the cached HTML contains neither snippet,
605
- * because the whole block is inside the client-side `analyticsAllowed`
606
- * condition.
607
- *
608
- * Declaring the grant as the DEFAULT rather than sending a later `update` is
609
- * what keeps the tag's first hit correct. An update would arrive after
610
- * `config`, so the session's first pageview — usually the entire session for
611
- * a marketing visit — would carry the denied state the visitor did not
612
- * choose.
613
- */
614
- export declare const GA_CONSENT_DEFAULT_WITH_ADS_SNIPPET: string;
615
- /**
616
- * Consent-mode URL passthrough for the platform's own tag (AGL-2548).
617
- *
618
- * The advertising signals in {@link GA_CONSENT_DEFAULT_SNIPPET} are denied, so
619
- * on `aglyn.com` gtag writes no `_gcl_aw` cookie and the `gclid` an ad click
620
- * arrives with lives only in that first URL. The conversions it should be
621
- * credited for fire one hop later, on the console, where the posture grants
622
- * advertising storage outside the prior-consent regions — and without this
623
- * declaration the click id never makes that hop. The console reports a
624
- * conversion with no click behind it, and Google Ads counts nothing.
625
- *
626
- * `url_passthrough` is Google's mechanism for exactly this state: while
627
- * `ad_storage` is denied, gtag appends the click identifiers to links into the
628
- * cross-domain-linked hosts as URL parameters instead of setting a cookie. The
629
- * receiving tag reads them under its own consent state. Nothing is stored on
630
- * the denying surface, so the marketing site's opt-in advertising posture is
631
- * unchanged; the click id simply survives the link it was always meant to
632
- * survive.
633
- *
634
- * `ads_data_redaction` is the companion Google documents alongside it: with
635
- * `ad_storage` denied, the ad click identifiers are stripped from the hits this
636
- * tag sends and its network requests go to a cookieless endpoint. Declared
637
- * together so a denied visitor is measured with less, not merely differently.
638
- *
639
- * Both are `set` calls, so they must precede `gtag('config', …)` in the same
640
- * inline block — a `set` applies to hits processed after it, and the session's
641
- * first pageview is the one carrying the click.
642
- *
643
- * PLATFORM ID ONLY. A customer's tag is configured with their id and their
644
- * consent posture; how their ad click ids travel is theirs to decide, and a
645
- * passthrough declared on their behalf would rewrite links on their site.
646
- */
647
- export declare const GA_CLICK_ID_PASSTHROUGH_SNIPPET: string;
648
- /**
649
- * The measurement ids whose tag is actually RESIDENT in this page.
650
- *
651
- * Discovered from the page rather than taken from the host document on
652
- * purpose: what has to be silenced is whatever gtag.js is currently loaded and
653
- * configured for, which is not always what the host record configures — a tag
654
- * can arrive through GTM, and a stale `<script>` from earlier in the pageview
655
- * is exactly the thing this function exists to find.
656
- *
657
- * Ids are format-checked before they are used, because each one becomes a
658
- * `window` property name.
659
- */
660
- export declare function residentGaMeasurementIds(): string[];
661
- /**
662
- * Make every resident GA tag agree with the consent state, and return the ids
663
- * it acted on.
664
- *
665
- * ## Why deleting the cookies is not enough (AGL-1608)
666
- *
667
- * The AGL-1498 gate stops the script from LOADING, which is the right
668
- * enforcement for a fresh pageview and the wrong tool for a visitor who
669
- * withdraws mid-pageview: `gtag.js` has already executed, and React unmounting
670
- * the `<script>` element does not unload it. `window.gtag` stays live, GA4
671
- * enhanced measurement fires on its own (scroll depth, outbound click, file
672
- * download), and the tag re-writes `_ga_<id>` AFTER
673
- * {@link clearAnalyticsCookies} has deleted it. Reproduced on aglyn.com: an
674
- * opt-out, a hand-run sweep to zero cookies, then one scroll to the footer
675
- * brought `_ga_YW5PG16YTM` back.
676
- *
677
- * Two signals, because they fail differently and neither is guaranteed:
678
- * the `ga-disable-<id>` window flag silences a tag that never received a
679
- * consent-mode default, and the `consent`/`update` call reaches a tag that
680
- * arrived through GTM with its own id this function never saw.
681
- *
682
- * ## What this deliberately does NOT do
683
- *
684
- * It never touches the gate. Both signals act only on a tag that is already
685
- * resident — which, by construction, only exists after a grant. AGL-1622 later
686
- * added a consent-mode DEFAULT ({@link GA_CONSENT_DEFAULT_SNIPPET}), but inside
687
- * the gated block and therefore only on a pageview the gate already allowed:
688
- * load-then-restrict is additive to "the script never LOADS", not a
689
- * replacement for it, and the prior-consent regions are unaffected.
558
+ * Make every resident analytics tag agree with the consent state, and return
559
+ * the ids it acted on (AGL-1608).
560
+ *
561
+ * The AGL-1498 gate stops a tag from LOADING, which is the right enforcement
562
+ * for a fresh pageview and the wrong tool for a visitor who withdraws
563
+ * mid-pageview: the vendor's library has already executed, and React
564
+ * unmounting its `<script>` element does not unload it. So the tag that is
565
+ * already there is told, through the adapter of whichever vendor it is
566
+ * (`analytics-provider.ts`). A tag only exists after a grant, so this never
567
+ * touches the gate.
690
568
  *
691
569
  * Symmetric on purpose: a visitor who opts out and changes their mind in the
692
- * same pageview would otherwise stay silently unmeasured until they navigated,
693
- * because the re-rendered `<Script>` cannot re-execute an already-loaded tag.
694
- * The re-grant restores whatever the CALLER passes — analytics alone on a site
695
- * that never asks the advertising question (`advertising` defaults to false),
696
- * and both categories where the visitor's record grants both (AGL-1649). The
697
- * clamp in {@link consentModeSignals} still makes ads-without-analytics
698
- * unrepresentable, so a withdrawal can never restore advertising alone.
570
+ * same pageview would otherwise stay silently unmeasured until they
571
+ * navigated. The re-grant restores whatever the CALLER passes — analytics
572
+ * alone on a site that never asks the advertising question (`advertising`
573
+ * defaults to false), and both categories where the visitor's record grants
574
+ * both (AGL-1649). Advertising is clamped to analytics, so a withdrawal can
575
+ * never restore advertising alone.
699
576
  */
700
577
  export declare function setResidentAnalyticsTags(granted: boolean, advertising?: boolean): string[];
701
578
  /**