@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/site-interactions.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 { resolveAuthoredEventName, sanitizeEventParams } from './analytics-events'\nimport { type AuthorHtmlRemoval, sanitizeAuthorHtml } from './author-html'\nimport { PLUGIN_INTERACTION_STEPS_DECLARED } from '../plugin-manager/first-party-plugins.generated'\n\n/**\n * SITE INTERACTIONS: what a published page does when a visitor does something.\n *\n * An interaction is a trigger and an ordered step list. The trigger names an\n * event — a SITE event the page runtime watches for itself (a click, a hover,\n * an element scrolled into view, exit intent), or an event raised on the\n * server — narrowed by payload conditions and capped in how often it fires.\n * The steps the platform knows are the ones a page carries out in the\n * visitor's own browser: showing and hiding elements, opening menus and\n * drawers, classes and ARIA attributes, scrolling, playing a video, an alert,\n * a redirect, an analytics event, author HTML and script.\n *\n * An interaction is stored on the element that carries it\n * (`node-interactions.ts`) or as a row of `hosts/{hostId}/actions`, and the\n * same shape holds more than the platform's own steps. A plugin adds steps of\n * its own — the workflows plugin's server steps, which a page hands to\n * `/api/events/dispatch` for the host-event listener that owns them. The\n * platform types only its own steps ({@link ClientInteractionStep}), knows any\n * other step only by its `type`, and treats every step that is not one of its\n * client steps as the server's to run. A plugin that wants one of its steps\n * offered in the interaction builder DECLARES it, under `interactionSteps` in\n * `plugins.config.json` ({@link InteractionStepDeclaration}); nothing here\n * names a plugin.\n */\n\n/**\n * Client-side site events (AGL-256) the tenant page runtime emits:\n * element interactions, scroll behavior, dwell time, and exit intent.\n * Trigger config (selector/threshold/path) rides the interaction's trigger.\n */\nexport const SITE_EVENT_TYPES = [\n 'scrollDepth',\n 'scrollToElement',\n 'elementClick',\n 'elementVisible',\n // Hover choreography (AGL-562): delegated enter/leave on the trigger\n // selector — the nav-menu/drawer show-hide building blocks.\n 'elementHoverEnter',\n 'elementHoverLeave',\n 'exitIntent',\n 'timeOnPage',\n 'pageVisit',\n] as const\n\n/** Site events that watch a specific element and need a selector. */\nexport const ELEMENT_SCOPED_SITE_EVENTS = [\n 'scrollToElement',\n 'elementClick',\n 'elementVisible',\n 'elementHoverEnter',\n 'elementHoverLeave',\n] as const\n\nexport type SiteEventType = (typeof SITE_EVENT_TYPES)[number]\n\nexport function isSiteEventType(event: string): event is SiteEventType {\n return SITE_EVENT_TYPES.includes(event as SiteEventType)\n}\n\n/** Structured condition operators (AGL-557). */\nexport const TRIGGER_CONDITION_OPS = [\n 'equals',\n 'contains',\n 'notEmpty',\n] as const\n\nexport type TriggerConditionOp = (typeof TRIGGER_CONDITION_OPS)[number]\n\n/**\n * Structured per-interaction condition over the event payload (AGL-557): the\n * no-code sibling of the free-text `filter` expression, e.g. \"only run\n * when the `subscribe` field is not empty\". Evaluated against the same\n * scope as the filter (event name + payload fields); the interaction is\n * skipped when unmet.\n */\nexport interface InteractionCondition {\n /** Payload field the condition reads (a submitted form field name). */\n field: string\n op: TriggerConditionOp\n /** Comparison value for `equals`/`contains`; unused by `notEmpty`. */\n value?: string\n}\n\n/**\n * Evaluates a structured trigger condition (AGL-557). String-coerces the\n * payload value; `equals`/`contains` compare trimmed + case-insensitive\n * so checkbox values like \"Yes\" match an author-typed \"yes\". A missing\n * field never matches (and is \"empty\" for `notEmpty`); an absent or\n * field-less condition always passes, mirroring the filter's default.\n */\nexport function evaluateTriggerCondition(\n condition: InteractionCondition | undefined | null,\n payload: Record<string, unknown>,\n): boolean {\n const field = condition?.field?.trim()\n if (!condition || !field) return true\n const raw = (payload ?? {})[field]\n const actual = (raw == null ? '' : String(raw)).trim().toLowerCase()\n if (condition.op === 'notEmpty') return actual.length > 0\n const expected = String(condition.value ?? '')\n .trim()\n .toLowerCase()\n if (condition.op === 'equals') return actual === expected\n if (condition.op === 'contains') {\n return expected.length > 0 && actual.includes(expected)\n }\n return false\n}\n\n/** How chained trigger conditions combine (AGL-565). */\nexport const TRIGGER_COMBINATORS = ['and', 'or'] as const\n\nexport type TriggerCombinator = (typeof TRIGGER_COMBINATORS)[number]\n\n/** Cap on chained conditions per trigger (AGL-565). */\nexport const ACTION_MAX_CONDITIONS = 5\n\n/**\n * Normalizes a trigger's condition clauses to a list (AGL-565): the\n * `conditions` array wins when present; a legacy single `condition`\n * (AGL-557) becomes a one-element list. Read-time only — persisted docs\n * are never migrated, so pre-565 single-condition docs keep working\n * unchanged.\n */\nexport function normalizeTriggerConditions(\n trigger:\n | Pick<InteractionTrigger, 'condition' | 'conditions'>\n | undefined\n | null,\n): InteractionCondition[] {\n const conditions = trigger?.conditions\n if (Array.isArray(conditions)) return conditions.filter(Boolean)\n return trigger?.condition ? [trigger.condition] : []\n}\n\n/**\n * Evaluates a trigger's condition list with its combinator (AGL-565):\n * `and` (the default) requires every condition to pass, `or` any one.\n * Per-condition semantics are unchanged from AGL-557\n * (`evaluateTriggerCondition`), and an empty list always passes — which\n * covers every pre-565 doc without conditions.\n */\nexport function evaluateTriggerConditions(\n trigger:\n | Pick<InteractionTrigger, 'condition' | 'conditions' | 'combinator'>\n | undefined\n | null,\n payload: Record<string, unknown>,\n): boolean {\n const conditions = normalizeTriggerConditions(trigger)\n if (!conditions.length) return true\n return trigger?.combinator === 'or'\n ? conditions.some((condition) =>\n evaluateTriggerCondition(condition, payload),\n )\n : conditions.every((condition) =>\n evaluateTriggerCondition(condition, payload),\n )\n}\n\nexport interface InteractionTrigger {\n /**\n * Event the interaction listens for: a HOST_EVENT_TYPE (server-emitted), a\n * SITE_EVENT_TYPE (client-emitted, AGL-256), or a custom name fired by\n * another automation's `customEvent` step.\n */\n event: string\n /** Optional expression over the payload; runs only when truthy. */\n filter?: string\n /** Optional structured payload condition (AGL-557); null clears it. */\n condition?: InteractionCondition | null\n /**\n * Chained payload conditions (AGL-565), combined with `combinator`.\n * When present this list wins over the legacy single `condition`\n * (see `normalizeTriggerConditions`); null clears it.\n */\n conditions?: InteractionCondition[] | null\n /** How the chained conditions combine (AGL-565); default `and`. */\n combinator?: TriggerCombinator | null\n /** CSS selector for element-scoped site events (click/visible/scroll-to). */\n selector?: string\n /** Percent 0-100 (scrollDepth) or seconds (timeOnPage). */\n threshold?: number\n /** Path pattern the site event listens on (overlay glob rules); empty = all. */\n pathPattern?: string\n /**\n * Fire at most once per visitor (AGL-266, localStorage-keyed) instead\n * of once per pageview. Site events only.\n */\n oncePerVisitor?: boolean\n /**\n * Fire at most once per browser session (AGL-274,\n * sessionStorage-keyed). Site events only.\n */\n oncePerSession?: boolean\n /**\n * Minimum minutes between fires for the same visitor (AGL-274,\n * localStorage timestamp). Site events only; ignored when\n * `oncePerVisitor` is set.\n */\n cooldownMinutes?: number\n /**\n * Fire on EVERY occurrence instead of once per pageview (AGL-562) —\n * required for repeatable UI choreography (menu toggles, drawer\n * open/close, hover show/hide). Site events only; the explicit\n * per-session/visitor/cooldown caps above still win when set.\n */\n everyTime?: boolean\n}\n\n/**\n * A per-STEP condition, and the difference between an automation and a flow.\n *\n * `InteractionTrigger.conditions` gates the whole interaction: every step runs or\n * none does. That is enough for \"when a form is submitted, do these three\n * things\" and not enough for anything with a shape — \"wait three days, then,\n * only if they have not ordered, send the reminder\" needs the condition to\n * belong to the step rather than to the run.\n *\n * Same clause type and the same combinator as the trigger's, evaluated\n * against the same scope, so an author learns one condition editor and the\n * two can never disagree about what `contains` means.\n */\nexport interface InteractionStepGuard {\n conditions: InteractionCondition[]\n /** How the clauses combine; default `and`, as on the trigger. */\n combinator?: TriggerCombinator | null\n}\n\n/**\n * Evaluates a step's own guard, with the trigger's semantics.\n *\n * An absent or clause-less guard passes, exactly as an absent trigger\n * condition does — so every step written before guards existed keeps running.\n */\nexport function evaluateStepGuard(\n guard: InteractionStepGuard | undefined | null,\n scope: Record<string, unknown>,\n): boolean {\n const conditions = (guard?.conditions ?? []).filter(Boolean)\n if (!conditions.length) return true\n return guard?.combinator === 'or'\n ? conditions.some((condition) => evaluateTriggerCondition(condition, scope))\n : conditions.every((condition) => evaluateTriggerCondition(condition, scope))\n}\n\n/**\n * The steps the platform itself carries out, in the visitor's browser (AGL-257).\n *\n * Every other step an interaction may hold is a plugin's, known here only as\n * {@link InteractionStepBase}.\n */\nexport type ClientInteractionStep = (\n | {\n type: 'siteAlert'\n message: string\n severity?: 'info' | 'success' | 'warning' | 'error'\n }\n // Client-side UI steps (AGL-257): run in the visitor's page by the\n // tenant automations engine; the server executor skips them.\n | { type: 'showOverlay'; overlayId?: string; overlayName?: string }\n | { type: 'stickyNav'; selector?: string }\n // Selectors accept CSS or a node id via [data-node-id=\"…\"] — the\n // besigner element picker emits the latter (AGL-314/319).\n | { type: 'addClass'; selector: string; className: string }\n | { type: 'removeClass'; selector: string; className: string }\n | { type: 'toggleClass'; selector: string; className: string }\n // Element show/hide choreography (AGL-562): sugar over the shared\n // hidden class (see element-ui.ts) so authors never type class names.\n // The besigner target picker emits the node's stable data-aglyn\n // selector; any CSS selector works.\n // Element visibility (AGL-562) with menu-grade choreography (AGL-589):\n // `delayMs` defers the change and a later visibility step on the same\n // selector cancels the pending one (the hover grace period), and\n // `dismissOn` self-dismisses a shown element on Escape / outside click.\n | {\n type: 'showElement'\n selector: string\n delayMs?: number\n dismissOn?: ElementDismissOption[]\n }\n | { type: 'hideElement'; selector: string; delayMs?: number }\n | {\n type: 'toggleElement'\n selector: string\n delayMs?: number\n dismissOn?: ElementDismissOption[]\n }\n // Drawer commands (AGL-562): delivered to muiDrawer instances over the\n // window event bus keyed by node id; an empty target addresses the\n // page's first drawer.\n // Attribute steps (AGL-2546): the semantics half of a hand-built\n // disclosure. `showElement` opens the panel; these are what let it say so.\n //\n // `selector`, deliberately, and NOT a new targeting field. Two mechanisms\n // key off that exact name: `regraftStepSelectors` rewrites it to the\n // grafted `cmp__{instance}__{id}` when an interaction is authored inside a\n // reusable component, and the runtime wraps it in `expandLeafSelector`.\n // The menu steps took `menuNodeId` instead, get neither, and compensate at\n // run time — one concept should not have three targeting models.\n //\n // Two verbs rather than one with an empty value: `\"\"` does not mean\n // \"absent\" uniformly. `hidden=\"\"` is true, `aria-expanded=\"\"` is invalid\n // and discarded, `data-x=\"\"` is a real value an attribute selector still\n // matches. Overloading it would make the step's meaning depend on which\n // attribute the author picked.\n | { type: 'setAttribute'; selector: string; name: string; value: string }\n | { type: 'removeAttribute'; selector: string; name: string }\n // Steps that act on ONE element (AGL-2867): the first rendered match of\n // `selector`, which is spelled `selector` for the reason the attribute\n // steps give above. `scrollTo` rather than `scrollToElement`, because that\n // name is already a TRIGGER — \"the visitor scrolled to the element\" — and\n // one word meaning both directions would be read wrong by every reader of a\n // stored document.\n | {\n type: 'scrollTo'\n selector: string\n /** Absent means `smooth`. A visitor who asks for reduced motion is always scrolled instantly. */\n behavior?: ScrollToBehavior\n /** Pixels left above the target, so a sticky header does not cover it. Absent means 0. */\n offsetPx?: number\n }\n // Presses a Video element's poster from outside it: the same press, so\n // the lightbox opens and plays, or the film plays in place of the poster.\n | { type: 'playVideo'; selector: string }\n | { type: 'openDrawer'; drawerNodeId?: string }\n | { type: 'closeDrawer'; drawerNodeId?: string }\n | { type: 'toggleDrawer'; drawerNodeId?: string }\n // Menu commands (AGL-568): the drawer pattern for Dropdown/Mega Menu\n // elements — delivered over their own window event bus keyed by node\n // id; an empty target addresses the page's first menu. Hover-triggered\n // opens carry a hover flag so the menu closes on pointer leave.\n | { type: 'openMenu'; menuNodeId?: string }\n | { type: 'closeMenu'; menuNodeId?: string }\n | { type: 'toggleMenu'; menuNodeId?: string }\n | { type: 'showHtml'; html: string }\n | { type: 'runJs'; code: string }\n // Screen targets are rename-safe (AGL-339): the tenant resolves the\n // screen id against the host routing map at run time; `url` is the\n // external/manual fallback.\n | { type: 'redirect'; url?: string; screenId?: string }\n | {\n type: 'trackGaEvent'\n eventName: string\n params?: Record<string, string>\n }\n) & {\n /** See {@link InteractionStepGuard}. Absent means the step always runs. */\n when?: InteractionStepGuard | null\n}\n\nexport type ClientInteractionStepType = ClientInteractionStep['type']\n\n/**\n * Any step, as the platform holds it: a type, the guard every step may carry,\n * and fields the platform does not read. A plugin's own step types its other\n * fields itself.\n */\nexport interface InteractionStepBase {\n type: string\n when?: InteractionStepGuard | null\n [field: string]: unknown\n}\n\n/**\n * An interaction, as `hosts/{hostId}/actions/{id}` and an element's own\n * `interactions` hold it.\n *\n * `Step` is the platform's client vocabulary unless a reader names more: the\n * workflows plugin reads the same documents with its server steps added, and\n * a step from a plugin this reader does not know is still carried, untyped,\n * in the list.\n */\nexport interface SiteInteraction<Step extends InteractionStepBase = ClientInteractionStep> {\n name: string\n trigger: InteractionTrigger\n steps: Step[]\n /** A disabled interaction never runs; a new one defaults enabled. */\n enabled?: boolean\n}\n\n/** Steps the tenant page runtime executes client-side (AGL-257). */\nexport const CLIENT_ACTION_STEP_TYPES: ReadonlySet<string> =\n new Set<ClientInteractionStepType>([\n 'showOverlay',\n 'stickyNav',\n 'addClass',\n 'removeClass',\n 'toggleClass',\n 'showElement',\n 'hideElement',\n 'toggleElement',\n 'openDrawer',\n 'closeDrawer',\n 'toggleDrawer',\n 'openMenu',\n 'closeMenu',\n 'toggleMenu',\n 'setAttribute',\n 'removeAttribute',\n 'scrollTo',\n 'playVideo',\n 'showHtml',\n 'runJs',\n 'redirect',\n 'trackGaEvent',\n 'siteAlert',\n ] as const)\n\nexport function isClientActionStep(step: InteractionStepBase): step is ClientInteractionStep {\n return CLIENT_ACTION_STEP_TYPES.has(step.type)\n}\n\n/**\n * Basic presentational interactions (AGL-577): the subset of client steps\n * that are pure DOM choreography — menu/drawer open-close, element\n * show/hide, class toggles, sticky nav, navigation, and client-only\n * alerts. They carry NO server cost and touch NO data, so they run on\n * every plan (the `interactions` feature, on by default everywhere).\n *\n * The powerful client steps stay behind the `actions` entitlement:\n * `showOverlay` (marketing overlays), `showHtml` (arbitrary HTML\n * injection), and `trackGaEvent` (analytics). `runJs` keeps its own\n * higher `webhooks` (Business) gate. A step that is not a client step is\n * the server's, and is re-checked against `actions` where it runs.\n */\nexport const BASIC_CLIENT_ACTION_STEP_TYPES: ReadonlySet<string> =\n new Set<ClientInteractionStepType>([\n 'stickyNav',\n 'addClass',\n 'removeClass',\n 'toggleClass',\n 'showElement',\n 'hideElement',\n 'toggleElement',\n 'openDrawer',\n 'closeDrawer',\n 'toggleDrawer',\n 'openMenu',\n 'closeMenu',\n 'toggleMenu',\n 'setAttribute',\n 'removeAttribute',\n // Moving the page and pressing a player the author placed: no data, no\n // server, and nothing a Video element's own poster does not already do\n // on every plan.\n 'scrollTo',\n 'playVideo',\n 'redirect',\n 'siteAlert',\n ] as const)\n\n/** A client step available on all plans (no `actions` entitlement). */\nexport function isBasicClientActionStep(step: InteractionStepBase): boolean {\n return BASIC_CLIENT_ACTION_STEP_TYPES.has(step.type)\n}\n\n/**\n * Whether a client step may run for the given entitlement tier (AGL-577).\n * This is the single source of truth the page enricher uses to trim the\n * client-automation payload per plan:\n *\n * - Basic presentational steps → always (every plan).\n * - `runJs` → `allowJs` (Business `webhooks` tier).\n * - Remaining advanced client steps (overlay / showHtml / analytics) →\n * `actionsEntitled` (`actions` tier).\n *\n * Server steps are not client steps and always return false here; they\n * are dispatched and re-authorized server-side.\n */\nexport function isClientStepEntitled(\n step: InteractionStepBase,\n entitlements: { actionsEntitled: boolean; allowJs: boolean },\n): boolean {\n if (!isClientActionStep(step)) return false\n if (isBasicClientActionStep(step)) return true\n if (step.type === 'runJs') return entitlements.allowJs\n return entitlements.actionsEntitled\n}\n\n/** The longest step list an interaction may hold. */\nexport const ACTION_MAX_STEPS = 10\n\n/**\n * Cap on the parameters one `trackGaEvent` step may carry (AGL-1587).\n *\n * Not a storage concern. These pairs are the least trustworthy analytics\n * input on the platform — both halves are typed by a site author and land in\n * that site's own GA4 property — and a bounded list is one an author can read\n * back in full before publishing, which is the only review a parameter ever\n * gets. GA4 caps custom parameters per event as well and drops the overflow\n * without saying so, so an unbounded editor would let an author configure\n * parameters that are never delivered and never explained. Ten, the same\n * ceiling {@link ACTION_MAX_STEPS} puts on the step list itself.\n */\nexport const ACTION_MAX_EVENT_PARAMS = 10\n\n/**\n * Longest analytics parameter NAME GA4 accepts; it drops a longer one on\n * arrival, so a name over this is silence rather than a truncated dimension.\n * The value half is capped by `ANALYTICS_PARAM_MAX_LENGTH`, which truncates\n * instead of dropping.\n */\nexport const ACTION_EVENT_PARAM_NAME_MAX_LENGTH = 40\n\n/** Self-dismiss triggers for shown elements (AGL-589). */\nexport const ELEMENT_DISMISS_OPTIONS = ['escape', 'outsideClick'] as const\nexport type ElementDismissOption = (typeof ELEMENT_DISMISS_OPTIONS)[number]\n/** Visibility grace-delay ceiling — enough for hover travel, no dead UIs. */\nexport const ELEMENT_VISIBILITY_MAX_DELAY_MS = 5000\n\n/**\n * How a `scrollTo` step moves the page. Stored in documents; never rename a\n * value. `instant` rather than the DOM's `auto`, because `auto` defers to the\n * site's CSS `scroll-behavior` — a site that sets `smooth` there would animate\n * a scroll the author asked to be instant.\n */\nexport const SCROLL_TO_BEHAVIORS = ['smooth', 'instant'] as const\nexport type ScrollToBehavior = (typeof SCROLL_TO_BEHAVIORS)[number]\n\n/**\n * The largest offset a `scrollTo` step may leave above its target.\n *\n * The offset exists to clear a sticky header, which is tens of pixels and\n * rarely more than a few hundred with an announcement bar stacked on it. A\n * thousand leaves room for any header while still refusing the extra digit\n * that would park the target off the bottom of the screen.\n */\nexport const SCROLL_TO_MAX_OFFSET_PX = 1000\n\n/** Custom event names: short, no collision with built-ins. */\nexport const CUSTOM_EVENT_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]{1,39}$/\n\n/**\n * The attribute names an interaction may write (AGL-2546).\n *\n * An unrestricted attribute setter is a self-XSS vector authored through the\n * interactions UI: it reaches `href`, `src`, `onclick`, `formaction` and\n * `style`, and on a site with collaborators the author and the victim need\n * not be the same person. `aria-*` and `data-*` carry the entire\n * accessibility use case and none of that exposure.\n *\n * Deliberately NOT solved by an entitlement gate. `runJs` is Business-gated\n * because it is dangerous at any tier; this has to work on every plan,\n * because a screen-reader user's access to a menu is not a paid feature.\n */\nexport const INTERACTION_ATTRIBUTE_NAME_PATTERN = /^(?:aria|data)-[a-z][a-z0-9-]*$/\n\n/**\n * Whether an interaction is allowed to write this attribute name.\n *\n * Case-insensitive because HTML attribute names are, and a rejected name is\n * a no-op rather than an error: a step that cannot run must not break the\n * steps after it in the same interaction.\n */\nexport function isInteractionAttributeAllowed(name: unknown): boolean {\n return (\n typeof name === 'string' &&\n INTERACTION_ATTRIBUTE_NAME_PATTERN.test(name.trim().toLowerCase())\n )\n}\n\n/**\n * An interaction step a plugin declares under `interactionSteps` in\n * `plugins.config.json`, compiled into the first-party catalog, so the\n * interaction builder can offer it and every validator can check it without\n * the plugin loaded.\n *\n * A step may be the plugin's own (a server step the plugin runs) or a\n * platform client step whose target is a record the plugin keeps. Either way\n * the declaration says how the builder names it and, for a step that PICKS a\n * record, where the builder lists the records from and which of the step's\n * fields hold the pick.\n */\nexport interface InteractionStepDeclaration {\n /** The plugin that declared it. */\n pluginId: string\n /** The stored step `type`. */\n type: string\n /** How the builder's step picker names it: \"Run a workflow\". */\n label: string\n /** The record this step acts on, picked in the builder. */\n picks?: {\n /** The site collection the records are listed from — one the plugin declares it owns. */\n collection: string\n /** How many records the builder lists. */\n limit: number\n /** The step field holding the picked record's id (rename-safe). */\n idField: string\n /** The step field holding its name at the time it was picked (display only). */\n nameField: string\n /** The picker's label: \"Workflow\". */\n label: string\n /** What a step with no pick is told, after its `Step N:` prefix. */\n missing: string\n }\n}\n\n/** Every step the first-party plugins declare, in `plugins.config.json` order. */\nexport function declaredInteractionSteps(): readonly InteractionStepDeclaration[] {\n return PLUGIN_INTERACTION_STEPS_DECLARED\n}\n\n/** The declared step of this type, or `null` for one no plugin declares. */\nexport function declaredInteractionStep(\n type: string,\n declarations: readonly InteractionStepDeclaration[] = PLUGIN_INTERACTION_STEPS_DECLARED,\n): InteractionStepDeclaration | null {\n return declarations.find((declaration) => declaration.type === type) ?? null\n}\n\nexport interface ValidateInteractionOptions {\n /**\n * One step's checks that belong to its owner — a plugin's own step types.\n * Runs after the step's guard is checked; its answer, when not `null`, is\n * the interaction's.\n */\n validateStep?: (step: InteractionStepBase, label: string) => string | null\n /** The declared steps; the compiled first-party catalog unless a spec says otherwise. */\n declarations?: readonly InteractionStepDeclaration[]\n}\n\n/**\n * Validates an interaction's shape; returns a human-readable error or null.\n * Every surface that writes one shares this, so a bad step never persists or\n * runs.\n *\n * The platform checks what is its own: the name, the trigger and its\n * conditions, the step count, each step's guard, its client steps, and the\n * pick of every declared step that picks a record. A step it does not know is\n * left to its owner, through `validateStep`.\n */\nexport function validateInteraction(\n interaction: SiteInteraction<InteractionStepBase>,\n options: ValidateInteractionOptions = {},\n): string | null {\n if (!interaction.name?.trim()) return 'Name the action'\n const event = interaction.trigger?.event?.trim() ?? ''\n if (!event) return 'Pick a trigger event'\n // A server event or a custom one fired by another step is, like a site\n // event, a name; every built-in server event fits the custom pattern.\n if (!isSiteEventType(event) && !CUSTOM_EVENT_PATTERN.test(event)) {\n return 'Custom event names are 2–40 letters, digits, dashes'\n }\n // Site-event trigger config (AGL-256; hover events AGL-562).\n if (\n (ELEMENT_SCOPED_SITE_EVENTS as readonly string[]).includes(event) &&\n !interaction.trigger?.selector?.trim()\n ) {\n return 'This trigger needs a CSS selector'\n }\n if (\n ['scrollDepth', 'timeOnPage'].includes(event) &&\n !(Number(interaction.trigger?.threshold) > 0)\n ) {\n return event === 'scrollDepth'\n ? 'Set the scroll percentage (1–100)'\n : 'Set the seconds on page'\n }\n // Frequency caps (AGL-274).\n if (\n interaction.trigger?.cooldownMinutes != null &&\n !(Number(interaction.trigger.cooldownMinutes) >= 1)\n ) {\n return 'Cooldown must be at least 1 minute'\n }\n // Structured payload conditions (AGL-557; chained AGL-565).\n const combinator = interaction.trigger?.combinator\n if (combinator != null && !TRIGGER_COMBINATORS.includes(combinator)) {\n return 'Combine conditions with AND or OR'\n }\n const conditions = normalizeTriggerConditions(interaction.trigger)\n if (conditions.length > ACTION_MAX_CONDITIONS) {\n return `Conditions are capped at ${ACTION_MAX_CONDITIONS}`\n }\n for (const [index, condition] of conditions.entries()) {\n // Single-condition messages stay exactly as AGL-557 shipped them;\n // the row marker only appears once there are rows to tell apart.\n const where = conditions.length > 1 ? ` (condition ${index + 1})` : ''\n if (!TRIGGER_CONDITION_OPS.includes(condition.op)) {\n return `Pick a condition operator${where}`\n }\n if (!condition.field?.trim()) {\n return `Name the field the condition checks${where}`\n }\n if (condition.op !== 'notEmpty' && !condition.value?.trim()) {\n return `Enter the value the condition compares against${where}`\n }\n }\n const steps = interaction.steps ?? []\n if (!steps.length) return 'Add at least one step'\n if (steps.length > ACTION_MAX_STEPS) {\n return `Actions are capped at ${ACTION_MAX_STEPS} steps`\n }\n for (const [index, step] of steps.entries()) {\n const label = `Step ${index + 1}`\n // A step's own guard, checked with the same messages the trigger's\n // clauses get — one condition editor, one set of complaints about it.\n const guardClauses = (step.when?.conditions ?? []).filter(Boolean)\n if (guardClauses.length > ACTION_MAX_CONDITIONS) {\n return `${label}: conditions are capped at ${ACTION_MAX_CONDITIONS}`\n }\n if (\n step.when?.combinator != null &&\n !TRIGGER_COMBINATORS.includes(step.when.combinator)\n ) {\n return `${label}: combine conditions with AND or OR`\n }\n for (const clause of guardClauses) {\n if (!TRIGGER_CONDITION_OPS.includes(clause.op)) {\n return `${label}: pick a condition operator`\n }\n if (!clause.field?.trim()) {\n return `${label}: name the field the condition checks`\n }\n if (clause.op !== 'notEmpty' && !clause.value?.trim()) {\n return `${label}: enter the value the condition compares against`\n }\n }\n const owned = options.validateStep?.(step, label)\n if (owned) return owned\n const declared = declaredInteractionStep(step.type, options.declarations)\n if (declared?.picks) {\n const id = step[declared.picks.idField]\n const name = step[declared.picks.nameField]\n if (\n !(typeof id === 'string' && id.trim()) &&\n !(typeof name === 'string' && name.trim())\n ) {\n return `${label}: ${declared.picks.missing}`\n }\n }\n if (isClientActionStep(step)) {\n const problem = clientStepProblem(step, label)\n if (problem) return problem\n }\n }\n return null\n}\n\n/** A client step's own complaint, or null. The step's guard is checked by the caller. */\nfunction clientStepProblem(step: ClientInteractionStep, label: string): string | null {\n if (step.type === 'siteAlert' && !step.message?.trim()) {\n return `${label}: enter the alert message`\n }\n if (\n step.type === 'addClass' ||\n step.type === 'removeClass' ||\n step.type === 'toggleClass'\n ) {\n if (!step.selector?.trim()) return `${label}: enter a CSS selector`\n if (!step.className?.trim()) return `${label}: enter the class name`\n }\n // Element show/hide steps (AGL-562) always target a selector; drawer\n // (AGL-562) and menu (AGL-568) commands may omit the target (the\n // page's first drawer/menu answers).\n if (\n (step.type === 'showElement' ||\n step.type === 'hideElement' ||\n step.type === 'toggleElement') &&\n !step.selector?.trim()\n ) {\n return `${label}: pick the element to show or hide`\n }\n if (\n step.type === 'showElement' ||\n step.type === 'hideElement' ||\n step.type === 'toggleElement'\n ) {\n // Choreography options (AGL-589).\n const delay = (step as { delayMs?: unknown }).delayMs\n if (\n delay != null &&\n !(\n Number.isInteger(delay) &&\n (delay as number) >= 0 &&\n (delay as number) <= ELEMENT_VISIBILITY_MAX_DELAY_MS\n )\n ) {\n return `${label}: delay must be 0–${ELEMENT_VISIBILITY_MAX_DELAY_MS}ms`\n }\n const dismiss = (step as { dismissOn?: unknown }).dismissOn\n if (\n dismiss != null &&\n !(\n Array.isArray(dismiss) &&\n dismiss.every((option) =>\n (ELEMENT_DISMISS_OPTIONS as readonly string[]).includes(\n option as string,\n ),\n )\n )\n ) {\n return `${label}: dismiss options are escape and outsideClick`\n }\n }\n // One-target steps (AGL-2867). The runtime does nothing for a target it\n // cannot find, so a missing one is refused here, where it can be fixed.\n if (step.type === 'scrollTo') {\n if (!step.selector?.trim()) {\n return `${label}: pick the element to scroll to`\n }\n const behavior = (step as { behavior?: unknown }).behavior\n if (\n behavior != null &&\n !(SCROLL_TO_BEHAVIORS as readonly unknown[]).includes(behavior)\n ) {\n return `${label}: scroll smoothly or instantly`\n }\n const offset = (step as { offsetPx?: unknown }).offsetPx\n if (\n offset != null &&\n !(\n Number.isInteger(offset) &&\n (offset as number) >= 0 &&\n (offset as number) <= SCROLL_TO_MAX_OFFSET_PX\n )\n ) {\n return `${label}: the offset must be 0–${SCROLL_TO_MAX_OFFSET_PX}px`\n }\n }\n if (step.type === 'playVideo' && !step.selector?.trim()) {\n return `${label}: pick the video to play`\n }\n if (step.type === 'showHtml') {\n if (!step.html?.trim()) return `${label}: enter the HTML`\n // AGL-2486: the author-facing half of the runtime sanitizer, exactly as\n // the reserved analytics name below is the author-facing half of the\n // runtime refusal. `site-runtime.tsx` runs this HTML through\n // `sanitizeAuthorHtml` for a VISITOR, where the only thing it can do\n // with markup it refuses is drop it and say nothing — so an author who\n // pastes an embed snippet gets a step that runs, reports success, and\n // shows nothing. The refusal is reported HERE, where the person who\n // can fix it is looking, and it is the SAME function that will run at\n // render rather than a second description of its rules.\n const removals: AuthorHtmlRemoval[] = []\n const safe = sanitizeAuthorHtml(step.html, removals)\n if (!safe.trim()) {\n return `${label}: none of this HTML can be shown on a published page — ${removals[0]?.message ?? 'it is removed by the sanitizer.'}`\n }\n if (removals.length) {\n return `${label}: ${removals[0].message}${removals.length > 1 ? ` (+${removals.length - 1} more)` : ''}`\n }\n }\n if (step.type === 'runJs' && !step.code?.trim()) {\n return `${label}: enter the JavaScript`\n }\n if (step.type === 'redirect' && !step.url?.trim() && !step.screenId) {\n return `${label}: pick a page or enter the destination URL`\n }\n if (step.type === 'trackGaEvent') {\n if (!step.eventName?.trim()) {\n return `${label}: name the analytics event`\n }\n // AGL-1587: the author-facing half of the runtime refusal. The runtime\n // can only drop a bad name silently — it is executing for a VISITOR —\n // so the name is checked HERE, where the person who can fix it is.\n const resolved = resolveAuthoredEventName(step.eventName)\n if (resolved.reason === 'reserved') {\n return `${label}: \"${step.eventName.trim()}\" is a reserved analytics event name — pick another`\n }\n if (!resolved.name) {\n return `${label}: the analytics event name must start with a letter`\n }\n const params = Object.entries(step.params ?? {})\n if (params.length > ACTION_MAX_EVENT_PARAMS) {\n return `${label}: analytics parameters are capped at ${ACTION_MAX_EVENT_PARAMS}`\n }\n for (const [key, value] of params) {\n if (!key.trim()) return `${label}: name every analytics parameter`\n if (key.trim().length > ACTION_EVENT_PARAM_NAME_MAX_LENGTH) {\n return `${label}: the \"${key.trim().slice(0, 16)}…\" parameter name is over ${ACTION_EVENT_PARAM_NAME_MAX_LENGTH} characters, which GA4 drops`\n }\n if (!String(value ?? '').trim()) {\n return `${label}: enter a value for the \"${key.trim()}\" parameter`\n }\n }\n // The author-facing half of the runtime sanitizer, the same shape the\n // reserved name above and the `showHtml` check take. `trackAuthoredEvent`\n // strips a parameter that can carry personal data and says nothing about\n // it, because it is executing for a VISITOR — so an author who binds a\n // form field into a parameter gets a step that runs, reports success,\n // and delivers an event missing the dimension it was created for. The\n // strip is reported HERE, by running the REAL `sanitizeEventParams`\n // rather than a second description of its rules, so the two can never\n // disagree about which parameter survives.\n const kept = sanitizeEventParams(step.params ?? undefined)\n const stripped = params.find(([key]) => !(key in kept))\n if (stripped) {\n return `${label}: the \"${stripped[0]}\" parameter is never sent — an analytics parameter must not carry a visitor's own details`\n }\n }\n return null\n}\n\n/** Alert produced by a `siteAlert` step, surfaced to the emitting client. */\nexport interface SiteAlert {\n message: string\n severity: 'info' | 'success' | 'warning' | 'error'\n}\n\n/**\n * How many LIVE action documents one host may hold (AGL-2266).\n *\n * `hosts/{hostId}/actions` was in NO exclusion list in\n * `cloud/firebase-firestore.rules`, so the host catch-all's `allow create`\n * granted it to any editor, client-direct, on any plan — and the import\n * route's own table said so in as many words: *\"no `RESOURCES` entry and no\n * quota key anywhere; all three creators write the document client-direct.\"*\n * A free org could therefore mint unbounded Firestore documents from the\n * browser against a $0 subscription. Unbounded infrastructure, not a bypass of\n * anything we sell, which is `WEBHOOK_MAX_PER_HOST`'s (AGL-1360) and\n * `NON_PAGE_SCREEN_MAX_PER_HOST`'s (AGL-1399) shape exactly: a flat PLATFORM\n * cap with no `OrgEntitlements` key, no variation by plan, and nothing on the\n * price list to explain.\n *\n * ## Not gated on the `actions` entitlement, deliberately\n *\n * `actions` is a Pro feature and `interactions` is free, and BOTH write this\n * collection — the interaction builder and the besigner's preset wiring create\n * the same document type the Pro actions card does. Gating creation on the\n * paid flag would take element interactions away from every free and starter\n * site, which is a pricing change wearing a cap's clothes. The runtime already\n * checks the entitlement where it decides what may RUN\n * (`run-event-actions.ts`), which is the right place for it.\n *\n * ## Why 500\n *\n * Sized to the busiest plausible site rather than to today's data. An action\n * is authored per interactive element, so a heavy marketing site with dropdown\n * choreography on every section reaches tens; presets wire a handful at a\n * time. 500 is far past anything authoring produces and well short of what a\n * script in a loop wants, which is the only property a flat cap needs. Live\n * documents only — the interactions provider soft-deletes with `deletedAt`, so\n * counting tombstones would be AGL-1173's bug one collection over, where\n * removing an interaction never frees its slot.\n */\nexport const ACTIONS_MAX_PER_HOST = 500\n"],"names":["resolveAuthoredEventName","sanitizeEventParams","sanitizeAuthorHtml","PLUGIN_INTERACTION_STEPS_DECLARED","SITE_EVENT_TYPES","ELEMENT_SCOPED_SITE_EVENTS","isSiteEventType","event","includes","TRIGGER_CONDITION_OPS","evaluateTriggerCondition","condition","payload","field","trim","raw","actual","String","toLowerCase","op","length","expected","value","TRIGGER_COMBINATORS","ACTION_MAX_CONDITIONS","normalizeTriggerConditions","trigger","conditions","Array","isArray","filter","Boolean","evaluateTriggerConditions","combinator","some","every","evaluateStepGuard","guard","scope","CLIENT_ACTION_STEP_TYPES","Set","isClientActionStep","step","has","type","BASIC_CLIENT_ACTION_STEP_TYPES","isBasicClientActionStep","isClientStepEntitled","entitlements","allowJs","actionsEntitled","ACTION_MAX_STEPS","ACTION_MAX_EVENT_PARAMS","ACTION_EVENT_PARAM_NAME_MAX_LENGTH","ELEMENT_DISMISS_OPTIONS","ELEMENT_VISIBILITY_MAX_DELAY_MS","SCROLL_TO_BEHAVIORS","SCROLL_TO_MAX_OFFSET_PX","CUSTOM_EVENT_PATTERN","INTERACTION_ATTRIBUTE_NAME_PATTERN","isInteractionAttributeAllowed","name","test","declaredInteractionSteps","declaredInteractionStep","declarations","find","declaration","validateInteraction","interaction","options","selector","Number","threshold","cooldownMinutes","index","entries","where","steps","label","guardClauses","when","clause","owned","validateStep","declared","picks","id","idField","nameField","missing","problem","clientStepProblem","message","className","delay","delayMs","isInteger","dismiss","dismissOn","option","behavior","offset","offsetPx","html","removals","safe","code","url","screenId","eventName","resolved","reason","params","Object","key","slice","kept","undefined","stripped","ACTIONS_MAX_PER_HOST"],"mappings":"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"}
@@ -15,9 +15,20 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { HostTheme, HostThemeScheme } from '@aglyn/shared-data-types';
18
- import { type ArtifactOverride } from './marketplace-overrides';
18
+ import { type ArtifactOverride } from './artifact-overrides';
19
19
  /**
20
- * Publish-time validation for themes as a marketplace artifact (AGL-1020).
20
+ * A site's theme (AGL-1020, named for it in AGL-3080): whether one is complete
21
+ * and readable, what it resolves to once the site's own edits are applied over
22
+ * an installed theme, and where it came from.
23
+ *
24
+ * Every reader is a platform surface or a plugin that is not the installer —
25
+ * the published page and its manifest, every besigner canvas, the site's theme
26
+ * settings, the AI plugin's theme proposals, the themes plugin's presets — so
27
+ * it is core's. It was first written for the marketplace's publish route,
28
+ * which is why the validation below is phrased as a gate on publishing; it is
29
+ * the same gate for a theme a model proposes.
30
+ *
31
+ * ## Validating a theme
21
32
  *
22
33
  * A plugin bundle is validated because it is code and the risk is a
23
34
  * compromised site. A theme is data, so the risk is different in kind but not
@@ -13,7 +13,7 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ import { diffOverride, isEmptyOverride, overrideConflicts, overridePaths, readArtifactOverride, resolveOverride } from "./marketplace-overrides.js";
16
+ */ import { diffOverride, isEmptyOverride, overrideConflicts, overridePaths, readArtifactOverride, resolveOverride } from "./artifact-overrides.js";
17
17
  /**
18
18
  * WCAG AA for body text. The same 4.5 the accessibility audit uses, rather
19
19
  * than a house number, so a theme that passes here passes there.
@@ -183,9 +183,12 @@ const round = (ratio)=>Math.round(ratio * 10) / 10;
183
183
  }
184
184
  // A button whose label disappears into its own fill. Only checkable when the
185
185
  // theme states contrastText — otherwise MUI derives one, and derives it well.
186
+ // Every accent a button can be filled with. Tertiary is one (AGL-3411):
187
+ // `<Button color="tertiary">` is offered wherever primary and secondary are.
186
188
  for (const key of [
187
189
  'primary',
188
190
  'secondary',
191
+ 'tertiary',
189
192
  'error'
190
193
  ]){
191
194
  const color = colors[key];
@@ -532,4 +535,4 @@ const SEGMENT_LABELS = {
532
535
  }));
533
536
  }
534
537
 
535
- //# sourceMappingURL=marketplace-theme.js.map
538
+ //# sourceMappingURL=site-theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-theme.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 HostTheme,\n HostThemeFont,\n HostThemeScheme,\n HostThemeSchemeColors,\n} from '@aglyn/shared-data-types'\nimport {\n diffOverride,\n isEmptyOverride,\n overrideConflicts,\n overridePaths,\n readArtifactOverride,\n resolveOverride,\n type ArtifactOverride,\n} from './artifact-overrides'\n\n/**\n * A site's theme (AGL-1020, named for it in AGL-3080): whether one is complete\n * and readable, what it resolves to once the site's own edits are applied over\n * an installed theme, and where it came from.\n *\n * Every reader is a platform surface or a plugin that is not the installer —\n * the published page and its manifest, every besigner canvas, the site's theme\n * settings, the AI plugin's theme proposals, the themes plugin's presets — so\n * it is core's. It was first written for the marketplace's publish route,\n * which is why the validation below is phrased as a gate on publishing; it is\n * the same gate for a theme a model proposes.\n *\n * ## Validating a theme\n *\n * A plugin bundle is validated because it is code and the risk is a\n * compromised site. A theme is data, so the risk is different in kind but not\n * in consequence: the failure mode is an unreadable or half-painted site, which\n * a publisher cannot see from their own machine because their fonts are\n * installed and their eyes already know what the text says.\n *\n * Both of the things that go wrong are mechanically checkable, so they are\n * checked here rather than left to review:\n *\n * * **Completeness.** Light and dark are not one palette with a filter. A theme\n * that defines only light is half a theme, and installing it leaves every\n * dark-mode visitor on MUI's stock blue — which reads as the site being\n * broken, not as the theme being partial.\n * * **Contrast.** Text on its background, and label on its button, are the two\n * pairs that make a site unusable when they are wrong. WCAG gives an exact\n * ratio, so \"is this readable\" has an answer rather than an opinion.\n *\n * Errors refuse the publish; warnings are shown and can be published past. The\n * split is deliberate — a 3.9:1 heading is a judgement call a designer may\n * legitimately win, but a theme with no dark scheme is not a complete artifact\n * whoever is asking.\n *\n * Pure and MUI-free so the publish route, the console's pre-flight and the\n * tests all reach the same verdict from the same code.\n */\n\nexport interface ThemeValidationIssue {\n /** Dotted path into the theme, so the editor can focus the offending field. */\n path: string\n message: string\n}\n\nexport interface ThemeValidation {\n /** No errors. Warnings do not block. */\n ok: boolean\n errors: ThemeValidationIssue[]\n warnings: ThemeValidationIssue[]\n}\n\n/**\n * WCAG AA for body text. The same 4.5 the accessibility audit uses, rather\n * than a house number, so a theme that passes here passes there.\n */\nexport const CONTRAST_AA = 4.5\n\n/**\n * WCAG AA for large text (18pt+/14pt bold) and UI component boundaries. Button\n * labels are judged against this rather than 4.5 because they are large, short\n * and centred, and holding them to body-text contrast rejects palettes that are\n * genuinely fine.\n */\nexport const CONTRAST_AA_LARGE = 3\n\nconst SCHEMES: HostThemeScheme[] = ['light', 'dark']\n\n/**\n * Font families that need no loading. Anything else is either declared in the\n * theme's own `fonts` array or will silently fall back to the browser's default\n * — which is the failure the issue calls out, so it is at least a warning.\n */\nconst SYSTEM_FAMILIES = new Set(\n [\n 'system-ui',\n 'sans-serif',\n 'serif',\n 'monospace',\n 'cursive',\n 'fantasy',\n 'ui-sans-serif',\n 'ui-serif',\n 'ui-monospace',\n 'ui-rounded',\n '-apple-system',\n 'blinkmacsystemfont',\n 'segoe ui',\n 'roboto',\n 'helvetica',\n 'helvetica neue',\n 'arial',\n 'georgia',\n 'times new roman',\n 'courier new',\n 'inherit',\n 'initial',\n ].map((family) => family.toLowerCase()),\n)\n\n/**\n * Parses a CSS colour into sRGB channels, or null when it is not something we\n * can reason about.\n *\n * `null` means \"not checked\", never \"failed\": a theme is free to use `color-mix`\n * or a CSS variable, and refusing to publish because this parser is narrower\n * than CSS would be a validator inventing a rule the platform does not have.\n */\nexport function parseColor(\n value: unknown,\n): { r: number; g: number; b: number } | null {\n if (typeof value !== 'string') return null\n const text = value.trim().toLowerCase()\n\n const hex = /^#([0-9a-f]{3,8})$/.exec(text)\n if (hex) {\n const digits = hex[1]\n if (digits.length === 3 || digits.length === 4) {\n return {\n r: parseInt(digits[0] + digits[0], 16),\n g: parseInt(digits[1] + digits[1], 16),\n b: parseInt(digits[2] + digits[2], 16),\n }\n }\n if (digits.length === 6 || digits.length === 8) {\n return {\n r: parseInt(digits.slice(0, 2), 16),\n g: parseInt(digits.slice(2, 4), 16),\n b: parseInt(digits.slice(4, 6), 16),\n }\n }\n return null\n }\n\n const rgb = /^rgba?\\(([^)]+)\\)$/.exec(text)\n if (rgb) {\n const parts = rgb[1].split(/[,/\\s]+/).filter(Boolean)\n if (parts.length < 3) return null\n const channels = parts.slice(0, 3).map((part) =>\n part.endsWith('%')\n ? (Number(part.slice(0, -1)) / 100) * 255\n : Number(part),\n )\n if (channels.some((channel) => !Number.isFinite(channel))) return null\n const [r, g, b] = channels\n return { r, g, b }\n }\n\n if (text === 'white') return { r: 255, g: 255, b: 255 }\n if (text === 'black') return { r: 0, g: 0, b: 0 }\n return null\n}\n\n/** WCAG relative luminance. */\nfunction luminance(channels: { r: number; g: number; b: number }): number {\n const channel = (raw: number) => {\n const value = Math.min(255, Math.max(0, raw)) / 255\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n }\n return (\n 0.2126 * channel(channels.r) +\n 0.7152 * channel(channels.g) +\n 0.0722 * channel(channels.b)\n )\n}\n\n/**\n * WCAG contrast ratio between two colours, or null when either is unparseable.\n * Ranges 1 (identical) to 21 (black on white).\n */\nexport function contrastRatio(a: unknown, b: unknown): number | null {\n const first = parseColor(a)\n const second = parseColor(b)\n if (!first || !second) return null\n const light = Math.max(luminance(first), luminance(second))\n const dark = Math.min(luminance(first), luminance(second))\n return (light + 0.05) / (dark + 0.05)\n}\n\nconst round = (ratio: number) => Math.round(ratio * 10) / 10\n\n/**\n * The colours a scheme must actually define to be usable on its own.\n *\n * Deliberately short. MUI derives an enormous amount from very little, and\n * demanding a full palette would reject good themes; but a scheme that names no\n * primary, no background and no text has not expressed an identity at all — it\n * is an empty object that happens to have a key.\n */\nfunction validateScheme(\n scheme: HostThemeScheme,\n colors: HostThemeSchemeColors | undefined,\n errors: ThemeValidationIssue[],\n warnings: ThemeValidationIssue[],\n): void {\n const at = `colorSchemes.${scheme}`\n if (!colors || !Object.keys(colors).length) {\n errors.push({\n path: at,\n message:\n `This theme defines no ${scheme} scheme. Light and dark are not one ` +\n 'palette with a filter — install it and every visitor in ' +\n `${scheme} mode sees the stock palette instead of your design.`,\n })\n return\n }\n if (!colors.primary?.main) {\n errors.push({\n path: `${at}.primary.main`,\n message: `The ${scheme} scheme has no primary color.`,\n })\n }\n\n const background = colors.background?.default\n const paper = colors.background?.paper\n const text = colors.text?.primary\n const secondaryText = colors.text?.secondary\n\n // Text on background is the pair that decides whether a site can be read at\n // all, so it is the one contrast failure that refuses the publish.\n const bodyContrast = contrastRatio(text, background)\n if (bodyContrast != null && bodyContrast < CONTRAST_AA) {\n errors.push({\n path: `${at}.text.primary`,\n message:\n `Body text on the ${scheme} background is ${round(bodyContrast)}:1, ` +\n `below the ${CONTRAST_AA}:1 minimum for readable text.`,\n })\n }\n\n const paperContrast = contrastRatio(text, paper)\n if (paperContrast != null && paperContrast < CONTRAST_AA) {\n errors.push({\n path: `${at}.background.paper`,\n message:\n `Body text on ${scheme} cards and menus is ${round(paperContrast)}:1, ` +\n `below the ${CONTRAST_AA}:1 minimum. Surfaces inherit this everywhere ` +\n 'a Paper is used, which is most of the site.',\n })\n }\n\n // Secondary text is legitimately dimmer by design, so it warns rather than\n // refuses — but silently shipping unreadable captions is worse than saying so.\n const captionContrast = contrastRatio(secondaryText, background)\n if (captionContrast != null && captionContrast < CONTRAST_AA) {\n warnings.push({\n path: `${at}.text.secondary`,\n message:\n `Secondary text on the ${scheme} background is ` +\n `${round(captionContrast)}:1. Captions and helper text will be hard ` +\n 'to read.',\n })\n }\n\n // A button whose label disappears into its own fill. Only checkable when the\n // theme states contrastText — otherwise MUI derives one, and derives it well.\n // Every accent a button can be filled with. Tertiary is one (AGL-3411):\n // `<Button color=\"tertiary\">` is offered wherever primary and secondary are.\n for (const key of ['primary', 'secondary', 'tertiary', 'error'] as const) {\n const color = colors[key]\n const ratio = contrastRatio(color?.contrastText, color?.main)\n if (ratio != null && ratio < CONTRAST_AA_LARGE) {\n warnings.push({\n path: `${at}.${key}.contrastText`,\n message:\n `The ${scheme} ${key} button label is ${round(ratio)}:1 against its ` +\n `own fill, below ${CONTRAST_AA_LARGE}:1. Leave contrast text unset ` +\n 'to let the theme derive a readable one.',\n })\n }\n }\n}\n\n/**\n * Every family named in the typography, split out of its CSS stack.\n *\n * A stack is a fallback chain, so only the FIRST entry is the one the design\n * depends on; the rest exist precisely to be substituted and warning about them\n * would be noise.\n */\nfunction declaredFamilies(theme: HostTheme): string[] {\n const stacks: string[] = []\n if (theme.typography?.fontFamily) stacks.push(theme.typography.fontFamily)\n for (const variant of Object.values(theme.typography?.variants ?? {})) {\n if (variant?.fontFamily) stacks.push(variant.fontFamily)\n }\n const primary: string[] = []\n for (const stack of stacks) {\n const first = stack.split(',')[0]?.trim().replace(/^[\"']|[\"']$/g, '')\n if (first) primary.push(first)\n }\n return primary\n}\n\nfunction validateFonts(\n theme: HostTheme,\n warnings: ThemeValidationIssue[],\n): void {\n const loadable = new Set(\n (theme.fonts ?? [])\n .map((font: HostThemeFont) => font.family?.trim().toLowerCase())\n .filter(Boolean) as string[],\n )\n const seen = new Set<string>()\n for (const family of declaredFamilies(theme)) {\n const key = family.toLowerCase()\n if (seen.has(key) || loadable.has(key) || SYSTEM_FAMILIES.has(key)) continue\n seen.add(key)\n warnings.push({\n path: 'typography.fontFamily',\n message:\n `\"${family}\" is used but not listed in the theme's fonts, so it will ` +\n 'only appear on machines that happen to have it installed. Add it as ' +\n 'a Google font, or fall back to a system family.',\n })\n }\n}\n\n/**\n * Validates a theme for publishing.\n *\n * The same function backs the console's pre-flight and the publish route: a\n * publisher who is told \"this will be refused\" and then has it accepted (or the\n * reverse) stops trusting either, so there is one verdict from one place.\n */\nexport function validateThemeForPublish(\n theme: HostTheme | null | undefined,\n): ThemeValidation {\n const errors: ThemeValidationIssue[] = []\n const warnings: ThemeValidationIssue[] = []\n\n if (!theme || !Object.keys(theme).length) {\n return {\n ok: false,\n errors: [\n {\n path: '',\n message:\n 'This site has no theme customisations yet, so there is nothing ' +\n 'to publish.',\n },\n ],\n warnings,\n }\n }\n\n for (const scheme of SCHEMES) {\n validateScheme(scheme, theme.colorSchemes?.[scheme], errors, warnings)\n }\n validateFonts(theme, warnings)\n\n return { ok: !errors.length, errors, warnings }\n}\n\n/**\n * Every field of a `HostTheme` that is part of the published artifact.\n *\n * Typed as a total record over `keyof HostTheme` on purpose: adding a field to\n * `HostTheme` fails to compile here until someone decides whether it ships. The\n * alternative — a hand-written list of `if` statements — silently drops the new\n * field, and a theme that loses one is not obviously broken, it is subtly\n * wrong. `mixins` arrived exactly that way (AGL-1242, toolbar heights) while\n * this was being written, which is why the check is a type and not a comment.\n *\n * A `false` means \"host-local, never published\". `darkScheme` is the one such\n * field: whether visitors get a dark scheme is a decision about a SITE's\n * content (does it read in dark?), not about the design being published, so a\n * publisher's opt-out must not travel with the theme and an installer's must\n * not read as a divergence from it.\n */\nconst THEME_ARTIFACT_FIELDS: Record<keyof Required<HostTheme>, boolean> = {\n colorSchemes: true,\n typography: true,\n fonts: true,\n shape: true,\n spacing: true,\n mixins: true,\n components: true,\n darkScheme: false,\n}\n\n/**\n * The theme content as published — the artifact and nothing else.\n *\n * What goes in here is what \"reset to the publisher's version\" restores and\n * what the update diff compares against, so it must hold no per-install data:\n * a host document also carries `updatedAt`, `updatedBy` and whatever else the\n * editor writes alongside, and any of those in the content would read as a user\n * edit on the next diff.\n *\n * Empty branches are dropped so two themes that render identically also hash\n * identically — `{fonts: []}` and `{}` must not produce two base snapshots.\n */\nexport function themeArtifactContent(\n theme: HostTheme | null | undefined,\n): HostTheme {\n const content: Record<string, unknown> = {}\n if (!theme) return content\n for (const [key, ships] of Object.entries(THEME_ARTIFACT_FIELDS)) {\n if (!ships) continue\n const value = (theme as Record<string, unknown>)[key]\n if (value == null) continue\n if (Array.isArray(value) ? !value.length : typeof value === 'object'\n ? !Object.keys(value as object).length\n : false) {\n continue\n }\n content[key] = value\n }\n return content\n}\n\n/**\n * A short, human summary of what a theme actually contains, for the install\n * confirmation and the listing card.\n *\n * Installing a theme replaces a site's visual identity, so the confirmation has\n * to say what is about to change in terms someone recognises — \"both schemes,\n * 2 fonts, 4 component styles\" — rather than showing them a JSON blob and\n * asking them to agree to it.\n */\nexport function describeTheme(theme: HostTheme | null | undefined): string[] {\n if (!theme) return []\n const parts: string[] = []\n const schemes = SCHEMES.filter(\n (scheme) => Object.keys(theme.colorSchemes?.[scheme] ?? {}).length,\n )\n if (schemes.length === 2) parts.push('Light and dark schemes')\n else if (schemes.length === 1) parts.push(`${schemes[0]} scheme only`)\n\n const fonts = theme.fonts?.length ?? 0\n if (fonts) parts.push(`${fonts} font${fonts === 1 ? '' : 's'}`)\n if (theme.typography?.variants) {\n const count = Object.keys(theme.typography.variants).length\n if (count) parts.push(`${count} text style${count === 1 ? '' : 's'}`)\n }\n if (theme.shape?.borderRadius != null) {\n parts.push(`${theme.shape.borderRadius}px corners`)\n }\n if (typeof theme.spacing === 'number') parts.push(`${theme.spacing}px spacing`)\n const components = Object.keys(theme.components ?? {}).length\n if (components) {\n parts.push(`${components} component style${components === 1 ? '' : 's'}`)\n }\n return parts\n}\n\n/* ------------------------------------------------------------------------ *\n * The override layer, applied to themes (AGL-1021)\n * ------------------------------------------------------------------------ */\n\n/**\n * The field on a host document holding the site's theme override.\n *\n * A theme is a FIELD on the host, not a document of its own, so it cannot use\n * `ARTIFACT_OVERRIDE_FIELD` — a host may one day override more than one thing,\n * and a bare `overrides` would be the first to claim the name. The override\n * layer is general precisely so a namespaced field works with it unchanged.\n */\nexport const THEME_OVERRIDE_FIELD = 'themeOverride'\n\n/** A host document, as far as the theme layer is concerned. */\nexport interface ThemeHostDocument {\n theme?: HostTheme | null\n themeOverride?: unknown\n themeInstalledFrom?: { sha256?: string | null; listingId?: string } | null\n /** The library theme the site picked (AGL-3404) — see `theme-library`. */\n themeSelection?: unknown\n}\n\n/** Reads the stored theme override off a host document, tolerating junk. */\nexport function readThemeOverride(\n host: ThemeHostDocument | null | undefined,\n): ArtifactOverride | undefined {\n // The override layer's reader is keyed to its own field name, so hand it a\n // shim rather than duplicating its validation — which is where the \"a\n // top-level null blanks the artifact\" guard lives.\n return readArtifactOverride({ overrides: host?.themeOverride })\n}\n\n/**\n * The site's effective theme: the marketplace theme (or the site's own) with\n * this site's overrides resolved over it.\n *\n * This is the MIDDLE and TOP of the three layers the issue describes. The\n * bottom — the platform default — is applied further down by\n * `HostThemeProvider`, which layers whatever this returns onto the brand base\n * (AGL-1180) so a theme that omits a value still yields a complete theme\n * rather than an undefined-shaped hole. Doing it there rather than here is not\n * a detail: the default is a runtime MUI theme with FUNCTION style overrides\n * that JSON cannot represent, so a \"default\" merged at this layer would be a\n * lossy copy of the real one.\n *\n * Every surface that renders a site's appearance calls this — the tenant, all\n * four besigner editors, and the document preview — because a theme that\n * resolves differently in the editor than on the site is worse than one that\n * does not resolve at all.\n */\nexport function resolveSiteTheme(\n host: ThemeHostDocument | null | undefined,\n): HostTheme | undefined {\n const base = host?.theme ?? undefined\n const override = readThemeOverride(host)\n if (!override) return base\n return resolveOverride<HostTheme>(base ?? {}, override.patch)\n}\n\n/**\n * Where a site's theme came from (AGL-2938).\n *\n * - `installed` — a marketplace theme. The site's edits are its override\n * patch over the publisher's version, which stays untouched and can still\n * take an update.\n * - `preset` — a built-in theme a plugin contributes (AGL-3404).\n * - `custom` — one of the site's own themes.\n * - `default` — the platform default.\n *\n * Whichever it is, an edit is an override patch over the picked theme and the\n * theme itself is never rewritten (AGL-3404), so every source can be restored.\n *\n * One reading for every surface that needs it — the page that lays out the\n * Theme section, a widget proposing a change there, and a generator deciding\n * what its change will be stored as — so none of them can file a site under\n * a different source than the save path does.\n */\nexport type HostThemeSource = 'installed' | 'preset' | 'custom' | 'default'\n\nconst THEME_SOURCES: readonly HostThemeSource[] = [\n 'installed',\n 'preset',\n 'custom',\n 'default',\n]\n\nexport function hostThemeSource(\n host: ThemeHostDocument | null | undefined,\n): HostThemeSource {\n const picked = (host?.themeSelection as { kind?: unknown } | null | undefined)\n ?.kind\n if (THEME_SOURCES.includes(picked as HostThemeSource)) {\n return picked as HostThemeSource\n }\n if (host?.themeInstalledFrom?.listingId) return 'installed'\n return host?.theme && Object.keys(host.theme).length ? 'custom' : 'default'\n}\n\n/**\n * Was this override authored against the theme currently installed?\n *\n * False after a theme swap, which is the whole reason `baseSha256` is recorded.\n * Overrides deliberately SURVIVE a swap (the issue's call, and the right one —\n * a brand colour still expresses intent when the theme underneath it changes)\n * but surviving silently is what would surprise people, so every surface that\n * shows an override can ask this and say so.\n *\n * True when there is no override, and when either side has no hash to compare:\n * \"we cannot tell\" must not render as \"these are stale\".\n */\nexport function isOverrideForCurrentTheme(\n host: ThemeHostDocument | null | undefined,\n): boolean {\n const override = readThemeOverride(host)\n if (!override?.baseSha256) return true\n const installed = host?.themeInstalledFrom?.sha256\n if (!installed) return true\n return override.baseSha256 === installed\n}\n\n/** One row of the \"what have I changed?\" view. */\nexport interface ThemeOverrideEntry {\n /** Dotted path into the theme, e.g. `colorSchemes.dark.primary.main`. */\n path: string\n /** A human label for the path, for a UI that is not a JSON viewer. */\n label: string\n /** Which scheme this touches, when it touches one. */\n scheme?: HostThemeScheme\n /** The publisher's value, or undefined when the override adds the field. */\n themeValue: unknown\n /** What this site set it to, or undefined when the override removes it. */\n overrideValue: unknown\n}\n\nconst SEGMENT_LABELS: Record<string, string> = {\n darkScheme: 'Dark scheme',\n colorSchemes: 'Color',\n typography: 'Typography',\n components: 'Component',\n shape: 'Shape',\n spacing: 'Spacing',\n fonts: 'Fonts',\n mixins: 'Layout',\n variants: '',\n background: 'background',\n text: 'text',\n main: '',\n contrastText: 'label color',\n borderRadius: 'corner radius',\n fontFamily: 'font',\n}\n\n/**\n * A readable label for a theme path.\n *\n * \"Colour · dark · primary\" beats `colorSchemes.dark.primary.main`, and the\n * difference matters because this list is the answer to \"what have I changed?\"\n * — a question nobody asks wanting to read JSON paths.\n */\nexport function describeThemePath(path: string): string {\n const segments = path.split('.').filter(Boolean)\n const words: string[] = []\n for (const segment of segments) {\n const mapped = SEGMENT_LABELS[segment]\n if (mapped === '') continue\n words.push(mapped ?? segment)\n }\n return words.join(' · ') || 'the whole theme'\n}\n\n/**\n * \"What have I changed?\" — the patch, rendered.\n *\n * Literally a read of the stored override rather than a diff computed at call\n * time, which is the property the whole layer exists to provide: the answer is\n * the thing that was saved, so it cannot disagree with what will be applied.\n */\nexport function describeThemeOverride(\n host: ThemeHostDocument | null | undefined,\n): ThemeOverrideEntry[] {\n const override = readThemeOverride(host)\n if (!override) return []\n const base = host?.theme ?? {}\n const resolved = resolveOverride<HostTheme>(base, override.patch)\n return overridePaths(override.patch).map((path) => {\n const segments = path.split('.')\n const scheme =\n segments[0] === 'colorSchemes' &&\n (segments[1] === 'light' || segments[1] === 'dark')\n ? (segments[1] as HostThemeScheme)\n : undefined\n return {\n path,\n label: describeThemePath(path),\n scheme,\n themeValue: readThemePath(base, path),\n overrideValue: readThemePath(resolved, path),\n }\n })\n}\n\n/** Reads a dotted path out of a theme. */\nfunction readThemePath(theme: unknown, path: string): unknown {\n let cursor: unknown = theme\n for (const segment of path.split('.')) {\n if (cursor == null || typeof cursor !== 'object') return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * The override this site should store after the theme editor produced `edited`.\n *\n * The editor edits the RESOLVED theme — that is what it renders and what the\n * user sees — so the patch is the difference between the publisher's base and\n * what came back. Diffing here rather than tracking per-field dirty state means\n * a field the user set and then set back to the theme's value stops being an\n * override, which is what \"reset\" means without needing a reset button to have\n * been pressed.\n */\nexport function themeOverridePatch(\n host: ThemeHostDocument | null | undefined,\n edited: HostTheme,\n): unknown {\n return diffOverride(host?.theme ?? {}, edited)\n}\n\n/**\n * Where this site's overrides sit on top of values a theme update also changes\n * — the only place a conflict can exist.\n *\n * Narrow by construction: an override touching four paths can conflict in at\n * most four places however much of the theme the publisher rewrote. That is the\n * difference between \"the publisher also changed your heading colour, whose\n * wins?\" and \"this update rewrites your theme, continue?\".\n */\nexport function themeUpdateConflicts(\n host: ThemeHostDocument | null | undefined,\n incoming: HostTheme,\n): ThemeOverrideEntry[] {\n const override = readThemeOverride(host)\n if (!override || isEmptyOverride(override.patch)) return []\n const base = host?.theme ?? {}\n const resolved = resolveOverride<HostTheme>(base, override.patch)\n return overrideConflicts(base, override.patch, incoming).map((path) => ({\n path,\n label: describeThemePath(path),\n scheme:\n path.startsWith('colorSchemes.light.')\n ? ('light' as const)\n : path.startsWith('colorSchemes.dark.')\n ? ('dark' as const)\n : undefined,\n themeValue: readThemePath(incoming, path),\n overrideValue: readThemePath(resolved, path),\n }))\n}\n"],"names":["diffOverride","isEmptyOverride","overrideConflicts","overridePaths","readArtifactOverride","resolveOverride","CONTRAST_AA","CONTRAST_AA_LARGE","SCHEMES","SYSTEM_FAMILIES","Set","map","family","toLowerCase","parseColor","value","text","trim","hex","exec","digits","length","r","parseInt","g","b","slice","rgb","parts","split","filter","Boolean","channels","part","endsWith","Number","some","channel","isFinite","luminance","raw","Math","min","max","contrastRatio","a","first","second","light","dark","round","ratio","validateScheme","scheme","colors","errors","warnings","at","Object","keys","push","path","message","primary","main","background","default","paper","secondaryText","secondary","bodyContrast","paperContrast","captionContrast","key","color","contrastText","declaredFamilies","theme","stacks","typography","fontFamily","variant","values","variants","stack","replace","validateFonts","loadable","fonts","font","seen","has","add","validateThemeForPublish","ok","colorSchemes","THEME_ARTIFACT_FIELDS","shape","spacing","mixins","components","darkScheme","themeArtifactContent","content","ships","entries","Array","isArray","describeTheme","schemes","count","borderRadius","THEME_OVERRIDE_FIELD","readThemeOverride","host","overrides","themeOverride","resolveSiteTheme","base","undefined","override","patch","THEME_SOURCES","hostThemeSource","picked","themeSelection","kind","includes","themeInstalledFrom","listingId","isOverrideForCurrentTheme","baseSha256","installed","sha256","SEGMENT_LABELS","describeThemePath","segments","words","segment","mapped","join","describeThemeOverride","resolved","label","themeValue","readThemePath","overrideValue","cursor","themeOverridePatch","edited","themeUpdateConflicts","incoming","startsWith"],"mappings":"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"}
@@ -33,10 +33,11 @@ export declare const SITEMAP_URLS_PER_FILE = 5000;
33
33
  * it is a sitemap. 100 pages is 500,000 URLs in one section.
34
34
  */
35
35
  export declare const SITEMAP_MAX_PAGES_PER_SECTION = 100;
36
- /** The fixed sections; a content collection adds one of its own. */
36
+ /**
37
+ * The platform's fixed sections. A content collection adds one of its own,
38
+ * and a plugin declares any its pages fill (`plugin-sitemap-sections`).
39
+ */
37
40
  export declare const SITEMAP_SECTION_PAGES = "pages";
38
- export declare const SITEMAP_SECTION_PRODUCTS = "products";
39
- export declare const SITEMAP_SECTION_CATALOG = "catalog";
40
41
  /**
41
42
  * Author pages (AGL-2518) — `/author/{slug}`, one per author record.
42
43
  *
@@ -17,8 +17,9 @@
17
17
  * Sitemap addressing and XML (AGL-2520).
18
18
  *
19
19
  * A site publishes ONE sitemap index at `/sitemap.xml` and a child sitemap per
20
- * section — pages, products, the catalog, and one per content collection —
21
- * each of which pages further at {@link SITEMAP_URLS_PER_FILE}.
20
+ * section — pages, authors, one per content collection, and any a plugin
21
+ * declares for its own pages — each of which pages further at
22
+ * {@link SITEMAP_URLS_PER_FILE}.
22
23
  *
23
24
  * The split is what makes the sitemap survive a site growing. The protocol
24
25
  * caps a single sitemap at 50,000 URLs and 50MB, and a flat file has no
@@ -47,9 +48,10 @@
47
48
  * index has its own 50,000 limit, and a sweep that large is a bug long before
48
49
  * it is a sitemap. 100 pages is 500,000 URLs in one section.
49
50
  */ export const SITEMAP_MAX_PAGES_PER_SECTION = 100;
50
- /** The fixed sections; a content collection adds one of its own. */ export const SITEMAP_SECTION_PAGES = 'pages';
51
- export const SITEMAP_SECTION_PRODUCTS = 'products';
52
- export const SITEMAP_SECTION_CATALOG = 'catalog';
51
+ /**
52
+ * The platform's fixed sections. A content collection adds one of its own,
53
+ * and a plugin declares any its pages fill (`plugin-sitemap-sections`).
54
+ */ export const SITEMAP_SECTION_PAGES = 'pages';
53
55
  /**
54
56
  * Author pages (AGL-2518) — `/author/{slug}`, one per author record.
55
57
  *
@@ -61,8 +63,8 @@ export const SITEMAP_SECTION_CATALOG = 'catalog';
61
63
  /**
62
64
  * The prefix a content collection's section carries.
63
65
  *
64
- * It exists so a collection slugged `products` cannot claim the commerce
65
- * section, and it cannot itself collide: stripping happens ONCE, so a
66
+ * It exists so a collection slugged like a section a plugin declares cannot
67
+ * claim it, and it cannot itself collide: stripping happens ONCE, so a
66
68
  * collection actually slugged `content-products` addresses
67
69
  * `content-content-products` and stays distinct from `content-products`,
68
70
  * which is the collection slugged `products`.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/sitemap.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 * Sitemap addressing and XML (AGL-2520).\n *\n * A site publishes ONE sitemap index at `/sitemap.xml` and a child sitemap per\n * section — pages, products, the catalog, and one per content collection —\n * each of which pages further at {@link SITEMAP_URLS_PER_FILE}.\n *\n * The split is what makes the sitemap survive a site growing. The protocol\n * caps a single sitemap at 50,000 URLs and 50MB, and a flat file has no\n * answer once a site crosses either: the URLs past the cap are simply not\n * submitted, silently. It also makes the sweep behind each file bounded —\n * a crawler asking for `/sitemaps/content-blog/2.xml` reads one page of one\n * collection's entries instead of the whole site.\n *\n * These are pure functions on purpose: the tenant route handler that answers\n * these URLs and the middleware that routes them to it must agree on the\n * shape exactly, and so must the tests.\n */\n\nimport { toEpochMs } from './lockdown'\n\n/** Where a site's sitemap index lives; what `robots.txt` advertises. */\nexport const SITEMAP_INDEX_PATH = '/sitemap.xml'\n\n/**\n * URLs per child sitemap.\n *\n * An order of magnitude under the protocol's 50,000 because the cap that\n * binds first here is not the protocol's but Firestore's: a page of this file\n * is a page of a query, and 5,000 keeps one crawler fetch to 5,000 document\n * reads and a few hundred KB of XML. Raising it trades read cost for fewer\n * files, and nothing in the format cares which side of that trade we take.\n */\nexport const SITEMAP_URLS_PER_FILE = 5000\n\n/**\n * A ceiling on how many pages one section may advertise, so a runaway\n * collection cannot produce an index with tens of thousands of entries — the\n * index has its own 50,000 limit, and a sweep that large is a bug long before\n * it is a sitemap. 100 pages is 500,000 URLs in one section.\n */\nexport const SITEMAP_MAX_PAGES_PER_SECTION = 100\n\n/** The fixed sections; a content collection adds one of its own. */\nexport const SITEMAP_SECTION_PAGES = 'pages'\nexport const SITEMAP_SECTION_PRODUCTS = 'products'\nexport const SITEMAP_SECTION_CATALOG = 'catalog'\n/**\n * Author pages (AGL-2518) — `/author/{slug}`, one per author record.\n *\n * A fixed section rather than a per-collection one, because there is one\n * author page per PERSON and it spans every collection they wrote in. A\n * `content-…` section per collection would submit the same author several\n * times over, at addresses that no longer exist.\n */\nexport const SITEMAP_SECTION_AUTHORS = 'authors'\n\n/**\n * The prefix a content collection's section carries.\n *\n * It exists so a collection slugged `products` cannot claim the commerce\n * section, and it cannot itself collide: stripping happens ONCE, so a\n * collection actually slugged `content-products` addresses\n * `content-content-products` and stays distinct from `content-products`,\n * which is the collection slugged `products`.\n */\nconst CONTENT_SECTION_PREFIX = 'content-'\n\n/** The sitemap section a content collection's URLs live in. */\nexport function contentSitemapSection(collectionSlug: string): string {\n return `${CONTENT_SECTION_PREFIX}${collectionSlug}`\n}\n\n/** The collection a `content-…` section names, or `undefined` for the rest. */\nexport function contentSitemapSectionSlug(\n section: string,\n): string | undefined {\n if (!section.startsWith(CONTENT_SECTION_PREFIX)) return undefined\n const slug = section.slice(CONTENT_SECTION_PREFIX.length)\n return slug || undefined\n}\n\n/**\n * A child sitemap's path.\n *\n * The page is ALWAYS its own segment, including page 1. A one-file section is\n * the common case and could have been the bare `/sitemaps/{section}.xml`, but\n * then the page number would have to be encoded into the section name for\n * every other page — and `content-blog-2` is ambiguous between page 2 of the\n * collection `blog` and page 1 of the collection `blog-2`, which is a real\n * slug someone can create. A separate segment cannot be ambiguous, and\n * nothing but the index ever writes these URLs.\n */\nexport function sitemapSectionPath(section: string, page: number): string {\n return `/sitemaps/${section}/${Math.max(1, Math.floor(page))}.xml`\n}\n\n/** Matches {@link sitemapSectionPath}; the one parser, shared with the middleware. */\nconst SITEMAP_SECTION_PATH = /^\\/sitemaps\\/([^/]+)\\/([0-9]+)\\.xml$/\n\n/** The section and page a path addresses, or `undefined` if it addresses none. */\nexport function parseSitemapSectionPath(\n pathname: string,\n): { section: string; page: number } | undefined {\n const match = SITEMAP_SECTION_PATH.exec(pathname)\n if (!match) return undefined\n const page = Number(match[2])\n if (!Number.isFinite(page) || page < 1) return undefined\n return { section: decodeURIComponent(match[1]), page }\n}\n\n/**\n * How many files a section of `urlCount` URLs needs. Zero URLs means zero\n * files — a section with nothing in it is left out of the index rather than\n * advertised as an empty document a crawler has to fetch to discover is empty.\n */\nexport function sitemapPageCount(urlCount: number): number {\n if (!Number.isFinite(urlCount) || urlCount <= 0) return 0\n return Math.min(\n SITEMAP_MAX_PAGES_PER_SECTION,\n Math.ceil(urlCount / SITEMAP_URLS_PER_FILE),\n )\n}\n\n/** XML text escaping — a slug or a query string may carry any of these. */\nexport function escapeSitemapXml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\n/**\n * One address in a sitemap document. A bare string is an address with no\n * known modification date; the object form carries a `lastmod` when the\n * builder could derive one.\n */\nexport type SitemapLocation = string | { loc: string; lastmod?: string }\n\n/**\n * A `<lastmod>` value from whatever timestamp shape a document carries\n * (AGL-2647): a Firestore `Timestamp` (`toMillis()`), the `{seconds}` object\n * a serialized one decays into, epoch milliseconds, or an ISO string.\n *\n * The DAY only, in UTC. `YYYY-MM-DD` is a complete W3C datetime, and a date a\n * crawler can compare is worth more than a time it cannot: a value that moved\n * with every render would be a `lastmod` crawlers learn to discount, and the\n * whole point of the element is to be believed.\n *\n * `undefined` for anything that does not decode, and the caller omits the\n * element. Never \"now\": an invented date claims a change that did not\n * happen, and a sitemap whose dates are consistently wrong is read as having\n * none at all.\n */\nexport function sitemapLastmod(value: unknown): string | undefined {\n const ms = toEpochMs(value)\n if (ms === undefined || ms <= 0) return undefined\n return new Date(ms).toISOString().slice(0, 10)\n}\n\n/** The `lastmod` a location carries, if any. */\nexport function sitemapLocationLastmod(\n location: SitemapLocation,\n): string | undefined {\n return typeof location === 'string' ? undefined : location.lastmod\n}\n\n/**\n * The newest of some `lastmod` values, or `undefined` when none is known.\n *\n * What a listing page and an index entry are dated by: a listing changed when\n * its newest entry did, and a child sitemap changed when its newest URL did.\n * Comparison is on the string, which is sound because every value is the\n * fixed-width `YYYY-MM-DD` {@link sitemapLastmod} produces.\n */\nexport function newestSitemapLastmod(\n lastmods: Iterable<string | undefined>,\n): string | undefined {\n let newest: string | undefined\n for (const lastmod of lastmods) {\n if (lastmod && (!newest || lastmod > newest)) newest = lastmod\n }\n return newest\n}\n\n/**\n * De-duplicated (AGL-582): a collection slug can shadow a screen path, and\n * the same URL twice in one sitemap is a submission of a duplicate, not a\n * stronger signal. The FIRST occurrence keeps its date — the routing order\n * that decides which page a shadowed URL serves is the order the builders\n * emit in, so the first source is the one whose date describes the page.\n */\nfunction uniqueSitemapLocations(\n locations: readonly SitemapLocation[],\n): Array<{ loc: string; lastmod?: string }> {\n const byLoc = new Map<string, { loc: string; lastmod?: string }>()\n for (const location of locations) {\n const entry = typeof location === 'string' ? { loc: location } : location\n if (!byLoc.has(entry.loc)) {\n byLoc.set(entry.loc, { loc: entry.loc, lastmod: entry.lastmod })\n }\n }\n return [...byLoc.values()]\n}\n\n/** `<url>` or `<sitemap>`; the `<lastmod>` child only when a date is known. */\nfunction sitemapLocationXml(\n tag: 'url' | 'sitemap',\n { loc, lastmod }: { loc: string; lastmod?: string },\n): string {\n const date = lastmod ? `<lastmod>${escapeSitemapXml(lastmod)}</lastmod>` : ''\n return ` <${tag}><loc>${escapeSitemapXml(loc)}</loc>${date}</${tag}>`\n}\n\n/** A `<urlset>` document. */\nexport function sitemapUrlsetXml(urls: readonly SitemapLocation[]): string {\n return (\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' +\n '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n' +\n uniqueSitemapLocations(urls)\n .map((item) => sitemapLocationXml('url', item))\n .join('\\n') +\n '\\n</urlset>\\n'\n )\n}\n\n/** A `<sitemapindex>` document — the children of `/sitemap.xml`. */\nexport function sitemapIndexXml(locs: readonly SitemapLocation[]): string {\n return (\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' +\n '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n' +\n uniqueSitemapLocations(locs)\n .map((item) => sitemapLocationXml('sitemap', item))\n .join('\\n') +\n '\\n</sitemapindex>\\n'\n )\n}\n"],"names":["toEpochMs","SITEMAP_INDEX_PATH","SITEMAP_URLS_PER_FILE","SITEMAP_MAX_PAGES_PER_SECTION","SITEMAP_SECTION_PAGES","SITEMAP_SECTION_PRODUCTS","SITEMAP_SECTION_CATALOG","SITEMAP_SECTION_AUTHORS","CONTENT_SECTION_PREFIX","contentSitemapSection","collectionSlug","contentSitemapSectionSlug","section","startsWith","undefined","slug","slice","length","sitemapSectionPath","page","Math","max","floor","SITEMAP_SECTION_PATH","parseSitemapSectionPath","pathname","match","exec","Number","isFinite","decodeURIComponent","sitemapPageCount","urlCount","min","ceil","escapeSitemapXml","value","replace","sitemapLastmod","ms","Date","toISOString","sitemapLocationLastmod","location","lastmod","newestSitemapLastmod","lastmods","newest","uniqueSitemapLocations","locations","byLoc","Map","entry","loc","has","set","values","sitemapLocationXml","tag","date","sitemapUrlsetXml","urls","map","item","join","sitemapIndexXml","locs"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/sitemap.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 * Sitemap addressing and XML (AGL-2520).\n *\n * A site publishes ONE sitemap index at `/sitemap.xml` and a child sitemap per\n * section — pages, authors, one per content collection, and any a plugin\n * declares for its own pages — each of which pages further at\n * {@link SITEMAP_URLS_PER_FILE}.\n *\n * The split is what makes the sitemap survive a site growing. The protocol\n * caps a single sitemap at 50,000 URLs and 50MB, and a flat file has no\n * answer once a site crosses either: the URLs past the cap are simply not\n * submitted, silently. It also makes the sweep behind each file bounded —\n * a crawler asking for `/sitemaps/content-blog/2.xml` reads one page of one\n * collection's entries instead of the whole site.\n *\n * These are pure functions on purpose: the tenant route handler that answers\n * these URLs and the middleware that routes them to it must agree on the\n * shape exactly, and so must the tests.\n */\n\nimport { toEpochMs } from './lockdown'\n\n/** Where a site's sitemap index lives; what `robots.txt` advertises. */\nexport const SITEMAP_INDEX_PATH = '/sitemap.xml'\n\n/**\n * URLs per child sitemap.\n *\n * An order of magnitude under the protocol's 50,000 because the cap that\n * binds first here is not the protocol's but Firestore's: a page of this file\n * is a page of a query, and 5,000 keeps one crawler fetch to 5,000 document\n * reads and a few hundred KB of XML. Raising it trades read cost for fewer\n * files, and nothing in the format cares which side of that trade we take.\n */\nexport const SITEMAP_URLS_PER_FILE = 5000\n\n/**\n * A ceiling on how many pages one section may advertise, so a runaway\n * collection cannot produce an index with tens of thousands of entries — the\n * index has its own 50,000 limit, and a sweep that large is a bug long before\n * it is a sitemap. 100 pages is 500,000 URLs in one section.\n */\nexport const SITEMAP_MAX_PAGES_PER_SECTION = 100\n\n/**\n * The platform's fixed sections. A content collection adds one of its own,\n * and a plugin declares any its pages fill (`plugin-sitemap-sections`).\n */\nexport const SITEMAP_SECTION_PAGES = 'pages'\n/**\n * Author pages (AGL-2518) — `/author/{slug}`, one per author record.\n *\n * A fixed section rather than a per-collection one, because there is one\n * author page per PERSON and it spans every collection they wrote in. A\n * `content-…` section per collection would submit the same author several\n * times over, at addresses that no longer exist.\n */\nexport const SITEMAP_SECTION_AUTHORS = 'authors'\n\n/**\n * The prefix a content collection's section carries.\n *\n * It exists so a collection slugged like a section a plugin declares cannot\n * claim it, and it cannot itself collide: stripping happens ONCE, so a\n * collection actually slugged `content-products` addresses\n * `content-content-products` and stays distinct from `content-products`,\n * which is the collection slugged `products`.\n */\nconst CONTENT_SECTION_PREFIX = 'content-'\n\n/** The sitemap section a content collection's URLs live in. */\nexport function contentSitemapSection(collectionSlug: string): string {\n return `${CONTENT_SECTION_PREFIX}${collectionSlug}`\n}\n\n/** The collection a `content-…` section names, or `undefined` for the rest. */\nexport function contentSitemapSectionSlug(\n section: string,\n): string | undefined {\n if (!section.startsWith(CONTENT_SECTION_PREFIX)) return undefined\n const slug = section.slice(CONTENT_SECTION_PREFIX.length)\n return slug || undefined\n}\n\n/**\n * A child sitemap's path.\n *\n * The page is ALWAYS its own segment, including page 1. A one-file section is\n * the common case and could have been the bare `/sitemaps/{section}.xml`, but\n * then the page number would have to be encoded into the section name for\n * every other page — and `content-blog-2` is ambiguous between page 2 of the\n * collection `blog` and page 1 of the collection `blog-2`, which is a real\n * slug someone can create. A separate segment cannot be ambiguous, and\n * nothing but the index ever writes these URLs.\n */\nexport function sitemapSectionPath(section: string, page: number): string {\n return `/sitemaps/${section}/${Math.max(1, Math.floor(page))}.xml`\n}\n\n/** Matches {@link sitemapSectionPath}; the one parser, shared with the middleware. */\nconst SITEMAP_SECTION_PATH = /^\\/sitemaps\\/([^/]+)\\/([0-9]+)\\.xml$/\n\n/** The section and page a path addresses, or `undefined` if it addresses none. */\nexport function parseSitemapSectionPath(\n pathname: string,\n): { section: string; page: number } | undefined {\n const match = SITEMAP_SECTION_PATH.exec(pathname)\n if (!match) return undefined\n const page = Number(match[2])\n if (!Number.isFinite(page) || page < 1) return undefined\n return { section: decodeURIComponent(match[1]), page }\n}\n\n/**\n * How many files a section of `urlCount` URLs needs. Zero URLs means zero\n * files — a section with nothing in it is left out of the index rather than\n * advertised as an empty document a crawler has to fetch to discover is empty.\n */\nexport function sitemapPageCount(urlCount: number): number {\n if (!Number.isFinite(urlCount) || urlCount <= 0) return 0\n return Math.min(\n SITEMAP_MAX_PAGES_PER_SECTION,\n Math.ceil(urlCount / SITEMAP_URLS_PER_FILE),\n )\n}\n\n/** XML text escaping — a slug or a query string may carry any of these. */\nexport function escapeSitemapXml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\n/**\n * One address in a sitemap document. A bare string is an address with no\n * known modification date; the object form carries a `lastmod` when the\n * builder could derive one.\n */\nexport type SitemapLocation = string | { loc: string; lastmod?: string }\n\n/**\n * A `<lastmod>` value from whatever timestamp shape a document carries\n * (AGL-2647): a Firestore `Timestamp` (`toMillis()`), the `{seconds}` object\n * a serialized one decays into, epoch milliseconds, or an ISO string.\n *\n * The DAY only, in UTC. `YYYY-MM-DD` is a complete W3C datetime, and a date a\n * crawler can compare is worth more than a time it cannot: a value that moved\n * with every render would be a `lastmod` crawlers learn to discount, and the\n * whole point of the element is to be believed.\n *\n * `undefined` for anything that does not decode, and the caller omits the\n * element. Never \"now\": an invented date claims a change that did not\n * happen, and a sitemap whose dates are consistently wrong is read as having\n * none at all.\n */\nexport function sitemapLastmod(value: unknown): string | undefined {\n const ms = toEpochMs(value)\n if (ms === undefined || ms <= 0) return undefined\n return new Date(ms).toISOString().slice(0, 10)\n}\n\n/** The `lastmod` a location carries, if any. */\nexport function sitemapLocationLastmod(\n location: SitemapLocation,\n): string | undefined {\n return typeof location === 'string' ? undefined : location.lastmod\n}\n\n/**\n * The newest of some `lastmod` values, or `undefined` when none is known.\n *\n * What a listing page and an index entry are dated by: a listing changed when\n * its newest entry did, and a child sitemap changed when its newest URL did.\n * Comparison is on the string, which is sound because every value is the\n * fixed-width `YYYY-MM-DD` {@link sitemapLastmod} produces.\n */\nexport function newestSitemapLastmod(\n lastmods: Iterable<string | undefined>,\n): string | undefined {\n let newest: string | undefined\n for (const lastmod of lastmods) {\n if (lastmod && (!newest || lastmod > newest)) newest = lastmod\n }\n return newest\n}\n\n/**\n * De-duplicated (AGL-582): a collection slug can shadow a screen path, and\n * the same URL twice in one sitemap is a submission of a duplicate, not a\n * stronger signal. The FIRST occurrence keeps its date — the routing order\n * that decides which page a shadowed URL serves is the order the builders\n * emit in, so the first source is the one whose date describes the page.\n */\nfunction uniqueSitemapLocations(\n locations: readonly SitemapLocation[],\n): Array<{ loc: string; lastmod?: string }> {\n const byLoc = new Map<string, { loc: string; lastmod?: string }>()\n for (const location of locations) {\n const entry = typeof location === 'string' ? { loc: location } : location\n if (!byLoc.has(entry.loc)) {\n byLoc.set(entry.loc, { loc: entry.loc, lastmod: entry.lastmod })\n }\n }\n return [...byLoc.values()]\n}\n\n/** `<url>` or `<sitemap>`; the `<lastmod>` child only when a date is known. */\nfunction sitemapLocationXml(\n tag: 'url' | 'sitemap',\n { loc, lastmod }: { loc: string; lastmod?: string },\n): string {\n const date = lastmod ? `<lastmod>${escapeSitemapXml(lastmod)}</lastmod>` : ''\n return ` <${tag}><loc>${escapeSitemapXml(loc)}</loc>${date}</${tag}>`\n}\n\n/** A `<urlset>` document. */\nexport function sitemapUrlsetXml(urls: readonly SitemapLocation[]): string {\n return (\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' +\n '<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n' +\n uniqueSitemapLocations(urls)\n .map((item) => sitemapLocationXml('url', item))\n .join('\\n') +\n '\\n</urlset>\\n'\n )\n}\n\n/** A `<sitemapindex>` document — the children of `/sitemap.xml`. */\nexport function sitemapIndexXml(locs: readonly SitemapLocation[]): string {\n return (\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' +\n '<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\\n' +\n uniqueSitemapLocations(locs)\n .map((item) => sitemapLocationXml('sitemap', item))\n .join('\\n') +\n '\\n</sitemapindex>\\n'\n )\n}\n"],"names":["toEpochMs","SITEMAP_INDEX_PATH","SITEMAP_URLS_PER_FILE","SITEMAP_MAX_PAGES_PER_SECTION","SITEMAP_SECTION_PAGES","SITEMAP_SECTION_AUTHORS","CONTENT_SECTION_PREFIX","contentSitemapSection","collectionSlug","contentSitemapSectionSlug","section","startsWith","undefined","slug","slice","length","sitemapSectionPath","page","Math","max","floor","SITEMAP_SECTION_PATH","parseSitemapSectionPath","pathname","match","exec","Number","isFinite","decodeURIComponent","sitemapPageCount","urlCount","min","ceil","escapeSitemapXml","value","replace","sitemapLastmod","ms","Date","toISOString","sitemapLocationLastmod","location","lastmod","newestSitemapLastmod","lastmods","newest","uniqueSitemapLocations","locations","byLoc","Map","entry","loc","has","set","values","sitemapLocationXml","tag","date","sitemapUrlsetXml","urls","map","item","join","sitemapIndexXml","locs"],"mappings":"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"}
