@aglyn/aglyn 1.0.0-beta.218 → 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 (334) hide show
  1. package/package.json +11 -11
  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/analytics-events.d.ts +1 -1
  9. package/src/lib/app-utils/analytics-events.js.map +1 -1
  10. package/src/lib/app-utils/analytics-provider.d.ts +182 -0
  11. package/src/lib/app-utils/analytics-provider.js +133 -0
  12. package/src/lib/app-utils/analytics-provider.js.map +1 -0
  13. package/src/lib/app-utils/api-plugins.d.ts +46 -0
  14. package/src/lib/app-utils/api-plugins.js +19 -0
  15. package/src/lib/app-utils/api-plugins.js.map +1 -1
  16. package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
  17. package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
  18. package/src/lib/app-utils/artifact-overrides.js.map +1 -0
  19. package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
  20. package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
  21. package/src/lib/app-utils/artifact-provenance.js.map +1 -0
  22. package/src/lib/app-utils/assist-credits.js +2 -1
  23. package/src/lib/app-utils/assist-credits.js.map +1 -1
  24. package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
  25. package/src/lib/app-utils/binding-token-catalog.js +17 -41
  26. package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
  27. package/src/lib/app-utils/brand-support-line.d.ts +36 -0
  28. package/src/lib/app-utils/brand-support-line.js +38 -0
  29. package/src/lib/app-utils/brand-support-line.js.map +1 -0
  30. package/src/lib/app-utils/campaign-touch.d.ts +5 -5
  31. package/src/lib/app-utils/campaign-touch.js +8 -8
  32. package/src/lib/app-utils/campaign-touch.js.map +1 -1
  33. package/src/lib/app-utils/collection-entries.d.ts +2 -2
  34. package/src/lib/app-utils/collection-entries.js.map +1 -1
  35. package/src/lib/app-utils/consent-group-change.d.ts +4 -0
  36. package/src/lib/app-utils/consent-group-change.js.map +1 -1
  37. package/src/lib/app-utils/console-record-links.d.ts +10 -89
  38. package/src/lib/app-utils/console-record-links.js +3 -107
  39. package/src/lib/app-utils/console-record-links.js.map +1 -1
  40. package/src/lib/app-utils/console-routes.d.ts +16 -7
  41. package/src/lib/app-utils/console-routes.js +9 -3
  42. package/src/lib/app-utils/console-routes.js.map +1 -1
  43. package/src/lib/app-utils/contacts.d.ts +23 -1
  44. package/src/lib/app-utils/contacts.js +21 -5
  45. package/src/lib/app-utils/contacts.js.map +1 -1
  46. package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
  47. package/src/lib/app-utils/cross-pool-desync.js +47 -0
  48. package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
  49. package/src/lib/app-utils/csv-upload.d.ts +29 -0
  50. package/src/lib/app-utils/csv-upload.js +29 -0
  51. package/src/lib/app-utils/csv-upload.js.map +1 -0
  52. package/src/lib/app-utils/dataset-models.d.ts +5 -5
  53. package/src/lib/app-utils/dataset-models.js +19 -2
  54. package/src/lib/app-utils/dataset-models.js.map +1 -1
  55. package/src/lib/app-utils/datasets.d.ts +65 -11
  56. package/src/lib/app-utils/datasets.js +74 -11
  57. package/src/lib/app-utils/datasets.js.map +1 -1
  58. package/src/lib/app-utils/docs-deflection.d.ts +6 -6
  59. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  60. package/src/lib/app-utils/docs-help.generated.d.ts +12 -6
  61. package/src/lib/app-utils/docs-help.generated.js +21 -6
  62. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  63. package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
  64. package/src/lib/app-utils/docs-index.generated.js +514 -318
  65. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  66. package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
  67. package/src/lib/app-utils/docs-retrieval.js +14 -10
  68. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  69. package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
  70. package/src/lib/app-utils/duplicate-resource.js +7 -4
  71. package/src/lib/app-utils/duplicate-resource.js.map +1 -1
  72. package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
  73. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  74. package/src/lib/app-utils/element-ui.d.ts +1 -1
  75. package/src/lib/app-utils/element-ui.js +1 -1
  76. package/src/lib/app-utils/element-ui.js.map +1 -1
  77. package/src/lib/app-utils/email-starter-policy.js +3 -3
  78. package/src/lib/app-utils/email-starter-policy.js.map +1 -1
  79. package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
  80. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  81. package/src/lib/app-utils/error-beacon.d.ts +75 -6
  82. package/src/lib/app-utils/error-beacon.js +160 -21
  83. package/src/lib/app-utils/error-beacon.js.map +1 -1
  84. package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
  85. package/src/lib/app-utils/expand-repeatables.js +10 -10
  86. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  87. package/src/lib/app-utils/host-events.d.ts +82 -0
  88. package/src/lib/app-utils/host-events.js +62 -0
  89. package/src/lib/app-utils/host-events.js.map +1 -0
  90. package/src/lib/app-utils/margin-utilization.d.ts +4 -69
  91. package/src/lib/app-utils/margin-utilization.js +82 -69
  92. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  93. package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
  94. package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
  95. package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
  96. package/src/lib/app-utils/node-interactions.d.ts +7 -7
  97. package/src/lib/app-utils/node-interactions.js.map +1 -1
  98. package/src/lib/app-utils/notifications.d.ts +78 -47
  99. package/src/lib/app-utils/notifications.js +63 -44
  100. package/src/lib/app-utils/notifications.js.map +1 -1
  101. package/src/lib/app-utils/payments-configured.d.ts +8 -21
  102. package/src/lib/app-utils/payments-configured.js +8 -23
  103. package/src/lib/app-utils/payments-configured.js.map +1 -1
  104. package/src/lib/app-utils/plan-entitlements.d.ts +43 -128
  105. package/src/lib/app-utils/plan-entitlements.js +145 -304
  106. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  107. package/src/lib/app-utils/platform-brand.d.ts +10 -0
  108. package/src/lib/app-utils/platform-brand.js +14 -3
  109. package/src/lib/app-utils/platform-brand.js.map +1 -1
  110. package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
  111. package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
  112. package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
  113. package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
  114. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  115. package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
  116. package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
  117. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
  118. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
  119. package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
  120. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
  121. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  122. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  123. package/src/lib/app-utils/release-flags.d.ts +14 -5
  124. package/src/lib/app-utils/release-flags.js +21 -125
  125. package/src/lib/app-utils/release-flags.js.map +1 -1
  126. package/src/lib/app-utils/repeat-sources.d.ts +9 -0
  127. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  128. package/src/lib/app-utils/retained-refusals.d.ts +73 -0
  129. package/src/lib/app-utils/retained-refusals.js +186 -0
  130. package/src/lib/app-utils/retained-refusals.js.map +1 -0
  131. package/src/lib/app-utils/scope-tokens.d.ts +27 -0
  132. package/src/lib/app-utils/scope-tokens.js +61 -0
  133. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  134. package/src/lib/app-utils/screen-link-context.js +3 -3
  135. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  136. package/src/lib/app-utils/screen-route.js +1 -1
  137. package/src/lib/app-utils/screen-route.js.map +1 -1
  138. package/src/lib/app-utils/seo-audit.d.ts +132 -0
  139. package/src/lib/app-utils/seo-audit.js +246 -0
  140. package/src/lib/app-utils/seo-audit.js.map +1 -0
  141. package/src/lib/app-utils/seo-keywords.d.ts +48 -0
  142. package/src/lib/app-utils/seo-keywords.js +60 -0
  143. package/src/lib/app-utils/seo-keywords.js.map +1 -0
  144. package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
  145. package/src/lib/app-utils/seo-page-facts.js +155 -0
  146. package/src/lib/app-utils/seo-page-facts.js.map +1 -0
  147. package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
  148. package/src/lib/app-utils/seo-site-scan.js +162 -0
  149. package/src/lib/app-utils/seo-site-scan.js.map +1 -0
  150. package/src/lib/app-utils/seo-title-variables.js +1 -1
  151. package/src/lib/app-utils/seo-title-variables.js.map +1 -1
  152. package/src/lib/app-utils/server.d.ts +6 -9
  153. package/src/lib/app-utils/server.js +17 -27
  154. package/src/lib/app-utils/server.js.map +1 -1
  155. package/src/lib/app-utils/site-interactions.d.ts +501 -0
  156. package/src/lib/app-utils/site-interactions.js +554 -0
  157. package/src/lib/app-utils/site-interactions.js.map +1 -0
  158. package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
  159. package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +2 -2
  160. package/src/lib/app-utils/site-theme.js.map +1 -0
  161. package/src/lib/app-utils/sitemap.d.ts +4 -3
  162. package/src/lib/app-utils/sitemap.js +9 -7
  163. package/src/lib/app-utils/sitemap.js.map +1 -1
  164. package/src/lib/app-utils/stale-build-error.d.ts +51 -0
  165. package/src/lib/app-utils/stale-build-error.js +100 -1
  166. package/src/lib/app-utils/stale-build-error.js.map +1 -1
  167. package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
  168. package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
  169. package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
  170. package/src/lib/app-utils/theme-library.d.ts +10 -3
  171. package/src/lib/app-utils/theme-library.js +13 -4
  172. package/src/lib/app-utils/theme-library.js.map +1 -1
  173. package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
  174. package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
  175. package/src/lib/app-utils/utm-attribution.js.map +1 -0
  176. package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
  177. package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
  178. package/src/lib/app-utils/utm-forwarding.js.map +1 -0
  179. package/src/lib/app-utils/video-object.js +6 -5
  180. package/src/lib/app-utils/video-object.js.map +1 -1
  181. package/src/lib/app-utils/visitor-consent.d.ts +41 -164
  182. package/src/lib/app-utils/visitor-consent.js +66 -277
  183. package/src/lib/app-utils/visitor-consent.js.map +1 -1
  184. package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
  185. package/src/lib/app-utils/web-vitals-rum.js +31 -63
  186. package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
  187. package/src/lib/app-utils/where-used.d.ts +11 -6
  188. package/src/lib/app-utils/where-used.js +3 -8
  189. package/src/lib/app-utils/where-used.js.map +1 -1
  190. package/src/lib/foundation/definitions/components.types.d.ts +1 -1
  191. package/src/lib/foundation/definitions/components.types.js +1 -1
  192. package/src/lib/foundation/definitions/components.types.js.map +1 -1
  193. package/src/lib/foundation/definitions/org-billing.types.d.ts +9 -135
  194. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  195. package/src/lib/foundation/definitions/organization.types.d.ts +1 -3
  196. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  197. package/src/lib/foundation/definitions/platform.types.d.ts +11 -5
  198. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  199. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
  200. package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
  201. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  202. package/src/lib/foundation/index.d.ts +1 -1
  203. package/src/lib/foundation/index.js.map +1 -1
  204. package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
  205. package/src/lib/plugin-manager/besigner-documents.js +61 -0
  206. package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
  207. package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
  208. package/src/lib/plugin-manager/computed-variables.js +74 -0
  209. package/src/lib/plugin-manager/computed-variables.js.map +1 -0
  210. package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
  211. package/src/lib/plugin-manager/custom-fields.js +47 -1
  212. package/src/lib/plugin-manager/custom-fields.js.map +1 -1
  213. package/src/lib/plugin-manager/feature-plugins.d.ts +292 -20
  214. package/src/lib/plugin-manager/feature-plugins.js +105 -17
  215. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  216. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
  217. package/src/lib/plugin-manager/first-party-plugins.generated.js +784 -10
  218. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  219. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
  220. package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
  221. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
  222. package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
  223. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  224. package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
  225. package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
  226. package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
  227. package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
  228. package/src/lib/plugin-manager/plugin-dependents.js +75 -0
  229. package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
  230. package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
  231. package/src/lib/plugin-manager/plugin-distribution.js +45 -0
  232. package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
  233. package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
  234. package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
  235. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
  236. package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
  237. package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
  238. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
  239. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
  240. package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
  241. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  242. package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
  243. package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
  244. package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
  245. package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
  246. package/src/lib/plugin-manager/plugin-record-index.js +51 -0
  247. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
  248. package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
  249. package/src/lib/plugin-manager/plugin-record-written.js +42 -0
  250. package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
  251. package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
  252. package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
  253. package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
  254. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
  255. package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
  256. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
  257. package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
  258. package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
  259. package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
  260. package/src/lib/plugin-manager/plugin-site-export.js +40 -0
  261. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
  263. package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
  264. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
  265. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
  266. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  267. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
  268. package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
  269. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
  270. package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
  271. package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
  272. package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
  273. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +107 -0
  274. package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
  275. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
  276. package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
  277. package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
  278. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  279. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  280. package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
  281. package/src/lib/plugin-manager/record-captured-contact.js +10 -32
  282. package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
  283. package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
  284. package/src/lib/plugin-manager/repeat-rows.js +108 -0
  285. package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
  286. package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
  287. package/src/lib/plugin-manager/submission-record-target.js +125 -0
  288. package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
  289. package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
  290. package/src/lib/plugin-manager/video-embed-provider.js +165 -0
  291. package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
  292. package/src/lib/types/nodes.d.ts +2 -2
  293. package/src/lib/types/nodes.js.map +1 -1
  294. package/src/lib/app-utils/campaign-attribution.js.map +0 -1
  295. package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
  296. package/src/lib/app-utils/contact-holder.d.ts +0 -68
  297. package/src/lib/app-utils/contact-holder.js +0 -129
  298. package/src/lib/app-utils/contact-holder.js.map +0 -1
  299. package/src/lib/app-utils/contact-merge.d.ts +0 -136
  300. package/src/lib/app-utils/contact-merge.js +0 -412
  301. package/src/lib/app-utils/contact-merge.js.map +0 -1
  302. package/src/lib/app-utils/crm-csv.d.ts +0 -239
  303. package/src/lib/app-utils/crm-csv.js +0 -467
  304. package/src/lib/app-utils/crm-csv.js.map +0 -1
  305. package/src/lib/app-utils/crm-digest.d.ts +0 -185
  306. package/src/lib/app-utils/crm-digest.js +0 -272
  307. package/src/lib/app-utils/crm-digest.js.map +0 -1
  308. package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
  309. package/src/lib/app-utils/form-dataset-binding.js +0 -74
  310. package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
  311. package/src/lib/app-utils/list-import.d.ts +0 -158
  312. package/src/lib/app-utils/list-import.js +0 -332
  313. package/src/lib/app-utils/list-import.js.map +0 -1
  314. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
  315. package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
  316. package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
  317. package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
  318. package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
  319. package/src/lib/app-utils/marketplace-theme.js.map +0 -1
  320. package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
  321. package/src/lib/app-utils/marketplace-update-state.js +0 -132
  322. package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
  323. package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
  324. package/src/lib/app-utils/publisher-agreement.js +0 -321
  325. package/src/lib/app-utils/publisher-agreement.js.map +0 -1
  326. package/src/lib/app-utils/wistia-embed.d.ts +0 -104
  327. package/src/lib/app-utils/wistia-embed.js +0 -138
  328. package/src/lib/app-utils/wistia-embed.js.map +0 -1
  329. package/src/lib/app-utils/workflows.d.ts +0 -136
  330. package/src/lib/app-utils/workflows.js +0 -346
  331. package/src/lib/app-utils/workflows.js.map +0 -1
  332. package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
  333. package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
  334. package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
