@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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-page-facts.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 {\n buildPageMarkdown,\n headingLevelOf,\n isPageChromeNode,\n pageContentRootId,\n type PageMarkdownNode,\n type PageMarkdownNodes,\n} from './page-markdown'\n\n/**\n * What a page SAYS, read off its node tree (AGL-2910): the text a search\n * listing summarizes, the headings, and the images — each heading and image\n * by node id, so a finding can name the element it is about and a fix can\n * address the element it changes.\n *\n * Pure, and the platform's: the SEO check (`seo-audit.ts`) judges a page by\n * these facts, and anything that proposes a listing or a content fix reads\n * the same ones rather than walking the tree its own way.\n *\n * The text is the page's Markdown representation, the one an agent is\n * served, so what the listing summarizes is what a reader of the page gets.\n * The walk starts where that serializer starts — the content region — and\n * skips what it skips, through the same chrome predicate: a heading in the\n * site's navigation is not the page's heading.\n */\n\n/** How much of a page's text the facts carry. A listing summarizes; it does not need the page. */\nexport const SEO_PAGE_TEXT_MAX_CHARS = 2_000\n\n/** How much text around an image describes it. */\nconst IMAGE_CONTEXT_MAX_CHARS = 160\n\n/** The deepest a walk goes, and it stops at a cycle — a node map is data. */\nconst MAX_DEPTH = 64\n\n/** The text components whose heading level `headingLevelOf` reads. */\nconst TEXT_COMPONENTS: ReadonlySet<string> = new Set(['muiTypography', 'muiInlineText', 'muiListItemText'])\n\nexport interface SeoHeading {\n /** The element, or `null` for a heading inside rich text a fix cannot address. */\n nodeId: string | null\n level: number\n text: string\n /** Whether a content fix can change it: a text component with plain text. */\n editable: boolean\n}\n\nexport interface SeoImage {\n nodeId: string\n src: string\n alt: string\n /** The nearest heading or text before it, which is what the image illustrates. */\n context: string\n}\n\nexport interface SeoPageFacts {\n /** The page's Markdown body, capped. */\n text: string\n wordCount: number\n headings: SeoHeading[]\n h1s: SeoHeading[]\n images: SeoImage[]\n imagesMissingAlt: SeoImage[]\n /** The node the content region starts at; a new heading goes first inside it. */\n contentRootId: string | null\n}\n\nconst collapse = (value: string): string => value.replace(/\\s+/g, ' ').trim()\n\n/** Author HTML to its text, for a heading stored as rich text. */\nfunction htmlText(html: string): string {\n return collapse(html.replace(/<[^>]*>/g, ' ').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&'))\n}\n\n/** A text component's own text, and whether it is plain (a fix can rewrite it). */\nfunction textOf(props: Record<string, unknown>): { text: string; plain: boolean } {\n const html = props['html']\n if (typeof html === 'string' && html.trim()) return { text: htmlText(html), plain: false }\n const children = props['children']\n return typeof children === 'string' ? { text: collapse(children), plain: true } : { text: '', plain: true }\n}\n\nconst childIdsOf = (node: PageMarkdownNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node?.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\n/** Read the facts a listing and the SEO check need from one page's node map. */\nexport function seoPageFacts(\n nodes: PageMarkdownNodes | null | undefined,\n options: { rootId?: string; maxChars?: number } = {},\n): SeoPageFacts {\n const empty: SeoPageFacts = {\n text: '',\n wordCount: 0,\n headings: [],\n h1s: [],\n images: [],\n imagesMissingAlt: [],\n contentRootId: null,\n }\n if (!nodes) return empty\n const contentRootId = pageContentRootId(nodes, options.rootId)\n if (!contentRootId) return empty\n\n const markdown = buildPageMarkdown({ nodes, rootId: options.rootId })\n const text = markdown.trim().slice(0, options.maxChars ?? SEO_PAGE_TEXT_MAX_CHARS)\n const headings: SeoHeading[] = []\n const images: SeoImage[] = []\n let lastText = ''\n\n const seen = new Set<string>()\n const visit = (id: string, depth: number): void => {\n if (depth > MAX_DEPTH || seen.has(id)) return\n const node = nodes[id]\n if (!node) return\n seen.add(id)\n if (isPageChromeNode(node)) return\n const props = (node.props ?? {}) as Record<string, unknown>\n const componentId = String(node.componentId ?? '')\n if (TEXT_COMPONENTS.has(componentId)) {\n const { text: own, plain } = textOf(props)\n const level = headingLevelOf(props)\n if (own) {\n if (level > 0) headings.push({ nodeId: id, level, text: own, editable: plain })\n lastText = own\n }\n } else if (componentId === 'markdown' && typeof props['content'] === 'string') {\n for (const line of String(props['content']).split('\\n')) {\n const match = /^(#{1,6})\\s+(.+)$/.exec(line.trim())\n if (match) {\n headings.push({ nodeId: null, level: match[1].length, text: collapse(match[2]), editable: false })\n }\n }\n lastText = collapse(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS)\n } else if (componentId === 'custom-html' && typeof (props['html'] ?? props['children']) === 'string') {\n const html = String(props['html'] ?? props['children'])\n for (const match of html.matchAll(/<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi)) {\n headings.push({ nodeId: null, level: Number(match[1]), text: htmlText(match[2]), editable: false })\n }\n } else if (componentId === 'image') {\n const src = String(props['src'] ?? '').trim()\n if (src) {\n images.push({\n nodeId: id,\n src,\n alt: collapse(String(props['alt'] ?? '')),\n context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS),\n })\n }\n }\n for (const childId of childIdsOf(node)) visit(childId, depth + 1)\n }\n visit(contentRootId, 0)\n\n return {\n text,\n wordCount: text ? text.split(/\\s+/).filter(Boolean).length : 0,\n headings,\n h1s: headings.filter((heading) => heading.level === 1),\n images,\n imagesMissingAlt: images.filter((image) => !image.alt),\n contentRootId,\n }\n}\n"],"names":["buildPageMarkdown","headingLevelOf","isPageChromeNode","pageContentRootId","SEO_PAGE_TEXT_MAX_CHARS","IMAGE_CONTEXT_MAX_CHARS","MAX_DEPTH","TEXT_COMPONENTS","Set","collapse","value","replace","trim","htmlText","html","textOf","props","text","plain","children","childIdsOf","node","Array","isArray","nodes","filter","id","seoPageFacts","options","empty","wordCount","headings","h1s","images","imagesMissingAlt","contentRootId","rootId","markdown","slice","maxChars","lastText","seen","visit","depth","has","add","componentId","String","own","level","push","nodeId","editable","line","split","match","exec","length","matchAll","Number","src","alt","context","childId","Boolean","heading","image"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,iBAAiB,EACjBC,cAAc,EACdC,gBAAgB,EAChBC,iBAAiB,QAGZ,qBAAiB;AAExB;;;;;;;;;;;;;;;CAeC,GAED,gGAAgG,GAChG,OAAO,MAAMC,0BAA0B,KAAK;AAE5C,gDAAgD,GAChD,MAAMC,0BAA0B;AAEhC,2EAA2E,GAC3E,MAAMC,YAAY;AAElB,oEAAoE,GACpE,MAAMC,kBAAuC,IAAIC,IAAI;IAAC;IAAiB;IAAiB;CAAkB;AA+B1G,MAAMC,WAAW,CAACC,QAA0BA,MAAMC,OAAO,CAAC,QAAQ,KAAKC,IAAI;AAE3E,gEAAgE,GAChE,SAASC,SAASC,IAAY;IAC5B,OAAOL,SAASK,KAAKH,OAAO,CAAC,YAAY,KAAKA,OAAO,CAAC,WAAW,KAAKA,OAAO,CAAC,UAAU;AAC1F;AAEA,iFAAiF,GACjF,SAASI,OAAOC,KAA8B;IAC5C,MAAMF,OAAOE,KAAK,CAAC,OAAO;IAC1B,IAAI,OAAOF,SAAS,YAAYA,KAAKF,IAAI,IAAI,OAAO;QAAEK,MAAMJ,SAASC;QAAOI,OAAO;IAAM;IACzF,MAAMC,WAAWH,KAAK,CAAC,WAAW;IAClC,OAAO,OAAOG,aAAa,WAAW;QAAEF,MAAMR,SAASU;QAAWD,OAAO;IAAK,IAAI;QAAED,MAAM;QAAIC,OAAO;IAAK;AAC5G;AAEA,MAAME,aAAa,CAACC,OAClBC,MAAMC,OAAO,CAACF,wBAAAA,KAAMG,KAAK,IAAI,CAACH,wBAAAA,KAAMG,KAAK,EAAeC,MAAM,CAAC,CAACC,KAAqB,OAAOA,OAAO,YAAY,EAAE;AAEnH,8EAA8E,GAC9E,OAAO,SAASC,aACdH,KAA2C,EAC3CI,UAAkD,CAAC,CAAC;QAgBdA;IAdtC,MAAMC,QAAsB;QAC1BZ,MAAM;QACNa,WAAW;QACXC,UAAU,EAAE;QACZC,KAAK,EAAE;QACPC,QAAQ,EAAE;QACVC,kBAAkB,EAAE;QACpBC,eAAe;IACjB;IACA,IAAI,CAACX,OAAO,OAAOK;IACnB,MAAMM,gBAAgBhC,kBAAkBqB,OAAOI,QAAQQ,MAAM;IAC7D,IAAI,CAACD,eAAe,OAAON;IAE3B,MAAMQ,WAAWrC,kBAAkB;QAAEwB;QAAOY,QAAQR,QAAQQ,MAAM;IAAC;IACnE,MAAMnB,OAAOoB,SAASzB,IAAI,GAAG0B,KAAK,CAAC,IAAGV,oBAAAA,QAAQW,QAAQ,YAAhBX,oBAAoBxB;IAC1D,MAAM2B,WAAyB,EAAE;IACjC,MAAME,SAAqB,EAAE;IAC7B,IAAIO,WAAW;IAEf,MAAMC,OAAO,IAAIjC;IACjB,MAAMkC,QAAQ,CAAChB,IAAYiB;YAMVtB,aACYA,mBAgByBL;QAtBpD,IAAI2B,QAAQrC,aAAamC,KAAKG,GAAG,CAAClB,KAAK;QACvC,MAAML,OAAOG,KAAK,CAACE,GAAG;QACtB,IAAI,CAACL,MAAM;QACXoB,KAAKI,GAAG,CAACnB;QACT,IAAIxB,iBAAiBmB,OAAO;QAC5B,MAAML,SAASK,cAAAA,KAAKL,KAAK,YAAVK,cAAc,CAAC;QAC9B,MAAMyB,cAAcC,QAAO1B,oBAAAA,KAAKyB,WAAW,YAAhBzB,oBAAoB;QAC/C,IAAId,gBAAgBqC,GAAG,CAACE,cAAc;YACpC,MAAM,EAAE7B,MAAM+B,GAAG,EAAE9B,KAAK,EAAE,GAAGH,OAAOC;YACpC,MAAMiC,QAAQhD,eAAee;YAC7B,IAAIgC,KAAK;gBACP,IAAIC,QAAQ,GAAGlB,SAASmB,IAAI,CAAC;oBAAEC,QAAQzB;oBAAIuB;oBAAOhC,MAAM+B;oBAAKI,UAAUlC;gBAAM;gBAC7EsB,WAAWQ;YACb;QACF,OAAO,IAAIF,gBAAgB,cAAc,OAAO9B,KAAK,CAAC,UAAU,KAAK,UAAU;YAC7E,KAAK,MAAMqC,QAAQN,OAAO/B,KAAK,CAAC,UAAU,EAAEsC,KAAK,CAAC,MAAO;gBACvD,MAAMC,QAAQ,oBAAoBC,IAAI,CAACH,KAAKzC,IAAI;gBAChD,IAAI2C,OAAO;oBACTxB,SAASmB,IAAI,CAAC;wBAAEC,QAAQ;wBAAMF,OAAOM,KAAK,CAAC,EAAE,CAACE,MAAM;wBAAExC,MAAMR,SAAS8C,KAAK,CAAC,EAAE;wBAAGH,UAAU;oBAAM;gBAClG;YACF;YACAZ,WAAW/B,SAASsC,OAAO/B,KAAK,CAAC,UAAU,GAAGsB,KAAK,CAAC,GAAGjC;QACzD,OAAO,IAAIyC,gBAAgB,iBAAiB,SAAQ9B,cAAAA,KAAK,CAAC,OAAO,YAAbA,cAAiBA,KAAK,CAAC,WAAW,MAAM,UAAU;gBAChFA;YAApB,MAAMF,OAAOiC,QAAO/B,eAAAA,KAAK,CAAC,OAAO,YAAbA,eAAiBA,KAAK,CAAC,WAAW;YACtD,KAAK,MAAMuC,SAASzC,KAAK4C,QAAQ,CAAC,sCAAuC;gBACvE3B,SAASmB,IAAI,CAAC;oBAAEC,QAAQ;oBAAMF,OAAOU,OAAOJ,KAAK,CAAC,EAAE;oBAAGtC,MAAMJ,SAAS0C,KAAK,CAAC,EAAE;oBAAGH,UAAU;gBAAM;YACnG;QACF,OAAO,IAAIN,gBAAgB,SAAS;gBACf9B;YAAnB,MAAM4C,MAAMb,QAAO/B,aAAAA,KAAK,CAAC,MAAM,YAAZA,aAAgB,IAAIJ,IAAI;YAC3C,IAAIgD,KAAK;oBAIgB5C;gBAHvBiB,OAAOiB,IAAI,CAAC;oBACVC,QAAQzB;oBACRkC;oBACAC,KAAKpD,SAASsC,QAAO/B,aAAAA,KAAK,CAAC,MAAM,YAAZA,aAAgB;oBACrC8C,SAAStB,SAASF,KAAK,CAAC,GAAGjC;gBAC7B;YACF;QACF;QACA,KAAK,MAAM0D,WAAW3C,WAAWC,MAAOqB,MAAMqB,SAASpB,QAAQ;IACjE;IACAD,MAAMP,eAAe;IAErB,OAAO;QACLlB;QACAa,WAAWb,OAAOA,KAAKqC,KAAK,CAAC,OAAO7B,MAAM,CAACuC,SAASP,MAAM,GAAG;QAC7D1B;QACAC,KAAKD,SAASN,MAAM,CAAC,CAACwC,UAAYA,QAAQhB,KAAK,KAAK;QACpDhB;QACAC,kBAAkBD,OAAOR,MAAM,CAAC,CAACyC,QAAU,CAACA,MAAML,GAAG;QACrD1B;IACF;AACF"}
@@ -0,0 +1,126 @@
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 SeoAuditPage, type SeoAuditSite } from './seo-audit';
18
+ /**
19
+ * Read a site for the SEO check (`seo-audit.ts`) the way its sitemap lists
20
+ * it: every screen the routing map publishes, less the template screens, the
21
+ * status pages and every screen that is not public — through the same shared
22
+ * predicates the tenant's `sitemap.xml` and `/llms.txt` apply, so the check
23
+ * covers exactly the pages a crawler is handed. Then each page's published
24
+ * version, and the shared layouts, for the links that decide which pages
25
+ * nothing points to.
26
+ *
27
+ * It knows nothing about the Firestore SDK. A caller hands it a
28
+ * {@link SeoScanStore} — the Admin SDK's `Firestore` satisfies it — and the
29
+ * template screen ids, which the tenant runtime resolves; so this module stays
30
+ * pure enough to import from anywhere, and a spec hands it a fake.
31
+ *
32
+ * Reads: one projected query of the screens, one of the layouts, and one
33
+ * version document per checked page and per layout — at most
34
+ * {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2.
35
+ */
36
+ /** Shared layouts read for the links they carry. */
37
+ export declare const SEO_SCAN_LAYOUT_LIMIT = 20;
38
+ export interface SeoScanSnapshot {
39
+ readonly exists: boolean;
40
+ get(field: string): unknown;
41
+ }
42
+ export interface SeoScanQueryDocument {
43
+ readonly id: string;
44
+ data(): unknown;
45
+ get(field: string): unknown;
46
+ readonly ref: SeoScanDocumentRef;
47
+ }
48
+ export interface SeoScanQuery {
49
+ limit(count: number): SeoScanQuery;
50
+ get(): Promise<{
51
+ readonly docs: readonly SeoScanQueryDocument[];
52
+ }>;
53
+ }
54
+ export interface SeoScanCollection {
55
+ select(...fields: string[]): SeoScanQuery;
56
+ doc(id: string): SeoScanDocumentRef;
57
+ }
58
+ export interface SeoScanDocumentRef {
59
+ collection(name: string): SeoScanCollection;
60
+ get(): Promise<SeoScanSnapshot>;
61
+ }
62
+ /** What the scan reads through; the Admin SDK's `Firestore` is one. */
63
+ export interface SeoScanStore {
64
+ collection(name: string): SeoScanCollection;
65
+ }
66
+ /** The host document fields the scan and the site findings read. */
67
+ export interface SeoSiteHost {
68
+ displayName?: string;
69
+ screens?: Record<string, string>;
70
+ errorScreens?: Record<string, string>;
71
+ notFoundScreenId?: string | null;
72
+ seo?: {
73
+ title?: string;
74
+ separator?: string;
75
+ discourageSearchEngines?: boolean;
76
+ entity?: {
77
+ name?: string;
78
+ description?: string;
79
+ };
80
+ agent?: {
81
+ whenToUse?: string;
82
+ };
83
+ };
84
+ }
85
+ /** The screen document fields the scan reads. */
86
+ interface SeoScreenDocument {
87
+ displayName?: string;
88
+ description?: string;
89
+ versionId?: string;
90
+ visibility?: unknown;
91
+ deletedAt?: unknown;
92
+ seo?: {
93
+ title?: string;
94
+ description?: string;
95
+ breadcrumb?: string;
96
+ image?: string;
97
+ imageAlt?: string;
98
+ };
99
+ }
100
+ export interface SeoScannedPage extends SeoAuditPage {
101
+ /** The listing as the screen document stores it: a title's variables unresolved. */
102
+ storedSeo: NonNullable<SeoScreenDocument['seo']>;
103
+ /** The page's node map as checked. */
104
+ nodes: Record<string, unknown> | null;
105
+ }
106
+ export interface SeoSiteScan {
107
+ pages: SeoScannedPage[];
108
+ /** Published pages past {@link SEO_AUDIT_MAX_PAGES}. */
109
+ skipped: number;
110
+ /** Customer-safe lines: a keyword line naming a page the site does not publish. */
111
+ notes: string[];
112
+ }
113
+ /** The site as the check reads it, from its host document. */
114
+ export declare function seoAuditSiteOf(host: SeoSiteHost): SeoAuditSite;
115
+ /**
116
+ * Read `hostId`'s published pages for the check.
117
+ *
118
+ * `keywords` are the target keyword lines a person typed (`/path: a, b`);
119
+ * `templateScreenIds` are the screens a collection, the store or the host
120
+ * renders other URLs through, which are not pages of their own.
121
+ */
122
+ export declare function scanSeoSite(store: SeoScanStore, hostId: string, host: SeoSiteHost, options?: {
123
+ keywords?: unknown;
124
+ templateScreenIds?: Iterable<string>;
125
+ }): Promise<SeoSiteScan>;
126
+ export {};
@@ -0,0 +1,162 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { screenRoutePathToUrl } from "./screen-route.js";
18
+ import { nodesReferenceScreen } from "./screen-link-value.js";
19
+ import { isScreenIndexable, isSearchDiscouraged, statusPageScreenIds } from "./search-indexing.js";
20
+ import { SEO_AUDIT_MAX_PAGES, parseSeoKeywordLines, seoNormalizePath } from "./seo-audit.js";
21
+ import { seoPageFacts } from "./seo-page-facts.js";
22
+ import { resolveSeoTitleVariables } from "./seo-title-variables.js";
23
+ import { decodeStoredNodes } from "./stored-nodes.js";
24
+ /**
25
+ * Read a site for the SEO check (`seo-audit.ts`) the way its sitemap lists
26
+ * it: every screen the routing map publishes, less the template screens, the
27
+ * status pages and every screen that is not public — through the same shared
28
+ * predicates the tenant's `sitemap.xml` and `/llms.txt` apply, so the check
29
+ * covers exactly the pages a crawler is handed. Then each page's published
30
+ * version, and the shared layouts, for the links that decide which pages
31
+ * nothing points to.
32
+ *
33
+ * It knows nothing about the Firestore SDK. A caller hands it a
34
+ * {@link SeoScanStore} — the Admin SDK's `Firestore` satisfies it — and the
35
+ * template screen ids, which the tenant runtime resolves; so this module stays
36
+ * pure enough to import from anywhere, and a spec hands it a fake.
37
+ *
38
+ * Reads: one projected query of the screens, one of the layouts, and one
39
+ * version document per checked page and per layout — at most
40
+ * {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2.
41
+ */ /** Shared layouts read for the links they carry. */ export const SEO_SCAN_LAYOUT_LIMIT = 20;
42
+ /** Version documents read at once. */ const READ_CHUNK = 10;
43
+ /** Screen documents the projected query reads. */ const SCREEN_SCAN_LIMIT = 1000;
44
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
45
+ /** The site as the check reads it, from its host document. */ export function seoAuditSiteOf(host) {
46
+ var _ref, _ref1;
47
+ var _host_seo, _host_seo1;
48
+ return {
49
+ discouraged: isSearchDiscouraged(host),
50
+ entity: (_ref = (_host_seo = host.seo) == null ? void 0 : _host_seo.entity) != null ? _ref : {},
51
+ agent: (_ref1 = (_host_seo1 = host.seo) == null ? void 0 : _host_seo1.agent) != null ? _ref1 : {}
52
+ };
53
+ }
54
+ /** A version's node map and root, or `null` when it cannot be read. */ async function readVersionNodes(parent, versionId) {
55
+ if (!versionId) return null;
56
+ const snapshot = await parent.collection('versions').doc(versionId).get();
57
+ if (!snapshot.exists) return null;
58
+ const nodes = decodeStoredNodes(snapshot.get('nodes'));
59
+ if (!nodes) return null;
60
+ const rootId = snapshot.get('rootId');
61
+ return _extends({
62
+ nodes
63
+ }, typeof rootId === 'string' ? {
64
+ rootId
65
+ } : {});
66
+ }
67
+ async function inChunks(items, run) {
68
+ for(let start = 0; start < items.length; start += READ_CHUNK){
69
+ await Promise.all(items.slice(start, start + READ_CHUNK).map(run));
70
+ }
71
+ }
72
+ /**
73
+ * Read `hostId`'s published pages for the check.
74
+ *
75
+ * `keywords` are the target keyword lines a person typed (`/path: a, b`);
76
+ * `templateScreenIds` are the screens a collection, the store or the host
77
+ * renders other URLs through, which are not pages of their own.
78
+ */ export async function scanSeoSite(store, hostId, host, options = {}) {
79
+ var _host_screens, _options_templateScreenIds;
80
+ var _host_seo;
81
+ const hostRef = store.collection('hosts').doc(hostId);
82
+ const routing = (_host_screens = host.screens) != null ? _host_screens : {};
83
+ const [screenDocs, layoutDocs] = await Promise.all([
84
+ hostRef.collection('screens').select('visibility', 'seo', 'description', 'displayName', 'versionId', 'deletedAt').limit(SCREEN_SCAN_LIMIT).get(),
85
+ hostRef.collection('layouts').select('versionId').limit(SEO_SCAN_LAYOUT_LIMIT).get()
86
+ ]);
87
+ const screens = new Map(screenDocs.docs.map((doc)=>{
88
+ var _doc_data;
89
+ return [
90
+ doc.id,
91
+ (_doc_data = doc.data()) != null ? _doc_data : {}
92
+ ];
93
+ }));
94
+ const excluded = new Set([
95
+ ...statusPageScreenIds(host),
96
+ ...(_options_templateScreenIds = options.templateScreenIds) != null ? _options_templateScreenIds : []
97
+ ]);
98
+ const published = Object.entries(routing).filter(([screenId])=>!excluded.has(screenId)).filter(([screenId])=>{
99
+ const screen = screens.get(screenId);
100
+ return Boolean(screen) && !(screen == null ? void 0 : screen.deletedAt) && isScreenIndexable(screen);
101
+ }).map(([screenId, route])=>({
102
+ screenId,
103
+ path: screenRoutePathToUrl(String(route))
104
+ })).sort((a, b)=>a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
105
+ const checked = published.slice(0, SEO_AUDIT_MAX_PAGES);
106
+ const versions = new Map();
107
+ await inChunks(checked, async ({ screenId })=>{
108
+ var _screens_get;
109
+ versions.set(screenId, await readVersionNodes(hostRef.collection('screens').doc(screenId), str((_screens_get = screens.get(screenId)) == null ? void 0 : _screens_get.versionId)));
110
+ });
111
+ const layoutNodes = [];
112
+ await inChunks(layoutDocs.docs.filter((doc)=>str(doc.get('versionId'))), async (doc)=>{
113
+ const version = await readVersionNodes(doc.ref, str(doc.get('versionId')));
114
+ if (version) layoutNodes.push(version.nodes);
115
+ });
116
+ const keywordsByPath = parseSeoKeywordLines(options.keywords);
117
+ const checkedPaths = new Set(checked.map((page)=>seoNormalizePath(page.path)));
118
+ const notes = Object.keys(keywordsByPath).filter((path)=>!checkedPaths.has(path)).map((path)=>`Keywords for ${path} were not used: no checked page is published at that address.`);
119
+ const siteName = str((_host_seo = host.seo) == null ? void 0 : _host_seo.title) || str(host.displayName);
120
+ const pages = checked.map(({ screenId, path })=>{
121
+ var _screens_get, _versions_get, _screen_seo, _keywordsByPath_seoNormalizePath, _ref;
122
+ var _host_seo;
123
+ const screen = (_screens_get = screens.get(screenId)) != null ? _screens_get : {};
124
+ const version = (_versions_get = versions.get(screenId)) != null ? _versions_get : null;
125
+ const storedSeo = (_screen_seo = screen.seo) != null ? _screen_seo : {};
126
+ const linkedFrom = layoutNodes.some((nodes)=>nodesReferenceScreen(nodes, screenId)) || checked.some((other)=>{
127
+ var _ref;
128
+ var _versions_get;
129
+ return other.screenId !== screenId && nodesReferenceScreen((_ref = (_versions_get = versions.get(other.screenId)) == null ? void 0 : _versions_get.nodes) != null ? _ref : null, screenId);
130
+ });
131
+ return {
132
+ screenId,
133
+ path,
134
+ name: str(screen.displayName) || path,
135
+ versionId: str(screen.versionId) || null,
136
+ // Judged as the head renders it: `{{page.name}} – {{site.name}}` on two
137
+ // pages is two different titles, and its length is the rendered one.
138
+ seo: _extends({}, storedSeo, {
139
+ title: resolveSeoTitleVariables(storedSeo.title, {
140
+ 'page.name': str(screen.displayName),
141
+ 'site.name': siteName,
142
+ 'site.separator': str((_host_seo = host.seo) == null ? void 0 : _host_seo.separator)
143
+ })
144
+ }),
145
+ storedSeo,
146
+ description: str(screen.description),
147
+ facts: seoPageFacts(version == null ? void 0 : version.nodes, {
148
+ rootId: version == null ? void 0 : version.rootId
149
+ }),
150
+ linkedFrom,
151
+ keywords: (_keywordsByPath_seoNormalizePath = keywordsByPath[seoNormalizePath(path)]) != null ? _keywordsByPath_seoNormalizePath : [],
152
+ nodes: (_ref = version == null ? void 0 : version.nodes) != null ? _ref : null
153
+ };
154
+ });
155
+ return {
156
+ pages,
157
+ skipped: Math.max(0, published.length - checked.length),
158
+ notes
159
+ };
160
+ }
161
+
162
+ //# sourceMappingURL=seo-site-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-site-scan.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 { screenRoutePathToUrl } from './screen-route'\nimport { nodesReferenceScreen } from './screen-link-value'\nimport {\n isScreenIndexable,\n isSearchDiscouraged,\n statusPageScreenIds,\n type SearchIndexingScreen,\n} from './search-indexing'\nimport {\n SEO_AUDIT_MAX_PAGES,\n parseSeoKeywordLines,\n seoNormalizePath,\n type SeoAuditPage,\n type SeoAuditSite,\n} from './seo-audit'\nimport { seoPageFacts } from './seo-page-facts'\nimport type { PageMarkdownNodes } from './page-markdown'\nimport { resolveSeoTitleVariables } from './seo-title-variables'\nimport { decodeStoredNodes } from './stored-nodes'\n\n/**\n * Read a site for the SEO check (`seo-audit.ts`) the way its sitemap lists\n * it: every screen the routing map publishes, less the template screens, the\n * status pages and every screen that is not public — through the same shared\n * predicates the tenant's `sitemap.xml` and `/llms.txt` apply, so the check\n * covers exactly the pages a crawler is handed. Then each page's published\n * version, and the shared layouts, for the links that decide which pages\n * nothing points to.\n *\n * It knows nothing about the Firestore SDK. A caller hands it a\n * {@link SeoScanStore} — the Admin SDK's `Firestore` satisfies it — and the\n * template screen ids, which the tenant runtime resolves; so this module stays\n * pure enough to import from anywhere, and a spec hands it a fake.\n *\n * Reads: one projected query of the screens, one of the layouts, and one\n * version document per checked page and per layout — at most\n * {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2.\n */\n\n/** Shared layouts read for the links they carry. */\nexport const SEO_SCAN_LAYOUT_LIMIT = 20\n\n/** Version documents read at once. */\nconst READ_CHUNK = 10\n\n/** Screen documents the projected query reads. */\nconst SCREEN_SCAN_LIMIT = 1000\n\n/* ---- The store, structurally ---------------------------------------------- */\n\nexport interface SeoScanSnapshot {\n readonly exists: boolean\n get(field: string): unknown\n}\n\nexport interface SeoScanQueryDocument {\n readonly id: string\n data(): unknown\n get(field: string): unknown\n readonly ref: SeoScanDocumentRef\n}\n\nexport interface SeoScanQuery {\n limit(count: number): SeoScanQuery\n get(): Promise<{ readonly docs: readonly SeoScanQueryDocument[] }>\n}\n\nexport interface SeoScanCollection {\n select(...fields: string[]): SeoScanQuery\n doc(id: string): SeoScanDocumentRef\n}\n\nexport interface SeoScanDocumentRef {\n collection(name: string): SeoScanCollection\n get(): Promise<SeoScanSnapshot>\n}\n\n/** What the scan reads through; the Admin SDK's `Firestore` is one. */\nexport interface SeoScanStore {\n collection(name: string): SeoScanCollection\n}\n\n/* ---- What it reads --------------------------------------------------------- */\n\n/** The host document fields the scan and the site findings read. */\nexport interface SeoSiteHost {\n displayName?: string\n screens?: Record<string, string>\n errorScreens?: Record<string, string>\n notFoundScreenId?: string | null\n seo?: {\n title?: string\n separator?: string\n discourageSearchEngines?: boolean\n entity?: { name?: string; description?: string }\n agent?: { whenToUse?: string }\n }\n}\n\n/** The screen document fields the scan reads. */\ninterface SeoScreenDocument {\n displayName?: string\n description?: string\n versionId?: string\n visibility?: unknown\n deletedAt?: unknown\n seo?: {\n title?: string\n description?: string\n breadcrumb?: string\n image?: string\n imageAlt?: string\n }\n}\n\nexport interface SeoScannedPage extends SeoAuditPage {\n /** The listing as the screen document stores it: a title's variables unresolved. */\n storedSeo: NonNullable<SeoScreenDocument['seo']>\n /** The page's node map as checked. */\n nodes: Record<string, unknown> | null\n}\n\nexport interface SeoSiteScan {\n pages: SeoScannedPage[]\n /** Published pages past {@link SEO_AUDIT_MAX_PAGES}. */\n skipped: number\n /** Customer-safe lines: a keyword line naming a page the site does not publish. */\n notes: string[]\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The site as the check reads it, from its host document. */\nexport function seoAuditSiteOf(host: SeoSiteHost): SeoAuditSite {\n return {\n discouraged: isSearchDiscouraged(host),\n entity: host.seo?.entity ?? {},\n agent: host.seo?.agent ?? {},\n }\n}\n\n/** A version's node map and root, or `null` when it cannot be read. */\nasync function readVersionNodes(\n parent: SeoScanDocumentRef,\n versionId: string,\n): Promise<{ nodes: Record<string, unknown>; rootId?: string } | null> {\n if (!versionId) return null\n const snapshot = await parent.collection('versions').doc(versionId).get()\n if (!snapshot.exists) return null\n const nodes = decodeStoredNodes<Record<string, unknown>>(snapshot.get('nodes'))\n if (!nodes) return null\n const rootId = snapshot.get('rootId')\n return { nodes, ...(typeof rootId === 'string' ? { rootId } : {}) }\n}\n\nasync function inChunks<T>(items: readonly T[], run: (item: T) => Promise<void>): Promise<void> {\n for (let start = 0; start < items.length; start += READ_CHUNK) {\n await Promise.all(items.slice(start, start + READ_CHUNK).map(run))\n }\n}\n\n/**\n * Read `hostId`'s published pages for the check.\n *\n * `keywords` are the target keyword lines a person typed (`/path: a, b`);\n * `templateScreenIds` are the screens a collection, the store or the host\n * renders other URLs through, which are not pages of their own.\n */\nexport async function scanSeoSite(\n store: SeoScanStore,\n hostId: string,\n host: SeoSiteHost,\n options: { keywords?: unknown; templateScreenIds?: Iterable<string> } = {},\n): Promise<SeoSiteScan> {\n const hostRef = store.collection('hosts').doc(hostId)\n const routing = host.screens ?? {}\n const [screenDocs, layoutDocs] = await Promise.all([\n hostRef\n .collection('screens')\n .select('visibility', 'seo', 'description', 'displayName', 'versionId', 'deletedAt')\n .limit(SCREEN_SCAN_LIMIT)\n .get(),\n hostRef.collection('layouts').select('versionId').limit(SEO_SCAN_LAYOUT_LIMIT).get(),\n ])\n const screens = new Map(screenDocs.docs.map((doc) => [doc.id, (doc.data() ?? {}) as SeoScreenDocument]))\n const excluded = new Set<string>([...statusPageScreenIds(host), ...(options.templateScreenIds ?? [])])\n const published = Object.entries(routing)\n .filter(([screenId]) => !excluded.has(screenId))\n .filter(([screenId]) => {\n const screen = screens.get(screenId)\n return Boolean(screen) && !screen?.deletedAt && isScreenIndexable(screen as SearchIndexingScreen)\n })\n .map(([screenId, route]) => ({ screenId, path: screenRoutePathToUrl(String(route)) }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))\n const checked = published.slice(0, SEO_AUDIT_MAX_PAGES)\n\n const versions = new Map<string, { nodes: Record<string, unknown>; rootId?: string } | null>()\n await inChunks(checked, async ({ screenId }) => {\n versions.set(\n screenId,\n await readVersionNodes(hostRef.collection('screens').doc(screenId), str(screens.get(screenId)?.versionId)),\n )\n })\n const layoutNodes: Record<string, unknown>[] = []\n await inChunks(\n layoutDocs.docs.filter((doc) => str(doc.get('versionId'))),\n async (doc) => {\n const version = await readVersionNodes(doc.ref, str(doc.get('versionId')))\n if (version) layoutNodes.push(version.nodes)\n },\n )\n\n const keywordsByPath = parseSeoKeywordLines(options.keywords)\n const checkedPaths = new Set(checked.map((page) => seoNormalizePath(page.path)))\n const notes = Object.keys(keywordsByPath)\n .filter((path) => !checkedPaths.has(path))\n .map((path) => `Keywords for ${path} were not used: no checked page is published at that address.`)\n\n const siteName = str(host.seo?.title) || str(host.displayName)\n const pages: SeoScannedPage[] = checked.map(({ screenId, path }) => {\n const screen = screens.get(screenId) ?? {}\n const version = versions.get(screenId) ?? null\n const storedSeo = screen.seo ?? {}\n const linkedFrom =\n layoutNodes.some((nodes) => nodesReferenceScreen(nodes, screenId)) ||\n checked.some(\n (other) =>\n other.screenId !== screenId &&\n nodesReferenceScreen(versions.get(other.screenId)?.nodes ?? null, screenId),\n )\n return {\n screenId,\n path,\n name: str(screen.displayName) || path,\n versionId: str(screen.versionId) || null,\n // Judged as the head renders it: `{{page.name}} – {{site.name}}` on two\n // pages is two different titles, and its length is the rendered one.\n seo: {\n ...storedSeo,\n title: resolveSeoTitleVariables(storedSeo.title, {\n 'page.name': str(screen.displayName),\n 'site.name': siteName,\n 'site.separator': str(host.seo?.separator),\n }),\n },\n storedSeo,\n description: str(screen.description),\n facts: seoPageFacts(version?.nodes as PageMarkdownNodes | undefined, { rootId: version?.rootId }),\n linkedFrom,\n keywords: keywordsByPath[seoNormalizePath(path)] ?? [],\n nodes: version?.nodes ?? null,\n }\n })\n return { pages, skipped: Math.max(0, published.length - checked.length), notes }\n}\n"],"names":["screenRoutePathToUrl","nodesReferenceScreen","isScreenIndexable","isSearchDiscouraged","statusPageScreenIds","SEO_AUDIT_MAX_PAGES","parseSeoKeywordLines","seoNormalizePath","seoPageFacts","resolveSeoTitleVariables","decodeStoredNodes","SEO_SCAN_LAYOUT_LIMIT","READ_CHUNK","SCREEN_SCAN_LIMIT","str","value","trim","seoAuditSiteOf","host","discouraged","entity","seo","agent","readVersionNodes","parent","versionId","snapshot","collection","doc","get","exists","nodes","rootId","inChunks","items","run","start","length","Promise","all","slice","map","scanSeoSite","store","hostId","options","hostRef","routing","screens","screenDocs","layoutDocs","select","limit","Map","docs","id","data","excluded","Set","templateScreenIds","published","Object","entries","filter","screenId","has","screen","Boolean","deletedAt","route","path","String","sort","a","b","checked","versions","set","layoutNodes","version","ref","push","keywordsByPath","keywords","checkedPaths","page","notes","keys","siteName","title","displayName","pages","storedSeo","linkedFrom","some","other","name","separator","description","facts","skipped","Math","max"],"mappings":"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"}
@@ -71,7 +71,7 @@
71
71
  {
72
72
  name: 'page.name',
73
73
  label: 'Page name',
74
- description: 'What this page is called — a screen’s display name.'
74
+ description: 'What this page is called — its display name.'
75
75
  },
76
76
  {
77
77
  name: 'site.separator',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-title-variables.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The variables a page title may be written in terms of, and how a written\n * title becomes a rendered one.\n *\n * ## Why this exists\n *\n * A title is verbatim (AGL-1341, and that rule is right — see `seo-title`), so\n * an author who wants the brand in a title TYPES it. Fifty screens on the\n * marketing site end in a literal ` | Aglyn`, which means the site name and\n * the character in front of it are stored fifty times, and changing either one\n * is fifty edits that nothing reminds you to make. `seo.separator` already\n * exists as a setting and every one of those titles ignores it.\n *\n * So a title may now be WRITTEN in terms of what it depends on:\n *\n * ```\n * {{page.name}} {{site.separator}} {{site.name}}\n * ```\n *\n * and a site-name change is one edit in Host setup → SEO.\n *\n * ## ⛔ No rendered title changes because this shipped\n *\n * The whole feature is opt-in at the string level: a title with no `{{` in it\n * is returned unchanged, character for character, by the same code path that\n * returned it before. Nothing migrates the fifty existing titles — a migration\n * could not be output-preserving anyway, because ` | Aglyn` is not what\n * `{{site.separator}}{{site.name}}` resolves to on a host whose separator is\n * `-` and whose site title is the long form. They keep working as literals\n * until somebody decides otherwise, one at a time.\n *\n * `DEFAULT_TITLE_PATTERN` is held to the same standard: it is the composition\n * `resolveSeoTitle` already performed for a page with no authored title, said\n * in tokens instead of in code, and `seo-title.spec` pins that the two agree.\n *\n * ## The grammar is `{{…}}`, not `[…]`\n *\n * Because the platform already has one: the besigner's attribute fields, the\n * markdown blocks and the campaign editors all serialize `{{…}}` and the\n * people writing SEO titles are the same people who write those. A second\n * grammar for the same idea is a second thing to learn and a second parser to\n * keep in step.\n *\n * ## What is NOT a variable here\n *\n * Anything per-visitor or per-request. A `<title>` is rendered into a cached,\n * shared HTML document and read by crawlers, so a variable that resolved to\n * something about the reader would be wrong for everyone it was cached for.\n * These three are properties of the page and the site, both of which are the\n * same for everybody who asks for that URL.\n */\n\n/** A variable's name, as it is written between the braces. */\nexport type SeoTitleVariableName = 'page.name' | 'site.name' | 'site.separator'\n\nexport interface SeoTitleVariableDefinition {\n name: SeoTitleVariableName\n /** What the insert menu calls it. */\n label: string\n /** One line under the label, saying where the value comes from. */\n description: string\n}\n\n/**\n * Every variable a title may use, in the order a picker should offer them —\n * which is the order they appear in the default pattern, so the menu reads as\n * the sentence it builds.\n */\nexport const SEO_TITLE_VARIABLES: readonly SeoTitleVariableDefinition[] = [\n {\n name: 'page.name',\n label: 'Page name',\n description: 'What this page is called — a screen’s display name.',\n },\n {\n name: 'site.separator',\n label: 'Separator',\n description: 'The character from Host setup → SEO that joins the two.',\n },\n {\n name: 'site.name',\n label: 'Site name',\n description: 'The site title from Host setup → SEO.',\n },\n]\n\n/**\n * The composition every untitled page gets when the site sets no pattern of\n * its own.\n *\n * This is `resolveSeoTitle`'s previous hard-coded composition, written out:\n * name, separator padded by a space on each side, site title. It is a constant\n * rather than a string built at the call site so that the setting's default,\n * the placeholder the editor shows and the value the renderer falls back to\n * are one thing.\n */\nexport const DEFAULT_TITLE_PATTERN = '{{page.name}} {{site.separator}} {{site.name}}'\n\n/** The values the variables stand for, for one page of one site. */\nexport interface SeoTitleVariableValues {\n 'page.name'?: string | null\n 'site.name'?: string | null\n 'site.separator'?: string | null\n}\n\n/** Trimmed, or `''` for anything that is not a live string. */\nfunction clean(value?: string | null): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * The separator a site uses when it has set none.\n *\n * An en dash, which is what `seo-title` has padded around since AGL-1341. It\n * lives here because `{{site.separator}}` has to resolve to the SAME character\n * the code composed with — a variable that stood for \"the separator, unless\n * it is unset, in which case nothing\" would silently change the title of every\n * untitled page on every site that never filled the field in.\n */\nexport const DEFAULT_TITLE_SEPARATOR = '\\u2013'\n\n/** The site's separator, defaulted the one way everything defaults it. */\nexport function seoSeparatorOrDefault(separator?: string | null): string {\n return clean(separator) || DEFAULT_TITLE_SEPARATOR\n}\n\n/**\n * Whether a written value uses any variable at all.\n *\n * The cheap gate in front of every path here: a title with no `{{` is returned\n * by identity, which is what makes \"nothing renders differently\" a property of\n * the code rather than a claim about the data.\n */\nexport function hasSeoTitleVariables(value?: string | null): boolean {\n return typeof value === 'string' && value.includes('{{')\n}\n\n/** Whether a name is one this catalog defines. */\nexport function isSeoTitleVariable(name: string): name is SeoTitleVariableName {\n return SEO_TITLE_VARIABLES.some((variable) => variable.name === name)\n}\n\n/**\n * `{{ name }}` — the whole token, and the name inside it with its padding\n * trimmed, so a human who typed a space after the braces is not punished for\n * it.\n */\nconst TOKEN = /\\{\\{\\s*([a-zA-Z][\\w.]*)\\s*\\}\\}/g\n\ninterface Part {\n /** What renders here, once the variable is resolved. */\n text: string\n /** The variable this came from, if it came from one. */\n variable?: string\n}\n\n/** The written string as literal runs and variable slots, in order. */\nfunction partsOf(source: string, values: SeoTitleVariableValues): Part[] {\n const parts: Part[] = []\n let at = 0\n TOKEN.lastIndex = 0\n for (let match = TOKEN.exec(source); match; match = TOKEN.exec(source)) {\n if (match.index > at) parts.push({ text: source.slice(at, match.index) })\n const name = match[1]\n parts.push({\n variable: name,\n // An unknown variable renders AS ITSELF — see the resolver's docblock.\n text: !isSeoTitleVariable(name)\n ? match[0]\n : name === 'site.separator'\n ? seoSeparatorOrDefault(values[name])\n : clean(values[name]),\n })\n at = match.index + match[0].length\n }\n if (at < source.length) parts.push({ text: source.slice(at) })\n return parts\n}\n\n/**\n * Resolve a written title into the one that is rendered.\n *\n * ## An unknown variable is left alone\n *\n * `{{page.subtitle}}` renders as itself, visibly, rather than as an empty gap.\n * A typo that vanished would produce a title that is silently missing a piece,\n * and the author's next look at the live page would show them a sentence that\n * merely reads a bit short. Leaving it visible is the version somebody\n * notices. The editor's picker and autocomplete are what keep it from\n * happening; this is what happens when they are bypassed.\n *\n * ## A separator needs something on BOTH sides\n *\n * This is `resolveSeoTitle`'s own rule (AGL-1341) and it survives the move into\n * a pattern, because it is the rule that stops `<title>` from beginning with a\n * dash on a page whose name is missing. A `{{site.separator}}` with nothing\n * rendered on one side of it is dropped, whatever order the pattern put the\n * pieces in — which is why the parts are walked rather than regex-substituted:\n * a substitution cannot see its neighbours.\n *\n * ## An empty variable takes its spacing with it\n *\n * Otherwise a site with no name set renders `Pricing ` — the gap where the\n * missing piece was, and a trailing separator's worth of whitespace. Runs\n * collapse to one space and the result is trimmed.\n */\nexport function resolveSeoTitleVariables(\n written: string | null | undefined,\n values: SeoTitleVariableValues,\n): string {\n const source = typeof written === 'string' ? written : ''\n if (!hasSeoTitleVariables(source)) return source\n\n const parts = partsOf(source, values)\n const rendered = parts.map((part) => part.text)\n // A separator survives only where something renders on each side of it.\n parts.forEach((part, index) => {\n if (part.variable !== 'site.separator') return\n const live = (from: number, to: number) =>\n rendered.slice(from, to).some((text) => text.trim())\n if (!live(0, index) || !live(index + 1, rendered.length)) rendered[index] = ''\n })\n\n return rendered.join('').replace(/\\s{2,}/g, ' ').trim()\n}\n"],"names":["SEO_TITLE_VARIABLES","name","label","description","DEFAULT_TITLE_PATTERN","clean","value","trim","DEFAULT_TITLE_SEPARATOR","seoSeparatorOrDefault","separator","hasSeoTitleVariables","includes","isSeoTitleVariable","some","variable","TOKEN","partsOf","source","values","parts","at","lastIndex","match","exec","index","push","text","slice","length","resolveSeoTitleVariables","written","rendered","map","part","forEach","live","from","to","join","replace"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-title-variables.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The variables a page title may be written in terms of, and how a written\n * title becomes a rendered one.\n *\n * ## Why this exists\n *\n * A title is verbatim (AGL-1341, and that rule is right — see `seo-title`), so\n * an author who wants the brand in a title TYPES it. Fifty screens on the\n * marketing site end in a literal ` | Aglyn`, which means the site name and\n * the character in front of it are stored fifty times, and changing either one\n * is fifty edits that nothing reminds you to make. `seo.separator` already\n * exists as a setting and every one of those titles ignores it.\n *\n * So a title may now be WRITTEN in terms of what it depends on:\n *\n * ```\n * {{page.name}} {{site.separator}} {{site.name}}\n * ```\n *\n * and a site-name change is one edit in Host setup → SEO.\n *\n * ## ⛔ No rendered title changes because this shipped\n *\n * The whole feature is opt-in at the string level: a title with no `{{` in it\n * is returned unchanged, character for character, by the same code path that\n * returned it before. Nothing migrates the fifty existing titles — a migration\n * could not be output-preserving anyway, because ` | Aglyn` is not what\n * `{{site.separator}}{{site.name}}` resolves to on a host whose separator is\n * `-` and whose site title is the long form. They keep working as literals\n * until somebody decides otherwise, one at a time.\n *\n * `DEFAULT_TITLE_PATTERN` is held to the same standard: it is the composition\n * `resolveSeoTitle` already performed for a page with no authored title, said\n * in tokens instead of in code, and `seo-title.spec` pins that the two agree.\n *\n * ## The grammar is `{{…}}`, not `[…]`\n *\n * Because the platform already has one: the besigner's attribute fields, the\n * markdown blocks and the campaign editors all serialize `{{…}}` and the\n * people writing SEO titles are the same people who write those. A second\n * grammar for the same idea is a second thing to learn and a second parser to\n * keep in step.\n *\n * ## What is NOT a variable here\n *\n * Anything per-visitor or per-request. A `<title>` is rendered into a cached,\n * shared HTML document and read by crawlers, so a variable that resolved to\n * something about the reader would be wrong for everyone it was cached for.\n * These three are properties of the page and the site, both of which are the\n * same for everybody who asks for that URL.\n */\n\n/** A variable's name, as it is written between the braces. */\nexport type SeoTitleVariableName = 'page.name' | 'site.name' | 'site.separator'\n\nexport interface SeoTitleVariableDefinition {\n name: SeoTitleVariableName\n /** What the insert menu calls it. */\n label: string\n /** One line under the label, saying where the value comes from. */\n description: string\n}\n\n/**\n * Every variable a title may use, in the order a picker should offer them —\n * which is the order they appear in the default pattern, so the menu reads as\n * the sentence it builds.\n */\nexport const SEO_TITLE_VARIABLES: readonly SeoTitleVariableDefinition[] = [\n {\n name: 'page.name',\n label: 'Page name',\n description: 'What this page is called — its display name.',\n },\n {\n name: 'site.separator',\n label: 'Separator',\n description: 'The character from Host setup → SEO that joins the two.',\n },\n {\n name: 'site.name',\n label: 'Site name',\n description: 'The site title from Host setup → SEO.',\n },\n]\n\n/**\n * The composition every untitled page gets when the site sets no pattern of\n * its own.\n *\n * This is `resolveSeoTitle`'s previous hard-coded composition, written out:\n * name, separator padded by a space on each side, site title. It is a constant\n * rather than a string built at the call site so that the setting's default,\n * the placeholder the editor shows and the value the renderer falls back to\n * are one thing.\n */\nexport const DEFAULT_TITLE_PATTERN = '{{page.name}} {{site.separator}} {{site.name}}'\n\n/** The values the variables stand for, for one page of one site. */\nexport interface SeoTitleVariableValues {\n 'page.name'?: string | null\n 'site.name'?: string | null\n 'site.separator'?: string | null\n}\n\n/** Trimmed, or `''` for anything that is not a live string. */\nfunction clean(value?: string | null): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * The separator a site uses when it has set none.\n *\n * An en dash, which is what `seo-title` has padded around since AGL-1341. It\n * lives here because `{{site.separator}}` has to resolve to the SAME character\n * the code composed with — a variable that stood for \"the separator, unless\n * it is unset, in which case nothing\" would silently change the title of every\n * untitled page on every site that never filled the field in.\n */\nexport const DEFAULT_TITLE_SEPARATOR = '\\u2013'\n\n/** The site's separator, defaulted the one way everything defaults it. */\nexport function seoSeparatorOrDefault(separator?: string | null): string {\n return clean(separator) || DEFAULT_TITLE_SEPARATOR\n}\n\n/**\n * Whether a written value uses any variable at all.\n *\n * The cheap gate in front of every path here: a title with no `{{` is returned\n * by identity, which is what makes \"nothing renders differently\" a property of\n * the code rather than a claim about the data.\n */\nexport function hasSeoTitleVariables(value?: string | null): boolean {\n return typeof value === 'string' && value.includes('{{')\n}\n\n/** Whether a name is one this catalog defines. */\nexport function isSeoTitleVariable(name: string): name is SeoTitleVariableName {\n return SEO_TITLE_VARIABLES.some((variable) => variable.name === name)\n}\n\n/**\n * `{{ name }}` — the whole token, and the name inside it with its padding\n * trimmed, so a human who typed a space after the braces is not punished for\n * it.\n */\nconst TOKEN = /\\{\\{\\s*([a-zA-Z][\\w.]*)\\s*\\}\\}/g\n\ninterface Part {\n /** What renders here, once the variable is resolved. */\n text: string\n /** The variable this came from, if it came from one. */\n variable?: string\n}\n\n/** The written string as literal runs and variable slots, in order. */\nfunction partsOf(source: string, values: SeoTitleVariableValues): Part[] {\n const parts: Part[] = []\n let at = 0\n TOKEN.lastIndex = 0\n for (let match = TOKEN.exec(source); match; match = TOKEN.exec(source)) {\n if (match.index > at) parts.push({ text: source.slice(at, match.index) })\n const name = match[1]\n parts.push({\n variable: name,\n // An unknown variable renders AS ITSELF — see the resolver's docblock.\n text: !isSeoTitleVariable(name)\n ? match[0]\n : name === 'site.separator'\n ? seoSeparatorOrDefault(values[name])\n : clean(values[name]),\n })\n at = match.index + match[0].length\n }\n if (at < source.length) parts.push({ text: source.slice(at) })\n return parts\n}\n\n/**\n * Resolve a written title into the one that is rendered.\n *\n * ## An unknown variable is left alone\n *\n * `{{page.subtitle}}` renders as itself, visibly, rather than as an empty gap.\n * A typo that vanished would produce a title that is silently missing a piece,\n * and the author's next look at the live page would show them a sentence that\n * merely reads a bit short. Leaving it visible is the version somebody\n * notices. The editor's picker and autocomplete are what keep it from\n * happening; this is what happens when they are bypassed.\n *\n * ## A separator needs something on BOTH sides\n *\n * This is `resolveSeoTitle`'s own rule (AGL-1341) and it survives the move into\n * a pattern, because it is the rule that stops `<title>` from beginning with a\n * dash on a page whose name is missing. A `{{site.separator}}` with nothing\n * rendered on one side of it is dropped, whatever order the pattern put the\n * pieces in — which is why the parts are walked rather than regex-substituted:\n * a substitution cannot see its neighbours.\n *\n * ## An empty variable takes its spacing with it\n *\n * Otherwise a site with no name set renders `Pricing ` — the gap where the\n * missing piece was, and a trailing separator's worth of whitespace. Runs\n * collapse to one space and the result is trimmed.\n */\nexport function resolveSeoTitleVariables(\n written: string | null | undefined,\n values: SeoTitleVariableValues,\n): string {\n const source = typeof written === 'string' ? written : ''\n if (!hasSeoTitleVariables(source)) return source\n\n const parts = partsOf(source, values)\n const rendered = parts.map((part) => part.text)\n // A separator survives only where something renders on each side of it.\n parts.forEach((part, index) => {\n if (part.variable !== 'site.separator') return\n const live = (from: number, to: number) =>\n rendered.slice(from, to).some((text) => text.trim())\n if (!live(0, index) || !live(index + 1, rendered.length)) rendered[index] = ''\n })\n\n return rendered.join('').replace(/\\s{2,}/g, ' ').trim()\n}\n"],"names":["SEO_TITLE_VARIABLES","name","label","description","DEFAULT_TITLE_PATTERN","clean","value","trim","DEFAULT_TITLE_SEPARATOR","seoSeparatorOrDefault","separator","hasSeoTitleVariables","includes","isSeoTitleVariable","some","variable","TOKEN","partsOf","source","values","parts","at","lastIndex","match","exec","index","push","text","slice","length","resolveSeoTitleVariables","written","rendered","map","part","forEach","live","from","to","join","replace"],"mappings":"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"}
@@ -49,12 +49,8 @@ export * from './email-state';
49
49
  export * from './crm';
50
50
  export * from './crm-email-templates';
51
51
  export * from './crm-booking';
52
- export * from './contact-merge';
53
52
  export * from './person-erasure';
54
53
  export * from './crm-reports';
55
- export * from './crm-csv';
56
- export * from './contact-holder';
57
- export * from './crm-digest';
58
54
  export * from './crm-task-reminders';
59
55
  export * from './crm-inbound';
60
56
  export * from './member-email-aliases';
@@ -64,8 +60,8 @@ export type { ImportChunkResult, ImportDroppedValue, ImportSkippedRow, } from '.
64
60
  export * from './contact-custom-fields';
65
61
  export * from './consent-groups';
66
62
  export * from './marketing-consent';
63
+ export * from './retained-refusals';
67
64
  export * from './list-assignment-policy';
68
- export * from './list-import';
69
65
  export * from './dynamic-list-rule';
70
66
  export * from './email-topics';
71
67
  export * from './compose-layout-nodes';
@@ -90,7 +86,7 @@ export * from './host-permissions';
90
86
  export * from './password-policy';
91
87
  export * from './idp-profile';
92
88
  export * from './onboarding-deep-link';
93
- export * from './campaign-attribution';
89
+ export * from './utm-attribution';
94
90
  export * from './campaign-membership';
95
91
  export * from './deployment-shape';
96
92
  export * from './upload-cors';
@@ -117,7 +113,7 @@ export * from './repeat-infringer';
117
113
  export * from './org-override-reason';
118
114
  export * from './host-tokens';
119
115
  export * from './variables';
120
- export * from './workflows';
116
+ export * from './host-events';
121
117
  export * from './datasets';
122
118
  export * from './expand-repeatables';
123
119
  export * from './org-roles';
@@ -140,6 +136,7 @@ export * from './name-match';
140
136
  export * from './api-plugins';
141
137
  export * from './plugin-api-rate-limit';
142
138
  export * from './plugin-api-cross-origin';
139
+ export * from './site-interactions';
143
140
  export * from './actions';
144
141
  export * from './element-animation';
145
142
  export * from './attribution-guard';
@@ -155,8 +152,8 @@ export * from './author-css';
155
152
  export * from './author-html';
156
153
  export * from './dataset-models';
157
154
  export * from './dataset-csv';
158
- export * from './marketplace-provenance';
159
- export * from './marketplace-update-state';
155
+ export * from './csv-upload';
156
+ export * from './artifact-provenance';
160
157
  export * from './dataset-query';
161
158
  export * from './plugin-manifest';
162
159
  export * from './email-starter-policy';
@@ -49,9 +49,9 @@ export * from "./child-contract-compose.js";
49
49
  // The one reading of `restrictChildren`/`restrictParent`, shared by the
50
50
  // besigner's drop check and the AI plugin's node-tree validator (AGL-2905).
51
51
  export * from "./lineal-order.js";
52
- // What a node tree must satisfy before it lands on a canvas that did not
53
- // author it (AGL-2939): the marketplace's install sanitizer and the AI
54
- // plugin's node-tree validator both pass through it.
52
+ // What a portable node tree must satisfy before it lands on a canvas that did
53
+ // not author it (AGL-2939): a tree installed from another workspace and a tree
54
+ // a model generated both pass through it.
55
55
  export * from "./node-definition-sanitizer.js";
56
56
  export * from "./console-routes.js";
57
57
  export * from "./console-record-links.js";
@@ -80,10 +80,6 @@ export * from "./crm-email-templates.js";
80
80
  // link carries, the record reference inside it, and the wording and due
81
81
  // date of what a booking files back on the record.
82
82
  export * from "./crm-booking.js";
83
- // Two contact documents folded into one (AGL-2625): the plan the console
84
- // previews and the server writes, reading the facet shape from `contacts`
85
- // and the company mirror from `crm`.
86
- export * from "./contact-merge.js";
87
83
  // A person's privacy erasure from one workspace (AGL-2623): the request's
88
84
  // shape and id, the marker a record carries while it waits, and the lists
89
85
  // the dialog and the docs share. Pure like `crm`; the sweep is server-side.
@@ -93,27 +89,14 @@ export * from "./person-erasure.js";
93
89
  // and beside it because it reads the stage list and the deal weighting from
94
90
  // there rather than restating either.
95
91
  export * from "./crm-reports.js";
96
- // Every CRM CSV, written once (AGL-2662): the column lists and the cells
97
- // each section's Export button writes, and the server's whole-collection
98
- // export streams — one implementation, because the console app may not
99
- // import the plugin whose sections used to hold it.
100
- export * from "./crm-csv.js";
101
- // Which holder a cross-site reader sees one contact through (AGL-2630),
102
- // beside the files that write it: the org-level list and the server's
103
- // export both flatten a contact this way.
104
- export * from "./contact-holder.js";
105
- // What one member is owed this morning — the day's window in a named zone,
106
- // the task and lead bucketing, and the words the notification and the mail
107
- // say (AGL-2619). Pure like `crm-reports`, read by the scheduled route.
108
- export * from "./crm-digest.js";
109
92
  // A task's reminder at its own time (AGL-2659): which reminder a save
110
93
  // leaves, whether one is owed, and the words the notification and the mail
111
- // say. Pure like `crm-digest`, read by the hourly route and by every
94
+ // say. Pure like `crm-reports`, read by the hourly route and by every
112
95
  // writer of the field.
113
96
  export * from "./crm-task-reminders.js";
114
97
  // Email capture (AGL-2657): the address's shape and token, which of a
115
98
  // message's addresses is the correspondent, the excerpt, and the row a
116
- // captured message becomes. Pure like `crm-digest`; the webhook route and
99
+ // captured message becomes. Pure like `crm-reports`; the webhook route and
117
100
  // the address route are the readers with Firestore.
118
101
  export * from "./crm-inbound.js";
119
102
  // A member's own addresses in a workspace (AGL-2975): the ones they have
@@ -139,6 +122,9 @@ export * from "./contact-custom-fields.js";
139
122
  // belongs to.
140
123
  export * from "./consent-groups.js";
141
124
  export * from "./marketing-consent.js";
125
+ // A refusal kept after the record it was written on is deleted (AGL-3338),
126
+ // read back through the same parser as the record's own.
127
+ export * from "./retained-refusals.js";
142
128
  // The ENROLLMENT-time half of the same question, beside its reader for the
143
129
  // same reason: `marketing-consent` decides whether a recorded basis lets us
144
130
  // mail somebody, and this decides what basis putting them on a list may
@@ -150,7 +136,6 @@ export * from "./list-assignment-policy.js";
150
136
  // mechanical screening only, so that an import asks the enrollment question
151
137
  // through the same module the one-address add path asks it through rather
152
138
  // than answering it a second way.
153
- export * from "./list-import.js";
154
139
  // The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and
155
140
  // it composes `contacts` for the segment vocabulary rather than restating it.
156
141
  export * from "./dynamic-list-rule.js";
@@ -192,7 +177,7 @@ export * from "./onboarding-deep-link.js";
192
177
  // Where an account came from (AGL-1731). Beside the plan intent because
193
178
  // they are the same hop — the marketing CTA's query string — and both are
194
179
  // remembered on `users/{uid}` across the verification wall.
195
- export * from "./campaign-attribution.js";
180
+ export * from "./utm-attribution.js";
196
181
  // The edge between a campaign and the forms, screens and contacts a push is
197
182
  // coordinated across. Beside the attribution above because the two answer
198
183
  // opposite questions about the same word: attribution records where somebody
@@ -256,7 +241,9 @@ export * from "./repeat-infringer.js";
256
241
  export * from "./org-override-reason.js";
257
242
  export * from "./host-tokens.js";
258
243
  export * from "./variables.js";
259
- export * from "./workflows.js";
244
+ // What happened on a site that an automation can start on: the platform's
245
+ // own event and the ones plugins declare (AGL-3080).
246
+ export * from "./host-events.js";
260
247
  export * from "./datasets.js";
261
248
  export * from "./expand-repeatables.js";
262
249
  export * from "./org-roles.js";
@@ -284,6 +271,9 @@ export * from "./plugin-api-rate-limit.js";
284
271
  // `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the
285
272
  // two gates exempt exactly the same machine surfaces.
286
273
  export * from "./plugin-api-cross-origin.js";
274
+ // What a published page does when a visitor does something (AGL-3080): the
275
+ // platform's interaction vocabulary, which `./actions` builds its automations on.
276
+ export * from "./site-interactions.js";
287
277
  export * from "./actions.js";
288
278
  export * from "./element-animation.js";
289
279
  export * from "./attribution-guard.js";
@@ -301,8 +291,8 @@ export * from "./author-css.js";
301
291
  export * from "./author-html.js";
302
292
  export * from "./dataset-models.js";
303
293
  export * from "./dataset-csv.js";
304
- export * from "./marketplace-provenance.js";
305
- export * from "./marketplace-update-state.js";
294
+ export * from "./csv-upload.js";
295
+ export * from "./artifact-provenance.js";
306
296
  export * from "./dataset-query.js";
307
297
  export * from "./plugin-manifest.js";
308
298
  // After `plugin-manifest`, whose revocation predicates it asks the kill