@@ -32,3 +32,54 @@ export declare function isStaleBuildError(error: unknown): boolean;
32
32
  * never be a second error.
33
33
  */
34
34
  export declare function shouldReloadForStaleBuild(error: unknown): boolean;
35
+ /**
36
+ * What EVERY error boundary does with what it caught: reload once for a stale
37
+ * build, and otherwise hand the error to the beacon. `global-error.tsx`
38
+ * included: it catches what the root layout threw, and a stale tab's root
39
+ * layout asks for its chunks like any other segment.
40
+ *
41
+ * One function so that no boundary can run half of it. The ones that matter
42
+ * most are not the root boundaries but the two below them on a published
43
+ * site — `PageBodyBoundary` around the page body and
44
+ * `[host]/[scheme]/error.tsx` around the page — because a site plugin's chunk
45
+ * is loaded by the page body, so those are what a stale tab reaches. A
46
+ * boundary there that only re-dispatched would leave an empty body, or a
47
+ * **Try again** that cannot work, and report a `ChunkLoadError` for it.
48
+ *
49
+ * A reloaded error is NOT reported; the fresh document is the fix, and the
50
+ * tab it happened in is gone. What still reaches the beacon is the failure a
51
+ * reload did not cure — a second one inside the recovery window, or a
52
+ * browser that refuses storage — which the beacon labels `chunk-load`.
53
+ *
54
+ * `reload` is a parameter only so a spec can observe it: jsdom refuses to let
55
+ * one redefine `location.reload`.
56
+ *
57
+ * @returns whether the tab is reloading.
58
+ */
59
+ export declare function recoverStaleBuildOrReport(error: unknown, reload?: () => void): boolean;
60
+ /**
61
+ * THE SAME TAB, WHEN NO BOUNDARY SAW IT (AGL-3423).
62
+ *
63
+ * An `import()` that nothing awaits inside a render, a click handler's lazy
64
+ * module or an effect's, rejects past every boundary and reaches `window` as
65
+ * an `unhandledrejection`. The beacon labels it `chunk-load`, and until now
66
+ * that was all: the tab stayed on the old build with one feature silently
67
+ * dead.
68
+ *
69
+ * The recovery differs from a boundary's in WHEN it reloads. A boundary
70
+ * reloads a page that has already crashed. Here the page is still up and
71
+ * may hold work the visitor has not saved: a Besigner edit, a half-typed
72
+ * form. So the reload waits until the tab is hidden, the moment AGL-3280's
73
+ * cross-pool recovery also uses, and runs at once only when the tab is
74
+ * already hidden. The visitor comes back to the build that is deployed.
75
+ *
76
+ * It spends the same rate-bound mark as a boundary, so it can never loop,
77
+ * and it absorbs every later stale-build rejection in the tab while the
78
+ * reload is pending: they are the same stale document, not new failures.
79
+ * A boundary that catches one meanwhile takes the pending reload at once.
80
+ *
81
+ * @returns whether the rejection was recovered, and so should not be
82
+ * reported. False for any other rejection, and for a stale build whose
83
+ * recovery is spent, which the beacon still reports as `chunk-load`.
84
+ */
85
+ export declare function recoverStaleBuildRejection(reason: unknown, reload?: () => void): boolean;
@@ -47,7 +47,8 @@
47
47
  * `redispatch-caught-error.ts` gives: an error boundary's chunk is
