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

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 (188) hide show
  1. package/package.json +12 -12
  2. package/src/lib/app-utils/actions.d.ts +1 -59
  3. package/src/lib/app-utils/actions.js +7 -72
  4. package/src/lib/app-utils/actions.js.map +1 -1
  5. package/src/lib/app-utils/consent-groups.d.ts +1 -1
  6. package/src/lib/app-utils/consent-groups.js.map +1 -1
  7. package/src/lib/app-utils/console-routes.d.ts +0 -36
  8. package/src/lib/app-utils/console-routes.js +2 -37
  9. package/src/lib/app-utils/console-routes.js.map +1 -1
  10. package/src/lib/app-utils/contacts.d.ts +1 -1
  11. package/src/lib/app-utils/contacts.js.map +1 -1
  12. package/src/lib/app-utils/container-membership.d.ts +93 -0
  13. package/src/lib/app-utils/container-membership.js +165 -0
  14. package/src/lib/app-utils/container-membership.js.map +1 -0
  15. package/src/lib/app-utils/crm-task-reminders.d.ts +23 -6
  16. package/src/lib/app-utils/crm-task-reminders.js +32 -11
  17. package/src/lib/app-utils/crm-task-reminders.js.map +1 -1
  18. package/src/lib/app-utils/crm.d.ts +24 -4
  19. package/src/lib/app-utils/crm.js +23 -0
  20. package/src/lib/app-utils/crm.js.map +1 -1
  21. package/src/lib/app-utils/csv-upload.d.ts +1 -1
  22. package/src/lib/app-utils/csv-upload.js +1 -1
  23. package/src/lib/app-utils/csv-upload.js.map +1 -1
  24. package/src/lib/app-utils/{dataset-csv.d.ts → csv.d.ts} +10 -43
  25. package/src/lib/app-utils/{dataset-csv.js → csv.js} +10 -81
  26. package/src/lib/app-utils/csv.js.map +1 -0
  27. package/src/lib/app-utils/datasets.d.ts +0 -19
  28. package/src/lib/app-utils/datasets.js +0 -13
  29. package/src/lib/app-utils/datasets.js.map +1 -1
  30. package/src/lib/app-utils/docs-help.generated.d.ts +4 -4
  31. package/src/lib/app-utils/docs-help.generated.js +5 -0
  32. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  33. package/src/lib/app-utils/docs-index.generated.js +71 -36
  34. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  35. package/src/lib/app-utils/dynamic-list-rule.js +3 -3
  36. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  37. package/src/lib/app-utils/email-text.d.ts +80 -0
  38. package/src/lib/app-utils/email-text.js +189 -0
  39. package/src/lib/app-utils/email-text.js.map +1 -0
  40. package/src/lib/app-utils/{list-assignment-policy.d.ts → enrollment-basis.d.ts} +4 -0
  41. package/src/lib/app-utils/{list-assignment-policy.js → enrollment-basis.js} +5 -1
  42. package/src/lib/app-utils/enrollment-basis.js.map +1 -0
  43. package/src/lib/app-utils/expand-repeatables.js +4 -4
  44. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  45. package/src/lib/app-utils/forms.js +2 -2
  46. package/src/lib/app-utils/forms.js.map +1 -1
  47. package/src/lib/app-utils/health-report.d.ts +2 -3
  48. package/src/lib/app-utils/health-report.js +30 -6
  49. package/src/lib/app-utils/health-report.js.map +1 -1
  50. package/src/lib/app-utils/inbound-mail-domain.d.ts +27 -0
  51. package/src/lib/app-utils/inbound-mail-domain.js +31 -0
  52. package/src/lib/app-utils/inbound-mail-domain.js.map +1 -0
  53. package/src/lib/app-utils/leaving-notice.d.ts +96 -0
  54. package/src/lib/app-utils/leaving-notice.js +157 -0
  55. package/src/lib/app-utils/leaving-notice.js.map +1 -0
  56. package/src/lib/app-utils/lockdown.d.ts +11 -0
  57. package/src/lib/app-utils/lockdown.js +33 -3
  58. package/src/lib/app-utils/lockdown.js.map +1 -1
  59. package/src/lib/app-utils/member-email-aliases.d.ts +25 -0
  60. package/src/lib/app-utils/member-email-aliases.js +29 -0
  61. package/src/lib/app-utils/member-email-aliases.js.map +1 -1
  62. package/src/lib/app-utils/notifications.d.ts +69 -0
  63. package/src/lib/app-utils/notifications.js +119 -0
  64. package/src/lib/app-utils/notifications.js.map +1 -1
  65. package/src/lib/app-utils/operator-alerts.d.ts +15 -1
  66. package/src/lib/app-utils/operator-alerts.js +64 -10
  67. package/src/lib/app-utils/operator-alerts.js.map +1 -1
  68. package/src/lib/app-utils/plan-entitlements.d.ts +164 -178
  69. package/src/lib/app-utils/plan-entitlements.js +200 -224
  70. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  71. package/src/lib/app-utils/platform-marketing-consent.d.ts +56 -2
  72. package/src/lib/app-utils/platform-marketing-consent.js +6 -5
  73. package/src/lib/app-utils/platform-marketing-consent.js.map +1 -1
  74. package/src/lib/app-utils/plugin-starter-templates.generated.d.ts +12 -0
  75. package/src/lib/app-utils/plugin-starter-templates.generated.js +711 -0
  76. package/src/lib/app-utils/plugin-starter-templates.generated.js.map +1 -0
  77. package/src/lib/app-utils/repeat-query.d.ts +56 -0
  78. package/src/lib/app-utils/{dataset-query.js → repeat-query.js} +16 -16
  79. package/src/lib/app-utils/repeat-query.js.map +1 -0
  80. package/src/lib/app-utils/screen-link-context-value.d.ts +7 -0
  81. package/src/lib/app-utils/screen-link-context-value.js.map +1 -1
  82. package/src/lib/app-utils/screen-link-context.js +14 -2
  83. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  84. package/src/lib/app-utils/screen-route.d.ts +3 -0
  85. package/src/lib/app-utils/screen-route.js +6 -1
  86. package/src/lib/app-utils/screen-route.js.map +1 -1
  87. package/src/lib/app-utils/server.d.ts +7 -9
  88. package/src/lib/app-utils/server.js +18 -26
  89. package/src/lib/app-utils/server.js.map +1 -1
  90. package/src/lib/app-utils/starter-template-nodes.d.ts +137 -0
  91. package/src/lib/app-utils/starter-template-nodes.js +129 -0
  92. package/src/lib/app-utils/starter-template-nodes.js.map +1 -0
  93. package/src/lib/app-utils/starter-templates.d.ts +11 -34
  94. package/src/lib/app-utils/starter-templates.js +19 -230
  95. package/src/lib/app-utils/starter-templates.js.map +1 -1
  96. package/src/lib/app-utils/{email-topics.d.ts → subscription-topics.d.ts} +64 -48
  97. package/src/lib/app-utils/{email-topics.js → subscription-topics.js} +39 -109
  98. package/src/lib/app-utils/subscription-topics.js.map +1 -0
  99. package/src/lib/app-utils/usage-budget.d.ts +73 -188
  100. package/src/lib/app-utils/usage-budget.js +58 -194
  101. package/src/lib/app-utils/usage-budget.js.map +1 -1
  102. package/src/lib/app-utils/utm-attribution.d.ts +1 -1
  103. package/src/lib/app-utils/utm-attribution.js +1 -1
  104. package/src/lib/app-utils/utm-attribution.js.map +1 -1
  105. package/src/lib/app-utils/{campaign-touch.d.ts → utm-touch.d.ts} +21 -13
  106. package/src/lib/app-utils/{campaign-touch.js → utm-touch.js} +30 -22
  107. package/src/lib/app-utils/utm-touch.js.map +1 -0
  108. package/src/lib/app-utils/variables.d.ts +9 -9
  109. package/src/lib/app-utils/variables.js.map +1 -1
  110. package/src/lib/app-utils/visitor-record-ceiling.d.ts +32 -0
  111. package/src/lib/app-utils/visitor-record-ceiling.js +39 -0
  112. package/src/lib/app-utils/visitor-record-ceiling.js.map +1 -1
  113. package/src/lib/foundation/definitions/org-billing.types.d.ts +2 -2
  114. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  115. package/src/lib/plugin-manager/feature-plugins.d.ts +80 -79
  116. package/src/lib/plugin-manager/feature-plugins.js +22 -35
  117. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  118. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +18 -1
  119. package/src/lib/plugin-manager/first-party-plugins.generated.js +62 -2
  120. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  121. package/src/lib/plugin-manager/index.d.ts +0 -1
  122. package/src/lib/plugin-manager/index.js +0 -1
  123. package/src/lib/plugin-manager/index.js.map +1 -1
  124. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +9 -4
  125. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  126. package/src/lib/plugin-manager/plugin-containers.d.ts +68 -0
  127. package/src/lib/plugin-manager/plugin-containers.generated.d.ts +10 -0
  128. package/src/lib/plugin-manager/plugin-containers.generated.js +20 -0
  129. package/src/lib/plugin-manager/plugin-containers.generated.js.map +1 -0
  130. package/src/lib/plugin-manager/plugin-containers.js +27 -0
  131. package/src/lib/plugin-manager/plugin-containers.js.map +1 -0
  132. package/src/lib/plugin-manager/plugin-entitlements.d.ts +8 -0
  133. package/src/lib/plugin-manager/plugin-entitlements.js.map +1 -1
  134. package/src/lib/plugin-manager/plugin-membership-detach.js +1 -1
  135. package/src/lib/plugin-manager/plugin-membership-detach.js.map +1 -1
  136. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +69 -0
  137. package/src/lib/plugin-manager/plugin-record-email-state.js +32 -0
  138. package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
  139. package/src/lib/plugin-manager/plugin-record-lists.d.ts +115 -0
  140. package/src/lib/plugin-manager/plugin-record-lists.js +75 -0
  141. package/src/lib/plugin-manager/plugin-record-lists.js.map +1 -0
  142. package/src/lib/plugin-manager/plugin-record-timeline.d.ts +42 -8
  143. package/src/lib/plugin-manager/plugin-record-timeline.js.map +1 -1
  144. package/src/lib/plugin-manager/plugin-server-steps.d.ts +170 -0
  145. package/src/lib/plugin-manager/plugin-server-steps.js +116 -0
  146. package/src/lib/plugin-manager/plugin-server-steps.js.map +1 -0
  147. package/src/lib/plugin-manager/plugin-site-beacons.d.ts +71 -0
  148. package/src/lib/plugin-manager/plugin-site-beacons.js +54 -0
  149. package/src/lib/plugin-manager/plugin-site-beacons.js.map +1 -0
  150. package/src/lib/plugin-manager/plugin-subscription-topics.generated.d.ts +11 -0
  151. package/src/lib/plugin-manager/plugin-subscription-topics.generated.js +41 -0
  152. package/src/lib/plugin-manager/plugin-subscription-topics.generated.js.map +1 -0
  153. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +212 -0
  154. package/src/lib/plugin-manager/plugin-usage-axes.js +82 -3
  155. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -1
  156. package/src/lib/plugin-manager/plugin-usage-meters.d.ts +108 -0
  157. package/src/lib/plugin-manager/plugin-usage-meters.js +105 -0
  158. package/src/lib/plugin-manager/plugin-usage-meters.js.map +1 -0
  159. package/src/lib/plugin-manager/usage-alert-contributors.d.ts +76 -6
  160. package/src/lib/plugin-manager/usage-alert-contributors.js +23 -10
  161. package/src/lib/plugin-manager/usage-alert-contributors.js.map +1 -1
  162. package/src/lib/app-utils/assist-credits.d.ts +0 -482
  163. package/src/lib/app-utils/assist-credits.js +0 -510
  164. package/src/lib/app-utils/assist-credits.js.map +0 -1
  165. package/src/lib/app-utils/campaign-membership.d.ts +0 -121
  166. package/src/lib/app-utils/campaign-membership.js +0 -189
  167. package/src/lib/app-utils/campaign-membership.js.map +0 -1
  168. package/src/lib/app-utils/campaign-touch.js.map +0 -1
  169. package/src/lib/app-utils/console-record-links.d.ts +0 -84
  170. package/src/lib/app-utils/console-record-links.js +0 -76
  171. package/src/lib/app-utils/console-record-links.js.map +0 -1
  172. package/src/lib/app-utils/crm-booking.d.ts +0 -75
  173. package/src/lib/app-utils/crm-booking.js +0 -133
  174. package/src/lib/app-utils/crm-booking.js.map +0 -1
  175. package/src/lib/app-utils/crm-inbound.d.ts +0 -245
  176. package/src/lib/app-utils/crm-inbound.js +0 -407
  177. package/src/lib/app-utils/crm-inbound.js.map +0 -1
  178. package/src/lib/app-utils/crm-reports.d.ts +0 -479
  179. package/src/lib/app-utils/crm-reports.js +0 -680
  180. package/src/lib/app-utils/crm-reports.js.map +0 -1
  181. package/src/lib/app-utils/dataset-csv.js.map +0 -1
  182. package/src/lib/app-utils/dataset-query.d.ts +0 -57
  183. package/src/lib/app-utils/dataset-query.js.map +0 -1
  184. package/src/lib/app-utils/email-topics.js.map +0 -1
  185. package/src/lib/app-utils/list-assignment-policy.js.map +0 -1
  186. package/src/lib/plugin-manager/plugin-metered-lines.d.ts +0 -97
  187. package/src/lib/plugin-manager/plugin-metered-lines.js +0 -123
  188. package/src/lib/plugin-manager/plugin-metered-lines.js.map +0 -1
