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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/package.json +14 -14
  2. package/src/lib/app-utils/actions.d.ts +25 -374
  3. package/src/lib/app-utils/actions.js +101 -561
  4. package/src/lib/app-utils/actions.js.map +1 -1
  5. package/src/lib/app-utils/activity-presenter.d.ts +3 -3
  6. package/src/lib/app-utils/activity-presenter.js +36 -52
  7. package/src/lib/app-utils/activity-presenter.js.map +1 -1
  8. package/src/lib/app-utils/admin-audit-index.js +2 -2
  9. package/src/lib/app-utils/admin-audit-index.js.map +1 -1
  10. package/src/lib/app-utils/analytics-events.d.ts +11 -2
  11. package/src/lib/app-utils/analytics-events.js +11 -2
  12. package/src/lib/app-utils/analytics-events.js.map +1 -1
  13. package/src/lib/app-utils/analytics-provider.d.ts +182 -0
  14. package/src/lib/app-utils/analytics-provider.js +133 -0
  15. package/src/lib/app-utils/analytics-provider.js.map +1 -0
  16. package/src/lib/app-utils/api-plugins.d.ts +46 -0
  17. package/src/lib/app-utils/api-plugins.js +19 -0
  18. package/src/lib/app-utils/api-plugins.js.map +1 -1
  19. package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
  20. package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
  21. package/src/lib/app-utils/artifact-overrides.js.map +1 -0
  22. package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
  23. package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
  24. package/src/lib/app-utils/artifact-provenance.js.map +1 -0
  25. package/src/lib/app-utils/assist-credits.js +2 -1
  26. package/src/lib/app-utils/assist-credits.js.map +1 -1
  27. package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
  28. package/src/lib/app-utils/binding-token-catalog.js +17 -41
  29. package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
  30. package/src/lib/app-utils/binding-tokens.js +1 -1
  31. package/src/lib/app-utils/binding-tokens.js.map +1 -1
  32. package/src/lib/app-utils/brand-support-line.d.ts +36 -0
  33. package/src/lib/app-utils/brand-support-line.js +38 -0
  34. package/src/lib/app-utils/brand-support-line.js.map +1 -0
  35. package/src/lib/app-utils/campaign-touch.d.ts +5 -5
  36. package/src/lib/app-utils/campaign-touch.js +8 -8
  37. package/src/lib/app-utils/campaign-touch.js.map +1 -1
  38. package/src/lib/app-utils/collection-entries.d.ts +2 -2
  39. package/src/lib/app-utils/collection-entries.js.map +1 -1
  40. package/src/lib/app-utils/consent-group-change.d.ts +4 -0
  41. package/src/lib/app-utils/consent-group-change.js.map +1 -1
  42. package/src/lib/app-utils/console-record-links.d.ts +10 -89
  43. package/src/lib/app-utils/console-record-links.js +3 -107
  44. package/src/lib/app-utils/console-record-links.js.map +1 -1
  45. package/src/lib/app-utils/console-routes.d.ts +16 -7
  46. package/src/lib/app-utils/console-routes.js +9 -3
  47. package/src/lib/app-utils/console-routes.js.map +1 -1
  48. package/src/lib/app-utils/contacts.d.ts +23 -1
  49. package/src/lib/app-utils/contacts.js +21 -5
  50. package/src/lib/app-utils/contacts.js.map +1 -1
  51. package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
  52. package/src/lib/app-utils/cross-pool-desync.js +47 -0
  53. package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
  54. package/src/lib/app-utils/csv-upload.d.ts +29 -0
  55. package/src/lib/app-utils/csv-upload.js +29 -0
  56. package/src/lib/app-utils/csv-upload.js.map +1 -0
  57. package/src/lib/app-utils/dataset-models.d.ts +5 -5
  58. package/src/lib/app-utils/dataset-models.js +19 -2
  59. package/src/lib/app-utils/dataset-models.js.map +1 -1
  60. package/src/lib/app-utils/datasets.d.ts +65 -11
  61. package/src/lib/app-utils/datasets.js +74 -11
  62. package/src/lib/app-utils/datasets.js.map +1 -1
  63. package/src/lib/app-utils/docs-deflection.d.ts +6 -6
  64. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  65. package/src/lib/app-utils/docs-help.generated.d.ts +12 -6
  66. package/src/lib/app-utils/docs-help.generated.js +21 -6
  67. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  68. package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
  69. package/src/lib/app-utils/docs-index.generated.js +514 -318
  70. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  71. package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
  72. package/src/lib/app-utils/docs-retrieval.js +14 -10
  73. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  74. package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
  75. package/src/lib/app-utils/duplicate-resource.js +7 -4
  76. package/src/lib/app-utils/duplicate-resource.js.map +1 -1
  77. package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
  78. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  79. package/src/lib/app-utils/element-ui.d.ts +1 -1
  80. package/src/lib/app-utils/element-ui.js +1 -1
  81. package/src/lib/app-utils/element-ui.js.map +1 -1
  82. package/src/lib/app-utils/email-starter-policy.js +3 -3
  83. package/src/lib/app-utils/email-starter-policy.js.map +1 -1
  84. package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
  85. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  86. package/src/lib/app-utils/error-beacon.d.ts +75 -6
  87. package/src/lib/app-utils/error-beacon.js +160 -21
  88. package/src/lib/app-utils/error-beacon.js.map +1 -1
  89. package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
  90. package/src/lib/app-utils/expand-repeatables.js +10 -10
  91. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  92. package/src/lib/app-utils/host-events.d.ts +82 -0
  93. package/src/lib/app-utils/host-events.js +62 -0
  94. package/src/lib/app-utils/host-events.js.map +1 -0
  95. package/src/lib/app-utils/lockdown.d.ts +33 -1
  96. package/src/lib/app-utils/lockdown.js +45 -4
  97. package/src/lib/app-utils/lockdown.js.map +1 -1
  98. package/src/lib/app-utils/margin-utilization.d.ts +4 -69
  99. package/src/lib/app-utils/margin-utilization.js +82 -69
  100. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  101. package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
  102. package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
  103. package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
  104. package/src/lib/app-utils/node-interactions.d.ts +7 -7
  105. package/src/lib/app-utils/node-interactions.js.map +1 -1
  106. package/src/lib/app-utils/notifications.d.ts +78 -47
  107. package/src/lib/app-utils/notifications.js +63 -44
  108. package/src/lib/app-utils/notifications.js.map +1 -1
  109. package/src/lib/app-utils/payments-configured.d.ts +8 -21
  110. package/src/lib/app-utils/payments-configured.js +8 -23
  111. package/src/lib/app-utils/payments-configured.js.map +1 -1
  112. package/src/lib/app-utils/plan-entitlements.d.ts +43 -128
  113. package/src/lib/app-utils/plan-entitlements.js +145 -304
  114. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  115. package/src/lib/app-utils/platform-brand.d.ts +10 -0
  116. package/src/lib/app-utils/platform-brand.js +14 -3
  117. package/src/lib/app-utils/platform-brand.js.map +1 -1
  118. package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
  119. package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
  120. package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
  121. package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
  122. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  123. package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
  124. package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
  125. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
  126. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
  127. package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
  128. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
  129. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  130. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  131. package/src/lib/app-utils/release-flags.d.ts +14 -5
  132. package/src/lib/app-utils/release-flags.js +21 -125
  133. package/src/lib/app-utils/release-flags.js.map +1 -1
  134. package/src/lib/app-utils/repeat-sources.d.ts +9 -0
  135. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  136. package/src/lib/app-utils/retained-refusals.d.ts +73 -0
  137. package/src/lib/app-utils/retained-refusals.js +186 -0
  138. package/src/lib/app-utils/retained-refusals.js.map +1 -0
  139. package/src/lib/app-utils/scope-tokens.d.ts +27 -0
  140. package/src/lib/app-utils/scope-tokens.js +61 -0
  141. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  142. package/src/lib/app-utils/screen-link-context.js +3 -3
  143. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  144. package/src/lib/app-utils/screen-route.d.ts +15 -0
  145. package/src/lib/app-utils/screen-route.js +21 -1
  146. package/src/lib/app-utils/screen-route.js.map +1 -1
  147. package/src/lib/app-utils/seo-audit.d.ts +132 -0
  148. package/src/lib/app-utils/seo-audit.js +246 -0
  149. package/src/lib/app-utils/seo-audit.js.map +1 -0
  150. package/src/lib/app-utils/seo-keywords.d.ts +48 -0
  151. package/src/lib/app-utils/seo-keywords.js +60 -0
  152. package/src/lib/app-utils/seo-keywords.js.map +1 -0
  153. package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
  154. package/src/lib/app-utils/seo-page-facts.js +155 -0
  155. package/src/lib/app-utils/seo-page-facts.js.map +1 -0
  156. package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
  157. package/src/lib/app-utils/seo-site-scan.js +162 -0
  158. package/src/lib/app-utils/seo-site-scan.js.map +1 -0
  159. package/src/lib/app-utils/seo-title-variables.js +1 -1
  160. package/src/lib/app-utils/seo-title-variables.js.map +1 -1
  161. package/src/lib/app-utils/server.d.ts +6 -9
  162. package/src/lib/app-utils/server.js +17 -27
  163. package/src/lib/app-utils/server.js.map +1 -1
  164. package/src/lib/app-utils/site-interactions.d.ts +501 -0
  165. package/src/lib/app-utils/site-interactions.js +554 -0
  166. package/src/lib/app-utils/site-interactions.js.map +1 -0
  167. package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
  168. package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +5 -2
  169. package/src/lib/app-utils/site-theme.js.map +1 -0
  170. package/src/lib/app-utils/sitemap.d.ts +4 -3
  171. package/src/lib/app-utils/sitemap.js +9 -7
  172. package/src/lib/app-utils/sitemap.js.map +1 -1
  173. package/src/lib/app-utils/stale-build-error.d.ts +51 -0
  174. package/src/lib/app-utils/stale-build-error.js +100 -1
  175. package/src/lib/app-utils/stale-build-error.js.map +1 -1
  176. package/src/lib/app-utils/starter-templates.d.ts +20 -0
  177. package/src/lib/app-utils/starter-templates.js +49 -0
  178. package/src/lib/app-utils/starter-templates.js.map +1 -1
  179. package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
  180. package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
  181. package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
  182. package/src/lib/app-utils/theme-library.d.ts +10 -3
  183. package/src/lib/app-utils/theme-library.js +13 -4
  184. package/src/lib/app-utils/theme-library.js.map +1 -1
  185. package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
  186. package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
  187. package/src/lib/app-utils/utm-attribution.js.map +1 -0
  188. package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
  189. package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
  190. package/src/lib/app-utils/utm-forwarding.js.map +1 -0
  191. package/src/lib/app-utils/video-object.js +6 -5
  192. package/src/lib/app-utils/video-object.js.map +1 -1
  193. package/src/lib/app-utils/visitor-consent.d.ts +41 -164
  194. package/src/lib/app-utils/visitor-consent.js +66 -277
  195. package/src/lib/app-utils/visitor-consent.js.map +1 -1
  196. package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
  197. package/src/lib/app-utils/web-vitals-rum.js +31 -63
  198. package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
  199. package/src/lib/app-utils/where-used.d.ts +11 -6
  200. package/src/lib/app-utils/where-used.js +3 -8
  201. package/src/lib/app-utils/where-used.js.map +1 -1
  202. package/src/lib/canvas-manager/canvas-manager.js +3 -3
  203. package/src/lib/canvas-manager/canvas-manager.js.map +1 -1
  204. package/src/lib/foundation/definitions/components.types.d.ts +1 -1
  205. package/src/lib/foundation/definitions/components.types.js +1 -1
  206. package/src/lib/foundation/definitions/components.types.js.map +1 -1
  207. package/src/lib/foundation/definitions/org-billing.types.d.ts +18 -135
  208. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  209. package/src/lib/foundation/definitions/organization.types.d.ts +6 -3
  210. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  211. package/src/lib/foundation/definitions/platform.types.d.ts +27 -5
  212. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  213. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
  214. package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
  215. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  216. package/src/lib/foundation/index.d.ts +1 -1
  217. package/src/lib/foundation/index.js.map +1 -1
  218. package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
  219. package/src/lib/plugin-manager/besigner-documents.js +61 -0
  220. package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
  221. package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
  222. package/src/lib/plugin-manager/computed-variables.js +74 -0
  223. package/src/lib/plugin-manager/computed-variables.js.map +1 -0
  224. package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
  225. package/src/lib/plugin-manager/custom-fields.js +47 -1
  226. package/src/lib/plugin-manager/custom-fields.js.map +1 -1
  227. package/src/lib/plugin-manager/feature-plugins.d.ts +292 -20
  228. package/src/lib/plugin-manager/feature-plugins.js +105 -17
  229. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  230. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
  231. package/src/lib/plugin-manager/first-party-plugins.generated.js +785 -14
  232. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  233. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
  234. package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
  235. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
  236. package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
  237. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  238. package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
  239. package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
  240. package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
  241. package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
  242. package/src/lib/plugin-manager/plugin-dependents.js +75 -0
  243. package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
  244. package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
  245. package/src/lib/plugin-manager/plugin-distribution.js +45 -0
  246. package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
  247. package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
  248. package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
  249. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
  251. package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
  252. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
  254. package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
  255. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  256. package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
  257. package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
  258. package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
  259. package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
  260. package/src/lib/plugin-manager/plugin-record-index.js +51 -0
  261. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
  263. package/src/lib/plugin-manager/plugin-record-written.js +42 -0
  264. package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
  265. package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
  266. package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
  267. package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
  269. package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
  270. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
  271. package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
  272. package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
  273. package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
  274. package/src/lib/plugin-manager/plugin-site-export.js +40 -0
  275. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
  276. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
  277. package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
  278. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
  279. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
  280. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  281. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
  282. package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
  283. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
  284. package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
  285. package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
  286. package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
  287. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +107 -0
  288. package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
  289. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
  290. package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
  291. package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
  292. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  293. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  294. package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
  295. package/src/lib/plugin-manager/record-captured-contact.js +10 -32
  296. package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
  297. package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
  298. package/src/lib/plugin-manager/repeat-rows.js +108 -0
  299. package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
  300. package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
  301. package/src/lib/plugin-manager/submission-record-target.js +125 -0
  302. package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
  303. package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
  304. package/src/lib/plugin-manager/video-embed-provider.js +165 -0
  305. package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
  306. package/src/lib/types/nodes.d.ts +2 -2
  307. package/src/lib/types/nodes.js.map +1 -1
  308. package/src/lib/app-utils/campaign-attribution.js.map +0 -1
  309. package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
  310. package/src/lib/app-utils/contact-holder.d.ts +0 -68
  311. package/src/lib/app-utils/contact-holder.js +0 -129
  312. package/src/lib/app-utils/contact-holder.js.map +0 -1
  313. package/src/lib/app-utils/contact-merge.d.ts +0 -136
  314. package/src/lib/app-utils/contact-merge.js +0 -412
  315. package/src/lib/app-utils/contact-merge.js.map +0 -1
  316. package/src/lib/app-utils/crm-csv.d.ts +0 -239
  317. package/src/lib/app-utils/crm-csv.js +0 -467
  318. package/src/lib/app-utils/crm-csv.js.map +0 -1
  319. package/src/lib/app-utils/crm-digest.d.ts +0 -185
  320. package/src/lib/app-utils/crm-digest.js +0 -272
  321. package/src/lib/app-utils/crm-digest.js.map +0 -1
  322. package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
  323. package/src/lib/app-utils/form-dataset-binding.js +0 -74
  324. package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
  325. package/src/lib/app-utils/list-import.d.ts +0 -158
  326. package/src/lib/app-utils/list-import.js +0 -332
  327. package/src/lib/app-utils/list-import.js.map +0 -1
  328. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
  329. package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
  330. package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
  331. package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
  332. package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
  333. package/src/lib/app-utils/marketplace-theme.js.map +0 -1
  334. package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
  335. package/src/lib/app-utils/marketplace-update-state.js +0 -132
  336. package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
  337. package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
  338. package/src/lib/app-utils/publisher-agreement.js +0 -321
  339. package/src/lib/app-utils/publisher-agreement.js.map +0 -1
  340. package/src/lib/app-utils/wistia-embed.d.ts +0 -104
  341. package/src/lib/app-utils/wistia-embed.js +0 -138
  342. package/src/lib/app-utils/wistia-embed.js.map +0 -1
  343. package/src/lib/app-utils/workflows.d.ts +0 -136
  344. package/src/lib/app-utils/workflows.js +0 -346
  345. package/src/lib/app-utils/workflows.js.map +0 -1
  346. package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
  347. package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
  348. package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/types/nodes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ConditionDefinition,\n DataType,\n Validator,\n} from '@data-driven-forms/react-form-renderer'\nimport type {\n FieldActions,\n ResolvePropsFunction,\n} from '@data-driven-forms/react-form-renderer/common-types'\nimport type { MuiStyledOptions } from '@mui/system/createStyled'\nimport type { SvgIconProps } from '@mui/material/SvgIcon'\n/** Minimal icon-props type used in component/preset schemas. Structurally compatible with @aglyn/shared-ui-jsx MdiIconProps. */\nexport type MdiIconProps<D extends React.ElementType = 'svg', P = object> = SvgIconProps<D, P> & {\n path?: string\n}\nimport type {\n ComponentId,\n ComponentsLinealOrder,\n NodeId,\n PresetId,\n} from '../foundation'\nimport { ComponentCategory } from '../foundation/constants/components'\nimport type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type React from 'react'\nimport type { ComponentClass, ComponentProps } from 'react'\nimport type { NODE_ROOT_ID } from '../canvas-manager'\nimport type { NodeInteraction } from '../app-utils/node-interactions'\n\nimport type {\n FEATURE_FLAG,\n FieldComponentType,\n RICH_TEXT_COMMANDS,\n} from '../foundation'\nimport type { PluginId } from '../plugin-manager'\nimport type { ElementContentMap, Node, Props, Taxonomic } from './ast'\nimport type { AglynDocument } from '../foundation'\nimport type { HostUid } from '../foundation'\n\ndeclare module './ast' {\n /**\n * This map registers all node types that may be used as top-level content in\n * the document.\n *\n * These types are accepted inside `root` nodes.\n *\n * This interface can be augmented to register custom node types.\n *\n * @example\n * declare module 'hast' {\n * interface RootContentMap {\n * // Allow using raw nodes defined by `rehype-raw`.\n * raw: Raw;\n * }\n * }\n */\n export interface RootContentMap {\n canvasNode: CanvasNode\n canvasElement: CanvasElement\n }\n\n /**\n * This map registers all node types that may be used as content in an\n * element.\n *\n * These types are accepted inside `element` nodes.\n *\n * This interface can be augmented to register custom node types.\n *\n * @example\n * declare module 'hast' {\n * interface RootContentMap {\n * custom: Custom;\n * }\n * }\n */\n export interface ElementContentMap {\n canvasNode: CanvasNode\n canvasElement: CanvasElement\n }\n}\n\n/**\n * This map registers all node types that may be used as content in an canvas\n * node.\n *\n * These types are accepted inside `element` nodes.\n *\n * This interface can be augmented to register custom node types.\n *\n * @example\n * declare module 'hast' {\n * interface RootContentMap {\n * custom: Custom;\n * }\n * }\n */\nexport interface CanvasNodeContentMap extends ElementContentMap {\n canvasNode: CanvasNode\n canvasElement: CanvasElement\n}\n\nexport type CanvasNodeContent = CanvasNodeContentMap[keyof CanvasNodeContentMap]\n\n/**\n * CanvasNode represents a the schema for building a JSX Element.\n */\nexport interface CanvasNode extends Node {\n /**\n * Represents this variant of a Node.\n */\n type: 'node'\n\n /**\n * List representing the children of a node.\n */\n children?: CanvasNodeContent[] | undefined\n\n /**\n * Represents the element's internal name defined by the user\n */\n name?: string | undefined\n\n /**\n * Represents the element's parent unique identifier\n */\n parentId?: string | undefined\n\n /**\n * Represents the elements' component unique identifier\n */\n componentId?: string | undefined\n\n /**\n * Represents the elements' components' plugin unique identifier\n */\n pluginId?: string | undefined\n\n /**\n * Represents information associated with the element.\n */\n props?: Props | undefined\n}\n\n/**\n * CanvasElement represents a living JSX Element\n */\nexport interface CanvasElement extends CanvasNode {\n /**\n * Represents the element's unique identifier\n */\n $id: string\n}\n\nexport type AbstractId = string\n\n// @TODO ⚠️ Refactor for better adoption of hast\nexport interface AbstractNodeSchema<P = JSX.AnyProps> extends CanvasNode {\n /**\n * The unique identifier for a node\n */\n $id: AbstractId\n title?: string\n displayName?: string\n description?: string\n\n children?: any\n nodes?: any\n\n /**\n * The node style properties for emotion\n */\n sx?: JSX.SxProps\n /**\n * The node props/attributes passed to the component\n */\n props?: P\n\n category?: any\n icon?: any\n\n createdAt?: ITimestamp\n updatedAt?: ITimestamp\n deletedAt?: ITimestamp\n}\n\n/**\n * CanvasPreset represents an encapsulated 'node' type as the root node for\n * crafting a living JSX Element\n */\nexport interface CanvasPreset extends Node, Taxonomic {\n /**\n * Represents this variant of a Node.\n */\n type: 'preset'\n\n /**\n * Represents the element's unique identifier\n */\n $id: string\n\n /**\n * Represents the elements' component unique identifier\n */\n componentId?: string | undefined\n\n /**\n * Represents the elements' components' plugin unique identifier\n */\n pluginId?: string | undefined\n\n /**\n * Represents information associated with the element.\n */\n props?: Props | undefined\n\n /**\n * Represents the element's internal name defined by the user\n */\n name?: string | undefined\n}\n\nexport type { NodeId }\n\n// @TODO ⚠️ Refactor for better adoption of hast\ntype PresetI = AglynDocument &\n Omit<AbstractNodeSchema<any>, 'type' | 'data'> &\n Omit<CanvasPreset, 'type' | 'data'>\n\ninterface PresetII extends PresetI {\n type: NodeType.PRESET | string\n}\n\nexport interface PresetSchema<P = JSX.AnyProps> extends PresetII {\n $id: PresetId\n pluginId?: PluginId\n displayName?: string\n description?: string\n category?: string | ComponentCategory\n /**\n * Extra words this element should be findable by in the element picker —\n * synonyms and the name people reach for when it is not the name we chose\n * (\"picture\" for Image, \"dropdown\" for Select).\n *\n * Search terms only; never rendered. A hit here ranks BELOW every hit on a\n * name, so a tag widens what the picker finds without letting a synonym\n * outrank the element actually called that (AGL-2486).\n */\n tags?: string[]\n /** Alias of {@link tags}; both are searched. */\n keywords?: string[]\n icon?: MdiIconProps\n data: NodeSchemaNested<P>\n /**\n * Interaction templates wired when the preset is inserted (AGL-589):\n * the host app resolves each `presetRef` marker to a freshly minted node\n * and persists the result. Presets that need hover choreography (a\n * primitive dropdown panel) ship working out of the box instead of asking\n * the user to author the interactions by hand.\n *\n * TEMPLATES, and named so. These are not interactions: they are\n * unresolved, they reference nodes that do not exist yet, and they are\n * consumed at insert time and never stored. What they resolve INTO lives\n * on the node as {@link NodeSchema.interactions}, and a `PresetSchema` is\n * assignable to a `NodeSchema` — so while the two shared a name, one of\n * them had to be wrong wherever a preset was passed as a node.\n */\n interactionTemplates?: PresetInteractionSchema[]\n}\n\n/**\n * A visibility step inside a {@link PresetSchema} interaction template.\n * Only the basic client visibility steps are expressible — presets\n * scaffold in-page choreography, never server automations.\n */\nexport interface PresetInteractionStepSchema {\n type: 'showElement' | 'hideElement' | 'toggleElement'\n /** `presetRef` of the preset node this step targets. */\n targetRef: string\n /** Optional grace delay, validated to 0–5000ms like the builder. */\n delayMs?: number\n /** Self-dismissal for steps that can reveal the target. */\n dismissOn?: Array<'escape' | 'outsideClick'>\n}\n\n/** An interaction template a preset wires on insert (AGL-589). */\nexport interface PresetInteractionSchema {\n name: string\n event: 'elementClick' | 'elementHoverEnter' | 'elementHoverLeave'\n /** `presetRef` of the preset node whose selector fires the trigger. */\n triggerRef: string\n steps: PresetInteractionStepSchema[]\n}\n\n// @TODO ⚠️ Refactor for better adoption of hast\ntype NodeI<P> = AglynDocument & Omit<AbstractNodeSchema<P>, 'type'>\n\nexport interface NodeSchema<P = JSX.AnyProps> extends NodeI<P> {\n type: NodeType | string\n /**\n * List of the children unique identifiers for the node\n */\n nodes?: NodeId[]\n /**\n * Class name to pass the DOM node, can also be defined in props\n */\n className?: string\n /**\n * Preset-authoring marker (AGL-589): names a node inside a preset's\n * `data` tree so the preset's interaction templates can reference it\n * (`triggerRef` / `targetRef`). Cloned onto the inserted node; inert\n * everywhere else.\n */\n presetRef?: string\n /**\n * Per-instance style overrides on a reusable-component instance node\n * (AGL-1306), keyed by target: `root` (see `STYLE_OVERRIDES_ROOT_KEY`)\n * addresses the component ROOT, and any other key is a DEFINITION-internal\n * node id addressing one leaf inside the component (AGL-1332). Each slice\n * is an sx-shaped record merged over that node's own sx at graft time\n * (`composeReusableComponentNodes` → `mergeNodeSx`), so one page's CTA can\n * switch to a white background AND repaint its headline without widening\n * the component's declared props or detaching.\n *\n * Keyed on the definition's id, never the grafted `cmp__{instance}__{def}`\n * id: the graft id is derived per placement, so it would not survive a\n * duplicate or a re-key, and the same component could not offer two\n * instances the same authoring vocabulary. A key naming a node the\n * definition no longer has is ignored — a component edited later degrades\n * to its own styling instead of taking a page down.\n *\n * A first-class node field, NOT a prop: `AglynNode`'s constructor drops\n * unknown top-level keys, and anything in `props` is spread onto the\n * instance's wrapper DOM element. Lives on the instance node in the\n * document that places it — it survives component republish and never\n * pins the instance to an old component version, because the graft\n * always merges it over the definition's CURRENT nodes.\n */\n styleOverrides?: Record<string, JSX.SxProps>\n /**\n * Per-instance ATTRIBUTE overrides on a reusable-component instance node\n * (AGL-1899) — the same addressing as {@link styleOverrides}, applied to\n * a node's props instead of its sx, so one placement can carry a\n * different button `variant`, `size` or `href` without the component\n * declaring a prop for it and without detaching.\n *\n * Merged **per named prop**: a slice replaces only the props it names and\n * leaves the definition's others alone, so a component that later adds a\n * prop still gives every existing instance the new default. That is\n * deliberately shallower than the sx merge next door — an sx slice\n * cascades because `@scheme dark` and breakpoint objects are themselves\n * selectors, whereas a prop value is one value, and half-merging two\n * objects that merely look alike (say a `slotProps`) would produce a\n * third thing neither side asked for.\n *\n * `sx` is refused here. Styling an instance already has a mechanism one\n * field up, and honouring both would leave two writers racing for one\n * rendered value with no rule to say which wins.\n *\n * Applied BEFORE `{{prop.*}}` substitution, so an override may itself be\n * written in tokens and still resolves, and an override on a prop the\n * definition binds to a declared prop wins — the more specific placement\n * beats the component's own default.\n */\n attrOverrides?: Record<string, Record<string, unknown>>\n /**\n * EDITOR-ONLY: a screen's per-page restyling of its layout's elements\n * (AGL-3286), parked on the screen canvas ROOT while the besigner is open.\n *\n * Stored on the screen VERSION as `layoutStyleOverrides`, never in `nodes`:\n * the editor injects it here on load and lifts it back out on save\n * (`injectLayoutStyleOverrides` / `extractLayoutStyleOverrides`). Carried on\n * the root so an edit is an undo step, marks the screen dirty and rides the\n * working draft like any other change. Composition ignores it — the screen\n * root is dropped when it is grafted into the layout's slot.\n */\n layoutStyleOverrides?: Record<string, Record<string, Record<string, unknown>>>\n /**\n * Interactions this element carries — the click, hover and\n * scroll-into-view choreography authored on it in the besigner.\n *\n * ## Why they live on the NODE\n *\n * They used to be rows in `hosts/{hostId}/actions`, bound to an element by\n * a `[data-aglyn=\"leaf:<id>\"]` selector — the same collection the Workflows\n * page lists. An interaction is not a site-wide automation, and storing it\n * as one had four consequences, every one of them a thing an author would\n * expect to work:\n *\n * - it was not versioned with the document, so it neither published nor\n * rolled back with the screen it belongs to;\n * - it did not travel with a component copied to another site, or packaged\n * into a template or a plugin — the element arrived inert;\n * - deleting the element left the action behind, pointing at a selector\n * nothing resolves;\n * - and a nav's hover choreography sat in the site's automation list\n * beside \"when an order is placed\", which is a different kind of thing.\n *\n * Host actions remain, and remain the right home for a genuine site event.\n * The split is the concept: an ACTION is something the site does, an\n * INTERACTION is something an element does.\n *\n * ## Shape\n *\n * Deliberately the same `HostAction` the automations engine already takes,\n * so nothing about the runtime changes: the selector is stamped from this\n * node's own id when the page is composed, and the compiled result is the\n * shape the client engine has always consumed. `trigger.selector` is\n * therefore ignored here — the node IS the selector.\n */\n interactions?: NodeInteraction[]\n /**\n * Hidden by the author — the eye on the element's hierarchy row.\n *\n * Renders `display: none` on the canvas AND on the published site. It is\n * the plain \"I do not want this on the page\" switch: no runtime, no\n * interaction, nothing reveals it. Showing it again is the same eye.\n *\n * ## Why a FIELD and not a style\n *\n * Because it has to override the element's own display rather than replace\n * it. An author who toggles the eye on a Stack laid out with\n * `display: flex` must get that flex back when they toggle it off, and\n * writing `display: none` into `node.sx` destroys the value it overwrote —\n * there is nothing left to restore. Composed LAST in `Leaf` instead, so it\n * wins on the cascade and the stored style is never touched.\n *\n * ## Not {@link ELEMENT_HIDDEN_CLASS}\n *\n * `aglyn-hidden` is a different thing wearing a similar name: it means\n * \"starts hidden, and an interaction shows it\" — a mega-menu panel, a\n * drawer. That class is part of a runtime contract (the show/hide steps\n * toggle it, and the canvas reveals it for designing), and it is bespoke to\n * that job. A visibility toggle that wrote it would enrol every hidden\n * element in a choreography it is not part of.\n */\n hidden?: boolean\n /**\n * The computed node parent (only for type completion)\n */\n readonly parent?: NodeSchema<any> | null\n /**\n * The computed node parent (only for type completion)\n */\n readonly children?: NodeSchema<any>[]\n /**\n * The computed index in parent nodes (only for type completion)\n */\n readonly index?: number | null\n /**\n * The computed label (only for type completion)\n */\n readonly labelShort?: string | undefined\n /**\n * The computed breadcrumb path (only for type completion)\n */\n readonly breadcrumbPath?: NodeBreadcrumbPath\n /**\n * The computed component schema (only for type completion)\n */\n readonly componentSchema?: ComponentSchema | undefined\n /**\n * The computed guard for of child nodes (only for type completion)\n */\n readonly hasNodes?: boolean\n /**\n * The computed property for the resolved props from component schema\n */\n readonly resolvedProps?: P\n}\n\nexport enum NodeType {\n NODE = 'node',\n TEXT = 'text',\n NUMBER = 'number',\n SCREEN = 'screen',\n REF = 'ref',\n PRESET = 'preset',\n}\n\nexport type NodeSchemaJSON<P = JSX.AnyProps> = Omit<\n NodeSchema<P>,\n | 'parent'\n | 'index'\n | 'labelShort'\n | 'breadcrumbPath'\n | 'componentSchema'\n | 'hasNodes'\n>\n\nexport type NodeSchemaNested<P = JSX.AnyProps> = Omit<\n NodeSchemaJSON<P>,\n 'nodes'\n> & { nodes?: NodeSchemaNested<any>[] }\n\nexport type NodesMap = Record<NodeId, NodeSchema<any>>\n\nexport type NodeBreadcrumbPath = [\n root: string & typeof NODE_ROOT_ID,\n ...nodes: [...ancestors: NodeId[], node: NodeId],\n]\n\nexport type ProcessableNodes =\n | NodeSchemaNested<any>[]\n | NodeSchemaNested<any>\n | Record<NodeId, NodeSchema>\n\nexport { ComponentCategory }\n\nexport type { ComponentId }\nexport type { PresetId }\n\nexport type ComponentFactory<\n P extends ComponentProps<C> | any = any,\n C extends keyof JSX.IntrinsicElements | JSX.ElementConstructor<any> = any,\n> = ComponentClass<P> | JSX.ElementConstructor<P> | keyof JSX.IntrinsicElements\n// | keyof JSX.IntrinsicElements[keyof JSX.IntrinsicElements]\n\nexport type { ComponentsLinealOrder }\n\n/**\n * What the Attributes panel knows about the element a field edits, handed to\n * an attribute's `resolveProps` as its fourth argument.\n *\n * data-driven-forms gives a field its own value and the form's values, which\n * are the element's props and nothing else. A rule about the element as a\n * whole needs more than that: whether a Link Container holds anything that\n * names its link depends on the elements inside it. So the panel adds the\n * node, and a lookup for the ids listed in its `nodes`.\n *\n * Absent wherever a field is drawn outside that panel, including a unit test\n * calling the function directly, so a `resolveProps` that reads it has to\n * treat a missing context as having nothing to say.\n */\nexport interface AttributeFieldContext {\n /** The element the Attributes panel is editing. */\n node: NodeSchema<any>\n /** An element of the same document by id, such as a child in `nodes`. */\n getNode: (id: NodeId) => NodeSchema<any> | undefined\n}\n\n/**\n * An attribute's `resolveProps`: the three arguments data-driven-forms\n * passes, then the {@link AttributeFieldContext} the Attributes panel adds.\n */\nexport type AttributeResolveProps = (\n ...args: [\n ...Parameters<ResolvePropsFunction>,\n context?: AttributeFieldContext,\n ]\n) => ReturnType<ResolvePropsFunction>\n\n// @TODO ⚠️ Refactor for better adoption of hast\nexport interface AttributeSchema extends Dictionary<any> {\n name: string\n dataType?: DataType\n component: string | FieldComponentType\n validate?: Validator[]\n condition?: ConditionDefinition | ConditionDefinition[]\n initializeOnMount?: boolean\n initialValue?: any\n clearedValue?: any\n clearOnUnmount?: boolean\n actions?: FieldActions\n resolveProps?: AttributeResolveProps\n description?: string\n}\n\n// @TODO ⚠️ Refactor for better adoption of hast\nexport interface ComponentSchema<P = any> {\n $id?: ComponentId\n pluginId?: PluginId\n kind?: 'element' | 'plaintext' | 'markdown'\n\n displayName: string\n title?: string\n subtitle?: string\n description?: string\n category?: string | ComponentCategory\n\n /**\n * Extra words this element should be findable by in the element picker —\n * synonyms and the name people reach for when it is not the name we chose\n * (\"picture\" for Image, \"dropdown\" for Select).\n *\n * Search terms only; never rendered. A hit here ranks BELOW every hit on a\n * name, so a tag widens what the picker finds without letting a synonym\n * outrank the element actually called that (AGL-2486).\n */\n tags?: string[]\n /** Alias of {@link tags}; both are searched. */\n keywords?: string[]\n\n /**\n * Icon props for display around besigner\n */\n icon?: MdiIconProps\n /**\n * Options to be passed to styled(Component, \\{...styledOptions\\})\n */\n styledOptions?: MuiStyledOptions\n\n /**\n * Define a limitation for nodes allowed as direct descendents\n */\n restrictChildren?: ComponentsLinealOrder\n /**\n * Define a limitation for nodes allowed to be direct ancestors\n */\n restrictParent?: ComponentsLinealOrder\n\n /**\n * Filter props\n */\n resolveProps?: JSX.ResolveProps<NodeSchema<P>>\n\n /**\n * Attribute fields to modify the contextual properties\n * New version\n */\n attributes?: AttributeSchema[]\n\n /**\n * Which groups of formatting the inline rich-text toolbar offers for this\n * component (AGL-2557). Read only where `flags.richTextEditable` is on.\n *\n * Omitted means every group, which is what leaves Typography exactly as it\n * was. Naming a subset narrows the toolbar AND the commit: a surface that\n * offers neither lists nor links is phrasing-only, and the sanitizer holds\n * it to that whatever an author pastes in.\n */\n richTextCommands?: RICH_TEXT_COMMANDS[]\n\n /**\n * Feature flags\n */\n flags?: {\n /**\n * Disable the use of emotion styled\n */\n emotion?: FEATURE_FLAG\n /**\n * Can the nodes of this component type be cloned?\n */\n cloning?: FEATURE_FLAG\n /**\n * Allow dragging nodes of this component type\n */\n dragging?: FEATURE_FLAG\n /**\n * Allow dropping nodes inside nodes of this component type\n */\n dropping?: FEATURE_FLAG\n /**\n * Allow editing element attributes of this component type\n */\n editing?: FEATURE_FLAG\n /**\n * Allow removing nodes of this component type\n */\n removing?: FEATURE_FLAG\n /**\n * Describe nodes of this component type to be self-closing\n */\n selfClosing?: FEATURE_FLAG\n /**\n * Component renders its `children` prop as text content the editor may\n * edit directly (Attributes \"Text\" field, inline canvas editing).\n */\n textEditable?: FEATURE_FLAG\n /**\n * Component also accepts basic rich text (AGL-54): the inline editor\n * upgrades to a formatting surface and commits sanitized HTML into the\n * `html` prop (with `children` kept as the plain-text fallback).\n */\n richTextEditable?: FEATURE_FLAG\n /**\n * Component reads its children POSITIONALLY (AGL-1237), so the renderer\n * hands it one React child per node child instead of the single `<Branch>`\n * element it normally passes.\n *\n * MUI's Accordion does `const [summary, ...rest] = Children.toArray(children)`,\n * and `toArray` does not traverse into an element — so with the default\n * wrapping the summary swallowed the whole subtree and the Collapse got\n * nothing. Every accordion on every published site expanded to reveal an\n * empty panel while its content rendered unconditionally inside the\n * summary's heading.\n *\n * Opt in only for components with this contract; the wrapper is what keeps\n * the Branch/Stem/Leaf seam swappable for everything else.\n */\n positionalChildren?: FEATURE_FLAG\n }\n}\n\nexport type SchemasByCategory = Record<\n ComponentCategory | string,\n (ComponentSchema<any> | PresetSchema<any>)[]\n>\n\nexport interface ScreenSchema {\n nodes: NodeSchemaNested\n createdAt?: ITimestamp\n updatedAt?: ITimestamp\n hostId?: HostUid\n}\n"],"names":["ComponentCategory","NodeType"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAuBD,SAASA,iBAAiB,QAAQ,wCAAoC;AAictE,OAAO,IAAA,AAAKC,kCAAAA;;;;;;;WAAAA;MAOX;AA6BD,SAASD,iBAAiB,GAAE"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/types/nodes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ConditionDefinition,\n DataType,\n Validator,\n} from '@data-driven-forms/react-form-renderer'\nimport type {\n FieldActions,\n ResolvePropsFunction,\n} from '@data-driven-forms/react-form-renderer/common-types'\nimport type { MuiStyledOptions } from '@mui/system/createStyled'\nimport type { SvgIconProps } from '@mui/material/SvgIcon'\n/** Minimal icon-props type used in component/preset schemas. Structurally compatible with @aglyn/shared-ui-jsx MdiIconProps. */\nexport type MdiIconProps<D extends React.ElementType = 'svg', P = object> = SvgIconProps<D, P> & {\n path?: string\n}\nimport type {\n ComponentId,\n ComponentsLinealOrder,\n NodeId,\n PresetId,\n} from '../foundation'\nimport { ComponentCategory } from '../foundation/constants/components'\nimport type { ITimestamp } from '@aglyn/shared-util-timestamp'\nimport type React from 'react'\nimport type { ComponentClass, ComponentProps } from 'react'\nimport type { NODE_ROOT_ID } from '../canvas-manager'\nimport type { NodeInteraction } from '../app-utils/node-interactions'\n\nimport type {\n FEATURE_FLAG,\n FieldComponentType,\n RICH_TEXT_COMMANDS,\n} from '../foundation'\nimport type { PluginId } from '../plugin-manager'\nimport type { ElementContentMap, Node, Props, Taxonomic } from './ast'\nimport type { AglynDocument } from '../foundation'\nimport type { HostUid } from '../foundation'\n\ndeclare module './ast' {\n /**\n * This map registers all node types that may be used as top-level content in\n * the document.\n *\n * These types are accepted inside `root` nodes.\n *\n * This interface can be augmented to register custom node types.\n *\n * @example\n * declare module 'hast' {\n * interface RootContentMap {\n * // Allow using raw nodes defined by `rehype-raw`.\n * raw: Raw;\n * }\n * }\n */\n export interface RootContentMap {\n canvasNode: CanvasNode\n canvasElement: CanvasElement\n }\n\n /**\n * This map registers all node types that may be used as content in an\n * element.\n *\n * These types are accepted inside `element` nodes.\n *\n * This interface can be augmented to register custom node types.\n *\n * @example\n * declare module 'hast' {\n * interface RootContentMap {\n * custom: Custom;\n * }\n * }\n */\n export interface ElementContentMap {\n canvasNode: CanvasNode\n canvasElement: CanvasElement\n }\n}\n\n/**\n * This map registers all node types that may be used as content in an canvas\n * node.\n *\n * These types are accepted inside `element` nodes.\n *\n * This interface can be augmented to register custom node types.\n *\n * @example\n * declare module 'hast' {\n * interface RootContentMap {\n * custom: Custom;\n * }\n * }\n */\nexport interface CanvasNodeContentMap extends ElementContentMap {\n canvasNode: CanvasNode\n canvasElement: CanvasElement\n}\n\nexport type CanvasNodeContent = CanvasNodeContentMap[keyof CanvasNodeContentMap]\n\n/**\n * CanvasNode represents a the schema for building a JSX Element.\n */\nexport interface CanvasNode extends Node {\n /**\n * Represents this variant of a Node.\n */\n type: 'node'\n\n /**\n * List representing the children of a node.\n */\n children?: CanvasNodeContent[] | undefined\n\n /**\n * Represents the element's internal name defined by the user\n */\n name?: string | undefined\n\n /**\n * Represents the element's parent unique identifier\n */\n parentId?: string | undefined\n\n /**\n * Represents the elements' component unique identifier\n */\n componentId?: string | undefined\n\n /**\n * Represents the elements' components' plugin unique identifier\n */\n pluginId?: string | undefined\n\n /**\n * Represents information associated with the element.\n */\n props?: Props | undefined\n}\n\n/**\n * CanvasElement represents a living JSX Element\n */\nexport interface CanvasElement extends CanvasNode {\n /**\n * Represents the element's unique identifier\n */\n $id: string\n}\n\nexport type AbstractId = string\n\n// @TODO ⚠️ Refactor for better adoption of hast\nexport interface AbstractNodeSchema<P = JSX.AnyProps> extends CanvasNode {\n /**\n * The unique identifier for a node\n */\n $id: AbstractId\n title?: string\n displayName?: string\n description?: string\n\n children?: any\n nodes?: any\n\n /**\n * The node style properties for emotion\n */\n sx?: JSX.SxProps\n /**\n * The node props/attributes passed to the component\n */\n props?: P\n\n category?: any\n icon?: any\n\n createdAt?: ITimestamp\n updatedAt?: ITimestamp\n deletedAt?: ITimestamp\n}\n\n/**\n * CanvasPreset represents an encapsulated 'node' type as the root node for\n * crafting a living JSX Element\n */\nexport interface CanvasPreset extends Node, Taxonomic {\n /**\n * Represents this variant of a Node.\n */\n type: 'preset'\n\n /**\n * Represents the element's unique identifier\n */\n $id: string\n\n /**\n * Represents the elements' component unique identifier\n */\n componentId?: string | undefined\n\n /**\n * Represents the elements' components' plugin unique identifier\n */\n pluginId?: string | undefined\n\n /**\n * Represents information associated with the element.\n */\n props?: Props | undefined\n\n /**\n * Represents the element's internal name defined by the user\n */\n name?: string | undefined\n}\n\nexport type { NodeId }\n\n// @TODO ⚠️ Refactor for better adoption of hast\ntype PresetI = AglynDocument &\n Omit<AbstractNodeSchema<any>, 'type' | 'data'> &\n Omit<CanvasPreset, 'type' | 'data'>\n\ninterface PresetII extends PresetI {\n type: NodeType.PRESET | string\n}\n\nexport interface PresetSchema<P = JSX.AnyProps> extends PresetII {\n $id: PresetId\n pluginId?: PluginId\n displayName?: string\n description?: string\n category?: string | ComponentCategory\n /**\n * Extra words this element should be findable by in the element picker —\n * synonyms and the name people reach for when it is not the name we chose\n * (\"picture\" for Image, \"dropdown\" for Select).\n *\n * Search terms only; never rendered. A hit here ranks BELOW every hit on a\n * name, so a tag widens what the picker finds without letting a synonym\n * outrank the element actually called that (AGL-2486).\n */\n tags?: string[]\n /** Alias of {@link tags}; both are searched. */\n keywords?: string[]\n icon?: MdiIconProps\n data: NodeSchemaNested<P>\n /**\n * Interaction templates wired when the preset is inserted (AGL-589):\n * the host app resolves each `presetRef` marker to a freshly minted node\n * and persists the result. Presets that need hover choreography (a\n * primitive dropdown panel) ship working out of the box instead of asking\n * the user to author the interactions by hand.\n *\n * TEMPLATES, and named so. These are not interactions: they are\n * unresolved, they reference nodes that do not exist yet, and they are\n * consumed at insert time and never stored. What they resolve INTO lives\n * on the node as {@link NodeSchema.interactions}, and a `PresetSchema` is\n * assignable to a `NodeSchema` — so while the two shared a name, one of\n * them had to be wrong wherever a preset was passed as a node.\n */\n interactionTemplates?: PresetInteractionSchema[]\n}\n\n/**\n * A visibility step inside a {@link PresetSchema} interaction template.\n * Only the basic client visibility steps are expressible — presets\n * scaffold in-page choreography, never server automations.\n */\nexport interface PresetInteractionStepSchema {\n type: 'showElement' | 'hideElement' | 'toggleElement'\n /** `presetRef` of the preset node this step targets. */\n targetRef: string\n /** Optional grace delay, validated to 0–5000ms like the builder. */\n delayMs?: number\n /** Self-dismissal for steps that can reveal the target. */\n dismissOn?: Array<'escape' | 'outsideClick'>\n}\n\n/** An interaction template a preset wires on insert (AGL-589). */\nexport interface PresetInteractionSchema {\n name: string\n event: 'elementClick' | 'elementHoverEnter' | 'elementHoverLeave'\n /** `presetRef` of the preset node whose selector fires the trigger. */\n triggerRef: string\n steps: PresetInteractionStepSchema[]\n}\n\n// @TODO ⚠️ Refactor for better adoption of hast\ntype NodeI<P> = AglynDocument & Omit<AbstractNodeSchema<P>, 'type'>\n\nexport interface NodeSchema<P = JSX.AnyProps> extends NodeI<P> {\n type: NodeType | string\n /**\n * List of the children unique identifiers for the node\n */\n nodes?: NodeId[]\n /**\n * Class name to pass the DOM node, can also be defined in props\n */\n className?: string\n /**\n * Preset-authoring marker (AGL-589): names a node inside a preset's\n * `data` tree so the preset's interaction templates can reference it\n * (`triggerRef` / `targetRef`). Cloned onto the inserted node; inert\n * everywhere else.\n */\n presetRef?: string\n /**\n * Per-instance style overrides on a reusable-component instance node\n * (AGL-1306), keyed by target: `root` (see `STYLE_OVERRIDES_ROOT_KEY`)\n * addresses the component ROOT, and any other key is a DEFINITION-internal\n * node id addressing one leaf inside the component (AGL-1332). Each slice\n * is an sx-shaped record merged over that node's own sx at graft time\n * (`composeReusableComponentNodes` → `mergeNodeSx`), so one page's CTA can\n * switch to a white background AND repaint its headline without widening\n * the component's declared props or detaching.\n *\n * Keyed on the definition's id, never the grafted `cmp__{instance}__{def}`\n * id: the graft id is derived per placement, so it would not survive a\n * duplicate or a re-key, and the same component could not offer two\n * instances the same authoring vocabulary. A key naming a node the\n * definition no longer has is ignored — a component edited later degrades\n * to its own styling instead of taking a page down.\n *\n * A first-class node field, NOT a prop: `AglynNode`'s constructor drops\n * unknown top-level keys, and anything in `props` is spread onto the\n * instance's wrapper DOM element. Lives on the instance node in the\n * document that places it — it survives component republish and never\n * pins the instance to an old component version, because the graft\n * always merges it over the definition's CURRENT nodes.\n */\n styleOverrides?: Record<string, JSX.SxProps>\n /**\n * Per-instance ATTRIBUTE overrides on a reusable-component instance node\n * (AGL-1899) — the same addressing as {@link styleOverrides}, applied to\n * a node's props instead of its sx, so one placement can carry a\n * different button `variant`, `size` or `href` without the component\n * declaring a prop for it and without detaching.\n *\n * Merged **per named prop**: a slice replaces only the props it names and\n * leaves the definition's others alone, so a component that later adds a\n * prop still gives every existing instance the new default. That is\n * deliberately shallower than the sx merge next door — an sx slice\n * cascades because `@scheme dark` and breakpoint objects are themselves\n * selectors, whereas a prop value is one value, and half-merging two\n * objects that merely look alike (say a `slotProps`) would produce a\n * third thing neither side asked for.\n *\n * `sx` is refused here. Styling an instance already has a mechanism one\n * field up, and honouring both would leave two writers racing for one\n * rendered value with no rule to say which wins.\n *\n * Applied BEFORE `{{prop.*}}` substitution, so an override may itself be\n * written in tokens and still resolves, and an override on a prop the\n * definition binds to a declared prop wins — the more specific placement\n * beats the component's own default.\n */\n attrOverrides?: Record<string, Record<string, unknown>>\n /**\n * EDITOR-ONLY: a screen's per-page restyling of its layout's elements\n * (AGL-3286), parked on the screen canvas ROOT while the besigner is open.\n *\n * Stored on the screen VERSION as `layoutStyleOverrides`, never in `nodes`:\n * the editor injects it here on load and lifts it back out on save\n * (`injectLayoutStyleOverrides` / `extractLayoutStyleOverrides`). Carried on\n * the root so an edit is an undo step, marks the screen dirty and rides the\n * working draft like any other change. Composition ignores it — the screen\n * root is dropped when it is grafted into the layout's slot.\n */\n layoutStyleOverrides?: Record<string, Record<string, Record<string, unknown>>>\n /**\n * Interactions this element carries — the click, hover and\n * scroll-into-view choreography authored on it in the besigner.\n *\n * ## Why they live on the NODE\n *\n * They used to be rows in `hosts/{hostId}/actions`, bound to an element by\n * a `[data-aglyn=\"leaf:<id>\"]` selector — the same collection the Workflows\n * page lists. An interaction is not a site-wide automation, and storing it\n * as one had four consequences, every one of them a thing an author would\n * expect to work:\n *\n * - it was not versioned with the document, so it neither published nor\n * rolled back with the screen it belongs to;\n * - it did not travel with a component copied to another site, or packaged\n * into a template or a plugin — the element arrived inert;\n * - deleting the element left the action behind, pointing at a selector\n * nothing resolves;\n * - and a nav's hover choreography sat in the site's automation list\n * beside \"when an order is placed\", which is a different kind of thing.\n *\n * Host actions remain, and remain the right home for a genuine site event.\n * The split is the concept: an ACTION is something the site does, an\n * INTERACTION is something an element does.\n *\n * ## Shape\n *\n * Deliberately the same `SiteInteraction` a site-wide interaction is, so\n * nothing about the runtime changes: the selector is stamped from this\n * node's own id when the page is composed, and the compiled result is the\n * shape the client engine has always consumed. `trigger.selector` is\n * therefore ignored here — the node IS the selector.\n */\n interactions?: NodeInteraction[]\n /**\n * Hidden by the author — the eye on the element's hierarchy row.\n *\n * Renders `display: none` on the canvas AND on the published site. It is\n * the plain \"I do not want this on the page\" switch: no runtime, no\n * interaction, nothing reveals it. Showing it again is the same eye.\n *\n * ## Why a FIELD and not a style\n *\n * Because it has to override the element's own display rather than replace\n * it. An author who toggles the eye on a Stack laid out with\n * `display: flex` must get that flex back when they toggle it off, and\n * writing `display: none` into `node.sx` destroys the value it overwrote —\n * there is nothing left to restore. Composed LAST in `Leaf` instead, so it\n * wins on the cascade and the stored style is never touched.\n *\n * ## Not {@link ELEMENT_HIDDEN_CLASS}\n *\n * `aglyn-hidden` is a different thing wearing a similar name: it means\n * \"starts hidden, and an interaction shows it\" — a mega-menu panel, a\n * drawer. That class is part of a runtime contract (the show/hide steps\n * toggle it, and the canvas reveals it for designing), and it is bespoke to\n * that job. A visibility toggle that wrote it would enrol every hidden\n * element in a choreography it is not part of.\n */\n hidden?: boolean\n /**\n * The computed node parent (only for type completion)\n */\n readonly parent?: NodeSchema<any> | null\n /**\n * The computed node parent (only for type completion)\n */\n readonly children?: NodeSchema<any>[]\n /**\n * The computed index in parent nodes (only for type completion)\n */\n readonly index?: number | null\n /**\n * The computed label (only for type completion)\n */\n readonly labelShort?: string | undefined\n /**\n * The computed breadcrumb path (only for type completion)\n */\n readonly breadcrumbPath?: NodeBreadcrumbPath\n /**\n * The computed component schema (only for type completion)\n */\n readonly componentSchema?: ComponentSchema | undefined\n /**\n * The computed guard for of child nodes (only for type completion)\n */\n readonly hasNodes?: boolean\n /**\n * The computed property for the resolved props from component schema\n */\n readonly resolvedProps?: P\n}\n\nexport enum NodeType {\n NODE = 'node',\n TEXT = 'text',\n NUMBER = 'number',\n SCREEN = 'screen',\n REF = 'ref',\n PRESET = 'preset',\n}\n\nexport type NodeSchemaJSON<P = JSX.AnyProps> = Omit<\n NodeSchema<P>,\n | 'parent'\n | 'index'\n | 'labelShort'\n | 'breadcrumbPath'\n | 'componentSchema'\n | 'hasNodes'\n>\n\nexport type NodeSchemaNested<P = JSX.AnyProps> = Omit<\n NodeSchemaJSON<P>,\n 'nodes'\n> & { nodes?: NodeSchemaNested<any>[] }\n\nexport type NodesMap = Record<NodeId, NodeSchema<any>>\n\nexport type NodeBreadcrumbPath = [\n root: string & typeof NODE_ROOT_ID,\n ...nodes: [...ancestors: NodeId[], node: NodeId],\n]\n\nexport type ProcessableNodes =\n | NodeSchemaNested<any>[]\n | NodeSchemaNested<any>\n | Record<NodeId, NodeSchema>\n\nexport { ComponentCategory }\n\nexport type { ComponentId }\nexport type { PresetId }\n\nexport type ComponentFactory<\n P extends ComponentProps<C> | any = any,\n C extends keyof JSX.IntrinsicElements | JSX.ElementConstructor<any> = any,\n> = ComponentClass<P> | JSX.ElementConstructor<P> | keyof JSX.IntrinsicElements\n// | keyof JSX.IntrinsicElements[keyof JSX.IntrinsicElements]\n\nexport type { ComponentsLinealOrder }\n\n/**\n * What the Attributes panel knows about the element a field edits, handed to\n * an attribute's `resolveProps` as its fourth argument.\n *\n * data-driven-forms gives a field its own value and the form's values, which\n * are the element's props and nothing else. A rule about the element as a\n * whole needs more than that: whether a Link Container holds anything that\n * names its link depends on the elements inside it. So the panel adds the\n * node, and a lookup for the ids listed in its `nodes`.\n *\n * Absent wherever a field is drawn outside that panel, including a unit test\n * calling the function directly, so a `resolveProps` that reads it has to\n * treat a missing context as having nothing to say.\n */\nexport interface AttributeFieldContext {\n /** The element the Attributes panel is editing. */\n node: NodeSchema<any>\n /** An element of the same document by id, such as a child in `nodes`. */\n getNode: (id: NodeId) => NodeSchema<any> | undefined\n}\n\n/**\n * An attribute's `resolveProps`: the three arguments data-driven-forms\n * passes, then the {@link AttributeFieldContext} the Attributes panel adds.\n */\nexport type AttributeResolveProps = (\n ...args: [\n ...Parameters<ResolvePropsFunction>,\n context?: AttributeFieldContext,\n ]\n) => ReturnType<ResolvePropsFunction>\n\n// @TODO ⚠️ Refactor for better adoption of hast\nexport interface AttributeSchema extends Dictionary<any> {\n name: string\n dataType?: DataType\n component: string | FieldComponentType\n validate?: Validator[]\n condition?: ConditionDefinition | ConditionDefinition[]\n initializeOnMount?: boolean\n initialValue?: any\n clearedValue?: any\n clearOnUnmount?: boolean\n actions?: FieldActions\n resolveProps?: AttributeResolveProps\n description?: string\n}\n\n// @TODO ⚠️ Refactor for better adoption of hast\nexport interface ComponentSchema<P = any> {\n $id?: ComponentId\n pluginId?: PluginId\n kind?: 'element' | 'plaintext' | 'markdown'\n\n displayName: string\n title?: string\n subtitle?: string\n description?: string\n category?: string | ComponentCategory\n\n /**\n * Extra words this element should be findable by in the element picker —\n * synonyms and the name people reach for when it is not the name we chose\n * (\"picture\" for Image, \"dropdown\" for Select).\n *\n * Search terms only; never rendered. A hit here ranks BELOW every hit on a\n * name, so a tag widens what the picker finds without letting a synonym\n * outrank the element actually called that (AGL-2486).\n */\n tags?: string[]\n /** Alias of {@link tags}; both are searched. */\n keywords?: string[]\n\n /**\n * Icon props for display around besigner\n */\n icon?: MdiIconProps\n /**\n * Options to be passed to styled(Component, \\{...styledOptions\\})\n */\n styledOptions?: MuiStyledOptions\n\n /**\n * Define a limitation for nodes allowed as direct descendents\n */\n restrictChildren?: ComponentsLinealOrder\n /**\n * Define a limitation for nodes allowed to be direct ancestors\n */\n restrictParent?: ComponentsLinealOrder\n\n /**\n * Filter props\n */\n resolveProps?: JSX.ResolveProps<NodeSchema<P>>\n\n /**\n * Attribute fields to modify the contextual properties\n * New version\n */\n attributes?: AttributeSchema[]\n\n /**\n * Which groups of formatting the inline rich-text toolbar offers for this\n * component (AGL-2557). Read only where `flags.richTextEditable` is on.\n *\n * Omitted means every group, which is what leaves Typography exactly as it\n * was. Naming a subset narrows the toolbar AND the commit: a surface that\n * offers neither lists nor links is phrasing-only, and the sanitizer holds\n * it to that whatever an author pastes in.\n */\n richTextCommands?: RICH_TEXT_COMMANDS[]\n\n /**\n * Feature flags\n */\n flags?: {\n /**\n * Disable the use of emotion styled\n */\n emotion?: FEATURE_FLAG\n /**\n * Can the nodes of this component type be cloned?\n */\n cloning?: FEATURE_FLAG\n /**\n * Allow dragging nodes of this component type\n */\n dragging?: FEATURE_FLAG\n /**\n * Allow dropping nodes inside nodes of this component type\n */\n dropping?: FEATURE_FLAG\n /**\n * Allow editing element attributes of this component type\n */\n editing?: FEATURE_FLAG\n /**\n * Allow removing nodes of this component type\n */\n removing?: FEATURE_FLAG\n /**\n * Describe nodes of this component type to be self-closing\n */\n selfClosing?: FEATURE_FLAG\n /**\n * Component renders its `children` prop as text content the editor may\n * edit directly (Attributes \"Text\" field, inline canvas editing).\n */\n textEditable?: FEATURE_FLAG\n /**\n * Component also accepts basic rich text (AGL-54): the inline editor\n * upgrades to a formatting surface and commits sanitized HTML into the\n * `html` prop (with `children` kept as the plain-text fallback).\n */\n richTextEditable?: FEATURE_FLAG\n /**\n * Component reads its children POSITIONALLY (AGL-1237), so the renderer\n * hands it one React child per node child instead of the single `<Branch>`\n * element it normally passes.\n *\n * MUI's Accordion does `const [summary, ...rest] = Children.toArray(children)`,\n * and `toArray` does not traverse into an element — so with the default\n * wrapping the summary swallowed the whole subtree and the Collapse got\n * nothing. Every accordion on every published site expanded to reveal an\n * empty panel while its content rendered unconditionally inside the\n * summary's heading.\n *\n * Opt in only for components with this contract; the wrapper is what keeps\n * the Branch/Stem/Leaf seam swappable for everything else.\n */\n positionalChildren?: FEATURE_FLAG\n }\n}\n\nexport type SchemasByCategory = Record<\n ComponentCategory | string,\n (ComponentSchema<any> | PresetSchema<any>)[]\n>\n\nexport interface ScreenSchema {\n nodes: NodeSchemaNested\n createdAt?: ITimestamp\n updatedAt?: ITimestamp\n hostId?: HostUid\n}\n"],"names":["ComponentCategory","NodeType"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAuBD,SAASA,iBAAiB,QAAQ,wCAAoC;AAictE,OAAO,IAAA,AAAKC,kCAAAA;;;;;;;WAAAA;MAOX;AA6BD,SAASD,iBAAiB,GAAE"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/campaign-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 * 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 CAMPAIGN_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 CampaignAttribution {\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 `CampaignAttribution` field each URL key fills. */\nconst FIELD_FOR_KEY: Record<\n (typeof CAMPAIGN_QUERY_KEYS)[number],\n keyof CampaignAttribution\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 CampaignParamSource =\n | URLSearchParams\n | Record<string, string | string[] | undefined>\n | null\n | undefined\n\nfunction rawValue(params: CampaignParamSource, 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 parseCampaignAttribution(\n params: CampaignParamSource,\n): CampaignAttribution | null {\n if (!params) return null\n const attribution: CampaignAttribution = {}\n for (const key of CAMPAIGN_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 campaignAttributionQuery(\n attribution: CampaignAttribution | null | undefined,\n): string {\n if (!attribution) return ''\n const params = new URLSearchParams()\n for (const key of CAMPAIGN_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 campaignEventParams(\n attribution: CampaignAttribution | 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":["CAMPAIGN_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","parseCampaignAttribution","attribution","Object","keys","campaignAttributionQuery","URLSearchParams","set","toString","campaignEventParams","source","campaign_source","medium","campaign_medium","campaign","campaign_name"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDC,GAED;;;;;CAKC,GACD,OAAO,MAAMA,sBAAsB;IACjC;IACA;IACA;CACD,CAAS;AAYV,4EAA4E,GAC5E,MAAMC,mBAAmB;AAEzB,kFAAkF,GAClF,MAAMC,eAAe;AAErB,0DAA0D,GAC1D,MAAMC,gBAGF;IACFC,YAAY;IACZC,YAAY;IACZC,cAAc;AAChB;AASA,SAASC,SAASC,MAA2B,EAAEC,GAAW;IACxD,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,yBACdZ,MAA2B;IAE3B,IAAI,CAACA,QAAQ,OAAO;IACpB,MAAMa,cAAmC,CAAC;IAC1C,KAAK,MAAMZ,OAAOT,oBAAqB;QACrC,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,yBACdH,WAAmD;IAEnD,IAAI,CAACA,aAAa,OAAO;IACzB,MAAMb,SAAS,IAAIiB;IACnB,KAAK,MAAMhB,OAAOT,oBAAqB;QACrC,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,oBACdP,WAAmD;IAMnD,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"}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/campaign-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 campaign across the domain hop (AGL-1731).\n *\n * `campaign-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 `campaign-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 CAMPAIGN_QUERY_KEYS,\n campaignAttributionQuery,\n parseCampaignAttribution,\n type CampaignAttribution,\n} from './campaign-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 CAMPAIGN_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 VisitCampaignRead =\n | { status: 'campaign'; campaign: CampaignAttribution }\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 readVisitCampaign(): VisitCampaignRead {\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(CAMPAIGN_VISIT_STORAGE_KEY)\n } catch {\n return { status: 'unreadable' }\n }\n if (!raw) return { status: 'none' }\n const campaign = parseCampaignAttribution(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 rememberVisitCampaign(search?: string): CampaignAttribution | null {\n const existing = readVisitCampaign()\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 = parseCampaignAttribution(new URLSearchParams(source))\n if (!campaign) return null\n try {\n store.setItem(CAMPAIGN_VISIT_STORAGE_KEY, campaignAttributionQuery(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 setCampaignForwardingConsent(allowed: boolean | null): void {\n storageConsent = allowed === true ? true : allowed === false ? false : null\n if (storageConsent === true) {\n rememberVisitCampaign()\n return\n }\n if (storageConsent === false) {\n const store = sessionStore()\n if (!store) return\n try {\n store.removeItem(CAMPAIGN_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 campaignToForward(search?: string): CampaignAttribution | null {\n const stored = readVisitCampaign()\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 parseCampaignAttribution(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 decorateCampaignHref(\n href: string,\n baseHref: string,\n consoleOrigin: string,\n campaign: CampaignAttribution | 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 CAMPAIGN_QUERY_KEYS) url.searchParams.delete(key)\n const query = new URLSearchParams(campaignAttributionQuery(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 installCampaignForwarding(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 = decorateCampaignHref(\n anchor.getAttribute('href') || '',\n window.location.href,\n origin,\n campaignToForward(),\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 resetCampaignForwarding\n}\n\n/** Test seam — removes both listeners, forgets them, and forgets consent. */\nexport function resetCampaignForwarding(): 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":["CAMPAIGN_QUERY_KEYS","campaignAttributionQuery","parseCampaignAttribution","CAMPAIGN_VISIT_STORAGE_KEY","storageConsent","installedHandler","sessionStore","window","sessionStorage","readVisitCampaign","status","store","raw","getItem","campaign","URLSearchParams","rememberVisitCampaign","search","existing","source","location","setItem","setCampaignForwardingConsent","allowed","removeItem","campaignToForward","stored","live","normalizeOrigin","value","URL","origin","decorateCampaignHref","href","baseHref","consoleOrigin","url","key","searchParams","delete","query","forEach","set","next","toString","installCampaignForwarding","options","document","undefined","onActivate","event","target","closest","anchor","decorated","getAttribute","setAttribute","addEventListener","resetCampaignForwarding","removeEventListener"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoGC,GAED,SACEA,mBAAmB,EACnBC,wBAAwB,EACxBC,wBAAwB,QAEnB,4BAAwB;AAE/B;;;;;CAKC,GACD,OAAO,MAAMC,6BAA6B,iBAAgB;AAkB1D;;;;;;;CAOC,GACD,IAAIC,iBAAiC;AAErC;;;8CAG8C,GAC9C,IAAIC,mBAAoD;AAExD,SAASC;IACP,IAAI,OAAOC,WAAW,aAAa,OAAO;IAC1C,gEAAgE;IAChE,IAAI;YACKA;QAAP,QAAOA,yBAAAA,OAAOC,cAAc,YAArBD,yBAAyB;IAClC,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;;;CAOC,GACD,OAAO,SAASE;IACd,IAAIL,mBAAmB,MAAM,OAAO;QAAEM,QAAQ;IAAO;IACrD,MAAMC,QAAQL;IACd,IAAI,CAACK,OAAO,OAAO;QAAED,QAAQ;IAAa;IAC1C,IAAIE;IACJ,IAAI;QACFA,MAAMD,MAAME,OAAO,CAACV;IACtB,EAAE,eAAM;QACN,OAAO;YAAEO,QAAQ;QAAa;IAChC;IACA,IAAI,CAACE,KAAK,OAAO;QAAEF,QAAQ;IAAO;IAClC,MAAMI,WAAWZ,yBAAyB,IAAIa,gBAAgBH;IAC9D,OAAOE,WAAW;QAAEJ,QAAQ;QAAYI;IAAS,IAAI;QAAEJ,QAAQ;IAAO;AACxE;AAEA;;;;;;;;;CASC,GACD,OAAO,SAASM,sBAAsBC,MAAe;IACnD,MAAMC,WAAWT;IACjB,IAAIS,SAASR,MAAM,KAAK,YAAY,OAAOQ,SAASJ,QAAQ;IAC5D,IAAIV,mBAAmB,MAAM,OAAO;IACpC,MAAMO,QAAQL;IACd,IAAI,CAACK,OAAO,OAAO;IACnB,MAAMQ,SACJ,OAAOF,WAAW,WACdA,SACA,OAAOV,WAAW,cAChB,KACAA,OAAOa,QAAQ,CAACH,MAAM;IAC9B,MAAMH,WAAWZ,yBAAyB,IAAIa,gBAAgBI;IAC9D,IAAI,CAACL,UAAU,OAAO;IACtB,IAAI;QACFH,MAAMU,OAAO,CAAClB,4BAA4BF,yBAAyBa;IACrE,EAAE,eAAM;QACN,yEAAyE;QACzE,oEAAoE;QACpE,2CAA2C;QAC3C,OAAO;IACT;IACA,OAAOA;AACT;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASQ,6BAA6BC,OAAuB;IAClEnB,iBAAiBmB,YAAY,OAAO,OAAOA,YAAY,QAAQ,QAAQ;IACvE,IAAInB,mBAAmB,MAAM;QAC3BY;QACA;IACF;IACA,IAAIZ,mBAAmB,OAAO;QAC5B,MAAMO,QAAQL;QACd,IAAI,CAACK,OAAO;QACZ,IAAI;YACFA,MAAMa,UAAU,CAACrB;QACnB,EAAE,eAAM;QACN,qEAAqE;QACvE;IACF;AACF;AAEA;;;;;;CAMC,GACD,OAAO,SAASsB,kBAAkBR,MAAe;IAC/C,MAAMS,SAASjB;IACf,IAAIiB,OAAOhB,MAAM,KAAK,YAAY,OAAOgB,OAAOZ,QAAQ;IACxD,MAAMa,OACJ,OAAOV,WAAW,WACdA,SACA,OAAOV,WAAW,cAChB,KACAA,OAAOa,QAAQ,CAACH,MAAM;IAC9B,OAAOf,yBAAyB,IAAIa,gBAAgBY;AACtD;AAEA,SAASC,gBAAgBC,KAAgC;IACvD,IAAI,CAACA,OAAO,OAAO;IACnB,IAAI;QACF,OAAO,IAAIC,IAAID,OAAOE,MAAM;IAC9B,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASC,qBACdC,IAAY,EACZC,QAAgB,EAChBC,aAAqB,EACrBrB,QAAoC;IAEpC,IAAI,CAACA,UAAU,OAAO;IACtB,MAAMiB,SAASH,gBAAgBO;IAC/B,IAAI,CAACJ,QAAQ,OAAO;IACpB,IAAIK;IACJ,IAAI;QACFA,MAAM,IAAIN,IAAIG,MAAMC;IACtB,EAAE,eAAM;QACN,OAAO;IACT;IACA,IAAIE,IAAIL,MAAM,KAAKA,QAAQ,OAAO;IAClC,KAAK,MAAMM,OAAOrC,oBAAqBoC,IAAIE,YAAY,CAACC,MAAM,CAACF;IAC/D,MAAMG,QAAQ,IAAIzB,gBAAgBd,yBAAyBa;IAC3D0B,MAAMC,OAAO,CAAC,CAACZ,OAAOQ,MAAQD,IAAIE,YAAY,CAACI,GAAG,CAACL,KAAKR;IACxD,MAAMc,OAAOP,IAAIQ,QAAQ;IACzB,OAAOD,SAASV,OAAO,OAAOU;AAChC;AAEA;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,SAASE,0BAA0BC,OAEzC;IACC,IAAI,OAAOC,aAAa,eAAe,OAAOxC,WAAW,aAAa;QACpE,OAAO,IAAMyC;IACf;IACA,IAAI3C,kBAAkB,OAAO,IAAM2C;IACnC,MAAMjB,SAASH,gBAAgBkB,2BAAAA,QAASX,aAAa;IACrD,IAAI,CAACJ,QAAQ,OAAO,IAAMiB;IAE1B,MAAMC,aAAa,CAACC;QAClB,IAAI;YACF,MAAMC,SAASD,MAAMC,MAAM;YAC3B,IAAI,CAACA,UAAU,OAAOA,OAAOC,OAAO,KAAK,YAAY;YACrD,MAAMC,SAASF,OAAOC,OAAO,CAAC;YAC9B,IAAI,CAACC,QAAQ;YACb,MAAMC,YAAYtB,qBAChBqB,OAAOE,YAAY,CAAC,WAAW,IAC/BhD,OAAOa,QAAQ,CAACa,IAAI,EACpBF,QACAN;YAEF,IAAI6B,WAAWD,OAAOG,YAAY,CAAC,QAAQF;QAC7C,EAAE,eAAM;QACN,uEAAuE;QACvE,yEAAyE;QAC3E;IACF;IAEAjD,mBAAmB4C;IACnBF,SAASU,gBAAgB,CAAC,eAAeR,YAAY;IACrDF,SAASU,gBAAgB,CAAC,SAASR,YAAY;IAC/C,OAAOS;AACT;AAEA,2EAA2E,GAC3E,OAAO,SAASA;IACd,IAAIrD,oBAAoB,OAAO0C,aAAa,aAAa;QACvDA,SAASY,mBAAmB,CAAC,eAAetD,kBAAkB;QAC9D0C,SAASY,mBAAmB,CAAC,SAAStD,kBAAkB;IAC1D;IACAA,mBAAmB;IACnBD,iBAAiB;AACnB"}