48
48
  * downloaded by every visitor, and importing one name off the beacon would
49
49
  * drag its whole installer in with it.
50
- */ const RELOADED_KEY = 'aglyn.staleBuildReloaded';
50
+ */ import { redispatchCaughtError } from "./redispatch-caught-error.js";
51
+ const RELOADED_KEY = 'aglyn.staleBuildReloaded';
51
52
  /** How long one recovery suppresses the next, in this tab. */ export const RECOVERY_INTERVAL_MS = 30 * 60 * 1000;
52
53
  /**
53
54
  * Messages the browsers spell differently for one event — the module the
@@ -96,5 +97,103 @@
96
97
  }
97
98
  return true;
98
99
  }
100
+ /**
101
+ * Withdraws the reload {@link recoverStaleBuildRejection} scheduled; true
102
+ * when there was one.
103
+ */ let cancelPending = null;
104
+ function cancelPendingReload() {
105
+ if (!cancelPending) return false;
106
+ cancelPending();
107
+ return true;
108
+ }
109
+ /** Re-load the document; a refused reload leaves the tab where it was. */ function reloadDocument() {
110
+ try {
111
+ window.location.reload();
112
+ } catch (unused) {
113
+ // Nothing to do: the crash page is already on screen.
114
+ }
115
+ }
116
+ /**
117
+ * What EVERY error boundary does with what it caught: reload once for a stale
118
+ * build, and otherwise hand the error to the beacon. `global-error.tsx`
119
+ * included: it catches what the root layout threw, and a stale tab's root
120
+ * layout asks for its chunks like any other segment.
121
+ *
122
+ * One function so that no boundary can run half of it. The ones that matter
123
+ * most are not the root boundaries but the two below them on a published
124
+ * site — `PageBodyBoundary` around the page body and
125
+ * `[host]/[scheme]/error.tsx` around the page — because a site plugin's chunk
126
+ * is loaded by the page body, so those are what a stale tab reaches. A
127
+ * boundary there that only re-dispatched would leave an empty body, or a
128
+ * **Try again** that cannot work, and report a `ChunkLoadError` for it.
129
+ *
130
+ * A reloaded error is NOT reported; the fresh document is the fix, and the
131
+ * tab it happened in is gone. What still reaches the beacon is the failure a
132
+ * reload did not cure — a second one inside the recovery window, or a
133
+ * browser that refuses storage — which the beacon labels `chunk-load`.
134
+ *
135
+ * `reload` is a parameter only so a spec can observe it: jsdom refuses to let
136
+ * one redefine `location.reload`.
137
+ *
138
+ * @returns whether the tab is reloading.
139
+ */ export function recoverStaleBuildOrReport(error, reload = reloadDocument) {
140
+ // A rejection already spent this tab's recovery on a reload that waits for
141
+ // the tab to be hidden. The page has crashed now, so there is nothing left
142
+ // on screen to protect: take that reload immediately.
143
+ if (isStaleBuildError(error) && cancelPendingReload()) {
144
+ reload();
145
+ return true;
146
+ }
147
+ if (shouldReloadForStaleBuild(error)) {
148
+ reload();
149
+ return true;
150
+ }
151
+ redispatchCaughtError(error);
152
+ return false;
153
+ }
154
+ /**
155
+ * THE SAME TAB, WHEN NO BOUNDARY SAW IT (AGL-3423).
156
+ *
157
+ * An `import()` that nothing awaits inside a render, a click handler's lazy
158
+ * module or an effect's, rejects past every boundary and reaches `window` as
159
+ * an `unhandledrejection`. The beacon labels it `chunk-load`, and until now
160
+ * that was all: the tab stayed on the old build with one feature silently
161
+ * dead.
162
+ *
163
+ * The recovery differs from a boundary's in WHEN it reloads. A boundary
164
+ * reloads a page that has already crashed. Here the page is still up and
165
+ * may hold work the visitor has not saved: a Besigner edit, a half-typed
166
+ * form. So the reload waits until the tab is hidden, the moment AGL-3280's
167
+ * cross-pool recovery also uses, and runs at once only when the tab is
168
+ * already hidden. The visitor comes back to the build that is deployed.
169
+ *
170
+ * It spends the same rate-bound mark as a boundary, so it can never loop,
171
+ * and it absorbs every later stale-build rejection in the tab while the
172
+ * reload is pending: they are the same stale document, not new failures.
173
+ * A boundary that catches one meanwhile takes the pending reload at once.
174
+ *
175
+ * @returns whether the rejection was recovered, and so should not be
176
+ * reported. False for any other rejection, and for a stale build whose
177
+ * recovery is spent, which the beacon still reports as `chunk-load`.
178
+ */ export function recoverStaleBuildRejection(reason, reload = reloadDocument) {
179
+ if (typeof document === 'undefined' || !isStaleBuildError(reason)) return false;
180
+ if (cancelPending) return true;
181
+ if (!shouldReloadForStaleBuild(reason)) return false;
182
+ if (document.visibilityState === 'hidden') {
183
+ reload();
184
+ return true;
185
+ }
186
+ const onVisibilityChange = ()=>{
187
+ if (document.visibilityState !== 'hidden') return;
188
+ cancelPendingReload();
189
+ reload();
190
+ };
191
+ cancelPending = ()=>{
192
+ document.removeEventListener('visibilitychange', onVisibilityChange);
193
+ cancelPending = null;
194
+ };
195
+ document.addEventListener('visibilitychange', onVisibilityChange);
196
+ return true;
197
+ }
99
198
 
100
199
  //# sourceMappingURL=stale-build-error.js.map