@@ -527,7 +527,7 @@ export const DOCS_SECTION_INDEX = [
527
527
  title: 'Aglyn AI: the AI website builder',
528
528
  heading: 'The Aglyn AI add-on',
529
529
  anchor: '#the-add-on',
530
- text: 'Aglyn AI is an add-on, bought once for the workspace on any paid plan, not per seat and not per site. With it on, every member who has the permission to generate can use every door above, on every site in the workspace.\n- It adds a band of AI credits to the band your plan already includes.\n- Every paid plan already includes some credits, Starter included, so the add-on widens a band you have rather than opening your first one. On Starter it also opens the guided assist that Pro and up already include.\n- Enterprise workspaces carry generative building in their agreement rather than buying the add-on.\n- Free workspaces do not buy it. A Free workspace gets a small monthly allowance of credits instead, enough to try generating, and it stops at that allowance rather than billing you.\nWhat the add-on costs, and the size of the band it adds, are on aglyn.com/pricing and in Billing before you confirm — see Add-ons.'
530
+ text: 'Aglyn AI is an add-on, bought once for the workspace on any paid plan, not per seat and not per site. With it on, every member who has the permission to generate can use every door above, on every site in the workspace.\n- It adds a band of AI credits to the band your plan already includes.\n- Every paid plan, Starter included, already includes some credits and the guided assist, so the add-on widens a band you have rather than opening your first one.\n- Enterprise workspaces carry generative building in their agreement rather than buying the add-on.\n- Free workspaces do not buy it. A Free workspace gets a small monthly allowance of credits instead, enough to try generating, and it stops at that allowance rather than billing you.\nWhat the add-on costs, and the size of the band it adds, are on aglyn.com/pricing and in Billing before you confirm — see Add-ons.'
531
531
  },
