@aglyn/plugins-email 1.0.0-beta.143

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 (144) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +58 -0
  4. package/src/index.d.ts +35 -0
  5. package/src/index.js +35 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/campaign-design-create-widget.d.ts +21 -0
  8. package/src/lib/components/campaign-design-create-widget.js +61 -0
  9. package/src/lib/components/campaign-design-create-widget.js.map +1 -0
  10. package/src/lib/components/campaign-sender-editor-widget.d.ts +18 -0
  11. package/src/lib/components/campaign-sender-editor-widget.js +39 -0
  12. package/src/lib/components/campaign-sender-editor-widget.js.map +1 -0
  13. package/src/lib/components/campaign-topic-options-widget.d.ts +19 -0
  14. package/src/lib/components/campaign-topic-options-widget.js +48 -0
  15. package/src/lib/components/campaign-topic-options-widget.js.map +1 -0
  16. package/src/lib/components/campaign-topic-select.d.ts +32 -0
  17. package/src/lib/components/campaign-topic-select.js +87 -0
  18. package/src/lib/components/campaign-topic-select.js.map +1 -0
  19. package/src/lib/components/dynamic-list-rule-fields.d.ts +173 -0
  20. package/src/lib/components/dynamic-list-rule-fields.js +1473 -0
  21. package/src/lib/components/dynamic-list-rule-fields.js.map +1 -0
  22. package/src/lib/components/email-blocks.d.ts +111 -0
  23. package/src/lib/components/email-blocks.js +875 -0
  24. package/src/lib/components/email-blocks.js.map +1 -0
  25. package/src/lib/components/email-design-preview.d.ts +62 -0
  26. package/src/lib/components/email-design-preview.js +174 -0
  27. package/src/lib/components/email-design-preview.js.map +1 -0
  28. package/src/lib/components/email-screens-card.d.ts +42 -0
  29. package/src/lib/components/email-screens-card.js +277 -0
  30. package/src/lib/components/email-screens-card.js.map +1 -0
  31. package/src/lib/components/email-template-detail.d.ts +48 -0
  32. package/src/lib/components/email-template-detail.js +681 -0
  33. package/src/lib/components/email-template-detail.js.map +1 -0
  34. package/src/lib/components/email-topic-detail.d.ts +32 -0
  35. package/src/lib/components/email-topic-detail.js +293 -0
  36. package/src/lib/components/email-topic-detail.js.map +1 -0
  37. package/src/lib/components/email-topics-card.d.ts +46 -0
  38. package/src/lib/components/email-topics-card.js +327 -0
  39. package/src/lib/components/email-topics-card.js.map +1 -0
  40. package/src/lib/components/email-zones.d.ts +28 -0
  41. package/src/lib/components/email-zones.js +20 -0
  42. package/src/lib/components/email-zones.js.map +1 -0
  43. package/src/lib/components/emails-console-page.d.ts +32 -0
  44. package/src/lib/components/emails-console-page.js +229 -0
  45. package/src/lib/components/emails-console-page.js.map +1 -0
  46. package/src/lib/components/emails-console-sections.d.ts +36 -0
  47. package/src/lib/components/emails-console-sections.js +108 -0
  48. package/src/lib/components/emails-console-sections.js.map +1 -0
  49. package/src/lib/components/list-detail-card.d.ts +47 -0
  50. package/src/lib/components/list-detail-card.js +273 -0
  51. package/src/lib/components/list-detail-card.js.map +1 -0
  52. package/src/lib/components/list-edit-card.d.ts +11 -0
  53. package/src/lib/components/list-edit-card.js +287 -0
  54. package/src/lib/components/list-edit-card.js.map +1 -0
  55. package/src/lib/components/list-import-drawer.d.ts +22 -0
  56. package/src/lib/components/list-import-drawer.js +662 -0
  57. package/src/lib/components/list-import-drawer.js.map +1 -0
  58. package/src/lib/components/list-members-panel.d.ts +94 -0
  59. package/src/lib/components/list-members-panel.js +686 -0
  60. package/src/lib/components/list-members-panel.js.map +1 -0
  61. package/src/lib/components/lists-card.d.ts +28 -0
  62. package/src/lib/components/lists-card.js +377 -0
  63. package/src/lib/components/lists-card.js.map +1 -0
  64. package/src/lib/components/sending-domain-detail.d.ts +26 -0
  65. package/src/lib/components/sending-domain-detail.js +496 -0
  66. package/src/lib/components/sending-domain-detail.js.map +1 -0
  67. package/src/lib/components/sending-domains-card.d.ts +33 -0
  68. package/src/lib/components/sending-domains-card.js +962 -0
  69. package/src/lib/components/sending-domains-card.js.map +1 -0
  70. package/src/lib/components/sending-sender-drawer.d.ts +94 -0
  71. package/src/lib/components/sending-sender-drawer.js +543 -0
  72. package/src/lib/components/sending-sender-drawer.js.map +1 -0
  73. package/src/lib/components/suppressions-card.d.ts +49 -0
  74. package/src/lib/components/suppressions-card.js +639 -0
  75. package/src/lib/components/suppressions-card.js.map +1 -0
  76. package/src/lib/components/use-org-email-topics.d.ts +79 -0
  77. package/src/lib/components/use-org-email-topics.js +111 -0
  78. package/src/lib/components/use-org-email-topics.js.map +1 -0
  79. package/src/lib/constants/bundle-common.d.ts +18 -0
  80. package/src/lib/constants/bundle-common.js +18 -0
  81. package/src/lib/constants/bundle-common.js.map +1 -0
  82. package/src/lib/hooks/use-org-company-options.d.ts +20 -0
  83. package/src/lib/hooks/use-org-company-options.js +138 -0
  84. package/src/lib/hooks/use-org-company-options.js.map +1 -0
  85. package/src/lib/hooks/use-org-contact-fields.d.ts +40 -0
  86. package/src/lib/hooks/use-org-contact-fields.js +91 -0
  87. package/src/lib/hooks/use-org-contact-fields.js.map +1 -0
  88. package/src/lib/hooks/use-org-contact-segments.d.ts +16 -0
  89. package/src/lib/hooks/use-org-contact-segments.js +55 -0
  90. package/src/lib/hooks/use-org-contact-segments.js.map +1 -0
  91. package/src/lib/hooks/use-org-crm-views.d.ts +8 -0
  92. package/src/lib/hooks/use-org-crm-views.js +74 -0
  93. package/src/lib/hooks/use-org-crm-views.js.map +1 -0
  94. package/src/lib/hooks/use-org-lists.d.ts +8 -0
  95. package/src/lib/hooks/use-org-lists.js +47 -0
  96. package/src/lib/hooks/use-org-lists.js.map +1 -0
  97. package/src/lib/model/email-design-document.d.ts +52 -0
  98. package/src/lib/model/email-design-document.js +62 -0
  99. package/src/lib/model/email-design-document.js.map +1 -0
  100. package/src/lib/model/index.d.ts +64 -0
  101. package/src/lib/model/index.js +71 -0
  102. package/src/lib/model/index.js.map +1 -0
  103. package/src/lib/model/sending-domain-status.d.ts +99 -0
  104. package/src/lib/model/sending-domain-status.js +196 -0
  105. package/src/lib/model/sending-domain-status.js.map +1 -0
  106. package/src/lib/model/template-provenance.d.ts +113 -0
  107. package/src/lib/model/template-provenance.js +107 -0
  108. package/src/lib/model/template-provenance.js.map +1 -0
  109. package/src/lib/model/template-report.d.ts +158 -0
  110. package/src/lib/model/template-report.js +249 -0
  111. package/src/lib/model/template-report.js.map +1 -0
  112. package/src/lib/plugin.d.ts +27 -0
  113. package/src/lib/plugin.js +163 -0
  114. package/src/lib/plugin.js.map +1 -0
  115. package/src/lib/server-console.d.ts +116 -0
  116. package/src/lib/server-console.js +422 -0
  117. package/src/lib/server-console.js.map +1 -0
  118. package/src/lib/server-email-drafts.d.ts +104 -0
  119. package/src/lib/server-email-drafts.js +381 -0
  120. package/src/lib/server-email-drafts.js.map +1 -0
  121. package/src/lib/server-list-gate.d.ts +183 -0
  122. package/src/lib/server-list-gate.js +365 -0
  123. package/src/lib/server-list-gate.js.map +1 -0
  124. package/src/lib/server-list-import.d.ts +199 -0
  125. package/src/lib/server-list-import.js +632 -0
  126. package/src/lib/server-list-import.js.map +1 -0
  127. package/src/lib/server-suppressions.d.ts +135 -0
  128. package/src/lib/server-suppressions.js +295 -0
  129. package/src/lib/server-suppressions.js.map +1 -0
  130. package/src/lib/server.d.ts +19 -0
  131. package/src/lib/server.js +834 -0
  132. package/src/lib/server.js.map +1 -0
  133. package/src/lib/site.d.ts +26 -0
  134. package/src/lib/site.js +81 -0
  135. package/src/lib/site.js.map +1 -0
  136. package/src/lib/unsubscribe-link.d.ts +311 -0
  137. package/src/lib/unsubscribe-link.js +398 -0
  138. package/src/lib/unsubscribe-link.js.map +1 -0
  139. package/src/lib/utils/create-email-screen.d.ts +59 -0
  140. package/src/lib/utils/create-email-screen.js +59 -0
  141. package/src/lib/utils/create-email-screen.js.map +1 -0
  142. package/src/lib/utils/generate-preset-id.d.ts +19 -0
  143. package/src/lib/utils/generate-preset-id.js +25 -0
  144. package/src/lib/utils/generate-preset-id.js.map +1 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ /**
19
+ * The org's custom contact field DEFINITIONS, for the rule editor to offer
20
+ * (AGL-2603).
21
+ *
22
+ * A twin of `use-org-lists.ts`: a LOOKUP that fills one control's options,
23
+ * bounded so an org cannot turn the dropdown into an unbounded read. The
24
+ * definitions live in `orgs/{orgId}/contactFields` and the values a rule
25
+ * compares against live under each contact facet's `custom`, keyed by the
26
+ * definition's `key` — so the picker offers keys, and the clause it authors
27
+ * names one.
28
+ *
29
+ * ## Scoped like the contacts themselves
30
+ *
31
+ * The rules gate the collection on the contacts predicate, per document, so
32
+ * a collaborator scoped to one site reading it unfiltered is refused rather
33
+ * than shown less. The viewer's scope tokens narrow the query the way every
34
+ * CRM read is narrowed; an org-wide member reads without a filter, which is
35
+ * what keeps a definition the backfill never stamped from vanishing for them.
36
+ *
37
+ * ## A retired field is offered nowhere new
38
+ *
39
+ * `retiredAt` is a retirement, not a deletion: values written under the key
40
+ * survive and an export still reads them back. A rule written before the
41
+ * retirement keeps its clause — the editor shows the key it names — but the
42
+ * picker stops offering the field, because a merchant building a new
43
+ * audience on a field their team has retired is building it on a value
44
+ * nobody is maintaining.
45
+ */ import { CRM_COLLECTIONS } from "@aglyn/aglyn";
46
+ import { collection, limit, query, where } from "firebase/firestore";
47
+ import { useMemo } from "react";
48
+ import { useFirestore, useFirestoreCollection, useScopeTokens } from "@aglyn/tenant-feature-instance";
49
+ /** The picker's window. Matches the list and segment pickers beside it. */ export const CONTACT_FIELD_PICKER_LIMIT = 50;
50
+ export function useOrgContactFields(scope) {
51
+ const firestore = useFirestore();
52
+ const { tokens, orgWide, loaded } = useScopeTokens(scope[1]);
53
+ const { data, status } = useFirestoreCollection(()=>// Not until the viewer's reach is known: `useScopeTokens` reports
54
+ // org-wide while loading, and a scoped member's first render would
55
+ // send an unfiltered query the rules deny per document.
56
+ loaded ? query(collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.contactFields), ...orgWide ? [] : [
57
+ where('visibleTo', 'array-contains-any', tokens)
58
+ ], limit(CONTACT_FIELD_PICKER_LIMIT)) : null, [
59
+ firestore,
60
+ scope[0],
61
+ scope[1],
62
+ loaded,
63
+ orgWide,
64
+ tokens
65
+ ], {
66
+ idField: '$id'
67
+ });
68
+ return useMemo(()=>({
69
+ fields: (data != null ? data : []).filter((field)=>!field.retiredAt && typeof field.key === 'string').sort((a, b)=>{
70
+ var _a_order, _b_order;
71
+ return ((_a_order = a.order) != null ? _a_order : 0) - ((_b_order = b.order) != null ? _b_order : 0);
72
+ }).map(({ $id, key, label, type, options, order })=>_extends({
73
+ $id,
74
+ key,
75
+ label: label || key,
76
+ type
77
+ }, options ? {
78
+ options
79
+ } : {}, {
80
+ order: order != null ? order : 0
81
+ })),
82
+ ready: loaded && status !== 'loading'
83
+ }), [
84
+ data,
85
+ loaded,
86
+ status
87
+ ]);
88
+ }
89
+ export default useOrgContactFields;
90
+
91
+ //# sourceMappingURL=use-org-contact-fields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/hooks/use-org-contact-fields.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The org's custom contact field DEFINITIONS, for the rule editor to offer\n * (AGL-2603).\n *\n * A twin of `use-org-lists.ts`: a LOOKUP that fills one control's options,\n * bounded so an org cannot turn the dropdown into an unbounded read. The\n * definitions live in `orgs/{orgId}/contactFields` and the values a rule\n * compares against live under each contact facet's `custom`, keyed by the\n * definition's `key` — so the picker offers keys, and the clause it authors\n * names one.\n *\n * ## Scoped like the contacts themselves\n *\n * The rules gate the collection on the contacts predicate, per document, so\n * a collaborator scoped to one site reading it unfiltered is refused rather\n * than shown less. The viewer's scope tokens narrow the query the way every\n * CRM read is narrowed; an org-wide member reads without a filter, which is\n * what keeps a definition the backfill never stamped from vanishing for them.\n *\n * ## A retired field is offered nowhere new\n *\n * `retiredAt` is a retirement, not a deletion: values written under the key\n * survive and an export still reads them back. A rule written before the\n * retirement keeps its clause — the editor shows the key it names — but the\n * picker stops offering the field, because a merchant building a new\n * audience on a field their team has retired is building it on a value\n * nobody is maintaining.\n */\n\nimport { CRM_COLLECTIONS, type ContactFieldDefinition } from '@aglyn/aglyn'\nimport { collection, limit, query, where } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useScopeTokens,\n} from '@aglyn/tenant-feature-instance'\n\n/** One definition, as the picker and the value control need it. */\nexport type OrgContactFieldOption = Pick<\n ContactFieldDefinition,\n 'key' | 'label' | 'type' | 'options' | 'order'\n> & { $id: string }\n\n/** The picker's window. Matches the list and segment pickers beside it. */\nexport const CONTACT_FIELD_PICKER_LIMIT = 50\n\nexport function useOrgContactFields(\n scope: readonly [string, string],\n): { fields: OrgContactFieldOption[]; ready: boolean } {\n const firestore = useFirestore()\n const { tokens, orgWide, loaded } = useScopeTokens(scope[1])\n const { data, status } = useFirestoreCollection<\n OrgContactFieldOption & { retiredAt?: number | null }\n >(\n () =>\n // Not until the viewer's reach is known: `useScopeTokens` reports\n // org-wide while loading, and a scoped member's first render would\n // send an unfiltered query the rules deny per document.\n loaded\n ? query(\n collection(\n firestore,\n scope[0],\n scope[1],\n CRM_COLLECTIONS.contactFields,\n ),\n ...(orgWide ? [] : [where('visibleTo', 'array-contains-any', tokens)]),\n limit(CONTACT_FIELD_PICKER_LIMIT),\n )\n : null,\n [firestore, scope[0], scope[1], loaded, orgWide, tokens],\n { idField: '$id' },\n )\n return useMemo(\n () => ({\n fields: (data ?? [])\n .filter((field) => !field.retiredAt && typeof field.key === 'string')\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))\n .map(({ $id, key, label, type, options, order }) => ({\n $id,\n key,\n label: label || key,\n type,\n ...(options ? { options } : {}),\n order: order ?? 0,\n })),\n ready: loaded && status !== 'loading',\n }),\n [data, loaded, status],\n )\n}\n\nexport default useOrgContactFields\n"],"names":["CRM_COLLECTIONS","collection","limit","query","where","useMemo","useFirestore","useFirestoreCollection","useScopeTokens","CONTACT_FIELD_PICKER_LIMIT","useOrgContactFields","scope","firestore","tokens","orgWide","loaded","data","status","contactFields","idField","fields","filter","field","retiredAt","key","sort","a","b","order","map","$id","label","type","options","ready"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GAED,SAASA,eAAe,QAAqC,eAAc;AAC3E,SAASC,UAAU,EAAEC,KAAK,EAAEC,KAAK,EAAEC,KAAK,QAAQ,qBAAoB;AACpE,SAASC,OAAO,QAAQ,QAAO;AAC/B,SACEC,YAAY,EACZC,sBAAsB,EACtBC,cAAc,QACT,iCAAgC;AAQvC,yEAAyE,GACzE,OAAO,MAAMC,6BAA6B,GAAE;AAE5C,OAAO,SAASC,oBACdC,KAAgC;IAEhC,MAAMC,YAAYN;IAClB,MAAM,EAAEO,MAAM,EAAEC,OAAO,EAAEC,MAAM,EAAE,GAAGP,eAAeG,KAAK,CAAC,EAAE;IAC3D,MAAM,EAAEK,IAAI,EAAEC,MAAM,EAAE,GAAGV,uBAGvB,IACE,kEAAkE;QAClE,mEAAmE;QACnE,wDAAwD;QACxDQ,SACIZ,MACEF,WACEW,WACAD,KAAK,CAAC,EAAE,EACRA,KAAK,CAAC,EAAE,EACRX,gBAAgBkB,aAAa,MAE3BJ,UAAU,EAAE,GAAG;YAACV,MAAM,aAAa,sBAAsBS;SAAQ,EACrEX,MAAMO,+BAER,MACN;QAACG;QAAWD,KAAK,CAAC,EAAE;QAAEA,KAAK,CAAC,EAAE;QAAEI;QAAQD;QAASD;KAAO,EACxD;QAAEM,SAAS;IAAM;IAEnB,OAAOd,QACL,IAAO,CAAA;YACLe,QAAQ,CAACJ,eAAAA,OAAQ,EAAE,EAChBK,MAAM,CAAC,CAACC,QAAU,CAACA,MAAMC,SAAS,IAAI,OAAOD,MAAME,GAAG,KAAK,UAC3DC,IAAI,CAAC,CAACC,GAAGC;oBAAOD,UAAiBC;uBAAlB,EAACD,WAAAA,EAAEE,KAAK,YAAPF,WAAW,OAAMC,WAAAA,EAAEC,KAAK,YAAPD,WAAW;eAC5CE,GAAG,CAAC,CAAC,EAAEC,GAAG,EAAEN,GAAG,EAAEO,KAAK,EAAEC,IAAI,EAAEC,OAAO,EAAEL,KAAK,EAAE,GAAM;oBACnDE;oBACAN;oBACAO,OAAOA,SAASP;oBAChBQ;mBACIC,UAAU;oBAAEA;gBAAQ,IAAI,CAAC;oBAC7BL,KAAK,EAAEA,gBAAAA,QAAS;;YAEpBM,OAAOnB,UAAUE,WAAW;QAC9B,CAAA,GACA;QAACD;QAAMD;QAAQE;KAAO;AAE1B;AAEA,eAAeP,oBAAmB"}
@@ -0,0 +1,16 @@
1
+ /** One saved segment, as the picker needs it. */
2
+ export interface OrgContactSegment {
3
+ $id: string;
4
+ name?: string;
5
+ }
6
+ /**
7
+ * The picker's window.
8
+ *
9
+ * Bounded rather than paged — see the module note. A rule that names a segment
10
+ * beyond this window still shows its own reference: the picker adds the stored
11
+ * id as an option of its own, because a `Select` whose value is absent from
12
+ * its options renders empty, and saving from that screen would erase the
13
+ * reference the merchant could not see.
14
+ */
15
+ export declare const SEGMENT_PICKER_LIMIT = 50;
16
+ export declare function useOrgContactSegments(scope: readonly [string, string]): OrgContactSegment[];
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ /**
18
+ * The org's saved contact segments, for a picker to offer.
19
+ *
20
+ * A LOOKUP, not a list. Nothing pages this: it fills the options of one
21
+ * `Select`, a reader opens it rather than walking it, and the window is
22
+ * bounded so an org with a thousand segments cannot turn one dropdown into an
23
+ * unbounded read. That is a different question from the one a paged table
24
+ * answers, and keeping the read in its own module is what stops the form that
25
+ * renders the dropdown reading like a list surface that forgot its footer.
26
+ *
27
+ * `orderBy('name')` is safe here, which is a claim about the writer rather
28
+ * than a preference: `handleSaveSegment` in the contacts console is the only
29
+ * thing that creates a segment and it refuses an empty name. `orderBy` filters
30
+ * as well as sorts, so a segment written without one would be missing from its
31
+ * own picker rather than merely out of order.
32
+ */ import { collection, limit, orderBy, query } from "firebase/firestore";
33
+ import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
34
+ /**
35
+ * The picker's window.
36
+ *
37
+ * Bounded rather than paged — see the module note. A rule that names a segment
38
+ * beyond this window still shows its own reference: the picker adds the stored
39
+ * id as an option of its own, because a `Select` whose value is absent from
40
+ * its options renders empty, and saving from that screen would erase the
41
+ * reference the merchant could not see.
42
+ */ export const SEGMENT_PICKER_LIMIT = 50;
43
+ export function useOrgContactSegments(scope) {
44
+ const firestore = useFirestore();
45
+ const { data } = useFirestoreCollection(()=>query(collection(firestore, scope[0], scope[1], 'contactSegments'), orderBy('name'), limit(SEGMENT_PICKER_LIMIT)), [
46
+ firestore,
47
+ scope[0],
48
+ scope[1]
49
+ ], {
50
+ idField: '$id'
51
+ });
52
+ return data != null ? data : [];
53
+ }
54
+
55
+ //# sourceMappingURL=use-org-contact-segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/hooks/use-org-contact-segments.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The org's saved contact segments, for a picker to offer.\n *\n * A LOOKUP, not a list. Nothing pages this: it fills the options of one\n * `Select`, a reader opens it rather than walking it, and the window is\n * bounded so an org with a thousand segments cannot turn one dropdown into an\n * unbounded read. That is a different question from the one a paged table\n * answers, and keeping the read in its own module is what stops the form that\n * renders the dropdown reading like a list surface that forgot its footer.\n *\n * `orderBy('name')` is safe here, which is a claim about the writer rather\n * than a preference: `handleSaveSegment` in the contacts console is the only\n * thing that creates a segment and it refuses an empty name. `orderBy` filters\n * as well as sorts, so a segment written without one would be missing from its\n * own picker rather than merely out of order.\n */\n\nimport { collection, limit, orderBy, query } from 'firebase/firestore'\nimport { useFirestore, useFirestoreCollection } from '@aglyn/tenant-feature-instance'\n\n/** One saved segment, as the picker needs it. */\nexport interface OrgContactSegment {\n $id: string\n name?: string\n}\n\n/**\n * The picker's window.\n *\n * Bounded rather than paged — see the module note. A rule that names a segment\n * beyond this window still shows its own reference: the picker adds the stored\n * id as an option of its own, because a `Select` whose value is absent from\n * its options renders empty, and saving from that screen would erase the\n * reference the merchant could not see.\n */\nexport const SEGMENT_PICKER_LIMIT = 50\n\nexport function useOrgContactSegments(\n scope: readonly [string, string],\n): OrgContactSegment[] {\n const firestore = useFirestore()\n const { data } = useFirestoreCollection<OrgContactSegment>(\n () =>\n query(\n collection(firestore, scope[0], scope[1], 'contactSegments'),\n orderBy('name'),\n limit(SEGMENT_PICKER_LIMIT),\n ),\n [firestore, scope[0], scope[1]],\n { idField: '$id' },\n )\n return data ?? []\n}\n"],"names":["collection","limit","orderBy","query","useFirestore","useFirestoreCollection","SEGMENT_PICKER_LIMIT","useOrgContactSegments","scope","firestore","data","idField"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,QAAQ,qBAAoB;AACtE,SAASC,YAAY,EAAEC,sBAAsB,QAAQ,iCAAgC;AAQrF;;;;;;;;CAQC,GACD,OAAO,MAAMC,uBAAuB,GAAE;AAEtC,OAAO,SAASC,sBACdC,KAAgC;IAEhC,MAAMC,YAAYL;IAClB,MAAM,EAAEM,IAAI,EAAE,GAAGL,uBACf,IACEF,MACEH,WAAWS,WAAWD,KAAK,CAAC,EAAE,EAAEA,KAAK,CAAC,EAAE,EAAE,oBAC1CN,QAAQ,SACRD,MAAMK,wBAEV;QAACG;QAAWD,KAAK,CAAC,EAAE;QAAEA,KAAK,CAAC,EAAE;KAAC,EAC/B;QAAEG,SAAS;IAAM;IAEnB,OAAOD,eAAAA,OAAQ,EAAE;AACnB"}
@@ -0,0 +1,8 @@
1
+ /** One saved view, as the picker needs it. */
2
+ export interface OrgCrmViewOption {
3
+ $id: string;
4
+ name: string;
5
+ }
6
+ export declare const CRM_VIEW_PICKER_LIMIT = 50;
7
+ export declare function useOrgCrmViews(scope: readonly [string, string]): OrgCrmViewOption[];
8
+ export default useOrgCrmViews;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ /**
18
+ * The org's saved Contacts views that can be an audience, for the rule
19
+ * editor's picker (AGL-2617).
20
+ *
21
+ * A LOOKUP like the segment picker's, and bounded for the same reason. Two
22
+ * things narrow it past the segments'. It lists only the views this reader
23
+ * would see on the Contacts list — their own and the shared ones — because
24
+ * a private view is one person's working arrangement and offering it as a
25
+ * colleague's audience would put it to a use its owner never saw. And it
26
+ * lists only views that TRANSLATE WHOLE into the rule language: a view
27
+ * carrying a name prefix or an `updatedAt` window has no audience meaning,
28
+ * and the sweep would refuse it by narrowing to nobody — so the picker
29
+ * does not offer what the sweep cannot honor.
30
+ *
31
+ * Scoped like the field picker: the reader's own tokens filter the query
32
+ * for a scoped member, and an org-wide member reads the section unfiltered
33
+ * on the `(section, name)` index.
34
+ */ import { CRM_COLLECTIONS, crmViewIsListed, dynamicListDimensionsForCrmView, normalizeCrmViewFilters } from "@aglyn/aglyn";
35
+ import { collection, limit, orderBy, query, where } from "firebase/firestore";
36
+ import { useMemo } from "react";
37
+ import { useFirestore, useFirestoreCollection, useScopeTokens, useUser } from "@aglyn/tenant-feature-instance";
38
+ export const CRM_VIEW_PICKER_LIMIT = 50;
39
+ export function useOrgCrmViews(scope) {
40
+ var _ref;
41
+ const firestore = useFirestore();
42
+ const { data: user } = useUser();
43
+ const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : null;
44
+ const { tokens, orgWide, loaded } = useScopeTokens(scope[1]);
45
+ const { data } = useFirestoreCollection(()=>// Not until the viewer's reach is known — see the field picker.
46
+ loaded ? query(collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.views), ...orgWide ? [] : [
47
+ where('visibleTo', 'array-contains-any', tokens)
48
+ ], where('section', '==', 'contacts'), orderBy('name'), limit(CRM_VIEW_PICKER_LIMIT)) : null, [
49
+ firestore,
50
+ scope[0],
51
+ scope[1],
52
+ loaded,
53
+ orgWide,
54
+ tokens
55
+ ], {
56
+ idField: '$id'
57
+ });
58
+ return useMemo(()=>(data != null ? data : []).filter((row)=>{
59
+ var _row_ownerUid;
60
+ return typeof row['name'] === 'string' && row['name'] && crmViewIsListed({
61
+ shared: row['shared'] === true,
62
+ ownerUid: String((_row_ownerUid = row['ownerUid']) != null ? _row_ownerUid : '')
63
+ }, uid) && dynamicListDimensionsForCrmView(normalizeCrmViewFilters(row['filters'])).unsupported.length === 0;
64
+ }).map((row)=>({
65
+ $id: String(row['$id']),
66
+ name: String(row['name'])
67
+ })), [
68
+ data,
69
+ uid
70
+ ]);
71
+ }
72
+ export default useOrgCrmViews;
73
+
74
+ //# sourceMappingURL=use-org-crm-views.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/hooks/use-org-crm-views.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The org's saved Contacts views that can be an audience, for the rule\n * editor's picker (AGL-2617).\n *\n * A LOOKUP like the segment picker's, and bounded for the same reason. Two\n * things narrow it past the segments'. It lists only the views this reader\n * would see on the Contacts list — their own and the shared ones — because\n * a private view is one person's working arrangement and offering it as a\n * colleague's audience would put it to a use its owner never saw. And it\n * lists only views that TRANSLATE WHOLE into the rule language: a view\n * carrying a name prefix or an `updatedAt` window has no audience meaning,\n * and the sweep would refuse it by narrowing to nobody — so the picker\n * does not offer what the sweep cannot honor.\n *\n * Scoped like the field picker: the reader's own tokens filter the query\n * for a scoped member, and an org-wide member reads the section unfiltered\n * on the `(section, name)` index.\n */\n\nimport {\n CRM_COLLECTIONS,\n crmViewIsListed,\n dynamicListDimensionsForCrmView,\n normalizeCrmViewFilters,\n} from '@aglyn/aglyn'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useScopeTokens,\n useUser,\n} from '@aglyn/tenant-feature-instance'\n\n/** One saved view, as the picker needs it. */\nexport interface OrgCrmViewOption {\n $id: string\n name: string\n}\n\nexport const CRM_VIEW_PICKER_LIMIT = 50\n\nexport function useOrgCrmViews(\n scope: readonly [string, string],\n): OrgCrmViewOption[] {\n const firestore = useFirestore()\n const { data: user } = useUser()\n const uid = (user as { uid?: string } | null | undefined)?.uid ?? null\n const { tokens, orgWide, loaded } = useScopeTokens(scope[1])\n const { data } = useFirestoreCollection<Record<string, unknown>>(\n () =>\n // Not until the viewer's reach is known — see the field picker.\n loaded\n ? query(\n collection(firestore, scope[0], scope[1], CRM_COLLECTIONS.views),\n ...(orgWide ? [] : [where('visibleTo', 'array-contains-any', tokens)]),\n where('section', '==', 'contacts'),\n orderBy('name'),\n limit(CRM_VIEW_PICKER_LIMIT),\n )\n : null,\n [firestore, scope[0], scope[1], loaded, orgWide, tokens],\n { idField: '$id' },\n )\n return useMemo(\n () =>\n (data ?? [])\n .filter(\n (row) =>\n typeof row['name'] === 'string' &&\n row['name'] &&\n crmViewIsListed(\n {\n shared: row['shared'] === true,\n ownerUid: String(row['ownerUid'] ?? ''),\n },\n uid,\n ) &&\n dynamicListDimensionsForCrmView(normalizeCrmViewFilters(row['filters']))\n .unsupported.length === 0,\n )\n .map((row) => ({ $id: String(row['$id']), name: String(row['name']) })),\n [data, uid],\n )\n}\n\nexport default useOrgCrmViews\n"],"names":["CRM_COLLECTIONS","crmViewIsListed","dynamicListDimensionsForCrmView","normalizeCrmViewFilters","collection","limit","orderBy","query","where","useMemo","useFirestore","useFirestoreCollection","useScopeTokens","useUser","CRM_VIEW_PICKER_LIMIT","useOrgCrmViews","scope","firestore","data","user","uid","tokens","orgWide","loaded","views","idField","filter","row","shared","ownerUid","String","unsupported","length","map","$id","name"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA;;;;;;;;;;;;;;;;;CAiBC,GAED,SACEA,eAAe,EACfC,eAAe,EACfC,+BAA+B,EAC/BC,uBAAuB,QAClB,eAAc;AACrB,SAASC,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,EAAEC,KAAK,QAAQ,qBAAoB;AAC7E,SAASC,OAAO,QAAQ,QAAO;AAC/B,SACEC,YAAY,EACZC,sBAAsB,EACtBC,cAAc,EACdC,OAAO,QACF,iCAAgC;AAQvC,OAAO,MAAMC,wBAAwB,GAAE;AAEvC,OAAO,SAASC,eACdC,KAAgC;;IAEhC,MAAMC,YAAYP;IAClB,MAAM,EAAEQ,MAAMC,IAAI,EAAE,GAAGN;IACvB,MAAMO,cAAOD,wBAAD,AAACA,KAA8CC,GAAG,mBAAI;IAClE,MAAM,EAAEC,MAAM,EAAEC,OAAO,EAAEC,MAAM,EAAE,GAAGX,eAAeI,KAAK,CAAC,EAAE;IAC3D,MAAM,EAAEE,IAAI,EAAE,GAAGP,uBACf,IACE,gEAAgE;QAChEY,SACIhB,MACEH,WAAWa,WAAWD,KAAK,CAAC,EAAE,EAAEA,KAAK,CAAC,EAAE,EAAEhB,gBAAgBwB,KAAK,MAC3DF,UAAU,EAAE,GAAG;YAACd,MAAM,aAAa,sBAAsBa;SAAQ,EACrEb,MAAM,WAAW,MAAM,aACvBF,QAAQ,SACRD,MAAMS,0BAER,MACN;QAACG;QAAWD,KAAK,CAAC,EAAE;QAAEA,KAAK,CAAC,EAAE;QAAEO;QAAQD;QAASD;KAAO,EACxD;QAAEI,SAAS;IAAM;IAEnB,OAAOhB,QACL,IACE,CAACS,eAAAA,OAAQ,EAAE,EACRQ,MAAM,CACL,CAACC;gBAMsBA;mBALrB,OAAOA,GAAG,CAAC,OAAO,KAAK,YACvBA,GAAG,CAAC,OAAO,IACX1B,gBACE;gBACE2B,QAAQD,GAAG,CAAC,SAAS,KAAK;gBAC1BE,UAAUC,QAAOH,gBAAAA,GAAG,CAAC,WAAW,YAAfA,gBAAmB;YACtC,GACAP,QAEFlB,gCAAgCC,wBAAwBwB,GAAG,CAAC,UAAU,GACnEI,WAAW,CAACC,MAAM,KAAK;WAE7BC,GAAG,CAAC,CAACN,MAAS,CAAA;gBAAEO,KAAKJ,OAAOH,GAAG,CAAC,MAAM;gBAAGQ,MAAML,OAAOH,GAAG,CAAC,OAAO;YAAE,CAAA,IACxE;QAACT;QAAME;KAAI;AAEf;AAEA,eAAeL,eAAc"}
@@ -0,0 +1,8 @@
1
+ /** One audience, as the picker needs it. */
2
+ export interface OrgListOption {
3
+ $id: string;
4
+ name?: string;
5
+ }
6
+ /** The picker's window. Matches the segment picker beside it. */
7
+ export declare const LIST_PICKER_LIMIT = 50;
8
+ export declare function useOrgLists(scope: readonly [string, string]): OrgListOption[];
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ /**
18
+ * The org's audiences, as the options of a picker.
19
+ *
20
+ * The sibling of `use-org-contact-segments.ts` and deliberately its twin: a
21
+ * LOOKUP rather than a list. Nothing pages this — it fills the options of one
22
+ * `Select` that a reader opens rather than walks, and the window is bounded so
23
+ * an org with a thousand audiences cannot turn a dropdown into an unbounded
24
+ * read. The paged surface that answers the other question already exists in
25
+ * `lists-card.tsx`.
26
+ *
27
+ * `orderBy('name')` is safe here, and that is a claim about the writer rather
28
+ * than a preference: the create drawer in `lists-card.tsx` is the only thing
29
+ * that creates a list and it refuses an empty name. `orderBy` filters as well
30
+ * as sorts, so a list written without one would be missing from its own picker
31
+ * rather than merely out of order.
32
+ */ import { collection, limit, orderBy, query } from "firebase/firestore";
33
+ import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
34
+ /** The picker's window. Matches the segment picker beside it. */ export const LIST_PICKER_LIMIT = 50;
35
+ export function useOrgLists(scope) {
36
+ const firestore = useFirestore();
37
+ const { data } = useFirestoreCollection(()=>query(collection(firestore, scope[0], scope[1], 'lists'), orderBy('name'), limit(LIST_PICKER_LIMIT)), [
38
+ firestore,
39
+ scope[0],
40
+ scope[1]
41
+ ], {
42
+ idField: '$id'
43
+ });
44
+ return data != null ? data : [];
45
+ }
46
+
47
+ //# sourceMappingURL=use-org-lists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/hooks/use-org-lists.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * The org's audiences, as the options of a picker.\n *\n * The sibling of `use-org-contact-segments.ts` and deliberately its twin: a\n * LOOKUP rather than a list. Nothing pages this — it fills the options of one\n * `Select` that a reader opens rather than walks, and the window is bounded so\n * an org with a thousand audiences cannot turn a dropdown into an unbounded\n * read. The paged surface that answers the other question already exists in\n * `lists-card.tsx`.\n *\n * `orderBy('name')` is safe here, and that is a claim about the writer rather\n * than a preference: the create drawer in `lists-card.tsx` is the only thing\n * that creates a list and it refuses an empty name. `orderBy` filters as well\n * as sorts, so a list written without one would be missing from its own picker\n * rather than merely out of order.\n */\n\nimport { collection, limit, orderBy, query } from 'firebase/firestore'\nimport { useFirestore, useFirestoreCollection } from '@aglyn/tenant-feature-instance'\n\n/** One audience, as the picker needs it. */\nexport interface OrgListOption {\n $id: string\n name?: string\n}\n\n/** The picker's window. Matches the segment picker beside it. */\nexport const LIST_PICKER_LIMIT = 50\n\nexport function useOrgLists(\n scope: readonly [string, string],\n): OrgListOption[] {\n const firestore = useFirestore()\n const { data } = useFirestoreCollection<OrgListOption>(\n () =>\n query(\n collection(firestore, scope[0], scope[1], 'lists'),\n orderBy('name'),\n limit(LIST_PICKER_LIMIT),\n ),\n [firestore, scope[0], scope[1]],\n { idField: '$id' },\n )\n return data ?? []\n}\n"],"names":["collection","limit","orderBy","query","useFirestore","useFirestoreCollection","LIST_PICKER_LIMIT","useOrgLists","scope","firestore","data","idField"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,UAAU,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,QAAQ,qBAAoB;AACtE,SAASC,YAAY,EAAEC,sBAAsB,QAAQ,iCAAgC;AAQrF,+DAA+D,GAC/D,OAAO,MAAMC,oBAAoB,GAAE;AAEnC,OAAO,SAASC,YACdC,KAAgC;IAEhC,MAAMC,YAAYL;IAClB,MAAM,EAAEM,IAAI,EAAE,GAAGL,uBACf,IACEF,MACEH,WAAWS,WAAWD,KAAK,CAAC,EAAE,EAAEA,KAAK,CAAC,EAAE,EAAE,UAC1CN,QAAQ,SACRD,MAAMK,qBAEV;QAACG;QAAWD,KAAK,CAAC,EAAE;QAAEA,KAAK,CAAC,EAAE;KAAC,EAC/B;QAAEG,SAAS;IAAM;IAEnB,OAAOD,eAAAA,OAAQ,EAAE;AACnB"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { SCREEN_KIND_EMAIL } from '@aglyn/aglyn/app-utils/screen-route';
18
+ /**
19
+ * THE TWO DOCUMENTS AN EMAIL DESIGN IS: a screen with `kind: 'email'`, and its
20
+ * first version holding the node map.
21
+ *
22
+ * Leaf modules only, so the console's create (a client component) and the
23
+ * server's draft writer build the same pair from one function: a design made
24
+ * on either side opens in the same besigner, lists on the same Templates tab,
25
+ * and is picked up by the same campaign composer.
26
+ */
27
+ /** The screen document of a new email design. */
28
+ export interface EmailDesignScreenDocument {
29
+ displayName: string;
30
+ kind: typeof SCREEN_KIND_EMAIL;
31
+ versionId: string;
32
+ }
33
+ /** The first version of a new email design. */
34
+ export interface EmailDesignVersionDocument {
35
+ screenId: string;
36
+ nodes: Record<string, unknown>;
37
+ }
38
+ /** A new design's first canvas: one email section holding a greeting. */
39
+ export declare function emailDesignStarterNodes(ids: {
40
+ sectionId: string;
41
+ textId: string;
42
+ }): Record<string, unknown>;
43
+ /** The screen and first version a design with these ids and this canvas is stored as. */
44
+ export declare function emailDesignDocuments(input: {
45
+ screenId: string;
46
+ versionId: string;
47
+ displayName: string;
48
+ nodes: Record<string, unknown>;
49
+ }): {
50
+ screen: EmailDesignScreenDocument;
51
+ version: EmailDesignVersionDocument;
52
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { SCREEN_KIND_EMAIL } from "@aglyn/aglyn/app-utils/screen-route";
17
+ import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
18
+ /** A new design's first canvas: one email section holding a greeting. */ export function emailDesignStarterNodes(ids) {
19
+ return {
20
+ [CANVAS_ROOT_ELEMENT_ID]: {
21
+ $id: CANVAS_ROOT_ELEMENT_ID,
22
+ componentId: 'div',
23
+ nodes: [
24
+ ids.sectionId
25
+ ]
26
+ },
27
+ [ids.sectionId]: {
28
+ $id: ids.sectionId,
29
+ componentId: 'emailSection',
30
+ pluginId: 'email',
31
+ parentId: CANVAS_ROOT_ELEMENT_ID,
32
+ nodes: [
33
+ ids.textId
34
+ ]
35
+ },
36
+ [ids.textId]: {
37
+ $id: ids.textId,
38
+ componentId: 'emailText',
39
+ pluginId: 'email',
40
+ parentId: ids.sectionId,
41
+ props: {
42
+ children: 'Hello {{contact.firstName}},',
43
+ variant: 'body'
44
+ }
45
+ }
46
+ };
47
+ }
48
+ /** The screen and first version a design with these ids and this canvas is stored as. */ export function emailDesignDocuments(input) {
49
+ return {
50
+ screen: {
51
+ displayName: input.displayName,
52
+ kind: SCREEN_KIND_EMAIL,
53
+ versionId: input.versionId
54
+ },
55
+ version: {
56
+ screenId: input.screenId,
57
+ nodes: input.nodes
58
+ }
59
+ };
60
+ }
61
+
62
+ //# sourceMappingURL=email-design-document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/model/email-design-document.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SCREEN_KIND_EMAIL } from '@aglyn/aglyn/app-utils/screen-route'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\n\n/**\n * THE TWO DOCUMENTS AN EMAIL DESIGN IS: a screen with `kind: 'email'`, and its\n * first version holding the node map.\n *\n * Leaf modules only, so the console's create (a client component) and the\n * server's draft writer build the same pair from one function: a design made\n * on either side opens in the same besigner, lists on the same Templates tab,\n * and is picked up by the same campaign composer.\n */\n\n/** The screen document of a new email design. */\nexport interface EmailDesignScreenDocument {\n displayName: string\n kind: typeof SCREEN_KIND_EMAIL\n versionId: string\n}\n\n/** The first version of a new email design. */\nexport interface EmailDesignVersionDocument {\n screenId: string\n nodes: Record<string, unknown>\n}\n\n/** A new design's first canvas: one email section holding a greeting. */\nexport function emailDesignStarterNodes(ids: {\n sectionId: string\n textId: string\n}): Record<string, unknown> {\n return {\n [CANVAS_ROOT_ELEMENT_ID]: {\n $id: CANVAS_ROOT_ELEMENT_ID,\n componentId: 'div',\n nodes: [ids.sectionId],\n },\n [ids.sectionId]: {\n $id: ids.sectionId,\n componentId: 'emailSection',\n pluginId: 'email',\n parentId: CANVAS_ROOT_ELEMENT_ID,\n nodes: [ids.textId],\n },\n [ids.textId]: {\n $id: ids.textId,\n componentId: 'emailText',\n pluginId: 'email',\n parentId: ids.sectionId,\n props: {\n children: 'Hello {{contact.firstName}},',\n variant: 'body',\n },\n },\n }\n}\n\n/** The screen and first version a design with these ids and this canvas is stored as. */\nexport function emailDesignDocuments(input: {\n screenId: string\n versionId: string\n displayName: string\n nodes: Record<string, unknown>\n}): { screen: EmailDesignScreenDocument; version: EmailDesignVersionDocument } {\n return {\n screen: {\n displayName: input.displayName,\n kind: SCREEN_KIND_EMAIL,\n versionId: input.versionId,\n },\n version: { screenId: input.screenId, nodes: input.nodes },\n }\n}\n"],"names":["SCREEN_KIND_EMAIL","CANVAS_ROOT_ELEMENT_ID","emailDesignStarterNodes","ids","$id","componentId","nodes","sectionId","pluginId","parentId","textId","props","children","variant","emailDesignDocuments","input","screen","displayName","kind","versionId","version","screenId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,QAAQ,sCAAqC;AACvE,SAASC,sBAAsB,QAAQ,2CAA0C;AAyBjF,uEAAuE,GACvE,OAAO,SAASC,wBAAwBC,GAGvC;IACC,OAAO;QACL,CAACF,uBAAuB,EAAE;YACxBG,KAAKH;YACLI,aAAa;YACbC,OAAO;gBAACH,IAAII,SAAS;aAAC;QACxB;QACA,CAACJ,IAAII,SAAS,CAAC,EAAE;YACfH,KAAKD,IAAII,SAAS;YAClBF,aAAa;YACbG,UAAU;YACVC,UAAUR;YACVK,OAAO;gBAACH,IAAIO,MAAM;aAAC;QACrB;QACA,CAACP,IAAIO,MAAM,CAAC,EAAE;YACZN,KAAKD,IAAIO,MAAM;YACfL,aAAa;YACbG,UAAU;YACVC,UAAUN,IAAII,SAAS;YACvBI,OAAO;gBACLC,UAAU;gBACVC,SAAS;YACX;QACF;IACF;AACF;AAEA,uFAAuF,GACvF,OAAO,SAASC,qBAAqBC,KAKpC;IACC,OAAO;QACLC,QAAQ;YACNC,aAAaF,MAAME,WAAW;YAC9BC,MAAMlB;YACNmB,WAAWJ,MAAMI,SAAS;QAC5B;QACAC,SAAS;YAAEC,UAAUN,MAAMM,QAAQ;YAAEf,OAAOS,MAAMT,KAAK;QAAC;IAC1D;AACF"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Email rendering model (AGL-412): besigner-built email HTML rendering + merge tags, relocated from core app-utils.
19
+ * Context-free — importable by client components, /server handlers, and
20
+ * other plugins/apps via `@aglyn/plugins-email/model`.
21
+ */
22
+ export { renderEmailHtml, substituteMergeTokens, resolveMergeTags, type EmailRenderOptions, type EmailRenderProduct, type RenderedEmail, } from '@aglyn/shared-util-email';
23
+ /**
24
+ * Campaign reporting: the rate math, the populations the send recorded, and
25
+ * the link rollup — pure, so every denominator is named once and provable
26
+ * rather than chosen in JSX.
27
+ */
28
+ export * from '@aglyn/shared-ui-email-campaigns/model/campaign-report';
29
+ /**
30
+ * What a campaign EARNED: the last-click window, the gross/refunded pair and
31
+ * the per-currency buckets that are never added together. Separate from the
32
+ * rate math because it reads a different document and answers the merchant's
33
+ * second question rather than their first.
34
+ */
35
+ export * from '@aglyn/shared-ui-email-campaigns/model/campaign-revenue';
36
+ /**
37
+ * The same math taken across every message sent from one TEMPLATE. Separate
38
+ * from `campaign-report` because a sum has a membership: which messages went
39
+ * into it, and therefore which population the rate describes.
40
+ */
41
+ export * from './template-report';
42
+ /** Reading one message record — its state, and when it went out. */
43
+ export * from '@aglyn/shared-ui-email-campaigns/model/email-record';
44
+ /**
45
+ * Whose template this is: authored here, or installed from a marketplace
46
+ * listing and versioned by somebody else.
47
+ */
48
+ export * from './template-provenance';
49
+ /**
50
+ * One campaign message, rendered. Shared by the send loop, the composer's
51
+ * pre-send preview and any other surface that has to show what will be
52
+ * mailed — a preview that renders the message a second way previews
53
+ * something else.
54
+ */
55
+ /**
56
+ * The campaign CONTAINER — its window, its lists, and the arithmetic that
57
+ * rolls its sends into one set of figures.
58
+ */
59
+ export * from '@aglyn/shared-ui-email-campaigns/model/campaign-container';
60
+ /**
61
+ * How each sending-domain state reads, said once for the list and the
62
+ * domain's own page — including why `inconclusive` is neither of them.
63
+ */
64
+ export * from './sending-domain-status';
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * Email rendering model (AGL-412): besigner-built email HTML rendering + merge tags, relocated from core app-utils.
18
+ * Context-free — importable by client components, /server handlers, and
19
+ * other plugins/apps via `@aglyn/plugins-email/model`.
20
+ */ // The renderer and merge-tag helpers moved to `@aglyn/shared-util-email`
21
+ // (AGL-750): they are pure functions over a node map with no imports at all,
22
+ // and the console's own API routes need them to render system email
23
+ // templates. Apps are forbidden to import feature plugins (the `scope:app`
24
+ // → `aglyn:addons` boundary), so leaving them here made them unreachable
25
+ // from exactly the code that has to send the mail.
26
+ //
27
+ // Re-exported so `@aglyn/plugins-email/model` keeps working unchanged.
28
+ export { renderEmailHtml, substituteMergeTokens, resolveMergeTags } from "@aglyn/shared-util-email";
29
+ // The campaign document model and its arithmetic live in
30
+ // `@aglyn/shared-ui-email-campaigns`. Marketing owns the campaign console
31
+ // section and Email owns the message surfaces; both are independently
32
+ // toggleable plugins, so a shape either one reads has to sit in a lib they
33
+ // depend on as peers rather than inside one of them. Re-exported here so
34
+ // `@aglyn/plugins-email/model` remains one barrel for this plugin's own
35
+ // callers.
36
+ /**
37
+ * Campaign reporting: the rate math, the populations the send recorded, and
38
+ * the link rollup — pure, so every denominator is named once and provable
39
+ * rather than chosen in JSX.
40
+ */ export * from "@aglyn/shared-ui-email-campaigns/model/campaign-report";
41
+ /**
42
+ * What a campaign EARNED: the last-click window, the gross/refunded pair and
43
+ * the per-currency buckets that are never added together. Separate from the
44
+ * rate math because it reads a different document and answers the merchant's
45
+ * second question rather than their first.
46
+ */ export * from "@aglyn/shared-ui-email-campaigns/model/campaign-revenue";
47
+ /**
48
+ * The same math taken across every message sent from one TEMPLATE. Separate
49
+ * from `campaign-report` because a sum has a membership: which messages went
50
+ * into it, and therefore which population the rate describes.
51
+ */ export * from "./template-report.js";
52
+ /** Reading one message record — its state, and when it went out. */ export * from "@aglyn/shared-ui-email-campaigns/model/email-record";
53
+ /**
54
+ * Whose template this is: authored here, or installed from a marketplace
55
+ * listing and versioned by somebody else.
56
+ */ export * from "./template-provenance.js";
57
+ /**
58
+ * One campaign message, rendered. Shared by the send loop, the composer's
59
+ * pre-send preview and any other surface that has to show what will be
60
+ * mailed — a preview that renders the message a second way previews
61
+ * something else.
62
+ */ /**
63
+ * The campaign CONTAINER — its window, its lists, and the arithmetic that
64
+ * rolls its sends into one set of figures.
65
+ */ export * from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
66
+ /**
67
+ * How each sending-domain state reads, said once for the list and the
68
+ * domain's own page — including why `inconclusive` is neither of them.
69
+ */ export * from "./sending-domain-status.js";
70
+
71
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/model/index.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Email rendering model (AGL-412): besigner-built email HTML rendering + merge tags, relocated from core app-utils.\n * Context-free — importable by client components, /server handlers, and\n * other plugins/apps via `@aglyn/plugins-email/model`.\n */\n// The renderer and merge-tag helpers moved to `@aglyn/shared-util-email`\n// (AGL-750): they are pure functions over a node map with no imports at all,\n// and the console's own API routes need them to render system email\n// templates. Apps are forbidden to import feature plugins (the `scope:app`\n// → `aglyn:addons` boundary), so leaving them here made them unreachable\n// from exactly the code that has to send the mail.\n//\n// Re-exported so `@aglyn/plugins-email/model` keeps working unchanged.\nexport {\n renderEmailHtml,\n substituteMergeTokens,\n resolveMergeTags,\n type EmailRenderOptions,\n type EmailRenderProduct,\n type RenderedEmail,\n} from '@aglyn/shared-util-email'\n\n// The campaign document model and its arithmetic live in\n// `@aglyn/shared-ui-email-campaigns`. Marketing owns the campaign console\n// section and Email owns the message surfaces; both are independently\n// toggleable plugins, so a shape either one reads has to sit in a lib they\n// depend on as peers rather than inside one of them. Re-exported here so\n// `@aglyn/plugins-email/model` remains one barrel for this plugin's own\n// callers.\n\n/**\n * Campaign reporting: the rate math, the populations the send recorded, and\n * the link rollup — pure, so every denominator is named once and provable\n * rather than chosen in JSX.\n */\nexport * from '@aglyn/shared-ui-email-campaigns/model/campaign-report'\n\n/**\n * What a campaign EARNED: the last-click window, the gross/refunded pair and\n * the per-currency buckets that are never added together. Separate from the\n * rate math because it reads a different document and answers the merchant's\n * second question rather than their first.\n */\nexport * from '@aglyn/shared-ui-email-campaigns/model/campaign-revenue'\n\n/**\n * The same math taken across every message sent from one TEMPLATE. Separate\n * from `campaign-report` because a sum has a membership: which messages went\n * into it, and therefore which population the rate describes.\n */\nexport * from './template-report'\n\n/** Reading one message record — its state, and when it went out. */\nexport * from '@aglyn/shared-ui-email-campaigns/model/email-record'\n\n/**\n * Whose template this is: authored here, or installed from a marketplace\n * listing and versioned by somebody else.\n */\nexport * from './template-provenance'\n\n/**\n * One campaign message, rendered. Shared by the send loop, the composer's\n * pre-send preview and any other surface that has to show what will be\n * mailed — a preview that renders the message a second way previews\n * something else.\n */\n\n/**\n * The campaign CONTAINER — its window, its lists, and the arithmetic that\n * rolls its sends into one set of figures.\n */\nexport * from '@aglyn/shared-ui-email-campaigns/model/campaign-container'\n\n/**\n * How each sending-domain state reads, said once for the list and the\n * domain's own page — including why `inconclusive` is neither of them.\n */\nexport * from './sending-domain-status'\n"],"names":["renderEmailHtml","substituteMergeTokens","resolveMergeTags"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,yEAAyE;AACzE,6EAA6E;AAC7E,oEAAoE;AACpE,2EAA2E;AAC3E,yEAAyE;AACzE,mDAAmD;AACnD,EAAE;AACF,uEAAuE;AACvE,SACEA,eAAe,EACfC,qBAAqB,EACrBC,gBAAgB,QAIX,2BAA0B;AAEjC,yDAAyD;AACzD,0EAA0E;AAC1E,sEAAsE;AACtE,2EAA2E;AAC3E,yEAAyE;AACzE,wEAAwE;AACxE,WAAW;AAEX;;;;CAIC,GACD,cAAc,yDAAwD;AAEtE;;;;;CAKC,GACD,cAAc,0DAAyD;AAEvE;;;;CAIC,GACD,cAAc,uBAAmB;AAEjC,kEAAkE,GAClE,cAAc,sDAAqD;AAEnE;;;CAGC,GACD,cAAc,2BAAuB;AAErC;;;;;CAKC,GAED;;;CAGC,GACD,cAAc,4DAA2D;AAEzE;;;CAGC,GACD,cAAc,6BAAyB"}