@aglyn/aglyn 1.0.0-beta.203 → 1.0.0-beta.204
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.
- package/package.json +11 -11
- package/src/lib/app-utils/console-routes.d.ts +4 -0
- package/src/lib/app-utils/console-routes.js +4 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/csv-import.d.ts +12 -0
- package/src/lib/app-utils/csv-import.js +13 -3
- package/src/lib/app-utils/csv-import.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
- package/src/lib/app-utils/docs-help.generated.js +1 -0
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +76 -6
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/health-report.js +10 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +1 -1
- package/src/lib/app-utils/notifications.js +4 -1
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/recipient-email-render.d.ts +21 -1
- package/src/lib/app-utils/recipient-email-render.js +34 -5
- package/src/lib/app-utils/recipient-email-render.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +1 -1
- package/src/lib/app-utils/server.js +1 -1
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-email-chrome.d.ts +52 -0
- package/src/lib/app-utils/site-email-chrome.js +82 -0
- package/src/lib/app-utils/site-email-chrome.js.map +1 -0
- package/src/lib/app-utils/system-email-chrome.d.ts +6 -0
- package/src/lib/app-utils/system-email-chrome.js.map +1 -1
- package/src/lib/plugin-manager/plugin-record-email-state.d.ts +7 -0
- package/src/lib/plugin-manager/plugin-record-email-state.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 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 * Server-safe app-utils: every app-util EXCEPT the client-only React\n * contexts (see ./contexts). Nothing here calls `createContext` at module\n * scope, so this barrel — re-exported by `@aglyn/aglyn/server` — is safe to\n * import from tenant Server Components (AGL-405). The full `./index` barrel\n * re-adds the contexts for client consumers.\n */\nexport * from './binding-token-catalog'\nexport * from './binding-tokens'\nexport * from './breadcrumb-json-ld'\n// What a search listing holds and how long each field may be (AGL-2910):\n// the SEO editors and anything that proposes a value read one catalog.\nexport * from './seo-listing-fields'\n// `VideoObject` for the Video elements a page actually ships (AGL-2747).\n// Beside the other structured-data builders, and server-only like them: only\n// the route emits JSON-LD, and a crawler-facing block has no client caller.\nexport * from './video-object'\nexport * from './collection-delete'\nexport * from './collection-entries'\nexport * from './collection-kind'\nexport * from './collection-slug'\nexport * from './content-authors'\nexport * from './content-query-fields'\nexport * from './content-schema-type'\nexport * from './content-author-profile'\nexport * from './url-slug'\nexport * from './child-contract'\nexport * from './child-contract-compose'\n// The one reading of `restrictChildren`/`restrictParent`, shared by the\n// besigner's drop check and the AI plugin's node-tree validator (AGL-2905).\nexport * from './lineal-order'\n// What a node tree must satisfy before it lands on a canvas that did not\n// author it (AGL-2939): the marketplace's install sanitizer and the AI\n// plugin's node-tree validator both pass through it.\nexport * from './node-definition-sanitizer'\nexport * from './console-routes'\nexport * from './console-record-links'\n// What a stored screen-link value means, with no React attached — the\n// where-used scan reads these on the server (AGL-703).\nexport * from './screen-link-value'\n// Which entries a page's links name (AGL-3118). The tenant render reads\n// exactly those entry documents, so the walk runs on the server.\nexport * from './link-references'\nexport * from './analytics-path-key'\nexport * from './contacts'\n// A lead's or a contact's email state (AGL-3245): the last verdict on its\n// address, in words a person can act on, mirrored from the senders' lists.\nexport * from './email-state'\n// The records that sit beside a contact — companies, pipelines, deals, tasks,\n// activities, field definitions (AGL-2595). Pure like `contacts`, and beside\n// it because `ContactFacet` names its lifecycle stage and custom-value types;\n// the scope stamp every CRM creator writes lives here too.\nexport * from './crm'\n// Email templates, snippets and the merge-field resolver (AGL-2658): the\n// letter a rep keeps under a name, and the one grammar the send route and\n// the dialog fill it in by. Pure like `crm`, and beside it because a\n// template is scoped the way a task is.\nexport * from './crm-email-templates'\n// The booking door's shared vocabulary (AGL-2660): the query keys a booking\n// link carries, the record reference inside it, and the wording and due\n// date of what a booking files back on the record.\nexport * from './crm-booking'\n// Two contact documents folded into one (AGL-2625): the plan the console\n// previews and the server writes, reading the facet shape from `contacts`\n// and the company mirror from `crm`.\nexport * from './contact-merge'\n// A person's privacy erasure from one workspace (AGL-2623): the request's\n// shape and id, the marker a record carries while it waits, and the lists\n// the dialog and the docs share. Pure like `crm`; the sweep is server-side.\nexport * from './person-erasure'\n// What the CRM's reports compute from what they read — periods, weekly\n// buckets, the lifecycle funnel, pipeline totals (AGL-2604). Pure like `crm`,\n// and beside it because it reads the stage list and the deal weighting from\n// there rather than restating either.\nexport * from './crm-reports'\n// Every CRM CSV, written once (AGL-2662): the column lists and the cells\n// each section's Export button writes, and the server's whole-collection\n// export streams — one implementation, because the console app may not\n// import the plugin whose sections used to hold it.\nexport * from './crm-csv'\n// Which holder a cross-site reader sees one contact through (AGL-2630),\n// beside the files that write it: the org-level list and the server's\n// export both flatten a contact this way.\nexport * from './contact-holder'\n// What one member is owed this morning — the day's window in a named zone,\n// the task and lead bucketing, and the words the notification and the mail\n// say (AGL-2619). Pure like `crm-reports`, read by the scheduled route.\nexport * from './crm-digest'\n// A task's reminder at its own time (AGL-2659): which reminder a save\n// leaves, whether one is owed, and the words the notification and the mail\n// say. Pure like `crm-digest`, read by the hourly route and by every\n// writer of the field.\nexport * from './crm-task-reminders'\n// Email capture (AGL-2657): the address's shape and token, which of a\n// message's addresses is the correspondent, the excerpt, and the row a\n// captured message becomes. Pure like `crm-digest`; the webhook route and\n// the address route are the readers with Firestore.\nexport * from './crm-inbound'\n// A member's own addresses in a workspace (AGL-2975): the ones they have\n// confirmed count as theirs wherever the roster is asked who wrote a\n// message. Pure; the store and the confirmation link are server-side.\nexport * from './member-email-aliases'\n// The next activity a record carries (AGL-2661): what `nextTaskAtMs` IS given\n// a record's open tasks, the view clause a list filters on, the report figure.\n// Pure like `crm`; the admin library's `recomputeCrmNextTaskAt` is the writer.\nexport * from './crm-next-activity'\n// The CSV cell grammar the exports write with (AGL-2621), and the target a\n// mapped column names for a custom field — named rather than starred, because\n// an importer built on this file exports the shared ceilings under its own\n// names.\nexport {\n csvCell,\n csvDocument,\n customImportTarget,\n customImportTargetKey,\n} from './csv-import'\nexport type {\n ImportChunkResult,\n ImportDroppedValue,\n ImportSkippedRow,\n} from './csv-import'\n// What a VALUE under a custom contact field may be, and the coercion every\n// door applies to one (AGL-2601): a mapped form field, an API `custom` body,\n// and the carry-over that keeps a mapping across a publish. Pure like `crm`.\nexport * from './contact-custom-fields'\n// The consent JOIN (`docs/specs/email-overhaul.md` §3f). Pure, and it composes\n// `contacts` for the normalizer, so it sits directly beside it. No Node\n// builtin, which is what keeps it out of the `/server`-only group `person-key`\n// belongs to.\nexport * from './consent-groups'\nexport * from './marketing-consent'\n// The ENROLLMENT-time half of the same question, beside its reader for the\n// same reason: `marketing-consent` decides whether a recorded basis lets us\n// mail somebody, and this decides what basis putting them on a list may\n// record. Two enrollment surfaces import it — the Inbox assignment route and\n// the Emails console's audience card — and neither could import the other.\nexport * from './list-assignment-policy'\n// Reading the FILE a merchant arrives with (`docs/specs/email-competitive-gaps.md`\n// G5/P4), directly after the policy it hands its addresses to: parsing and\n// mechanical screening only, so that an import asks the enrollment question\n// through the same module the one-address add path asks it through rather\n// than answering it a second way.\nexport * from './list-import'\n// The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and\n// it composes `contacts` for the segment vocabulary rather than restating it.\nexport * from './dynamic-list-rule'\n// The subscribable streams a recipient can leave one at a time\n// (`docs/specs/email-competitive-gaps.md` §1f). Pure, and read from all three\n// sides of the feature: the composer picks one, the send path signs it into\n// the unsubscribe link, and the unauthenticated preference page renders the\n// catalog. No Node builtin, so it stays out of the `/server`-only group.\nexport * from './email-topics'\nexport * from './compose-layout-nodes'\n// Layout properties applied through the chain (AGL-2893).\nexport * from './compose-layout-props'\n// A screen's per-page restyling of its layout's elements (AGL-3286).\nexport * from './layout-style-overrides'\nexport * from './document-landmark'\nexport * from './functions'\nexport * from './compose-reusable-components'\n// Where a reusable component is placed — pages or emails (AGL-3287).\nexport * from './reusable-component-kind'\nexport * from './compress'\nexport * from './merge-node-sx'\nexport * from './palette-sx'\nexport * from './scheme-sx'\n// An element pinned to one color scheme (AGL-3284).\nexport * from './element-color-scheme'\nexport * from './create-resource-uid'\nexport * from './decompress'\nexport * from './stored-nodes'\nexport * from './strip-undefined'\nexport * from './organizations'\nexport * from './org-permissions'\n// The AI verdict on either membership axis (AGL-2927). After both modules it\n// composes: the org catalog for org-wide members, the host role for site\n// collaborators.\nexport * from './host-permissions'\nexport * from './password-policy'\nexport * from './idp-profile'\nexport * from './onboarding-deep-link'\n// Where an account came from (AGL-1731). Beside the plan intent because\n// they are the same hop — the marketing CTA's query string — and both are\n// remembered on `users/{uid}` across the verification wall.\nexport * from './campaign-attribution'\n// The edge between a campaign and the forms, screens and contacts a push is\n// coordinated across. Beside the attribution above because the two answer\n// opposite questions about the same word: attribution records where somebody\n// CAME from, this records what a merchant DECLARED belongs together.\nexport * from './campaign-membership'\nexport * from './deployment-shape'\n// Which browser origins may complete a signed direct-to-GCS upload (AGL-1452).\n// GCS matches the origin list EXACTLY, so every serving console name needs its\n// own entry — the rule nobody could be expected to remember at attach time.\nexport * from './upload-cors'\n// The docs-help subset the first-party plugin consoles read (AGL-2213) — the\n// console's own registry lives in apps/console/constants and a lib cannot\n// import an app.\nexport * from './docs-help'\nexport * from './platform-brand'\nexport * from './plan-entitlements'\n// The free plan's bandwidth hard cap (AGL-1967/2070/2155). After\n// `plan-entitlements`, which owns the predicate it keys off.\nexport * from './bandwidth-cap'\nexport * from './form-abuse-ceiling'\nexport * from './forms'\n// What a form's DESIGN must still satisfy for its submissions to arrive.\n// After `forms`, whose field walk it reads the drawn fields with.\nexport * from './form-contract'\nexport * from './visitor-record-ceiling'\nexport * from './health-report'\n// Did a webhook delivery actually DO anything (AGL-1954)? After\n// `health-report`, whose billing verdict consumes the counts this produces.\nexport * from './webhook-delivery'\nexport * from './support-tiers'\nexport * from './release-flags'\n// Who runs THIS deployment, and where its notices go (AGL-2016). Ordered\n// before the four surfaces that print it, all of which used to declare their\n// own `support@aglyn.com` literal.\nexport * from './operator-identity'\nexport * from './lockdown'\nexport * from './media-quarantine'\nexport * from './media-takedown-reach'\nexport * from './upload-inspection'\nexport * from './abuse-report'\n// The other three quarters of §512 (AGL-1983): the put-back procedure that\n// answers a notice, and the strike ledger that conditions the safe harbour.\nexport * from './dmca-counter-notice'\nexport * from './repeat-infringer'\n// `addBusinessDays` briefly existed TWICE — once here in './support-tiers'\n// and once in './dmca-counter-notice' — and two `export *` made the name\n// ambiguous, TS2308'ing this barrel and every tsconfig that reads it. The\n// interleave was fixed twice in parallel: once by naming a winner here, and\n// once by deleting the second definition so './dmca-counter-notice' imports\n// this one. Only the deletion survives, so the disambiguating re-export that\n// stood on this line is gone with it — `export *` is unambiguous again.\n//\n// The concern that argued for keeping two copies is worth answering rather\n// than dropping: a later tweak to the SUPPORT SLA window must never silently\n// move the §512(g)(2)(C) put-back deadline. It cannot. This function embeds\n// no window — it is \"add N business days\", and N is the argument. The two\n// windows live in their own constants (`SUPPORT_*` there,\n// `COUNTER_NOTICE_*_BUSINESS_DAYS` in the DMCA module) and never meet. What\n// SHOULD stay shared is the day arithmetic itself: two copies of a weekend\n// rule are two chances to disagree about which day a deadline falls on.\nexport * from './org-override-reason'\nexport * from './host-tokens'\nexport * from './variables'\nexport * from './workflows'\nexport * from './datasets'\nexport * from './expand-repeatables'\nexport * from './org-roles'\nexport * from './markdown-lite'\nexport * from './notifications'\nexport * from './definition-canvas-tree'\nexport * from './ensure-canvas-root'\nexport * from './repair-canvas-nodes'\nexport * from './detect-template-placeholders'\nexport * from './guarded-version-save'\nexport * from './local-storage-budget'\nexport * from './measure-node-map'\nexport * from './resolve-named-tokens'\nexport * from './screen-route'\nexport * from './host-naming'\nexport * from './name-search'\nexport * from './artifact-list-keys'\nexport * from './name-match'\nexport * from './api-plugins'\nexport * from './plugin-api-rate-limit'\n// The same-origin gate on visitor-facing plugin writes (AGL-1880). After\n// `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the\n// two gates exempt exactly the same machine surfaces.\nexport * from './plugin-api-cross-origin'\nexport * from './actions'\nexport * from './element-animation'\nexport * from './attribution-guard'\nexport * from './node-interactions'\nexport * from './element-ui'\nexport * from './image-upload-types'\nexport * from './sanitize-svg'\nexport * from './media-folders'\nexport * from './media-metadata'\nexport * from './media-ref'\n// After `media-ref`, whose reference grammar it reads stored URLs with.\nexport * from './paid-media-source'\nexport * from './author-css'\n// The isomorphic HTML rule (AGL-1901), after the CSS one it depends on.\nexport * from './author-html'\nexport * from './dataset-models'\nexport * from './dataset-csv'\nexport * from './marketplace-provenance'\nexport * from './marketplace-update-state'\nexport * from './dataset-query'\nexport * from './plugin-manifest'\n// After `plugin-manifest`, whose revocation predicates it asks the kill\n// question with, and after `media-ref`, whose grammar decides what counts as a\n// first-party image.\nexport * from './email-starter-policy'\nexport * from './safe-json-ld'\n// Agent readiness (AGL-2716): content negotiation, the Markdown rendering of a\n// page, `/llms.txt` and the per-site OpenAPI document. All pure, all server-safe.\nexport * from './accept-negotiation'\nexport * from './agent-api-catalog'\nexport * from './agent-openapi'\nexport * from './author-html-markdown'\nexport * from './llms-txt'\nexport * from './page-markdown'\nexport * from './request-geo'\nexport * from './request-ip'\nexport * from './search-indexing'\nexport * from './seo-title'\nexport * from './sitemap'\nexport * from './visitor-consent'\nexport * from './social-image'\nexport * from './scope-tokens'\nexport * from './org-billing-doc'\nexport * from './stripe-deployment-mode'\nexport * from './deferred-image'\n"],"names":["csvCell","csvDocument","customImportTarget","customImportTargetKey"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 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 * Server-safe app-utils: every app-util EXCEPT the client-only React\n * contexts (see ./contexts). Nothing here calls `createContext` at module\n * scope, so this barrel — re-exported by `@aglyn/aglyn/server` — is safe to\n * import from tenant Server Components (AGL-405). The full `./index` barrel\n * re-adds the contexts for client consumers.\n */\nexport * from './binding-token-catalog'\nexport * from './binding-tokens'\nexport * from './breadcrumb-json-ld'\n// What a search listing holds and how long each field may be (AGL-2910):\n// the SEO editors and anything that proposes a value read one catalog.\nexport * from './seo-listing-fields'\n// `VideoObject` for the Video elements a page actually ships (AGL-2747).\n// Beside the other structured-data builders, and server-only like them: only\n// the route emits JSON-LD, and a crawler-facing block has no client caller.\nexport * from './video-object'\nexport * from './collection-delete'\nexport * from './collection-entries'\nexport * from './collection-kind'\nexport * from './collection-slug'\nexport * from './content-authors'\nexport * from './content-query-fields'\nexport * from './content-schema-type'\nexport * from './content-author-profile'\nexport * from './url-slug'\nexport * from './child-contract'\nexport * from './child-contract-compose'\n// The one reading of `restrictChildren`/`restrictParent`, shared by the\n// besigner's drop check and the AI plugin's node-tree validator (AGL-2905).\nexport * from './lineal-order'\n// What a node tree must satisfy before it lands on a canvas that did not\n// author it (AGL-2939): the marketplace's install sanitizer and the AI\n// plugin's node-tree validator both pass through it.\nexport * from './node-definition-sanitizer'\nexport * from './console-routes'\nexport * from './console-record-links'\n// What a stored screen-link value means, with no React attached — the\n// where-used scan reads these on the server (AGL-703).\nexport * from './screen-link-value'\n// Which entries a page's links name (AGL-3118). The tenant render reads\n// exactly those entry documents, so the walk runs on the server.\nexport * from './link-references'\nexport * from './analytics-path-key'\nexport * from './contacts'\n// A lead's or a contact's email state (AGL-3245): the last verdict on its\n// address, in words a person can act on, mirrored from the senders' lists.\nexport * from './email-state'\n// The records that sit beside a contact — companies, pipelines, deals, tasks,\n// activities, field definitions (AGL-2595). Pure like `contacts`, and beside\n// it because `ContactFacet` names its lifecycle stage and custom-value types;\n// the scope stamp every CRM creator writes lives here too.\nexport * from './crm'\n// Email templates, snippets and the merge-field resolver (AGL-2658): the\n// letter a rep keeps under a name, and the one grammar the send route and\n// the dialog fill it in by. Pure like `crm`, and beside it because a\n// template is scoped the way a task is.\nexport * from './crm-email-templates'\n// The booking door's shared vocabulary (AGL-2660): the query keys a booking\n// link carries, the record reference inside it, and the wording and due\n// date of what a booking files back on the record.\nexport * from './crm-booking'\n// Two contact documents folded into one (AGL-2625): the plan the console\n// previews and the server writes, reading the facet shape from `contacts`\n// and the company mirror from `crm`.\nexport * from './contact-merge'\n// A person's privacy erasure from one workspace (AGL-2623): the request's\n// shape and id, the marker a record carries while it waits, and the lists\n// the dialog and the docs share. Pure like `crm`; the sweep is server-side.\nexport * from './person-erasure'\n// What the CRM's reports compute from what they read — periods, weekly\n// buckets, the lifecycle funnel, pipeline totals (AGL-2604). Pure like `crm`,\n// and beside it because it reads the stage list and the deal weighting from\n// there rather than restating either.\nexport * from './crm-reports'\n// Every CRM CSV, written once (AGL-2662): the column lists and the cells\n// each section's Export button writes, and the server's whole-collection\n// export streams — one implementation, because the console app may not\n// import the plugin whose sections used to hold it.\nexport * from './crm-csv'\n// Which holder a cross-site reader sees one contact through (AGL-2630),\n// beside the files that write it: the org-level list and the server's\n// export both flatten a contact this way.\nexport * from './contact-holder'\n// What one member is owed this morning — the day's window in a named zone,\n// the task and lead bucketing, and the words the notification and the mail\n// say (AGL-2619). Pure like `crm-reports`, read by the scheduled route.\nexport * from './crm-digest'\n// A task's reminder at its own time (AGL-2659): which reminder a save\n// leaves, whether one is owed, and the words the notification and the mail\n// say. Pure like `crm-digest`, read by the hourly route and by every\n// writer of the field.\nexport * from './crm-task-reminders'\n// Email capture (AGL-2657): the address's shape and token, which of a\n// message's addresses is the correspondent, the excerpt, and the row a\n// captured message becomes. Pure like `crm-digest`; the webhook route and\n// the address route are the readers with Firestore.\nexport * from './crm-inbound'\n// A member's own addresses in a workspace (AGL-2975): the ones they have\n// confirmed count as theirs wherever the roster is asked who wrote a\n// message. Pure; the store and the confirmation link are server-side.\nexport * from './member-email-aliases'\n// The next activity a record carries (AGL-2661): what `nextTaskAtMs` IS given\n// a record's open tasks, the view clause a list filters on, the report figure.\n// Pure like `crm`; the admin library's `recomputeCrmNextTaskAt` is the writer.\nexport * from './crm-next-activity'\n// The CSV cell grammar the exports write with (AGL-2621), and the target a\n// mapped column names for a custom field — named rather than starred, because\n// an importer built on this file exports the shared ceilings under its own\n// names.\nexport {\n csvCell,\n csvDocument,\n customImportTarget,\n customImportTargetKey,\n importNoMailServerSentence,\n} from './csv-import'\nexport type {\n ImportChunkResult,\n ImportDroppedValue,\n ImportSkippedRow,\n} from './csv-import'\n// What a VALUE under a custom contact field may be, and the coercion every\n// door applies to one (AGL-2601): a mapped form field, an API `custom` body,\n// and the carry-over that keeps a mapping across a publish. Pure like `crm`.\nexport * from './contact-custom-fields'\n// The consent JOIN (`docs/specs/email-overhaul.md` §3f). Pure, and it composes\n// `contacts` for the normalizer, so it sits directly beside it. No Node\n// builtin, which is what keeps it out of the `/server`-only group `person-key`\n// belongs to.\nexport * from './consent-groups'\nexport * from './marketing-consent'\n// The ENROLLMENT-time half of the same question, beside its reader for the\n// same reason: `marketing-consent` decides whether a recorded basis lets us\n// mail somebody, and this decides what basis putting them on a list may\n// record. Two enrollment surfaces import it — the Inbox assignment route and\n// the Emails console's audience card — and neither could import the other.\nexport * from './list-assignment-policy'\n// Reading the FILE a merchant arrives with (`docs/specs/email-competitive-gaps.md`\n// G5/P4), directly after the policy it hands its addresses to: parsing and\n// mechanical screening only, so that an import asks the enrollment question\n// through the same module the one-address add path asks it through rather\n// than answering it a second way.\nexport * from './list-import'\n// The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and\n// it composes `contacts` for the segment vocabulary rather than restating it.\nexport * from './dynamic-list-rule'\n// The subscribable streams a recipient can leave one at a time\n// (`docs/specs/email-competitive-gaps.md` §1f). Pure, and read from all three\n// sides of the feature: the composer picks one, the send path signs it into\n// the unsubscribe link, and the unauthenticated preference page renders the\n// catalog. No Node builtin, so it stays out of the `/server`-only group.\nexport * from './email-topics'\nexport * from './compose-layout-nodes'\n// Layout properties applied through the chain (AGL-2893).\nexport * from './compose-layout-props'\n// A screen's per-page restyling of its layout's elements (AGL-3286).\nexport * from './layout-style-overrides'\nexport * from './document-landmark'\nexport * from './functions'\nexport * from './compose-reusable-components'\n// Where a reusable component is placed — pages or emails (AGL-3287).\nexport * from './reusable-component-kind'\nexport * from './compress'\nexport * from './merge-node-sx'\nexport * from './palette-sx'\nexport * from './scheme-sx'\n// An element pinned to one color scheme (AGL-3284).\nexport * from './element-color-scheme'\nexport * from './create-resource-uid'\nexport * from './decompress'\nexport * from './stored-nodes'\nexport * from './strip-undefined'\nexport * from './organizations'\nexport * from './org-permissions'\n// The AI verdict on either membership axis (AGL-2927). After both modules it\n// composes: the org catalog for org-wide members, the host role for site\n// collaborators.\nexport * from './host-permissions'\nexport * from './password-policy'\nexport * from './idp-profile'\nexport * from './onboarding-deep-link'\n// Where an account came from (AGL-1731). Beside the plan intent because\n// they are the same hop — the marketing CTA's query string — and both are\n// remembered on `users/{uid}` across the verification wall.\nexport * from './campaign-attribution'\n// The edge between a campaign and the forms, screens and contacts a push is\n// coordinated across. Beside the attribution above because the two answer\n// opposite questions about the same word: attribution records where somebody\n// CAME from, this records what a merchant DECLARED belongs together.\nexport * from './campaign-membership'\nexport * from './deployment-shape'\n// Which browser origins may complete a signed direct-to-GCS upload (AGL-1452).\n// GCS matches the origin list EXACTLY, so every serving console name needs its\n// own entry — the rule nobody could be expected to remember at attach time.\nexport * from './upload-cors'\n// The docs-help subset the first-party plugin consoles read (AGL-2213) — the\n// console's own registry lives in apps/console/constants and a lib cannot\n// import an app.\nexport * from './docs-help'\nexport * from './platform-brand'\nexport * from './plan-entitlements'\n// The free plan's bandwidth hard cap (AGL-1967/2070/2155). After\n// `plan-entitlements`, which owns the predicate it keys off.\nexport * from './bandwidth-cap'\nexport * from './form-abuse-ceiling'\nexport * from './forms'\n// What a form's DESIGN must still satisfy for its submissions to arrive.\n// After `forms`, whose field walk it reads the drawn fields with.\nexport * from './form-contract'\nexport * from './visitor-record-ceiling'\nexport * from './health-report'\n// Did a webhook delivery actually DO anything (AGL-1954)? After\n// `health-report`, whose billing verdict consumes the counts this produces.\nexport * from './webhook-delivery'\nexport * from './support-tiers'\nexport * from './release-flags'\n// Who runs THIS deployment, and where its notices go (AGL-2016). Ordered\n// before the four surfaces that print it, all of which used to declare their\n// own `support@aglyn.com` literal.\nexport * from './operator-identity'\nexport * from './lockdown'\nexport * from './media-quarantine'\nexport * from './media-takedown-reach'\nexport * from './upload-inspection'\nexport * from './abuse-report'\n// The other three quarters of §512 (AGL-1983): the put-back procedure that\n// answers a notice, and the strike ledger that conditions the safe harbour.\nexport * from './dmca-counter-notice'\nexport * from './repeat-infringer'\n// `addBusinessDays` briefly existed TWICE — once here in './support-tiers'\n// and once in './dmca-counter-notice' — and two `export *` made the name\n// ambiguous, TS2308'ing this barrel and every tsconfig that reads it. The\n// interleave was fixed twice in parallel: once by naming a winner here, and\n// once by deleting the second definition so './dmca-counter-notice' imports\n// this one. Only the deletion survives, so the disambiguating re-export that\n// stood on this line is gone with it — `export *` is unambiguous again.\n//\n// The concern that argued for keeping two copies is worth answering rather\n// than dropping: a later tweak to the SUPPORT SLA window must never silently\n// move the §512(g)(2)(C) put-back deadline. It cannot. This function embeds\n// no window — it is \"add N business days\", and N is the argument. The two\n// windows live in their own constants (`SUPPORT_*` there,\n// `COUNTER_NOTICE_*_BUSINESS_DAYS` in the DMCA module) and never meet. What\n// SHOULD stay shared is the day arithmetic itself: two copies of a weekend\n// rule are two chances to disagree about which day a deadline falls on.\nexport * from './org-override-reason'\nexport * from './host-tokens'\nexport * from './variables'\nexport * from './workflows'\nexport * from './datasets'\nexport * from './expand-repeatables'\nexport * from './org-roles'\nexport * from './markdown-lite'\nexport * from './notifications'\nexport * from './definition-canvas-tree'\nexport * from './ensure-canvas-root'\nexport * from './repair-canvas-nodes'\nexport * from './detect-template-placeholders'\nexport * from './guarded-version-save'\nexport * from './local-storage-budget'\nexport * from './measure-node-map'\nexport * from './resolve-named-tokens'\nexport * from './screen-route'\nexport * from './host-naming'\nexport * from './name-search'\nexport * from './artifact-list-keys'\nexport * from './name-match'\nexport * from './api-plugins'\nexport * from './plugin-api-rate-limit'\n// The same-origin gate on visitor-facing plugin writes (AGL-1880). After\n// `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the\n// two gates exempt exactly the same machine surfaces.\nexport * from './plugin-api-cross-origin'\nexport * from './actions'\nexport * from './element-animation'\nexport * from './attribution-guard'\nexport * from './node-interactions'\nexport * from './element-ui'\nexport * from './image-upload-types'\nexport * from './sanitize-svg'\nexport * from './media-folders'\nexport * from './media-metadata'\nexport * from './media-ref'\n// After `media-ref`, whose reference grammar it reads stored URLs with.\nexport * from './paid-media-source'\nexport * from './author-css'\n// The isomorphic HTML rule (AGL-1901), after the CSS one it depends on.\nexport * from './author-html'\nexport * from './dataset-models'\nexport * from './dataset-csv'\nexport * from './marketplace-provenance'\nexport * from './marketplace-update-state'\nexport * from './dataset-query'\nexport * from './plugin-manifest'\n// After `plugin-manifest`, whose revocation predicates it asks the kill\n// question with, and after `media-ref`, whose grammar decides what counts as a\n// first-party image.\nexport * from './email-starter-policy'\nexport * from './safe-json-ld'\n// Agent readiness (AGL-2716): content negotiation, the Markdown rendering of a\n// page, `/llms.txt` and the per-site OpenAPI document. All pure, all server-safe.\nexport * from './accept-negotiation'\nexport * from './agent-api-catalog'\nexport * from './agent-openapi'\nexport * from './author-html-markdown'\nexport * from './llms-txt'\nexport * from './page-markdown'\nexport * from './request-geo'\nexport * from './request-ip'\nexport * from './search-indexing'\nexport * from './seo-title'\nexport * from './sitemap'\nexport * from './visitor-consent'\nexport * from './social-image'\nexport * from './scope-tokens'\nexport * from './org-billing-doc'\nexport * from './stripe-deployment-mode'\nexport * from './deferred-image'\n"],"names":["csvCell","csvDocument","customImportTarget","customImportTargetKey","importNoMailServerSentence"],"mappings":"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"}
|
|
@@ -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
|
+
/**
|
|
18
|
+
* The header and footer a SITE's email is drawn inside (AGL-3370).
|
|
19
|
+
*
|
|
20
|
+
* The site-side twin of `system-email-chrome.ts`. A site's mail to its own
|
|
21
|
+
* customers (an order receipt, a member's password reset, a newsletter
|
|
22
|
+
* confirmation) is the site's, so everything in the chrome is read off the
|
|
23
|
+
* site: its logo or its name in bold, linked to its address; a footer of why
|
|
24
|
+
* the reader is getting the mail, the site's support address, and a
|
|
25
|
+
* `© {year} {business name} · {postal address}` line.
|
|
26
|
+
*
|
|
27
|
+
* Nothing here names the platform. A site's customer bought from the site,
|
|
28
|
+
* not from Aglyn, and a vendor's name at the foot of a receipt is the same
|
|
29
|
+
* leak white-label exists to close, on every plan.
|
|
30
|
+
*
|
|
31
|
+
* Which emails wear it is the caller's decision: the built-in copy and the
|
|
32
|
+
* text-only messages do; a design the site owner published is theirs, placed
|
|
33
|
+
* header and footer included, and is sent as they built it.
|
|
34
|
+
*
|
|
35
|
+
* Pure: no reads, and a clock a test can replace.
|
|
36
|
+
*/
|
|
37
|
+
import type { EmailChrome } from '@aglyn/shared-util-email/email-render';
|
|
38
|
+
import { type HostTokenSource } from './host-tokens';
|
|
39
|
+
export interface SiteEmailChromeInput {
|
|
40
|
+
/** The site's host document, or the fields of it the tokens read. */
|
|
41
|
+
host: HostTokenSource | null | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Why the recipient is getting this email, as a template: the renderer
|
|
44
|
+
* substitutes `{{host.businessName}}` and the email's own tokens in it, as
|
|
45
|
+
* it does in the body.
|
|
46
|
+
*/
|
|
47
|
+
reason?: string;
|
|
48
|
+
/** The clock the copyright year is read from. */
|
|
49
|
+
now?: () => Date;
|
|
50
|
+
}
|
|
51
|
+
/** See the module note. */
|
|
52
|
+
export declare function buildSiteEmailChrome(input: SiteEmailChromeInput): EmailChrome;
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
* The header and footer a SITE's email is drawn inside (AGL-3370).
|
|
18
|
+
*
|
|
19
|
+
* The site-side twin of `system-email-chrome.ts`. A site's mail to its own
|
|
20
|
+
* customers (an order receipt, a member's password reset, a newsletter
|
|
21
|
+
* confirmation) is the site's, so everything in the chrome is read off the
|
|
22
|
+
* site: its logo or its name in bold, linked to its address; a footer of why
|
|
23
|
+
* the reader is getting the mail, the site's support address, and a
|
|
24
|
+
* `© {year} {business name} · {postal address}` line.
|
|
25
|
+
*
|
|
26
|
+
* Nothing here names the platform. A site's customer bought from the site,
|
|
27
|
+
* not from Aglyn, and a vendor's name at the foot of a receipt is the same
|
|
28
|
+
* leak white-label exists to close, on every plan.
|
|
29
|
+
*
|
|
30
|
+
* Which emails wear it is the caller's decision: the built-in copy and the
|
|
31
|
+
* text-only messages do; a design the site owner published is theirs, placed
|
|
32
|
+
* header and footer included, and is sent as they built it.
|
|
33
|
+
*
|
|
34
|
+
* Pure: no reads, and a clock a test can replace.
|
|
35
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
36
|
+
import { resolveHostToken } from "./host-tokens.js";
|
|
37
|
+
/**
|
|
38
|
+
* A logo an inbox can draw, or undefined.
|
|
39
|
+
*
|
|
40
|
+
* An SVG is refused: Gmail and Outlook draw no SVG in mail, so a site whose
|
|
41
|
+
* logo is one would open every email with an empty box where its name
|
|
42
|
+
* belongs. The business name in bold is the header instead.
|
|
43
|
+
*/ function inboxLogo(url) {
|
|
44
|
+
if (!url) return undefined;
|
|
45
|
+
return /\.svg(?:[?#]|$)/i.test(url) || /^data:image\/svg/i.test(url) ? undefined : url;
|
|
46
|
+
}
|
|
47
|
+
/** See the module note. */ export function buildSiteEmailChrome(input) {
|
|
48
|
+
var _input_now, _resolveHostToken, _input_reason;
|
|
49
|
+
const { host } = input;
|
|
50
|
+
const now = (_input_now = input.now) != null ? _input_now : ()=>new Date();
|
|
51
|
+
const name = (_resolveHostToken = resolveHostToken('businessName', host)) != null ? _resolveHostToken : '';
|
|
52
|
+
const logo = inboxLogo(resolveHostToken('logo', host));
|
|
53
|
+
const url = resolveHostToken('url', host);
|
|
54
|
+
const supportEmail = resolveHostToken('supportEmail', host);
|
|
55
|
+
const address = resolveHostToken('address', host);
|
|
56
|
+
// The site's name is resolved here rather than left to the merge, so a
|
|
57
|
+
// site that has not set one reads "this site" where a merge would leave a
|
|
58
|
+
// hole in the sentence ("you placed an order with .").
|
|
59
|
+
const reason = String((_input_reason = input.reason) != null ? _input_reason : '').replace(/\{\{\s*host\.businessName\s*\}\}/g, name || 'this site').trim();
|
|
60
|
+
const footer = _extends({}, reason ? {
|
|
61
|
+
reason
|
|
62
|
+
} : {}, supportEmail ? {
|
|
63
|
+
support: {
|
|
64
|
+
label: supportEmail,
|
|
65
|
+
href: `mailto:${supportEmail}`
|
|
66
|
+
}
|
|
67
|
+
} : {}, name ? {
|
|
68
|
+
legal: `© ${now().getUTCFullYear()} ${name}` + (address ? ` · ${address.replace(/\s*\n\s*/g, ', ')}` : '')
|
|
69
|
+
} : {});
|
|
70
|
+
return {
|
|
71
|
+
header: _extends({}, logo ? {
|
|
72
|
+
logoUrl: logo
|
|
73
|
+
} : {}, {
|
|
74
|
+
logoAlt: name
|
|
75
|
+
}, url ? {
|
|
76
|
+
href: url
|
|
77
|
+
} : {}),
|
|
78
|
+
footer
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=site-email-chrome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/site-email-chrome.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The header and footer a SITE's email is drawn inside (AGL-3370).\n *\n * The site-side twin of `system-email-chrome.ts`. A site's mail to its own\n * customers (an order receipt, a member's password reset, a newsletter\n * confirmation) is the site's, so everything in the chrome is read off the\n * site: its logo or its name in bold, linked to its address; a footer of why\n * the reader is getting the mail, the site's support address, and a\n * `© {year} {business name} · {postal address}` line.\n *\n * Nothing here names the platform. A site's customer bought from the site,\n * not from Aglyn, and a vendor's name at the foot of a receipt is the same\n * leak white-label exists to close, on every plan.\n *\n * Which emails wear it is the caller's decision: the built-in copy and the\n * text-only messages do; a design the site owner published is theirs, placed\n * header and footer included, and is sent as they built it.\n *\n * Pure: no reads, and a clock a test can replace.\n */\n\nimport type {\n EmailChrome,\n EmailChromeFooter,\n} from '@aglyn/shared-util-email/email-render'\n\nimport { resolveHostToken, type HostTokenSource } from './host-tokens'\n\nexport interface SiteEmailChromeInput {\n /** The site's host document, or the fields of it the tokens read. */\n host: HostTokenSource | null | undefined\n /**\n * Why the recipient is getting this email, as a template: the renderer\n * substitutes `{{host.businessName}}` and the email's own tokens in it, as\n * it does in the body.\n */\n reason?: string\n /** The clock the copyright year is read from. */\n now?: () => Date\n}\n\n/**\n * A logo an inbox can draw, or undefined.\n *\n * An SVG is refused: Gmail and Outlook draw no SVG in mail, so a site whose\n * logo is one would open every email with an empty box where its name\n * belongs. The business name in bold is the header instead.\n */\nfunction inboxLogo(url: string | undefined): string | undefined {\n if (!url) return undefined\n return /\\.svg(?:[?#]|$)/i.test(url) || /^data:image\\/svg/i.test(url)\n ? undefined\n : url\n}\n\n/** See the module note. */\nexport function buildSiteEmailChrome(input: SiteEmailChromeInput): EmailChrome {\n const { host } = input\n const now = input.now ?? (() => new Date())\n const name = resolveHostToken('businessName', host) ?? ''\n const logo = inboxLogo(resolveHostToken('logo', host))\n const url = resolveHostToken('url', host)\n const supportEmail = resolveHostToken('supportEmail', host)\n const address = resolveHostToken('address', host)\n\n // The site's name is resolved here rather than left to the merge, so a\n // site that has not set one reads \"this site\" where a merge would leave a\n // hole in the sentence (\"you placed an order with .\").\n const reason = String(input.reason ?? '')\n .replace(/\\{\\{\\s*host\\.businessName\\s*\\}\\}/g, name || 'this site')\n .trim()\n const footer: EmailChromeFooter = {\n ...(reason ? { reason } : {}),\n ...(supportEmail\n ? { support: { label: supportEmail, href: `mailto:${supportEmail}` } }\n : {}),\n ...(name\n ? {\n legal:\n `© ${now().getUTCFullYear()} ${name}` +\n (address ? ` · ${address.replace(/\\s*\\n\\s*/g, ', ')}` : ''),\n }\n : {}),\n }\n\n return {\n header: {\n ...(logo ? { logoUrl: logo } : {}),\n logoAlt: name,\n ...(url ? { href: url } : {}),\n },\n footer,\n }\n}\n"],"names":["resolveHostToken","inboxLogo","url","undefined","test","buildSiteEmailChrome","input","host","now","Date","name","logo","supportEmail","address","reason","String","replace","trim","footer","support","label","href","legal","getUTCFullYear","header","logoUrl","logoAlt"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;CAmBC;AAOD,SAASA,gBAAgB,QAA8B,mBAAe;AAetE;;;;;;CAMC,GACD,SAASC,UAAUC,GAAuB;IACxC,IAAI,CAACA,KAAK,OAAOC;IACjB,OAAO,mBAAmBC,IAAI,CAACF,QAAQ,oBAAoBE,IAAI,CAACF,OAC5DC,YACAD;AACN;AAEA,yBAAyB,GACzB,OAAO,SAASG,qBAAqBC,KAA2B;QAElDA,YACCN,mBASSM;IAXtB,MAAM,EAAEC,IAAI,EAAE,GAAGD;IACjB,MAAME,OAAMF,aAAAA,MAAME,GAAG,YAATF,aAAc,IAAM,IAAIG;IACpC,MAAMC,QAAOV,oBAAAA,iBAAiB,gBAAgBO,iBAAjCP,oBAA0C;IACvD,MAAMW,OAAOV,UAAUD,iBAAiB,QAAQO;IAChD,MAAML,MAAMF,iBAAiB,OAAOO;IACpC,MAAMK,eAAeZ,iBAAiB,gBAAgBO;IACtD,MAAMM,UAAUb,iBAAiB,WAAWO;IAE5C,uEAAuE;IACvE,0EAA0E;IAC1E,uDAAuD;IACvD,MAAMO,SAASC,QAAOT,gBAAAA,MAAMQ,MAAM,YAAZR,gBAAgB,IACnCU,OAAO,CAAC,qCAAqCN,QAAQ,aACrDO,IAAI;IACP,MAAMC,SAA4B,aAC5BJ,SAAS;QAAEA;IAAO,IAAI,CAAC,GACvBF,eACA;QAAEO,SAAS;YAAEC,OAAOR;YAAcS,MAAM,CAAC,OAAO,EAAET,cAAc;QAAC;IAAE,IACnE,CAAC,GACDF,OACA;QACEY,OACE,CAAC,EAAE,EAAEd,MAAMe,cAAc,GAAG,CAAC,EAAEb,MAAM,GACpCG,CAAAA,UAAU,CAAC,GAAG,EAAEA,QAAQG,OAAO,CAAC,aAAa,OAAO,GAAG,EAAC;IAC7D,IACA,CAAC;IAGP,OAAO;QACLQ,QAAQ,aACFb,OAAO;YAAEc,SAASd;QAAK,IAAI,CAAC;YAChCe,SAAShB;WACLR,MAAM;YAAEmB,MAAMnB;QAAI,IAAI,CAAC;QAE7BgB;IACF;AACF"}
|
|
@@ -82,6 +82,12 @@ export interface SystemEmailBrandOptions {
|
|
|
82
82
|
* `http(s)` URL is linked; a `mailto:` home is no destination for a logo.
|
|
83
83
|
*/
|
|
84
84
|
brandHomeUrl?: string | null;
|
|
85
|
+
/**
|
|
86
|
+
* The org's resolved `primaryColor` (AGL-3370): a white-label send's
|
|
87
|
+
* buttons and links wear it, over the platform palette. Absent or blank is
|
|
88
|
+
* the platform's own accent.
|
|
89
|
+
*/
|
|
90
|
+
brandPrimaryColor?: string | null;
|
|
85
91
|
}
|
|
86
92
|
/**
|
|
87
93
|
* Whether a send goes out under the PLATFORM's brand, which is the only kind
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/system-email-chrome.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The header and footer a system email is drawn inside, for the brand it is\n * sent under (AGL-3322).\n *\n * A system email's built-in copy is a few lines of text, and only a staff\n * design can place the platform marketing site's email blocks. Without this,\n * the mail the platform sends most — every password reset, invite and alert\n * nobody has designed — would name its sender nowhere but the From line, and\n * tell the reader neither why it came nor where to get help. The renderer\n * draws whatever `EmailChrome` it is handed; this module decides what that\n * is, which comes down to one question: whose mail is this?\n *\n * ## The platform's own\n *\n * Its logo linked to its home page, and a footer of the email's reason, the\n * support link and a `© {year} {legal name} · {postal address}` line. Every\n * value is read from `platform-brand.ts`, so a renamed or self-hosted\n * deployment prints its own configuration: its logo or its name in bold, its\n * home page or no link, its address or none — and never ours. The Aglyn\n * defaults there apply only while the deployment is still called Aglyn.\n *\n * ## A white-label organization's\n *\n * Its own email logo, or its product name in bold; the reason, whose brand\n * tokens are the agency's; and a support link only when the agency set one.\n * No legal line: the only legal name and address this module knows are the\n * platform's, and the recipient is the agency's customer, who must not learn\n * the platform exists from the foot of an invite.\n *\n * Pure: no reads, no clock of its own that a test cannot replace.\n */\n\nimport type {\n EmailChrome,\n EmailChromeFooter,\n} from '@aglyn/shared-util-email/email-render'\nimport type { SystemEmailTemplateDefinition } from '@aglyn/shared-util-email/system-email-catalog'\n\nimport { brandMergeTokens } from './plan-entitlements'\nimport {\n PLATFORM_BRANDING_PROFILE,\n PLATFORM_BRAND_LEGAL_NAME,\n PLATFORM_BRAND_NAME,\n PLATFORM_EMAIL_LOGO_URL,\n PLATFORM_HOME_URL,\n PLATFORM_POSTAL_ADDRESS,\n} from './platform-brand'\n\n/**\n * `brand.*` tokens every system email resolves, UNDER whatever the caller\n * supplies (AGL-2139).\n *\n * The order is the whole safety property. A system email blanks any token\n * the caller did not provide, so a designed template written against\n * `{{brand.productName}}` would ship with a hole in the sentence at any send\n * site that forgot to pass one. Defaulting to the platform profile means the\n * worst case is the platform's own copy rather than a gap — and an\n * org-context sender that spreads `brandMergeTokens(branding)` into its merge\n * map wins, because a later key overwrites an earlier one.\n *\n * The platform-scoped senders — password reset, email verification, the\n * security alerts, the staff erasure-hold alert — genuinely have no org (each\n * says so at its own call site, and password reset deliberately refuses to\n * look one up because that would be an enumeration oracle). The platform IS\n * their brand, so these are the right answer for them.\n */\nexport const PLATFORM_BRAND_MERGE_TOKENS: Readonly<Record<string, string>> =\n brandMergeTokens(PLATFORM_BRANDING_PROFILE)\n\n/**\n * Brand inputs that are NOT merge tokens (AGL-2139).\n *\n * `brand.productName` and friends ride the merge map because a designer types\n * them into copy. The logo and the home page are structural — the header the\n * renderer draws, or does not — so they are options rather than tokens a\n * template could forget to place.\n */\nexport interface SystemEmailBrandOptions {\n /** The org's resolved `emailLogoUrl`; absent or blank is no org logo. */\n brandLogoUrl?: string | null\n /**\n * The org's resolved `homeUrl`: where a white-label header links. Only an\n * `http(s)` URL is linked; a `mailto:` home is no destination for a logo.\n */\n brandHomeUrl?: string | null\n}\n\n/**\n * Whether a send goes out under the PLATFORM's brand, which is the only kind\n * that may carry the platform's header and footer — the marketing site's\n * email blocks (AGL-3318), or the chrome {@link buildSystemEmailChrome} draws\n * in their place (AGL-3322).\n *\n * Those are the platform's logo, its address, its line telling the reader why\n * they get the mail. An agency on white-label sells its clients a product\n * with no vendor in it, and an invite or a usage summary sent to one of their\n * people reads as the agency's own mail. One platform footer in it names the\n * vendor the whole tier is bought to hide.\n *\n * Read off what the send is actually branded as, rather than asked of the\n * caller, so no sender can forget to say. An org-context sender spreads\n * `brandMergeTokens(resolveBrandingProfile(org))` over the defaults and passes\n * the profile's email logo. A white-label profile never inherits the\n * platform's support URL (AGL-2428), so its tokens differ from\n * {@link PLATFORM_BRAND_MERGE_TOKENS} on `brand.supportUrl` at least, and the\n * platform profile has no email logo of its own, so any logo is an org's. The\n * org-less senders, and every org without white-label, which resolves to the\n * platform profile itself, match the defaults exactly: those sends are the\n * platform's.\n */\nexport function isPlatformBrandedSend(\n merged: Readonly<Record<string, string>>,\n options: SystemEmailBrandOptions = {},\n): boolean {\n if (String(options.brandLogoUrl ?? '').trim()) return false\n return Object.entries(PLATFORM_BRAND_MERGE_TOKENS).every(\n ([token, value]) => merged[token] === value,\n )\n}\n\nexport interface SystemEmailChromeInput extends SystemEmailBrandOptions {\n /** The email being sent; its `footerReason` is the footer's first line. */\n definition: Pick<SystemEmailTemplateDefinition, 'footerReason'>\n /**\n * The send's merge map, platform brand tokens under the caller's — the map\n * the body renders with, which is what makes the reason's tokens resolve to\n * the same names the body uses.\n */\n merged: Readonly<Record<string, string>>\n /** The clock the copyright year is read from. */\n now?: () => Date\n}\n\n/** A non-empty trimmed string, else undefined. */\nconst clean = (value: string | null | undefined): string | undefined => {\n const text = typeof value === 'string' ? value.trim() : ''\n return text.length ? text : undefined\n}\n\n/**\n * The footer's support link, from the send's `brand.supportUrl`, or\n * undefined when it has none.\n *\n * Empty is a decision, not a gap: a white-label org that set no support URL\n * gets NO line rather than the platform's desk (AGL-2428), which cannot help\n * its customers and would name the vendor to them. A `mailto:` reads as the\n * mailbox, which is what the reader has to type into a client that does not\n * follow the link; a web page reads as an instruction.\n */\nfunction supportLink(\n url: string | undefined,\n): EmailChromeFooter['support'] | undefined {\n const href = clean(url)\n if (!href) return undefined\n if (/^mailto:/i.test(href)) {\n const address = clean(href.slice('mailto:'.length).split('?')[0])\n return address ? { label: address, href } : undefined\n }\n return { label: 'Get help', href }\n}\n\n/**\n * The chrome one system email is drawn inside, for the brand it is sent\n * under. See the module comment for the two shapes and why they differ.\n *\n * The reason and the other lines go out as templates: the renderer runs them\n * through the same merge substitution as the body, so `{{org.name}}` and\n * `{{brand.productName}}` read exactly as they do in the copy above them.\n */\nexport function buildSystemEmailChrome(\n input: SystemEmailChromeInput,\n): EmailChrome {\n const { definition, merged, brandLogoUrl, brandHomeUrl } = input\n const now = input.now ?? (() => new Date())\n const reason = clean(definition.footerReason)\n const support = supportLink(merged['brand.supportUrl'])\n const footer: EmailChromeFooter = {\n ...(reason ? { reason } : {}),\n ...(support ? { support } : {}),\n }\n\n if (isPlatformBrandedSend(merged, { brandLogoUrl })) {\n const legal =\n `© ${now().getUTCFullYear()} ${PLATFORM_BRAND_LEGAL_NAME}` +\n (PLATFORM_POSTAL_ADDRESS ? ` · ${PLATFORM_POSTAL_ADDRESS}` : '')\n return {\n header: {\n ...(PLATFORM_EMAIL_LOGO_URL ? { logoUrl: PLATFORM_EMAIL_LOGO_URL } : {}),\n logoAlt: PLATFORM_BRAND_NAME,\n ...(PLATFORM_HOME_URL ? { href: PLATFORM_HOME_URL } : {}),\n },\n footer: { ...footer, legal },\n }\n }\n\n const logoUrl = clean(brandLogoUrl)\n const home = clean(brandHomeUrl)\n return {\n header: {\n ...(logoUrl ? { logoUrl } : {}),\n logoAlt: merged['brand.productName'] ?? '',\n ...(home && /^https?:\\/\\//i.test(home) ? { href: home } : {}),\n },\n footer,\n }\n}\n"],"names":["brandMergeTokens","PLATFORM_BRANDING_PROFILE","PLATFORM_BRAND_LEGAL_NAME","PLATFORM_BRAND_NAME","PLATFORM_EMAIL_LOGO_URL","PLATFORM_HOME_URL","PLATFORM_POSTAL_ADDRESS","PLATFORM_BRAND_MERGE_TOKENS","isPlatformBrandedSend","merged","options","String","brandLogoUrl","trim","Object","entries","every","token","value","clean","text","length","undefined","supportLink","url","href","test","address","slice","split","label","buildSystemEmailChrome","input","definition","brandHomeUrl","now","Date","reason","footerReason","support","footer","legal","getUTCFullYear","header","logoUrl","logoAlt","home"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC;AAQD,SAASA,gBAAgB,QAAQ,yBAAqB;AACtD,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,mBAAmB,EACnBC,uBAAuB,EACvBC,iBAAiB,EACjBC,uBAAuB,QAClB,sBAAkB;AAEzB;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,MAAMC,8BACXP,iBAAiBC,2BAA0B;AAoB7C;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,SAASO,sBACdC,MAAwC,EACxCC,UAAmC,CAAC,CAAC;QAE1BA;IAAX,IAAIC,QAAOD,wBAAAA,QAAQE,YAAY,YAApBF,wBAAwB,IAAIG,IAAI,IAAI,OAAO;IACtD,OAAOC,OAAOC,OAAO,CAACR,6BAA6BS,KAAK,CACtD,CAAC,CAACC,OAAOC,MAAM,GAAKT,MAAM,CAACQ,MAAM,KAAKC;AAE1C;AAeA,gDAAgD,GAChD,MAAMC,QAAQ,CAACD;IACb,MAAME,OAAO,OAAOF,UAAU,WAAWA,MAAML,IAAI,KAAK;IACxD,OAAOO,KAAKC,MAAM,GAAGD,OAAOE;AAC9B;AAEA;;;;;;;;;CASC,GACD,SAASC,YACPC,GAAuB;IAEvB,MAAMC,OAAON,MAAMK;IACnB,IAAI,CAACC,MAAM,OAAOH;IAClB,IAAI,YAAYI,IAAI,CAACD,OAAO;QAC1B,MAAME,UAAUR,MAAMM,KAAKG,KAAK,CAAC,UAAUP,MAAM,EAAEQ,KAAK,CAAC,IAAI,CAAC,EAAE;QAChE,OAAOF,UAAU;YAAEG,OAAOH;YAASF;QAAK,IAAIH;IAC9C;IACA,OAAO;QAAEQ,OAAO;QAAYL;IAAK;AACnC;AAEA;;;;;;;CAOC,GACD,OAAO,SAASM,uBACdC,KAA6B;QAGjBA,YA2BCvB;IA5Bb,MAAM,EAAEwB,UAAU,EAAExB,MAAM,EAAEG,YAAY,EAAEsB,YAAY,EAAE,GAAGF;IAC3D,MAAMG,OAAMH,aAAAA,MAAMG,GAAG,YAATH,aAAc,IAAM,IAAII;IACpC,MAAMC,SAASlB,MAAMc,WAAWK,YAAY;IAC5C,MAAMC,UAAUhB,YAAYd,MAAM,CAAC,mBAAmB;IACtD,MAAM+B,SAA4B,aAC5BH,SAAS;QAAEA;IAAO,IAAI,CAAC,GACvBE,UAAU;QAAEA;IAAQ,IAAI,CAAC;IAG/B,IAAI/B,sBAAsBC,QAAQ;QAAEG;IAAa,IAAI;QACnD,MAAM6B,QACJ,CAAC,EAAE,EAAEN,MAAMO,cAAc,GAAG,CAAC,EAAExC,2BAA2B,GACzDI,CAAAA,0BAA0B,CAAC,GAAG,EAAEA,yBAAyB,GAAG,EAAC;QAChE,OAAO;YACLqC,QAAQ,aACFvC,0BAA0B;gBAAEwC,SAASxC;YAAwB,IAAI,CAAC;gBACtEyC,SAAS1C;eACLE,oBAAoB;gBAAEoB,MAAMpB;YAAkB,IAAI,CAAC;YAEzDmC,QAAQ,aAAKA;gBAAQC;;QACvB;IACF;IAEA,MAAMG,UAAUzB,MAAMP;IACtB,MAAMkC,OAAO3B,MAAMe;IACnB,OAAO;QACLS,QAAQ,aACFC,UAAU;YAAEA;QAAQ,IAAI,CAAC;YAC7BC,OAAO,GAAEpC,2BAAAA,MAAM,CAAC,oBAAoB,YAA3BA,2BAA+B;WACpCqC,QAAQ,gBAAgBpB,IAAI,CAACoB,QAAQ;YAAErB,MAAMqB;QAAK,IAAI,CAAC;QAE7DN;IACF;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/system-email-chrome.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The header and footer a system email is drawn inside, for the brand it is\n * sent under (AGL-3322).\n *\n * A system email's built-in copy is a few lines of text, and only a staff\n * design can place the platform marketing site's email blocks. Without this,\n * the mail the platform sends most — every password reset, invite and alert\n * nobody has designed — would name its sender nowhere but the From line, and\n * tell the reader neither why it came nor where to get help. The renderer\n * draws whatever `EmailChrome` it is handed; this module decides what that\n * is, which comes down to one question: whose mail is this?\n *\n * ## The platform's own\n *\n * Its logo linked to its home page, and a footer of the email's reason, the\n * support link and a `© {year} {legal name} · {postal address}` line. Every\n * value is read from `platform-brand.ts`, so a renamed or self-hosted\n * deployment prints its own configuration: its logo or its name in bold, its\n * home page or no link, its address or none — and never ours. The Aglyn\n * defaults there apply only while the deployment is still called Aglyn.\n *\n * ## A white-label organization's\n *\n * Its own email logo, or its product name in bold; the reason, whose brand\n * tokens are the agency's; and a support link only when the agency set one.\n * No legal line: the only legal name and address this module knows are the\n * platform's, and the recipient is the agency's customer, who must not learn\n * the platform exists from the foot of an invite.\n *\n * Pure: no reads, no clock of its own that a test cannot replace.\n */\n\nimport type {\n EmailChrome,\n EmailChromeFooter,\n} from '@aglyn/shared-util-email/email-render'\nimport type { SystemEmailTemplateDefinition } from '@aglyn/shared-util-email/system-email-catalog'\n\nimport { brandMergeTokens } from './plan-entitlements'\nimport {\n PLATFORM_BRANDING_PROFILE,\n PLATFORM_BRAND_LEGAL_NAME,\n PLATFORM_BRAND_NAME,\n PLATFORM_EMAIL_LOGO_URL,\n PLATFORM_HOME_URL,\n PLATFORM_POSTAL_ADDRESS,\n} from './platform-brand'\n\n/**\n * `brand.*` tokens every system email resolves, UNDER whatever the caller\n * supplies (AGL-2139).\n *\n * The order is the whole safety property. A system email blanks any token\n * the caller did not provide, so a designed template written against\n * `{{brand.productName}}` would ship with a hole in the sentence at any send\n * site that forgot to pass one. Defaulting to the platform profile means the\n * worst case is the platform's own copy rather than a gap — and an\n * org-context sender that spreads `brandMergeTokens(branding)` into its merge\n * map wins, because a later key overwrites an earlier one.\n *\n * The platform-scoped senders — password reset, email verification, the\n * security alerts, the staff erasure-hold alert — genuinely have no org (each\n * says so at its own call site, and password reset deliberately refuses to\n * look one up because that would be an enumeration oracle). The platform IS\n * their brand, so these are the right answer for them.\n */\nexport const PLATFORM_BRAND_MERGE_TOKENS: Readonly<Record<string, string>> =\n brandMergeTokens(PLATFORM_BRANDING_PROFILE)\n\n/**\n * Brand inputs that are NOT merge tokens (AGL-2139).\n *\n * `brand.productName` and friends ride the merge map because a designer types\n * them into copy. The logo and the home page are structural — the header the\n * renderer draws, or does not — so they are options rather than tokens a\n * template could forget to place.\n */\nexport interface SystemEmailBrandOptions {\n /** The org's resolved `emailLogoUrl`; absent or blank is no org logo. */\n brandLogoUrl?: string | null\n /**\n * The org's resolved `homeUrl`: where a white-label header links. Only an\n * `http(s)` URL is linked; a `mailto:` home is no destination for a logo.\n */\n brandHomeUrl?: string | null\n /**\n * The org's resolved `primaryColor` (AGL-3370): a white-label send's\n * buttons and links wear it, over the platform palette. Absent or blank is\n * the platform's own accent.\n */\n brandPrimaryColor?: string | null\n}\n\n/**\n * Whether a send goes out under the PLATFORM's brand, which is the only kind\n * that may carry the platform's header and footer — the marketing site's\n * email blocks (AGL-3318), or the chrome {@link buildSystemEmailChrome} draws\n * in their place (AGL-3322).\n *\n * Those are the platform's logo, its address, its line telling the reader why\n * they get the mail. An agency on white-label sells its clients a product\n * with no vendor in it, and an invite or a usage summary sent to one of their\n * people reads as the agency's own mail. One platform footer in it names the\n * vendor the whole tier is bought to hide.\n *\n * Read off what the send is actually branded as, rather than asked of the\n * caller, so no sender can forget to say. An org-context sender spreads\n * `brandMergeTokens(resolveBrandingProfile(org))` over the defaults and passes\n * the profile's email logo. A white-label profile never inherits the\n * platform's support URL (AGL-2428), so its tokens differ from\n * {@link PLATFORM_BRAND_MERGE_TOKENS} on `brand.supportUrl` at least, and the\n * platform profile has no email logo of its own, so any logo is an org's. The\n * org-less senders, and every org without white-label, which resolves to the\n * platform profile itself, match the defaults exactly: those sends are the\n * platform's.\n */\nexport function isPlatformBrandedSend(\n merged: Readonly<Record<string, string>>,\n options: SystemEmailBrandOptions = {},\n): boolean {\n if (String(options.brandLogoUrl ?? '').trim()) return false\n return Object.entries(PLATFORM_BRAND_MERGE_TOKENS).every(\n ([token, value]) => merged[token] === value,\n )\n}\n\nexport interface SystemEmailChromeInput extends SystemEmailBrandOptions {\n /** The email being sent; its `footerReason` is the footer's first line. */\n definition: Pick<SystemEmailTemplateDefinition, 'footerReason'>\n /**\n * The send's merge map, platform brand tokens under the caller's — the map\n * the body renders with, which is what makes the reason's tokens resolve to\n * the same names the body uses.\n */\n merged: Readonly<Record<string, string>>\n /** The clock the copyright year is read from. */\n now?: () => Date\n}\n\n/** A non-empty trimmed string, else undefined. */\nconst clean = (value: string | null | undefined): string | undefined => {\n const text = typeof value === 'string' ? value.trim() : ''\n return text.length ? text : undefined\n}\n\n/**\n * The footer's support link, from the send's `brand.supportUrl`, or\n * undefined when it has none.\n *\n * Empty is a decision, not a gap: a white-label org that set no support URL\n * gets NO line rather than the platform's desk (AGL-2428), which cannot help\n * its customers and would name the vendor to them. A `mailto:` reads as the\n * mailbox, which is what the reader has to type into a client that does not\n * follow the link; a web page reads as an instruction.\n */\nfunction supportLink(\n url: string | undefined,\n): EmailChromeFooter['support'] | undefined {\n const href = clean(url)\n if (!href) return undefined\n if (/^mailto:/i.test(href)) {\n const address = clean(href.slice('mailto:'.length).split('?')[0])\n return address ? { label: address, href } : undefined\n }\n return { label: 'Get help', href }\n}\n\n/**\n * The chrome one system email is drawn inside, for the brand it is sent\n * under. See the module comment for the two shapes and why they differ.\n *\n * The reason and the other lines go out as templates: the renderer runs them\n * through the same merge substitution as the body, so `{{org.name}}` and\n * `{{brand.productName}}` read exactly as they do in the copy above them.\n */\nexport function buildSystemEmailChrome(\n input: SystemEmailChromeInput,\n): EmailChrome {\n const { definition, merged, brandLogoUrl, brandHomeUrl } = input\n const now = input.now ?? (() => new Date())\n const reason = clean(definition.footerReason)\n const support = supportLink(merged['brand.supportUrl'])\n const footer: EmailChromeFooter = {\n ...(reason ? { reason } : {}),\n ...(support ? { support } : {}),\n }\n\n if (isPlatformBrandedSend(merged, { brandLogoUrl })) {\n const legal =\n `© ${now().getUTCFullYear()} ${PLATFORM_BRAND_LEGAL_NAME}` +\n (PLATFORM_POSTAL_ADDRESS ? ` · ${PLATFORM_POSTAL_ADDRESS}` : '')\n return {\n header: {\n ...(PLATFORM_EMAIL_LOGO_URL ? { logoUrl: PLATFORM_EMAIL_LOGO_URL } : {}),\n logoAlt: PLATFORM_BRAND_NAME,\n ...(PLATFORM_HOME_URL ? { href: PLATFORM_HOME_URL } : {}),\n },\n footer: { ...footer, legal },\n }\n }\n\n const logoUrl = clean(brandLogoUrl)\n const home = clean(brandHomeUrl)\n return {\n header: {\n ...(logoUrl ? { logoUrl } : {}),\n logoAlt: merged['brand.productName'] ?? '',\n ...(home && /^https?:\\/\\//i.test(home) ? { href: home } : {}),\n },\n footer,\n }\n}\n"],"names":["brandMergeTokens","PLATFORM_BRANDING_PROFILE","PLATFORM_BRAND_LEGAL_NAME","PLATFORM_BRAND_NAME","PLATFORM_EMAIL_LOGO_URL","PLATFORM_HOME_URL","PLATFORM_POSTAL_ADDRESS","PLATFORM_BRAND_MERGE_TOKENS","isPlatformBrandedSend","merged","options","String","brandLogoUrl","trim","Object","entries","every","token","value","clean","text","length","undefined","supportLink","url","href","test","address","slice","split","label","buildSystemEmailChrome","input","definition","brandHomeUrl","now","Date","reason","footerReason","support","footer","legal","getUTCFullYear","header","logoUrl","logoAlt","home"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BC;AAQD,SAASA,gBAAgB,QAAQ,yBAAqB;AACtD,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,mBAAmB,EACnBC,uBAAuB,EACvBC,iBAAiB,EACjBC,uBAAuB,QAClB,sBAAkB;AAEzB;;;;;;;;;;;;;;;;;CAiBC,GACD,OAAO,MAAMC,8BACXP,iBAAiBC,2BAA0B;AA0B7C;;;;;;;;;;;;;;;;;;;;;;CAsBC,GACD,OAAO,SAASO,sBACdC,MAAwC,EACxCC,UAAmC,CAAC,CAAC;QAE1BA;IAAX,IAAIC,QAAOD,wBAAAA,QAAQE,YAAY,YAApBF,wBAAwB,IAAIG,IAAI,IAAI,OAAO;IACtD,OAAOC,OAAOC,OAAO,CAACR,6BAA6BS,KAAK,CACtD,CAAC,CAACC,OAAOC,MAAM,GAAKT,MAAM,CAACQ,MAAM,KAAKC;AAE1C;AAeA,gDAAgD,GAChD,MAAMC,QAAQ,CAACD;IACb,MAAME,OAAO,OAAOF,UAAU,WAAWA,MAAML,IAAI,KAAK;IACxD,OAAOO,KAAKC,MAAM,GAAGD,OAAOE;AAC9B;AAEA;;;;;;;;;CASC,GACD,SAASC,YACPC,GAAuB;IAEvB,MAAMC,OAAON,MAAMK;IACnB,IAAI,CAACC,MAAM,OAAOH;IAClB,IAAI,YAAYI,IAAI,CAACD,OAAO;QAC1B,MAAME,UAAUR,MAAMM,KAAKG,KAAK,CAAC,UAAUP,MAAM,EAAEQ,KAAK,CAAC,IAAI,CAAC,EAAE;QAChE,OAAOF,UAAU;YAAEG,OAAOH;YAASF;QAAK,IAAIH;IAC9C;IACA,OAAO;QAAEQ,OAAO;QAAYL;IAAK;AACnC;AAEA;;;;;;;CAOC,GACD,OAAO,SAASM,uBACdC,KAA6B;QAGjBA,YA2BCvB;IA5Bb,MAAM,EAAEwB,UAAU,EAAExB,MAAM,EAAEG,YAAY,EAAEsB,YAAY,EAAE,GAAGF;IAC3D,MAAMG,OAAMH,aAAAA,MAAMG,GAAG,YAATH,aAAc,IAAM,IAAII;IACpC,MAAMC,SAASlB,MAAMc,WAAWK,YAAY;IAC5C,MAAMC,UAAUhB,YAAYd,MAAM,CAAC,mBAAmB;IACtD,MAAM+B,SAA4B,aAC5BH,SAAS;QAAEA;IAAO,IAAI,CAAC,GACvBE,UAAU;QAAEA;IAAQ,IAAI,CAAC;IAG/B,IAAI/B,sBAAsBC,QAAQ;QAAEG;IAAa,IAAI;QACnD,MAAM6B,QACJ,CAAC,EAAE,EAAEN,MAAMO,cAAc,GAAG,CAAC,EAAExC,2BAA2B,GACzDI,CAAAA,0BAA0B,CAAC,GAAG,EAAEA,yBAAyB,GAAG,EAAC;QAChE,OAAO;YACLqC,QAAQ,aACFvC,0BAA0B;gBAAEwC,SAASxC;YAAwB,IAAI,CAAC;gBACtEyC,SAAS1C;eACLE,oBAAoB;gBAAEoB,MAAMpB;YAAkB,IAAI,CAAC;YAEzDmC,QAAQ,aAAKA;gBAAQC;;QACvB;IACF;IAEA,MAAMG,UAAUzB,MAAMP;IACtB,MAAMkC,OAAO3B,MAAMe;IACnB,OAAO;QACLS,QAAQ,aACFC,UAAU;YAAEA;QAAQ,IAAI,CAAC;YAC7BC,OAAO,GAAEpC,2BAAAA,MAAM,CAAC,oBAAoB,YAA3BA,2BAA+B;WACpCqC,QAAQ,gBAAgBpB,IAAI,CAACoB,QAAQ;YAAErB,MAAMqB;QAAK,IAAI,CAAC;QAE7DN;IACF;AACF"}
|
|
@@ -51,6 +51,13 @@ export interface PluginRecordEmailStateRequest {
|
|
|
51
51
|
state: EmailState;
|
|
52
52
|
/** Write the state whatever the record holds — a release. */
|
|
53
53
|
force?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Take the verdict back instead of writing it: a record whose standing
|
|
56
|
+
* state has `state.status` loses it, and any other record is left alone.
|
|
57
|
+
* The deliverability check's way back from `undeliverable` once the
|
|
58
|
+
* domain takes mail (AGL-3328); it never clears a verdict it did not give.
|
|
59
|
+
*/
|
|
60
|
+
withdraw?: boolean;
|
|
54
61
|
}
|
|
55
62
|
export interface PluginRecordEmailStateReport {
|
|
56
63
|
/** Records whose state moved. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-record-email-state.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { EmailState } from '../app-utils/email-state'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * The verdict on an address, written onto the records that carry it\n * (AGL-3245).\n *\n * The senders keep their lists — the platform's suppression list, a site's\n * suppressions, an organization's do-not-contact list — and those are what\n * a send consults. The record a person reads is the record system's: a\n * lead, a contact, whatever the plugin that keeps the workspace's records\n * calls them. A sender must not write that plugin's documents itself, nor\n * import it; so the record system registers a WRITER here, and a sender\n * that filed a verdict on a list says so through it.\n *\n * The writer finds every record the address is — the plugin knows its own\n * storage and its own index — and stamps `emailState` on each, keeping the\n * stronger verdict (`nextEmailState`). A single-implementation contract: a\n * workspace keeps one record system.\n *\n * ## The caller proves who is asking\n *\n * Like the timeline seam, the registry authenticates nobody. The caller\n * decides, in its own terms and before it asks, that the verdict is the\n * workspace's to write: an address it sent to, a list it wrote.\n */\n\nexport interface PluginRecordEmailStateRequest {\n /** The organization whose records are stamped. */\n orgId?: string\n /**\n * Or the site the send left from, when the caller knows only that — a\n * campaign webhook, an unsubscribe link. The writer reads the\n * organization off it.\n */\n hostId?: string\n email: string\n state: EmailState\n /** Write the state whatever the record holds — a release. */\n force?: boolean\n}\n\nexport interface PluginRecordEmailStateReport {\n /** Records whose state moved. */\n records: number\n}\n\nexport interface PluginRecordEmailStateWriter {\n /** Never throws: a list is the control, the stamp is what a person reads. */\n stamp(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport>\n}\n\nexport const PLUGIN_RECORD_EMAIL_STATE = definePluginServiceContract<PluginRecordEmailStateWriter>(\n 'core.record-email-state',\n { multiple: false },\n)\n\n/**\n * Registers the workspace's record email-state writer. The owner is the\n * loader's marker when a register fn is running, else `options.pluginId`;\n * with neither the registration throws, and a second plugin's writer is\n * refused naming both — the incumbent keeps serving.\n */\nexport function registerPluginRecordEmailStateWriter(\n writer: PluginRecordEmailStateWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_RECORD_EMAIL_STATE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\nexport interface ResolvedPluginRecordEmailStateWriter {\n /** The plugin that keeps the records. */\n pluginId: string\n writer: PluginRecordEmailStateWriter\n}\n\n/** The writer, with its owner, or `null` when no plugin keeps records here. */\nexport function pluginRecordEmailStateWriter(): ResolvedPluginRecordEmailStateWriter | null {\n const entry = resolvePluginServices(PLUGIN_RECORD_EMAIL_STATE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n\n/**\n * Stamps the verdict through whichever plugin keeps the records, or answers\n * `null` when none does. Never throws: every caller is a sender for whom\n * the stamp is a courtesy beside the list it already wrote.\n */\nexport async function stampRecordEmailState(\n request: PluginRecordEmailStateRequest,\n): Promise<PluginRecordEmailStateReport | null> {\n const resolved = pluginRecordEmailStateWriter()\n if (!resolved) return null\n try {\n return await resolved.writer.stamp(request)\n } catch (error) {\n console.error('[record-email-state] the record system could not stamp the address', error)\n return null\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_EMAIL_STATE","multiple","registerPluginRecordEmailStateWriter","writer","options","pluginId","pluginRecordEmailStateWriter","entry","impl","stampRecordEmailState","request","resolved","stamp","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/plugin-manager/plugin-record-email-state.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { EmailState } from '../app-utils/email-state'\nimport {\n definePluginServiceContract,\n registerPluginService,\n resolvePluginServices,\n} from './plugin-services'\n\n/**\n * The verdict on an address, written onto the records that carry it\n * (AGL-3245).\n *\n * The senders keep their lists — the platform's suppression list, a site's\n * suppressions, an organization's do-not-contact list — and those are what\n * a send consults. The record a person reads is the record system's: a\n * lead, a contact, whatever the plugin that keeps the workspace's records\n * calls them. A sender must not write that plugin's documents itself, nor\n * import it; so the record system registers a WRITER here, and a sender\n * that filed a verdict on a list says so through it.\n *\n * The writer finds every record the address is — the plugin knows its own\n * storage and its own index — and stamps `emailState` on each, keeping the\n * stronger verdict (`nextEmailState`). A single-implementation contract: a\n * workspace keeps one record system.\n *\n * ## The caller proves who is asking\n *\n * Like the timeline seam, the registry authenticates nobody. The caller\n * decides, in its own terms and before it asks, that the verdict is the\n * workspace's to write: an address it sent to, a list it wrote.\n */\n\nexport interface PluginRecordEmailStateRequest {\n /** The organization whose records are stamped. */\n orgId?: string\n /**\n * Or the site the send left from, when the caller knows only that — a\n * campaign webhook, an unsubscribe link. The writer reads the\n * organization off it.\n */\n hostId?: string\n email: string\n state: EmailState\n /** Write the state whatever the record holds — a release. */\n force?: boolean\n /**\n * Take the verdict back instead of writing it: a record whose standing\n * state has `state.status` loses it, and any other record is left alone.\n * The deliverability check's way back from `undeliverable` once the\n * domain takes mail (AGL-3328); it never clears a verdict it did not give.\n */\n withdraw?: boolean\n}\n\nexport interface PluginRecordEmailStateReport {\n /** Records whose state moved. */\n records: number\n}\n\nexport interface PluginRecordEmailStateWriter {\n /** Never throws: a list is the control, the stamp is what a person reads. */\n stamp(request: PluginRecordEmailStateRequest): Promise<PluginRecordEmailStateReport>\n}\n\nexport const PLUGIN_RECORD_EMAIL_STATE = definePluginServiceContract<PluginRecordEmailStateWriter>(\n 'core.record-email-state',\n { multiple: false },\n)\n\n/**\n * Registers the workspace's record email-state writer. The owner is the\n * loader's marker when a register fn is running, else `options.pluginId`;\n * with neither the registration throws, and a second plugin's writer is\n * refused naming both — the incumbent keeps serving.\n */\nexport function registerPluginRecordEmailStateWriter(\n writer: PluginRecordEmailStateWriter,\n options?: { pluginId?: string },\n): void {\n registerPluginService(PLUGIN_RECORD_EMAIL_STATE, writer, {\n ...(options?.pluginId ? { pluginId: options.pluginId } : {}),\n })\n}\n\nexport interface ResolvedPluginRecordEmailStateWriter {\n /** The plugin that keeps the records. */\n pluginId: string\n writer: PluginRecordEmailStateWriter\n}\n\n/** The writer, with its owner, or `null` when no plugin keeps records here. */\nexport function pluginRecordEmailStateWriter(): ResolvedPluginRecordEmailStateWriter | null {\n const entry = resolvePluginServices(PLUGIN_RECORD_EMAIL_STATE)[0]\n return entry ? { pluginId: entry.pluginId, writer: entry.impl } : null\n}\n\n/**\n * Stamps the verdict through whichever plugin keeps the records, or answers\n * `null` when none does. Never throws: every caller is a sender for whom\n * the stamp is a courtesy beside the list it already wrote.\n */\nexport async function stampRecordEmailState(\n request: PluginRecordEmailStateRequest,\n): Promise<PluginRecordEmailStateReport | null> {\n const resolved = pluginRecordEmailStateWriter()\n if (!resolved) return null\n try {\n return await resolved.writer.stamp(request)\n } catch (error) {\n console.error('[record-email-state] the record system could not stamp the address', error)\n return null\n }\n}\n"],"names":["definePluginServiceContract","registerPluginService","resolvePluginServices","PLUGIN_RECORD_EMAIL_STATE","multiple","registerPluginRecordEmailStateWriter","writer","options","pluginId","pluginRecordEmailStateWriter","entry","impl","stampRecordEmailState","request","resolved","stamp","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,2BAA2B,EAC3BC,qBAAqB,EACrBC,qBAAqB,QAChB,uBAAmB;AA0D1B,OAAO,MAAMC,4BAA4BH,4BACvC,2BACA;IAAEI,UAAU;AAAM,GACnB;AAED;;;;;CAKC,GACD,OAAO,SAASC,qCACdC,MAAoC,EACpCC,OAA+B;IAE/BN,sBAAsBE,2BAA2BG,QAAQ,aACnDC,CAAAA,2BAAAA,QAASC,QAAQ,IAAG;QAAEA,UAAUD,QAAQC,QAAQ;IAAC,IAAI,CAAC;AAE9D;AAQA,6EAA6E,GAC7E,OAAO,SAASC;IACd,MAAMC,QAAQR,sBAAsBC,0BAA0B,CAAC,EAAE;IACjE,OAAOO,QAAQ;QAAEF,UAAUE,MAAMF,QAAQ;QAAEF,QAAQI,MAAMC,IAAI;IAAC,IAAI;AACpE;AAEA;;;;CAIC,GACD,OAAO,eAAeC,sBACpBC,OAAsC;IAEtC,MAAMC,WAAWL;IACjB,IAAI,CAACK,UAAU,OAAO;IACtB,IAAI;QACF,OAAO,MAAMA,SAASR,MAAM,CAACS,KAAK,CAACF;IACrC,EAAE,OAAOG,OAAO;QACdC,QAAQD,KAAK,CAAC,sEAAsEA;QACpF,OAAO;IACT;AACF"}
|