@@ -18,6 +18,11 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  * Where an account came from (AGL-1731) — the campaign contract between the
19
19
  * marketing site's links and the console's signup.
20
20
  *
21
+ * Named for what it parses, the web's UTM labels. The `campaign` in
22
+ * `utm_campaign` is the label an ad push carries; it is not the Marketing
23
+ * plugin's campaign, and nothing here reads one. What a site's own visitors
24
+ * carry to the moment they identify is `campaign-touch.ts`'s.
25
+ *
21
26
  * Until this existed a paid click, an organic visit and a partner link
22
27
  * arrived indistinguishable: `sign_up` carried `method` and nothing else, so
23
28
  * no report could say which spend produced a customer. That is tolerable
@@ -68,14 +73,14 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
68
73
  * site's links, the stored wire form and this parser all have to spell them
69
74
  * identically — two spellings would read as two dimensions in GA4 and split
70
75
  * every report in half.
71
- */ export const CAMPAIGN_QUERY_KEYS = [
76
+ */ export const UTM_QUERY_KEYS = [
72
77
  'utm_source',
73
78
  'utm_medium',
74
79
  'utm_campaign'
75
80
  ];
76
81
  /** Longest value stored or sent, matching `sanitizeEventParams`'s own cap. */ const MAX_VALUE_LENGTH = 100;
77
82
  /** The same shape test the event sanitizer uses, applied to the stored exit too. */ const EMAIL_SHAPED = /[^\s@]+@[^\s@]+\.[^\s@]+/;
78
- /** Which `CampaignAttribution` field each URL key fills. */ const FIELD_FOR_KEY = {
83
+ /** Which `UtmAttribution` field each URL key fills. */ const FIELD_FOR_KEY = {
79
84
  utm_source: 'source',
80
85
  utm_medium: 'medium',
81
86
  utm_campaign: 'campaign'
@@ -113,10 +118,10 @@ function rawValue(params, key) {
113
118
  * Null rather than `{}` so a caller can branch on "was there a campaign at
114
119
  * all". An empty object written to an account would read as "arrived from
115
120
  * nowhere, confirmed" instead of "never asked", and those are different facts.
116
- */ export function parseCampaignAttribution(params) {
121
+ */ export function parseUtmAttribution(params) {
117
122
  if (!params) return null;
118
123
  const attribution = {};
119
- for (const key of CAMPAIGN_QUERY_KEYS){
124
+ for (const key of UTM_QUERY_KEYS){
120
125
  const value = scrubValue(rawValue(params, key));
121
126
  if (value) attribution[FIELD_FOR_KEY[key]] = value;
122
127
  }
@@ -125,10 +130,10 @@ function rawValue(params, key) {
125
130
  /**
126
131
  * The canonical query string for a campaign — the ONLY place the stored form
127
132
  * is written, so it cannot drift from what the parser reads back.
128
- */ export function campaignAttributionQuery(attribution) {
133
+ */ export function utmAttributionQuery(attribution) {
129
134
  if (!attribution) return '';
130
135
  const params = new URLSearchParams();
131
- for (const key of CAMPAIGN_QUERY_KEYS){
136
+ for (const key of UTM_QUERY_KEYS){
132
137
  const value = attribution[FIELD_FOR_KEY[key]];
133
138
  if (value) params.set(key, value);
134
139
  }
@@ -148,7 +153,7 @@ function rawValue(params, key) {
148
153
  * `campaign_source: undefined` on every unattributed signup would survive the
149
154
  * sanitizer as an absence but read, to whoever writes the report, as a
150
155
  * dimension that is populated sometimes and broken the rest of the time.
151
- */ export function campaignEventParams(attribution) {
156
+ */ export function utmEventParams(attribution) {
152
157
  if (!attribution) return {};
153
158
  return _extends({}, attribution.source ? {
154
159
  campaign_source: attribution.source
@@ -159,4 +164,4 @@ function rawValue(params, key) {
159
164
  } : {});
160
165
  }
161
166
 
162
- //# sourceMappingURL=campaign-attribution.js.map
167
+ //# sourceMappingURL=utm-attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/utm-attribution.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 * Where an account came from (AGL-1731) — the campaign contract between the\n * marketing site's links and the console's signup.\n *\n * Named for what it parses, the web's UTM labels. The `campaign` in\n * `utm_campaign` is the label an ad push carries; it is not the Marketing\n * plugin's campaign, and nothing here reads one. What a site's own visitors\n * carry to the moment they identify is `campaign-touch.ts`'s.\n *\n * Until this existed a paid click, an organic visit and a partner link\n * arrived indistinguishable: `sign_up` carried `method` and nothing else, so\n * no report could say which spend produced a customer. That is tolerable\n * while nothing is being spent and not tolerable the day advertising starts,\n * because the measurement has to exist BEFORE the traffic — a signup that\n * lands unattributed is unattributed forever.\n *\n * ## Why an allowlist, and why exactly three\n *\n * `utm_source`, `utm_medium` and `utm_campaign` are the three axes an ad\n * report is read on: which channel, what kind of placement, which push. They\n * are also the three the marketing site can be told to emit without further\n * decisions.\n *\n * `utm_term` and `utm_content` are deliberately OUT, matching the refusal the\n * tenant's own first-party collector already made for its visitors\n * (`apps/tenant/app/api/analytics/collect/route.ts`): keyword- and\n * variant-level labels multiply cardinality without answering a question\n * anyone asks of a signup, and a keyword string is the most likely of the\n * five to carry something a person typed. `gclid` and bare `ref` are out for\n * that same cardinality reason. Not for a privacy one: the property runs ads\n * personalization ON in every region, and Google's own auto-tagging already\n * carries the click identifier into it, so re-collecting `gclid` here would\n * duplicate a field the platform owns rather than withhold anything.\n *\n * The allowlist is the privacy mechanism, not a convenience. A parser that\n * copied \"the campaign-ish parameters\" would be one marketing link away from\n * storing `?email=` against a person, and the standing rule is that personal\n * data never travels in a URL parameter in the first place.\n *\n * ## Two exits, and only one of them is already guarded\n *\n * A parsed campaign leaves this process twice: onto the GA4 `sign_up` hit,\n * and into `users/{uid}` so revenue can be attributed to it months later.\n * Only the first passes `sanitizeEventParams`. The Firestore write does not,\n * so the scrub — email shape refused, length capped, blanks dropped — lives\n * HERE, on the way in, and both exits inherit it.\n *\n * ## Why the wire form is what gets stored\n *\n * Same contract as the AGL-1535 plan intent it rides beside: the stored value\n * is the query string, re-parsed by this parser on the way out. `users/{uid}`\n * is owner-writable, so a stored field is a client-FORGEABLE field. Re-parsing\n * means a hand-edited document can claim no more than a hand-edited URL could,\n * which is a marketing label and nothing that touches entitlement.\n */\n\n/**\n * The URL parameter names, in report order. Exported because the marketing\n * site's links, the stored wire form and this parser all have to spell them\n * identically — two spellings would read as two dimensions in GA4 and split\n * every report in half.\n */\nexport const UTM_QUERY_KEYS = [\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n] as const\n\n/** A campaign, with every field optional — a partial one is still worth having. */\nexport interface UtmAttribution {\n /** `utm_source` — the channel: `google`, `hn`, a partner's name. */\n source?: string\n /** `utm_medium` — the kind of placement: `cpc`, `email`, `referral`. */\n medium?: string\n /** `utm_campaign` — which push: `sept-launch`. */\n campaign?: string\n}\n\n/** Longest value stored or sent, matching `sanitizeEventParams`'s own cap. */\nconst MAX_VALUE_LENGTH = 100\n\n/** The same shape test the event sanitizer uses, applied to the stored exit too. */\nconst EMAIL_SHAPED = /[^\\s@]+@[^\\s@]+\\.[^\\s@]+/\n\n/** Which `UtmAttribution` field each URL key fills. */\nconst FIELD_FOR_KEY: Record<\n (typeof UTM_QUERY_KEYS)[number],\n keyof UtmAttribution\n> = {\n utm_source: 'source',\n utm_medium: 'medium',\n utm_campaign: 'campaign',\n}\n\n/** What a Server Component holds before it has a `URLSearchParams`. */\nexport type UtmParamSource =\n | URLSearchParams\n | Record<string, string | string[] | undefined>\n | null\n | undefined\n\nfunction rawValue(params: UtmParamSource, key: string): string | null {\n if (!params) return null\n if (typeof (params as URLSearchParams).get === 'function') {\n return (params as URLSearchParams).get(key)\n }\n const value = (params as Record<string, string | string[] | undefined>)[key]\n // A repeated parameter arrives as an array. The first one wins rather than\n // being joined: `utm_medium=cpc&utm_medium=organic` is a malformed link, and\n // inventing `cpc,organic` would put a value in the reports that no campaign\n // ever set.\n if (Array.isArray(value)) return value.length ? value[0] : null\n return typeof value === 'string' ? value : null\n}\n\n/**\n * Reduce one raw parameter to something storable, or null to drop it.\n *\n * Dropping the single value rather than the whole campaign is deliberate: a\n * mail-merge link that puts an address in `utm_source` still names a real\n * `utm_campaign`, and keeping the half that is safe beats losing the\n * attribution to protect the half that is not.\n */\nfunction scrubValue(value: string | null): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (!trimmed) return null\n if (EMAIL_SHAPED.test(trimmed)) return null\n return trimmed.slice(0, MAX_VALUE_LENGTH)\n}\n\n/**\n * Read the campaign off a signup URL, or null when it names none.\n *\n * Null rather than `{}` so a caller can branch on \"was there a campaign at\n * all\". An empty object written to an account would read as \"arrived from\n * nowhere, confirmed\" instead of \"never asked\", and those are different facts.\n */\nexport function parseUtmAttribution(\n params: UtmParamSource,\n): UtmAttribution | null {\n if (!params) return null\n const attribution: UtmAttribution = {}\n for (const key of UTM_QUERY_KEYS) {\n const value = scrubValue(rawValue(params, key))\n if (value) attribution[FIELD_FOR_KEY[key]] = value\n }\n return Object.keys(attribution).length ? attribution : null\n}\n\n/**\n * The canonical query string for a campaign — the ONLY place the stored form\n * is written, so it cannot drift from what the parser reads back.\n */\nexport function utmAttributionQuery(\n attribution: UtmAttribution | null | undefined,\n): string {\n if (!attribution) return ''\n const params = new URLSearchParams()\n for (const key of UTM_QUERY_KEYS) {\n const value = attribution[FIELD_FOR_KEY[key]]\n if (value) params.set(key, value)\n }\n return params.toString()\n}\n\n/**\n * The campaign as GA4 event parameters, ready to spread into `sign_up`.\n *\n * Renamed off the `utm_` spellings on purpose. These are OUR event parameters,\n * registered as custom dimensions on the property (see `docs/ANALYTICS.md`);\n * the `utm_` names belong to GA's own automatic campaign collection, and\n * shipping a custom parameter under a name the platform also owns is how a\n * dimension ends up meaning two things.\n *\n * Returns an empty object — never keys with `undefined` values — so that\n * spreading it into an organic signup's params adds nothing at all. A\n * `campaign_source: undefined` on every unattributed signup would survive the\n * sanitizer as an absence but read, to whoever writes the report, as a\n * dimension that is populated sometimes and broken the rest of the time.\n */\nexport function utmEventParams(\n attribution: UtmAttribution | null | undefined,\n): {\n campaign_source?: string\n campaign_medium?: string\n campaign_name?: string\n} {\n if (!attribution) return {}\n return {\n ...(attribution.source ? { campaign_source: attribution.source } : {}),\n ...(attribution.medium ? { campaign_medium: attribution.medium } : {}),\n ...(attribution.campaign ? { campaign_name: attribution.campaign } : {}),\n }\n}\n"],"names":["UTM_QUERY_KEYS","MAX_VALUE_LENGTH","EMAIL_SHAPED","FIELD_FOR_KEY","utm_source","utm_medium","utm_campaign","rawValue","params","key","get","value","Array","isArray","length","scrubValue","trimmed","trim","test","slice","parseUtmAttribution","attribution","Object","keys","utmAttributionQuery","URLSearchParams","set","toString","utmEventParams","source","campaign_source","medium","campaign_medium","campaign","campaign_name"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDC,GAED;;;;;CAKC,GACD,OAAO,MAAMA,iBAAiB;IAC5B;IACA;IACA;CACD,CAAS;AAYV,4EAA4E,GAC5E,MAAMC,mBAAmB;AAEzB,kFAAkF,GAClF,MAAMC,eAAe;AAErB,qDAAqD,GACrD,MAAMC,gBAGF;IACFC,YAAY;IACZC,YAAY;IACZC,cAAc;AAChB;AASA,SAASC,SAASC,MAAsB,EAAEC,GAAW;IACnD,IAAI,CAACD,QAAQ,OAAO;IACpB,IAAI,OAAO,AAACA,OAA2BE,GAAG,KAAK,YAAY;QACzD,OAAO,AAACF,OAA2BE,GAAG,CAACD;IACzC;IACA,MAAME,QAAQ,AAACH,MAAwD,CAACC,IAAI;IAC5E,2EAA2E;IAC3E,6EAA6E;IAC7E,4EAA4E;IAC5E,YAAY;IACZ,IAAIG,MAAMC,OAAO,CAACF,QAAQ,OAAOA,MAAMG,MAAM,GAAGH,KAAK,CAAC,EAAE,GAAG;IAC3D,OAAO,OAAOA,UAAU,WAAWA,QAAQ;AAC7C;AAEA;;;;;;;CAOC,GACD,SAASI,WAAWJ,KAAoB;IACtC,IAAI,OAAOA,UAAU,UAAU,OAAO;IACtC,MAAMK,UAAUL,MAAMM,IAAI;IAC1B,IAAI,CAACD,SAAS,OAAO;IACrB,IAAId,aAAagB,IAAI,CAACF,UAAU,OAAO;IACvC,OAAOA,QAAQG,KAAK,CAAC,GAAGlB;AAC1B;AAEA;;;;;;CAMC,GACD,OAAO,SAASmB,oBACdZ,MAAsB;IAEtB,IAAI,CAACA,QAAQ,OAAO;IACpB,MAAMa,cAA8B,CAAC;IACrC,KAAK,MAAMZ,OAAOT,eAAgB;QAChC,MAAMW,QAAQI,WAAWR,SAASC,QAAQC;QAC1C,IAAIE,OAAOU,WAAW,CAAClB,aAAa,CAACM,IAAI,CAAC,GAAGE;IAC/C;IACA,OAAOW,OAAOC,IAAI,CAACF,aAAaP,MAAM,GAAGO,cAAc;AACzD;AAEA;;;CAGC,GACD,OAAO,SAASG,oBACdH,WAA8C;IAE9C,IAAI,CAACA,aAAa,OAAO;IACzB,MAAMb,SAAS,IAAIiB;IACnB,KAAK,MAAMhB,OAAOT,eAAgB;QAChC,MAAMW,QAAQU,WAAW,CAAClB,aAAa,CAACM,IAAI,CAAC;QAC7C,IAAIE,OAAOH,OAAOkB,GAAG,CAACjB,KAAKE;IAC7B;IACA,OAAOH,OAAOmB,QAAQ;AACxB;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,eACdP,WAA8C;IAM9C,IAAI,CAACA,aAAa,OAAO,CAAC;IAC1B,OAAO,aACDA,YAAYQ,MAAM,GAAG;QAAEC,iBAAiBT,YAAYQ,MAAM;IAAC,IAAI,CAAC,GAChER,YAAYU,MAAM,GAAG;QAAEC,iBAAiBX,YAAYU,MAAM;IAAC,IAAI,CAAC,GAChEV,YAAYY,QAAQ,GAAG;QAAEC,eAAeb,YAAYY,QAAQ;IAAC,IAAI,CAAC;AAE1E"}
@@ -15,9 +15,9 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  /**
18
- * Carry the campaign across the domain hop (AGL-1731).
18
+ * Carry the visit's UTM labels across the domain hop (AGL-1731).
19
19
  *
20
- * `campaign-attribution.ts` is the READER half of this contract and it works.
20
+ * `utm-attribution.ts` is the READER half of this contract and it works.
21
21
  * This is the half that was missing, and without it the reader was fed
22
22
  * nothing: `aglyn.com` is a tenant published site and `app.aglyn.com` is the
23
23
  * console, a real cross-origin hop, and no code anywhere put a `utm_*`
@@ -95,7 +95,7 @@
95
95
  * it. There is no default, no inference from the referrer, and no
96
96
  * `utm_source=direct` — a signup with no campaign must reach the console with
97
97
  * no campaign, so that "arrived from nowhere" and "never asked" stay the
98
- * distinct facts `campaign-attribution.ts` keeps them.
98
+ * distinct facts `utm-attribution.ts` keeps them.
99
99
  *
100
100
  * Only links to OUR OWN console origin are touched, and the origin is supplied
101
101
  * by the caller rather than written here — a self-host install points
@@ -115,14 +115,14 @@
115
115
  * is the exact failure AGL-1731 is about, and it is worse than a few
116
116
  * mislabelled rows that GA's own Hostname dimension can already separate.
117
117
  */
118
- import { type CampaignAttribution } from './campaign-attribution';
118
+ import { type UtmAttribution } from './utm-attribution';
119
119
  /**
120
120
  * Where the first touch of this visit is held. `sessionStorage`, so it dies
121
121
  * with the tab: a campaign is a fact about one visit, and a value that
122
122
  * outlived the visit would start attributing next week's organic return to
123
123
  * this week's ad.
124
124
  */
125
- export declare const CAMPAIGN_VISIT_STORAGE_KEY = "aglyn:campaign";
125
+ export declare const UTM_VISIT_STORAGE_KEY = "aglyn:campaign";
126
126
  /**
127
127
  * What a read of the first-touch store produced.
128
128
  *
@@ -134,9 +134,9 @@ export declare const CAMPAIGN_VISIT_STORAGE_KEY = "aglyn:campaign";
134
134
  * treats it differently: it falls through to the live URL instead of
135
135
  * concluding anything.
136
136
  */
137
- export type VisitCampaignRead = {
137
+ export type VisitUtmRead = {
138
138
  status: 'campaign';
139
- campaign: CampaignAttribution;
139
+ campaign: UtmAttribution;
140
140
  } | {
141
141
  status: 'none';
142
142
  } | {
@@ -150,7 +150,7 @@ export type VisitCampaignRead = {
150
150
  * string can claim no more than an inbound URL could — three allowlisted keys,
151
151
  * trimmed, email shapes refused, capped.
152
152
  */
153
- export declare function readVisitCampaign(): VisitCampaignRead;
153
+ export declare function readVisitUtm(): VisitUtmRead;
154
154
  /**
155
155
  * Remember the campaign on the current URL as this visit's first touch.
156
156
  *
@@ -161,7 +161,7 @@ export declare function readVisitCampaign(): VisitCampaignRead;
161
161
  *
162
162
  * Returns what is now remembered so a caller can assert on it.
163
163
  */
164
- export declare function rememberVisitCampaign(search?: string): CampaignAttribution | null;
164
+ export declare function rememberVisitUtm(search?: string): UtmAttribution | null;
165
165
  /**
166
166
  * Tell this module what the visitor's analytics consent state is.
167
167
  *
@@ -171,7 +171,7 @@ export declare function rememberVisitCampaign(search?: string): CampaignAttribut
171
171
  * visitor who changes their mind mid-visit should not leave the thing they
172
172
  * withdrew consent for sitting on their device.
173
173
  */
174
- export declare function setCampaignForwardingConsent(allowed: boolean | null): void;
174
+ export declare function setUtmForwardingConsent(allowed: boolean | null): void;
175
175
  /**
176
176
  * The campaign to forward, first touch preferred.
177
177
  *
@@ -179,7 +179,7 @@ export declare function setCampaignForwardingConsent(allowed: boolean | null): v
179
179
  * which is the point of carrying that state at all: a broken store degrades
180
180
  * this to tier 1 rather than to silence.
181
181
  */
182
- export declare function campaignToForward(search?: string): CampaignAttribution | null;
182
+ export declare function utmToForward(search?: string): UtmAttribution | null;
183
183
  /**
184
184
  * The href a console-bound link should carry for this visitor, or null to
185
185
  * leave it exactly as authored.
@@ -194,7 +194,7 @@ export declare function campaignToForward(search?: string): CampaignAttribution
194
194
  * row describes an event that never happened. Everything else on the href —
195
195
  * `plan`, `interval`, the AGL-1535 intent — is preserved untouched.
196
196
  */
197
- export declare function decorateCampaignHref(href: string, baseHref: string, consoleOrigin: string, campaign: CampaignAttribution | null): string | null;
197
+ export declare function decorateUtmHref(href: string, baseHref: string, consoleOrigin: string, campaign: UtmAttribution | null): string | null;
198
198
  /**
199
199
  * Start forwarding the campaign onto console-bound links on this page.
200
200
  *
@@ -213,8 +213,8 @@ export declare function decorateCampaignHref(href: string, baseHref: string, con
213
213
  * Returns an uninstall function for tests and for a caller that owns the page
214
214
  * lifecycle.
215
215
  */
216
- export declare function installCampaignForwarding(options: {
216
+ export declare function installUtmForwarding(options: {
217
217
  consoleOrigin: string;
218
218
  }): () => void;
219
219
  /** Test seam — removes both listeners, forgets them, and forgets consent. */
220
- export declare function resetCampaignForwarding(): void;
220
+ export declare function resetUtmForwarding(): void;
@@ -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":"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"}
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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"}