532
532
  {
533
533
  path: '/ai/overview',
@@ -2011,7 +2011,7 @@ export const DOCS_SECTION_INDEX = [
2011
2011
  title: 'Add a locale',
2012
2012
  heading: '',
2013
2013
  anchor: '',
2014
- text: 'Add a locale\nPublish your site in another language by adding a locale variant.\nPlan availability Paid. This is multilingual v1.'
2014
+ text: 'Add a locale\nPublish your site in another language by adding a locale variant.\nPlan availability Business and above. This is multilingual v1.'
2015
2015
  },
2016
2016
  {
2017
2017
  path: '/building-sites/multilingual/add-a-locale',
@@ -2039,7 +2039,7 @@ export const DOCS_SECTION_INDEX = [
2039
2039
  title: 'Add a language switcher',
2040
2040
  heading: '',
2041
2041
  anchor: '',
2042
- text: 'Add a language switcher\nOnce you\'ve added a locale, give visitors a language switcher so they can pick their language.\nPlan availability Paid.'
2042
+ text: 'Add a language switcher\nOnce you\'ve added a locale, give visitors a language switcher so they can pick their language.\nPlan availability Business and above.'
2043
2043
  },
2044
2044
  {
2045
2045
  path: '/building-sites/multilingual/language-switcher',
@@ -2067,7 +2067,7 @@ export const DOCS_SECTION_INDEX = [
2067
2067
  title: 'Multilingual',
2068
2068
  heading: '',
2069
2069
  anchor: '',
2070
- text: 'Multilingual\nReach a wider audience by publishing your site in more than one language.\nPlan availability Paid. Multilingual is available on paid tiers.'
2070
+ text: 'Multilingual\nReach a wider audience by publishing your site in more than one language.\nPlan availability Business and above. Multilingual is on Business, Scale, Advanced, Agency and Enterprise.'
2071
2071
  },
2072
2072
  {
2073
2073
  path: '/building-sites/multilingual/overview',
@@ -2186,7 +2186,7 @@ export const DOCS_SECTION_INDEX = [
2186
2186
  title: 'Redirects',
2187
2187
  heading: 'Sending visitors to another site',
2188
2188
  anchor: '#sending-visitors-to-another-site',
2189
- text: 'A destination can be an internal path (/pricing) or an absolute https:// URL — useful for vanity domains and campaign links.\nBecause an off-site destination sends your visitors somewhere you don\'t control, the published site only follows one after a publishing role has saved the rule. A rule with an external destination that nobody has confirmed shows a not serving badge on the redirects page and is skipped until an Editor or Admin opens it and saves. Internal destinations are never affected.'
2189
+ text: 'A destination can be an internal path (/pricing) or an absolute https:// URL — useful for vanity domains and campaign links.\nBecause an off-site destination sends your visitors somewhere you don\'t control, the published site only follows one after a publishing role has saved the rule. A rule with an external destination that nobody has confirmed shows a not serving badge on the redirects page and is skipped until an Editor or Admin opens it and saves. Internal destinations are never affected.\nAn off-site destination also passes the same safety review as a published page. A destination whose address looks like another company\'s website can\'t be saved. A redirect that is held for review does not redirect until our review team releases it, and its owners get a notice that names it; see Holds and reviews.'
2190
2190
  },
2191
2191
  {
2192
2192
  path: '/building-sites/redirects/overview',
@@ -3194,7 +3194,7 @@ export const DOCS_SECTION_INDEX = [
3194
3194
  title: 'Commerce',
3195
3195
  heading: '',
3196
3196
  anchor: '',
3197
- text: 'Commerce\nAglyn commerce sells from your own Stripe account: buyers pay you directly, and Aglyn collects a per-sale platform fee set by your plan (see Billing & plans — higher plans reduce fees to 0%).\nPlan availability Paid. Starter sells up to 100 products (2% physical / 5% digital fee); Pro and Business raise limits and remove fees.'
3197
+ text: 'Commerce\nAglyn commerce sells from your own Stripe account: buyers pay you directly, and Aglyn collects a per-sale platform fee set by your plan (see Platform fees — the fee falls as you upgrade, to 0% on physical goods from Pro and on everything from Advanced).\nPlan availability Paid. Starter sells up to 100 products (2% physical / 5% digital fee). Higher plans raise the product limit and lower the fee: physical goods carry no platform fee from Pro up, while digital and service products carry 3% on Pro, 2% on Business, 1% on Scale and 0% from Advanced up.'
3198
3198
  },
3199
3199
  {
3200
3200
  path: '/commerce-and-bookings/commerce/overview',
@@ -3593,7 +3593,7 @@ export const DOCS_SECTION_INDEX = [
3593
3593
  title: 'Bulk actions',
3594
3594
  heading: 'Leads',
3595
3595
  anchor: '#leads',
3596
- text: '| Action | What happens |\n| Set owner | Pick a team member, or Nobody to clear the owner. The lead alone is assigned; its contact, when there is one, is assigned from its own record. |\n| Set status | Pick New or Working. A lead already at that status is skipped and named, and a converted lead is left alone — its status is its conversion. Setting a closed lead back to Working reopens it and clears its reason, as the row does. |\n| Unqualify | Asks for one reason, then closes every open selected lead with it; the reason is kept on each so the lead funnel can count it. Leads already closed or converted are skipped and named. |\n| Add to campaign | Pick one or more of the site\'s campaigns; each selected lead is filed under them beside the campaigns it is already in. Under a site only — a campaign belongs to one site. Grouping, not consent: nothing is mailed. |\n| Export CSV | Download the selected leads as leads-selected.csv — the same file the list\'s Export CSV writes: email, name, status, the owner by email address, the sources by name joined with \\|, first and last seen, how many times the person was captured, the unqualified reason, when the lead converted, and notes. The list\'s Import CSV reads the columns a file may set back in; the capture columns are its to write, not yours. |\n| Clear | Deselect everything. |\nA lead\'s owner and status are the team\'s own notes on a capture — no event and no notification fire — so the bar writes them directly, the way the row\'s inline controls do.'
3596
+ text: '| Action | What happens |\n| Set owner | Pick a team member, or Nobody to clear the owner. The lead alone is assigned; its contact, when there is one, is assigned from its own record. |\n| Set status | Pick New, Nurturing or Working. A lead already at that status is skipped and named, and a converted lead is left alone — its status is its conversion. Setting a closed lead back to Working reopens it and clears its reason, as the row does. |\n| Unqualify | Asks for one reason, then closes every open selected lead with it; the reason is kept on each so the lead funnel can count it. Leads already closed or converted are skipped and named. |\n| Add to campaign | Pick one or more of the site\'s campaigns; each selected lead is filed under them beside the campaigns it is already in. Under a site only — a campaign belongs to one site. Grouping, not consent: nothing is mailed. |\n| Export CSV | Download the selected leads as leads-selected.csv — the same file the list\'s Export CSV writes: email, name, status, the owner by email address, the sources by name joined with \\|, first and last seen, how many times the person was captured, the unqualified reason, when the lead converted, and notes. The list\'s Import CSV reads the columns a file may set back in; the capture columns are its to write, not yours. |\n| Clear | Deselect everything. |\nA lead\'s owner and status are the team\'s own notes on a capture — no event and no notification fire — so the bar writes them directly, the way the row\'s inline controls do.'
3597
3597
  },
3598
3598
  {
3599
3599
  path: '/content-and-data/crm/bulk-actions',
@@ -4048,7 +4048,7 @@ export const DOCS_SECTION_INDEX = [
4048
4048
  title: 'Leads',
4049
4049
  heading: 'The Leads list',
4050
4050
  anchor: '#the-leads-list',
4051
- text: 'The list shows the most recently seen leads first — a person who booked yesterday sits above one who signed up last month, whichever came first. It keeps that order: the column headers do not re-sort it. Each row carries:\n| Column | What it shows |\n| Lead | The name the person gave, with their email beneath it — or the email alone. |\n| Company, Title | The lead\'s own company and job title, as text. |\n| Status | New, Working, Qualified or Unqualified. Change it in place from the row. |\n| Email | The last verdict on the lead\'s address — see Email state — or a dash when nothing is known. |\n| Owner | The team member working the lead, or Unassigned. A lead inherits its contact\'s owner when one is assigned on capture. |\n| Source | Every surface that captured this person: Booking, the form they submitted, an import, New lead, or the API — and Sign-up on leads filed before sign-ups stopped making leads. |\n| Lead source | The lead\'s lead source. |\n| Tags | The lead\'s tags. |\n| Campaign | The campaigns the lead is filed under, by name. Under a site only — a campaign belongs to one site. |\n| Last seen | When the person last did something on your site. |\nFilter and search the leads {#filter-the-leads}\nThe table\'s own toolbar filters the list: Filters opens the filter panel, and Search finds a lead by a word of its name, its email address — and each part of the address, so the domain alone finds it — its company, its title or its tags. Status, Email, Owner, Lead source and Campaign are picked from a list in the panel, and from each column\'s menu. Status starts on Open (new or working), every lead that still needs working; pick one status instead, or remove the filter to see every lead. A lead nobody has touched yet has no status of its own and reads as New, so the leads your site co'
4051
+ text: 'The list shows the most recently seen leads first — a person who booked yesterday sits above one who signed up last month, whichever came first. It keeps that order: the column headers do not re-sort it. Each row carries:\n| Column | What it shows |\n| Lead | The name the person gave, with their email beneath it — or the email alone. |\n| Company, Title | The lead\'s own company and job title, as text. |\n| Status | New, Nurturing, Working, Qualified or Unqualified — see Lead statuses. Change it in place from the row. |\n| Email | The last verdict on the lead\'s address — see Email state — or a dash when nothing is known. |\n| Owner | The team member working the lead, or Unassigned. A lead inherits its contact\'s owner when one is assigned on capture. |\n| Source | Every surface that captured this person: Booking, the form they submitted, an import, New lead, or the API — and Sign-up on leads filed before sign-ups stopped making leads. |\n| Lead source | The lead\'s lead source. |\n| Tags | The lead\'s tags. |\n| Campaign | The campaigns the lead is filed under, by name. Under a site only — a campaign belongs to one site. |\n| Last seen | When the person last did something on your site. |\nLead statuses {#lead-statuses}\nA lead moves through its statuses in the order a person works one:\n| Status | What it means |\n| New | Nobody and nothing has touched the lead yet. A lead your site captured carries no status of its own and reads as New. |\n| Nurturing | Automated email is reaching the lead — a sequence sent it a step, or a campaign email was delivered to it — and nobody has engaged yet. |\n| Working | A person is in a conversation with the lead: it replied to a sequence, or a teammate set it to Working. |\n| Qualified | The lead was converted into a contact. |\n| Unqualified | The lead was c'
4052
4052
  },
4053
4053
  {
4054
4054
  path: '/content-and-data/crm/leads',
@@ -4216,7 +4216,7 @@ export const DOCS_SECTION_INDEX = [
4216
4216
  title: 'Reports',
4217
4217
  heading: 'Lead funnel',
4218
4218
  anchor: '#lead-funnel',
4219
- text: 'Of the leads this site captured in the period, where each one stands now, and why the ones closed without converting were closed.\n- Leads captured — leads first seen in the period, counted on the server, with the change against the previous period.\n- Qualified and Unqualified — how many of those captured have been converted, or closed without converting, each with its share of the leads captured.\n- Still open — leads that are New or Working.\n- Where they stand — one bar per status. A lead nobody has touched yet is New.\n- Why leads were unqualified — the reasons given when leads were unqualified, most common first, each with its share of the unqualified leads. Different spellings of one reason are counted together; a lead closed with no reason is listed as No reason given.\nA lead is placed by when it was first seen, so a returning visitor\'s second form does not make a second lead in a second period. The funnel is read from the period\'s 200 most recently captured leads, and says so when the period held more. Because leads belong to a site rather than to the workspace, this card reports the site you are looking at; at the organization level it reads every site — at most 200 leads per site — and totals them, naming the per-site window when any site held more.'
4219
+ text: 'Of the leads this site captured in the period, where each one stands now, and why the ones closed without converting were closed.\n- Leads captured — leads first seen in the period, counted on the server, with the change against the previous period.\n- Qualified and Unqualified — how many of those captured have been converted, or closed without converting, each with its share of the leads captured.\n- Still open — leads that are New, Nurturing or Working.\n- Where they stand — one bar per status. A lead nobody has touched yet is New.\n- Why leads were unqualified — the reasons given when leads were unqualified, most common first, each with its share of the unqualified leads. Different spellings of one reason are counted together; a lead closed with no reason is listed as No reason given.\nA lead is placed by when it was first seen, so a returning visitor\'s second form does not make a second lead in a second period. The funnel is read from the period\'s 200 most recently captured leads, and says so when the period held more. Because leads belong to a site rather than to the workspace, this card reports the site you are looking at; at the organization level it reads every site — at most 200 leads per site — and totals them, naming the per-site window when any site held more.'
4220
4220
  },
4221
4221
  {
4222
4222
  path: '/content-and-data/crm/reports',
@@ -4349,7 +4349,7 @@ export const DOCS_SECTION_INDEX = [
4349
4349
  title: 'Sequences',
4350
4350
  heading: 'Enroll people',
4351
4351
  anchor: '#enroll',
4352
- text: 'Select Enroll people on an active sequence, and choose them from a saved Contacts or Leads view, by searching your contacts at the sequence\'s site, or from the Leads tab — the site\'s open leads — up to 50 at a time. Enrolling reads your contacts and leads, so it takes the Manage data permission as well as Use Sequences.\nA lead is enrolled as it is, with no contact made: the emails read the lead\'s name, company and title wherever the steps say {{contact.firstName}}, {{contact.company}} or {{contact.title}} (and {{lead.…}} reads the same fields), the sends and replies land on the lead\'s page, a call step files its task on the lead, and a reply moves a lead from New to Working. When the lead converts, its enrollment carries on as the contact\'s — same thread, same place in the steps — so a person is never enrolled twice by being two records. A lead that already converted, one whose address is already a contact, and one closed as unqualified are blocked with the reason: enroll the contact, or reopen the lead.\nBefore anything is enrolled, each person is marked:\n- Eligible — enrolled as they are.\n- Needs you — enrolled once you\'ve written their personal line and confirmed what\'s asked below.\n- Blocked — not enrolled, with the reason: a personal mailbox, which a cold email never goes to; an address outside the allowed countries, or in no known country; on the platform\'s or your site\'s suppression list after a bounce, a complaint or an unsubscribe; opted out of sales email from the site; on your organization\'s do-not-contact list, or at a domain on it; a member of your workspace; a customer, unless the sequence includes customers; already in a sequence; or a lead that converted, is already a contact, or was closed as unqualified.\nBeside each person, Preview the first email shows'
4352
+ text: 'Select Enroll people on an active sequence, and choose them from a saved Contacts or Leads view, by searching your contacts at the sequence\'s site, or from the Leads tab — the site\'s open leads — up to 50 at a time. Enrolling reads your contacts and leads, so it takes the Manage data permission as well as Use Sequences.\nA lead is enrolled as it is, with no contact made: the emails read the lead\'s name, company and title wherever the steps say {{contact.firstName}}, {{contact.company}} or {{contact.title}} (and {{lead.…}} reads the same fields), the sends and replies land on the lead\'s page, a call step files its task on the lead, and the lead\'s status follows the sequence: the first email actually sent to a New lead moves it to Nurturing — enrolling alone does not, nor does a step that never goes out — and a reply moves a New or Nurturing lead to Working. A lead already Working or closed keeps its status. When the lead converts, its enrollment carries on as the contact\'s — same thread, same place in the steps — so a person is never enrolled twice by being two records. A lead that already converted, one whose address is already a contact, and one closed as unqualified are blocked with the reason: enroll the contact, or reopen the lead.\nBefore anything is enrolled, each person is marked:\n- Eligible — enrolled as they are.\n- Needs you — enrolled once you\'ve written their personal line and confirmed what\'s asked below.\n- Blocked — not enrolled, with the reason: a personal mailbox, which a cold email never goes to; an address outside the allowed countries, or in no known country; on the platform\'s or your site\'s suppression list after a bounce, a complaint or an unsubscribe; opted out of sales email from the site; on your organization\'s do-not-contact list, or at a domain on'
4353
4353
  },
4354
4354
  {
4355
4355
  path: '/content-and-data/crm/sequences',
@@ -4573,7 +4573,7 @@ export const DOCS_SECTION_INDEX = [
4573
4573
  title: 'Tasks & follow-ups',
4574
4574
  heading: 'The daily digest',
4575
4575
  anchor: '#the-daily-digest',
4576
- text: 'Because overdue and today are read off the clock, nothing on a task fires on its own except its reminder. What says what is owed is the daily CRM digest: once a morning, at 8:00 (America/Chicago), every member with open work gets one console notification and one email saying what they owe — for example, "3 tasks due today, 2 overdue, 1 unworked lead".\nIt counts, per organization:\n- Overdue and due today — your open tasks, by the same day boundaries the Tasks page paints with, read in the digest\'s time zone rather than yours.\n- Unworked leads — leads on the sites you can reach that are still New after two days and have no owner, plus any lead whose owner is you. A lead with an owner is only on that owner\'s list.\nThe notification opens the Tasks section of the site the first task belongs to (or the Leads section when only leads are owed). The email lists each section — up to ten items apiece, with a count of the rest — and links to the same pages. Members who have nothing due, nothing late and no unworked lead get nothing; a digest is sent at most once per member per day, so a re-run on the same day reaches only whoever the first run missed.\nThe digest goes only to members who can open the CRM (the same manage data permission as everything else here), and only in organizations whose plan includes it.\nTurning it off\nDaily CRM digest is a switch under Account settings → Notifications, on by default. Off, it stops both the notification and the email, on every workspace you belong to. Muting the Forms & bookings category on that same page silences the console notification only — the email still arrives while the digest is on. (A reminder is the other way round: the category mute is its only switch, and stops its email too.) See Workspace settings & notifications.'
4576
+ text: 'Because overdue and today are read off the clock, nothing on a task fires on its own except its reminder. What says what is owed is the daily CRM digest: once a morning, at 8:00 (America/Chicago), every member with open work gets one console notification and one email saying what they owe — for example, "3 tasks due today, 2 overdue, 1 unworked lead".\nIt counts, per organization:\n- Overdue and due today — your open tasks, by the same day boundaries the Tasks page paints with, read in the digest\'s time zone rather than yours.\n- Unworked leads — leads on the sites you can reach that are still New after two days and have no owner, plus any lead whose owner is you. A lead with an owner is only on that owner\'s list.\n- Leads in sequences or campaigns — the same leads by the same rule, but Nurturing rather than New: a sequence or a campaign email is already reaching them. The email counts them on one line ("12 leads are in sequences or campaigns") and does not list them, and they are never a reason on their own to send you a digest.\nThe notification opens the Tasks section of the site the first task belongs to (or the Leads section when only leads are owed). The email lists each section — up to ten items apiece, with a count of the rest — and links to the same pages. Members who have nothing due, nothing late and no unworked lead get nothing; a digest is sent at most once per member per day, so a re-run on the same day reaches only whoever the first run missed.\nThe digest goes only to members who can open the CRM (the same manage data permission as everything else here), and only in organizations whose plan includes it.\nTurning it off\nDaily CRM digest is a switch under Account settings → Notifications, on by default. Off, it stops both the notification and the email, on every '
4577
4577
  },
4578
4578
  {
4579
4579
  path: '/content-and-data/crm/tasks',
@@ -4965,7 +4965,7 @@ export const DOCS_SECTION_INDEX = [
4965
4965
  title: 'Media Library & CDN',
4966
4966
  heading: '',
4967
4967
  anchor: '',
4968
- text: 'Media Library & CDN\nThe media library stores your images, video, and files, keeps them organized, and serves them quickly. Media plugs into components like Image and Video and into the theme\'s favicon.\nPlan availability Free with storage quotas. CDN delivery with WebP variants is on every plan, free included; document uploads and higher storage are gated by plan. Video uploads are paused on every plan for now — see Upload.'
4968
+ text: 'Media Library & CDN\nThe media library stores your images, video, and files, keeps them organized, and serves them quickly. Media plugs into components like Image and Video and into the theme\'s favicon.\nA site\'s Media page has two tabs: This site, the site\'s own library, and Organization (shared), the workspace assets this site may use. The organization library loads only when you open its tab.\nPlan availability Free with storage quotas. CDN delivery with WebP variants is on every plan, free included; document uploads and higher storage are gated by plan. Video uploads are paused on every plan for now — see Upload.'
4969
4969
  },
4970
4970
  {
4971
4971
  path: '/content-and-data/media/overview',
@@ -5595,14 +5595,14 @@ export const DOCS_SECTION_INDEX = [
5595
5595
  title: 'Injection zones',
5596
5596
  heading: 'Zones a plugin hosts',
5597
5597
  anchor: '#zones-a-plugin-hosts',
5598
- text: 'A zone can sit on a plugin\'s own surface rather than on a console page, such as hostForms on the forms plugin\'s Forms page, hostAutomations, automationEditor and automationRun on the workflows plugin\'s Automation page, or recordInsights, recordEmail and importMapping on the CRM plugin\'s record pages, one-to-one composer and import drawers. A plugin cannot import the console\'s PluginWidgetSlot, so the shell hands its renderer down: read it with useConsoleWidgetSlot() from @aglyn/aglyn and draw the zone through it.\nThe renderer is the same gated slot a console page mounts, so a widget there passes the same enablement, entitlement and permission gates. Outside the console shell it is null, and the zone draws nothing.\nA plugin that hosts a zone also declares it, with registerPluginZone and a token that carries the props it hands each widget (see Zones a plugin hosts in the plugin-manager reference). The commerce plugin declares these three on its product surfaces; a widget from another plugin restates the props it reads rather than importing the commerce package:\n| Zone | Where it renders | Props your widget receives |\n| productEditor | The commerce product editor, under a product\'s description, tags and categories: copy proposed for the fields, which Save product writes | hostId, orgId, product (as the editor holds it), categories, proposeValues(values, key) — stages copy in the editor as unsaved edits |\n| productsHub | The commerce products page, above its catalog table: proposals the hub writes when a member applies them | hostId, orgId, products (the catalog rows the hub holds), lastImport (the products the latest import created, with its options, or null), and the hub\'s writes a widget asks for: applyProductCopy, createProductDrafts, createCategories, createDiscountDra'
5598
+ text: 'A zone can sit on a plugin\'s own surface rather than on a console page, such as hostForms on the forms plugin\'s Forms page, hostAutomations, automationEditor and automationRun on the workflows plugin\'s Automation page, or recordInsights, recordEmail and importMapping on the CRM plugin\'s record pages, one-to-one composer and import drawers. A plugin cannot import the console\'s PluginWidgetSlot, so the shell hands its renderer down: read it with useConsoleWidgetSlot() from @aglyn/aglyn and draw the zone through it.\nThe renderer is the same gated slot a console page mounts, so a widget there passes the same enablement, entitlement and permission gates. Outside the console shell it is null, and the zone draws nothing.\nA plugin that hosts a zone also declares it, with registerPluginZone and a token that carries the props it hands each widget (see Zones a plugin hosts in the plugin-manager reference). The forms, workflows and commerce plugins declare these on their own surfaces; a widget from another plugin restates the props it reads rather than importing the host\'s package:\n| Zone | Where it renders | Props your widget receives |\n| hostForms | A site\'s Forms page, the forms plugin\'s, beside Create Form: another way to start a form | hostId, orgId |\n| hostAutomations | The workflows plugin\'s Automation page, its Actions, beside Add action and Recipes: another way to start an automation | hostId, orgId, openAction(actionId) — opens a listed action in the Actions editor, and answers false for one the list has not read yet |\n| automationEditor | Inside the editor of one saved automation, an action or a workflow, on the Automation page | hostId, orgId, target ({ type: \'action\' \\| \'workflow\', id, name }, the automation as it is stored) |\n| automationRun | On each failed run in an'
5599
5599
  },
5600
5600
  {
5601
5601
  path: '/developers/plugins/reference/injection-zones',
5602
5602
  title: 'Injection zones',
5603
5603
  heading: 'How a zone spaces your widget',
5604
5604
  anchor: '#how-a-zone-spaces-your-widget',
5605
- text: 'Most zones are a stack. The shell draws their widgets one under another, with the same gap the page puts between its own cards, and keeps that gap between the zone and the page\'s cards beside it. Render your card with no outer margin: the zone spaces it, and a margin on your widget\'s root is reset.\nThe other zones hand each widget to a layout the page draws itself, and the page spaces it there:\n- hostDashboard, commerceGlance and orgDashboard: a tile of a dashboard grid.\n- hostScreens, hostTemplates, hostLayouts, hostForms, hostComponents and besignerToolbar: a control in a row.\n- hostAutomations, automationEditor and automationRun: a control the workflows plugin places beside its Actions buttons, in an automation\'s editor, and on a failed run.\n- besignerInspector and seoFields: a section among a panel\'s own fields.\n- productEditor, productsHub and productImport: a section the commerce plugin places among its product editor\'s fields, above its catalog table, and in its CSV import dialog.\n- recordEmail and importMapping: a section the CRM plugin places under its one-to-one composer\'s message and under an import drawer\'s column matching.\n- besignerFunctions, orgData, orgMarketplace, orgAddons and marketplaceListing: the body of a dialog or a page.\n- consoleDock: a floating dock.\n- orgMembersListColumn, staffOrgsListColumn and staffOrgUsageColumn: a column of a table, or, on staffOrgUsageColumn, a line above it.\nA widget that renders nothing leaves no gap in either kind of zone.'
5605
+ text: 'Most zones are a stack. The shell draws their widgets one under another, with the same gap the page puts between its own cards, and keeps that gap between the zone and the page\'s cards beside it. Render your card with no outer margin: the zone spaces it, and a margin on your widget\'s root is reset.\nThe other zones hand each widget to a layout the page draws itself, and the page spaces it there:\n- hostDashboard, commerceGlance and orgDashboard: a tile of a dashboard grid.\n- hostScreens, hostTemplates, hostLayouts, hostForms, hostComponents and besignerToolbar: a control in a row.\n- hostAutomations, automationEditor and automationRun: a control the workflows plugin places beside its Actions buttons, in an automation\'s editor, and on a failed run.\n- siteMember: a section of a site user\'s drawer.\n- besignerInspector and seoFields: a section among a panel\'s own fields.\n- productEditor, productsHub and productImport: a section the commerce plugin places among its product editor\'s fields, above its catalog table, and in its CSV import dialog.\n- recordEmail and importMapping: a section the CRM plugin places under its one-to-one composer\'s message and under an import drawer\'s column matching.\n- besignerFunctions, orgData, orgMarketplace, orgAddons and marketplaceListing: the body of a dialog or a page.\n- consoleDock: a floating dock.\n- besignerInteractions: nothing; a widget there reports to the section and renders null.\n- orgMembersListColumn, staffOrgsListColumn and staffOrgUsageColumn: a column of a table, or, on staffOrgUsageColumn, a line above it.\nA widget that renders nothing leaves no gap in either kind of zone.'
5606
5606
  },
5607
5607
  {
5608
5608
  path: '/developers/plugins/reference/injection-zones',
@@ -5756,7 +5756,7 @@ export const DOCS_SECTION_INDEX = [
5756
5756
  title: 'Plugin-manager API reference',
5757
5757
  heading: 'Customer REST API resources — `api-v1-resources` (data layer, console server)',
5758
5758
  anchor: '#customer-rest-api-resources--api-v1-resources-data-layer-console-server',
5759
- text: 'A plugin that models records an integration reads and writes serves them on the customer REST API, /v1/ /…, from its consoleServerDeclarations, with the handler loaded on the first request:\n| API | Semantics |\n| registerApiV1Resource(resource, { handle, entitlement? }, { pluginId? }) | Serves every request under /v1/ . The name is one lowercase path segment; the platform\'s own resources (datasets, sites, media, usage, me) and a resource another plugin already serves are refused, naming both, and the incumbent keeps serving. The same plugin registering again replaces its own. |\n| apiV1Resource(resource) | What the router asks after the platform\'s own resources: the serving plugin and its registration, or null. A name nobody registered is asked once more after the app\'s declarations step runs, so a process whose boot failed repairs itself rather than answering 404. |\n| handle(request, ctx, segments, url) | Runs after the pipeline admitted the request — the API key, the plan\'s API access, the request quota, the rate limit. ctx is the ApiV1Context; the handler asks for its own scopes with requireScope and answers in the published envelope (apiJson, ApiErrors). |\n| entitlement | The plan feature the resource needs. The router refuses an organization without it — 402 planrequired, the feature as the code, the registration\'s sentence as the message — before the handler runs and before any scope is asked, so a key minted while the plan carried the feature cannot outlive it. |\n| describe | Loads the resource\'s description — tag, record schema, writable members, operations — in the terms of @aglyn/tenant-data-admin/server/api-v1-description. The OpenAPI document at /api/v1/openapi.json, and the MCP tools derived from it, list every resource the build serves and nothing it does no'
5759
+ text: 'A plugin that models records an integration reads and writes serves them on the customer REST API, /v1/ /…, from its consoleServerDeclarations, with the handler loaded on the first request:\n| API | Semantics |\n| registerApiV1Resource(resource, { handle, entitlement? }, { pluginId? }) | Serves every request under /v1/ . The name is one lowercase path segment; the platform\'s own resources (sites, media, usage, me) and a resource another plugin already serves are refused, naming both, and the incumbent keeps serving. The same plugin registering again replaces its own. |\n| apiV1Resource(resource) | What the router asks after the platform\'s own resources: the serving plugin and its registration, or null. A name nobody registered is asked once more after the app\'s declarations step runs, so a process whose boot failed repairs itself rather than answering 404. |\n| handle(request, ctx, segments, url) | Runs after the pipeline admitted the request — the API key, the plan\'s API access, the request quota, the rate limit. ctx is the ApiV1Context; the handler asks for its own scopes with requireScope and answers in the published envelope (apiJson, ApiErrors). |\n| entitlement | The plan feature the resource needs. The router refuses an organization without it — 402 planrequired, the feature as the code, the registration\'s sentence as the message — before the handler runs and before any scope is asked, so a key minted while the plan carried the feature cannot outlive it. |\n| describe | Loads the resource\'s description — tag, record schema, writable members, operations — in the terms of @aglyn/tenant-data-admin/server/api-v1-description. The OpenAPI document at /api/v1/openapi.json, and the MCP tools derived from it, list every resource the build serves and nothing it does not; a descr'
5760
5760
  },
5761
5761
  {
5762
5762
  path: '/developers/plugins/reference/plugin-manager-api',
@@ -5821,6 +5821,13 @@ export const DOCS_SECTION_INDEX = [
5821
5821
  anchor: '#documents-authored-in-the-besigner--besigner-documents',
5822
5822
  text: 'Pages, layouts and components are the platform\'s own Besigner documents. A plugin can keep one too — a node tree under hosts/{hostId}/{collection}/{docId}, its working copies under versions/{versionId} and the published copy on the document itself — and the console serves its editor and its preview without naming it, at /{orgSlug}/hosts/{host}/{segment}/{docId}/versions/{versionId}/besigner and …/preview. The plugin declares the kind in a besignerDocuments block of plugins.config.json:\n| Field | Semantics |\n| kind | What presence rooms, saved drafts and previews key on — a stored value, never renamed. One owner per kind. |\n| segment | The URL segment under a site. The document\'s own page is the plugin\'s hub at / (declare ownsSubtree on the nav item), where the editor\'s Back and Close land. Never one the console routes itself (screens, layouts, components, templates, emails, theme). |\n| collection | The host subcollection the documents live in; it must also be in the same plugin\'s hostCollections. |\n| noun | What one is called in the editor\'s sentences, lower case. |\n| publish | The console route that makes a version the one the site serves, and the body field that carries the document id. |\nThe editor saves the working version itself; publishing is the plugin\'s. It POSTs { hostId, [idField]: docId, versionId } to publish.path, a server route that reads the stored version itself (nothing about the design crosses the wire), refuses a design that breaks what the document promises, writes the published copy and drops the live pages that place it. A refusal answers { error, violations?: [{ message }] }; the editor shows the first violation in full with a count of the rest (besignerPublishRefusal). A version the site already serves is published again when an author asks to pu'
5823
5823
  },
5824
+ {
5825
+ path: '/developers/plugins/reference/plugin-manager-api',
5826
+ title: 'Plugin-manager API reference',
5827
+ heading: 'Container kinds — `plugin-containers`',
5828
+ anchor: '#container-kinds--plugin-containers',
5829
+ text: 'A container is a document other records are FILED UNDER: a form, a screen, a lead or a contact filed under a campaign. The member holds the edge, as an array of container ids on its own document, so reading "what is this filed under" costs nothing on a page that already read the record, and deleting a member leaves nothing behind. The plugin that keeps a kind declares it in the containers block of plugins.config.json, compiled like the collections above:\n| API | Semantics |\n| pluginContainerKind(kind) / listPluginContainerKinds() | One declared kind with its owner and its owner\'s catalog label, or null when no plugin keeps it; and every declared kind. |\n| containerMembershipField(kind) (app-utils/container-membership) | The field a member holds its containers in: Ids. Derived, not declared, so a record page in one plugin files itself under a kind another plugin keeps by the kind\'s name alone. |\n| readContainerIds(record, kind) / readContactContainerIds(contact, groupId, kind) / contactContainerFieldPath(groupId, kind) | Read a host record\'s membership, read one holder\'s filing of a contact, and the facet path to write it — a contact is shared by every site, so what one merchant filed a person under lives in that merchant\'s facet. |\n| normalizeContainerIds(raw) / containerMembershipValue(selected) / containerMembershipUnchanged(stored, selected) | The cleaning every reader applies (deduped, trimmed, capped at CONTAINERMEMBERSHIPCAP), the value a save stores (an empty selection is stored as [], never removed), and an order-insensitive "nothing to save". |\n| useSiteContainerOptions(kind, hostId, { enabled }) / useOrgContainerOptions(kind, orgId, { enabled }) (@aglyn/tenant-feature-instance) | The containers a picker offers: the org collection the kind declares, narrowed on'
5830
+ },
5824
5831
  {
5825
5832
  path: '/developers/plugins/reference/plugin-manager-api',
5826
5833
  title: 'Plugin-manager API reference',
@@ -5840,7 +5847,7 @@ export const DOCS_SECTION_INDEX = [
5840
5847
  title: 'Plugin-manager API reference',
5841
5848
  heading: 'Record indexes — `plugin-record-index` (`/server`)',
5842
5849
  anchor: '#record-indexes--plugin-record-index-server',
5843
- text: 'The records a plugin keeps, LISTED and READ for another plugin that works over them — an AI job enriching a catalog, a composer binding the products an email names — without that plugin reaching for the owner\'s collection. Keyed by record kind, like the card, the facts reader and the route; a card says how a record LOOKS, an index finds records and reads what the owner shares about them.\n| API | Semantics |\n| registerPluginRecordIndex(kind, index, { pluginId? }) | A kind another plugin keeps throws naming both; the incumbent keeps serving, and the owner re-registering replaces its own. |\n| pluginRecordIndex(kind) | { pluginId, index }, or null when no plugin keeps the kind here — a reader treats that as "none here", never as a reason to read the collection itself. The pluginId is also how a reader asks whether the keeper is switched on for a site. |\n| index.list({ orgId?, hostId?, limit }) / index.get({ orgId?, hostId?, id }) | Live, named records only — the owner decides what "deleted" is — each { id, name, facts }, with facts in the shape the owner documents. truncated says the scope holds more. |\nImport it by its own subpath (@aglyn/aglyn/plugin-manager/plugin-record-index). Commerce publishes product and productCategory; Workflows publishes a site\'s workflow, webhook and action records (a webhook\'s facts never carry its URL or secret).'
5850
+ text: 'The records a plugin keeps, LISTED and READ for another plugin that works over them — an AI job enriching a catalog, a composer binding the products an email names — without that plugin reaching for the owner\'s collection. Keyed by record kind, like the card, the facts reader and the route; a card says how a record LOOKS, an index finds records and reads what the owner shares about them.\n| API | Semantics |\n| registerPluginRecordIndex(kind, index, { pluginId? }) | A kind another plugin keeps throws naming both; the incumbent keeps serving, and the owner re-registering replaces its own. |\n| pluginRecordIndex(kind) | { pluginId, index }, or null when no plugin keeps the kind here — a reader treats that as "none here", never as a reason to read the collection itself. The pluginId is also how a reader asks whether the keeper is switched on for a site. |\n| index.list({ orgId?, hostId?, limit }) / index.get({ orgId?, hostId?, id }) | Live, named records only — the owner decides what "deleted" is — each { id, name, facts }, with facts in the shape the owner documents. truncated says the scope holds more. |\nImport it by its own subpath (@aglyn/aglyn/plugin-manager/plugin-record-index). Commerce publishes product and productCategory; Workflows publishes a site\'s workflow, webhook and action records (a webhook\'s facts never carry its URL or secret); Data publishes the workspace\'s dataset records, narrowed to a site to the ones shared with it, with their fields, their scope tokens and the listing an installed one came from (never a record of one), from its console-only server declarations.'
5844
5851
  },
5845
5852
  {
5846
5853
  path: '/developers/plugins/reference/plugin-manager-api',
@@ -5849,6 +5856,13 @@ export const DOCS_SECTION_INDEX = [
5849
5856
  anchor: '#what-depends-on-a-thing--plugin-dependents-server',
5850
5857
  text: 'The console\'s "Used by" scan (/api/hosts/where-used, asked through @aglyn/aglyn/app-utils/where-used) tells a person what they would break by renaming or deleting a site\'s variable, function or workflow. It reads the published pages itself. What refers to the thing from inside a plugin\'s own records is answered by that plugin: register a dependents source for the kinds of thing your records point at, and the scan lists what it finds beside its own.\n| API | Semantics |\n| registerPluginDependentsSource(source, { pluginId?, key? }) | source.kinds names the kinds it answers for; find answers one site\'s referring records. Owner = the loader\'s marker, else pluginId; no owner or no kind throws. The same plugin re-registering the same key replaces its own. |\n| findPluginDependents({ hostId, kind, id, name? }) | What the scan calls: every source for the kind, in registration order. { dependents, complete }, and complete is false when a source was truncated or threw — the scan then never says "nothing uses this". No source for the kind is a complete, empty answer. |\nA dependent\'s type is the word the scan lists it under (variable, workflow), and via says how it refers: id survives a rename, name does not. Workflows answers for a function (the workflows whose steps call it); Logic answers for a workflow (the variables it computes), which is how the Automation page learns what a workflow\'s deletion would leave on its fallback value without either plugin importing the other.'
5851
5858
  },
5859
+ {
5860
+ path: '/developers/plugins/reference/plugin-manager-api',
5861
+ title: 'Plugin-manager API reference',
5862
+ heading: 'Record lists — `plugin-record-lists` (console)',
5863
+ anchor: '#record-lists--plugin-record-lists-console',
5864
+ text: 'The browser\'s half of the record index: the records of a kind, LISTED in the console for another plugin\'s picker or check — an automation step choosing a dataset, a reference check confirming a workflow still exists, a deal searching the catalog — without that plugin querying the owner\'s collection from the browser itself.\n| API | Semantics |\n| registerPluginRecordListSource(kind, source, { pluginId? }) | A kind another plugin lists throws naming both; the incumbent keeps serving, and the owner re-registering replaces its own. |\n| source.query(firestore, { orgId?, hostId?, search?, memberScope?, installedFrom?, limit }) | The query the signed-in member\'s read of the scope is proved by — the owner applies the filter its security rules require — at most limit documents, or null for a scope the kind has none in. search is what a person typed, matched the owner\'s way. memberScope is the reading member\'s own scope tokens where they are not organization-wide, for an org-scoped kind to narrow by when no site is named. installedFrom keeps the records installed from that listing (their install stamp\'s listingId); a kind that is never installed answers none. |\n| source.record(id, data) | One stored document as the owner shares it, in the same shape its server index answers, or null to leave it out (deleted, unnamed). |\n| pluginRecordListQuery(kind, firestore, request) / pluginRecordsFromRows(kind, rows) | The reader\'s half: the query to listen to, and the rows read back through the owner. Both answer nothing where no plugin keeps the kind here. |\nThe reader runs the query with the console\'s own collection listener, so the read is bounded, retried and reported like every other list. Import it by its own subpath (@aglyn/aglyn/plugin-manager/plugin-record-lists). Data lists the work'
5865
+ },
5852
5866
  {
5853
5867
  path: '/developers/plugins/reference/plugin-manager-api',
5854
5868
  title: 'Plugin-manager API reference',
@@ -5896,7 +5910,7 @@ export const DOCS_SECTION_INDEX = [
5896
5910
  title: 'Plugin-manager API reference',
5897
5911
  heading: 'Record timeline — `plugin-record-timeline` (`/server`)',
5898
5912
  anchor: '#record-timeline--plugin-record-timeline-server',
5899
- text: 'A plugin that sends mail or books meetings files what happened on the contact, company or deal it happened with — an email sent, a reply read, a task for a person — without writing the record system\'s documents or importing its plugin. The record system registers one writer (a slot), and a caller asks for it:\n| API | Semantics |\n| registerPluginRecordTimelineWriter(writer, { pluginId? }) | The record system\'s writer. A single-implementation contract: a second plugin\'s writer throws naming both, and the incumbent keeps serving. The CRM registers it from its console API surface. |\n| pluginRecordTimelineWriter() | The writer with its owner, or null when no plugin keeps records — a caller then files nothing. |\n| writer.logActivity(request) | An activity on a record, scoped like one a member made on hostId. An email is filed ONCE per Message-ID, under the id the capture address files a copy of the same message by; any other kind once per the caller\'s dedupeKey. |\n| writer.createTask(request) | A task for assigneeUid, due at dueAtMs, filed once per dedupeKey. |\nBoth answer { ok: true, id, created } — created: false for an entry a previous call, or a copy through another door, already filed — or a refusal ({ ok: false, status, error }: no record system on the plan, a record at its activity ceiling), and never throw for a refusal. The registry authenticates nobody: the caller has already decided the entry is the workspace\'s to write.'
5913
+ text: 'A plugin that sends mail or books meetings files what happened on the person or the company it happened with — an email sent, a reply read, a meeting booked, a task for a person — without writing the record system\'s documents or importing its plugin. The record system registers one writer (a slot), and a caller asks for it:\n| API | Semantics |\n| registerPluginRecordTimelineWriter(writer, { pluginId? }) | The record system\'s writer. A single-implementation contract: a second plugin\'s writer throws naming both, and the incumbent keeps serving. The CRM registers it from its server declarations, so a door in either app reaches it. |\n| pluginRecordTimelineWriter() | The writer with its owner, or null when no plugin keeps records — a caller then files nothing. |\n| writer.logActivity(request) | An activity on a record, scoped like one a member made on hostId. An email is filed ONCE per Message-ID, under the id the capture address files a copy of the same message by; any other kind once per the caller\'s dedupeKey. |\n| writer.createTask(request) | A task for assigneeUid (\'\' for nobody, null for whoever holds the record it lands on), due at dueAtMs, filed once per dedupeKey. |\nA caller that holds no id of the record system\'s names how to find the record instead: link: { record: { kind, id }, email }. record is one the record system handed it — the record a booking link was dropped from, through the zone the owner drew the link in — carried back unread; email is the person\'s address. The owner files on the record it finds, as hostId sees it: the named record first, then the person at the address, and answers 404 when neither finds one. A kind it does not keep, or a record the site cannot see, falls through to the address.\nBoth answer { ok: true, id, created } — created: false for '
5900
5914
  },
5901
5915
  {
5902
5916
  path: '/developers/plugins/reference/plugin-manager-api',
@@ -5933,6 +5947,13 @@ export const DOCS_SECTION_INDEX = [
5933
5947
  anchor: '#interaction-steps--site-interactions',
5934
5948
  text: 'An interaction is what a published page does when a visitor does something: a trigger (a click, a hover, an element scrolled into view, a server event) and an ordered list of steps. The platform owns the shape and its own client steps — showing and hiding elements, menus and drawers, classes and ARIA attributes, scrolling, a video, an alert, a redirect, an analytics event — and treats every step it does not know as the server\'s, handed to whichever host-event listener owns it.\nA plugin whose step the besigner\'s interaction builder should offer declares it in plugins.config.json, not from code:\n- type is the name the step is stored under; one plugin declares each type.\n- picks, for a step that acts on one of the plugin\'s records: the builder lists them from collection (one the SAME plugin declares under hostCollections), stores the pick\'s id in idField and its name, as a display hint, in nameField. validateInteraction refuses a step that names neither, with Step N: .\n- The declarations are compiled into core (declaredInteractionSteps()), because the builder and every validator read them with no plugin loaded.\n| API | Semantics |\n| SiteInteraction / InteractionTrigger / InteractionStepBase | The stored shape. Step is the platform\'s client vocabulary (ClientInteractionStep) unless a reader names more. |\n| validateInteraction(interaction, { validateStep? }) | The name, the trigger and its conditions, the step count, each step\'s guard, the client steps and every declared pick. validateStep is the owner\'s check for its own step types. |\n| isClientActionStep(step) / isClientStepEntitled(step, tiers) | Whether the page runs a step, and whether the site\'s plan lets it. |\n| SiteAlert | What a siteAlert step, or a listener, hands back to the visitor\'s page. |'
5935
5949
  },
5950
+ {
5951
+ path: '/developers/plugins/reference/plugin-manager-api',
5952
+ title: 'Plugin-manager API reference',
5953
+ heading: 'Server steps — `plugin-server-steps` (`/server`)',
5954
+ anchor: '#server-steps--plugin-server-steps-server',
5955
+ text: 'Every interaction step that is not a client step is the server\'s to run. The automation engine runs its own; a step that writes ANOTHER plugin\'s records — a dataset row, a contact\'s stage — is that plugin\'s, and the plugin registers the executor that runs it from its serverDeclarations entry. The engine keeps the step\'s guard, the run\'s order, the run history and the nesting cap, and hands the executor one step at a time:\nA first-party plugin also declares the steps it runs, so a boot that skipped the registration is refused rather than read as a step that ran:\n| API | Semantics |\n| registerServerStepExecutor(types, run, { pluginId? }) | Owner = the loader\'s marker, else pluginId; no owner throws. A type another plugin declares or already holds throws, and nothing is registered unless every type is accepted. Registering again replaces the plugin\'s own. Returns the unregister. |\n| ServerStepRequest | The site, the owning organization\'s billing document and id (already read by the run\'s gate), the automation (run: kind, id, name), the event, its payload and the step as stored. |\n| ServerStepAnswer | error is the step\'s line in the run history when nothing was written; the run goes on to the next step, and a throw reads the same. detail is the fact the history carries. emit is an event the write earned, which the ENGINE raises one level deeper under its nesting cap. |\n| pluginServerStepExecutor(type) | What the engine asks: the registered executor; for a declared step with none, the app\'s declarations step once more and then a ServerStepUnavailableError (the step\'s failure); null for a type nobody declares or runs. A type the engine runs itself never reaches it. |\n| declaredServerSteps() / declaredServerStep(type) | The compiled serverSteps rows. |'
5956
+ },
5936
5957
  {
5937
5958
  path: '/developers/plugins/reference/plugin-manager-api',
5938
5959
  title: 'Plugin-manager API reference',
@@ -5957,9 +5978,16 @@ export const DOCS_SECTION_INDEX = [
5957
5978
  {
5958
5979
  path: '/developers/plugins/reference/plugin-manager-api',
5959
5980
  title: 'Plugin-manager API reference',
5960
- heading: 'A meter line a plugin bills itself — `plugin-metered-lines` (`/server`)',
5961
- anchor: '#a-meter-line-a-plugin-bills-itself--plugin-metered-lines-server',
5962
- text: 'The monthly usage sweep prices several lines into one figure and posts it as a single Stripe meter event. A plugin that bills one of those lines its own way claims it, and the sweep leaves it out of the metered figure from the month the claim names — so one month\'s usage reaches exactly one invoice.\n| API | Semantics |\n| registerPluginMeteredLine({ lineId, billsFrom, closeMonth?, pluginId? }) | Claims a line. billsFrom() answers the first YYYY-MM the plugin bills, or null while it is registered and not yet switched on; it is called per question, so a deployment change takes effect without a restart. One plugin per line — a second claimant throws. |\n| pluginBillsMeteredLine(lineId, month) | What the sweep asks. false for an unclaimed line, a month before the claim, an unparseable start month, and a claim that throws — so every failure bills through the sweep, which already works. |\n| runPluginMeteredLineClose(lineId, context) | Run by the sweep once a month has CLOSED, per workspace, with { orgId, month, org, stripeCustomerId } — the remainder of a line charged as it accrues is owed whether or not the meter reported. Errors are logged, never the sweep\'s. |\n| pluginMeteredLineOwner(lineId) | The claiming plugin, for diagnostics. |\nOnly the BILLING moves. A claimed line is still measured, still priced and still written to the month\'s audit fields, so a month\'s usage history reads the same either way and the handover is countable from the rows.\n| API | Semantics |\n| registerPluginEventHandler(event, handler, { pluginId? }) | Subscribes; attributed to the registering plugin. Idempotence is the subscriber\'s to keep — check listPluginEventHandlers(event) before subscribing again after a registry reset. |\n| runPluginEventHandlers(event, payload) | What the core route calls afte'
5981
+ heading: 'Site beacons — `plugin-site-beacons` (`/server`)',
5982
+ anchor: '#site-beacons--plugin-site-beacons-server',
5983
+ text: 'A published page reports what visitors did through one collector, and the collector owns what every beacon owes before anything is counted: the host must exist, a lockdown refuses it silently, and the per-address rate limit applies. What a plugin\'s site runtime reports beyond a pageview — an announcement bar seen, a popup dismissed — is the plugin\'s to count, in its own documents. It claims the body FIELD that marks its beacons, from serverDeclarations, and imports its counting code on the first beacon:\n| API | Semantics |\n| registerPluginSiteBeacon({ field, count }, { pluginId? }) | Claims a field. One owner per field: a second plugin throws naming both and the incumbent keeps it; the owner registering again replaces its own. |\n| count({ hostId, day, dayExpiresAt, body }) | One beacon, after the collector\'s gates. day is the collector\'s UTC day bucket and dayExpiresAt the platform\'s retention for that day\'s analytics document — an owner that writes there stamps it. body is what the browser sent: validate every field read. The collector answers 204 and counts no pageview; a throw is logged, never surfaced. |\n| pluginSiteBeaconFor(body) | The owner of a beacon, or null when no claimed field holds a non-empty string. |\nThe site runtime posts with sendAnalyticsBeacon({ hostId, tasting: \'poured\' }). Marketing\'s announcement bar and popup are the first: { overlay, overlayId? }.\n| API | Semantics |\n| registerPluginEventHandler(event, handler, { pluginId? }) | Subscribes; attributed to the registering plugin. Idempotence is the subscriber\'s to keep — check listPluginEventHandlers(event) before subscribing again after a registry reset. |\n| runPluginEventHandlers(event, payload) | What the core route calls after its write: every handler in registration order, a failure logged an'
5984
+ },
5985
+ {
5986
+ path: '/developers/plugins/reference/plugin-manager-api',
5987
+ title: 'Plugin-manager API reference',
5988
+ heading: 'Usage meters — `plugin-usage-meters` (`/server`)',
5989
+ anchor: '#usage-meters--plugin-usage-meters-server',
5990
+ text: 'The monthly usage sweep writes one rollup per workspace and month (orgs/{orgId}/usage/{month}) and posts the month\'s billed figure as one Stripe meter event. A plugin whose usage is measured in its own storage and priced by its own band registers a meter from its serverDeclarations entry, and the sweep asks it once per workspace; the plugin also declares the meter\'s id under usageAxes → meters, so the sweep refuses to bill a month while a declared meter is unregistered — the workspace fails loudly for that pass and is swept again the next day, rather than billed without it. Import the registry by its subpath; it is not on a barrel.\n| API | Semantics |\n| registerPluginUsageMeter({ id, measure, closeMonth?, pluginId? }) | Idempotent per plugin and id. A meter with no owner, no id or no measure throws. |\n| measure(context) | context is { orgId, org, month, closed, previous, releaseFlagOn(key) } — previous is the month\'s rollup as it stood before this run, for a stock meter\'s period-end reading. Answers { fields, periodEndFields?, billedUsd, periodEndBasis? }: fields are written onto the rollup on every run, under the names the plugin\'s usage axes read, and never over a field the platform writes; periodEndFields only while the month is open; billedUsd enters the billed figure, rounded to cents. A throw fails that workspace\'s pass. |\n| closeMonth(context) | Run for every workspace of a CLOSED month, every day the sweep runs, before its already-reported skip — for a plugin that bills part of its usage itself and settles the remainder at the month\'s end. context.stripeCustomerId() reads the workspace\'s billing document only when called. Errors are logged, never the sweep\'s. |\n| listPluginUsageMeters() / unregisteredPluginUsageMeters() | What the sweep runs, declared meters fir'
5963
5991
  },
5964
5992
  {
5965
5993
  path: '/developers/plugins/reference/plugin-manager-api',
@@ -5989,6 +6017,13 @@ export const DOCS_SECTION_INDEX = [
5989
6017
  anchor: '#consent-group-changes--plugin-consent-group-change-server',
5990
6018
  text: 'When an organization creates, edits or dissolves a consent group, its sites start or stop reading each other\'s refusals, and records keyed by a group\'s id have to move. Core carries the refusals it stores per site — the unsubscribe list, the topic opt-outs, the email frequency — and flips the declaration. A plugin that keeps its own per-site refusals, or records keyed by a group\'s id, registers a participant from its server declarations:\n| API | Semantics |\n| registerPluginConsentGroupParticipant(participant, { pluginId? }) | One participant per plugin; registering again replaces it in place. Participants run in registration order. |\n| preview({ orgId, plan }) | Lines for the admin\'s review step, each { id, text, count, severity }, with count: null for a figure it did not count. Isolated: a preview that throws shows as null lines, and the review says part of the change could not be counted. |\n| run({ orgId, changeId, plan, phase, cursor, deadlineMs, dryRun }) | Called for carry before the declaration flips (twice: once, and again as the catch-up), rehome after it, and sweep six minutes after it. Answers { done, cursor, counts }; the executor calls again from cursor until done. Not isolated: a throw stops the change before it takes effect, and five failures in a row mark it stalled while it keeps retrying. |\n| summarize(counts) | Optional. The participant\'s clause of the "Finished a consent group change" activity line, from its counts totaled over the change. |\nThe plan names each carry — toHostId is about to stop reading fromHostId\'s refusals, so copy them first — and each holder flow, a move or a split of the records under a group\'s id or a lone site\'s id. A run returns before deadlineMs, writes nothing on dryRun, and keeps every write create-if-absent or a restrictive'
5991
6019
  },
6020
+ {
6021
+ path: '/developers/plugins/reference/plugin-manager-api',
6022
+ title: 'Plugin-manager API reference',
6023
+ heading: 'Subscription topics — `subscriptionTopics`, `app-utils/subscription-topics`',
6024
+ anchor: '#subscription-topics--subscriptiontopics-app-utilssubscription-topics',
6025
+ text: 'Every marketing-purpose message names the stream it is sent under, its unsubscribe link carries that stream, and the send path refuses a recipient who left it. The state and the policy are the mail rail\'s; a plugin brings the streams its mail is sent under, declared in the subscriptionTopics block of plugins.config.json and compiled, because the unsubscribe and preference pages name a stream whether or not the sending plugin has loaded:\nid is a wire value — a Firestore path component and a component of the unsubscribe link\'s signed subject — so it is never renamed, and it may carry no / or :. order is the stream\'s place on the preference page. One plugin marks its stream default: the stream a campaign or a scheduled automated email belongs to when it names none.\n| API | Semantics |\n| DECLAREDSUBSCRIPTIONTOPICS / DEFAULTSUBSCRIPTIONTOPICID | The declared streams in preference-page order — the floor of every org\'s catalog, present with no write anywhere — and the default stream\'s id (\'\' when none is declared). |\n| mergeSubscriptionTopics(stored) / activeSubscriptionTopics(topics) / resolveSubscriptionTopic(id, topics) | The catalog a reader sees (the floor, overlaid by what the org stored, plus its own topics), the same without retired ones, and the stream a message belongs to — an unknown or missing id resolves to the default. |\n| readTopicSubscriptionState(entry) / topicRequiresDoubleOptIn(topic, siteDefault) / TOPICOPTOUTSSUBCOLLECTION | One recipient\'s standing on one stream (subscribed, pending, opted-out) from hosts/{hostId}/topicOptOuts, and whether joining needs a confirmation click. Written through @aglyn/tenant-data-admin/server/topic-subscriptions. |\nThe catalog an org authors is kept by the Email plugin, which publishes it as the subscriptionTopic record index'
6026
+ },
5992
6027
  {
5993
6028
  path: '/developers/plugins/reference/plugin-manager-api',
5994
6029
  title: 'Plugin-manager API reference',
@@ -6015,7 +6050,7 @@ export const DOCS_SECTION_INDEX = [
6015
6050
  title: 'Plugin-manager API reference',
6016
6051
  heading: 'Usage alerts — `plugin-manager/usage-alert-contributors`',
6017
6052
  anchor: '#usage-alerts--plugin-managerusage-alert-contributors',
6018
- text: 'The usage-alerts sweep walks every org once, reads its usage, and sends core\'s own alerts: the plan quotas, the customer\'s budget and the free plan\'s bandwidth cap. A plugin that meters a cost or enforces a ceiling core knows nothing about adds staff alerts to the same sweep by registering a contributor from its serverDeclarations entry, importing the rule itself lazily. Import the registry by its subpath; it is not on a barrel.\n| API | Semantics |\n| registerUsageAlertContributor(contributor) | Idempotent per plugin and id: the same pair again replaces the earlier contributor in place. A contributor with no plugin id, no id or no evaluate throws. |\n| listUsageAlertContributors() | What the sweep runs for each org, after the budget alert and before the bandwidth cap: FIRSTPARTYPLUGINS catalog order, then any other plugin id, then registration order within a plugin. |\n| context.recordAlert(key, threshold) | Records the dedupe guard and answers whether the alert may be sent. On an org\'s first, silent evaluation it records the guard and answers false. Guard keys share one map with core\'s checks, so name yours for what it measures. |\n| context.alertStaff(alert) | The sweep\'s own sender: the staff bell, the staff inbox with the same words, and a row in the run\'s report. It sends nothing on an org\'s first, silent evaluation. |\n| context.org / spend / guards / month | The org document, its spend this month, its guard map as read, and the month the run dedupes against. A contributor never writes a guard itself. |\nContributors run one at a time and are isolated: a throw is logged against the contributor, a guard it recorded for an alert it never delivered is dropped so the next sweep tries again, and the next contributor runs.'
6053
+ text: 'The usage-alerts sweep walks every org once, reads its usage, and sends core\'s own alerts: the plan quotas, the customer\'s budget and the free plan\'s bandwidth cap. A plugin that meters a cost or enforces a ceiling core knows nothing about adds to the same sweep by registering a contributor from its serverDeclarations entry, importing the rule itself lazily: quotaChecks for the bands the WORKSPACE is warned about, evaluate for STAFF alerts, or both. Import the registry by its subpath; it is not on a barrel.\nA band that needs no code — a monthly host counter against what the plan includes of an entitlement — is declared on the plugin\'s usage band instead (bands[].alert, Usage axes), and an org capacity a plugin declares (plugin-org-capacity) is warned about as it fills with no contributor at all.\n| API | Semantics |\n| registerUsageAlertContributor(contributor) | Idempotent per plugin and id: the same pair again replaces the earlier contributor in place. A contributor with no plugin id, no id, or neither evaluate nor quotaChecks throws. |\n| quotaChecks(context) | Answers { key, label, noun, used, limit, cadence, outcome, reached, approach, reachedTitle? }[] for one org, from context.org, spend and month. cadence is crossing (something the workspace has: announced once when reached, again only after usage falls back) or monthly (a meter that resets on the 1st); outcome is stops, bills or continues; reached and approach are the sentences that say what happens at the band. The sweep opens every notice itself — the workspace, the figures, then noun — and runs the checks after its own through the same approach threshold, guard map, first-sweep seeding, console notification and email, so a plugin\'s band warns exactly as a platform band does. A throw costs only that contributor\''
6019
6054
  },
6020
6055
  {
6021
6056
  path: '/developers/plugins/reference/plugin-manager-api',
@@ -6050,7 +6085,7 @@ export const DOCS_SECTION_INDEX = [
6050
6085
  title: 'Plugin-manager API reference',
6051
6086
  heading: 'Usage axes — `plugin-usage-axes`',
6052
6087
  anchor: '#usage-axes--plugin-usage-axes',
6053
- text: 'The meters a plugin contributes to the platform\'s ONE cost model (orgMonthlyCogsUsd, which the discount guardrail and the staff org page read) and to the staff utilization table. A first-party plugin names a function under register.usageAxes; the manifest generator loads ${package}/usage-axes, validates the answer and compiles it into first-party-plugins.generated.ts, beside the plan figures.\n| Field | Semantics |\n| register.usageAxes | The function\'s name. Called by the generator, never at runtime — the guardrail and the staff page price a rollup without loading a plugin, and a meter a registry had not filled would price at nothing, which approves a discount. |\n| costAxes[] | { id, order, fields, fallbackFields?, recordedFields?, rate?, live? }. fields are summed from the month\'s usage rollup; fallbackFields are read only when a rollup carries none of fields (an older, narrower basis — a measured zero never falls back); recordedFields ride along so the sum stays legible and are never priced. rate names a key of ORGCOGSUNITRATESUSD — never a number: the money stays core\'s, beside the billed table it reconciles against — or is left out when the fields are already dollars. live: { collection, fields } names orgs/{orgId}/{collection}/{month}, whose first positive field replaces the rollup\'s snapshot wherever a reader fetches it. |\n| bands[] | { id, label, order, fields, fallbackFields?, entitlement, perHost?, unitCostUsd? }. entitlement is the resolved key holding what the plan includes (read with planQuotaOf, so an undeclared key is nothing included); perHost expands it by the host limit; unitCostUsd is set when the band is sold in a unit OF cost and the rollup records dollars, and usage is then the dollars over it, rounded up. |\n| pluginCostAxes() / pluginUsageBands() / '
6088
+ text: 'The meters a plugin contributes to the platform\'s ONE cost model (orgMonthlyCogsUsd, which the discount guardrail and the staff org page read), to the staff utilization table, and to a workspace\'s usage budget. A first-party plugin names a function under register.usageAxes; the manifest generator loads ${package}/usage-axes, validates the answer and compiles it into first-party-plugins.generated.ts, beside the plan figures.\n| Field | Semantics |\n| register.usageAxes | The function\'s name. Called by the generator, never at runtime — the guardrail and the staff page price a rollup without loading a plugin, and a meter a registry had not filled would price at nothing, which approves a discount. |\n| costAxes[] | { id, order, fields, fallbackFields?, recordedFields?, staffFields?, rate?, live? }. fields are summed from the month\'s usage rollup; fallbackFields are read only when a rollup carries none of fields (an older, narrower basis — a measured zero never falls back); recordedFields ride along so the sum stays legible and are never priced; staffFields are what the plugin\'s usage sweep writes beside the meter for its own staff columns to read — served on the staff usage rows (null where a rollup never wrote one) and never handed to the cost model. rate names a key of ORGCOGSUNITRATESUSD — never a number: the money stays core\'s, beside the billed table it reconciles against — or is left out when the fields are already dollars. live: { collection, fields } names orgs/{orgId}/{collection}/{month}, whose first positive field replaces the rollup\'s snapshot wherever a reader fetches it. |\n| bands[] | { id, label, order, fields, fallbackFields?, entitlement, perHost?, unitCostUsd?, hostCounter?, alert?, metered? }. entitlement is the resolved key holding what the plan includes (r'
6054
6089
  },
6055
6090
  {
6056
6091
  path: '/developers/plugins/reference/plugin-manager-api',
@@ -6519,7 +6554,7 @@ export const DOCS_SECTION_INDEX = [
6519
6554
  title: 'Aglyn Assist',
6520
6555
  heading: 'What it can do',
6521
6556
  anchor: '#what-it-can-do',
6522
- text: '- Answer how-to questions about building sites, publishing, domains, commerce, bookings, workflows, datasets, team roles, and billing.\n- Link you to the source — under each written answer is a Sources list naming the documentation sections it drew on, each one a link straight to that section. See Where an answer came from.\n- Guide you on the page you\'re on (Pro plans and up) — the assistant knows which console page you\'re viewing and answers about that page, rather than giving you a general answer you then have to translate.\n- Offer to take you to the right page (Pro plans and up) — when what you asked for starts somewhere else, it can show a card that opens that page for you. See Offers to open a page.\n- Propose edits in the Besigner (with AI generation on your plan) — ask for a change to the page, component or layout you have open, and it shows exactly what would change before anything does. See Edits in the Besigner.\nAglyn Assist answers and directs. It never saves, publishes or changes a setting on its own. The one thing it can change is the canvas you have open in the Besigner — and only when you apply a change it proposed, as unsaved edits you can undo — so there\'s nothing it can do by accident; you stay in control of every change.'
6557
+ text: '- Answer how-to questions about building sites, publishing, domains, commerce, bookings, workflows, datasets, team roles, and billing.\n- Link you to the source — under each written answer is a Sources list naming the documentation sections it drew on, each one a link straight to that section. See Where an answer came from.\n- Guide you on the page you\'re on (Starter and up) — the assistant knows which console page you\'re viewing and answers about that page, rather than giving you a general answer you then have to translate.\n- Offer to take you to the right page (Starter and up) — when what you asked for starts somewhere else, it can show a card that opens that page for you. See Offers to open a page.\n- Propose edits in the Besigner (with AI generation on your plan) — ask for a change to the page, component or layout you have open, and it shows exactly what would change before anything does. See Edits in the Besigner.\nAglyn Assist answers and directs. It never saves, publishes or changes a setting on its own. The one thing it can change is the canvas you have open in the Besigner — and only when you apply a change it proposed, as unsaved edits you can undo — so there\'s nothing it can do by accident; you stay in control of every change.'
6523
6558
  },
6524
6559
  {
6525
6560
  path: '/getting-started/aglyn-assist',
@@ -6568,7 +6603,7 @@ export const DOCS_SECTION_INDEX = [
6568
6603
  title: 'Aglyn Assist',
6569
6604
  heading: 'Message limits',
6570
6605
  anchor: '#message-limits',
6571
- text: '- Free workspaces get a limited number of assistant messages each day, with answers and docs links, and 300 AI credits a month for the assistant and AI generation together. The credits belong to the person who owns the workspace, so several Free workspaces owned by one account share them; when they are used, AI pauses until next month or an upgrade, and nothing is ever billed. A new account waits a short while after signing up before it can generate, and free requests are capped per day.\n- Paid plans include their own monthly AI credits for the assistant and AI generation together, rising with the plan, and sell credits past the band rather than stopping at it — so the assistant keeps working and the month\'s overage is billed, unless you switch that off or set a ceiling in Billing.\n- Pro and higher plans also get page-aware guidance and the offers to open a page described above.\nWhen a free workspace reaches its daily limit, the assistant says so and the counter resets the next day (UTC).\nAnswers quoted straight from the documentation are free and are not counted, so a workspace at its daily limit can still ask how-to questions.'
6606
+ text: '- Free workspaces get a limited number of assistant messages each day, with answers and docs links, and 300 AI credits a month for the assistant and AI generation together. The credits belong to the person who owns the workspace, so several Free workspaces owned by one account share them; when they are used, AI pauses until next month or an upgrade, and nothing is ever billed. A new account waits a short while after signing up before it can generate, and free requests are capped per day.\n- Paid plans include their own monthly AI credits for the assistant and AI generation together, rising with the plan, and sell credits past the band rather than stopping at it — so the assistant keeps working and the month\'s overage is billed, unless you switch that off or set a ceiling in Billing.\n- Every paid plan, Starter included, also gets page-aware guidance and the offers to open a page described above.\nWhen a free workspace reaches its daily limit, the assistant says so and the counter resets the next day (UTC).\nAnswers quoted straight from the documentation are free and are not counted, so a workspace at its daily limit can still ask how-to questions.'
6572
6607
  },
6573
6608
  {
6574
6609
  path: '/getting-started/aglyn-assist',
@@ -6638,7 +6673,7 @@ export const DOCS_SECTION_INDEX = [
6638
6673
  title: 'The console tour',
6639
6674
  heading: 'The app bar',
6640
6675
  anchor: '#the-app-bar',
6641
- text: '- Site switcher (left) — jump between the sites you belong to, search them by name with Find site…, or create a new one.\n- Breadcrumbs — show the current site\'s display name and where you are.\n- Page switcher — a searchable dropdown for hopping between pages without leaving the editor: your recent pages by default, or type to find any page or layout in the site by name. View all pages opens the full list.\n- Version dropdown (right, near your avatar) — pick or schedule a version to view.\n- Notifications — the bell shows an unread badge and drops down your 10 most recent notifications (form submissions, bookings, invoices, team changes), split across an Inbox tab (unread) and an Archive tab (already read). Mark all read clears the inbox, the gear opens your notification preferences, and View all opens the full paginated feed.\n- Account menu — your avatar opens it. The header shows your name and email with a gear to Manage Account, and the first row below repeats that destination as a labeled Manage account — a gear is not discoverable as a name. Then come Manage Team, Billing and Support, which appear only on a page that names a workspace and only for members whose role can reach those pages; Staff console (Aglyn staff only); a Documentation link that opens this docs site in a new tab; and Report an issue, directly under Documentation because "the docs didn\'t answer it" is the step before reporting it. Report an issue opens a form over the page you\'re on rather than navigating away — the page you were looking at is the most useful thing the report carries — and unlike the rows above it, it needs no particular role, no resolved workspace, and no paid plan: every signed-in member has it on every page. See Report an issue. Below that the menu holds the theme toggle (light / '
6676
+ text: '- Site switcher (left) — jump between the sites you belong to, search them by name with Find site…, or create a new one.\n- Breadcrumbs — show the current site\'s display name and where you are.\n- Page switcher — a searchable dropdown for hopping between pages without leaving the editor: your recent pages by default, or type to find any page or layout in the site by name. View all pages opens the full list.\n- Version dropdown (right, near your avatar) — pick or schedule a version to view.\n- Notifications — the bell shows an unread badge and drops down your 10 most recent notifications (form submissions, bookings, invoices, team changes), split across an Inbox tab (unread) and an Archive tab (already read). Mark all read clears the inbox, the gear opens your notification preferences, and View all opens the full paginated feed. The badge turns red while an unread critical notification is waiting, and amber for an unread warning. Each notification is colored the way the feed colors it (below).\n- Account menu — your avatar opens it. The header shows your name and email with a gear to Manage Account, and the first row below repeats that destination as a labeled Manage account — a gear is not discoverable as a name. Then come Manage Team, Billing and Support, which appear only on a page that names a workspace and only for members whose role can reach those pages; Staff console (Aglyn staff only); a Documentation link that opens this docs site in a new tab; and Report an issue, directly under Documentation because "the docs didn\'t answer it" is the step before reporting it. Report an issue opens a form over the page you\'re on rather than navigating away — the page you were looking at is the most useful thing the report carries — and unlike the rows above it, it needs no particul'
6642
6677
  },
6643
6678
  {
6644
6679
  path: '/getting-started/console-tour',
@@ -6694,7 +6729,7 @@ export const DOCS_SECTION_INDEX = [
6694
6729
  title: 'The console tour',
6695
6730
  heading: 'Workspace settings & notifications',
6696
6731
  anchor: '#workspace-settings--notifications',
6697
- text: 'Organization-wide settings (name, workspace URL) live under Organization → Settings, which also holds Profile, API keys, Branding, Single sign-on, Privacy, Ownership and Delete. Turning plugins on and off for the workspace is its own section, Organization → Plugins — Marketplace is for finding and installing new ones, not for administering what you already run:\nThe notifications feed\nYour in-app notification feed — billing, publishing, workflow failures — lives under Notifications, which has two sections in the rail on the left: All notifications and Settings. The first shows what arrived, newest first, with Mark all read above it. The feed\'s Filters narrows it by Type (is one type, or is any of up to thirty — a larger pick is refused, with a note above the feed) and by Status (is New or Read), and both can stand together. Each filter in force shows as a chip above the feed; remove the chip to drop it. The filters apply to the whole feed, not just the page on screen, and the pager then turns through the matches; see Filter and search a list. There is no search box, because a notification\'s words are not indexed:\nNotification settings\nEverything about what reaches you lives on the second section, Settings.\nWhat you are told about is a table: one row per category, one column for In console and one for Email. Each row says what arrives in that category, so you are never switching off a bucket you would have to guess the contents of.\n- In console is on for every category. Switching it off stops that category appearing in the feed and in the bell.\n- Email is off for every category. Switch one on and you also get a message in your inbox when one of those notifications arrives. This does not touch the mail Aglyn already sends you — invites, verification, receipts, password res'
6732
+ text: 'Organization-wide settings (name, workspace URL) live under Organization → Settings, which also holds Profile, API keys, Branding, Single sign-on, Privacy, Ownership and Delete. Turning plugins on and off for the workspace is its own section, Organization → Plugins — Marketplace is for finding and installing new ones, not for administering what you already run:\nThe notifications feed\nYour in-app notification feed — billing, publishing, workflow failures — lives under Notifications, which has two sections in the rail on the left: All notifications and Settings. The first shows what arrived, newest first, with Mark all read above it. The feed\'s Filters narrows it by Type (is one type, or is any of up to thirty — a larger pick is refused, with a note above the feed) and by Status (is New or Read), and both can stand together. Each filter in force shows as a chip above the feed; remove the chip to drop it. The filters apply to the whole feed, not just the page on screen, and the pager then turns through the matches; see Filter and search a list. There is no search box, because a notification\'s words are not indexed:\nHow urgent each notification is {#notification-levels}\nEvery notification carries a level, shown by the icon at the start of its row and the color of its type, both in the feed and in the bell:\n| Level | Looks like | For example |\n| Critical | Red icon, solid red type, red bar and a red wash over the row | A fraud signal, a failed payment, a form that stopped accepting submissions |\n| Warning | Amber icon, amber type, amber bar and a light amber wash | A limit reached, low stock, a held email |\n| Good news | Green icon, green type, green bar | An order or a booking, a new subscription |\n| Info | Blue icon, blue type | An invitation, a support reply, a usage step'
6698
6733
  },
6699
6734
  {
6700
6735
  path: '/getting-started/create-a-site',
@@ -7261,7 +7296,7 @@ export const DOCS_SECTION_INDEX = [
7261
7296
  title: 'Run an agency workspace',
7262
7297
  heading: 'Step 5 — Back up before you hand over',
7263
7298
  anchor: '#step-5-backups',
7264
- text: 'Every site exports a full backup from its own settings. Take one:\n- before any large redesign, so "put it back how it was" is a real option;\n- at handover, and give the client a copy — it is theirs, and having it makes the relationship easier to end well;\n- before deleting anything. Deleting a site is immediate and permanent, with no hold period — unlike deleting an organization, which has one. There is no undo and we keep no copy.\nTake one from the site\'s Admin → Backup & template. It includes the datasets that site can see — up to 50, with up to 1,000 records each — so a larger dataset also wants its own export from the Data tab (CSV or JSON), which downloads every record.'
7299
+ text: 'On Pro and above, every site exports a full backup from its own settings. Take one:\n- before any large redesign, so "put it back how it was" is a real option;\n- at handover, and give the client a copy — it is theirs, and having it makes the relationship easier to end well;\n- before deleting anything. Deleting a site is immediate and permanent, with no hold period — unlike deleting an organization, which has one. There is no undo and we keep no copy.\nTake one from the site\'s Admin → Backup & template. It includes the datasets that site can see — up to 50, with up to 1,000 records each — so a larger dataset also wants its own export from the Data tab (CSV or JSON), which downloads every record.'
7265
7300
  },
7266
7301
  {
7267
7302
  path: '/guides/run-an-agency-workspace',
@@ -7296,7 +7331,7 @@ export const DOCS_SECTION_INDEX = [
7296
7331
  title: 'Your first API call',
7297
7332
  heading: '',
7298
7333
  anchor: '',
7299
- text: 'Your first API call\nBy the end of this page you\'ll have made a real request to your own data and handled the errors properly. It assumes nothing — if you\'ve never used an API before, start at the top and follow along; if you have, skip to Step 3 and then read the API reference instead of the rest of this.\nEverything here uses curl, which is already installed on macOS and Linux and ships with Windows 10 and later. There\'s a JavaScript version of each step underneath.\nWhat you need\n- An organization on a plan that includes API access (Business or Advanced).\n- Permission to manage the organization — only owners and admins can create keys.\n- A terminal.'
7334
+ text: 'Your first API call\nBy the end of this page you\'ll have made a real request to your own data and handled the errors properly. It assumes nothing — if you\'ve never used an API before, start at the top and follow along; if you have, skip to Step 3 and then read the API reference instead of the rest of this.\nEverything here uses curl, which is already installed on macOS and Linux and ships with Windows 10 and later. There\'s a JavaScript version of each step underneath.\nWhat you need\n- An organization on a plan that includes API access — Business or any plan above it.\n- Permission to manage the organization — only owners and admins can create keys.\n- A terminal.'
7300
7335
  },
7301
7336
  {
7302
7337
  path: '/guides/your-first-api-call',
@@ -7947,7 +7982,7 @@ export const DOCS_SECTION_INDEX = [
7947
7982
  title: 'Actions builder',
7948
7983
  heading: 'When to use which',
7949
7984
  anchor: '#when-to-use-which',
7950
- text: 'Both builders run the same steps — everything listed under Steps that runs on the server is offered in a workflow too, with the same fields, conditions and plan tiers. What differs is what starts the automation and whether there is a page in front of it:\n| Use the actions builder | Use a workflow |\n| Something a visitor does — a click, a hover, exit intent, a scroll depth | Something that happened on the server — a submission, an order, a CRM event |\n| Anything that changes the page: menus, drawers, class toggles, overlays, redirects | Server-side work only; the in-page effects are not offered |\n| Fastest to set up | Composes your functions and variables, and binds each result for the steps after it |'
7985
+ text: 'Both builders run the same steps — everything listed under Steps that runs on the server is offered in a workflow too, with the same fields, conditions and plan tiers. What differs is what starts the automation and whether there is a page in front of it:\n| Use the actions builder | Use a workflow |\n| Something a visitor does — a click, a hover, exit intent, a scroll depth | Something that happened on the server — a submission, a booking, a CRM event |\n| Anything that changes the page: menus, drawers, class toggles, overlays, redirects | Server-side work only; the in-page effects are not offered |\n| Fastest to set up | Composes your functions and variables, and binds each result for the steps after it |'
7951
7986
  },
7952
7987
  {
7953
7988
  path: '/marketing-and-automation/workflows-and-actions/actions-builder',
@@ -7975,7 +8010,7 @@ export const DOCS_SECTION_INDEX = [
7975
8010
  title: 'Build a workflow',
7976
8011
  heading: '2. Choose a trigger',
7977
8012
  anchor: '#2-choose-a-trigger',
7978
- text: 'Pick the site event that starts the workflow — for example a form submission, a new member, an order, or a CRM event such as Contact created. The event\'s data is available to every step that follows, and the Filter field\'s helper text names the keys the chosen event puts in scope.'
8013
+ text: 'Pick the site event that starts the workflow — for example a form submission, a new member, a booking, or a CRM event such as Contact created. The event\'s data is available to every step that follows, and the Filter field\'s helper text names the keys the chosen event puts in scope.'
7979
8014
  },
7980
8015
  {
7981
8016
  path: '/marketing-and-automation/workflows-and-actions/build-a-workflow',
@@ -8066,7 +8101,7 @@ export const DOCS_SECTION_INDEX = [
8066
8101
  title: 'Automation',
8067
8102
  heading: '',
8068
8103
  anchor: '',
8069
- text: 'Automation\nAutomate what happens on your site. The section holds three tabs — Workflows, Actions and Webhooks — over one engine. A workflow step is either a call to one of your functions or any of the steps the actions builder offers, so "write to a dataset, send an email, update the contact" is a sequence of one workflow rather than two automations that have to find each other. Webhooks connect Aglyn to outside systems.\nThe two tabs are two ways into the same steps, kept apart by what they run on:\n| | What starts it | What it can do |\n| Workflows | a server event — a form submission, an order, a new member, a CRM event | function calls and every server-side step |\n| Actions | the same server events, and what a visitor does on the page — a click, a hover, exit intent, a scroll depth | everything a workflow can do, plus the in-page effects |\nSo an automation that touches the page is an action; one triggered by something that happened on the server is a workflow; and the steps in between are the same steps. Nothing you have already built moves or changes.\nPlan availability Basic in-page interactions (menu/drawer open-close, show/hide, class toggles, sticky nav, navigation, site alerts) are on every plan and never metered. The automations engine — server-side steps, analytics, overlays, and custom JS — is Pro+ with metered runs per tier. Webhooks are Business.'
8104
+ text: 'Automation\nAutomate what happens on your site. The section holds three tabs — Workflows, Actions and Webhooks — over one engine. A workflow step is either a call to one of your functions or any of the steps the actions builder offers, so "write to a dataset, send an email, update the contact" is a sequence of one workflow rather than two automations that have to find each other. Webhooks connect Aglyn to outside systems.\nThe two tabs are two ways into the same steps, kept apart by what they run on:\n| | What starts it | What it can do |\n| Workflows | a server event — a form submission, a booking, a new member, a CRM event | function calls and every server-side step |\n| Actions | the same server events, and what a visitor does on the page — a click, a hover, exit intent, a scroll depth | everything a workflow can do, plus the in-page effects |\nSo an automation that touches the page is an action; one triggered by something that happened on the server is a workflow; and the steps in between are the same steps. Nothing you have already built moves or changes.\nPlan availability Basic in-page interactions (menu/drawer open-close, show/hide, class toggles, sticky nav, navigation, site alerts) are on every plan and never metered. The automations engine — server-side steps, analytics, overlays, and custom JS — is Pro+ with metered runs per tier. Webhooks are Business.'
8070
8105
  },
8071
8106
  {
8072
8107
  path: '/marketing-and-automation/workflows-and-actions/overview',
@@ -8122,7 +8157,7 @@ export const DOCS_SECTION_INDEX = [
8122
8157
  title: 'Webhooks',
8123
8158
  heading: 'Outbound webhooks',
8124
8159
  anchor: '#outbound-webhooks',
8125
- text: 'Send an HTTP request to a URL you control when a site event fires — for example, post to a Slack endpoint or your own API when an order is placed.\n1. Add an outbound webhook and paste the destination URL.\n2. Choose the event that should fire it.\n3. Save. Aglyn calls your URL with the event payload each time it fires.'
8160
+ text: 'Send an HTTP request to a URL you control when a site event fires — for example, post to a Slack endpoint or your own API when a form is submitted or a new booking comes in.\n1. Add an outbound webhook and paste the destination URL.\n2. Choose the event that should fire it.\n3. Save. Aglyn calls your URL with the event payload each time it fires.'
8126
8161
  },
8127
8162
  {
8128
8163
  path: '/marketing-and-automation/workflows-and-actions/webhooks',
@@ -8178,7 +8213,7 @@ export const DOCS_SECTION_INDEX = [
8178
8213
  title: 'Add-ons',
8179
8214
  heading: 'Aglyn AI',
8180
8215
  anchor: '#aglyn-ai',
8181
- text: 'Aglyn AI is the generative add-on: with it on, the assistant builds for you — pages, components, emails, campaigns, products and more — rather than only answering questions about them. It is bought once for the workspace, on any paid plan, and applies to every member who has the permission to generate. On Starter it also unlocks the guided AI assist that Pro and up already include. What each door does is under Aglyn AI.\n- Everything it builds is a draft. The add-on cannot publish a page, change a live one or send an email. It writes drafts and unpublished versions, which a person reviews and publishes through the buttons that already exist — see the draft rule.\n- One pool, one meter. The add-on adds a band of AI credits to the band your plan already includes, every month. There is no second meter: the AI credits meter on Billing → Usage shows your plan\'s credits and the add-on\'s as one figure, and the same overage rate, stop switch and monthly ceiling apply to the whole band — see AI credits and overage.\n- Priced by plan. The add-on\'s monthly price and the credits it adds both rise with the plan. The figures are on aglyn.com/pricing and in Billing before you confirm; on annual billing it bills yearly with the plan, like every add-on. Enterprise workspaces carry generative building in their agreement instead of buying it.\n- Starter widens a band it already has. Starter includes its own monthly AI credits, and credits past them are sold at the same rate as Pro — so the stop switch and the ceiling are on its billing page with or without the add-on. Adding Aglyn AI increases the band and opens generative building and the guided assist.\n- Free workspaces do not buy it. They generate against the monthly taste instead, which stops at its band and never bills.\n- Removing it tak'
8216
+ text: 'Aglyn AI is the generative add-on: with it on, the assistant builds for you — pages, components, emails, campaigns, products and more — rather than only answering questions about them. It is bought once for the workspace, on any paid plan, and applies to every member who has the permission to generate. What each door does is under Aglyn AI.\n- Everything it builds is a draft. The add-on cannot publish a page, change a live one or send an email. It writes drafts and unpublished versions, which a person reviews and publishes through the buttons that already exist — see the draft rule.\n- One pool, one meter. The add-on adds a band of AI credits to the band your plan already includes, every month. There is no second meter: the AI credits meter on Billing → Usage shows your plan\'s credits and the add-on\'s as one figure, and the same overage rate, stop switch and monthly ceiling apply to the whole band — see AI credits and overage.\n- Priced by plan. The add-on\'s monthly price and the credits it adds both rise with the plan. The figures are on aglyn.com/pricing and in Billing before you confirm; on annual billing it bills yearly with the plan, like every add-on. Enterprise workspaces carry generative building in their agreement instead of buying it.\n- Starter widens a band it already has. Starter includes its own monthly AI credits, and credits past them are sold at the same rate as Pro — so the stop switch and the ceiling are on its billing page with or without the add-on. Adding Aglyn AI increases the band and opens generative building.\n- Free workspaces do not buy it. They generate against the monthly taste instead, which stops at its band and never bills.\n- Removing it takes effect at the end of the period you already paid for, like every add-on removal. Then the band shrin'
8182
8217
  },
8183
8218
  {
8184
8219
  path: '/workspace-and-billing/billing-and-plans/add-ons',
@@ -8213,7 +8248,7 @@ export const DOCS_SECTION_INDEX = [
8213
8248
  title: 'Bandwidth',
8214
8249
  heading: 'What each plan includes',
8215
8250
  anchor: '#what-each-plan-includes',
8216
- text: '| Plan | Included bandwidth per month | Past the allowance |\n| Free | 2 GB | Sites are paused until the start of next month |\n| Starter | 50 GB | Keeps serving; the extra is billed |\n| Pro | 125 GB | Keeps serving; the extra is billed |\n| Business | 185 GB | Keeps serving; the extra is billed |\n| Scale | 290 GB | Keeps serving; the extra is billed |\n| Advanced | 345 GB | Keeps serving; the extra is billed |\n| Agency | 1,540 GB | Keeps serving; the extra is billed |\n| Enterprise | 3,080 GB by default; more by agreement | Keeps serving; not billed — your agreement sets the terms |\nThe allowance is per organization, across every site in it — not per site. If you run four sites on one Business plan, they share the 185 GB.'
8251
+ text: '| Plan | Included bandwidth per month | Past the allowance |\n| Free | 2 GB | Sites are paused until the start of next month |\n| Starter | 20 GB | Keeps serving; the extra is billed |\n| Pro | 35 GB | Keeps serving; the extra is billed |\n| Business | 55 GB | Keeps serving; the extra is billed |\n| Scale | 90 GB | Keeps serving; the extra is billed |\n| Advanced | 105 GB | Keeps serving; the extra is billed |\n| Agency | 485 GB | Keeps serving; the extra is billed |\n| Enterprise | 970 GB by default; more by agreement | Keeps serving; not billed — your agreement sets the terms |\nThe allowance is per organization, across every site in it — not per site. If you run four sites on one Business plan, they share the 55 GB.'
8217
8252
  },
8218
8253
  {
8219
8254
  path: '/workspace-and-billing/billing-and-plans/bandwidth',
@@ -8248,7 +8283,7 @@ export const DOCS_SECTION_INDEX = [
8248
8283
  title: 'Bandwidth',
8249
8284
  heading: 'Reference',
8250
8285
  anchor: '#reference',
8251
- text: 'For developers and operators. None of this is needed to use the feature.\nHow usage is counted\nBandwidth is derived from page views rather than measured byte-for-byte at the edge. The platform uses a fixed accounting figure of 1,012.8 KB per page view and converts in both directions, so the meter you read in GB and the counters the analytics pipeline writes are the same number expressed differently. On Free, 2 GB works out to roughly 2,070 page views a month; on Starter, 50 GB is roughly 51,800, and the same division gives every other band.\nWhich views are counted\nA view is counted when a published page reports one from a visitor\'s browser. Two kinds of traffic are deliberately not counted, so they neither bill nor consume your band:\n- Views served by a preview or development build. Only a real production deployment reports. A local development server and a preview URL serve your pages without moving your meter. (A self-hosted deployment at a real domain is a production deployment and counts normally; one served over localhost does not.)\n- Your own browser, once you tell the platform it is yours. Visit any page of your site with ?aglyninternal=1 on the URL and that browser stops being counted on that site — useful while you are checking your own pages. The visit carrying the parameter is not counted either. The setting lives in that browser and lasts until you clear its storage or visit with ?aglyninternal=0, and it is per site address, so set it once per domain you want to exclude.\nEditing your site does not count either: the design canvas and the console\'s preview never report a view, so building a page is free however long you spend on it.\nThat 1,012.8 KB is a billing convention, not a measurement of your pages. It is set from a real cold load of one of our own publis'
8286
+ text: 'For developers and operators. None of this is needed to use the feature.\nHow usage is counted\nBandwidth is derived from page views rather than measured byte-for-byte at the edge. The platform uses a fixed accounting figure of 1,012.8 KB per page view and converts in both directions, so the meter you read in GB and the counters the analytics pipeline writes are the same number expressed differently. On Free, 2 GB works out to roughly 2,070 page views a month; on Starter, 20 GB is roughly 20,700, and the same division gives every other band.\nWhich views are counted\nA view is counted when a published page reports one from a visitor\'s browser. Two kinds of traffic are deliberately not counted, so they neither bill nor consume your band:\n- Views served by a preview or development build. Only a real production deployment reports. A local development server and a preview URL serve your pages without moving your meter. (A self-hosted deployment at a real domain is a production deployment and counts normally; one served over localhost does not.)\n- Your own browser, once you tell the platform it is yours. Visit any page of your site with ?aglyninternal=1 on the URL and that browser stops being counted on that site — useful while you are checking your own pages. The visit carrying the parameter is not counted either. The setting lives in that browser and lasts until you clear its storage or visit with ?aglyninternal=0, and it is per site address, so set it once per domain you want to exclude.\nEditing your site does not count either: the design canvas and the console\'s preview never report a view, so building a page is free however long you spend on it.\nThat 1,012.8 KB is a billing convention, not a measurement of your pages. It is set from a real cold load of one of our own publis'
8252
8287
  },
8253
8288
  {
8254
8289
  path: '/workspace-and-billing/billing-and-plans/bandwidth',
@@ -8318,7 +8353,7 @@ export const DOCS_SECTION_INDEX = [
8318
8353
  title: 'Billing & Plans',
8319
8354
  heading: 'Tiers & entitlements',
8320
8355
  anchor: '#tiers--entitlements',
8321
- text: 'Prices live on one page Current prices — monthly, annual, transaction fees and per-unit overage rates — are on aglyn.com/pricing, and your own plan and next invoice are in Billing in the console. This page explains how billing WORKS; it deliberately does not restate the numbers, because a second copy is a copy that goes stale.\n| Plan | Commerce |\n| Free | Build & publish only — no selling |\n| Starter | Sell products, with the highest transaction fee of the paid tiers |\n| Pro | More products, a lower fee, POS, abandoned-cart recovery, reviews, dropshipping |\n| Business | More products again, a lower fee, subscriptions & paywalls, gift cards |\n| Scale | Higher product and site limits, lower fee again |\n| Advanced | Unlimited products, no transaction fee, high-volume commerce & API |\n| Agency | Many sites under one organization, white-label |\n| Enterprise | Twice Agency\'s capacity by default and more by agreement, SSO, white-label, no transaction fee |\nTransaction fees are Aglyn platform fees on the sales you take through your site — storefront orders, paid memberships and paid bookings alike — separate from Stripe\'s payment-processing fees. Upgrading is the way to reduce them.\nEvery plan also includes an amount of monthly traffic — 2 GB on Free, rising to 3,080 GB on Enterprise by default (an agreement can set more). Passing it is metered and billed on a paid plan, and pauses the site until the start of next month on Free. See Bandwidth for the table and for what a paused site shows a visitor.\nEnterprise\nEnterprise is the one tier you cannot buy from the Billing page. It has no list price — the plan, term, and invoicing are agreed with us, and we provision the organization directly. On top of everything in Agency it doubles every quota Agency bounds — and your agreement c'
8356
+ text: 'Prices live on one page Current prices — monthly, annual, transaction fees and per-unit overage rates — are on aglyn.com/pricing, and your own plan and next invoice are in Billing in the console. This page explains how billing WORKS; it deliberately does not restate the numbers, because a second copy is a copy that goes stale.\n| Plan | Commerce |\n| Free | Build & publish only — no selling |\n| Starter | Sell products, with the highest transaction fee of the paid tiers |\n| Pro | More products, a lower fee, POS, abandoned-cart recovery, reviews, dropshipping |\n| Business | More products again, a lower fee, subscriptions & paywalls, gift cards |\n| Scale | Higher product and site limits, lower fee again |\n| Advanced | Unlimited products, no transaction fee, high-volume commerce & API |\n| Agency | Many sites under one organization, white-label |\n| Enterprise | Twice Agency\'s capacity by default and more by agreement, SSO, white-label, no transaction fee |\nTransaction fees are Aglyn platform fees on the sales you take through your site — storefront orders, paid memberships and paid bookings alike — separate from Stripe\'s payment-processing fees. Upgrading is the way to reduce them.\nEvery plan also includes an amount of monthly traffic — 2 GB on Free, rising to 970 GB on Enterprise by default (an agreement can set more). Passing it is metered and billed on a paid plan, and pauses the site until the start of next month on Free. See Bandwidth for the table and for what a paused site shows a visitor.\nLinks on a new Free site {#leaving-notice}\nFor a Free workspace\'s first 14 days, its sites send links and redirects to other websites through a short "You\'re leaving" page on the site\'s own address first. The page shows the full address the link goes to, says Aglyn doesn\'t operate tha'
8322
8357
  },
8323
8358
  {
8324
8359
  path: '/workspace-and-billing/billing-and-plans/overview',