@aglyn/shared-util-email 1.0.0-beta.161 → 1.0.0-beta.164

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-util-email",
3
- "version": "1.0.0-beta.161",
3
+ "version": "1.0.0-beta.164",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,8 +25,8 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-enums": "1.0.0-beta.161",
29
- "@aglyn/shared-util-http": "1.0.0-beta.161",
28
+ "@aglyn/shared-data-enums": "1.0.0-beta.164",
29
+ "@aglyn/shared-util-http": "1.0.0-beta.164",
30
30
  "@msgpack/msgpack": "^3.1.3",
31
31
  "@swc/helpers": "0.5.23"
32
32
  },
@@ -33,7 +33,15 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
33
33
  * Mirrors `PLATFORM_SUPPORT_URL`'s precedence, including the step that makes
34
34
  * the operator identity sufficient on its own: a configured support URL, else
35
35
  * the operator's support mailbox as a `mailto:`, else ours.
36
- */ const SAMPLE_SUPPORT_URL = (process.env.NEXT_PUBLIC_PLATFORM_SUPPORT_URL || '').trim() || ((process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim() ? `mailto:${(process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim()}` : 'https://aglyn.com/support');
36
+ *
37
+ * The last step is built from `SAMPLE_CONSOLE_ORIGIN` rather than written out
38
+ * (AGL-3262). The support entry point is a CONSOLE route — `/support`
39
+ * resolves the workspace from the session (AGL-3265) — so the origin it hangs
40
+ * off is the one directly above, and an operator who set
41
+ * `NEXT_PUBLIC_CONSOLE_URL` gets their own console in the sample instead of
42
+ * ours. The literal it replaces was `aglyn.com/support`, which answers 404 and
43
+ * never existed.
44
+ */ const SAMPLE_SUPPORT_URL = (process.env.NEXT_PUBLIC_PLATFORM_SUPPORT_URL || '').trim() || ((process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim() ? `mailto:${(process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim()}` : `${SAMPLE_CONSOLE_ORIGIN}/support`);
37
45
  // Deep import, NOT the barrel (AGL-1151): `@aglyn/shared-data-enums` re-exports
38
46
  // `firebase-auth`, whose `AuthErrorCodes` is a VALUE import of `firebase/auth`.
39
47
  // This module is reachable from the email plugin's site half, which a PUBLISHED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/system-email-catalog.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 * Origins used as merge-token SAMPLE values in the staff email designer\n * (AGL-2202).\n *\n * These are never sent — they render in the template preview so an author can\n * see what `{{console.url}}` will become. That still made them wrong on a\n * self-host install: the preview showed Aglyn's console as the example for a\n * link that will resolve to the operator's, which is exactly the sort of\n * quiet mis-teaching that ends up copied into a hand-written template.\n *\n * Read from the environment rather than through `@aglyn/aglyn`'s\n * `platform-brand` / `constants`: `shared-util-email` is tagged `scope:shared`\n * and `@nx/enforce-module-boundaries` refuses the edge. Same defaults, so a\n * deployment that configures nothing sees exactly what it saw before.\n */\nconst SAMPLE_CONSOLE_ORIGIN: string =\n (process.env.NEXT_PUBLIC_CONSOLE_URL || '').trim().replace(/\\/+$/, '') ||\n 'https://app.aglyn.com'\n\n/**\n * Mirrors `PLATFORM_SUPPORT_URL`'s precedence, including the step that makes\n * the operator identity sufficient on its own: a configured support URL, else\n * the operator's support mailbox as a `mailto:`, else ours.\n */\nconst SAMPLE_SUPPORT_URL: string =\n (process.env.NEXT_PUBLIC_PLATFORM_SUPPORT_URL || '').trim() ||\n ((process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim()\n ? `mailto:${(process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim()}`\n : 'https://aglyn.com/support')\n\n// Deep import, NOT the barrel (AGL-1151): `@aglyn/shared-data-enums` re-exports\n// `firebase-auth`, whose `AuthErrorCodes` is a VALUE import of `firebase/auth`.\n// This module is reachable from the email plugin's site half, which a PUBLISHED\n// tenant page loads, so the barrel put the Firebase auth client into that\n// page's chunks for one brand constant.\nimport { BRAND } from '@aglyn/shared-data-enums/aglyn'\nimport { EMAIL_NODE_ROOT_ID } from './email-render'\n\n/**\n * Who actually puts the message on the wire.\n *\n * `resend` — Aglyn composes and sends it, so a besigner template can change\n * what the recipient sees.\n *\n * `firebase` — Firebase Auth sends it from its own templates, configured in\n * the Firebase console. **A besigner template cannot affect these at all**\n * because the sender owns the copy (AGL-767).\n *\n * `stripe` — Stripe sends it on Aglyn's behalf from the Dashboard's\n * Customer-emails settings (billing receipts, dunning, refunds, expiring-card\n * notices). Aglyn never composes these, so like `firebase` they cannot be\n * designed here (AGL-767).\n *\n * The non-`resend` entries are listed so staff can see the full set of mail\n * the product sends and where each one is controlled; the UI must present\n * them as non-editable rather than offering an editor that silently does\n * nothing.\n */\n/**\n * Who actually puts the message on the wire.\n *\n * `'firebase'` was removed by AGL-1112, once nothing was left that Firebase\n * Auth composed. Deliberately not kept \"just in case\": with the variant gone,\n * reintroducing a Firebase-sent email is a compile error rather than a row in\n * a table that quietly stops being editable. The staff editor is wired to\n * `'resend'`, so any other value silently produces a template that renders\n * nothing — which is exactly how the `[aglyn.io]` subject survived so long.\n */\nexport type SystemEmailDeliveredBy = 'resend' | 'stripe'\n\nexport interface SystemEmailMergeToken {\n /** Token as written in the template, without braces. */\n name: string\n description: string\n /** Stand-in used by the staff preview. */\n sample: string\n}\n\n/**\n * One block of a template's starting content (AGL-764). Declarative on\n * purpose: the catalog stays a dependency-free data module, and the console\n * turns these into email-plugin nodes (`emailSection` → `emailText`/\n * `emailButton`) when it seeds the first version. Mirror the copy the send\n * site uses today, with the same `{{tokens}}`, so a staffer who opens the\n * editor starts from what recipients actually get rather than a blank canvas.\n */\nexport type SystemEmailDefaultBlock =\n | {\n block: 'text'\n text: string\n variant?: 'heading' | 'subheading' | 'body' | 'caption'\n }\n | { block: 'button'; label: string; href: string }\n\nexport interface SystemEmailTemplateDefinition {\n /** Stable storage key — the Firestore document id. Never rename. */\n key: string\n name: string\n description: string\n deliveredBy: SystemEmailDeliveredBy\n /** Subject used when no template has been published. */\n defaultSubject: string\n mergeTokens: SystemEmailMergeToken[]\n /**\n * Starting content for the first version staff design (AGL-764). Every\n * `resend` template should carry one; a `firebase` template has no editor\n * so it needs none. Absent → the editor seeds a minimal placeholder.\n */\n defaultBody?: readonly SystemEmailDefaultBlock[]\n /**\n * Where the fallback copy lives, for staff wondering what recipients get\n * today. Informational — nothing reads it at runtime.\n */\n source: string\n}\n\n/**\n * Every system email Aglyn sends.\n *\n * Deliberately **code-defined and fixed**: staff edit the system emails that\n * exist, they do not create or delete them. Adding one is a code change,\n * which is what keeps this list from drifting into advertising an email the\n * product never actually sends — the failure mode a Firestore-backed list\n * would have.\n *\n * The staff page renders from this registry, not from Firestore, so an email\n * with no template designed yet still appears (as \"Using default\").\n */\n/**\n * Brand tokens EVERY system email resolves (AGL-2139).\n *\n * White-label used to invert precisely when staff published a template: the\n * designed template wins over each sender's branded fallback, and the copy\n * here hard-coded \"Aglyn\". Nor was there anything to design against — the\n * merge maps carried `org.name`, `invite.role`, `signInUrl` and friends and\n * nothing about the brand, while `blankUnresolvedTokens` DELETES any token\n * the caller did not supply, so a designer who typed `{{brand.productName}}`\n * would have shipped an email with a hole in the sentence.\n *\n * Declared once and appended to every template's own list rather than copied\n * into each: 13 hand-maintained copies is how the token list falls behind the\n * copy, and the editor renders this list as the palette a designer picks\n * from — a token missing here is a token nobody knows exists.\n *\n * The samples are the DEPLOYMENT's own values (`BRAND.ORG_NAME`, AGL-2319),\n * because that is what an unbranded org genuinely renders — on a self-host\n * install a hardcoded \"Aglyn\" would preview a brand that appears nowhere in\n * the mail the designer is editing.\n */\nexport const BRAND_MERGE_TOKENS: readonly SystemEmailMergeToken[] = [\n {\n name: 'brand.productName',\n description: \"The sender's product name — the org's own on white-label\",\n sample: BRAND.ORG_NAME,\n },\n {\n name: 'brand.fromName',\n description: 'Sender display name the mail is delivered under',\n sample: BRAND.ORG_NAME,\n },\n {\n name: 'brand.supportUrl',\n description: 'Where the recipient should go for help',\n sample: SAMPLE_SUPPORT_URL,\n },\n]\n\n// NOT declared: a `brand.logoUrl` token. The logo is STRUCTURAL — the\n// renderer emits it as a header row above the designed body, or emits\n// nothing — so a designer never places it, and a palette entry for it would\n// need a sample URL that a staff test-send would render as a broken image.\n// The three tokens above are the ones that belong in copy.\n\nconst BASE_SYSTEM_EMAIL_TEMPLATES: readonly SystemEmailTemplateDefinition[] =\n [\n {\n key: 'org-invite',\n name: 'Organization invite',\n description:\n 'Sent when an admin invites someone to an organization from the ' +\n 'Team page.',\n deliveredBy: 'resend',\n defaultSubject: \"You've been invited to {{org.name}} on {{brand.productName}}\",\n mergeTokens: [\n {\n name: 'org.name',\n description: 'Name of the inviting organization',\n sample: 'Test Org',\n },\n {\n name: 'invite.role',\n description: 'Role the person is invited as',\n sample: 'editor',\n },\n {\n name: 'signInUrl',\n description: 'Console URL to sign in and accept',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in invites/route.ts.\n defaultBody: [\n {\n block: 'text',\n text: \"You've been invited to join {{org.name}} as {{invite.role}}.\",\n variant: 'body',\n },\n {\n block: 'text',\n text:\n 'Sign in with this email address and accept the invite from ' +\n 'your dashboard.',\n variant: 'body',\n },\n { block: 'button', label: 'Sign in', href: '{{signInUrl}}' },\n ],\n source: 'apps/console/app/api/orgs/invites/route.ts',\n },\n {\n key: 'usage-summary',\n name: 'Monthly usage summary',\n description:\n 'Monthly per-organization usage and metered-cost summary, sent by ' +\n 'the scheduled job.',\n deliveredBy: 'resend',\n defaultSubject: 'Your {{brand.productName}} usage summary for {{month}}',\n mergeTokens: [\n {\n name: 'month',\n description: 'Billing month as YYYY-MM',\n sample: '2026-06',\n },\n {\n name: 'org.name',\n description: 'Organization the summary is for',\n sample: 'Test Org',\n },\n {\n name: 'usage.summary',\n description: 'Pre-formatted usage lines',\n sample: 'Page views: 12,400',\n },\n ],\n // Mirrors the summary lines assembled in usage-email/route.ts.\n defaultBody: [\n { block: 'text', text: 'Usage summary for {{org.name}}', variant: 'heading' },\n {\n block: 'text',\n text: 'Here is your {{brand.productName}} usage summary for {{month}}.',\n variant: 'body',\n },\n { block: 'text', text: '{{usage.summary}}', variant: 'body' },\n ],\n source: 'apps/console/app/api/billing/usage-email/route.ts',\n },\n {\n key: 'erasure-hold-alert',\n name: 'Erasure hold staff alert',\n description:\n 'Internal alert to STAFF_ALERT_EMAIL when GDPR erasure requests ' +\n 'pass their 7-day hold.',\n deliveredBy: 'resend',\n defaultSubject: '{{count}} erasure request(s) past the 7-day hold',\n mergeTokens: [\n {\n name: 'count',\n description: 'How many organizations are past the hold',\n sample: '3',\n },\n {\n name: 'orgs.list',\n description: 'The organizations, one per line',\n sample: '- Test Org (org_123), requested 2026-07-01',\n },\n ],\n // Mirrors the staff-alert text in audit-archive/route.ts.\n defaultBody: [\n {\n block: 'text',\n text:\n 'These organizations are past their GDPR erasure hold. Run ' +\n 'tools/scripts/erase-tenant.mjs to hard-delete. No copy is kept:',\n variant: 'body',\n },\n { block: 'text', text: '{{orgs.list}}', variant: 'body' },\n ],\n source: 'apps/console/app/api/admin/audit-archive/route.ts',\n },\n {\n key: 'welcome',\n name: 'Welcome',\n description:\n 'Sent to a new owner the first time they create an organization ' +\n '(not on every organization they later create).',\n deliveredBy: 'resend',\n defaultSubject: 'Welcome to {{brand.productName}}',\n mergeTokens: [\n {\n name: 'name',\n description: \"The new owner's display name\",\n sample: 'Alex',\n },\n {\n name: 'org.name',\n description: 'The organization they just created',\n sample: 'Test Org',\n },\n {\n name: 'consoleUrl',\n description: 'Console dashboard URL',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in orgs/create/route.ts.\n defaultBody: [\n { block: 'text', text: 'Welcome to {{brand.productName}}', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Hi {{name}}, thanks for creating {{org.name}}. Your workspace ' +\n 'is ready.',\n variant: 'body',\n },\n {\n block: 'text',\n text:\n 'From your dashboard you can add a site, invite your team, and ' +\n 'start building.',\n variant: 'body',\n },\n { block: 'button', label: 'Open your dashboard', href: '{{consoleUrl}}' },\n ],\n source: 'apps/console/app/api/orgs/create/route.ts',\n },\n {\n key: 'member-added',\n name: 'Added to an organization',\n description:\n `Sent when an existing ${BRAND.ORG_NAME} account is added directly ` +\n 'to an organization. People who do not have an account yet get the ' +\n 'organization invite email instead.',\n deliveredBy: 'resend',\n defaultSubject: \"You've been added to {{org.name}}\",\n mergeTokens: [\n {\n name: 'org.name',\n description: 'The organization they were added to',\n sample: 'Test Org',\n },\n {\n name: 'member.role',\n description: 'Role they were given',\n sample: 'editor',\n },\n {\n name: 'signInUrl',\n description: 'Console URL to open the organization',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in orgs/members/route.ts.\n defaultBody: [\n {\n block: 'text',\n text: 'You were added to {{org.name}} as {{member.role}}.',\n variant: 'body',\n },\n {\n block: 'text',\n text: 'Sign in to switch to it from your dashboard.',\n variant: 'body',\n },\n { block: 'button', label: 'Open {{brand.productName}}', href: '{{signInUrl}}' },\n ],\n source: 'apps/console/app/api/orgs/members/route.ts',\n },\n {\n key: 'erasure-confirmation',\n name: 'Erasure completed',\n description:\n 'Sent to an organization owner once its data has been permanently ' +\n 'erased under a GDPR request.',\n deliveredBy: 'resend',\n defaultSubject: 'Your {{brand.productName}} data has been erased',\n mergeTokens: [\n {\n name: 'org.name',\n description: 'The organization that was erased',\n sample: 'Test Org',\n },\n ],\n // Mirrors the fallbackText in admin/run-erasures/route.ts.\n defaultBody: [\n { block: 'text', text: 'Your data has been erased', variant: 'heading' },\n {\n block: 'text',\n text:\n '{{org.name}} and all of its data have been permanently erased ' +\n 'from {{brand.productName}}, as requested.',\n variant: 'body',\n },\n {\n block: 'text',\n text: 'This is complete and cannot be undone.',\n variant: 'body',\n },\n ],\n source: 'apps/console/app/api/admin/run-erasures/route.ts',\n },\n {\n key: 'erasure-requested',\n name: 'Erasure requested',\n description:\n 'Confirms to an organization owner that a GDPR erasure request was ' +\n 'recorded, and that deletion follows after a 7-day hold.',\n deliveredBy: 'resend',\n defaultSubject: 'We received your erasure request',\n mergeTokens: [\n {\n name: 'org.name',\n description: 'The organization to be erased',\n sample: 'Test Org',\n },\n ],\n // Mirrors the fallbackText in admin/erasure-request/route.ts.\n defaultBody: [\n { block: 'text', text: 'Erasure request received', variant: 'heading' },\n {\n block: 'text',\n text:\n 'We have recorded a request to erase {{org.name}} and all of ' +\n 'its data from {{brand.productName}}.',\n variant: 'body',\n },\n {\n block: 'text',\n text:\n 'Deletion is permanent and happens after a 7-day hold. If this ' +\n 'was not intended, contact support before then to cancel.',\n variant: 'body',\n },\n ],\n source: 'apps/console/app/api/admin/erasure-request/route.ts',\n },\n // Admin-initiated password mail (AGL-910). Unlike the self-serve\n // `password-reset` below, Aglyn composes and sends these itself — the\n // reset LINK still comes from Firebase (generatePasswordResetLink), but\n // the message around it is ours, so both are designable.\n {\n key: 'admin-password-reset',\n name: 'Password reset (sent by an admin)',\n description:\n 'Password reset link sent on someone else\\'s behalf from the ' +\n 'console — by staff from a user\\'s detail page, or by an ' +\n 'organization admin from a team member\\'s page.',\n deliveredBy: 'resend',\n defaultSubject: 'Reset your {{brand.productName}} password',\n mergeTokens: [\n {\n name: 'resetUrl',\n description: 'One-time link that opens the reset form',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/reset-password?oobCode=…`,\n },\n {\n name: 'actor.name',\n description: 'Who asked for the reset',\n sample: '{{brand.productName}} support',\n },\n ],\n // Mirrors the fallbackText in _lib/password-admin.ts.\n defaultBody: [\n { block: 'text', text: 'Reset your password', variant: 'heading' },\n {\n block: 'text',\n text:\n '{{actor.name}} started a password reset for your {{brand.productName}} ' +\n 'account. Choose a new password here:',\n variant: 'body',\n },\n { block: 'button', label: 'Set a new password', href: '{{resetUrl}}' },\n {\n block: 'text',\n text:\n 'The link expires shortly. If you were not expecting this, you ' +\n 'can ignore this email — your password stays as it is.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/password-admin.ts',\n },\n {\n key: 'password-changed-by-admin',\n name: 'Password changed by an admin',\n description:\n 'Notice sent to the account holder after an admin sets their ' +\n 'password directly. Always sent — it is how someone finds out an ' +\n 'administrator changed their credentials.',\n deliveredBy: 'resend',\n defaultSubject: 'Your {{brand.productName}} password was changed',\n mergeTokens: [\n {\n name: 'actor.name',\n description: 'Who changed the password',\n sample: '{{brand.productName}} support',\n },\n {\n name: 'signInUrl',\n description: 'Where to sign in with the new password',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in _lib/password-admin.ts.\n defaultBody: [\n { block: 'text', text: 'Your password was changed', variant: 'heading' },\n {\n block: 'text',\n text:\n '{{actor.name}} set a new password on your account. You have ' +\n 'been signed out everywhere and will need the new password to ' +\n 'sign back in.',\n variant: 'body',\n },\n { block: 'button', label: 'Sign in', href: '{{signInUrl}}' },\n {\n block: 'text',\n text:\n 'If you did not expect this, contact whoever administers your ' +\n 'account straight away.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/password-admin.ts',\n },\n // Security alerts (AGL-665). Factual and actionable: what happened, from\n // where, and one button to the account-security surface. These must be\n // believed, so the send path only fires them on genuinely new devices —\n // never on every sign-in.\n {\n key: 'security-new-device',\n name: 'New device sign-in',\n description:\n 'Security alert sent when an account signs in from a device it has ' +\n 'not used before. Not sent on routine sign-ins from known devices.',\n deliveredBy: 'resend',\n defaultSubject: 'New sign-in to your {{brand.productName}} account',\n mergeTokens: [\n {\n name: 'device.name',\n description: 'Browser and operating system that signed in',\n sample: 'Chrome on macOS',\n },\n {\n name: 'device.location',\n description: 'Approximate location, from the request IP',\n sample: 'Denver, CO, US',\n },\n {\n name: 'device.ip',\n description: 'IP address the sign-in came from',\n sample: '203.0.113.7',\n },\n {\n name: 'device.time',\n description: 'When the sign-in happened (UTC)',\n sample: '2026-08-08 14:03 UTC',\n },\n {\n name: 'accountSecurityUrl',\n description:\n 'Manage-account Security section — Recent sign-ins, where the ' +\n 'device can be signed out, plus password and passkeys',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/manage/user/security`,\n },\n ],\n // Mirrors the fallbackText in _lib/security-alerts.ts.\n defaultBody: [\n { block: 'text', text: 'New device sign-in', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Your {{brand.productName}} account was just signed in to from a device it has ' +\n 'not used before.',\n variant: 'body',\n },\n { block: 'text', text: 'Device: {{device.name}}', variant: 'body' },\n { block: 'text', text: 'Location: {{device.location}}', variant: 'body' },\n { block: 'text', text: 'IP address: {{device.ip}}', variant: 'body' },\n { block: 'text', text: 'When: {{device.time}}', variant: 'body' },\n {\n // AGL-1959. The button used to say \"Review account security\" and\n // land on the account page's DEFAULT tab, which offered a password\n // change and nothing about the device the mail is about. AGL-665\n // asked for this: \"'Manage your devices' has to land somewhere that\n // can list and revoke sessions, or the alert tells you something is\n // wrong and offers no way to act on it — which is worse than sending\n // nothing.\"\n block: 'button',\n label: 'Manage your devices',\n href: '{{accountSecurityUrl}}',\n },\n {\n block: 'text',\n text:\n 'If this was you, no action is needed. If it was not, sign this ' +\n 'device out from Recent sign-ins and change your password right ' +\n 'away.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/security-alerts.ts',\n },\n {\n key: 'security-passkey-added',\n name: 'New passkey added',\n description:\n 'Security alert sent when a passkey is registered on an account. ' +\n 'The trigger ships with passkey support (AGL-662); until then no ' +\n 'call site sends this.',\n deliveredBy: 'resend',\n defaultSubject: 'A passkey was added to your {{brand.productName}} account',\n mergeTokens: [\n {\n name: 'passkey.label',\n description: 'Name the passkey was saved under',\n sample: 'MacBook Touch ID',\n },\n {\n name: 'device.time',\n description: 'When the passkey was added (UTC)',\n sample: '2026-08-08 14:03 UTC',\n },\n {\n name: 'accountSecurityUrl',\n description: 'Manage-account page where credentials are reviewed',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/manage/user`,\n },\n ],\n // Mirrors the fallbackText in _lib/security-alerts.ts.\n defaultBody: [\n { block: 'text', text: 'New passkey added', variant: 'heading' },\n {\n block: 'text',\n text:\n 'A passkey (\"{{passkey.label}}\") was just added to your {{brand.productName}} ' +\n 'account. It can be used to sign in without your password.',\n variant: 'body',\n },\n { block: 'text', text: 'When: {{device.time}}', variant: 'body' },\n {\n block: 'button',\n label: 'Review account security',\n href: '{{accountSecurityUrl}}',\n },\n {\n block: 'text',\n text:\n 'If you did not add this passkey, change your password from ' +\n 'Manage account right away.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/security-alerts.ts',\n },\n // Aglyn composes and sends both of these now (AGL-1112). They were\n // Firebase's, from a template nobody could edit — the subject still\n // carried `[aglyn.io]`, a domain the company no longer uses, and the link\n // landed on `aglyn-main.firebaseapp.com`. Neither could be fixed: every\n // write under `notification.sendEmail` is refused with\n // `EMAIL_TEMPLATE_UPDATE_NOT_ALLOWED`.\n //\n // Firebase still MINTS the one-time code, which is the part that has to\n // be its. Only the message around it, and the host the link points at,\n // became ours. So editing these here now does what it says.\n {\n key: 'password-reset',\n name: 'Forgot password',\n description:\n 'Password reset link, sent when someone asks for one from Account ' +\n 'recovery.',\n deliveredBy: 'resend',\n defaultSubject: 'Reset your {{brand.productName}} password',\n mergeTokens: [\n {\n name: 'resetUrl',\n description: 'One-time link that opens the choose-a-new-password page',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/reset-password?oobCode=\\u2026`,\n },\n ],\n // Mirrors the fallbackText in the route, so the designed and undesigned\n // versions say the same thing.\n defaultBody: [\n { block: 'text', text: 'Reset your password', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Someone asked to reset the password for your {{brand.productName}} account. ' +\n 'Choose a new one here:',\n variant: 'body',\n },\n { block: 'button', label: 'Choose a new password', href: '{{resetUrl}}' },\n {\n block: 'text',\n text:\n 'The link expires shortly. If this was not you, you can ignore ' +\n 'this email — your password stays as it is.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/auth/send-password-reset/route.ts',\n },\n {\n key: 'email-verification',\n name: 'Confirm email',\n description:\n 'Address verification link, sent after sign-up and whenever someone ' +\n 'asks for another from the verify screen.',\n deliveredBy: 'resend',\n defaultSubject: 'Confirm your email address',\n mergeTokens: [\n {\n name: 'verifyUrl',\n description: 'One-time link that confirms the address',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/verify-email?oobCode=\\u2026`,\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Confirm your email', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Confirm this address to finish setting up your {{brand.productName}} account:',\n variant: 'body',\n },\n { block: 'button', label: 'Confirm my email', href: '{{verifyUrl}}' },\n {\n block: 'text',\n text:\n 'If you did not create an {{brand.productName}} account, you can ignore this ' +\n 'email.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/auth/send-verification/route.ts',\n },\n // Stripe-delivered billing email (AGL-767). Aglyn never composes these —\n // Stripe sends them from the Dashboard's Customer-emails and Subscription\n // settings, so they are listed for visibility only and are not designable.\n // Whether each actually sends is a Dashboard toggle the code cannot read;\n // the descriptions say \"if enabled\" rather than asserting it is on.\n {\n key: 'stripe-receipt',\n name: 'Payment receipt',\n description:\n 'Receipt for a successful subscription or invoice payment. Sent by ' +\n 'Stripe if \"Successful payments\" is enabled under Customer emails.',\n deliveredBy: 'stripe',\n defaultSubject: `Your receipt from ${BRAND.ORG_NAME}`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Customer emails',\n },\n {\n key: 'stripe-payment-failed',\n name: 'Payment failed',\n description:\n 'Dunning notice when a subscription charge fails and Stripe retries ' +\n 'it. Sent by Stripe if failed-payment emails are enabled under ' +\n 'Subscriptions and emails.',\n deliveredBy: 'stripe',\n defaultSubject: `Your ${BRAND.ORG_NAME} payment could not be processed`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Subscriptions and emails',\n },\n {\n key: 'stripe-refund',\n name: 'Refund confirmation',\n description:\n 'Confirms a refund back to the customer. Sent by Stripe if ' +\n '\"Refunds\" is enabled under Customer emails.',\n deliveredBy: 'stripe',\n defaultSubject: `Your ${BRAND.ORG_NAME} refund`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Customer emails',\n },\n {\n key: 'stripe-card-expiring',\n name: 'Card expiring soon',\n description:\n 'Reminder that the card on file is about to expire. Sent by Stripe ' +\n 'if expiring-card reminders are enabled under Subscriptions and ' +\n 'emails.',\n deliveredBy: 'stripe',\n defaultSubject: 'Your card on file is expiring soon',\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Subscriptions and emails',\n },\n {\n key: 'stripe-invoice',\n name: 'Invoice',\n description:\n 'A finalized invoice emailed to the organization. Sent by Stripe ' +\n 'when invoice emails are enabled for the billing configuration.',\n deliveredBy: 'stripe',\n defaultSubject: `Your ${BRAND.ORG_NAME} invoice`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Customer emails',\n },\n ]\n\n/**\n * Every system email Aglyn sends, each with the universal brand tokens\n * appended to its own (AGL-2139).\n *\n * The template's own tokens come FIRST so the editor's palette still leads\n * with what is specific to that email; the brand block reads as the shared\n * footer it is.\n */\nexport const SYSTEM_EMAIL_TEMPLATES: readonly SystemEmailTemplateDefinition[] =\n BASE_SYSTEM_EMAIL_TEMPLATES.map((entry) => ({\n ...entry,\n mergeTokens: [...entry.mergeTokens, ...BRAND_MERGE_TOKENS],\n }))\n\n/** Firestore collection holding the designed templates. */\nexport const SYSTEM_EMAIL_COLLECTION = 'systemEmailTemplates'\n\nexport function getSystemEmailTemplate(\n key: string,\n): SystemEmailTemplateDefinition | undefined {\n return SYSTEM_EMAIL_TEMPLATES.find((entry) => entry.key === key)\n}\n\n/** True when a besigner template can actually change what is delivered. */\nexport function isSystemEmailEditable(\n definition: SystemEmailTemplateDefinition,\n): boolean {\n return definition.deliveredBy === 'resend'\n}\n\n/** The stored template document, when staff have designed one. */\nexport interface SystemEmailTemplateDoc {\n subject?: string\n preheader?: string\n versionId?: string\n updatedAt?: unknown\n updatedByEmail?: string\n}\n\n/** A besigner node in the flat, denormalized map a version stores. */\nexport interface SystemEmailNode {\n $id: string\n componentId: string\n pluginId?: string\n parentId?: string\n props?: Record<string, unknown>\n nodes?: string[]\n}\n\nconst PLACEHOLDER_DEFAULT_BODY: readonly SystemEmailDefaultBlock[] = [\n { block: 'text', text: 'Hello,', variant: 'body' },\n]\n\n/**\n * Turns a template's `defaultBody` into the besigner node map the editor\n * seeds and the send-time default renders (AGL-764/766).\n *\n * One place builds this so the two never diverge — the version a staffer\n * opens is byte-for-byte what a test/send renders when nothing is published.\n * Rooted at {@link EMAIL_NODE_ROOT_ID} so `renderEmailHtml` finds it (AGL-765),\n * with deterministic ids: a version doc only needs ids unique within itself,\n * and determinism keeps the map stable and diffable.\n */\nexport function buildDefaultEmailNodeMap(\n // Structural so the tenant catalog (AGL-770) can seed the same way — every\n // definition that carries a `defaultBody` builds an identical node map.\n definition: { defaultBody?: readonly SystemEmailDefaultBlock[] },\n): Record<string, SystemEmailNode> {\n const blocks = definition.defaultBody?.length\n ? definition.defaultBody\n : PLACEHOLDER_DEFAULT_BODY\n const sectionId = 'default-section'\n const childIds: string[] = []\n const map: Record<string, SystemEmailNode> = {}\n\n blocks.forEach((block, index) => {\n const id = `default-${index}`\n childIds.push(id)\n map[id] =\n block.block === 'button'\n ? {\n $id: id,\n componentId: 'emailButton',\n pluginId: 'email',\n parentId: sectionId,\n props: { children: block.label, href: block.href },\n }\n : {\n $id: id,\n componentId: 'emailText',\n pluginId: 'email',\n parentId: sectionId,\n props: { children: block.text, variant: block.variant ?? 'body' },\n }\n })\n\n map[EMAIL_NODE_ROOT_ID] = {\n $id: EMAIL_NODE_ROOT_ID,\n componentId: 'div',\n nodes: [sectionId],\n }\n map[sectionId] = {\n $id: sectionId,\n componentId: 'emailSection',\n pluginId: 'email',\n parentId: EMAIL_NODE_ROOT_ID,\n nodes: childIds,\n }\n return map\n}\n"],"names":["SAMPLE_CONSOLE_ORIGIN","process","env","NEXT_PUBLIC_CONSOLE_URL","trim","replace","SAMPLE_SUPPORT_URL","NEXT_PUBLIC_PLATFORM_SUPPORT_URL","NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL","BRAND","EMAIL_NODE_ROOT_ID","BRAND_MERGE_TOKENS","name","description","sample","ORG_NAME","BASE_SYSTEM_EMAIL_TEMPLATES","key","deliveredBy","defaultSubject","mergeTokens","defaultBody","block","text","variant","label","href","source","SYSTEM_EMAIL_TEMPLATES","map","entry","SYSTEM_EMAIL_COLLECTION","getSystemEmailTemplate","find","isSystemEmailEditable","definition","PLACEHOLDER_DEFAULT_BODY","buildDefaultEmailNodeMap","blocks","length","sectionId","childIds","forEach","index","id","push","$id","componentId","pluginId","parentId","props","children","nodes"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;CAcC,GACD,MAAMA,wBACJ,AAACC,CAAAA,QAAQC,GAAG,CAACC,uBAAuB,IAAI,EAAC,EAAGC,IAAI,GAAGC,OAAO,CAAC,QAAQ,OACnE;AAEF;;;;CAIC,GACD,MAAMC,qBACJ,AAACL,CAAAA,QAAQC,GAAG,CAACK,gCAAgC,IAAI,EAAC,EAAGH,IAAI,MACxD,CAAA,AAACH,CAAAA,QAAQC,GAAG,CAACM,kCAAkC,IAAI,EAAC,EAAGJ,IAAI,KACxD,CAAC,OAAO,EAAE,AAACH,CAAAA,QAAQC,GAAG,CAACM,kCAAkC,IAAI,EAAC,EAAGJ,IAAI,IAAI,GACzE,2BAA0B;AAEhC,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,0EAA0E;AAC1E,wCAAwC;AACxC,SAASK,KAAK,QAAQ,iCAAgC;AACtD,SAASC,kBAAkB,QAAQ,oBAAgB;AAgFnD;;;;;;;;;;;CAWC,GACD;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,qBAAuD;IAClE;QACEC,MAAM;QACNC,aAAa;QACbC,QAAQL,MAAMM,QAAQ;IACxB;IACA;QACEH,MAAM;QACNC,aAAa;QACbC,QAAQL,MAAMM,QAAQ;IACxB;IACA;QACEH,MAAM;QACNC,aAAa;QACbC,QAAQR;IACV;CACD,CAAA;AAED,sEAAsE;AACtE,sEAAsE;AACtE,4EAA4E;AAC5E,2EAA2E;AAC3E,2DAA2D;AAE3D,MAAMU,8BACJ;IACE;QACEC,KAAK;QACLL,MAAM;QACNC,aACE,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,gDAAgD;QAChDqB,aAAa;YACX;gBACEC,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MACE,gEACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAWC,MAAM;YAAgB;SAC5D;QACDC,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,sEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,+DAA+D;QAC/DO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAkCC,SAAS;YAAU;YAC5E;gBACEF,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAAqBC,SAAS;YAAO;SAC7D;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,0DAA0D;QAC1DO,aAAa;YACX;gBACEC,OAAO;gBACPC,MACE,+DACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAAiBC,SAAS;YAAO;SACzD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,oDAAoD;QACpDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAoCC,SAAS;YAAU;YAC9E;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAuBC,MAAM;YAAiB;SACzE;QACDC,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,CAAC,sBAAsB,EAAEJ,MAAMM,QAAQ,CAAC,2BAA2B,CAAC,GACpE,uEACA;QACFG,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,qDAAqD;QACrDqB,aAAa;YACX;gBACEC,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAA8BC,MAAM;YAAgB;SAC/E;QACDC,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,sEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,2DAA2D;QAC3DO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAA6BC,SAAS;YAAU;YACvE;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,8DAA8D;QAC9DO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAA4BC,SAAS;YAAU;YACtE;gBACEF,OAAO;gBACPC,MACE,iEACA;gBACFC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,iEAAiE;IACjE,sEAAsE;IACtE,wEAAwE;IACxE,yDAAyD;IACzD;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,iEACA,6DACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,yBAAyB,CAAC;YAC7D;YACA;gBACEY,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,sDAAsD;QACtDO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAuBC,SAAS;YAAU;YACjE;gBACEF,OAAO;gBACPC,MACE,4EACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAsBC,MAAM;YAAe;YACrE;gBACEJ,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,iEACA,qEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,sDAAsD;QACtDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAA6BC,SAAS;YAAU;YACvE;gBACEF,OAAO;gBACPC,MACE,iEACA,kEACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAWC,MAAM;YAAgB;YAC3D;gBACEJ,OAAO;gBACPC,MACE,kEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,yEAAyE;IACzE,uEAAuE;IACvE,wEAAwE;IACxE,0BAA0B;IAC1B;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aACE,kEACA;gBACFC,QAAQ,GAAGd,sBAAsB,qBAAqB,CAAC;YACzD;SACD;QACD,uDAAuD;QACvDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAsBC,SAAS;YAAU;YAChE;gBACEF,OAAO;gBACPC,MACE,mFACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAA2BC,SAAS;YAAO;YAClE;gBAAEF,OAAO;gBAAQC,MAAM;gBAAiCC,SAAS;YAAO;YACxE;gBAAEF,OAAO;gBAAQC,MAAM;gBAA6BC,SAAS;YAAO;YACpE;gBAAEF,OAAO;gBAAQC,MAAM;gBAAyBC,SAAS;YAAO;YAChE;gBACE,iEAAiE;gBACjE,mEAAmE;gBACnE,iEAAiE;gBACjE,oEAAoE;gBACpE,oEAAoE;gBACpE,qEAAqE;gBACrE,YAAY;gBACZF,OAAO;gBACPG,OAAO;gBACPC,MAAM;YACR;YACA;gBACEJ,OAAO;gBACPC,MACE,oEACA,oEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,qEACA,qEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,YAAY,CAAC;YAChD;SACD;QACD,uDAAuD;QACvDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAqBC,SAAS;YAAU;YAC/D;gBACEF,OAAO;gBACPC,MACE,kFACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAAyBC,SAAS;YAAO;YAChE;gBACEF,OAAO;gBACPG,OAAO;gBACPC,MAAM;YACR;YACA;gBACEJ,OAAO;gBACPC,MACE,gEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,mEAAmE;IACnE,oEAAoE;IACpE,0EAA0E;IAC1E,wEAAwE;IACxE,uDAAuD;IACvD,uCAAuC;IACvC,EAAE;IACF,wEAAwE;IACxE,uEAAuE;IACvE,4DAA4D;IAC5D;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,sEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,8BAA8B,CAAC;YAClE;SACD;QACD,wEAAwE;QACxE,+BAA+B;QAC/BqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAuBC,SAAS;YAAU;YACjE;gBACEF,OAAO;gBACPC,MACE,iFACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAyBC,MAAM;YAAe;YACxE;gBACEJ,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,wEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,4BAA4B,CAAC;YAChE;SACD;QACDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAsBC,SAAS;YAAU;YAChE;gBACEF,OAAO;gBACPC,MACE;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAoBC,MAAM;YAAgB;YACpE;gBACEJ,OAAO;gBACPC,MACE,iFACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,oEAAoE;IACpE;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,kBAAkB,EAAEV,MAAMM,QAAQ,EAAE;QACrDK,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,wEACA,mEACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,KAAK,EAAEV,MAAMM,QAAQ,CAAC,+BAA+B,CAAC;QACvEK,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,+DACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,KAAK,EAAEV,MAAMM,QAAQ,CAAC,OAAO,CAAC;QAC/CK,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,qEACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,KAAK,EAAEV,MAAMM,QAAQ,CAAC,QAAQ,CAAC;QAChDK,aAAa,EAAE;QACfO,QAAQ;IACV;CACD;AAEH;;;;;;;CAOC,GACD,OAAO,MAAMC,yBACXZ,4BAA4Ba,GAAG,CAAC,CAACC,QAAW,aACvCA;QACHV,aAAa;eAAIU,MAAMV,WAAW;eAAKT;SAAmB;QACzD;AAEL,yDAAyD,GACzD,OAAO,MAAMoB,0BAA0B,uBAAsB;AAE7D,OAAO,SAASC,uBACdf,GAAW;IAEX,OAAOW,uBAAuBK,IAAI,CAAC,CAACH,QAAUA,MAAMb,GAAG,KAAKA;AAC9D;AAEA,yEAAyE,GACzE,OAAO,SAASiB,sBACdC,UAAyC;IAEzC,OAAOA,WAAWjB,WAAW,KAAK;AACpC;AAqBA,MAAMkB,2BAA+D;IACnE;QAAEd,OAAO;QAAQC,MAAM;QAAUC,SAAS;IAAO;CAClD;AAED;;;;;;;;;CASC,GACD,OAAO,SAASa,yBACd,2EAA2E;AAC3E,wEAAwE;AACxEF,UAAgE;QAEjDA;IAAf,MAAMG,SAASH,EAAAA,0BAAAA,WAAWd,WAAW,qBAAtBc,wBAAwBI,MAAM,IACzCJ,WAAWd,WAAW,GACtBe;IACJ,MAAMI,YAAY;IAClB,MAAMC,WAAqB,EAAE;IAC7B,MAAMZ,MAAuC,CAAC;IAE9CS,OAAOI,OAAO,CAAC,CAACpB,OAAOqB;YAiB2BrB;QAhBhD,MAAMsB,KAAK,CAAC,QAAQ,EAAED,OAAO;QAC7BF,SAASI,IAAI,CAACD;QACdf,GAAG,CAACe,GAAG,GACLtB,MAAMA,KAAK,KAAK,WACZ;YACEwB,KAAKF;YACLG,aAAa;YACbC,UAAU;YACVC,UAAUT;YACVU,OAAO;gBAAEC,UAAU7B,MAAMG,KAAK;gBAAEC,MAAMJ,MAAMI,IAAI;YAAC;QACnD,IACA;YACEoB,KAAKF;YACLG,aAAa;YACbC,UAAU;YACVC,UAAUT;YACVU,OAAO;gBAAEC,UAAU7B,MAAMC,IAAI;gBAAEC,OAAO,GAAEF,iBAAAA,MAAME,OAAO,YAAbF,iBAAiB;YAAO;QAClE;IACR;IAEAO,GAAG,CAACnB,mBAAmB,GAAG;QACxBoC,KAAKpC;QACLqC,aAAa;QACbK,OAAO;YAACZ;SAAU;IACpB;IACAX,GAAG,CAACW,UAAU,GAAG;QACfM,KAAKN;QACLO,aAAa;QACbC,UAAU;QACVC,UAAUvC;QACV0C,OAAOX;IACT;IACA,OAAOZ;AACT"}
1
+ {"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/system-email-catalog.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 * Origins used as merge-token SAMPLE values in the staff email designer\n * (AGL-2202).\n *\n * These are never sent — they render in the template preview so an author can\n * see what `{{console.url}}` will become. That still made them wrong on a\n * self-host install: the preview showed Aglyn's console as the example for a\n * link that will resolve to the operator's, which is exactly the sort of\n * quiet mis-teaching that ends up copied into a hand-written template.\n *\n * Read from the environment rather than through `@aglyn/aglyn`'s\n * `platform-brand` / `constants`: `shared-util-email` is tagged `scope:shared`\n * and `@nx/enforce-module-boundaries` refuses the edge. Same defaults, so a\n * deployment that configures nothing sees exactly what it saw before.\n */\nconst SAMPLE_CONSOLE_ORIGIN: string =\n (process.env.NEXT_PUBLIC_CONSOLE_URL || '').trim().replace(/\\/+$/, '') ||\n 'https://app.aglyn.com'\n\n/**\n * Mirrors `PLATFORM_SUPPORT_URL`'s precedence, including the step that makes\n * the operator identity sufficient on its own: a configured support URL, else\n * the operator's support mailbox as a `mailto:`, else ours.\n *\n * The last step is built from `SAMPLE_CONSOLE_ORIGIN` rather than written out\n * (AGL-3262). The support entry point is a CONSOLE route — `/support`\n * resolves the workspace from the session (AGL-3265) — so the origin it hangs\n * off is the one directly above, and an operator who set\n * `NEXT_PUBLIC_CONSOLE_URL` gets their own console in the sample instead of\n * ours. The literal it replaces was `aglyn.com/support`, which answers 404 and\n * never existed.\n */\nconst SAMPLE_SUPPORT_URL: string =\n (process.env.NEXT_PUBLIC_PLATFORM_SUPPORT_URL || '').trim() ||\n ((process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim()\n ? `mailto:${(process.env.NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL || '').trim()}`\n : `${SAMPLE_CONSOLE_ORIGIN}/support`)\n\n// Deep import, NOT the barrel (AGL-1151): `@aglyn/shared-data-enums` re-exports\n// `firebase-auth`, whose `AuthErrorCodes` is a VALUE import of `firebase/auth`.\n// This module is reachable from the email plugin's site half, which a PUBLISHED\n// tenant page loads, so the barrel put the Firebase auth client into that\n// page's chunks for one brand constant.\nimport { BRAND } from '@aglyn/shared-data-enums/aglyn'\nimport { EMAIL_NODE_ROOT_ID } from './email-render'\n\n/**\n * Who actually puts the message on the wire.\n *\n * `resend` — Aglyn composes and sends it, so a besigner template can change\n * what the recipient sees.\n *\n * `firebase` — Firebase Auth sends it from its own templates, configured in\n * the Firebase console. **A besigner template cannot affect these at all**\n * because the sender owns the copy (AGL-767).\n *\n * `stripe` — Stripe sends it on Aglyn's behalf from the Dashboard's\n * Customer-emails settings (billing receipts, dunning, refunds, expiring-card\n * notices). Aglyn never composes these, so like `firebase` they cannot be\n * designed here (AGL-767).\n *\n * The non-`resend` entries are listed so staff can see the full set of mail\n * the product sends and where each one is controlled; the UI must present\n * them as non-editable rather than offering an editor that silently does\n * nothing.\n */\n/**\n * Who actually puts the message on the wire.\n *\n * `'firebase'` was removed by AGL-1112, once nothing was left that Firebase\n * Auth composed. Deliberately not kept \"just in case\": with the variant gone,\n * reintroducing a Firebase-sent email is a compile error rather than a row in\n * a table that quietly stops being editable. The staff editor is wired to\n * `'resend'`, so any other value silently produces a template that renders\n * nothing — which is exactly how the `[aglyn.io]` subject survived so long.\n */\nexport type SystemEmailDeliveredBy = 'resend' | 'stripe'\n\nexport interface SystemEmailMergeToken {\n /** Token as written in the template, without braces. */\n name: string\n description: string\n /** Stand-in used by the staff preview. */\n sample: string\n}\n\n/**\n * One block of a template's starting content (AGL-764). Declarative on\n * purpose: the catalog stays a dependency-free data module, and the console\n * turns these into email-plugin nodes (`emailSection` → `emailText`/\n * `emailButton`) when it seeds the first version. Mirror the copy the send\n * site uses today, with the same `{{tokens}}`, so a staffer who opens the\n * editor starts from what recipients actually get rather than a blank canvas.\n */\nexport type SystemEmailDefaultBlock =\n | {\n block: 'text'\n text: string\n variant?: 'heading' | 'subheading' | 'body' | 'caption'\n }\n | { block: 'button'; label: string; href: string }\n\nexport interface SystemEmailTemplateDefinition {\n /** Stable storage key — the Firestore document id. Never rename. */\n key: string\n name: string\n description: string\n deliveredBy: SystemEmailDeliveredBy\n /** Subject used when no template has been published. */\n defaultSubject: string\n mergeTokens: SystemEmailMergeToken[]\n /**\n * Starting content for the first version staff design (AGL-764). Every\n * `resend` template should carry one; a `firebase` template has no editor\n * so it needs none. Absent → the editor seeds a minimal placeholder.\n */\n defaultBody?: readonly SystemEmailDefaultBlock[]\n /**\n * Where the fallback copy lives, for staff wondering what recipients get\n * today. Informational — nothing reads it at runtime.\n */\n source: string\n}\n\n/**\n * Every system email Aglyn sends.\n *\n * Deliberately **code-defined and fixed**: staff edit the system emails that\n * exist, they do not create or delete them. Adding one is a code change,\n * which is what keeps this list from drifting into advertising an email the\n * product never actually sends — the failure mode a Firestore-backed list\n * would have.\n *\n * The staff page renders from this registry, not from Firestore, so an email\n * with no template designed yet still appears (as \"Using default\").\n */\n/**\n * Brand tokens EVERY system email resolves (AGL-2139).\n *\n * White-label used to invert precisely when staff published a template: the\n * designed template wins over each sender's branded fallback, and the copy\n * here hard-coded \"Aglyn\". Nor was there anything to design against — the\n * merge maps carried `org.name`, `invite.role`, `signInUrl` and friends and\n * nothing about the brand, while `blankUnresolvedTokens` DELETES any token\n * the caller did not supply, so a designer who typed `{{brand.productName}}`\n * would have shipped an email with a hole in the sentence.\n *\n * Declared once and appended to every template's own list rather than copied\n * into each: 13 hand-maintained copies is how the token list falls behind the\n * copy, and the editor renders this list as the palette a designer picks\n * from — a token missing here is a token nobody knows exists.\n *\n * The samples are the DEPLOYMENT's own values (`BRAND.ORG_NAME`, AGL-2319),\n * because that is what an unbranded org genuinely renders — on a self-host\n * install a hardcoded \"Aglyn\" would preview a brand that appears nowhere in\n * the mail the designer is editing.\n */\nexport const BRAND_MERGE_TOKENS: readonly SystemEmailMergeToken[] = [\n {\n name: 'brand.productName',\n description: \"The sender's product name — the org's own on white-label\",\n sample: BRAND.ORG_NAME,\n },\n {\n name: 'brand.fromName',\n description: 'Sender display name the mail is delivered under',\n sample: BRAND.ORG_NAME,\n },\n {\n name: 'brand.supportUrl',\n description: 'Where the recipient should go for help',\n sample: SAMPLE_SUPPORT_URL,\n },\n]\n\n// NOT declared: a `brand.logoUrl` token. The logo is STRUCTURAL — the\n// renderer emits it as a header row above the designed body, or emits\n// nothing — so a designer never places it, and a palette entry for it would\n// need a sample URL that a staff test-send would render as a broken image.\n// The three tokens above are the ones that belong in copy.\n\nconst BASE_SYSTEM_EMAIL_TEMPLATES: readonly SystemEmailTemplateDefinition[] =\n [\n {\n key: 'org-invite',\n name: 'Organization invite',\n description:\n 'Sent when an admin invites someone to an organization from the ' +\n 'Team page.',\n deliveredBy: 'resend',\n defaultSubject: \"You've been invited to {{org.name}} on {{brand.productName}}\",\n mergeTokens: [\n {\n name: 'org.name',\n description: 'Name of the inviting organization',\n sample: 'Test Org',\n },\n {\n name: 'invite.role',\n description: 'Role the person is invited as',\n sample: 'editor',\n },\n {\n name: 'signInUrl',\n description: 'Console URL to sign in and accept',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in invites/route.ts.\n defaultBody: [\n {\n block: 'text',\n text: \"You've been invited to join {{org.name}} as {{invite.role}}.\",\n variant: 'body',\n },\n {\n block: 'text',\n text:\n 'Sign in with this email address and accept the invite from ' +\n 'your dashboard.',\n variant: 'body',\n },\n { block: 'button', label: 'Sign in', href: '{{signInUrl}}' },\n ],\n source: 'apps/console/app/api/orgs/invites/route.ts',\n },\n {\n key: 'usage-summary',\n name: 'Monthly usage summary',\n description:\n 'Monthly per-organization usage and metered-cost summary, sent by ' +\n 'the scheduled job.',\n deliveredBy: 'resend',\n defaultSubject: 'Your {{brand.productName}} usage summary for {{month}}',\n mergeTokens: [\n {\n name: 'month',\n description: 'Billing month as YYYY-MM',\n sample: '2026-06',\n },\n {\n name: 'org.name',\n description: 'Organization the summary is for',\n sample: 'Test Org',\n },\n {\n name: 'usage.summary',\n description: 'Pre-formatted usage lines',\n sample: 'Page views: 12,400',\n },\n ],\n // Mirrors the summary lines assembled in usage-email/route.ts.\n defaultBody: [\n { block: 'text', text: 'Usage summary for {{org.name}}', variant: 'heading' },\n {\n block: 'text',\n text: 'Here is your {{brand.productName}} usage summary for {{month}}.',\n variant: 'body',\n },\n { block: 'text', text: '{{usage.summary}}', variant: 'body' },\n ],\n source: 'apps/console/app/api/billing/usage-email/route.ts',\n },\n {\n key: 'erasure-hold-alert',\n name: 'Erasure hold staff alert',\n description:\n 'Internal alert to STAFF_ALERT_EMAIL when GDPR erasure requests ' +\n 'pass their 7-day hold.',\n deliveredBy: 'resend',\n defaultSubject: '{{count}} erasure request(s) past the 7-day hold',\n mergeTokens: [\n {\n name: 'count',\n description: 'How many organizations are past the hold',\n sample: '3',\n },\n {\n name: 'orgs.list',\n description: 'The organizations, one per line',\n sample: '- Test Org (org_123), requested 2026-07-01',\n },\n ],\n // Mirrors the staff-alert text in audit-archive/route.ts.\n defaultBody: [\n {\n block: 'text',\n text:\n 'These organizations are past their GDPR erasure hold. Run ' +\n 'tools/scripts/erase-tenant.mjs to hard-delete. No copy is kept:',\n variant: 'body',\n },\n { block: 'text', text: '{{orgs.list}}', variant: 'body' },\n ],\n source: 'apps/console/app/api/admin/audit-archive/route.ts',\n },\n {\n key: 'welcome',\n name: 'Welcome',\n description:\n 'Sent to a new owner the first time they create an organization ' +\n '(not on every organization they later create).',\n deliveredBy: 'resend',\n defaultSubject: 'Welcome to {{brand.productName}}',\n mergeTokens: [\n {\n name: 'name',\n description: \"The new owner's display name\",\n sample: 'Alex',\n },\n {\n name: 'org.name',\n description: 'The organization they just created',\n sample: 'Test Org',\n },\n {\n name: 'consoleUrl',\n description: 'Console dashboard URL',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in orgs/create/route.ts.\n defaultBody: [\n { block: 'text', text: 'Welcome to {{brand.productName}}', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Hi {{name}}, thanks for creating {{org.name}}. Your workspace ' +\n 'is ready.',\n variant: 'body',\n },\n {\n block: 'text',\n text:\n 'From your dashboard you can add a site, invite your team, and ' +\n 'start building.',\n variant: 'body',\n },\n { block: 'button', label: 'Open your dashboard', href: '{{consoleUrl}}' },\n ],\n source: 'apps/console/app/api/orgs/create/route.ts',\n },\n {\n key: 'member-added',\n name: 'Added to an organization',\n description:\n `Sent when an existing ${BRAND.ORG_NAME} account is added directly ` +\n 'to an organization. People who do not have an account yet get the ' +\n 'organization invite email instead.',\n deliveredBy: 'resend',\n defaultSubject: \"You've been added to {{org.name}}\",\n mergeTokens: [\n {\n name: 'org.name',\n description: 'The organization they were added to',\n sample: 'Test Org',\n },\n {\n name: 'member.role',\n description: 'Role they were given',\n sample: 'editor',\n },\n {\n name: 'signInUrl',\n description: 'Console URL to open the organization',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in orgs/members/route.ts.\n defaultBody: [\n {\n block: 'text',\n text: 'You were added to {{org.name}} as {{member.role}}.',\n variant: 'body',\n },\n {\n block: 'text',\n text: 'Sign in to switch to it from your dashboard.',\n variant: 'body',\n },\n { block: 'button', label: 'Open {{brand.productName}}', href: '{{signInUrl}}' },\n ],\n source: 'apps/console/app/api/orgs/members/route.ts',\n },\n {\n key: 'erasure-confirmation',\n name: 'Erasure completed',\n description:\n 'Sent to an organization owner once its data has been permanently ' +\n 'erased under a GDPR request.',\n deliveredBy: 'resend',\n defaultSubject: 'Your {{brand.productName}} data has been erased',\n mergeTokens: [\n {\n name: 'org.name',\n description: 'The organization that was erased',\n sample: 'Test Org',\n },\n ],\n // Mirrors the fallbackText in admin/run-erasures/route.ts.\n defaultBody: [\n { block: 'text', text: 'Your data has been erased', variant: 'heading' },\n {\n block: 'text',\n text:\n '{{org.name}} and all of its data have been permanently erased ' +\n 'from {{brand.productName}}, as requested.',\n variant: 'body',\n },\n {\n block: 'text',\n text: 'This is complete and cannot be undone.',\n variant: 'body',\n },\n ],\n source: 'apps/console/app/api/admin/run-erasures/route.ts',\n },\n {\n key: 'erasure-requested',\n name: 'Erasure requested',\n description:\n 'Confirms to an organization owner that a GDPR erasure request was ' +\n 'recorded, and that deletion follows after a 7-day hold.',\n deliveredBy: 'resend',\n defaultSubject: 'We received your erasure request',\n mergeTokens: [\n {\n name: 'org.name',\n description: 'The organization to be erased',\n sample: 'Test Org',\n },\n ],\n // Mirrors the fallbackText in admin/erasure-request/route.ts.\n defaultBody: [\n { block: 'text', text: 'Erasure request received', variant: 'heading' },\n {\n block: 'text',\n text:\n 'We have recorded a request to erase {{org.name}} and all of ' +\n 'its data from {{brand.productName}}.',\n variant: 'body',\n },\n {\n block: 'text',\n text:\n 'Deletion is permanent and happens after a 7-day hold. If this ' +\n 'was not intended, contact support before then to cancel.',\n variant: 'body',\n },\n ],\n source: 'apps/console/app/api/admin/erasure-request/route.ts',\n },\n // Admin-initiated password mail (AGL-910). Unlike the self-serve\n // `password-reset` below, Aglyn composes and sends these itself — the\n // reset LINK still comes from Firebase (generatePasswordResetLink), but\n // the message around it is ours, so both are designable.\n {\n key: 'admin-password-reset',\n name: 'Password reset (sent by an admin)',\n description:\n 'Password reset link sent on someone else\\'s behalf from the ' +\n 'console — by staff from a user\\'s detail page, or by an ' +\n 'organization admin from a team member\\'s page.',\n deliveredBy: 'resend',\n defaultSubject: 'Reset your {{brand.productName}} password',\n mergeTokens: [\n {\n name: 'resetUrl',\n description: 'One-time link that opens the reset form',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/reset-password?oobCode=…`,\n },\n {\n name: 'actor.name',\n description: 'Who asked for the reset',\n sample: '{{brand.productName}} support',\n },\n ],\n // Mirrors the fallbackText in _lib/password-admin.ts.\n defaultBody: [\n { block: 'text', text: 'Reset your password', variant: 'heading' },\n {\n block: 'text',\n text:\n '{{actor.name}} started a password reset for your {{brand.productName}} ' +\n 'account. Choose a new password here:',\n variant: 'body',\n },\n { block: 'button', label: 'Set a new password', href: '{{resetUrl}}' },\n {\n block: 'text',\n text:\n 'The link expires shortly. If you were not expecting this, you ' +\n 'can ignore this email — your password stays as it is.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/password-admin.ts',\n },\n {\n key: 'password-changed-by-admin',\n name: 'Password changed by an admin',\n description:\n 'Notice sent to the account holder after an admin sets their ' +\n 'password directly. Always sent — it is how someone finds out an ' +\n 'administrator changed their credentials.',\n deliveredBy: 'resend',\n defaultSubject: 'Your {{brand.productName}} password was changed',\n mergeTokens: [\n {\n name: 'actor.name',\n description: 'Who changed the password',\n sample: '{{brand.productName}} support',\n },\n {\n name: 'signInUrl',\n description: 'Where to sign in with the new password',\n sample: SAMPLE_CONSOLE_ORIGIN,\n },\n ],\n // Mirrors the fallbackText in _lib/password-admin.ts.\n defaultBody: [\n { block: 'text', text: 'Your password was changed', variant: 'heading' },\n {\n block: 'text',\n text:\n '{{actor.name}} set a new password on your account. You have ' +\n 'been signed out everywhere and will need the new password to ' +\n 'sign back in.',\n variant: 'body',\n },\n { block: 'button', label: 'Sign in', href: '{{signInUrl}}' },\n {\n block: 'text',\n text:\n 'If you did not expect this, contact whoever administers your ' +\n 'account straight away.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/password-admin.ts',\n },\n // Security alerts (AGL-665). Factual and actionable: what happened, from\n // where, and one button to the account-security surface. These must be\n // believed, so the send path only fires them on genuinely new devices —\n // never on every sign-in.\n {\n key: 'security-new-device',\n name: 'New device sign-in',\n description:\n 'Security alert sent when an account signs in from a device it has ' +\n 'not used before. Not sent on routine sign-ins from known devices.',\n deliveredBy: 'resend',\n defaultSubject: 'New sign-in to your {{brand.productName}} account',\n mergeTokens: [\n {\n name: 'device.name',\n description: 'Browser and operating system that signed in',\n sample: 'Chrome on macOS',\n },\n {\n name: 'device.location',\n description: 'Approximate location, from the request IP',\n sample: 'Denver, CO, US',\n },\n {\n name: 'device.ip',\n description: 'IP address the sign-in came from',\n sample: '203.0.113.7',\n },\n {\n name: 'device.time',\n description: 'When the sign-in happened (UTC)',\n sample: '2026-08-08 14:03 UTC',\n },\n {\n name: 'accountSecurityUrl',\n description:\n 'Manage-account Security section — Recent sign-ins, where the ' +\n 'device can be signed out, plus password and passkeys',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/manage/user/security`,\n },\n ],\n // Mirrors the fallbackText in _lib/security-alerts.ts.\n defaultBody: [\n { block: 'text', text: 'New device sign-in', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Your {{brand.productName}} account was just signed in to from a device it has ' +\n 'not used before.',\n variant: 'body',\n },\n { block: 'text', text: 'Device: {{device.name}}', variant: 'body' },\n { block: 'text', text: 'Location: {{device.location}}', variant: 'body' },\n { block: 'text', text: 'IP address: {{device.ip}}', variant: 'body' },\n { block: 'text', text: 'When: {{device.time}}', variant: 'body' },\n {\n // AGL-1959. The button used to say \"Review account security\" and\n // land on the account page's DEFAULT tab, which offered a password\n // change and nothing about the device the mail is about. AGL-665\n // asked for this: \"'Manage your devices' has to land somewhere that\n // can list and revoke sessions, or the alert tells you something is\n // wrong and offers no way to act on it — which is worse than sending\n // nothing.\"\n block: 'button',\n label: 'Manage your devices',\n href: '{{accountSecurityUrl}}',\n },\n {\n block: 'text',\n text:\n 'If this was you, no action is needed. If it was not, sign this ' +\n 'device out from Recent sign-ins and change your password right ' +\n 'away.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/security-alerts.ts',\n },\n {\n key: 'security-passkey-added',\n name: 'New passkey added',\n description:\n 'Security alert sent when a passkey is registered on an account. ' +\n 'The trigger ships with passkey support (AGL-662); until then no ' +\n 'call site sends this.',\n deliveredBy: 'resend',\n defaultSubject: 'A passkey was added to your {{brand.productName}} account',\n mergeTokens: [\n {\n name: 'passkey.label',\n description: 'Name the passkey was saved under',\n sample: 'MacBook Touch ID',\n },\n {\n name: 'device.time',\n description: 'When the passkey was added (UTC)',\n sample: '2026-08-08 14:03 UTC',\n },\n {\n name: 'accountSecurityUrl',\n description: 'Manage-account page where credentials are reviewed',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/manage/user`,\n },\n ],\n // Mirrors the fallbackText in _lib/security-alerts.ts.\n defaultBody: [\n { block: 'text', text: 'New passkey added', variant: 'heading' },\n {\n block: 'text',\n text:\n 'A passkey (\"{{passkey.label}}\") was just added to your {{brand.productName}} ' +\n 'account. It can be used to sign in without your password.',\n variant: 'body',\n },\n { block: 'text', text: 'When: {{device.time}}', variant: 'body' },\n {\n block: 'button',\n label: 'Review account security',\n href: '{{accountSecurityUrl}}',\n },\n {\n block: 'text',\n text:\n 'If you did not add this passkey, change your password from ' +\n 'Manage account right away.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/_lib/security-alerts.ts',\n },\n // Aglyn composes and sends both of these now (AGL-1112). They were\n // Firebase's, from a template nobody could edit — the subject still\n // carried `[aglyn.io]`, a domain the company no longer uses, and the link\n // landed on `aglyn-main.firebaseapp.com`. Neither could be fixed: every\n // write under `notification.sendEmail` is refused with\n // `EMAIL_TEMPLATE_UPDATE_NOT_ALLOWED`.\n //\n // Firebase still MINTS the one-time code, which is the part that has to\n // be its. Only the message around it, and the host the link points at,\n // became ours. So editing these here now does what it says.\n {\n key: 'password-reset',\n name: 'Forgot password',\n description:\n 'Password reset link, sent when someone asks for one from Account ' +\n 'recovery.',\n deliveredBy: 'resend',\n defaultSubject: 'Reset your {{brand.productName}} password',\n mergeTokens: [\n {\n name: 'resetUrl',\n description: 'One-time link that opens the choose-a-new-password page',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/reset-password?oobCode=\\u2026`,\n },\n ],\n // Mirrors the fallbackText in the route, so the designed and undesigned\n // versions say the same thing.\n defaultBody: [\n { block: 'text', text: 'Reset your password', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Someone asked to reset the password for your {{brand.productName}} account. ' +\n 'Choose a new one here:',\n variant: 'body',\n },\n { block: 'button', label: 'Choose a new password', href: '{{resetUrl}}' },\n {\n block: 'text',\n text:\n 'The link expires shortly. If this was not you, you can ignore ' +\n 'this email — your password stays as it is.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/auth/send-password-reset/route.ts',\n },\n {\n key: 'email-verification',\n name: 'Confirm email',\n description:\n 'Address verification link, sent after sign-up and whenever someone ' +\n 'asks for another from the verify screen.',\n deliveredBy: 'resend',\n defaultSubject: 'Confirm your email address',\n mergeTokens: [\n {\n name: 'verifyUrl',\n description: 'One-time link that confirms the address',\n sample: `${SAMPLE_CONSOLE_ORIGIN}/verify-email?oobCode=\\u2026`,\n },\n ],\n defaultBody: [\n { block: 'text', text: 'Confirm your email', variant: 'heading' },\n {\n block: 'text',\n text:\n 'Confirm this address to finish setting up your {{brand.productName}} account:',\n variant: 'body',\n },\n { block: 'button', label: 'Confirm my email', href: '{{verifyUrl}}' },\n {\n block: 'text',\n text:\n 'If you did not create an {{brand.productName}} account, you can ignore this ' +\n 'email.',\n variant: 'caption',\n },\n ],\n source: 'apps/console/app/api/auth/send-verification/route.ts',\n },\n // Stripe-delivered billing email (AGL-767). Aglyn never composes these —\n // Stripe sends them from the Dashboard's Customer-emails and Subscription\n // settings, so they are listed for visibility only and are not designable.\n // Whether each actually sends is a Dashboard toggle the code cannot read;\n // the descriptions say \"if enabled\" rather than asserting it is on.\n {\n key: 'stripe-receipt',\n name: 'Payment receipt',\n description:\n 'Receipt for a successful subscription or invoice payment. Sent by ' +\n 'Stripe if \"Successful payments\" is enabled under Customer emails.',\n deliveredBy: 'stripe',\n defaultSubject: `Your receipt from ${BRAND.ORG_NAME}`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Customer emails',\n },\n {\n key: 'stripe-payment-failed',\n name: 'Payment failed',\n description:\n 'Dunning notice when a subscription charge fails and Stripe retries ' +\n 'it. Sent by Stripe if failed-payment emails are enabled under ' +\n 'Subscriptions and emails.',\n deliveredBy: 'stripe',\n defaultSubject: `Your ${BRAND.ORG_NAME} payment could not be processed`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Subscriptions and emails',\n },\n {\n key: 'stripe-refund',\n name: 'Refund confirmation',\n description:\n 'Confirms a refund back to the customer. Sent by Stripe if ' +\n '\"Refunds\" is enabled under Customer emails.',\n deliveredBy: 'stripe',\n defaultSubject: `Your ${BRAND.ORG_NAME} refund`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Customer emails',\n },\n {\n key: 'stripe-card-expiring',\n name: 'Card expiring soon',\n description:\n 'Reminder that the card on file is about to expire. Sent by Stripe ' +\n 'if expiring-card reminders are enabled under Subscriptions and ' +\n 'emails.',\n deliveredBy: 'stripe',\n defaultSubject: 'Your card on file is expiring soon',\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Subscriptions and emails',\n },\n {\n key: 'stripe-invoice',\n name: 'Invoice',\n description:\n 'A finalized invoice emailed to the organization. Sent by Stripe ' +\n 'when invoice emails are enabled for the billing configuration.',\n deliveredBy: 'stripe',\n defaultSubject: `Your ${BRAND.ORG_NAME} invoice`,\n mergeTokens: [],\n source: 'Stripe Dashboard → Settings → Customer emails',\n },\n ]\n\n/**\n * Every system email Aglyn sends, each with the universal brand tokens\n * appended to its own (AGL-2139).\n *\n * The template's own tokens come FIRST so the editor's palette still leads\n * with what is specific to that email; the brand block reads as the shared\n * footer it is.\n */\nexport const SYSTEM_EMAIL_TEMPLATES: readonly SystemEmailTemplateDefinition[] =\n BASE_SYSTEM_EMAIL_TEMPLATES.map((entry) => ({\n ...entry,\n mergeTokens: [...entry.mergeTokens, ...BRAND_MERGE_TOKENS],\n }))\n\n/** Firestore collection holding the designed templates. */\nexport const SYSTEM_EMAIL_COLLECTION = 'systemEmailTemplates'\n\nexport function getSystemEmailTemplate(\n key: string,\n): SystemEmailTemplateDefinition | undefined {\n return SYSTEM_EMAIL_TEMPLATES.find((entry) => entry.key === key)\n}\n\n/** True when a besigner template can actually change what is delivered. */\nexport function isSystemEmailEditable(\n definition: SystemEmailTemplateDefinition,\n): boolean {\n return definition.deliveredBy === 'resend'\n}\n\n/** The stored template document, when staff have designed one. */\nexport interface SystemEmailTemplateDoc {\n subject?: string\n preheader?: string\n versionId?: string\n updatedAt?: unknown\n updatedByEmail?: string\n}\n\n/** A besigner node in the flat, denormalized map a version stores. */\nexport interface SystemEmailNode {\n $id: string\n componentId: string\n pluginId?: string\n parentId?: string\n props?: Record<string, unknown>\n nodes?: string[]\n}\n\nconst PLACEHOLDER_DEFAULT_BODY: readonly SystemEmailDefaultBlock[] = [\n { block: 'text', text: 'Hello,', variant: 'body' },\n]\n\n/**\n * Turns a template's `defaultBody` into the besigner node map the editor\n * seeds and the send-time default renders (AGL-764/766).\n *\n * One place builds this so the two never diverge — the version a staffer\n * opens is byte-for-byte what a test/send renders when nothing is published.\n * Rooted at {@link EMAIL_NODE_ROOT_ID} so `renderEmailHtml` finds it (AGL-765),\n * with deterministic ids: a version doc only needs ids unique within itself,\n * and determinism keeps the map stable and diffable.\n */\nexport function buildDefaultEmailNodeMap(\n // Structural so the tenant catalog (AGL-770) can seed the same way — every\n // definition that carries a `defaultBody` builds an identical node map.\n definition: { defaultBody?: readonly SystemEmailDefaultBlock[] },\n): Record<string, SystemEmailNode> {\n const blocks = definition.defaultBody?.length\n ? definition.defaultBody\n : PLACEHOLDER_DEFAULT_BODY\n const sectionId = 'default-section'\n const childIds: string[] = []\n const map: Record<string, SystemEmailNode> = {}\n\n blocks.forEach((block, index) => {\n const id = `default-${index}`\n childIds.push(id)\n map[id] =\n block.block === 'button'\n ? {\n $id: id,\n componentId: 'emailButton',\n pluginId: 'email',\n parentId: sectionId,\n props: { children: block.label, href: block.href },\n }\n : {\n $id: id,\n componentId: 'emailText',\n pluginId: 'email',\n parentId: sectionId,\n props: { children: block.text, variant: block.variant ?? 'body' },\n }\n })\n\n map[EMAIL_NODE_ROOT_ID] = {\n $id: EMAIL_NODE_ROOT_ID,\n componentId: 'div',\n nodes: [sectionId],\n }\n map[sectionId] = {\n $id: sectionId,\n componentId: 'emailSection',\n pluginId: 'email',\n parentId: EMAIL_NODE_ROOT_ID,\n nodes: childIds,\n }\n return map\n}\n"],"names":["SAMPLE_CONSOLE_ORIGIN","process","env","NEXT_PUBLIC_CONSOLE_URL","trim","replace","SAMPLE_SUPPORT_URL","NEXT_PUBLIC_PLATFORM_SUPPORT_URL","NEXT_PUBLIC_OPERATOR_SUPPORT_EMAIL","BRAND","EMAIL_NODE_ROOT_ID","BRAND_MERGE_TOKENS","name","description","sample","ORG_NAME","BASE_SYSTEM_EMAIL_TEMPLATES","key","deliveredBy","defaultSubject","mergeTokens","defaultBody","block","text","variant","label","href","source","SYSTEM_EMAIL_TEMPLATES","map","entry","SYSTEM_EMAIL_COLLECTION","getSystemEmailTemplate","find","isSystemEmailEditable","definition","PLACEHOLDER_DEFAULT_BODY","buildDefaultEmailNodeMap","blocks","length","sectionId","childIds","forEach","index","id","push","$id","componentId","pluginId","parentId","props","children","nodes"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;CAcC,GACD,MAAMA,wBACJ,AAACC,CAAAA,QAAQC,GAAG,CAACC,uBAAuB,IAAI,EAAC,EAAGC,IAAI,GAAGC,OAAO,CAAC,QAAQ,OACnE;AAEF;;;;;;;;;;;;CAYC,GACD,MAAMC,qBACJ,AAACL,CAAAA,QAAQC,GAAG,CAACK,gCAAgC,IAAI,EAAC,EAAGH,IAAI,MACxD,CAAA,AAACH,CAAAA,QAAQC,GAAG,CAACM,kCAAkC,IAAI,EAAC,EAAGJ,IAAI,KACxD,CAAC,OAAO,EAAE,AAACH,CAAAA,QAAQC,GAAG,CAACM,kCAAkC,IAAI,EAAC,EAAGJ,IAAI,IAAI,GACzE,GAAGJ,sBAAsB,QAAQ,CAAC,AAAD;AAEvC,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,0EAA0E;AAC1E,wCAAwC;AACxC,SAASS,KAAK,QAAQ,iCAAgC;AACtD,SAASC,kBAAkB,QAAQ,oBAAgB;AAgFnD;;;;;;;;;;;CAWC,GACD;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,qBAAuD;IAClE;QACEC,MAAM;QACNC,aAAa;QACbC,QAAQL,MAAMM,QAAQ;IACxB;IACA;QACEH,MAAM;QACNC,aAAa;QACbC,QAAQL,MAAMM,QAAQ;IACxB;IACA;QACEH,MAAM;QACNC,aAAa;QACbC,QAAQR;IACV;CACD,CAAA;AAED,sEAAsE;AACtE,sEAAsE;AACtE,4EAA4E;AAC5E,2EAA2E;AAC3E,2DAA2D;AAE3D,MAAMU,8BACJ;IACE;QACEC,KAAK;QACLL,MAAM;QACNC,aACE,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,gDAAgD;QAChDqB,aAAa;YACX;gBACEC,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MACE,gEACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAWC,MAAM;YAAgB;SAC5D;QACDC,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,sEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,+DAA+D;QAC/DO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAkCC,SAAS;YAAU;YAC5E;gBACEF,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAAqBC,SAAS;YAAO;SAC7D;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,0DAA0D;QAC1DO,aAAa;YACX;gBACEC,OAAO;gBACPC,MACE,+DACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAAiBC,SAAS;YAAO;SACzD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,oDAAoD;QACpDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAoCC,SAAS;YAAU;YAC9E;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAuBC,MAAM;YAAiB;SACzE;QACDC,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,CAAC,sBAAsB,EAAEJ,MAAMM,QAAQ,CAAC,2BAA2B,CAAC,GACpE,uEACA;QACFG,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,qDAAqD;QACrDqB,aAAa;YACX;gBACEC,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAA8BC,MAAM;YAAgB;SAC/E;QACDC,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,sEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,2DAA2D;QAC3DO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAA6BC,SAAS;YAAU;YACvE;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MAAM;gBACNC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,8DAA8D;QAC9DO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAA4BC,SAAS;YAAU;YACtE;gBACEF,OAAO;gBACPC,MACE,iEACA;gBACFC,SAAS;YACX;YACA;gBACEF,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,iEAAiE;IACjE,sEAAsE;IACtE,wEAAwE;IACxE,yDAAyD;IACzD;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,iEACA,6DACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,yBAAyB,CAAC;YAC7D;YACA;gBACEY,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;SACD;QACD,sDAAsD;QACtDO,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAuBC,SAAS;YAAU;YACjE;gBACEF,OAAO;gBACPC,MACE,4EACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAsBC,MAAM;YAAe;YACrE;gBACEJ,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,iEACA,qEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQd;YACV;SACD;QACD,sDAAsD;QACtDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAA6BC,SAAS;YAAU;YACvE;gBACEF,OAAO;gBACPC,MACE,iEACA,kEACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAWC,MAAM;YAAgB;YAC3D;gBACEJ,OAAO;gBACPC,MACE,kEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,yEAAyE;IACzE,uEAAuE;IACvE,wEAAwE;IACxE,0BAA0B;IAC1B;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aACE,kEACA;gBACFC,QAAQ,GAAGd,sBAAsB,qBAAqB,CAAC;YACzD;SACD;QACD,uDAAuD;QACvDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAsBC,SAAS;YAAU;YAChE;gBACEF,OAAO;gBACPC,MACE,mFACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAA2BC,SAAS;YAAO;YAClE;gBAAEF,OAAO;gBAAQC,MAAM;gBAAiCC,SAAS;YAAO;YACxE;gBAAEF,OAAO;gBAAQC,MAAM;gBAA6BC,SAAS;YAAO;YACpE;gBAAEF,OAAO;gBAAQC,MAAM;gBAAyBC,SAAS;YAAO;YAChE;gBACE,iEAAiE;gBACjE,mEAAmE;gBACnE,iEAAiE;gBACjE,oEAAoE;gBACpE,oEAAoE;gBACpE,qEAAqE;gBACrE,YAAY;gBACZF,OAAO;gBACPG,OAAO;gBACPC,MAAM;YACR;YACA;gBACEJ,OAAO;gBACPC,MACE,oEACA,oEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,qEACA,qEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ;YACV;YACA;gBACEF,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,YAAY,CAAC;YAChD;SACD;QACD,uDAAuD;QACvDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAqBC,SAAS;YAAU;YAC/D;gBACEF,OAAO;gBACPC,MACE,kFACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAQC,MAAM;gBAAyBC,SAAS;YAAO;YAChE;gBACEF,OAAO;gBACPG,OAAO;gBACPC,MAAM;YACR;YACA;gBACEJ,OAAO;gBACPC,MACE,gEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,mEAAmE;IACnE,oEAAoE;IACpE,0EAA0E;IAC1E,wEAAwE;IACxE,uDAAuD;IACvD,uCAAuC;IACvC,EAAE;IACF,wEAAwE;IACxE,uEAAuE;IACvE,4DAA4D;IAC5D;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,sEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,8BAA8B,CAAC;YAClE;SACD;QACD,wEAAwE;QACxE,+BAA+B;QAC/BqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAuBC,SAAS;YAAU;YACjE;gBACEF,OAAO;gBACPC,MACE,iFACA;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAyBC,MAAM;YAAe;YACxE;gBACEJ,OAAO;gBACPC,MACE,mEACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,wEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa;YACX;gBACER,MAAM;gBACNC,aAAa;gBACbC,QAAQ,GAAGd,sBAAsB,4BAA4B,CAAC;YAChE;SACD;QACDqB,aAAa;YACX;gBAAEC,OAAO;gBAAQC,MAAM;gBAAsBC,SAAS;YAAU;YAChE;gBACEF,OAAO;gBACPC,MACE;gBACFC,SAAS;YACX;YACA;gBAAEF,OAAO;gBAAUG,OAAO;gBAAoBC,MAAM;YAAgB;YACpE;gBACEJ,OAAO;gBACPC,MACE,iFACA;gBACFC,SAAS;YACX;SACD;QACDG,QAAQ;IACV;IACA,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,0EAA0E;IAC1E,oEAAoE;IACpE;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,kBAAkB,EAAEV,MAAMM,QAAQ,EAAE;QACrDK,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,wEACA,mEACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,KAAK,EAAEV,MAAMM,QAAQ,CAAC,+BAA+B,CAAC;QACvEK,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,+DACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,KAAK,EAAEV,MAAMM,QAAQ,CAAC,OAAO,CAAC;QAC/CK,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,uEACA,oEACA;QACFK,aAAa;QACbC,gBAAgB;QAChBC,aAAa,EAAE;QACfO,QAAQ;IACV;IACA;QACEV,KAAK;QACLL,MAAM;QACNC,aACE,qEACA;QACFK,aAAa;QACbC,gBAAgB,CAAC,KAAK,EAAEV,MAAMM,QAAQ,CAAC,QAAQ,CAAC;QAChDK,aAAa,EAAE;QACfO,QAAQ;IACV;CACD;AAEH;;;;;;;CAOC,GACD,OAAO,MAAMC,yBACXZ,4BAA4Ba,GAAG,CAAC,CAACC,QAAW,aACvCA;QACHV,aAAa;eAAIU,MAAMV,WAAW;eAAKT;SAAmB;QACzD;AAEL,yDAAyD,GACzD,OAAO,MAAMoB,0BAA0B,uBAAsB;AAE7D,OAAO,SAASC,uBACdf,GAAW;IAEX,OAAOW,uBAAuBK,IAAI,CAAC,CAACH,QAAUA,MAAMb,GAAG,KAAKA;AAC9D;AAEA,yEAAyE,GACzE,OAAO,SAASiB,sBACdC,UAAyC;IAEzC,OAAOA,WAAWjB,WAAW,KAAK;AACpC;AAqBA,MAAMkB,2BAA+D;IACnE;QAAEd,OAAO;QAAQC,MAAM;QAAUC,SAAS;IAAO;CAClD;AAED;;;;;;;;;CASC,GACD,OAAO,SAASa,yBACd,2EAA2E;AAC3E,wEAAwE;AACxEF,UAAgE;QAEjDA;IAAf,MAAMG,SAASH,EAAAA,0BAAAA,WAAWd,WAAW,qBAAtBc,wBAAwBI,MAAM,IACzCJ,WAAWd,WAAW,GACtBe;IACJ,MAAMI,YAAY;IAClB,MAAMC,WAAqB,EAAE;IAC7B,MAAMZ,MAAuC,CAAC;IAE9CS,OAAOI,OAAO,CAAC,CAACpB,OAAOqB;YAiB2BrB;QAhBhD,MAAMsB,KAAK,CAAC,QAAQ,EAAED,OAAO;QAC7BF,SAASI,IAAI,CAACD;QACdf,GAAG,CAACe,GAAG,GACLtB,MAAMA,KAAK,KAAK,WACZ;YACEwB,KAAKF;YACLG,aAAa;YACbC,UAAU;YACVC,UAAUT;YACVU,OAAO;gBAAEC,UAAU7B,MAAMG,KAAK;gBAAEC,MAAMJ,MAAMI,IAAI;YAAC;QACnD,IACA;YACEoB,KAAKF;YACLG,aAAa;YACbC,UAAU;YACVC,UAAUT;YACVU,OAAO;gBAAEC,UAAU7B,MAAMC,IAAI;gBAAEC,OAAO,GAAEF,iBAAAA,MAAME,OAAO,YAAbF,iBAAiB;YAAO;QAClE;IACR;IAEAO,GAAG,CAACnB,mBAAmB,GAAG;QACxBoC,KAAKpC;QACLqC,aAAa;QACbK,OAAO;YAACZ;SAAU;IACpB;IACAX,GAAG,CAACW,UAAU,GAAG;QACfM,KAAKN;QACLO,aAAa;QACbC,UAAU;QACVC,UAAUvC;QACV0C,OAAOX;IACT;IACA,OAAOZ;AACT"}