@@ -1,68 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- import { type ConsentGroup } from './consent-groups';
18
- /**
19
- * The holder of nothing: the group a contact is flattened through when no
20
- * site has captured it and no facet exists — a row written by a path that
21
- * predates attribution, read at the org level (AGL-2630).
22
- *
23
- * An empty `groupId` reads an empty facet (`readContactFacet` answers the
24
- * empty facet for a key the map lacks) and REFUSES a write
25
- * (`contactFacetPath` throws on an empty holder), which is the right pair:
26
- * the row still shows its shared identity, and nothing edits a facet
27
- * nobody holds. Exported so a surface can test for it rather than for an
28
- * empty string.
29
- */
30
- export declare const NO_HOLDER_GROUP: ConsentGroup;
31
- /**
32
- * The group a LEAD is read and acted on through (AGL-3278).
33
- *
34
- * The same question {@link contactPrimaryGroup} answers, with a shorter
35
- * answer: a lead is one flat document — no facets, one profile, one
36
- * status — so there is nothing to choose between and the holder is simply
37
- * the site that captured the person first. What it is FOR is the same: at
38
- * the organization level the record page has no viewing site, and it needs
39
- * one to read the consent basis against, to file an activity under, to
40
- * offer a booking on and to convert as.
41
- *
42
- * A lead no site has captured — a row imported before attribution — reads
43
- * through {@link NO_HOLDER_GROUP}, whose empty `hostId` is how a surface
44
- * asks "is there a site here at all" without testing for an empty string.
45
- */
46
- export declare function leadPrimaryGroup(lead: Record<string, unknown> | null | undefined, org: Record<string, unknown> | null | undefined): ConsentGroup;
47
- /**
48
- * The group a CROSS-HOLDER reader flattens one contact through (AGL-2630).
49
- *
50
- * Under a site there is one answer for every row — the site's own group,
51
- * `consentGroupForHost` — and the reader never asks this. At the
52
- * organization level there is no viewing site, and a contact captured by
53
- * three sites has up to three facets; the org-wide member reading them all
54
- * needs ONE to show the row's profile, owner and stage under, and to write
55
- * an edit back to. This picks the person's PRIMARY holder: the first
56
- * capturing site, in capture order, whose group actually holds a facet;
57
- * then any facet at all, resolved back to its declared group or to the
58
- * site whose id it is keyed by; then the first capturing site even with no
59
- * facet yet (the facet is written on the first edit). A row nobody captured
60
- * and nobody wrote a facet for reads through {@link NO_HOLDER_GROUP}.
61
- *
62
- * Capture order rather than sorted order, deliberately: `capturedByHostIds`
63
- * is maintained by `arrayUnion`, so its first entry is the site that met
64
- * the person first, and that site's profile is the one a person reading
65
- * across brands would expect to see. `contactCaptureHostIds` sorts the same
66
- * array for RENDERING, where a stable order matters more than a first.
67
- */
68
- export declare function contactPrimaryGroup(row: Record<string, unknown> | null | undefined, org: Record<string, unknown> | null | undefined): ConsentGroup;
@@ -1,129 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */ /**
17
- * WHICH HOLDER A CROSS-SITE READER SEES A CONTACT THROUGH (AGL-2630).
18
- *
19
- * A contact document carries one facet per holder, and every field but the
20
- * shared identity lives in one of them. A surface standing on a site reads
21
- * that site's group; a surface standing at the organization — the org-level
22
- * Contacts list, and the whole-collection export that writes the same file
23
- * — has no site to read as and needs one answer per row.
24
- *
25
- * In the library rather than in the CRM plugin because both of those
26
- * readers need it and one of them is an app: an app may not import a
27
- * feature plugin (AGL-2662).
28
- */ import { CONTACT_FACETS_FIELD } from "./contacts.js";
29
- import { CAPTURED_BY_HOST_FIELD } from "./marketing-consent.js";
30
- import { consentGroupForHost, consentGroupsAwaitConfirmation, readConsentGroups, soloConsentGroup } from "./consent-groups.js";
31
- /**
32
- * The holder of nothing: the group a contact is flattened through when no
33
- * site has captured it and no facet exists — a row written by a path that
34
- * predates attribution, read at the org level (AGL-2630).
35
- *
36
- * An empty `groupId` reads an empty facet (`readContactFacet` answers the
37
- * empty facet for a key the map lacks) and REFUSES a write
38
- * (`contactFacetPath` throws on an empty holder), which is the right pair:
39
- * the row still shows its shared identity, and nothing edits a facet
40
- * nobody holds. Exported so a surface can test for it rather than for an
41
- * empty string.
42
- */ export const NO_HOLDER_GROUP = Object.freeze({
43
- hostId: '',
44
- groupId: '',
45
- name: null,
46
- hostIds: [],
47
- declared: false,
48
- awaitsConfirmation: false
49
- });
50
- /**
51
- * The sites that captured this person, in CAPTURE order — the order
52
- * `arrayUnion` left, so the first entry is the site that met them first.
53
- *
54
- * `contactCaptureHostIds` sorts the same array for RENDERING, where a
55
- * stable order matters more than a first. Both readers below want the
56
- * first, so neither may use the sorted one.
57
- */ function captureOrder(record) {
58
- const raw = (record != null ? record : {})[CAPTURED_BY_HOST_FIELD];
59
- if (!Array.isArray(raw)) return [];
60
- return raw.map((id)=>String(id != null ? id : '').trim()).filter(Boolean);
61
- }
62
- /**
63
- * The group a LEAD is read and acted on through (AGL-3278).
64
- *
65
- * The same question {@link contactPrimaryGroup} answers, with a shorter
66
- * answer: a lead is one flat document — no facets, one profile, one
67
- * status — so there is nothing to choose between and the holder is simply
68
- * the site that captured the person first. What it is FOR is the same: at
69
- * the organization level the record page has no viewing site, and it needs
70
- * one to read the consent basis against, to file an activity under, to
71
- * offer a booking on and to convert as.
72
- *
73
- * A lead no site has captured — a row imported before attribution — reads
74
- * through {@link NO_HOLDER_GROUP}, whose empty `hostId` is how a surface
75
- * asks "is there a site here at all" without testing for an empty string.
76
- */ export function leadPrimaryGroup(lead, org) {
77
- const captured = captureOrder(lead);
78
- return captured.length ? consentGroupForHost(org, captured[0]) : NO_HOLDER_GROUP;
79
- }
80
- /**
81
- * The group a CROSS-HOLDER reader flattens one contact through (AGL-2630).
82
- *
83
- * Under a site there is one answer for every row — the site's own group,
84
- * `consentGroupForHost` — and the reader never asks this. At the
85
- * organization level there is no viewing site, and a contact captured by
86
- * three sites has up to three facets; the org-wide member reading them all
87
- * needs ONE to show the row's profile, owner and stage under, and to write
88
- * an edit back to. This picks the person's PRIMARY holder: the first
89
- * capturing site, in capture order, whose group actually holds a facet;
90
- * then any facet at all, resolved back to its declared group or to the
91
- * site whose id it is keyed by; then the first capturing site even with no
92
- * facet yet (the facet is written on the first edit). A row nobody captured
93
- * and nobody wrote a facet for reads through {@link NO_HOLDER_GROUP}.
94
- *
95
- * Capture order rather than sorted order, deliberately: `capturedByHostIds`
96
- * is maintained by `arrayUnion`, so its first entry is the site that met
97
- * the person first, and that site's profile is the one a person reading
98
- * across brands would expect to see. `contactCaptureHostIds` sorts the same
99
- * array for RENDERING, where a stable order matters more than a first.
100
- */ export function contactPrimaryGroup(row, org) {
101
- const record = row != null ? row : {};
102
- const facets = record[CONTACT_FACETS_FIELD];
103
- const facetKeys = facets && typeof facets === 'object' && !Array.isArray(facets) ? Object.keys(facets) : [];
104
- const captured = captureOrder(record);
105
- for (const hostId of captured){
106
- const group = consentGroupForHost(org, hostId);
107
- if (facetKeys.includes(group.groupId)) return group;
108
- }
109
- for (const key of facetKeys){
110
- const declared = readConsentGroups(org)[key];
111
- if (declared) {
112
- return {
113
- hostId: declared.hostIds[0],
114
- groupId: key,
115
- name: declared.name,
116
- hostIds: [
117
- ...declared.hostIds
118
- ],
119
- declared: true,
120
- awaitsConfirmation: consentGroupsAwaitConfirmation(org)
121
- };
122
- }
123
- return soloConsentGroup(key);
124
- }
125
- if (captured.length) return consentGroupForHost(org, captured[0]);
126
- return NO_HOLDER_GROUP;
127
- }
128
-
129
- //# sourceMappingURL=contact-holder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/contact-holder.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 * WHICH HOLDER A CROSS-SITE READER SEES A CONTACT THROUGH (AGL-2630).\n *\n * A contact document carries one facet per holder, and every field but the\n * shared identity lives in one of them. A surface standing on a site reads\n * that site's group; a surface standing at the organization — the org-level\n * Contacts list, and the whole-collection export that writes the same file\n * — has no site to read as and needs one answer per row.\n *\n * In the library rather than in the CRM plugin because both of those\n * readers need it and one of them is an app: an app may not import a\n * feature plugin (AGL-2662).\n */\n\nimport { CONTACT_FACETS_FIELD } from './contacts'\nimport { CAPTURED_BY_HOST_FIELD } from './marketing-consent'\nimport {\n type ConsentGroup,\n consentGroupForHost,\n consentGroupsAwaitConfirmation,\n readConsentGroups,\n soloConsentGroup,\n} from './consent-groups'\n\n/**\n * The holder of nothing: the group a contact is flattened through when no\n * site has captured it and no facet exists — a row written by a path that\n * predates attribution, read at the org level (AGL-2630).\n *\n * An empty `groupId` reads an empty facet (`readContactFacet` answers the\n * empty facet for a key the map lacks) and REFUSES a write\n * (`contactFacetPath` throws on an empty holder), which is the right pair:\n * the row still shows its shared identity, and nothing edits a facet\n * nobody holds. Exported so a surface can test for it rather than for an\n * empty string.\n */\nexport const NO_HOLDER_GROUP: ConsentGroup = Object.freeze({\n hostId: '',\n groupId: '',\n name: null,\n hostIds: [],\n declared: false,\n awaitsConfirmation: false,\n}) as ConsentGroup\n\n/**\n * The sites that captured this person, in CAPTURE order — the order\n * `arrayUnion` left, so the first entry is the site that met them first.\n *\n * `contactCaptureHostIds` sorts the same array for RENDERING, where a\n * stable order matters more than a first. Both readers below want the\n * first, so neither may use the sorted one.\n */\nfunction captureOrder(\n record: Record<string, unknown> | null | undefined,\n): string[] {\n const raw = (record ?? {})[CAPTURED_BY_HOST_FIELD]\n if (!Array.isArray(raw)) return []\n return raw.map((id) => String(id ?? '').trim()).filter(Boolean)\n}\n\n/**\n * The group a LEAD is read and acted on through (AGL-3278).\n *\n * The same question {@link contactPrimaryGroup} answers, with a shorter\n * answer: a lead is one flat document — no facets, one profile, one\n * status — so there is nothing to choose between and the holder is simply\n * the site that captured the person first. What it is FOR is the same: at\n * the organization level the record page has no viewing site, and it needs\n * one to read the consent basis against, to file an activity under, to\n * offer a booking on and to convert as.\n *\n * A lead no site has captured — a row imported before attribution — reads\n * through {@link NO_HOLDER_GROUP}, whose empty `hostId` is how a surface\n * asks \"is there a site here at all\" without testing for an empty string.\n */\nexport function leadPrimaryGroup(\n lead: Record<string, unknown> | null | undefined,\n org: Record<string, unknown> | null | undefined,\n): ConsentGroup {\n const captured = captureOrder(lead)\n return captured.length ? consentGroupForHost(org, captured[0]) : NO_HOLDER_GROUP\n}\n\n/**\n * The group a CROSS-HOLDER reader flattens one contact through (AGL-2630).\n *\n * Under a site there is one answer for every row — the site's own group,\n * `consentGroupForHost` — and the reader never asks this. At the\n * organization level there is no viewing site, and a contact captured by\n * three sites has up to three facets; the org-wide member reading them all\n * needs ONE to show the row's profile, owner and stage under, and to write\n * an edit back to. This picks the person's PRIMARY holder: the first\n * capturing site, in capture order, whose group actually holds a facet;\n * then any facet at all, resolved back to its declared group or to the\n * site whose id it is keyed by; then the first capturing site even with no\n * facet yet (the facet is written on the first edit). A row nobody captured\n * and nobody wrote a facet for reads through {@link NO_HOLDER_GROUP}.\n *\n * Capture order rather than sorted order, deliberately: `capturedByHostIds`\n * is maintained by `arrayUnion`, so its first entry is the site that met\n * the person first, and that site's profile is the one a person reading\n * across brands would expect to see. `contactCaptureHostIds` sorts the same\n * array for RENDERING, where a stable order matters more than a first.\n */\nexport function contactPrimaryGroup(\n row: Record<string, unknown> | null | undefined,\n org: Record<string, unknown> | null | undefined,\n): ConsentGroup {\n const record = row ?? {}\n const facets = record[CONTACT_FACETS_FIELD]\n const facetKeys =\n facets && typeof facets === 'object' && !Array.isArray(facets)\n ? Object.keys(facets as Record<string, unknown>)\n : []\n const captured = captureOrder(record)\n for (const hostId of captured) {\n const group = consentGroupForHost(org, hostId)\n if (facetKeys.includes(group.groupId)) return group\n }\n for (const key of facetKeys) {\n const declared = readConsentGroups(org)[key]\n if (declared) {\n return {\n hostId: declared.hostIds[0],\n groupId: key,\n name: declared.name,\n hostIds: [...declared.hostIds],\n declared: true,\n awaitsConfirmation: consentGroupsAwaitConfirmation(org),\n }\n }\n return soloConsentGroup(key)\n }\n if (captured.length) return consentGroupForHost(org, captured[0])\n return NO_HOLDER_GROUP\n}"],"names":["CONTACT_FACETS_FIELD","CAPTURED_BY_HOST_FIELD","consentGroupForHost","consentGroupsAwaitConfirmation","readConsentGroups","soloConsentGroup","NO_HOLDER_GROUP","Object","freeze","hostId","groupId","name","hostIds","declared","awaitsConfirmation","captureOrder","record","raw","Array","isArray","map","id","String","trim","filter","Boolean","leadPrimaryGroup","lead","org","captured","length","contactPrimaryGroup","row","facets","facetKeys","keys","group","includes","key"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;CAYC,GAED,SAASA,oBAAoB,QAAQ,gBAAY;AACjD,SAASC,sBAAsB,QAAQ,yBAAqB;AAC5D,SAEEC,mBAAmB,EACnBC,8BAA8B,EAC9BC,iBAAiB,EACjBC,gBAAgB,QACX,sBAAkB;AAEzB;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,kBAAgCC,OAAOC,MAAM,CAAC;IACzDC,QAAQ;IACRC,SAAS;IACTC,MAAM;IACNC,SAAS,EAAE;IACXC,UAAU;IACVC,oBAAoB;AACtB,GAAkB;AAElB;;;;;;;CAOC,GACD,SAASC,aACPC,MAAkD;IAElD,MAAMC,MAAM,CAACD,iBAAAA,SAAU,CAAC,EAAE,CAACf,uBAAuB;IAClD,IAAI,CAACiB,MAAMC,OAAO,CAACF,MAAM,OAAO,EAAE;IAClC,OAAOA,IAAIG,GAAG,CAAC,CAACC,KAAOC,OAAOD,aAAAA,KAAM,IAAIE,IAAI,IAAIC,MAAM,CAACC;AACzD;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,iBACdC,IAAgD,EAChDC,GAA+C;IAE/C,MAAMC,WAAWd,aAAaY;IAC9B,OAAOE,SAASC,MAAM,GAAG5B,oBAAoB0B,KAAKC,QAAQ,CAAC,EAAE,IAAIvB;AACnE;AAEA;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASyB,oBACdC,GAA+C,EAC/CJ,GAA+C;IAE/C,MAAMZ,SAASgB,cAAAA,MAAO,CAAC;IACvB,MAAMC,SAASjB,MAAM,CAAChB,qBAAqB;IAC3C,MAAMkC,YACJD,UAAU,OAAOA,WAAW,YAAY,CAACf,MAAMC,OAAO,CAACc,UACnD1B,OAAO4B,IAAI,CAACF,UACZ,EAAE;IACR,MAAMJ,WAAWd,aAAaC;IAC9B,KAAK,MAAMP,UAAUoB,SAAU;QAC7B,MAAMO,QAAQlC,oBAAoB0B,KAAKnB;QACvC,IAAIyB,UAAUG,QAAQ,CAACD,MAAM1B,OAAO,GAAG,OAAO0B;IAChD;IACA,KAAK,MAAME,OAAOJ,UAAW;QAC3B,MAAMrB,WAAWT,kBAAkBwB,IAAI,CAACU,IAAI;QAC5C,IAAIzB,UAAU;YACZ,OAAO;gBACLJ,QAAQI,SAASD,OAAO,CAAC,EAAE;gBAC3BF,SAAS4B;gBACT3B,MAAME,SAASF,IAAI;gBACnBC,SAAS;uBAAIC,SAASD,OAAO;iBAAC;gBAC9BC,UAAU;gBACVC,oBAAoBX,+BAA+ByB;YACrD;QACF;QACA,OAAOvB,iBAAiBiC;IAC1B;IACA,IAAIT,SAASC,MAAM,EAAE,OAAO5B,oBAAoB0B,KAAKC,QAAQ,CAAC,EAAE;IAChE,OAAOvB;AACT"}
@@ -1,136 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- /**
18
- * MERGING TWO CONTACT DOCUMENTS INTO ONE (AGL-2625) — the pure half.
19
- *
20
- * Contacts are keyed by email, so exact duplicates cannot exist; one human
21
- * with `jane@acme.com` and `jane@gmail.com` is two documents, with tags,
22
- * deals, tasks and a timeline split between them. A merge folds the second
23
- * into the first: the SURVIVOR keeps its address as the identity, the MERGED
24
- * record's address becomes an alternate on it, and every field is combined
25
- * by one rule the dialog can show before the write happens.
26
- *
27
- * ## The rule
28
- *
29
- * - A scalar — a name, a phone, a title, a stage, an owner — is the
30
- * survivor's where the survivor has one, and fills from the merged record
31
- * where the survivor is empty. Nothing the survivor holds is overwritten.
32
- * - A list — tags, campaign filings, the timeline, the sites that captured
33
- * the person, the forms, the companies — is the UNION, deduplicated and
34
- * bounded where the write paths bound it.
35
- * - A figure that counts something — orders, lifetime value, refunds — is
36
- * the SUM: two records of one person's purchases are one person's
37
- * purchases. A "first" is the earlier, a "last" is the later.
38
- * - Notes are appended, survivor first: a note is a list of sentences, and
39
- * dropping the merged record's would be the one silent loss in a merge
40
- * that is otherwise lossless.
41
- * - A recorded REFUSAL of marketing mail on either record stands on the
42
- * survivor. The merge asserts the two are one person, and that person
43
- * said no.
44
- *
45
- * Applied per holder: each group's facet on the merged record is folded
46
- * into the same group's facet on the survivor, and a group only the merged
47
- * record had comes across whole. No holder's records reach another holder's
48
- * facet, which is the guarantee the facet shape exists for.
49
- *
50
- * ## Why this is a plan and not a write
51
- *
52
- * The console shows the two records side by side and says which value wins
53
- * before anything is committed; the server performs the write. Both read
54
- * this function, so the preview cannot show one outcome and the write land
55
- * another. The plan is plain values — no sentinels — so it can be rendered,
56
- * and it is shaped for a merge-`set`, which deep-merges the facet map one
57
- * holder at a time and replaces an array whole: exactly the two behaviors
58
- * the rule above needs.
59
- */
60
- import { type ContactInteraction } from './contacts';
61
- export interface ContactMergePlan {
62
- /**
63
- * What the survivor becomes, as a merge-`set` payload: every derived
64
- * field with its full value, and only the groups the merged record held
65
- * under `facets`. Plain values throughout — the writer adds its own
66
- * `updatedAt`.
67
- */
68
- survivor: Record<string, unknown>;
69
- /** Every address the survivor answers to afterwards, primary first. */
70
- emails: string[];
71
- /** The merged record's addresses — what its leads and index entries are keyed by. */
72
- mergedEmails: string[];
73
- /**
74
- * The companies that lose a contact: those BOTH records were filed under,
75
- * which counted two people and now count one. A company only one record
76
- * named keeps its count — the survivor still names it.
77
- */
78
- companyCounts: Array<{
79
- companyId: string;
80
- delta: -1;
81
- }>;
82
- /** The union of both records' scope tokens — who may read the survivor. */
83
- visibleTo: string[];
84
- }
85
- type Doc = Record<string, unknown>;
86
- /**
87
- * Both timelines as one: deduplicated by event, newest first, and capped
88
- * the way every capture caps it — so a merge of two full timelines keeps
89
- * the fifty most recent visits rather than growing the document past what
90
- * a capture may write.
91
- */
92
- export declare function mergeInteractions(survivor: readonly ContactInteraction[] | undefined, merged: readonly ContactInteraction[] | undefined): ContactInteraction[];
93
- /**
94
- * One holder's facet after the merge.
95
- *
96
- * `survivor` is that holder's facet on the surviving record — possibly
97
- * absent, when only the merged record was held by this group — and `merged`
98
- * the same holder's facet on the record being folded in.
99
- */
100
- export declare function mergeContactFacet(survivor: Doc | undefined, merged: Doc | undefined): Doc;
101
- /**
102
- * The plan for folding `merged` into `survivor`.
103
- *
104
- * Both are the stored documents as read. The survivor's `email` is the
105
- * identity that stands; the merged record's becomes an alternate.
106
- */
107
- export declare function planContactMerge(survivor: Doc, merged: Doc): ContactMergePlan;
108
- /** Where a previewed value comes from. */
109
- export type ContactMergeSource = 'survivor' | 'merged' | 'both' | 'none';
110
- /** One line of the side-by-side the dialog shows. */
111
- export interface ContactMergePreviewRow {
112
- key: string;
113
- label: string;
114
- /** The surviving record's value, as text. */
115
- survivor: string;
116
- /** The merged record's value, as text. */
117
- merged: string;
118
- /** What the survivor carries afterwards. */
119
- result: string;
120
- from: ContactMergeSource;
121
- }
122
- export interface ContactMergePreviewOptions {
123
- /** The owner's display name for a uid; the uid itself when absent. */
124
- memberName?: (uid: string) => string;
125
- }
126
- /**
127
- * The merge as the viewing group would see it, field by field.
128
- *
129
- * Reads through ONE group's facet, the way the record page does, so a
130
- * holder previewing a merge sees its own values and never another holder's.
131
- * Every row carries the survivor's value, the merged record's, and the
132
- * result the plan would write, so the dialog is a rendering and not a second
133
- * opinion.
134
- */
135
- export declare function contactMergePreview(survivor: Doc, merged: Doc, groupId: string, options?: ContactMergePreviewOptions): ContactMergePreviewRow[];
136
- export {};