@aglyn/plugins-email 1.0.0-beta.173 → 1.0.0-beta.174
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 +17 -17
- package/src/lib/components/dynamic-list-rule-fields.d.ts +4 -3
- package/src/lib/components/dynamic-list-rule-fields.js +1 -1
- package/src/lib/components/dynamic-list-rule-fields.js.map +1 -1
- package/src/lib/components/email-template-detail.js +22 -7
- package/src/lib/components/email-template-detail.js.map +1 -1
- package/src/lib/server.js +37 -19
- package/src/lib/server.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-email",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.174",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
42
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
43
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.174",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.174",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.174",
|
|
31
|
+
"@aglyn/shared-data-types": "1.0.0-beta.174",
|
|
32
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.174",
|
|
33
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.174",
|
|
34
|
+
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.174",
|
|
35
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.174",
|
|
36
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.174",
|
|
37
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.174",
|
|
38
|
+
"@aglyn/shared-util-email": "1.0.0-beta.174",
|
|
39
|
+
"@aglyn/shared-util-http": "1.0.0-beta.174",
|
|
40
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.174",
|
|
41
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.174",
|
|
42
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.174",
|
|
43
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.174",
|
|
44
44
|
"@swc/helpers": "0.5.23"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -135,11 +135,12 @@ export interface DynamicListRuleFieldsProps {
|
|
|
135
135
|
/** `['orgs', orgId]` — the resolved org scope the caller already holds. */
|
|
136
136
|
scope: readonly [string, string];
|
|
137
137
|
/**
|
|
138
|
-
* The site
|
|
138
|
+
* The site the campaign picker speaks for: it offers the org's campaigns
|
|
139
|
+
* placed on this site.
|
|
139
140
|
*
|
|
140
141
|
* A list is org-owned and its rule is materialized against ONE site's silos,
|
|
141
|
-
* which is the same site whose
|
|
142
|
-
*
|
|
142
|
+
* which is the same site whose picker a form or a screen is filed from.
|
|
143
|
+
* Offering campaigns the site does not carry would offer ids the sweep can
|
|
143
144
|
* never match, because the membership it reads was written by that site.
|
|
144
145
|
*/
|
|
145
146
|
hostId: string;
|
|
@@ -592,7 +592,7 @@ export function DynamicListRuleFields(props) {
|
|
|
592
592
|
const viewDocs = useOrgCrmViews(scope);
|
|
593
593
|
const listDocs = useOrgLists(scope);
|
|
594
594
|
/*
|
|
595
|
-
* The site
|
|
595
|
+
* The campaigns placed on the site, read because this form is on screen.
|
|
596
596
|
*
|
|
597
597
|
* `enabled` is off by default on the hook for the surfaces that render a
|
|
598
598
|
* picker beside fields a reader came for; here the picker IS one of the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/dynamic-list-rule-fields.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * Every field of `DynamicListRule`, with a control for each.\n *\n * ## Why the whole rule, and why in one place\n *\n * Every dimension the matcher reads has a control here, including the two\n * that are answered by a keyed lookup rather than by the person's own record\n * — engagement, and membership of another audience — and the combinator that\n * decides whether the filters are ANDed, ORed or inverted. Needing to\n * hand-write a stored structure to reach a shipped engine is a missing\n * picker, not a power-user affordance, which is the standard this form is\n * held to rather than a count of fields.\n *\n * One component rather than a form on the create card and a second on the edit\n * page: the two would drift, and the half that drifts is whichever the\n * merchant is not looking at. A rule authored at creation and the same rule\n * reopened for editing are the same questions.\n *\n * ## The draft is text, the value is a rule\n *\n * The controls hold what was TYPED — `vip, ` is a legitimate intermediate\n * state of a tag list, and a component that round-tripped every keystroke\n * through `normalizeDynamicListRule` would delete the comma the moment it was\n * typed. So the draft is strings, `draftToRule` is the one place it becomes a\n * rule, and the parent never sees anything else.\n *\n * ## Money is entered the way a merchant says it\n *\n * `ltvCentsAtLeast` is stored in cents. A field labeled for the stored unit\n * turns \"customers who spent over 500\" into customers who spent over five\n * dollars, silently, and the audience looks plausible either way. The control\n * takes whole currency units and this file owns the only multiplication.\n *\n * ## It reads the segments, and only when it is on screen\n *\n * The segment picker needs the org's saved segments, through\n * `useOrgContactSegments`. That listen belongs to this component so it opens\n * when a rule is actually being authored — the audiences section's cost is a\n * list of lists, and a segment read on arrival would be a read for a form\n * nobody has opened.\n */\n\nimport {\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_SOURCE_LABELS,\n DYNAMIC_LIST_CUSTOM_OPS,\n normalizeDynamicListRule,\n type ContactLifecycleStage,\n type ContactSource,\n type DynamicListCustomClause,\n type DynamicListCustomOp,\n type DynamicListDimensions,\n type DynamicListRule,\n type DynamicListSource,\n} from '@aglyn/aglyn'\nimport {\n Autocomplete,\n Box,\n Button,\n Divider,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n useHostCampaigns,\n useOrgMemberOptions,\n} from '@aglyn/tenant-feature-instance'\nimport CampaignPicker from '@aglyn/shared-ui-email-campaigns/components/campaign-picker.component'\nimport { useOrgCompanyOptions } from '../hooks/use-org-company-options'\nimport { useOrgContactFields } from '../hooks/use-org-contact-fields'\nimport { useOrgContactSegments } from '../hooks/use-org-contact-segments'\nimport { useOrgCrmViews } from '../hooks/use-org-crm-views'\nimport { useOrgLists } from '../hooks/use-org-lists'\n\n/** How each silo reads on screen. */\nconst SOURCE_LABELS: Record<DynamicListSource, string> = {\n contacts: 'Contacts',\n leads: 'Leads',\n siteMembers: 'Site members',\n formSubmissions: 'Form submissions',\n}\n\n/** The same silos, lower-cased for the middle of a sentence. */\nconst SOURCE_PHRASES: Record<DynamicListSource, string> = {\n contacts: 'contacts',\n leads: 'leads',\n siteMembers: 'site members',\n formSubmissions: 'form submissions',\n}\n\n/** A day, in the same UTC calendar the rule's boundaries are stored against. */\nconst dayLabel = (ms: number) => new Date(ms).toISOString().slice(0, 10)\n\n/**\n * The rule, in sentences a merchant can read back to themselves.\n *\n * Every clause names a dimension that is actually SET, so a rule with four\n * dimensions reads as four clauses and a rule with one reads as one — an\n * explanation that also lists the fields a rule does not use is an explanation\n * the reader has to subtract from. Rendered live beside the controls, because\n * the question a filter form has to answer continuously is \"who does this\n * describe\", and a form of eleven boxes does not answer it on its own.\n */\n/** Names for the ids a rule stores, so a clause can say \"Customers\", not a uid. */\nexport interface DynamicListRuleNames {\n /** List id → its name. */\n lists?: Record<string, string>\n /** Segment id → its name. */\n segments?: Record<string, string>\n /** Saved Contacts view id → its name. */\n views?: Record<string, string>\n /** Campaign id → its name. */\n campaigns?: Record<string, string>\n /** Team member uid → how they read on the roster. */\n members?: Record<string, string>\n /** Company id → its name. */\n companies?: Record<string, string>\n /** Custom field key → the definition's label. */\n fields?: Record<string, string>\n}\n\n/** An id, as its name when one is known and as itself when none is. */\nconst named = (id: string, names: Record<string, string> | undefined): string =>\n names?.[id] || id\n\n/**\n * How each custom-field operator reads, as the verb of a clause.\n *\n * `neq` carries its lean in the sentence: the matcher requires a VALUE that\n * differs, so a merchant excluding one plan does not sweep in everyone whose\n * plan was never recorded — and a reader checking a paragraph against their\n * intent cannot be expected to know that from the word \"not\".\n */\nconst CUSTOM_OP_LABELS: Record<DynamicListCustomOp, string> = {\n eq: 'is',\n neq: 'is not',\n contains: 'contains',\n gt: 'is more than',\n lt: 'is less than',\n set: 'is set',\n unset: 'is not set',\n}\n\n/** The operators that take no value — the clause is about presence. */\nconst PRESENCE_OPS: ReadonlySet<DynamicListCustomOp> = new Set(['set', 'unset'])\n\n/** One custom clause as a sentence fragment: `Plan is enterprise`. */\nconst describeCustomClause = (\n clause: DynamicListCustomClause,\n names?: DynamicListRuleNames,\n): string => {\n const field = named(clause.key, names?.fields)\n const verb = CUSTOM_OP_LABELS[clause.op]\n if (clause.op === 'set' || clause.op === 'unset') return `${field} ${verb}`\n const value = String(clause.value ?? '')\n return clause.op === 'neq'\n ? `${field} ${verb} ${value} (a blank does not count)`\n : `${field} ${verb} ${value}`\n}\n\n/** One AND-block, as clauses. The rule's own block and each branch share it. */\nfunction describeDimensions(\n rule: DynamicListDimensions,\n names?: DynamicListRuleNames,\n): string[] {\n const clauses: string[] = []\n if (rule.tags?.length) {\n clauses.push(`Tagged any of: ${rule.tags.join(', ')}.`)\n }\n if (rule.captureSources?.length) {\n clauses.push(\n `Captured by: ${rule.captureSources\n .map((source) => CONTACT_SOURCE_LABELS[source] ?? source)\n .join(', ')}.`,\n )\n }\n if (rule.formNames?.length) {\n clauses.push(`Submitted any of: ${rule.formNames.join(', ')}.`)\n }\n /*\n * The clause names the SILOS the dimension applies to, like every other\n * silo-specific clause here. A lead and a site member carry no campaign, so\n * the sentence has to say that the filter passes them through rather than\n * leaving a reader to conclude that a rule drawing from leads and naming a\n * campaign selects no leads.\n */\n if (rule.campaignIds?.length) {\n clauses.push(\n `Filed under any of these campaigns: ${rule.campaignIds\n .map((id) => named(id, names?.campaigns))\n .join(', ')} — contacts and form submissions only.`,\n )\n }\n if (rule.createdAfterMs !== undefined) {\n clauses.push(`Created on or after ${dayLabel(rule.createdAfterMs)}.`)\n }\n if (rule.createdBeforeMs !== undefined) {\n clauses.push(`Created before ${dayLabel(rule.createdBeforeMs)}.`)\n }\n const behavior = rule.behavior\n if (behavior?.ordersCountAtLeast !== undefined) {\n clauses.push(`At least ${behavior.ordersCountAtLeast} orders.`)\n }\n if (behavior?.ltvCentsAtLeast !== undefined) {\n clauses.push(`Spent at least ${behavior.ltvCentsAtLeast / 100}.`)\n }\n if (behavior?.lastPurchaseWithinDays !== undefined) {\n clauses.push(\n `Bought within the last ${behavior.lastPurchaseWithinDays} days.`,\n )\n }\n if (behavior?.noPurchaseForDays !== undefined) {\n clauses.push(`Has bought nothing for ${behavior.noPurchaseForDays} days.`)\n }\n /*\n * The quiet arms say \"or never\" out loud.\n *\n * The engine counts a person with no open on record as having not opened,\n * which is the opposite lean from `noPurchaseForDays` two clauses above —\n * and a reader checking a paragraph against their intent cannot be expected\n * to remember which dimension leans which way. So the sentence carries it.\n */\n const engagement = rule.engagement\n if (engagement?.openedWithinDays !== undefined) {\n clauses.push(\n `Opened one of your emails in the last ${engagement.openedWithinDays} days.`,\n )\n }\n if (engagement?.clickedWithinDays !== undefined) {\n clauses.push(\n `Clicked a link in one of your emails in the last ` +\n `${engagement.clickedWithinDays} days.`,\n )\n }\n if (engagement?.notOpenedForDays !== undefined) {\n clauses.push(\n `Has opened nothing for ${engagement.notOpenedForDays} days, or never ` +\n `opened anything.`,\n )\n }\n if (engagement?.notClickedForDays !== undefined) {\n clauses.push(\n `Has clicked nothing for ${engagement.notClickedForDays} days, or never ` +\n `clicked anything.`,\n )\n }\n if (rule.inListIds?.length) {\n clauses.push(\n `Already on: ${rule.inListIds\n .map((id) => named(id, names?.lists))\n .join(', ')}.`,\n )\n }\n if (rule.notInListIds?.length) {\n clauses.push(\n `Not on: ${rule.notInListIds\n .map((id) => named(id, names?.lists))\n .join(', ')}.`,\n )\n }\n /*\n * THE CRM DIMENSIONS (AGL-2603). Each names people, stages and companies\n * the way the pickers show them, never by id: a clause reading `uid-8f2a`\n * is a clause nobody can check against their intent. The custom clauses\n * are one sentence each, because each is its own condition and a reader\n * adding a second one has narrowed the audience by exactly one sentence.\n */\n if (rule.ownerUids?.length) {\n clauses.push(\n `Owned by: ${rule.ownerUids\n .map((uid) => named(uid, names?.members))\n .join(', ')}.`,\n )\n }\n if (rule.lifecycleStages?.length) {\n clauses.push(\n `In stage: ${rule.lifecycleStages\n .map((stage) => CONTACT_LIFECYCLE_STAGE_LABELS[stage] ?? stage)\n .join(', ')}.`,\n )\n }\n if (rule.companyIds?.length) {\n clauses.push(\n `At company: ${rule.companyIds\n .map((id) => named(id, names?.companies))\n .join(', ')}.`,\n )\n }\n /*\n * \"One of your campaigns\", against the engagement arms above that say\n * \"one of your emails\": those count every message this workspace sent the\n * address, this counts the campaigns the reading site's group sent — the\n * sentence is the only place a reader can tell the two windows apart.\n */\n if (rule.engagedWithinDays !== undefined) {\n clauses.push(\n `Opened or clicked one of your campaigns in the last ` +\n `${rule.engagedWithinDays} days.`,\n )\n }\n for (const clause of rule.custom ?? []) {\n clauses.push(`${describeCustomClause(clause, names)}.`)\n }\n return clauses\n}\n\nexport function describeDynamicListRule(\n rule: DynamicListRule,\n names?: DynamicListRuleNames,\n): string[] {\n const clauses: string[] = []\n clauses.push(\n rule.sources.length\n ? `Draws from ${rule.sources\n .map((source) => SOURCE_PHRASES[source] ?? source)\n .join(', ')}.`\n : 'Draws from nothing, so it matches nobody.',\n )\n if (rule.segmentId) {\n clauses.push(\n `Reuses saved segment ${named(rule.segmentId, names?.segments)}.`,\n )\n }\n if (rule.viewId) {\n clauses.push(`Reuses saved view ${named(rule.viewId, names?.views)}.`)\n }\n const own = describeDimensions(rule, names)\n if (rule.negate && own.length) {\n /*\n * A negated block is ONE clause, not a list of them.\n *\n * \"Excludes anyone who is tagged vip. Excludes anyone who has spent over\n * 500.\" would read as two independent exclusions, and the rule is one:\n * it excludes people who are BOTH. Somebody checking a paragraph against\n * their intent would come away with the wrong audience.\n */\n clauses.push(\n `Excludes anyone matching all of: ${own\n .map((clause) => clause.replace(/\\.$/, ''))\n .join('; ')}.`,\n )\n } else {\n clauses.push(...own)\n }\n /*\n * The OR branches are ONE clause covering all of them, not one clause each.\n *\n * A candidate has to satisfy at least one branch — so \"at least one of\"\n * printed once per branch would read as a separate requirement per branch,\n * which is the AND this operator exists to escape. The whole disjunction is\n * therefore rendered as a single sentence, with each branch's own filters\n * joined by \"and\" inside it.\n */\n const branches = (rule.any ?? [])\n .map((group) => {\n const inner = describeDimensions(group, names)\n if (!inner.length) return ''\n const joined = inner\n .map((clause) => clause.replace(/\\.$/, '').toLowerCase())\n .join(' and ')\n return group.negate ? `not (${joined})` : joined\n })\n .filter(Boolean)\n if (branches.length) {\n clauses.push(`And at least one of these: ${branches.join('; or ')}.`)\n }\n return clauses\n}\n\n/**\n * How the filters below combine.\n *\n * The three shapes this form authors, mapped onto the rule's two operators:\n * `all` is the plain AND block, `none` is that block with `negate`, and `any`\n * puts each filter into its own OR branch. A saved segment is outside all\n * three — it is resolved once from `segmentId` and always applies, which the\n * control says on screen rather than leaving to be discovered.\n */\nexport type DynamicListRuleMatch = 'all' | 'any' | 'none'\n\n/** The rule as the controls hold it: what was typed, not what it means. */\nexport interface DynamicListRuleDraft {\n sources: DynamicListSource[]\n match: DynamicListRuleMatch\n segmentId: string\n /** A saved Contacts view's id, resolved by the sweep beside the segment (AGL-2617). */\n viewId: string\n tags: string\n captureSources: ContactSource[]\n formNames: string\n /** Campaign ids, from the site's own campaigns picker. */\n campaignIds: string[]\n /** `yyyy-mm-dd`, read as UTC — the same instant `Date.parse` gives it. */\n createdAfter: string\n createdBefore: string\n ordersCountAtLeast: string\n /** WHOLE currency units. `draftToRule` is the only place this becomes cents. */\n ltvAtLeast: string\n lastPurchaseWithinDays: string\n noPurchaseForDays: string\n openedWithinDays: string\n clickedWithinDays: string\n notOpenedForDays: string\n notClickedForDays: string\n inListIds: string[]\n notInListIds: string[]\n /*\n * THE CRM DIMENSIONS (AGL-2603), held as the rule holds them.\n *\n * These are picked, not typed — a uid from the roster, a stage from the\n * fixed list, a company from a search — so there is no free-text\n * intermediate state to preserve and the draft carries the ids. The custom\n * clauses carry the TYPED value: a number field's control hands back a\n * number and a checkbox's a boolean, so the value the sentences describe\n * and the value the matcher compares are the same value.\n */\n ownerUids: string[]\n lifecycleStages: ContactLifecycleStage[]\n companyIds: string[]\n custom: DynamicListCustomClause[]\n /**\n * The re-engagement window (AGL-2616), typed like the purchase windows\n * — days, as text, so an empty box is not a zero-day window.\n */\n engagedWithinDays: string\n}\n\nexport const EMPTY_RULE_DRAFT: DynamicListRuleDraft = {\n sources: ['contacts'],\n match: 'all',\n segmentId: '',\n viewId: '',\n tags: '',\n captureSources: [],\n formNames: '',\n campaignIds: [],\n createdAfter: '',\n createdBefore: '',\n ordersCountAtLeast: '',\n ltvAtLeast: '',\n lastPurchaseWithinDays: '',\n noPurchaseForDays: '',\n openedWithinDays: '',\n clickedWithinDays: '',\n notOpenedForDays: '',\n notClickedForDays: '',\n inListIds: [],\n notInListIds: [],\n ownerUids: [],\n lifecycleStages: [],\n companyIds: [],\n custom: [],\n engagedWithinDays: '',\n}\n\n/** Comma-separated free text → the trimmed, non-empty values. */\nexport const splitRuleList = (value: string): string[] =>\n value\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n/**\n * A day stamp for a date input, in UTC.\n *\n * `Date.parse('2026-01-01')` is UTC midnight, so reading the value back\n * through the local calendar would move the boundary by the reader's offset —\n * a rule saved in Los Angeles would reopen naming the previous day, and saving\n * it again would walk it backwards once per edit.\n */\nconst dayStamp = (ms: number | undefined): string =>\n ms === undefined || !Number.isFinite(ms) ? '' : dayLabel(ms)\n\n/**\n * A stored rule, as the controls should show it.\n *\n * ⚠️ The three shapes {@link DynamicListRuleMatch} names are the shapes this\n * form AUTHORS. A rule written through the REST API can hold branches the\n * controls have no place for — a branch with its own `negate`, or top-level\n * filters alongside branches — and this reads such a rule back as the union\n * of every filter in it, in `any` mode. It is not a silent rewrite: the\n * sentences above the controls are computed from `draftToRule(draft)`, so\n * what a save would store is on screen before the reader saves it.\n */\nexport function ruleToDraft(rule: DynamicListRule): DynamicListRuleDraft {\n // Every filter the rule carries, wherever it carries it. The branches are\n // read for their VALUES; how they combined is answered by `match` below.\n const blocks = [rule, ...(rule.any ?? [])]\n const first = <T,>(read: (block: DynamicListDimensions) => T | undefined): T | undefined => {\n for (const block of blocks) {\n const value = read(block)\n if (value !== undefined) return value\n }\n return undefined\n }\n const list = (read: (block: DynamicListDimensions) => string[] | undefined): string[] => {\n const found = new Set<string>()\n for (const block of blocks) for (const entry of read(block) ?? []) found.add(entry)\n return [...found]\n }\n const number = (read: (block: DynamicListDimensions) => number | undefined): string => {\n const value = first(read)\n return value === undefined ? '' : String(value)\n }\n const ltvCents = first((block) => block.behavior?.ltvCentsAtLeast)\n return {\n sources: rule.sources ?? [],\n match: rule.any?.length ? 'any' : rule.negate ? 'none' : 'all',\n segmentId: rule.segmentId ?? '',\n viewId: rule.viewId ?? '',\n tags: list((block) => block.tags).join(', '),\n captureSources: list((block) => block.captureSources) as ContactSource[],\n formNames: list((block) => block.formNames).join(', '),\n campaignIds: list((block) => block.campaignIds),\n createdAfter: dayStamp(first((block) => block.createdAfterMs)),\n createdBefore: dayStamp(first((block) => block.createdBeforeMs)),\n ordersCountAtLeast: number((block) => block.behavior?.ordersCountAtLeast),\n ltvAtLeast: ltvCents === undefined ? '' : String(ltvCents / 100),\n lastPurchaseWithinDays: number(\n (block) => block.behavior?.lastPurchaseWithinDays,\n ),\n noPurchaseForDays: number((block) => block.behavior?.noPurchaseForDays),\n openedWithinDays: number((block) => block.engagement?.openedWithinDays),\n clickedWithinDays: number((block) => block.engagement?.clickedWithinDays),\n notOpenedForDays: number((block) => block.engagement?.notOpenedForDays),\n notClickedForDays: number((block) => block.engagement?.notClickedForDays),\n inListIds: list((block) => block.inListIds),\n notInListIds: list((block) => block.notInListIds),\n ownerUids: list((block) => block.ownerUids),\n lifecycleStages: list(\n (block) => block.lifecycleStages,\n ) as ContactLifecycleStage[],\n companyIds: list((block) => block.companyIds),\n engagedWithinDays: number((block) => block.engagedWithinDays),\n // Clauses are objects, so the string-set dedupe above cannot hold them;\n // two blocks carrying the same clause are one condition, keyed on its\n // whole shape.\n custom: (() => {\n const seen = new Map<string, DynamicListCustomClause>()\n for (const block of blocks) {\n for (const clause of block.custom ?? []) {\n seen.set(JSON.stringify(clause), clause)\n }\n }\n return [...seen.values()]\n })(),\n }\n}\n\n/**\n * A typed number, or nothing at all.\n *\n * An empty box is not zero. `Number('')` is `0`, and a rule normalizer that\n * accepts zero would turn every field the merchant left alone into an active\n * filter — `noPurchaseForDays: 0` matches nobody, so an untouched form would\n * quietly produce an empty audience.\n */\nconst typedNumber = (value: string): number | undefined => {\n const trimmed = value.trim()\n if (!trimmed) return undefined\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined\n}\n\n/**\n * One filter of the draft, as the smallest block it can live in.\n *\n * `any` mode puts each of these in its own OR branch, so this list is what\n * decides how finely \"any of these filters\" is read: per CONTROL, which is\n * what a reader who just typed into eleven boxes means by \"any of them\". A\n * coarser split — the whole purchase-history block as one branch — would ask\n * them to satisfy three purchase conditions together to match the branch.\n */\nfunction draftDimensions(draft: DynamicListRuleDraft): Record<string, unknown>[] {\n const blocks: Record<string, unknown>[] = []\n const tags = splitRuleList(draft.tags)\n if (tags.length) blocks.push({ tags })\n if (draft.captureSources.length) {\n blocks.push({ captureSources: draft.captureSources })\n }\n const formNames = splitRuleList(draft.formNames)\n if (formNames.length) blocks.push({ formNames })\n if (draft.campaignIds.length) blocks.push({ campaignIds: draft.campaignIds })\n const createdAfterMs = draft.createdAfter\n ? Date.parse(draft.createdAfter)\n : undefined\n if (createdAfterMs !== undefined && Number.isFinite(createdAfterMs)) {\n blocks.push({ createdAfterMs })\n }\n const createdBeforeMs = draft.createdBefore\n ? Date.parse(draft.createdBefore)\n : undefined\n if (createdBeforeMs !== undefined && Number.isFinite(createdBeforeMs)) {\n blocks.push({ createdBeforeMs })\n }\n const ordersCountAtLeast = typedNumber(draft.ordersCountAtLeast)\n if (ordersCountAtLeast !== undefined) {\n blocks.push({ behavior: { ordersCountAtLeast } })\n }\n // Whole units in, cents out — rounded, because a half-cent threshold is not\n // a quantity any order total can be compared against.\n const ltv = typedNumber(draft.ltvAtLeast)\n if (ltv !== undefined) {\n blocks.push({ behavior: { ltvCentsAtLeast: Math.round(ltv * 100) } })\n }\n const lastPurchaseWithinDays = typedNumber(draft.lastPurchaseWithinDays)\n if (lastPurchaseWithinDays !== undefined) {\n blocks.push({ behavior: { lastPurchaseWithinDays } })\n }\n const noPurchaseForDays = typedNumber(draft.noPurchaseForDays)\n if (noPurchaseForDays !== undefined) {\n blocks.push({ behavior: { noPurchaseForDays } })\n }\n const openedWithinDays = typedNumber(draft.openedWithinDays)\n if (openedWithinDays !== undefined) {\n blocks.push({ engagement: { openedWithinDays } })\n }\n const clickedWithinDays = typedNumber(draft.clickedWithinDays)\n if (clickedWithinDays !== undefined) {\n blocks.push({ engagement: { clickedWithinDays } })\n }\n const notOpenedForDays = typedNumber(draft.notOpenedForDays)\n if (notOpenedForDays !== undefined) {\n blocks.push({ engagement: { notOpenedForDays } })\n }\n const notClickedForDays = typedNumber(draft.notClickedForDays)\n if (notClickedForDays !== undefined) {\n blocks.push({ engagement: { notClickedForDays } })\n }\n if (draft.inListIds.length) blocks.push({ inListIds: draft.inListIds })\n if (draft.notInListIds.length) {\n blocks.push({ notInListIds: draft.notInListIds })\n }\n if (draft.ownerUids.length) blocks.push({ ownerUids: draft.ownerUids })\n if (draft.lifecycleStages.length) {\n blocks.push({ lifecycleStages: draft.lifecycleStages })\n }\n if (draft.companyIds.length) blocks.push({ companyIds: draft.companyIds })\n const engagedWithinDays = typedNumber(draft.engagedWithinDays)\n if (engagedWithinDays !== undefined) blocks.push({ engagedWithinDays })\n // One block PER CLAUSE, so that \"any one of the filters below\" reads each\n // condition row as a filter of its own — the same grain the purchase\n // figures get. `mergeDimensions` folds them back into one list for `all`.\n // An unfinished row (no value where one is compared) is dropped by the\n // normalizer at the end, so it is never a condition until it is one.\n for (const clause of draft.custom) blocks.push({ custom: [clause] })\n return blocks\n}\n\n/** The blocks merged into one AND-block, for `all` and `none`. */\nfunction mergeDimensions(\n blocks: Record<string, unknown>[],\n): Record<string, unknown> {\n const merged: Record<string, unknown> = {}\n for (const block of blocks) {\n for (const [key, value] of Object.entries(block)) {\n // `behavior` and `engagement` arrive one leaf at a time, so they are\n // merged at depth. A shallow assign would keep only the last leaf of\n // each and silently drop the other three. `custom` arrives one clause\n // at a time for the same reason and is concatenated for the same one.\n merged[key] =\n key === 'behavior' || key === 'engagement'\n ? { ...((merged[key] as object) ?? {}), ...(value as object) }\n : key === 'custom'\n ? [...((merged[key] as unknown[]) ?? []), ...(value as unknown[])]\n : value\n }\n }\n return merged\n}\n\n/** What the controls mean, coerced by the function the matcher reads through. */\nexport function draftToRule(draft: DynamicListRuleDraft): DynamicListRule {\n const blocks = draftDimensions(draft)\n /*\n * Normalized on the way IN by the same function the materializer reads it\n * back through. A rule coerced on the way out but not on the way in is a\n * rule the console can display differently from the way it evaluates.\n */\n return normalizeDynamicListRule({\n sources: draft.sources,\n ...(draft.segmentId ? { segmentId: draft.segmentId } : {}),\n ...(draft.viewId ? { viewId: draft.viewId } : {}),\n ...(draft.match === 'any'\n ? // Each filter its own branch. The top-level block stays empty, which\n // is no constraint — so the rule is `sources AND (one of these)`.\n { any: blocks }\n : {\n ...mergeDimensions(blocks),\n // `none` on an empty set of filters would be \"exclude everyone\n // matching nothing\", which excludes everyone. The flag is written\n // only when there is something to invert.\n ...(draft.match === 'none' && blocks.length ? { negate: true } : {}),\n }),\n })\n}\n\nexport interface DynamicListRuleFieldsProps {\n /** `['orgs', orgId]` — the resolved org scope the caller already holds. */\n scope: readonly [string, string]\n /**\n * The site whose campaigns the campaign picker offers.\n *\n * A list is org-owned and its rule is materialized against ONE site's silos,\n * which is the same site whose campaigns a form or a screen can be filed\n * under. Offering the org's every campaign would offer ids the sweep can\n * never match, because the membership it reads was written by that site.\n */\n hostId: string\n draft: DynamicListRuleDraft\n onChange: (draft: DynamicListRuleDraft) => void\n /**\n * The audience being edited, when there is one.\n *\n * Kept out of its own list pickers. A rule that referred to the list it\n * fills would oscillate — see `dynamicListRuleWithoutListReference`, which\n * strips it at the sweep as well, because a rule outlives any one form.\n * Offering it here and stripping it there would show a merchant a filter\n * that quietly does nothing.\n */\n listId?: string\n}\n\nexport function DynamicListRuleFields(props: DynamicListRuleFieldsProps) {\n const { scope, hostId, draft, onChange, listId } = props\n const segmentDocs = useOrgContactSegments(scope)\n const viewDocs = useOrgCrmViews(scope)\n const listDocs = useOrgLists(scope)\n /*\n * The site's campaigns, read because this form is on screen.\n *\n * `enabled` is off by default on the hook for the surfaces that render a\n * picker beside fields a reader came for; here the picker IS one of the\n * fields the reader came for, and the same reasoning that opens the segment\n * listen opens this one.\n */\n const siteCampaigns = useHostCampaigns(hostId, { enabled: true })\n /*\n * THE CRM PICKERS' OPTIONS (AGL-2603), read because this form is on screen\n * — the same bargain the segments and the campaigns get, one section up.\n *\n * The team comes through the members route rather than the collection, so\n * a collaborator scoped to one site can still assign an owner; the field\n * definitions are one bounded read; the companies are a SEARCH and read\n * nothing until something is typed.\n */\n const team = useOrgMemberOptions(scope[1], { enabled: true })\n const fieldDefinitions = useOrgContactFields(scope)\n const [companySearch, setCompanySearch] = useState('')\n const companies = useOrgCompanyOptions({\n scope,\n search: companySearch,\n selectedIds: draft.companyIds,\n })\n\n /*\n * The saved segment the rule already names, even when the picker's window\n * does not reach it.\n *\n * A `Select` whose value is not among its options renders empty and warns,\n * so a rule pointing at the fifty-first segment would reopen looking as\n * though it named none — and saving from that screen would erase the\n * reference. The id is offered as its own option instead.\n */\n const segments = useMemo(() => {\n const rows = segmentDocs\n if (!draft.segmentId || rows.some((row) => row.$id === draft.segmentId)) {\n return rows\n }\n return [{ $id: draft.segmentId, name: draft.segmentId }, ...rows]\n }, [segmentDocs, draft.segmentId])\n /*\n * The saved VIEW the rule already names, kept the way the segment is\n * (AGL-2617) — and for one more reason: the picker lists only views that\n * translate whole, so a view edited past that after the rule named it\n * would vanish from its own picker, and saving from that screen would\n * erase the reference. Shown as its id, the sweep's refusal is at least\n * visible.\n */\n const crmViews = useMemo(() => {\n const rows = viewDocs\n if (!draft.viewId || rows.some((row) => row.$id === draft.viewId)) {\n return rows\n }\n return [{ $id: draft.viewId, name: draft.viewId }, ...rows]\n }, [viewDocs, draft.viewId])\n\n /** Every audience except the one being edited — see the prop's note. */\n const lists = useMemo(\n () => listDocs.filter((row) => row.$id !== listId),\n [listDocs, listId],\n )\n\n /** Ids the pickers can show, so a clause names an audience rather than a uid. */\n const names = useMemo(\n () => ({\n lists: Object.fromEntries(\n listDocs.map((row) => [row.$id, row.name ?? row.$id]),\n ),\n segments: Object.fromEntries(\n segments.map((row) => [row.$id, row.name ?? row.$id]),\n ),\n views: Object.fromEntries(crmViews.map((row) => [row.$id, row.name])),\n campaigns: Object.fromEntries(\n siteCampaigns.options.map((option) => [option.value, option.label]),\n ),\n members: Object.fromEntries(\n team.options.map((option) => [option.uid, option.label]),\n ),\n companies: companies.names,\n fields: Object.fromEntries(\n fieldDefinitions.fields.map((field) => [field.key, field.label]),\n ),\n }),\n [\n listDocs,\n segments,\n crmViews,\n siteCampaigns.options,\n team.options,\n companies.names,\n fieldDefinitions.fields,\n ],\n )\n\n /*\n * A stored owner the roster no longer lists, and a stored field key no\n * definition names, are offered as their own option — the segment picker's\n * discipline, for the same reason: a `Select` whose value is not among its\n * options renders empty, and a rule saved from that screen would lose the\n * clause it was opened with. A team member who left still owns the\n * contacts they were assigned until somebody reassigns them, and a retired\n * field still holds every value written under it.\n */\n const ownerOptions = useMemo(() => {\n const known = new Set(team.options.map((option) => option.uid))\n return [\n ...draft.ownerUids\n .filter((uid) => !known.has(uid))\n .map((uid) => ({ uid, label: uid })),\n ...team.options,\n ]\n }, [team.options, draft.ownerUids])\n const fieldOptions = useMemo(() => {\n const known = new Set(fieldDefinitions.fields.map((field) => field.key))\n return [\n ...draft.custom\n .filter((clause) => !known.has(clause.key))\n .map((clause) => ({ key: clause.key, label: clause.key }))\n .filter(\n (entry, index, all) =>\n all.findIndex((other) => other.key === entry.key) === index,\n ),\n ...fieldDefinitions.fields.map(({ key, label }) => ({ key, label })),\n ]\n }, [fieldDefinitions.fields, draft.custom])\n /** The definition a clause's key names, when the org still has one. */\n const definitionFor = (key: string) =>\n fieldDefinitions.fields.find((field) => field.key === key)\n\n const set = <K extends keyof DynamicListRuleDraft>(\n key: K,\n value: DynamicListRuleDraft[K],\n ) => onChange({ ...draft, [key]: value })\n\n /*\n * THE CUSTOM-FIELD ROWS. A new row names the first definition with an\n * empty value, so it is on screen and NOT yet a filter — `draftDimensions`\n * hands it to the normalizer, which drops a valued operator with no value\n * until the reader types one. Changing the field clears the value, because\n * a value typed for a number field is not a value for the select that\n * replaced it; changing the operator to a presence test removes it, so the\n * stored clause carries no value the sentence does not read.\n */\n const addClause = () => {\n const first = fieldOptions[0]\n if (!first) return\n set('custom', [...draft.custom, { key: first.key, op: 'eq', value: '' }])\n }\n const updateClause = (index: number, patch: Partial<DynamicListCustomClause>) =>\n set(\n 'custom',\n draft.custom.map((clause, at) => {\n if (at !== index) return clause\n const next = { ...clause, ...patch }\n if (PRESENCE_OPS.has(next.op)) delete next.value\n return next\n }),\n )\n const removeClause = (index: number) =>\n set(\n 'custom',\n draft.custom.filter((_, at) => at !== index),\n )\n\n /** A multi-select hands back a string when only one option is chosen. */\n const asArray = <T extends string,>(value: unknown): T[] =>\n typeof value === 'string' ? [value as T] : ((value as T[]) ?? [])\n\n const contactsOnly = draft.sources.includes('contacts')\n\n return (\n <Stack spacing={1.5}>\n {/*\n The filters, read back as sentences, above the controls that set them.\n A merchant cannot check eleven boxes against their intent; they can\n check one paragraph. It sits ABOVE the fields on purpose — the reader's\n question is \"who is this\", and the answer should not be below the fold\n of the form that produced it.\n */}\n <Box\n sx={{\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n p: 1.5,\n }}\n >\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'This audience'}\n </Typography>\n {describeDynamicListRule(draftToRule(draft), names).map((clause) => (\n <Typography key={clause} variant=\"body2\">\n {clause}\n </Typography>\n ))}\n </Box>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"People from\"\n value={draft.sources}\n onChange={(event) =>\n set('sources', asArray<DynamicListSource>(event.target.value))\n }\n slotProps={{ select: { multiple: true } }}\n sx={{ minWidth: 220 }}\n >\n {Object.entries(SOURCE_LABELS).map(([value, label]) => (\n <MenuItem key={value} value={value}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n {/*\n The combinator, beside the source picker rather than buried below\n the filters it governs. It changes what every control under it\n means, and a reader who found it after filling the form in would\n have been answering a different question the whole time.\n */}\n <TextField\n select\n size=\"small\"\n label=\"Match\"\n value={draft.match}\n onChange={(event) =>\n set('match', event.target.value as DynamicListRuleMatch)\n }\n helperText=\"A saved segment always applies\"\n sx={{ minWidth: 210 }}\n >\n <MenuItem value=\"all\">{'All of the filters below'}</MenuItem>\n <MenuItem value=\"any\">{'Any one of the filters below'}</MenuItem>\n <MenuItem value=\"none\">{'Nobody matching all of them'}</MenuItem>\n </TextField>\n <TextField\n type=\"date\"\n size=\"small\"\n label=\"Created after\"\n value={draft.createdAfter}\n onChange={(event) => set('createdAfter', event.target.value)}\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ minWidth: 170 }}\n />\n <TextField\n type=\"date\"\n size=\"small\"\n label=\"Created before\"\n value={draft.createdBefore}\n onChange={(event) => set('createdBefore', event.target.value)}\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ minWidth: 170 }}\n />\n <TextField\n size=\"small\"\n label=\"Submitted form\"\n placeholder=\"Contact us\"\n helperText=\"Form submissions only\"\n value={draft.formNames}\n onChange={(event) => set('formNames', event.target.value)}\n sx={{ minWidth: 180 }}\n />\n {/*\n The campaign filter sits with the cross-silo controls rather than\n under Contacts, because it reads on a form submission too — the\n submission carries the campaigns its form was filed under at the\n moment it arrived.\n\n The SHARED picker, the one a form's page and a screen's page assign\n with. A merchant who filed three forms under the spring push should\n pick the campaign here from the same control and the same list of\n names they filed them with; a second select over the same ids is how\n one stored field comes to be presented two ways.\n */}\n <Box sx={{ minWidth: 240, flexGrow: 1, maxWidth: 360 }}>\n <CampaignPicker\n options={siteCampaigns.options}\n value={draft.campaignIds}\n onChange={(next) => set('campaignIds', next)}\n label=\"In campaign\"\n helperText=\"Contacts and form submissions filed under any campaign picked. Being in a campaign is not consent to be emailed.\"\n empty={siteCampaigns.ready && !siteCampaigns.options.length}\n emptyText=\"This site has no campaigns yet. Create one from Marketing to build an audience from it.\"\n />\n </Box>\n </Stack>\n\n <Divider />\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Contacts'}\n </Typography>\n {/*\n Named rather than merely disabled when contacts are not a source. A\n control that vanishes and a control that does nothing look the same to\n somebody who has not read the matcher — and these dimensions are\n SKIPPED for the other silos rather than failed, so a rule can carry\n them while drawing from leads as well.\n */}\n {contactsOnly ? null : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'These apply only to people drawn from Contacts. The rule does not ' +\n 'draw from Contacts, so they select nobody on their own — the ' +\n 'other sources are matched without them.'}\n </Typography>\n )}\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"Saved segment\"\n value={draft.segmentId}\n onChange={(event) => set('segmentId', event.target.value)}\n helperText=\"Reuses that segment's tags and sources\"\n sx={{ minWidth: 200 }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {segments.map((segment) => (\n <MenuItem key={segment.$id} value={segment.$id}>\n {segment.name ?? segment.$id}\n </MenuItem>\n ))}\n </TextField>\n {/*\n A saved Contacts view as an audience (AGL-2617), beside the\n segment: its filters — owner, stage, company, tags, sources,\n dates, purchases and custom fields — always apply, the way a\n segment's do. Only views the sweep can honor are offered.\n */}\n <TextField\n select\n size=\"small\"\n label=\"Saved view\"\n value={draft.viewId}\n onChange={(event) => set('viewId', event.target.value)}\n helperText=\"Reuses that Contacts view's filters\"\n sx={{ minWidth: 200 }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {crmViews.map((view) => (\n <MenuItem key={view.$id} value={view.$id}>\n {view.name}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tagged\"\n placeholder=\"vip, wholesale\"\n value={draft.tags}\n onChange={(event) => set('tags', event.target.value)}\n sx={{ minWidth: 180 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Captured by\"\n value={draft.captureSources}\n onChange={(event) =>\n set('captureSources', asArray<ContactSource>(event.target.value))\n }\n slotProps={{ select: { multiple: true } }}\n helperText=\"How the contact reached you\"\n sx={{ minWidth: 200 }}\n >\n {Object.entries(CONTACT_SOURCE_LABELS).map(([value, label]) => (\n <MenuItem key={value} value={value}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n {/*\n THE CRM DIMENSIONS (AGL-2603), under Contacts because that is the only\n silo that carries them. Each is PICKED rather than typed — a person\n from the roster, a stage from the fixed list, a company from a search\n — so the rule stores ids and the sentences above read them back as\n names. An absent value matches none of them: \"owned by Ada\" is not\n satisfied by a contact nobody owns, and the helper text says so where\n a reader would otherwise assume the blank was included.\n */}\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"Owned by\"\n value={draft.ownerUids}\n onChange={(event) => set('ownerUids', asArray<string>(event.target.value))}\n slotProps={{\n select: {\n multiple: true,\n renderValue: (selected) =>\n (selected as string[])\n .map((uid) => named(uid, names.members))\n .join(', '),\n },\n }}\n error={Boolean(team.error)}\n helperText={team.error ?? 'Any of these team members. Unowned contacts are left out.'}\n sx={{ minWidth: 220 }}\n >\n {ownerOptions.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Lifecycle stage\"\n value={draft.lifecycleStages}\n onChange={(event) =>\n set(\n 'lifecycleStages',\n asArray<ContactLifecycleStage>(event.target.value),\n )\n }\n slotProps={{\n select: {\n multiple: true,\n renderValue: (selected) =>\n (selected as ContactLifecycleStage[])\n .map((stage) => CONTACT_LIFECYCLE_STAGE_LABELS[stage] ?? stage)\n .join(', '),\n },\n }}\n helperText=\"Any of these stages\"\n sx={{ minWidth: 200 }}\n >\n {CONTACT_LIFECYCLE_STAGES.map((stage) => (\n <MenuItem key={stage} value={stage}>\n {CONTACT_LIFECYCLE_STAGE_LABELS[stage]}\n </MenuItem>\n ))}\n </TextField>\n {/*\n A SEARCH, not a list: the org's companies are the collection that\n outgrows any dropdown, so nothing is read until something is typed\n and the hook answers one screen of prefix matches. The chips keep\n their names through the hook's own memory of every id it has seen,\n which is what lets a rule reopened months later still say \"Acme\".\n */}\n <Autocomplete\n multiple\n size=\"small\"\n options={companies.hits}\n value={draft.companyIds.map((id) => ({\n id,\n label: companies.names[id] ?? id,\n }))}\n getOptionLabel={(option) => option.label}\n isOptionEqualToValue={(option, chosen) => option.id === chosen.id}\n // The query already narrowed the hits; a second, client-side\n // filter on the label would hide a match whose stored name differs\n // from its search key.\n filterOptions={(options) => options}\n inputValue={companySearch}\n onInputChange={(_event, value, reason) => {\n if (reason !== 'reset') setCompanySearch(value)\n }}\n onChange={(_event, value) =>\n set(\n 'companyIds',\n value.map((option) => option.id),\n )\n }\n loading={companies.searching}\n noOptionsText={\n companySearch ? 'No company by that name' : 'Type to search companies'\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"At company\"\n helperText=\"Any of these companies\"\n />\n )}\n sx={{ minWidth: 260, flexGrow: 1, maxWidth: 420 }}\n />\n {/*\n THE RE-ENGAGEMENT WINDOW (AGL-2616), beside the other facet reads\n rather than under \"Email engagement\" below: that section reads the\n address-level rollup — every message this workspace sent the\n person — and this reads the stamp the delivery webhook wrote on\n THIS site's contact for THIS site's campaigns. A contact never\n stamped is left out, the lean every CRM dimension takes, and the\n helper says so.\n */}\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Engaged with a campaign within (days)\"\n helperText=\"Opened or clicked one of your campaigns. Never engaged is left out.\"\n value={draft.engagedWithinDays}\n onChange={(event) => set('engagedWithinDays', event.target.value)}\n sx={{ minWidth: 280 }}\n />\n </Stack>\n {/*\n One row per condition, and every row must hold — a second condition\n NARROWS, which is what a second box means everywhere else on this\n form; the OR is the \"any one of the filters\" mode above, where each\n row becomes a branch of its own. The value control is typed by the\n field's definition, so a number field hands the matcher a number and\n a choice field offers its own options, and a presence test asks for\n no value at all.\n */}\n {draft.custom.map((clause, index) => {\n const definition = definitionFor(clause.key)\n const valueText = String(clause.value ?? '')\n return (\n <Stack\n key={index}\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap', alignItems: 'flex-start' }}\n >\n <TextField\n select\n size=\"small\"\n label=\"Field\"\n value={clause.key}\n onChange={(event) =>\n updateClause(index, { key: event.target.value, value: '' })\n }\n sx={{ minWidth: 180 }}\n >\n {fieldOptions.map((option) => (\n <MenuItem key={option.key} value={option.key}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Condition\"\n value={clause.op}\n onChange={(event) =>\n updateClause(index, {\n op: event.target.value as DynamicListCustomOp,\n })\n }\n sx={{ minWidth: 160 }}\n >\n {DYNAMIC_LIST_CUSTOM_OPS.map((op) => (\n <MenuItem key={op} value={op}>\n {CUSTOM_OP_LABELS[op]}\n </MenuItem>\n ))}\n </TextField>\n {PRESENCE_OPS.has(clause.op) ? null : definition?.type === 'select' ? (\n <TextField\n select\n size=\"small\"\n label=\"Value\"\n value={valueText}\n onChange={(event) =>\n updateClause(index, { value: event.target.value })\n }\n sx={{ minWidth: 180 }}\n >\n {(definition.options ?? []).map((option) => (\n <MenuItem key={option} value={option}>\n {option}\n </MenuItem>\n ))}\n </TextField>\n ) : definition?.type === 'checkbox' ? (\n <TextField\n select\n size=\"small\"\n label=\"Value\"\n value={\n clause.value === true\n ? 'true'\n : clause.value === false\n ? 'false'\n : ''\n }\n onChange={(event) =>\n updateClause(index, { value: event.target.value === 'true' })\n }\n sx={{ minWidth: 140 }}\n >\n <MenuItem value=\"true\">{'Checked'}</MenuItem>\n <MenuItem value=\"false\">{'Not checked'}</MenuItem>\n </TextField>\n ) : definition?.type === 'number' ? (\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Value\"\n value={valueText}\n onChange={(event) => {\n // A number field stores a NUMBER, so the matcher compares\n // 10 with 9 and not \"10\" with \"9\"; half-typed text stays\n // text until it parses, rather than becoming NaN.\n const text = event.target.value\n const parsed = Number(text)\n updateClause(index, {\n value: text.trim() !== '' && Number.isFinite(parsed) ? parsed : text,\n })\n }}\n sx={{ minWidth: 140 }}\n />\n ) : (\n <TextField\n type={definition?.type === 'date' ? 'date' : 'text'}\n size=\"small\"\n label=\"Value\"\n value={valueText}\n onChange={(event) =>\n updateClause(index, { value: event.target.value })\n }\n slotProps={\n definition?.type === 'date'\n ? { inputLabel: { shrink: true } }\n : undefined\n }\n sx={{ minWidth: 180 }}\n />\n )}\n <Button size=\"small\" onClick={() => removeClause(index)}>\n {'Remove'}\n </Button>\n </Stack>\n )\n })}\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!fieldOptions.length}\n onClick={addClause}\n >\n {'Add a field condition'}\n </Button>\n {fieldDefinitions.ready && !fieldDefinitions.fields.length ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 0.5 }}\n >\n {'This workspace has no custom contact fields yet. Define them ' +\n 'under CRM → Fields to filter on them here.'}\n </Typography>\n ) : null}\n </Box>\n\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Purchase history'}\n </Typography>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Orders at least\"\n value={draft.ordersCountAtLeast}\n onChange={(event) => set('ordersCountAtLeast', event.target.value)}\n sx={{ minWidth: 160 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Spent at least\"\n helperText=\"Lifetime, in your store's currency\"\n value={draft.ltvAtLeast}\n onChange={(event) => set('ltvAtLeast', event.target.value)}\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Bought within (days)\"\n value={draft.lastPurchaseWithinDays}\n onChange={(event) =>\n set('lastPurchaseWithinDays', event.target.value)\n }\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Nothing bought for (days)\"\n helperText=\"Lapsed customers — never bought is not lapsed\"\n value={draft.noPurchaseForDays}\n onChange={(event) => set('noPurchaseForDays', event.target.value)}\n sx={{ minWidth: 220 }}\n />\n </Stack>\n\n <Divider />\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Email engagement'}\n </Typography>\n {/*\n The two quiet fields carry their lean in the helper text, not only in\n the sentences above. They read the OPPOSITE way from \"Nothing bought\n for\", one section up — somebody who never opened anything counts as\n not having opened, where somebody who never bought is not lapsed — and\n a form that put two opposite defaults next to each other without\n saying so is a form that produces the wrong audience quietly.\n */}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Opens and clicks across every email this workspace has sent the ' +\n 'person. Clicks are the stronger signal: mail apps that preload ' +\n 'images record an open the reader never made.'}\n </Typography>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Opened within (days)\"\n value={draft.openedWithinDays}\n onChange={(event) => set('openedWithinDays', event.target.value)}\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Clicked within (days)\"\n value={draft.clickedWithinDays}\n onChange={(event) => set('clickedWithinDays', event.target.value)}\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Nothing opened for (days)\"\n helperText=\"Never opened counts\"\n value={draft.notOpenedForDays}\n onChange={(event) => set('notOpenedForDays', event.target.value)}\n sx={{ minWidth: 210 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Nothing clicked for (days)\"\n helperText=\"Never clicked counts\"\n value={draft.notClickedForDays}\n onChange={(event) => set('notClickedForDays', event.target.value)}\n sx={{ minWidth: 210 }}\n />\n </Stack>\n\n <Divider />\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Other audiences'}\n </Typography>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"Already on\"\n value={draft.inListIds}\n onChange={(event) => set('inListIds', asArray<string>(event.target.value))}\n slotProps={{ select: { multiple: true } }}\n helperText=\"Members of every audience picked\"\n sx={{ minWidth: 220 }}\n >\n {lists.map((row) => (\n <MenuItem key={row.$id} value={row.$id}>\n {row.name ?? row.$id}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Not on\"\n value={draft.notInListIds}\n onChange={(event) =>\n set('notInListIds', asArray<string>(event.target.value))\n }\n slotProps={{ select: { multiple: true } }}\n helperText=\"Excludes members of any audience picked\"\n sx={{ minWidth: 220 }}\n >\n {lists.map((row) => (\n <MenuItem key={row.$id} value={row.$id}>\n {row.name ?? row.$id}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n </Stack>\n )\n}\nDynamicListRuleFields.displayName = 'DynamicListRuleFields'\n\n/**\n * A draft seeded from a rule that arrives after the form does.\n *\n * The edit page mounts before its list document lands, so the controls are\n * built from an empty rule and filled in once. Seeding ONCE per subject is the\n * whole point: the document is a live listen, and re-seeding on every snapshot\n * would overwrite whatever the reader had typed each time anything on the list\n * changed — including the snapshot the reader's own save produces.\n */\nexport function useRuleDraft(\n rule: DynamicListRule | undefined,\n resetKey: string,\n): [DynamicListRuleDraft, (draft: DynamicListRuleDraft) => void] {\n const [draft, setDraft] = useState<DynamicListRuleDraft>(EMPTY_RULE_DRAFT)\n const seededFor = useRef('')\n useEffect(() => {\n if (!rule || seededFor.current === resetKey) return\n seededFor.current = resetKey\n setDraft(ruleToDraft(rule))\n }, [rule, resetKey])\n return [draft, setDraft]\n}\n\nexport default DynamicListRuleFields\n"],"names":["CONTACT_LIFECYCLE_STAGES","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_SOURCE_LABELS","DYNAMIC_LIST_CUSTOM_OPS","normalizeDynamicListRule","Autocomplete","Box","Button","Divider","MenuItem","Stack","TextField","Typography","useEffect","useMemo","useRef","useState","useHostCampaigns","useOrgMemberOptions","CampaignPicker","useOrgCompanyOptions","useOrgContactFields","useOrgContactSegments","useOrgCrmViews","useOrgLists","SOURCE_LABELS","contacts","leads","siteMembers","formSubmissions","SOURCE_PHRASES","dayLabel","ms","Date","toISOString","slice","named","id","names","CUSTOM_OP_LABELS","eq","neq","contains","gt","lt","set","unset","PRESENCE_OPS","Set","describeCustomClause","clause","field","key","fields","verb","op","value","String","describeDimensions","rule","clauses","tags","length","push","join","captureSources","map","source","formNames","campaignIds","campaigns","createdAfterMs","undefined","createdBeforeMs","behavior","ordersCountAtLeast","ltvCentsAtLeast","lastPurchaseWithinDays","noPurchaseForDays","engagement","openedWithinDays","clickedWithinDays","notOpenedForDays","notClickedForDays","inListIds","lists","notInListIds","ownerUids","uid","members","lifecycleStages","stage","companyIds","companies","engagedWithinDays","custom","describeDynamicListRule","sources","segmentId","segments","viewId","views","own","negate","replace","branches","any","group","inner","joined","toLowerCase","filter","Boolean","EMPTY_RULE_DRAFT","match","createdAfter","createdBefore","ltvAtLeast","splitRuleList","split","entry","trim","dayStamp","Number","isFinite","ruleToDraft","blocks","first","read","block","list","found","add","number","ltvCents","seen","Map","JSON","stringify","values","typedNumber","trimmed","parsed","draftDimensions","draft","parse","ltv","Math","round","mergeDimensions","merged","Object","entries","draftToRule","DynamicListRuleFields","props","team","scope","hostId","onChange","listId","segmentDocs","viewDocs","listDocs","siteCampaigns","enabled","fieldDefinitions","companySearch","setCompanySearch","search","selectedIds","rows","some","row","$id","name","crmViews","fromEntries","options","option","label","ownerOptions","known","has","fieldOptions","index","all","findIndex","other","definitionFor","find","addClause","updateClause","patch","at","next","removeClause","_","asArray","contactsOnly","includes","spacing","sx","border","borderColor","borderRadius","p","variant","color","direction","useFlexGap","flexWrap","select","size","event","target","slotProps","multiple","minWidth","helperText","type","inputLabel","shrink","placeholder","flexGrow","maxWidth","empty","ready","emptyText","segment","view","renderValue","selected","error","hits","getOptionLabel","isOptionEqualToValue","chosen","filterOptions","inputValue","onInputChange","_event","reason","loading","searching","noOptionsText","renderInput","params","definition","valueText","alignItems","text","onClick","disabled","display","mt","displayName","useRuleDraft","resetKey","setDraft","seededFor","current"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCC,GAED,SACEA,wBAAwB,EACxBC,8BAA8B,EAC9BC,qBAAqB,EACrBC,uBAAuB,EACvBC,wBAAwB,QAQnB,eAAc;AACrB,SACEC,YAAY,EACZC,GAAG,EACHC,MAAM,EACNC,OAAO,EACPC,QAAQ,EACRC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAC5D,SACEC,gBAAgB,EAChBC,mBAAmB,QACd,iCAAgC;AACvC,OAAOC,oBAAoB,wEAAuE;AAClG,SAASC,oBAAoB,QAAQ,sCAAkC;AACvE,SAASC,mBAAmB,QAAQ,qCAAiC;AACrE,SAASC,qBAAqB,QAAQ,uCAAmC;AACzE,SAASC,cAAc,QAAQ,gCAA4B;AAC3D,SAASC,WAAW,QAAQ,4BAAwB;AAEpD,mCAAmC,GACnC,MAAMC,gBAAmD;IACvDC,UAAU;IACVC,OAAO;IACPC,aAAa;IACbC,iBAAiB;AACnB;AAEA,8DAA8D,GAC9D,MAAMC,iBAAoD;IACxDJ,UAAU;IACVC,OAAO;IACPC,aAAa;IACbC,iBAAiB;AACnB;AAEA,8EAA8E,GAC9E,MAAME,WAAW,CAACC,KAAe,IAAIC,KAAKD,IAAIE,WAAW,GAAGC,KAAK,CAAC,GAAG;AA8BrE,qEAAqE,GACrE,MAAMC,QAAQ,CAACC,IAAYC,QACzBA,CAAAA,yBAAAA,KAAO,CAACD,GAAG,KAAIA;AAEjB;;;;;;;CAOC,GACD,MAAME,mBAAwD;IAC5DC,IAAI;IACJC,KAAK;IACLC,UAAU;IACVC,IAAI;IACJC,IAAI;IACJC,KAAK;IACLC,OAAO;AACT;AAEA,qEAAqE,GACrE,MAAMC,eAAiD,IAAIC,IAAI;IAAC;IAAO;CAAQ;AAE/E,oEAAoE,GACpE,MAAMC,uBAAuB,CAC3BC,QACAZ;QAKqBY;IAHrB,MAAMC,QAAQf,MAAMc,OAAOE,GAAG,EAAEd,yBAAAA,MAAOe,MAAM;IAC7C,MAAMC,OAAOf,gBAAgB,CAACW,OAAOK,EAAE,CAAC;IACxC,IAAIL,OAAOK,EAAE,KAAK,SAASL,OAAOK,EAAE,KAAK,SAAS,OAAO,GAAGJ,MAAM,CAAC,EAAEG,MAAM;IAC3E,MAAME,QAAQC,QAAOP,gBAAAA,OAAOM,KAAK,YAAZN,gBAAgB;IACrC,OAAOA,OAAOK,EAAE,KAAK,QACjB,GAAGJ,MAAM,CAAC,EAAEG,KAAK,CAAC,EAAEE,MAAM,yBAAyB,CAAC,GACpD,GAAGL,MAAM,CAAC,EAAEG,KAAK,CAAC,EAAEE,OAAO;AACjC;AAEA,8EAA8E,GAC9E,SAASE,mBACPC,IAA2B,EAC3BrB,KAA4B;QAyIPqB;QAtIjBA,YAGAA,sBAOAA,iBAUAA,mBA4DAA,iBAOAA,oBAcAA,iBAOAA,uBAOAA;IApHJ,MAAMC,UAAoB,EAAE;IAC5B,KAAID,aAAAA,KAAKE,IAAI,qBAATF,WAAWG,MAAM,EAAE;QACrBF,QAAQG,IAAI,CAAC,CAAC,eAAe,EAAEJ,KAAKE,IAAI,CAACG,IAAI,CAAC,MAAM,CAAC,CAAC;IACxD;IACA,KAAIL,uBAAAA,KAAKM,cAAc,qBAAnBN,qBAAqBG,MAAM,EAAE;QAC/BF,QAAQG,IAAI,CACV,CAAC,aAAa,EAAEJ,KAAKM,cAAc,CAChCC,GAAG,CAAC,CAACC;gBAAWjE;oBAAAA,gCAAAA,qBAAqB,CAACiE,OAAO,YAA7BjE,gCAAiCiE;WACjDH,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB;IACA,KAAIL,kBAAAA,KAAKS,SAAS,qBAAdT,gBAAgBG,MAAM,EAAE;QAC1BF,QAAQG,IAAI,CAAC,CAAC,kBAAkB,EAAEJ,KAAKS,SAAS,CAACJ,IAAI,CAAC,MAAM,CAAC,CAAC;IAChE;IACA;;;;;;GAMC,GACD,KAAIL,oBAAAA,KAAKU,WAAW,qBAAhBV,kBAAkBG,MAAM,EAAE;QAC5BF,QAAQG,IAAI,CACV,CAAC,oCAAoC,EAAEJ,KAAKU,WAAW,CACpDH,GAAG,CAAC,CAAC7B,KAAOD,MAAMC,IAAIC,yBAAAA,MAAOgC,SAAS,GACtCN,IAAI,CAAC,MAAM,sCAAsC,CAAC;IAEzD;IACA,IAAIL,KAAKY,cAAc,KAAKC,WAAW;QACrCZ,QAAQG,IAAI,CAAC,CAAC,oBAAoB,EAAEhC,SAAS4B,KAAKY,cAAc,EAAE,CAAC,CAAC;IACtE;IACA,IAAIZ,KAAKc,eAAe,KAAKD,WAAW;QACtCZ,QAAQG,IAAI,CAAC,CAAC,eAAe,EAAEhC,SAAS4B,KAAKc,eAAe,EAAE,CAAC,CAAC;IAClE;IACA,MAAMC,WAAWf,KAAKe,QAAQ;IAC9B,IAAIA,CAAAA,4BAAAA,SAAUC,kBAAkB,MAAKH,WAAW;QAC9CZ,QAAQG,IAAI,CAAC,CAAC,SAAS,EAAEW,SAASC,kBAAkB,CAAC,QAAQ,CAAC;IAChE;IACA,IAAID,CAAAA,4BAAAA,SAAUE,eAAe,MAAKJ,WAAW;QAC3CZ,QAAQG,IAAI,CAAC,CAAC,eAAe,EAAEW,SAASE,eAAe,GAAG,IAAI,CAAC,CAAC;IAClE;IACA,IAAIF,CAAAA,4BAAAA,SAAUG,sBAAsB,MAAKL,WAAW;QAClDZ,QAAQG,IAAI,CACV,CAAC,uBAAuB,EAAEW,SAASG,sBAAsB,CAAC,MAAM,CAAC;IAErE;IACA,IAAIH,CAAAA,4BAAAA,SAAUI,iBAAiB,MAAKN,WAAW;QAC7CZ,QAAQG,IAAI,CAAC,CAAC,uBAAuB,EAAEW,SAASI,iBAAiB,CAAC,MAAM,CAAC;IAC3E;IACA;;;;;;;GAOC,GACD,MAAMC,aAAapB,KAAKoB,UAAU;IAClC,IAAIA,CAAAA,8BAAAA,WAAYC,gBAAgB,MAAKR,WAAW;QAC9CZ,QAAQG,IAAI,CACV,CAAC,sCAAsC,EAAEgB,WAAWC,gBAAgB,CAAC,MAAM,CAAC;IAEhF;IACA,IAAID,CAAAA,8BAAAA,WAAYE,iBAAiB,MAAKT,WAAW;QAC/CZ,QAAQG,IAAI,CACV,CAAC,iDAAiD,CAAC,GACjD,GAAGgB,WAAWE,iBAAiB,CAAC,MAAM,CAAC;IAE7C;IACA,IAAIF,CAAAA,8BAAAA,WAAYG,gBAAgB,MAAKV,WAAW;QAC9CZ,QAAQG,IAAI,CACV,CAAC,uBAAuB,EAAEgB,WAAWG,gBAAgB,CAAC,gBAAgB,CAAC,GACrE,CAAC,gBAAgB,CAAC;IAExB;IACA,IAAIH,CAAAA,8BAAAA,WAAYI,iBAAiB,MAAKX,WAAW;QAC/CZ,QAAQG,IAAI,CACV,CAAC,wBAAwB,EAAEgB,WAAWI,iBAAiB,CAAC,gBAAgB,CAAC,GACvE,CAAC,iBAAiB,CAAC;IAEzB;IACA,KAAIxB,kBAAAA,KAAKyB,SAAS,qBAAdzB,gBAAgBG,MAAM,EAAE;QAC1BF,QAAQG,IAAI,CACV,CAAC,YAAY,EAAEJ,KAAKyB,SAAS,CAC1BlB,GAAG,CAAC,CAAC7B,KAAOD,MAAMC,IAAIC,yBAAAA,MAAO+C,KAAK,GAClCrB,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB;IACA,KAAIL,qBAAAA,KAAK2B,YAAY,qBAAjB3B,mBAAmBG,MAAM,EAAE;QAC7BF,QAAQG,IAAI,CACV,CAAC,QAAQ,EAAEJ,KAAK2B,YAAY,CACzBpB,GAAG,CAAC,CAAC7B,KAAOD,MAAMC,IAAIC,yBAAAA,MAAO+C,KAAK,GAClCrB,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB;IACA;;;;;;GAMC,GACD,KAAIL,kBAAAA,KAAK4B,SAAS,qBAAd5B,gBAAgBG,MAAM,EAAE;QAC1BF,QAAQG,IAAI,CACV,CAAC,UAAU,EAAEJ,KAAK4B,SAAS,CACxBrB,GAAG,CAAC,CAACsB,MAAQpD,MAAMoD,KAAKlD,yBAAAA,MAAOmD,OAAO,GACtCzB,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB;IACA,KAAIL,wBAAAA,KAAK+B,eAAe,qBAApB/B,sBAAsBG,MAAM,EAAE;QAChCF,QAAQG,IAAI,CACV,CAAC,UAAU,EAAEJ,KAAK+B,eAAe,CAC9BxB,GAAG,CAAC,CAACyB;gBAAU1F;oBAAAA,wCAAAA,8BAA8B,CAAC0F,MAAM,YAArC1F,wCAAyC0F;WACxD3B,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB;IACA,KAAIL,mBAAAA,KAAKiC,UAAU,qBAAfjC,iBAAiBG,MAAM,EAAE;QAC3BF,QAAQG,IAAI,CACV,CAAC,YAAY,EAAEJ,KAAKiC,UAAU,CAC3B1B,GAAG,CAAC,CAAC7B,KAAOD,MAAMC,IAAIC,yBAAAA,MAAOuD,SAAS,GACtC7B,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB;IACA;;;;;GAKC,GACD,IAAIL,KAAKmC,iBAAiB,KAAKtB,WAAW;QACxCZ,QAAQG,IAAI,CACV,CAAC,oDAAoD,CAAC,GACpD,GAAGJ,KAAKmC,iBAAiB,CAAC,MAAM,CAAC;IAEvC;IACA,KAAK,MAAM5C,WAAUS,eAAAA,KAAKoC,MAAM,YAAXpC,eAAe,EAAE,CAAE;QACtCC,QAAQG,IAAI,CAAC,GAAGd,qBAAqBC,QAAQZ,OAAO,CAAC,CAAC;IACxD;IACA,OAAOsB;AACT;AAEA,OAAO,SAASoC,wBACdrC,IAAqB,EACrBrB,KAA4B;QA6CVqB;IA3ClB,MAAMC,UAAoB,EAAE;IAC5BA,QAAQG,IAAI,CACVJ,KAAKsC,OAAO,CAACnC,MAAM,GACf,CAAC,WAAW,EAAEH,KAAKsC,OAAO,CACvB/B,GAAG,CAAC,CAACC;YAAWrC;gBAAAA,yBAAAA,cAAc,CAACqC,OAAO,YAAtBrC,yBAA0BqC;OAC1CH,IAAI,CAAC,MAAM,CAAC,CAAC,GAChB;IAEN,IAAIL,KAAKuC,SAAS,EAAE;QAClBtC,QAAQG,IAAI,CACV,CAAC,qBAAqB,EAAE3B,MAAMuB,KAAKuC,SAAS,EAAE5D,yBAAAA,MAAO6D,QAAQ,EAAE,CAAC,CAAC;IAErE;IACA,IAAIxC,KAAKyC,MAAM,EAAE;QACfxC,QAAQG,IAAI,CAAC,CAAC,kBAAkB,EAAE3B,MAAMuB,KAAKyC,MAAM,EAAE9D,yBAAAA,MAAO+D,KAAK,EAAE,CAAC,CAAC;IACvE;IACA,MAAMC,MAAM5C,mBAAmBC,MAAMrB;IACrC,IAAIqB,KAAK4C,MAAM,IAAID,IAAIxC,MAAM,EAAE;QAC7B;;;;;;;KAOC,GACDF,QAAQG,IAAI,CACV,CAAC,iCAAiC,EAAEuC,IACjCpC,GAAG,CAAC,CAAChB,SAAWA,OAAOsD,OAAO,CAAC,OAAO,KACtCxC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEpB,OAAO;QACLJ,QAAQG,IAAI,IAAIuC;IAClB;IACA;;;;;;;;GAQC,GACD,MAAMG,WAAW,EAAC9C,YAAAA,KAAK+C,GAAG,YAAR/C,YAAY,EAAE,EAC7BO,GAAG,CAAC,CAACyC;QACJ,MAAMC,QAAQlD,mBAAmBiD,OAAOrE;QACxC,IAAI,CAACsE,MAAM9C,MAAM,EAAE,OAAO;QAC1B,MAAM+C,SAASD,MACZ1C,GAAG,CAAC,CAAChB,SAAWA,OAAOsD,OAAO,CAAC,OAAO,IAAIM,WAAW,IACrD9C,IAAI,CAAC;QACR,OAAO2C,MAAMJ,MAAM,GAAG,CAAC,KAAK,EAAEM,OAAO,CAAC,CAAC,GAAGA;IAC5C,GACCE,MAAM,CAACC;IACV,IAAIP,SAAS3C,MAAM,EAAE;QACnBF,QAAQG,IAAI,CAAC,CAAC,2BAA2B,EAAE0C,SAASzC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtE;IACA,OAAOJ;AACT;AA4DA,OAAO,MAAMqD,mBAAyC;IACpDhB,SAAS;QAAC;KAAW;IACrBiB,OAAO;IACPhB,WAAW;IACXE,QAAQ;IACRvC,MAAM;IACNI,gBAAgB,EAAE;IAClBG,WAAW;IACXC,aAAa,EAAE;IACf8C,cAAc;IACdC,eAAe;IACfzC,oBAAoB;IACpB0C,YAAY;IACZxC,wBAAwB;IACxBC,mBAAmB;IACnBE,kBAAkB;IAClBC,mBAAmB;IACnBC,kBAAkB;IAClBC,mBAAmB;IACnBC,WAAW,EAAE;IACbE,cAAc,EAAE;IAChBC,WAAW,EAAE;IACbG,iBAAiB,EAAE;IACnBE,YAAY,EAAE;IACdG,QAAQ,EAAE;IACVD,mBAAmB;AACrB,EAAC;AAED,+DAA+D,GAC/D,OAAO,MAAMwB,gBAAgB,CAAC9D,QAC5BA,MACG+D,KAAK,CAAC,KACNrD,GAAG,CAAC,CAACsD,QAAUA,MAAMC,IAAI,IACzBV,MAAM,CAACC,SAAQ;AAEpB;;;;;;;CAOC,GACD,MAAMU,WAAW,CAAC1F,KAChBA,OAAOwC,aAAa,CAACmD,OAAOC,QAAQ,CAAC5F,MAAM,KAAKD,SAASC;AAE3D;;;;;;;;;;CAUC,GACD,OAAO,SAAS6F,YAAYlE,IAAqB;QAGrBA,WAmBfA,eAEEA,iBACHA;QAFDA;IAtBT,0EAA0E;IAC1E,yEAAyE;IACzE,MAAMmE,SAAS;QAACnE;YAAUA,YAAAA,KAAK+C,GAAG,YAAR/C,YAAY,EAAE;KAAE;IAC1C,MAAMoE,QAAQ,CAAKC;QACjB,KAAK,MAAMC,SAASH,OAAQ;YAC1B,MAAMtE,QAAQwE,KAAKC;YACnB,IAAIzE,UAAUgB,WAAW,OAAOhB;QAClC;QACA,OAAOgB;IACT;IACA,MAAM0D,OAAO,CAACF;YAEoCA;QADhD,MAAMG,QAAQ,IAAInF;QAClB,KAAK,MAAMiF,SAASH,OAAQ,KAAK,MAAMN,UAASQ,QAAAA,KAAKC,kBAALD,QAAe,EAAE,CAAEG,MAAMC,GAAG,CAACZ;QAC7E,OAAO;eAAIW;SAAM;IACnB;IACA,MAAME,SAAS,CAACL;QACd,MAAMxE,QAAQuE,MAAMC;QACpB,OAAOxE,UAAUgB,YAAY,KAAKf,OAAOD;IAC3C;IACA,MAAM8E,WAAWP,MAAM,CAACE;YAAUA;gBAAAA,kBAAAA,MAAMvD,QAAQ,qBAAduD,gBAAgBrD,eAAe;;IACjE,OAAO;QACLqB,OAAO,GAAEtC,gBAAAA,KAAKsC,OAAO,YAAZtC,gBAAgB,EAAE;QAC3BuD,OAAOvD,EAAAA,aAAAA,KAAK+C,GAAG,qBAAR/C,WAAUG,MAAM,IAAG,QAAQH,KAAK4C,MAAM,GAAG,SAAS;QACzDL,SAAS,GAAEvC,kBAAAA,KAAKuC,SAAS,YAAdvC,kBAAkB;QAC7ByC,MAAM,GAAEzC,eAAAA,KAAKyC,MAAM,YAAXzC,eAAe;QACvBE,MAAMqE,KAAK,CAACD,QAAUA,MAAMpE,IAAI,EAAEG,IAAI,CAAC;QACvCC,gBAAgBiE,KAAK,CAACD,QAAUA,MAAMhE,cAAc;QACpDG,WAAW8D,KAAK,CAACD,QAAUA,MAAM7D,SAAS,EAAEJ,IAAI,CAAC;QACjDK,aAAa6D,KAAK,CAACD,QAAUA,MAAM5D,WAAW;QAC9C8C,cAAcO,SAASK,MAAM,CAACE,QAAUA,MAAM1D,cAAc;QAC5D6C,eAAeM,SAASK,MAAM,CAACE,QAAUA,MAAMxD,eAAe;QAC9DE,oBAAoB0D,OAAO,CAACJ;gBAAUA;oBAAAA,kBAAAA,MAAMvD,QAAQ,qBAAduD,gBAAgBtD,kBAAkB;;QACxE0C,YAAYiB,aAAa9D,YAAY,KAAKf,OAAO6E,WAAW;QAC5DzD,wBAAwBwD,OACtB,CAACJ;gBAAUA;oBAAAA,kBAAAA,MAAMvD,QAAQ,qBAAduD,gBAAgBpD,sBAAsB;;QAEnDC,mBAAmBuD,OAAO,CAACJ;gBAAUA;oBAAAA,kBAAAA,MAAMvD,QAAQ,qBAAduD,gBAAgBnD,iBAAiB;;QACtEE,kBAAkBqD,OAAO,CAACJ;gBAAUA;oBAAAA,oBAAAA,MAAMlD,UAAU,qBAAhBkD,kBAAkBjD,gBAAgB;;QACtEC,mBAAmBoD,OAAO,CAACJ;gBAAUA;oBAAAA,oBAAAA,MAAMlD,UAAU,qBAAhBkD,kBAAkBhD,iBAAiB;;QACxEC,kBAAkBmD,OAAO,CAACJ;gBAAUA;oBAAAA,oBAAAA,MAAMlD,UAAU,qBAAhBkD,kBAAkB/C,gBAAgB;;QACtEC,mBAAmBkD,OAAO,CAACJ;gBAAUA;oBAAAA,oBAAAA,MAAMlD,UAAU,qBAAhBkD,kBAAkB9C,iBAAiB;;QACxEC,WAAW8C,KAAK,CAACD,QAAUA,MAAM7C,SAAS;QAC1CE,cAAc4C,KAAK,CAACD,QAAUA,MAAM3C,YAAY;QAChDC,WAAW2C,KAAK,CAACD,QAAUA,MAAM1C,SAAS;QAC1CG,iBAAiBwC,KACf,CAACD,QAAUA,MAAMvC,eAAe;QAElCE,YAAYsC,KAAK,CAACD,QAAUA,MAAMrC,UAAU;QAC5CE,mBAAmBuC,OAAO,CAACJ,QAAUA,MAAMnC,iBAAiB;QAC5D,wEAAwE;QACxE,sEAAsE;QACtE,eAAe;QACfC,QAAQ,AAAC,CAAA;YACP,MAAMwC,OAAO,IAAIC;YACjB,KAAK,MAAMP,SAASH,OAAQ;oBACLG;gBAArB,KAAK,MAAM/E,WAAU+E,gBAAAA,MAAMlC,MAAM,YAAZkC,gBAAgB,EAAE,CAAE;oBACvCM,KAAK1F,GAAG,CAAC4F,KAAKC,SAAS,CAACxF,SAASA;gBACnC;YACF;YACA,OAAO;mBAAIqF,KAAKI,MAAM;aAAG;QAC3B,CAAA;IACF;AACF;AAEA;;;;;;;CAOC,GACD,MAAMC,cAAc,CAACpF;IACnB,MAAMqF,UAAUrF,MAAMiE,IAAI;IAC1B,IAAI,CAACoB,SAAS,OAAOrE;IACrB,MAAMsE,SAASnB,OAAOkB;IACtB,OAAOlB,OAAOC,QAAQ,CAACkB,WAAWA,UAAU,IAAIA,SAAStE;AAC3D;AAEA;;;;;;;;CAQC,GACD,SAASuE,gBAAgBC,KAA2B;IAClD,MAAMlB,SAAoC,EAAE;IAC5C,MAAMjE,OAAOyD,cAAc0B,MAAMnF,IAAI;IACrC,IAAIA,KAAKC,MAAM,EAAEgE,OAAO/D,IAAI,CAAC;QAAEF;IAAK;IACpC,IAAImF,MAAM/E,cAAc,CAACH,MAAM,EAAE;QAC/BgE,OAAO/D,IAAI,CAAC;YAAEE,gBAAgB+E,MAAM/E,cAAc;QAAC;IACrD;IACA,MAAMG,YAAYkD,cAAc0B,MAAM5E,SAAS;IAC/C,IAAIA,UAAUN,MAAM,EAAEgE,OAAO/D,IAAI,CAAC;QAAEK;IAAU;IAC9C,IAAI4E,MAAM3E,WAAW,CAACP,MAAM,EAAEgE,OAAO/D,IAAI,CAAC;QAAEM,aAAa2E,MAAM3E,WAAW;IAAC;IAC3E,MAAME,iBAAiByE,MAAM7B,YAAY,GACrClF,KAAKgH,KAAK,CAACD,MAAM7B,YAAY,IAC7B3C;IACJ,IAAID,mBAAmBC,aAAamD,OAAOC,QAAQ,CAACrD,iBAAiB;QACnEuD,OAAO/D,IAAI,CAAC;YAAEQ;QAAe;IAC/B;IACA,MAAME,kBAAkBuE,MAAM5B,aAAa,GACvCnF,KAAKgH,KAAK,CAACD,MAAM5B,aAAa,IAC9B5C;IACJ,IAAIC,oBAAoBD,aAAamD,OAAOC,QAAQ,CAACnD,kBAAkB;QACrEqD,OAAO/D,IAAI,CAAC;YAAEU;QAAgB;IAChC;IACA,MAAME,qBAAqBiE,YAAYI,MAAMrE,kBAAkB;IAC/D,IAAIA,uBAAuBH,WAAW;QACpCsD,OAAO/D,IAAI,CAAC;YAAEW,UAAU;gBAAEC;YAAmB;QAAE;IACjD;IACA,4EAA4E;IAC5E,sDAAsD;IACtD,MAAMuE,MAAMN,YAAYI,MAAM3B,UAAU;IACxC,IAAI6B,QAAQ1E,WAAW;QACrBsD,OAAO/D,IAAI,CAAC;YAAEW,UAAU;gBAAEE,iBAAiBuE,KAAKC,KAAK,CAACF,MAAM;YAAK;QAAE;IACrE;IACA,MAAMrE,yBAAyB+D,YAAYI,MAAMnE,sBAAsB;IACvE,IAAIA,2BAA2BL,WAAW;QACxCsD,OAAO/D,IAAI,CAAC;YAAEW,UAAU;gBAAEG;YAAuB;QAAE;IACrD;IACA,MAAMC,oBAAoB8D,YAAYI,MAAMlE,iBAAiB;IAC7D,IAAIA,sBAAsBN,WAAW;QACnCsD,OAAO/D,IAAI,CAAC;YAAEW,UAAU;gBAAEI;YAAkB;QAAE;IAChD;IACA,MAAME,mBAAmB4D,YAAYI,MAAMhE,gBAAgB;IAC3D,IAAIA,qBAAqBR,WAAW;QAClCsD,OAAO/D,IAAI,CAAC;YAAEgB,YAAY;gBAAEC;YAAiB;QAAE;IACjD;IACA,MAAMC,oBAAoB2D,YAAYI,MAAM/D,iBAAiB;IAC7D,IAAIA,sBAAsBT,WAAW;QACnCsD,OAAO/D,IAAI,CAAC;YAAEgB,YAAY;gBAAEE;YAAkB;QAAE;IAClD;IACA,MAAMC,mBAAmB0D,YAAYI,MAAM9D,gBAAgB;IAC3D,IAAIA,qBAAqBV,WAAW;QAClCsD,OAAO/D,IAAI,CAAC;YAAEgB,YAAY;gBAAEG;YAAiB;QAAE;IACjD;IACA,MAAMC,oBAAoByD,YAAYI,MAAM7D,iBAAiB;IAC7D,IAAIA,sBAAsBX,WAAW;QACnCsD,OAAO/D,IAAI,CAAC;YAAEgB,YAAY;gBAAEI;YAAkB;QAAE;IAClD;IACA,IAAI6D,MAAM5D,SAAS,CAACtB,MAAM,EAAEgE,OAAO/D,IAAI,CAAC;QAAEqB,WAAW4D,MAAM5D,SAAS;IAAC;IACrE,IAAI4D,MAAM1D,YAAY,CAACxB,MAAM,EAAE;QAC7BgE,OAAO/D,IAAI,CAAC;YAAEuB,cAAc0D,MAAM1D,YAAY;QAAC;IACjD;IACA,IAAI0D,MAAMzD,SAAS,CAACzB,MAAM,EAAEgE,OAAO/D,IAAI,CAAC;QAAEwB,WAAWyD,MAAMzD,SAAS;IAAC;IACrE,IAAIyD,MAAMtD,eAAe,CAAC5B,MAAM,EAAE;QAChCgE,OAAO/D,IAAI,CAAC;YAAE2B,iBAAiBsD,MAAMtD,eAAe;QAAC;IACvD;IACA,IAAIsD,MAAMpD,UAAU,CAAC9B,MAAM,EAAEgE,OAAO/D,IAAI,CAAC;QAAE6B,YAAYoD,MAAMpD,UAAU;IAAC;IACxE,MAAME,oBAAoB8C,YAAYI,MAAMlD,iBAAiB;IAC7D,IAAIA,sBAAsBtB,WAAWsD,OAAO/D,IAAI,CAAC;QAAE+B;IAAkB;IACrE,0EAA0E;IAC1E,qEAAqE;IACrE,0EAA0E;IAC1E,uEAAuE;IACvE,qEAAqE;IACrE,KAAK,MAAM5C,UAAU8F,MAAMjD,MAAM,CAAE+B,OAAO/D,IAAI,CAAC;QAAEgC,QAAQ;YAAC7C;SAAO;IAAC;IAClE,OAAO4E;AACT;AAEA,gEAAgE,GAChE,SAASuB,gBACPvB,MAAiC;IAEjC,MAAMwB,SAAkC,CAAC;IACzC,KAAK,MAAMrB,SAASH,OAAQ;QAC1B,KAAK,MAAM,CAAC1E,KAAKI,MAAM,IAAI+F,OAAOC,OAAO,CAACvB,OAAQ;gBAOnCqB,aAECA;YARd,qEAAqE;YACrE,qEAAqE;YACrE,sEAAsE;YACtE,sEAAsE;YACtEA,MAAM,CAAClG,IAAI,GACTA,QAAQ,cAAcA,QAAQ,eAC1B,cAAOkG,cAAAA,MAAM,CAAClG,IAAI,YAAXkG,cAA0B,CAAC,GAAQ9F,SAC1CJ,QAAQ,WACN;oBAAMkG,eAAAA,MAAM,CAAClG,IAAI,YAAXkG,eAA6B,EAAE;mBAAO9F;aAAoB,GAChEA;QACV;IACF;IACA,OAAO8F;AACT;AAEA,+EAA+E,GAC/E,OAAO,SAASG,YAAYT,KAA2B;IACrD,MAAMlB,SAASiB,gBAAgBC;IAC/B;;;;GAIC,GACD,OAAO5I,yBAAyB;QAC9B6F,SAAS+C,MAAM/C,OAAO;OAClB+C,MAAM9C,SAAS,GAAG;QAAEA,WAAW8C,MAAM9C,SAAS;IAAC,IAAI,CAAC,GACpD8C,MAAM5C,MAAM,GAAG;QAAEA,QAAQ4C,MAAM5C,MAAM;IAAC,IAAI,CAAC,GAC3C4C,MAAM9B,KAAK,KAAK,QAEhB,kEAAkE;IAClE;QAAER,KAAKoB;IAAO,IACd,aACKuB,gBAAgBvB,SAIfkB,MAAM9B,KAAK,KAAK,UAAUY,OAAOhE,MAAM,GAAG;QAAEyC,QAAQ;IAAK,IAAI,CAAC;AAG5E;AA4BA,OAAO,SAASmD,sBAAsBC,KAAiC;QAqZjDC;IApZpB,MAAM,EAAEC,KAAK,EAAEC,MAAM,EAAEd,KAAK,EAAEe,QAAQ,EAAEC,MAAM,EAAE,GAAGL;IACnD,MAAMM,cAAc3I,sBAAsBuI;IAC1C,MAAMK,WAAW3I,eAAesI;IAChC,MAAMM,WAAW3I,YAAYqI;IAC7B;;;;;;;GAOC,GACD,MAAMO,gBAAgBnJ,iBAAiB6I,QAAQ;QAAEO,SAAS;IAAK;IAC/D;;;;;;;;GAQC,GACD,MAAMT,OAAO1I,oBAAoB2I,KAAK,CAAC,EAAE,EAAE;QAAEQ,SAAS;IAAK;IAC3D,MAAMC,mBAAmBjJ,oBAAoBwI;IAC7C,MAAM,CAACU,eAAeC,iBAAiB,GAAGxJ,SAAS;IACnD,MAAM6E,YAAYzE,qBAAqB;QACrCyI;QACAY,QAAQF;QACRG,aAAa1B,MAAMpD,UAAU;IAC/B;IAEA;;;;;;;;GAQC,GACD,MAAMO,WAAWrF,QAAQ;QACvB,MAAM6J,OAAOV;QACb,IAAI,CAACjB,MAAM9C,SAAS,IAAIyE,KAAKC,IAAI,CAAC,CAACC,MAAQA,IAAIC,GAAG,KAAK9B,MAAM9C,SAAS,GAAG;YACvE,OAAOyE;QACT;QACA,OAAO;YAAC;gBAAEG,KAAK9B,MAAM9C,SAAS;gBAAE6E,MAAM/B,MAAM9C,SAAS;YAAC;eAAMyE;SAAK;IACnE,GAAG;QAACV;QAAajB,MAAM9C,SAAS;KAAC;IACjC;;;;;;;GAOC,GACD,MAAM8E,WAAWlK,QAAQ;QACvB,MAAM6J,OAAOT;QACb,IAAI,CAAClB,MAAM5C,MAAM,IAAIuE,KAAKC,IAAI,CAAC,CAACC,MAAQA,IAAIC,GAAG,KAAK9B,MAAM5C,MAAM,GAAG;YACjE,OAAOuE;QACT;QACA,OAAO;YAAC;gBAAEG,KAAK9B,MAAM5C,MAAM;gBAAE2E,MAAM/B,MAAM5C,MAAM;YAAC;eAAMuE;SAAK;IAC7D,GAAG;QAACT;QAAUlB,MAAM5C,MAAM;KAAC;IAE3B,sEAAsE,GACtE,MAAMf,QAAQvE,QACZ,IAAMqJ,SAASpD,MAAM,CAAC,CAAC8D,MAAQA,IAAIC,GAAG,KAAKd,SAC3C;QAACG;QAAUH;KAAO;IAGpB,+EAA+E,GAC/E,MAAM1H,QAAQxB,QACZ,IAAO,CAAA;YACLuE,OAAOkE,OAAO0B,WAAW,CACvBd,SAASjG,GAAG,CAAC,CAAC2G;oBAAkBA;uBAAV;oBAACA,IAAIC,GAAG;qBAAED,YAAAA,IAAIE,IAAI,YAARF,YAAYA,IAAIC,GAAG;iBAAC;;YAEtD3E,UAAUoD,OAAO0B,WAAW,CAC1B9E,SAASjC,GAAG,CAAC,CAAC2G;oBAAkBA;uBAAV;oBAACA,IAAIC,GAAG;qBAAED,YAAAA,IAAIE,IAAI,YAARF,YAAYA,IAAIC,GAAG;iBAAC;;YAEtDzE,OAAOkD,OAAO0B,WAAW,CAACD,SAAS9G,GAAG,CAAC,CAAC2G,MAAQ;oBAACA,IAAIC,GAAG;oBAAED,IAAIE,IAAI;iBAAC;YACnEzG,WAAWiF,OAAO0B,WAAW,CAC3Bb,cAAcc,OAAO,CAAChH,GAAG,CAAC,CAACiH,SAAW;oBAACA,OAAO3H,KAAK;oBAAE2H,OAAOC,KAAK;iBAAC;YAEpE3F,SAAS8D,OAAO0B,WAAW,CACzBrB,KAAKsB,OAAO,CAAChH,GAAG,CAAC,CAACiH,SAAW;oBAACA,OAAO3F,GAAG;oBAAE2F,OAAOC,KAAK;iBAAC;YAEzDvF,WAAWA,UAAUvD,KAAK;YAC1Be,QAAQkG,OAAO0B,WAAW,CACxBX,iBAAiBjH,MAAM,CAACa,GAAG,CAAC,CAACf,QAAU;oBAACA,MAAMC,GAAG;oBAAED,MAAMiI,KAAK;iBAAC;QAEnE,CAAA,GACA;QACEjB;QACAhE;QACA6E;QACAZ,cAAcc,OAAO;QACrBtB,KAAKsB,OAAO;QACZrF,UAAUvD,KAAK;QACfgI,iBAAiBjH,MAAM;KACxB;IAGH;;;;;;;;GAQC,GACD,MAAMgI,eAAevK,QAAQ;QAC3B,MAAMwK,QAAQ,IAAItI,IAAI4G,KAAKsB,OAAO,CAAChH,GAAG,CAAC,CAACiH,SAAWA,OAAO3F,GAAG;QAC7D,OAAO;eACFwD,MAAMzD,SAAS,CACfwB,MAAM,CAAC,CAACvB,MAAQ,CAAC8F,MAAMC,GAAG,CAAC/F,MAC3BtB,GAAG,CAAC,CAACsB,MAAS,CAAA;oBAAEA;oBAAK4F,OAAO5F;gBAAI,CAAA;eAChCoE,KAAKsB,OAAO;SAChB;IACH,GAAG;QAACtB,KAAKsB,OAAO;QAAElC,MAAMzD,SAAS;KAAC;IAClC,MAAMiG,eAAe1K,QAAQ;QAC3B,MAAMwK,QAAQ,IAAItI,IAAIsH,iBAAiBjH,MAAM,CAACa,GAAG,CAAC,CAACf,QAAUA,MAAMC,GAAG;QACtE,OAAO;eACF4F,MAAMjD,MAAM,CACZgB,MAAM,CAAC,CAAC7D,SAAW,CAACoI,MAAMC,GAAG,CAACrI,OAAOE,GAAG,GACxCc,GAAG,CAAC,CAAChB,SAAY,CAAA;oBAAEE,KAAKF,OAAOE,GAAG;oBAAEgI,OAAOlI,OAAOE,GAAG;gBAAC,CAAA,GACtD2D,MAAM,CACL,CAACS,OAAOiE,OAAOC,MACbA,IAAIC,SAAS,CAAC,CAACC,QAAUA,MAAMxI,GAAG,KAAKoE,MAAMpE,GAAG,MAAMqI;eAEzDnB,iBAAiBjH,MAAM,CAACa,GAAG,CAAC,CAAC,EAAEd,GAAG,EAAEgI,KAAK,EAAE,GAAM,CAAA;oBAAEhI;oBAAKgI;gBAAM,CAAA;SAClE;IACH,GAAG;QAACd,iBAAiBjH,MAAM;QAAE2F,MAAMjD,MAAM;KAAC;IAC1C,qEAAqE,GACrE,MAAM8F,gBAAgB,CAACzI,MACrBkH,iBAAiBjH,MAAM,CAACyI,IAAI,CAAC,CAAC3I,QAAUA,MAAMC,GAAG,KAAKA;IAExD,MAAMP,MAAM,CACVO,KACAI,QACGuG,SAAS,aAAKf;YAAO,CAAC5F,IAAI,EAAEI;;IAEjC;;;;;;;;GAQC,GACD,MAAMuI,YAAY;QAChB,MAAMhE,QAAQyD,YAAY,CAAC,EAAE;QAC7B,IAAI,CAACzD,OAAO;QACZlF,IAAI,UAAU;eAAImG,MAAMjD,MAAM;YAAE;gBAAE3C,KAAK2E,MAAM3E,GAAG;gBAAEG,IAAI;gBAAMC,OAAO;YAAG;SAAE;IAC1E;IACA,MAAMwI,eAAe,CAACP,OAAeQ,QACnCpJ,IACE,UACAmG,MAAMjD,MAAM,CAAC7B,GAAG,CAAC,CAAChB,QAAQgJ;YACxB,IAAIA,OAAOT,OAAO,OAAOvI;YACzB,MAAMiJ,OAAO,aAAKjJ,QAAW+I;YAC7B,IAAIlJ,aAAawI,GAAG,CAACY,KAAK5I,EAAE,GAAG,OAAO4I,KAAK3I,KAAK;YAChD,OAAO2I;QACT;IAEJ,MAAMC,eAAe,CAACX,QACpB5I,IACE,UACAmG,MAAMjD,MAAM,CAACgB,MAAM,CAAC,CAACsF,GAAGH,KAAOA,OAAOT;IAG1C,uEAAuE,GACvE,MAAMa,UAAU,CAAoB9I,QAClC,OAAOA,UAAU,WAAW;YAACA;SAAW,GAAKA,gBAAAA,QAAiB,EAAE;IAElE,MAAM+I,eAAevD,MAAM/C,OAAO,CAACuG,QAAQ,CAAC;IAE5C,qBACE,MAAC9L;QAAM+L,SAAS;;0BAQd,MAACnM;gBACCoM,IAAI;oBACFC,QAAQ;oBACRC,aAAa;oBACbC,cAAc;oBACdC,GAAG;gBACL;;kCAEA,KAAClM;wBAAWmM,SAAQ;wBAAWC,OAAM;kCAClC;;oBAEFhH,wBAAwByD,YAAYT,QAAQ1G,OAAO4B,GAAG,CAAC,CAAChB,uBACvD,KAACtC;4BAAwBmM,SAAQ;sCAC9B7J;2BADcA;;;0BAKrB,MAACxC;gBAAMuM,WAAU;gBAAMR,SAAS;gBAAGS,UAAU;gBAACR,IAAI;oBAAES,UAAU;gBAAO;;kCACnE,KAACxM;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM/C,OAAO;wBACpB8D,UAAU,CAACuD,QACTzK,IAAI,WAAWyJ,QAA2BgB,MAAMC,MAAM,CAAC/J,KAAK;wBAE9DgK,WAAW;4BAAEJ,QAAQ;gCAAEK,UAAU;4BAAK;wBAAE;wBACxCf,IAAI;4BAAEgB,UAAU;wBAAI;kCAEnBnE,OAAOC,OAAO,CAAC/H,eAAeyC,GAAG,CAAC,CAAC,CAACV,OAAO4H,MAAM,iBAChD,KAAC3K;gCAAqB+C,OAAOA;0CAC1B4H;+BADY5H;;kCAWnB,MAAC7C;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM9B,KAAK;wBAClB6C,UAAU,CAACuD,QACTzK,IAAI,SAASyK,MAAMC,MAAM,CAAC/J,KAAK;wBAEjCmK,YAAW;wBACXjB,IAAI;4BAAEgB,UAAU;wBAAI;;0CAEpB,KAACjN;gCAAS+C,OAAM;0CAAO;;0CACvB,KAAC/C;gCAAS+C,OAAM;0CAAO;;0CACvB,KAAC/C;gCAAS+C,OAAM;0CAAQ;;;;kCAE1B,KAAC7C;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM7B,YAAY;wBACzB4C,UAAU,CAACuD,QAAUzK,IAAI,gBAAgByK,MAAMC,MAAM,CAAC/J,KAAK;wBAC3DgK,WAAW;4BAAEK,YAAY;gCAAEC,QAAQ;4BAAK;wBAAE;wBAC1CpB,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM5B,aAAa;wBAC1B2C,UAAU,CAACuD,QAAUzK,IAAI,iBAAiByK,MAAMC,MAAM,CAAC/J,KAAK;wBAC5DgK,WAAW;4BAAEK,YAAY;gCAAEC,QAAQ;4BAAK;wBAAE;wBAC1CpB,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACC0M,MAAK;wBACLjC,OAAM;wBACN2C,aAAY;wBACZJ,YAAW;wBACXnK,OAAOwF,MAAM5E,SAAS;wBACtB2F,UAAU,CAACuD,QAAUzK,IAAI,aAAayK,MAAMC,MAAM,CAAC/J,KAAK;wBACxDkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCActB,KAACpN;wBAAIoM,IAAI;4BAAEgB,UAAU;4BAAKM,UAAU;4BAAGC,UAAU;wBAAI;kCACnD,cAAA,KAAC9M;4BACC+J,SAASd,cAAcc,OAAO;4BAC9B1H,OAAOwF,MAAM3E,WAAW;4BACxB0F,UAAU,CAACoC,OAAStJ,IAAI,eAAesJ;4BACvCf,OAAM;4BACNuC,YAAW;4BACXO,OAAO9D,cAAc+D,KAAK,IAAI,CAAC/D,cAAcc,OAAO,CAACpH,MAAM;4BAC3DsK,WAAU;;;;;0BAKhB,KAAC5N;0BACD,KAACI;gBAAWmM,SAAQ;gBAAWC,OAAM;0BAClC;;YASFT,eAAe,qBACd,KAAC3L;gBAAWmM,SAAQ;gBAAUC,OAAM;0BACjC,uEACC,kEACA;;0BAGN,MAACtM;gBAAMuM,WAAU;gBAAMR,SAAS;gBAAGS,UAAU;gBAACR,IAAI;oBAAES,UAAU;gBAAO;;kCACnE,MAACxM;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM9C,SAAS;wBACtB6D,UAAU,CAACuD,QAAUzK,IAAI,aAAayK,MAAMC,MAAM,CAAC/J,KAAK;wBACxDmK,YAAW;wBACXjB,IAAI;4BAAEgB,UAAU;wBAAI;;0CAEpB,KAACjN;gCAAS+C,OAAM;0CAAI;;4BACnB2C,SAASjC,GAAG,CAAC,CAACmK;oCAEVA;qDADH,KAAC5N;oCAA2B+C,OAAO6K,QAAQvD,GAAG;+CAC3CuD,gBAAAA,QAAQtD,IAAI,YAAZsD,gBAAgBA,QAAQvD,GAAG;mCADfuD,QAAQvD,GAAG;;;;kCAW9B,MAACnK;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM5C,MAAM;wBACnB2D,UAAU,CAACuD,QAAUzK,IAAI,UAAUyK,MAAMC,MAAM,CAAC/J,KAAK;wBACrDmK,YAAW;wBACXjB,IAAI;4BAAEgB,UAAU;wBAAI;;0CAEpB,KAACjN;gCAAS+C,OAAM;0CAAI;;4BACnBwH,SAAS9G,GAAG,CAAC,CAACoK,qBACb,KAAC7N;oCAAwB+C,OAAO8K,KAAKxD,GAAG;8CACrCwD,KAAKvD,IAAI;mCADGuD,KAAKxD,GAAG;;;kCAK3B,KAACnK;wBACC0M,MAAK;wBACLjC,OAAM;wBACN2C,aAAY;wBACZvK,OAAOwF,MAAMnF,IAAI;wBACjBkG,UAAU,CAACuD,QAAUzK,IAAI,QAAQyK,MAAMC,MAAM,CAAC/J,KAAK;wBACnDkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM/E,cAAc;wBAC3B8F,UAAU,CAACuD,QACTzK,IAAI,kBAAkByJ,QAAuBgB,MAAMC,MAAM,CAAC/J,KAAK;wBAEjEgK,WAAW;4BAAEJ,QAAQ;gCAAEK,UAAU;4BAAK;wBAAE;wBACxCE,YAAW;wBACXjB,IAAI;4BAAEgB,UAAU;wBAAI;kCAEnBnE,OAAOC,OAAO,CAACtJ,uBAAuBgE,GAAG,CAAC,CAAC,CAACV,OAAO4H,MAAM,iBACxD,KAAC3K;gCAAqB+C,OAAOA;0CAC1B4H;+BADY5H;;;;0BAerB,MAAC9C;gBAAMuM,WAAU;gBAAMR,SAAS;gBAAGS,UAAU;gBAACR,IAAI;oBAAES,UAAU;gBAAO;;kCACnE,KAACxM;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAMzD,SAAS;wBACtBwE,UAAU,CAACuD,QAAUzK,IAAI,aAAayJ,QAAgBgB,MAAMC,MAAM,CAAC/J,KAAK;wBACxEgK,WAAW;4BACTJ,QAAQ;gCACNK,UAAU;gCACVc,aAAa,CAACC,WACZ,AAACA,SACEtK,GAAG,CAAC,CAACsB,MAAQpD,MAAMoD,KAAKlD,MAAMmD,OAAO,GACrCzB,IAAI,CAAC;4BACZ;wBACF;wBACAyK,OAAOzH,QAAQ4C,KAAK6E,KAAK;wBACzBd,UAAU,GAAE/D,cAAAA,KAAK6E,KAAK,YAAV7E,cAAc;wBAC1B8C,IAAI;4BAAEgB,UAAU;wBAAI;kCAEnBrC,aAAanH,GAAG,CAAC,CAACiH,uBACjB,KAAC1K;gCAA0B+C,OAAO2H,OAAO3F,GAAG;0CACzC2F,OAAOC,KAAK;+BADAD,OAAO3F,GAAG;;kCAK7B,KAAC7E;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAMtD,eAAe;wBAC5BqE,UAAU,CAACuD,QACTzK,IACE,mBACAyJ,QAA+BgB,MAAMC,MAAM,CAAC/J,KAAK;wBAGrDgK,WAAW;4BACTJ,QAAQ;gCACNK,UAAU;gCACVc,aAAa,CAACC,WACZ,AAACA,SACEtK,GAAG,CAAC,CAACyB;4CAAU1F;gDAAAA,wCAAAA,8BAA8B,CAAC0F,MAAM,YAArC1F,wCAAyC0F;uCACxD3B,IAAI,CAAC;4BACZ;wBACF;wBACA2J,YAAW;wBACXjB,IAAI;4BAAEgB,UAAU;wBAAI;kCAEnB1N,yBAAyBkE,GAAG,CAAC,CAACyB,sBAC7B,KAAClF;gCAAqB+C,OAAOmC;0CAC1B1F,8BAA8B,CAAC0F,MAAM;+BADzBA;;kCAYnB,KAACtF;wBACCoN,QAAQ;wBACRJ,MAAK;wBACLnC,SAASrF,UAAU6I,IAAI;wBACvBlL,OAAOwF,MAAMpD,UAAU,CAAC1B,GAAG,CAAC,CAAC7B;gCAEpBwD;mCAF4B;gCACnCxD;gCACA+I,KAAK,GAAEvF,sBAAAA,UAAUvD,KAAK,CAACD,GAAG,YAAnBwD,sBAAuBxD;4BAChC;;wBACAsM,gBAAgB,CAACxD,SAAWA,OAAOC,KAAK;wBACxCwD,sBAAsB,CAACzD,QAAQ0D,SAAW1D,OAAO9I,EAAE,KAAKwM,OAAOxM,EAAE;wBACjE,6DAA6D;wBAC7D,mEAAmE;wBACnE,uBAAuB;wBACvByM,eAAe,CAAC5D,UAAYA;wBAC5B6D,YAAYxE;wBACZyE,eAAe,CAACC,QAAQzL,OAAO0L;4BAC7B,IAAIA,WAAW,SAAS1E,iBAAiBhH;wBAC3C;wBACAuG,UAAU,CAACkF,QAAQzL,QACjBX,IACE,cACAW,MAAMU,GAAG,CAAC,CAACiH,SAAWA,OAAO9I,EAAE;wBAGnC8M,SAAStJ,UAAUuJ,SAAS;wBAC5BC,eACE9E,gBAAgB,4BAA4B;wBAE9C+E,aAAa,CAACC,uBACZ,KAAC5O,wBACK4O;gCACJnE,OAAM;gCACNuC,YAAW;;wBAGfjB,IAAI;4BAAEgB,UAAU;4BAAKM,UAAU;4BAAGC,UAAU;wBAAI;;kCAWlD,KAACtN;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACNuC,YAAW;wBACXnK,OAAOwF,MAAMlD,iBAAiB;wBAC9BiE,UAAU,CAACuD,QAAUzK,IAAI,qBAAqByK,MAAMC,MAAM,CAAC/J,KAAK;wBAChEkJ,IAAI;4BAAEgB,UAAU;wBAAI;;;;YAYvB1E,MAAMjD,MAAM,CAAC7B,GAAG,CAAC,CAAChB,QAAQuI;oBAEAvI,eAsDfsM;gBAvDV,MAAMA,aAAa3D,cAAc3I,OAAOE,GAAG;gBAC3C,MAAMqM,YAAYhM,QAAOP,gBAAAA,OAAOM,KAAK,YAAZN,gBAAgB;gBACzC,qBACE,MAACxC;oBAECuM,WAAU;oBACVR,SAAS;oBACTS,UAAU;oBACVR,IAAI;wBAAES,UAAU;wBAAQuC,YAAY;oBAAa;;sCAEjD,KAAC/O;4BACCyM,MAAM;4BACNC,MAAK;4BACLjC,OAAM;4BACN5H,OAAON,OAAOE,GAAG;4BACjB2G,UAAU,CAACuD,QACTtB,aAAaP,OAAO;oCAAErI,KAAKkK,MAAMC,MAAM,CAAC/J,KAAK;oCAAEA,OAAO;gCAAG;4BAE3DkJ,IAAI;gCAAEgB,UAAU;4BAAI;sCAEnBlC,aAAatH,GAAG,CAAC,CAACiH,uBACjB,KAAC1K;oCAA0B+C,OAAO2H,OAAO/H,GAAG;8CACzC+H,OAAOC,KAAK;mCADAD,OAAO/H,GAAG;;sCAK7B,KAACzC;4BACCyM,MAAM;4BACNC,MAAK;4BACLjC,OAAM;4BACN5H,OAAON,OAAOK,EAAE;4BAChBwG,UAAU,CAACuD,QACTtB,aAAaP,OAAO;oCAClBlI,IAAI+J,MAAMC,MAAM,CAAC/J,KAAK;gCACxB;4BAEFkJ,IAAI;gCAAEgB,UAAU;4BAAI;sCAEnBvN,wBAAwB+D,GAAG,CAAC,CAACX,mBAC5B,KAAC9C;oCAAkB+C,OAAOD;8CACvBhB,gBAAgB,CAACgB,GAAG;mCADRA;;wBAKlBR,aAAawI,GAAG,CAACrI,OAAOK,EAAE,IAAI,OAAOiM,CAAAA,8BAAAA,WAAY5B,IAAI,MAAK,yBACzD,KAACjN;4BACCyM,MAAM;4BACNC,MAAK;4BACLjC,OAAM;4BACN5H,OAAOiM;4BACP1F,UAAU,CAACuD,QACTtB,aAAaP,OAAO;oCAAEjI,OAAO8J,MAAMC,MAAM,CAAC/J,KAAK;gCAAC;4BAElDkJ,IAAI;gCAAEgB,UAAU;4BAAI;sCAEnB,EAAC8B,sBAAAA,WAAWtE,OAAO,YAAlBsE,sBAAsB,EAAE,EAAEtL,GAAG,CAAC,CAACiH,uBAC/B,KAAC1K;oCAAsB+C,OAAO2H;8CAC3BA;mCADYA;6BAKjBqE,CAAAA,8BAAAA,WAAY5B,IAAI,MAAK,2BACvB,MAACjN;4BACCyM,MAAM;4BACNC,MAAK;4BACLjC,OAAM;4BACN5H,OACEN,OAAOM,KAAK,KAAK,OACb,SACAN,OAAOM,KAAK,KAAK,QACf,UACA;4BAERuG,UAAU,CAACuD,QACTtB,aAAaP,OAAO;oCAAEjI,OAAO8J,MAAMC,MAAM,CAAC/J,KAAK,KAAK;gCAAO;4BAE7DkJ,IAAI;gCAAEgB,UAAU;4BAAI;;8CAEpB,KAACjN;oCAAS+C,OAAM;8CAAQ;;8CACxB,KAAC/C;oCAAS+C,OAAM;8CAAS;;;6BAEzBgM,CAAAA,8BAAAA,WAAY5B,IAAI,MAAK,yBACvB,KAACjN;4BACCiN,MAAK;4BACLP,MAAK;4BACLjC,OAAM;4BACN5H,OAAOiM;4BACP1F,UAAU,CAACuD;gCACT,0DAA0D;gCAC1D,yDAAyD;gCACzD,kDAAkD;gCAClD,MAAMqC,OAAOrC,MAAMC,MAAM,CAAC/J,KAAK;gCAC/B,MAAMsF,SAASnB,OAAOgI;gCACtB3D,aAAaP,OAAO;oCAClBjI,OAAOmM,KAAKlI,IAAI,OAAO,MAAME,OAAOC,QAAQ,CAACkB,UAAUA,SAAS6G;gCAClE;4BACF;4BACAjD,IAAI;gCAAEgB,UAAU;4BAAI;2CAGtB,KAAC/M;4BACCiN,MAAM4B,CAAAA,8BAAAA,WAAY5B,IAAI,MAAK,SAAS,SAAS;4BAC7CP,MAAK;4BACLjC,OAAM;4BACN5H,OAAOiM;4BACP1F,UAAU,CAACuD,QACTtB,aAAaP,OAAO;oCAAEjI,OAAO8J,MAAMC,MAAM,CAAC/J,KAAK;gCAAC;4BAElDgK,WACEgC,CAAAA,8BAAAA,WAAY5B,IAAI,MAAK,SACjB;gCAAEC,YAAY;oCAAEC,QAAQ;gCAAK;4BAAE,IAC/BtJ;4BAENkI,IAAI;gCAAEgB,UAAU;4BAAI;;sCAGxB,KAACnN;4BAAO8M,MAAK;4BAAQuC,SAAS,IAAMxD,aAAaX;sCAC9C;;;mBAjHEA;YAqHX;0BACA,MAACnL;;kCACC,KAACC;wBACC8M,MAAK;wBACLN,SAAQ;wBACR8C,UAAU,CAACrE,aAAa1H,MAAM;wBAC9B8L,SAAS7D;kCAER;;oBAEFzB,iBAAiB6D,KAAK,IAAI,CAAC7D,iBAAiBjH,MAAM,CAACS,MAAM,iBACxD,KAAClD;wBACCmM,SAAQ;wBACRC,OAAM;wBACNN,IAAI;4BAAEoD,SAAS;4BAASC,IAAI;wBAAI;kCAE/B,kEACC;yBAEF;;;0BAGN,KAACnP;gBAAWmM,SAAQ;gBAAWC,OAAM;0BAClC;;0BAEH,MAACtM;gBAAMuM,WAAU;gBAAMR,SAAS;gBAAGS,UAAU;gBAACR,IAAI;oBAAES,UAAU;gBAAO;;kCACnE,KAACxM;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAMrE,kBAAkB;wBAC/BoF,UAAU,CAACuD,QAAUzK,IAAI,sBAAsByK,MAAMC,MAAM,CAAC/J,KAAK;wBACjEkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACNuC,YAAW;wBACXnK,OAAOwF,MAAM3B,UAAU;wBACvB0C,UAAU,CAACuD,QAAUzK,IAAI,cAAcyK,MAAMC,MAAM,CAAC/J,KAAK;wBACzDkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAMnE,sBAAsB;wBACnCkF,UAAU,CAACuD,QACTzK,IAAI,0BAA0ByK,MAAMC,MAAM,CAAC/J,KAAK;wBAElDkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACNuC,YAAW;wBACXnK,OAAOwF,MAAMlE,iBAAiB;wBAC9BiF,UAAU,CAACuD,QAAUzK,IAAI,qBAAqByK,MAAMC,MAAM,CAAC/J,KAAK;wBAChEkJ,IAAI;4BAAEgB,UAAU;wBAAI;;;;0BAIxB,KAAClN;0BACD,KAACI;gBAAWmM,SAAQ;gBAAWC,OAAM;0BAClC;;0BAUH,KAACpM;gBAAWmM,SAAQ;gBAAUC,OAAM;0BACjC,qEACC,oEACA;;0BAEJ,MAACtM;gBAAMuM,WAAU;gBAAMR,SAAS;gBAAGS,UAAU;gBAACR,IAAI;oBAAES,UAAU;gBAAO;;kCACnE,KAACxM;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAMhE,gBAAgB;wBAC7B+E,UAAU,CAACuD,QAAUzK,IAAI,oBAAoByK,MAAMC,MAAM,CAAC/J,KAAK;wBAC/DkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM/D,iBAAiB;wBAC9B8E,UAAU,CAACuD,QAAUzK,IAAI,qBAAqByK,MAAMC,MAAM,CAAC/J,KAAK;wBAChEkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACNuC,YAAW;wBACXnK,OAAOwF,MAAM9D,gBAAgB;wBAC7B6E,UAAU,CAACuD,QAAUzK,IAAI,oBAAoByK,MAAMC,MAAM,CAAC/J,KAAK;wBAC/DkJ,IAAI;4BAAEgB,UAAU;wBAAI;;kCAEtB,KAAC/M;wBACCiN,MAAK;wBACLP,MAAK;wBACLjC,OAAM;wBACNuC,YAAW;wBACXnK,OAAOwF,MAAM7D,iBAAiB;wBAC9B4E,UAAU,CAACuD,QAAUzK,IAAI,qBAAqByK,MAAMC,MAAM,CAAC/J,KAAK;wBAChEkJ,IAAI;4BAAEgB,UAAU;wBAAI;;;;0BAIxB,KAAClN;0BACD,KAACI;gBAAWmM,SAAQ;gBAAWC,OAAM;0BAClC;;0BAEH,MAACtM;gBAAMuM,WAAU;gBAAMR,SAAS;gBAAGS,UAAU;gBAACR,IAAI;oBAAES,UAAU;gBAAO;;kCACnE,KAACxM;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM5D,SAAS;wBACtB2E,UAAU,CAACuD,QAAUzK,IAAI,aAAayJ,QAAgBgB,MAAMC,MAAM,CAAC/J,KAAK;wBACxEgK,WAAW;4BAAEJ,QAAQ;gCAAEK,UAAU;4BAAK;wBAAE;wBACxCE,YAAW;wBACXjB,IAAI;4BAAEgB,UAAU;wBAAI;kCAEnBrI,MAAMnB,GAAG,CAAC,CAAC2G;gCAEPA;iDADH,KAACpK;gCAAuB+C,OAAOqH,IAAIC,GAAG;2CACnCD,YAAAA,IAAIE,IAAI,YAARF,YAAYA,IAAIC,GAAG;+BADPD,IAAIC,GAAG;;;kCAK1B,KAACnK;wBACCyM,MAAM;wBACNC,MAAK;wBACLjC,OAAM;wBACN5H,OAAOwF,MAAM1D,YAAY;wBACzByE,UAAU,CAACuD,QACTzK,IAAI,gBAAgByJ,QAAgBgB,MAAMC,MAAM,CAAC/J,KAAK;wBAExDgK,WAAW;4BAAEJ,QAAQ;gCAAEK,UAAU;4BAAK;wBAAE;wBACxCE,YAAW;wBACXjB,IAAI;4BAAEgB,UAAU;wBAAI;kCAEnBrI,MAAMnB,GAAG,CAAC,CAAC2G;gCAEPA;iDADH,KAACpK;gCAAuB+C,OAAOqH,IAAIC,GAAG;2CACnCD,YAAAA,IAAIE,IAAI,YAARF,YAAYA,IAAIC,GAAG;+BADPD,IAAIC,GAAG;;;;;;;AAQlC;AACApB,sBAAsBsG,WAAW,GAAG;AAEpC;;;;;;;;CAQC,GACD,OAAO,SAASC,aACdtM,IAAiC,EACjCuM,QAAgB;IAEhB,MAAM,CAAClH,OAAOmH,SAAS,GAAGnP,SAA+BiG;IACzD,MAAMmJ,YAAYrP,OAAO;IACzBF,UAAU;QACR,IAAI,CAAC8C,QAAQyM,UAAUC,OAAO,KAAKH,UAAU;QAC7CE,UAAUC,OAAO,GAAGH;QACpBC,SAAStI,YAAYlE;IACvB,GAAG;QAACA;QAAMuM;KAAS;IACnB,OAAO;QAAClH;QAAOmH;KAAS;AAC1B;AAEA,eAAezG,sBAAqB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/dynamic-list-rule-fields.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\n/**\n * Every field of `DynamicListRule`, with a control for each.\n *\n * ## Why the whole rule, and why in one place\n *\n * Every dimension the matcher reads has a control here, including the two\n * that are answered by a keyed lookup rather than by the person's own record\n * — engagement, and membership of another audience — and the combinator that\n * decides whether the filters are ANDed, ORed or inverted. Needing to\n * hand-write a stored structure to reach a shipped engine is a missing\n * picker, not a power-user affordance, which is the standard this form is\n * held to rather than a count of fields.\n *\n * One component rather than a form on the create card and a second on the edit\n * page: the two would drift, and the half that drifts is whichever the\n * merchant is not looking at. A rule authored at creation and the same rule\n * reopened for editing are the same questions.\n *\n * ## The draft is text, the value is a rule\n *\n * The controls hold what was TYPED — `vip, ` is a legitimate intermediate\n * state of a tag list, and a component that round-tripped every keystroke\n * through `normalizeDynamicListRule` would delete the comma the moment it was\n * typed. So the draft is strings, `draftToRule` is the one place it becomes a\n * rule, and the parent never sees anything else.\n *\n * ## Money is entered the way a merchant says it\n *\n * `ltvCentsAtLeast` is stored in cents. A field labeled for the stored unit\n * turns \"customers who spent over 500\" into customers who spent over five\n * dollars, silently, and the audience looks plausible either way. The control\n * takes whole currency units and this file owns the only multiplication.\n *\n * ## It reads the segments, and only when it is on screen\n *\n * The segment picker needs the org's saved segments, through\n * `useOrgContactSegments`. That listen belongs to this component so it opens\n * when a rule is actually being authored — the audiences section's cost is a\n * list of lists, and a segment read on arrival would be a read for a form\n * nobody has opened.\n */\n\nimport {\n CONTACT_LIFECYCLE_STAGES,\n CONTACT_LIFECYCLE_STAGE_LABELS,\n CONTACT_SOURCE_LABELS,\n DYNAMIC_LIST_CUSTOM_OPS,\n normalizeDynamicListRule,\n type ContactLifecycleStage,\n type ContactSource,\n type DynamicListCustomClause,\n type DynamicListCustomOp,\n type DynamicListDimensions,\n type DynamicListRule,\n type DynamicListSource,\n} from '@aglyn/aglyn'\nimport {\n Autocomplete,\n Box,\n Button,\n Divider,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n useHostCampaigns,\n useOrgMemberOptions,\n} from '@aglyn/tenant-feature-instance'\nimport CampaignPicker from '@aglyn/shared-ui-email-campaigns/components/campaign-picker.component'\nimport { useOrgCompanyOptions } from '../hooks/use-org-company-options'\nimport { useOrgContactFields } from '../hooks/use-org-contact-fields'\nimport { useOrgContactSegments } from '../hooks/use-org-contact-segments'\nimport { useOrgCrmViews } from '../hooks/use-org-crm-views'\nimport { useOrgLists } from '../hooks/use-org-lists'\n\n/** How each silo reads on screen. */\nconst SOURCE_LABELS: Record<DynamicListSource, string> = {\n contacts: 'Contacts',\n leads: 'Leads',\n siteMembers: 'Site members',\n formSubmissions: 'Form submissions',\n}\n\n/** The same silos, lower-cased for the middle of a sentence. */\nconst SOURCE_PHRASES: Record<DynamicListSource, string> = {\n contacts: 'contacts',\n leads: 'leads',\n siteMembers: 'site members',\n formSubmissions: 'form submissions',\n}\n\n/** A day, in the same UTC calendar the rule's boundaries are stored against. */\nconst dayLabel = (ms: number) => new Date(ms).toISOString().slice(0, 10)\n\n/**\n * The rule, in sentences a merchant can read back to themselves.\n *\n * Every clause names a dimension that is actually SET, so a rule with four\n * dimensions reads as four clauses and a rule with one reads as one — an\n * explanation that also lists the fields a rule does not use is an explanation\n * the reader has to subtract from. Rendered live beside the controls, because\n * the question a filter form has to answer continuously is \"who does this\n * describe\", and a form of eleven boxes does not answer it on its own.\n */\n/** Names for the ids a rule stores, so a clause can say \"Customers\", not a uid. */\nexport interface DynamicListRuleNames {\n /** List id → its name. */\n lists?: Record<string, string>\n /** Segment id → its name. */\n segments?: Record<string, string>\n /** Saved Contacts view id → its name. */\n views?: Record<string, string>\n /** Campaign id → its name. */\n campaigns?: Record<string, string>\n /** Team member uid → how they read on the roster. */\n members?: Record<string, string>\n /** Company id → its name. */\n companies?: Record<string, string>\n /** Custom field key → the definition's label. */\n fields?: Record<string, string>\n}\n\n/** An id, as its name when one is known and as itself when none is. */\nconst named = (id: string, names: Record<string, string> | undefined): string =>\n names?.[id] || id\n\n/**\n * How each custom-field operator reads, as the verb of a clause.\n *\n * `neq` carries its lean in the sentence: the matcher requires a VALUE that\n * differs, so a merchant excluding one plan does not sweep in everyone whose\n * plan was never recorded — and a reader checking a paragraph against their\n * intent cannot be expected to know that from the word \"not\".\n */\nconst CUSTOM_OP_LABELS: Record<DynamicListCustomOp, string> = {\n eq: 'is',\n neq: 'is not',\n contains: 'contains',\n gt: 'is more than',\n lt: 'is less than',\n set: 'is set',\n unset: 'is not set',\n}\n\n/** The operators that take no value — the clause is about presence. */\nconst PRESENCE_OPS: ReadonlySet<DynamicListCustomOp> = new Set(['set', 'unset'])\n\n/** One custom clause as a sentence fragment: `Plan is enterprise`. */\nconst describeCustomClause = (\n clause: DynamicListCustomClause,\n names?: DynamicListRuleNames,\n): string => {\n const field = named(clause.key, names?.fields)\n const verb = CUSTOM_OP_LABELS[clause.op]\n if (clause.op === 'set' || clause.op === 'unset') return `${field} ${verb}`\n const value = String(clause.value ?? '')\n return clause.op === 'neq'\n ? `${field} ${verb} ${value} (a blank does not count)`\n : `${field} ${verb} ${value}`\n}\n\n/** One AND-block, as clauses. The rule's own block and each branch share it. */\nfunction describeDimensions(\n rule: DynamicListDimensions,\n names?: DynamicListRuleNames,\n): string[] {\n const clauses: string[] = []\n if (rule.tags?.length) {\n clauses.push(`Tagged any of: ${rule.tags.join(', ')}.`)\n }\n if (rule.captureSources?.length) {\n clauses.push(\n `Captured by: ${rule.captureSources\n .map((source) => CONTACT_SOURCE_LABELS[source] ?? source)\n .join(', ')}.`,\n )\n }\n if (rule.formNames?.length) {\n clauses.push(`Submitted any of: ${rule.formNames.join(', ')}.`)\n }\n /*\n * The clause names the SILOS the dimension applies to, like every other\n * silo-specific clause here. A lead and a site member carry no campaign, so\n * the sentence has to say that the filter passes them through rather than\n * leaving a reader to conclude that a rule drawing from leads and naming a\n * campaign selects no leads.\n */\n if (rule.campaignIds?.length) {\n clauses.push(\n `Filed under any of these campaigns: ${rule.campaignIds\n .map((id) => named(id, names?.campaigns))\n .join(', ')} — contacts and form submissions only.`,\n )\n }\n if (rule.createdAfterMs !== undefined) {\n clauses.push(`Created on or after ${dayLabel(rule.createdAfterMs)}.`)\n }\n if (rule.createdBeforeMs !== undefined) {\n clauses.push(`Created before ${dayLabel(rule.createdBeforeMs)}.`)\n }\n const behavior = rule.behavior\n if (behavior?.ordersCountAtLeast !== undefined) {\n clauses.push(`At least ${behavior.ordersCountAtLeast} orders.`)\n }\n if (behavior?.ltvCentsAtLeast !== undefined) {\n clauses.push(`Spent at least ${behavior.ltvCentsAtLeast / 100}.`)\n }\n if (behavior?.lastPurchaseWithinDays !== undefined) {\n clauses.push(\n `Bought within the last ${behavior.lastPurchaseWithinDays} days.`,\n )\n }\n if (behavior?.noPurchaseForDays !== undefined) {\n clauses.push(`Has bought nothing for ${behavior.noPurchaseForDays} days.`)\n }\n /*\n * The quiet arms say \"or never\" out loud.\n *\n * The engine counts a person with no open on record as having not opened,\n * which is the opposite lean from `noPurchaseForDays` two clauses above —\n * and a reader checking a paragraph against their intent cannot be expected\n * to remember which dimension leans which way. So the sentence carries it.\n */\n const engagement = rule.engagement\n if (engagement?.openedWithinDays !== undefined) {\n clauses.push(\n `Opened one of your emails in the last ${engagement.openedWithinDays} days.`,\n )\n }\n if (engagement?.clickedWithinDays !== undefined) {\n clauses.push(\n `Clicked a link in one of your emails in the last ` +\n `${engagement.clickedWithinDays} days.`,\n )\n }\n if (engagement?.notOpenedForDays !== undefined) {\n clauses.push(\n `Has opened nothing for ${engagement.notOpenedForDays} days, or never ` +\n `opened anything.`,\n )\n }\n if (engagement?.notClickedForDays !== undefined) {\n clauses.push(\n `Has clicked nothing for ${engagement.notClickedForDays} days, or never ` +\n `clicked anything.`,\n )\n }\n if (rule.inListIds?.length) {\n clauses.push(\n `Already on: ${rule.inListIds\n .map((id) => named(id, names?.lists))\n .join(', ')}.`,\n )\n }\n if (rule.notInListIds?.length) {\n clauses.push(\n `Not on: ${rule.notInListIds\n .map((id) => named(id, names?.lists))\n .join(', ')}.`,\n )\n }\n /*\n * THE CRM DIMENSIONS (AGL-2603). Each names people, stages and companies\n * the way the pickers show them, never by id: a clause reading `uid-8f2a`\n * is a clause nobody can check against their intent. The custom clauses\n * are one sentence each, because each is its own condition and a reader\n * adding a second one has narrowed the audience by exactly one sentence.\n */\n if (rule.ownerUids?.length) {\n clauses.push(\n `Owned by: ${rule.ownerUids\n .map((uid) => named(uid, names?.members))\n .join(', ')}.`,\n )\n }\n if (rule.lifecycleStages?.length) {\n clauses.push(\n `In stage: ${rule.lifecycleStages\n .map((stage) => CONTACT_LIFECYCLE_STAGE_LABELS[stage] ?? stage)\n .join(', ')}.`,\n )\n }\n if (rule.companyIds?.length) {\n clauses.push(\n `At company: ${rule.companyIds\n .map((id) => named(id, names?.companies))\n .join(', ')}.`,\n )\n }\n /*\n * \"One of your campaigns\", against the engagement arms above that say\n * \"one of your emails\": those count every message this workspace sent the\n * address, this counts the campaigns the reading site's group sent — the\n * sentence is the only place a reader can tell the two windows apart.\n */\n if (rule.engagedWithinDays !== undefined) {\n clauses.push(\n `Opened or clicked one of your campaigns in the last ` +\n `${rule.engagedWithinDays} days.`,\n )\n }\n for (const clause of rule.custom ?? []) {\n clauses.push(`${describeCustomClause(clause, names)}.`)\n }\n return clauses\n}\n\nexport function describeDynamicListRule(\n rule: DynamicListRule,\n names?: DynamicListRuleNames,\n): string[] {\n const clauses: string[] = []\n clauses.push(\n rule.sources.length\n ? `Draws from ${rule.sources\n .map((source) => SOURCE_PHRASES[source] ?? source)\n .join(', ')}.`\n : 'Draws from nothing, so it matches nobody.',\n )\n if (rule.segmentId) {\n clauses.push(\n `Reuses saved segment ${named(rule.segmentId, names?.segments)}.`,\n )\n }\n if (rule.viewId) {\n clauses.push(`Reuses saved view ${named(rule.viewId, names?.views)}.`)\n }\n const own = describeDimensions(rule, names)\n if (rule.negate && own.length) {\n /*\n * A negated block is ONE clause, not a list of them.\n *\n * \"Excludes anyone who is tagged vip. Excludes anyone who has spent over\n * 500.\" would read as two independent exclusions, and the rule is one:\n * it excludes people who are BOTH. Somebody checking a paragraph against\n * their intent would come away with the wrong audience.\n */\n clauses.push(\n `Excludes anyone matching all of: ${own\n .map((clause) => clause.replace(/\\.$/, ''))\n .join('; ')}.`,\n )\n } else {\n clauses.push(...own)\n }\n /*\n * The OR branches are ONE clause covering all of them, not one clause each.\n *\n * A candidate has to satisfy at least one branch — so \"at least one of\"\n * printed once per branch would read as a separate requirement per branch,\n * which is the AND this operator exists to escape. The whole disjunction is\n * therefore rendered as a single sentence, with each branch's own filters\n * joined by \"and\" inside it.\n */\n const branches = (rule.any ?? [])\n .map((group) => {\n const inner = describeDimensions(group, names)\n if (!inner.length) return ''\n const joined = inner\n .map((clause) => clause.replace(/\\.$/, '').toLowerCase())\n .join(' and ')\n return group.negate ? `not (${joined})` : joined\n })\n .filter(Boolean)\n if (branches.length) {\n clauses.push(`And at least one of these: ${branches.join('; or ')}.`)\n }\n return clauses\n}\n\n/**\n * How the filters below combine.\n *\n * The three shapes this form authors, mapped onto the rule's two operators:\n * `all` is the plain AND block, `none` is that block with `negate`, and `any`\n * puts each filter into its own OR branch. A saved segment is outside all\n * three — it is resolved once from `segmentId` and always applies, which the\n * control says on screen rather than leaving to be discovered.\n */\nexport type DynamicListRuleMatch = 'all' | 'any' | 'none'\n\n/** The rule as the controls hold it: what was typed, not what it means. */\nexport interface DynamicListRuleDraft {\n sources: DynamicListSource[]\n match: DynamicListRuleMatch\n segmentId: string\n /** A saved Contacts view's id, resolved by the sweep beside the segment (AGL-2617). */\n viewId: string\n tags: string\n captureSources: ContactSource[]\n formNames: string\n /** Campaign ids, from the site's own campaigns picker. */\n campaignIds: string[]\n /** `yyyy-mm-dd`, read as UTC — the same instant `Date.parse` gives it. */\n createdAfter: string\n createdBefore: string\n ordersCountAtLeast: string\n /** WHOLE currency units. `draftToRule` is the only place this becomes cents. */\n ltvAtLeast: string\n lastPurchaseWithinDays: string\n noPurchaseForDays: string\n openedWithinDays: string\n clickedWithinDays: string\n notOpenedForDays: string\n notClickedForDays: string\n inListIds: string[]\n notInListIds: string[]\n /*\n * THE CRM DIMENSIONS (AGL-2603), held as the rule holds them.\n *\n * These are picked, not typed — a uid from the roster, a stage from the\n * fixed list, a company from a search — so there is no free-text\n * intermediate state to preserve and the draft carries the ids. The custom\n * clauses carry the TYPED value: a number field's control hands back a\n * number and a checkbox's a boolean, so the value the sentences describe\n * and the value the matcher compares are the same value.\n */\n ownerUids: string[]\n lifecycleStages: ContactLifecycleStage[]\n companyIds: string[]\n custom: DynamicListCustomClause[]\n /**\n * The re-engagement window (AGL-2616), typed like the purchase windows\n * — days, as text, so an empty box is not a zero-day window.\n */\n engagedWithinDays: string\n}\n\nexport const EMPTY_RULE_DRAFT: DynamicListRuleDraft = {\n sources: ['contacts'],\n match: 'all',\n segmentId: '',\n viewId: '',\n tags: '',\n captureSources: [],\n formNames: '',\n campaignIds: [],\n createdAfter: '',\n createdBefore: '',\n ordersCountAtLeast: '',\n ltvAtLeast: '',\n lastPurchaseWithinDays: '',\n noPurchaseForDays: '',\n openedWithinDays: '',\n clickedWithinDays: '',\n notOpenedForDays: '',\n notClickedForDays: '',\n inListIds: [],\n notInListIds: [],\n ownerUids: [],\n lifecycleStages: [],\n companyIds: [],\n custom: [],\n engagedWithinDays: '',\n}\n\n/** Comma-separated free text → the trimmed, non-empty values. */\nexport const splitRuleList = (value: string): string[] =>\n value\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n/**\n * A day stamp for a date input, in UTC.\n *\n * `Date.parse('2026-01-01')` is UTC midnight, so reading the value back\n * through the local calendar would move the boundary by the reader's offset —\n * a rule saved in Los Angeles would reopen naming the previous day, and saving\n * it again would walk it backwards once per edit.\n */\nconst dayStamp = (ms: number | undefined): string =>\n ms === undefined || !Number.isFinite(ms) ? '' : dayLabel(ms)\n\n/**\n * A stored rule, as the controls should show it.\n *\n * ⚠️ The three shapes {@link DynamicListRuleMatch} names are the shapes this\n * form AUTHORS. A rule written through the REST API can hold branches the\n * controls have no place for — a branch with its own `negate`, or top-level\n * filters alongside branches — and this reads such a rule back as the union\n * of every filter in it, in `any` mode. It is not a silent rewrite: the\n * sentences above the controls are computed from `draftToRule(draft)`, so\n * what a save would store is on screen before the reader saves it.\n */\nexport function ruleToDraft(rule: DynamicListRule): DynamicListRuleDraft {\n // Every filter the rule carries, wherever it carries it. The branches are\n // read for their VALUES; how they combined is answered by `match` below.\n const blocks = [rule, ...(rule.any ?? [])]\n const first = <T,>(read: (block: DynamicListDimensions) => T | undefined): T | undefined => {\n for (const block of blocks) {\n const value = read(block)\n if (value !== undefined) return value\n }\n return undefined\n }\n const list = (read: (block: DynamicListDimensions) => string[] | undefined): string[] => {\n const found = new Set<string>()\n for (const block of blocks) for (const entry of read(block) ?? []) found.add(entry)\n return [...found]\n }\n const number = (read: (block: DynamicListDimensions) => number | undefined): string => {\n const value = first(read)\n return value === undefined ? '' : String(value)\n }\n const ltvCents = first((block) => block.behavior?.ltvCentsAtLeast)\n return {\n sources: rule.sources ?? [],\n match: rule.any?.length ? 'any' : rule.negate ? 'none' : 'all',\n segmentId: rule.segmentId ?? '',\n viewId: rule.viewId ?? '',\n tags: list((block) => block.tags).join(', '),\n captureSources: list((block) => block.captureSources) as ContactSource[],\n formNames: list((block) => block.formNames).join(', '),\n campaignIds: list((block) => block.campaignIds),\n createdAfter: dayStamp(first((block) => block.createdAfterMs)),\n createdBefore: dayStamp(first((block) => block.createdBeforeMs)),\n ordersCountAtLeast: number((block) => block.behavior?.ordersCountAtLeast),\n ltvAtLeast: ltvCents === undefined ? '' : String(ltvCents / 100),\n lastPurchaseWithinDays: number(\n (block) => block.behavior?.lastPurchaseWithinDays,\n ),\n noPurchaseForDays: number((block) => block.behavior?.noPurchaseForDays),\n openedWithinDays: number((block) => block.engagement?.openedWithinDays),\n clickedWithinDays: number((block) => block.engagement?.clickedWithinDays),\n notOpenedForDays: number((block) => block.engagement?.notOpenedForDays),\n notClickedForDays: number((block) => block.engagement?.notClickedForDays),\n inListIds: list((block) => block.inListIds),\n notInListIds: list((block) => block.notInListIds),\n ownerUids: list((block) => block.ownerUids),\n lifecycleStages: list(\n (block) => block.lifecycleStages,\n ) as ContactLifecycleStage[],\n companyIds: list((block) => block.companyIds),\n engagedWithinDays: number((block) => block.engagedWithinDays),\n // Clauses are objects, so the string-set dedupe above cannot hold them;\n // two blocks carrying the same clause are one condition, keyed on its\n // whole shape.\n custom: (() => {\n const seen = new Map<string, DynamicListCustomClause>()\n for (const block of blocks) {\n for (const clause of block.custom ?? []) {\n seen.set(JSON.stringify(clause), clause)\n }\n }\n return [...seen.values()]\n })(),\n }\n}\n\n/**\n * A typed number, or nothing at all.\n *\n * An empty box is not zero. `Number('')` is `0`, and a rule normalizer that\n * accepts zero would turn every field the merchant left alone into an active\n * filter — `noPurchaseForDays: 0` matches nobody, so an untouched form would\n * quietly produce an empty audience.\n */\nconst typedNumber = (value: string): number | undefined => {\n const trimmed = value.trim()\n if (!trimmed) return undefined\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined\n}\n\n/**\n * One filter of the draft, as the smallest block it can live in.\n *\n * `any` mode puts each of these in its own OR branch, so this list is what\n * decides how finely \"any of these filters\" is read: per CONTROL, which is\n * what a reader who just typed into eleven boxes means by \"any of them\". A\n * coarser split — the whole purchase-history block as one branch — would ask\n * them to satisfy three purchase conditions together to match the branch.\n */\nfunction draftDimensions(draft: DynamicListRuleDraft): Record<string, unknown>[] {\n const blocks: Record<string, unknown>[] = []\n const tags = splitRuleList(draft.tags)\n if (tags.length) blocks.push({ tags })\n if (draft.captureSources.length) {\n blocks.push({ captureSources: draft.captureSources })\n }\n const formNames = splitRuleList(draft.formNames)\n if (formNames.length) blocks.push({ formNames })\n if (draft.campaignIds.length) blocks.push({ campaignIds: draft.campaignIds })\n const createdAfterMs = draft.createdAfter\n ? Date.parse(draft.createdAfter)\n : undefined\n if (createdAfterMs !== undefined && Number.isFinite(createdAfterMs)) {\n blocks.push({ createdAfterMs })\n }\n const createdBeforeMs = draft.createdBefore\n ? Date.parse(draft.createdBefore)\n : undefined\n if (createdBeforeMs !== undefined && Number.isFinite(createdBeforeMs)) {\n blocks.push({ createdBeforeMs })\n }\n const ordersCountAtLeast = typedNumber(draft.ordersCountAtLeast)\n if (ordersCountAtLeast !== undefined) {\n blocks.push({ behavior: { ordersCountAtLeast } })\n }\n // Whole units in, cents out — rounded, because a half-cent threshold is not\n // a quantity any order total can be compared against.\n const ltv = typedNumber(draft.ltvAtLeast)\n if (ltv !== undefined) {\n blocks.push({ behavior: { ltvCentsAtLeast: Math.round(ltv * 100) } })\n }\n const lastPurchaseWithinDays = typedNumber(draft.lastPurchaseWithinDays)\n if (lastPurchaseWithinDays !== undefined) {\n blocks.push({ behavior: { lastPurchaseWithinDays } })\n }\n const noPurchaseForDays = typedNumber(draft.noPurchaseForDays)\n if (noPurchaseForDays !== undefined) {\n blocks.push({ behavior: { noPurchaseForDays } })\n }\n const openedWithinDays = typedNumber(draft.openedWithinDays)\n if (openedWithinDays !== undefined) {\n blocks.push({ engagement: { openedWithinDays } })\n }\n const clickedWithinDays = typedNumber(draft.clickedWithinDays)\n if (clickedWithinDays !== undefined) {\n blocks.push({ engagement: { clickedWithinDays } })\n }\n const notOpenedForDays = typedNumber(draft.notOpenedForDays)\n if (notOpenedForDays !== undefined) {\n blocks.push({ engagement: { notOpenedForDays } })\n }\n const notClickedForDays = typedNumber(draft.notClickedForDays)\n if (notClickedForDays !== undefined) {\n blocks.push({ engagement: { notClickedForDays } })\n }\n if (draft.inListIds.length) blocks.push({ inListIds: draft.inListIds })\n if (draft.notInListIds.length) {\n blocks.push({ notInListIds: draft.notInListIds })\n }\n if (draft.ownerUids.length) blocks.push({ ownerUids: draft.ownerUids })\n if (draft.lifecycleStages.length) {\n blocks.push({ lifecycleStages: draft.lifecycleStages })\n }\n if (draft.companyIds.length) blocks.push({ companyIds: draft.companyIds })\n const engagedWithinDays = typedNumber(draft.engagedWithinDays)\n if (engagedWithinDays !== undefined) blocks.push({ engagedWithinDays })\n // One block PER CLAUSE, so that \"any one of the filters below\" reads each\n // condition row as a filter of its own — the same grain the purchase\n // figures get. `mergeDimensions` folds them back into one list for `all`.\n // An unfinished row (no value where one is compared) is dropped by the\n // normalizer at the end, so it is never a condition until it is one.\n for (const clause of draft.custom) blocks.push({ custom: [clause] })\n return blocks\n}\n\n/** The blocks merged into one AND-block, for `all` and `none`. */\nfunction mergeDimensions(\n blocks: Record<string, unknown>[],\n): Record<string, unknown> {\n const merged: Record<string, unknown> = {}\n for (const block of blocks) {\n for (const [key, value] of Object.entries(block)) {\n // `behavior` and `engagement` arrive one leaf at a time, so they are\n // merged at depth. A shallow assign would keep only the last leaf of\n // each and silently drop the other three. `custom` arrives one clause\n // at a time for the same reason and is concatenated for the same one.\n merged[key] =\n key === 'behavior' || key === 'engagement'\n ? { ...((merged[key] as object) ?? {}), ...(value as object) }\n : key === 'custom'\n ? [...((merged[key] as unknown[]) ?? []), ...(value as unknown[])]\n : value\n }\n }\n return merged\n}\n\n/** What the controls mean, coerced by the function the matcher reads through. */\nexport function draftToRule(draft: DynamicListRuleDraft): DynamicListRule {\n const blocks = draftDimensions(draft)\n /*\n * Normalized on the way IN by the same function the materializer reads it\n * back through. A rule coerced on the way out but not on the way in is a\n * rule the console can display differently from the way it evaluates.\n */\n return normalizeDynamicListRule({\n sources: draft.sources,\n ...(draft.segmentId ? { segmentId: draft.segmentId } : {}),\n ...(draft.viewId ? { viewId: draft.viewId } : {}),\n ...(draft.match === 'any'\n ? // Each filter its own branch. The top-level block stays empty, which\n // is no constraint — so the rule is `sources AND (one of these)`.\n { any: blocks }\n : {\n ...mergeDimensions(blocks),\n // `none` on an empty set of filters would be \"exclude everyone\n // matching nothing\", which excludes everyone. The flag is written\n // only when there is something to invert.\n ...(draft.match === 'none' && blocks.length ? { negate: true } : {}),\n }),\n })\n}\n\nexport interface DynamicListRuleFieldsProps {\n /** `['orgs', orgId]` — the resolved org scope the caller already holds. */\n scope: readonly [string, string]\n /**\n * The site the campaign picker speaks for: it offers the org's campaigns\n * placed on this site.\n *\n * A list is org-owned and its rule is materialized against ONE site's silos,\n * which is the same site whose picker a form or a screen is filed from.\n * Offering campaigns the site does not carry would offer ids the sweep can\n * never match, because the membership it reads was written by that site.\n */\n hostId: string\n draft: DynamicListRuleDraft\n onChange: (draft: DynamicListRuleDraft) => void\n /**\n * The audience being edited, when there is one.\n *\n * Kept out of its own list pickers. A rule that referred to the list it\n * fills would oscillate — see `dynamicListRuleWithoutListReference`, which\n * strips it at the sweep as well, because a rule outlives any one form.\n * Offering it here and stripping it there would show a merchant a filter\n * that quietly does nothing.\n */\n listId?: string\n}\n\nexport function DynamicListRuleFields(props: DynamicListRuleFieldsProps) {\n const { scope, hostId, draft, onChange, listId } = props\n const segmentDocs = useOrgContactSegments(scope)\n const viewDocs = useOrgCrmViews(scope)\n const listDocs = useOrgLists(scope)\n /*\n * The campaigns placed on the site, read because this form is on screen.\n *\n * `enabled` is off by default on the hook for the surfaces that render a\n * picker beside fields a reader came for; here the picker IS one of the\n * fields the reader came for, and the same reasoning that opens the segment\n * listen opens this one.\n */\n const siteCampaigns = useHostCampaigns(hostId, { enabled: true })\n /*\n * THE CRM PICKERS' OPTIONS (AGL-2603), read because this form is on screen\n * — the same bargain the segments and the campaigns get, one section up.\n *\n * The team comes through the members route rather than the collection, so\n * a collaborator scoped to one site can still assign an owner; the field\n * definitions are one bounded read; the companies are a SEARCH and read\n * nothing until something is typed.\n */\n const team = useOrgMemberOptions(scope[1], { enabled: true })\n const fieldDefinitions = useOrgContactFields(scope)\n const [companySearch, setCompanySearch] = useState('')\n const companies = useOrgCompanyOptions({\n scope,\n search: companySearch,\n selectedIds: draft.companyIds,\n })\n\n /*\n * The saved segment the rule already names, even when the picker's window\n * does not reach it.\n *\n * A `Select` whose value is not among its options renders empty and warns,\n * so a rule pointing at the fifty-first segment would reopen looking as\n * though it named none — and saving from that screen would erase the\n * reference. The id is offered as its own option instead.\n */\n const segments = useMemo(() => {\n const rows = segmentDocs\n if (!draft.segmentId || rows.some((row) => row.$id === draft.segmentId)) {\n return rows\n }\n return [{ $id: draft.segmentId, name: draft.segmentId }, ...rows]\n }, [segmentDocs, draft.segmentId])\n /*\n * The saved VIEW the rule already names, kept the way the segment is\n * (AGL-2617) — and for one more reason: the picker lists only views that\n * translate whole, so a view edited past that after the rule named it\n * would vanish from its own picker, and saving from that screen would\n * erase the reference. Shown as its id, the sweep's refusal is at least\n * visible.\n */\n const crmViews = useMemo(() => {\n const rows = viewDocs\n if (!draft.viewId || rows.some((row) => row.$id === draft.viewId)) {\n return rows\n }\n return [{ $id: draft.viewId, name: draft.viewId }, ...rows]\n }, [viewDocs, draft.viewId])\n\n /** Every audience except the one being edited — see the prop's note. */\n const lists = useMemo(\n () => listDocs.filter((row) => row.$id !== listId),\n [listDocs, listId],\n )\n\n /** Ids the pickers can show, so a clause names an audience rather than a uid. */\n const names = useMemo(\n () => ({\n lists: Object.fromEntries(\n listDocs.map((row) => [row.$id, row.name ?? row.$id]),\n ),\n segments: Object.fromEntries(\n segments.map((row) => [row.$id, row.name ?? row.$id]),\n ),\n views: Object.fromEntries(crmViews.map((row) => [row.$id, row.name])),\n campaigns: Object.fromEntries(\n siteCampaigns.options.map((option) => [option.value, option.label]),\n ),\n members: Object.fromEntries(\n team.options.map((option) => [option.uid, option.label]),\n ),\n companies: companies.names,\n fields: Object.fromEntries(\n fieldDefinitions.fields.map((field) => [field.key, field.label]),\n ),\n }),\n [\n listDocs,\n segments,\n crmViews,\n siteCampaigns.options,\n team.options,\n companies.names,\n fieldDefinitions.fields,\n ],\n )\n\n /*\n * A stored owner the roster no longer lists, and a stored field key no\n * definition names, are offered as their own option — the segment picker's\n * discipline, for the same reason: a `Select` whose value is not among its\n * options renders empty, and a rule saved from that screen would lose the\n * clause it was opened with. A team member who left still owns the\n * contacts they were assigned until somebody reassigns them, and a retired\n * field still holds every value written under it.\n */\n const ownerOptions = useMemo(() => {\n const known = new Set(team.options.map((option) => option.uid))\n return [\n ...draft.ownerUids\n .filter((uid) => !known.has(uid))\n .map((uid) => ({ uid, label: uid })),\n ...team.options,\n ]\n }, [team.options, draft.ownerUids])\n const fieldOptions = useMemo(() => {\n const known = new Set(fieldDefinitions.fields.map((field) => field.key))\n return [\n ...draft.custom\n .filter((clause) => !known.has(clause.key))\n .map((clause) => ({ key: clause.key, label: clause.key }))\n .filter(\n (entry, index, all) =>\n all.findIndex((other) => other.key === entry.key) === index,\n ),\n ...fieldDefinitions.fields.map(({ key, label }) => ({ key, label })),\n ]\n }, [fieldDefinitions.fields, draft.custom])\n /** The definition a clause's key names, when the org still has one. */\n const definitionFor = (key: string) =>\n fieldDefinitions.fields.find((field) => field.key === key)\n\n const set = <K extends keyof DynamicListRuleDraft>(\n key: K,\n value: DynamicListRuleDraft[K],\n ) => onChange({ ...draft, [key]: value })\n\n /*\n * THE CUSTOM-FIELD ROWS. A new row names the first definition with an\n * empty value, so it is on screen and NOT yet a filter — `draftDimensions`\n * hands it to the normalizer, which drops a valued operator with no value\n * until the reader types one. Changing the field clears the value, because\n * a value typed for a number field is not a value for the select that\n * replaced it; changing the operator to a presence test removes it, so the\n * stored clause carries no value the sentence does not read.\n */\n const addClause = () => {\n const first = fieldOptions[0]\n if (!first) return\n set('custom', [...draft.custom, { key: first.key, op: 'eq', value: '' }])\n }\n const updateClause = (index: number, patch: Partial<DynamicListCustomClause>) =>\n set(\n 'custom',\n draft.custom.map((clause, at) => {\n if (at !== index) return clause\n const next = { ...clause, ...patch }\n if (PRESENCE_OPS.has(next.op)) delete next.value\n return next\n }),\n )\n const removeClause = (index: number) =>\n set(\n 'custom',\n draft.custom.filter((_, at) => at !== index),\n )\n\n /** A multi-select hands back a string when only one option is chosen. */\n const asArray = <T extends string,>(value: unknown): T[] =>\n typeof value === 'string' ? [value as T] : ((value as T[]) ?? [])\n\n const contactsOnly = draft.sources.includes('contacts')\n\n return (\n <Stack spacing={1.5}>\n {/*\n The filters, read back as sentences, above the controls that set them.\n A merchant cannot check eleven boxes against their intent; they can\n check one paragraph. It sits ABOVE the fields on purpose — the reader's\n question is \"who is this\", and the answer should not be below the fold\n of the form that produced it.\n */}\n <Box\n sx={{\n border: 1,\n borderColor: 'divider',\n borderRadius: 1,\n p: 1.5,\n }}\n >\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'This audience'}\n </Typography>\n {describeDynamicListRule(draftToRule(draft), names).map((clause) => (\n <Typography key={clause} variant=\"body2\">\n {clause}\n </Typography>\n ))}\n </Box>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"People from\"\n value={draft.sources}\n onChange={(event) =>\n set('sources', asArray<DynamicListSource>(event.target.value))\n }\n slotProps={{ select: { multiple: true } }}\n sx={{ minWidth: 220 }}\n >\n {Object.entries(SOURCE_LABELS).map(([value, label]) => (\n <MenuItem key={value} value={value}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n {/*\n The combinator, beside the source picker rather than buried below\n the filters it governs. It changes what every control under it\n means, and a reader who found it after filling the form in would\n have been answering a different question the whole time.\n */}\n <TextField\n select\n size=\"small\"\n label=\"Match\"\n value={draft.match}\n onChange={(event) =>\n set('match', event.target.value as DynamicListRuleMatch)\n }\n helperText=\"A saved segment always applies\"\n sx={{ minWidth: 210 }}\n >\n <MenuItem value=\"all\">{'All of the filters below'}</MenuItem>\n <MenuItem value=\"any\">{'Any one of the filters below'}</MenuItem>\n <MenuItem value=\"none\">{'Nobody matching all of them'}</MenuItem>\n </TextField>\n <TextField\n type=\"date\"\n size=\"small\"\n label=\"Created after\"\n value={draft.createdAfter}\n onChange={(event) => set('createdAfter', event.target.value)}\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ minWidth: 170 }}\n />\n <TextField\n type=\"date\"\n size=\"small\"\n label=\"Created before\"\n value={draft.createdBefore}\n onChange={(event) => set('createdBefore', event.target.value)}\n slotProps={{ inputLabel: { shrink: true } }}\n sx={{ minWidth: 170 }}\n />\n <TextField\n size=\"small\"\n label=\"Submitted form\"\n placeholder=\"Contact us\"\n helperText=\"Form submissions only\"\n value={draft.formNames}\n onChange={(event) => set('formNames', event.target.value)}\n sx={{ minWidth: 180 }}\n />\n {/*\n The campaign filter sits with the cross-silo controls rather than\n under Contacts, because it reads on a form submission too — the\n submission carries the campaigns its form was filed under at the\n moment it arrived.\n\n The SHARED picker, the one a form's page and a screen's page assign\n with. A merchant who filed three forms under the spring push should\n pick the campaign here from the same control and the same list of\n names they filed them with; a second select over the same ids is how\n one stored field comes to be presented two ways.\n */}\n <Box sx={{ minWidth: 240, flexGrow: 1, maxWidth: 360 }}>\n <CampaignPicker\n options={siteCampaigns.options}\n value={draft.campaignIds}\n onChange={(next) => set('campaignIds', next)}\n label=\"In campaign\"\n helperText=\"Contacts and form submissions filed under any campaign picked. Being in a campaign is not consent to be emailed.\"\n empty={siteCampaigns.ready && !siteCampaigns.options.length}\n emptyText=\"This site has no campaigns yet. Create one from Marketing to build an audience from it.\"\n />\n </Box>\n </Stack>\n\n <Divider />\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Contacts'}\n </Typography>\n {/*\n Named rather than merely disabled when contacts are not a source. A\n control that vanishes and a control that does nothing look the same to\n somebody who has not read the matcher — and these dimensions are\n SKIPPED for the other silos rather than failed, so a rule can carry\n them while drawing from leads as well.\n */}\n {contactsOnly ? null : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'These apply only to people drawn from Contacts. The rule does not ' +\n 'draw from Contacts, so they select nobody on their own — the ' +\n 'other sources are matched without them.'}\n </Typography>\n )}\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"Saved segment\"\n value={draft.segmentId}\n onChange={(event) => set('segmentId', event.target.value)}\n helperText=\"Reuses that segment's tags and sources\"\n sx={{ minWidth: 200 }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {segments.map((segment) => (\n <MenuItem key={segment.$id} value={segment.$id}>\n {segment.name ?? segment.$id}\n </MenuItem>\n ))}\n </TextField>\n {/*\n A saved Contacts view as an audience (AGL-2617), beside the\n segment: its filters — owner, stage, company, tags, sources,\n dates, purchases and custom fields — always apply, the way a\n segment's do. Only views the sweep can honor are offered.\n */}\n <TextField\n select\n size=\"small\"\n label=\"Saved view\"\n value={draft.viewId}\n onChange={(event) => set('viewId', event.target.value)}\n helperText=\"Reuses that Contacts view's filters\"\n sx={{ minWidth: 200 }}\n >\n <MenuItem value=\"\">{'None'}</MenuItem>\n {crmViews.map((view) => (\n <MenuItem key={view.$id} value={view.$id}>\n {view.name}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Tagged\"\n placeholder=\"vip, wholesale\"\n value={draft.tags}\n onChange={(event) => set('tags', event.target.value)}\n sx={{ minWidth: 180 }}\n />\n <TextField\n select\n size=\"small\"\n label=\"Captured by\"\n value={draft.captureSources}\n onChange={(event) =>\n set('captureSources', asArray<ContactSource>(event.target.value))\n }\n slotProps={{ select: { multiple: true } }}\n helperText=\"How the contact reached you\"\n sx={{ minWidth: 200 }}\n >\n {Object.entries(CONTACT_SOURCE_LABELS).map(([value, label]) => (\n <MenuItem key={value} value={value}>\n {label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n {/*\n THE CRM DIMENSIONS (AGL-2603), under Contacts because that is the only\n silo that carries them. Each is PICKED rather than typed — a person\n from the roster, a stage from the fixed list, a company from a search\n — so the rule stores ids and the sentences above read them back as\n names. An absent value matches none of them: \"owned by Ada\" is not\n satisfied by a contact nobody owns, and the helper text says so where\n a reader would otherwise assume the blank was included.\n */}\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"Owned by\"\n value={draft.ownerUids}\n onChange={(event) => set('ownerUids', asArray<string>(event.target.value))}\n slotProps={{\n select: {\n multiple: true,\n renderValue: (selected) =>\n (selected as string[])\n .map((uid) => named(uid, names.members))\n .join(', '),\n },\n }}\n error={Boolean(team.error)}\n helperText={team.error ?? 'Any of these team members. Unowned contacts are left out.'}\n sx={{ minWidth: 220 }}\n >\n {ownerOptions.map((option) => (\n <MenuItem key={option.uid} value={option.uid}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Lifecycle stage\"\n value={draft.lifecycleStages}\n onChange={(event) =>\n set(\n 'lifecycleStages',\n asArray<ContactLifecycleStage>(event.target.value),\n )\n }\n slotProps={{\n select: {\n multiple: true,\n renderValue: (selected) =>\n (selected as ContactLifecycleStage[])\n .map((stage) => CONTACT_LIFECYCLE_STAGE_LABELS[stage] ?? stage)\n .join(', '),\n },\n }}\n helperText=\"Any of these stages\"\n sx={{ minWidth: 200 }}\n >\n {CONTACT_LIFECYCLE_STAGES.map((stage) => (\n <MenuItem key={stage} value={stage}>\n {CONTACT_LIFECYCLE_STAGE_LABELS[stage]}\n </MenuItem>\n ))}\n </TextField>\n {/*\n A SEARCH, not a list: the org's companies are the collection that\n outgrows any dropdown, so nothing is read until something is typed\n and the hook answers one screen of prefix matches. The chips keep\n their names through the hook's own memory of every id it has seen,\n which is what lets a rule reopened months later still say \"Acme\".\n */}\n <Autocomplete\n multiple\n size=\"small\"\n options={companies.hits}\n value={draft.companyIds.map((id) => ({\n id,\n label: companies.names[id] ?? id,\n }))}\n getOptionLabel={(option) => option.label}\n isOptionEqualToValue={(option, chosen) => option.id === chosen.id}\n // The query already narrowed the hits; a second, client-side\n // filter on the label would hide a match whose stored name differs\n // from its search key.\n filterOptions={(options) => options}\n inputValue={companySearch}\n onInputChange={(_event, value, reason) => {\n if (reason !== 'reset') setCompanySearch(value)\n }}\n onChange={(_event, value) =>\n set(\n 'companyIds',\n value.map((option) => option.id),\n )\n }\n loading={companies.searching}\n noOptionsText={\n companySearch ? 'No company by that name' : 'Type to search companies'\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"At company\"\n helperText=\"Any of these companies\"\n />\n )}\n sx={{ minWidth: 260, flexGrow: 1, maxWidth: 420 }}\n />\n {/*\n THE RE-ENGAGEMENT WINDOW (AGL-2616), beside the other facet reads\n rather than under \"Email engagement\" below: that section reads the\n address-level rollup — every message this workspace sent the\n person — and this reads the stamp the delivery webhook wrote on\n THIS site's contact for THIS site's campaigns. A contact never\n stamped is left out, the lean every CRM dimension takes, and the\n helper says so.\n */}\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Engaged with a campaign within (days)\"\n helperText=\"Opened or clicked one of your campaigns. Never engaged is left out.\"\n value={draft.engagedWithinDays}\n onChange={(event) => set('engagedWithinDays', event.target.value)}\n sx={{ minWidth: 280 }}\n />\n </Stack>\n {/*\n One row per condition, and every row must hold — a second condition\n NARROWS, which is what a second box means everywhere else on this\n form; the OR is the \"any one of the filters\" mode above, where each\n row becomes a branch of its own. The value control is typed by the\n field's definition, so a number field hands the matcher a number and\n a choice field offers its own options, and a presence test asks for\n no value at all.\n */}\n {draft.custom.map((clause, index) => {\n const definition = definitionFor(clause.key)\n const valueText = String(clause.value ?? '')\n return (\n <Stack\n key={index}\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap', alignItems: 'flex-start' }}\n >\n <TextField\n select\n size=\"small\"\n label=\"Field\"\n value={clause.key}\n onChange={(event) =>\n updateClause(index, { key: event.target.value, value: '' })\n }\n sx={{ minWidth: 180 }}\n >\n {fieldOptions.map((option) => (\n <MenuItem key={option.key} value={option.key}>\n {option.label}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Condition\"\n value={clause.op}\n onChange={(event) =>\n updateClause(index, {\n op: event.target.value as DynamicListCustomOp,\n })\n }\n sx={{ minWidth: 160 }}\n >\n {DYNAMIC_LIST_CUSTOM_OPS.map((op) => (\n <MenuItem key={op} value={op}>\n {CUSTOM_OP_LABELS[op]}\n </MenuItem>\n ))}\n </TextField>\n {PRESENCE_OPS.has(clause.op) ? null : definition?.type === 'select' ? (\n <TextField\n select\n size=\"small\"\n label=\"Value\"\n value={valueText}\n onChange={(event) =>\n updateClause(index, { value: event.target.value })\n }\n sx={{ minWidth: 180 }}\n >\n {(definition.options ?? []).map((option) => (\n <MenuItem key={option} value={option}>\n {option}\n </MenuItem>\n ))}\n </TextField>\n ) : definition?.type === 'checkbox' ? (\n <TextField\n select\n size=\"small\"\n label=\"Value\"\n value={\n clause.value === true\n ? 'true'\n : clause.value === false\n ? 'false'\n : ''\n }\n onChange={(event) =>\n updateClause(index, { value: event.target.value === 'true' })\n }\n sx={{ minWidth: 140 }}\n >\n <MenuItem value=\"true\">{'Checked'}</MenuItem>\n <MenuItem value=\"false\">{'Not checked'}</MenuItem>\n </TextField>\n ) : definition?.type === 'number' ? (\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Value\"\n value={valueText}\n onChange={(event) => {\n // A number field stores a NUMBER, so the matcher compares\n // 10 with 9 and not \"10\" with \"9\"; half-typed text stays\n // text until it parses, rather than becoming NaN.\n const text = event.target.value\n const parsed = Number(text)\n updateClause(index, {\n value: text.trim() !== '' && Number.isFinite(parsed) ? parsed : text,\n })\n }}\n sx={{ minWidth: 140 }}\n />\n ) : (\n <TextField\n type={definition?.type === 'date' ? 'date' : 'text'}\n size=\"small\"\n label=\"Value\"\n value={valueText}\n onChange={(event) =>\n updateClause(index, { value: event.target.value })\n }\n slotProps={\n definition?.type === 'date'\n ? { inputLabel: { shrink: true } }\n : undefined\n }\n sx={{ minWidth: 180 }}\n />\n )}\n <Button size=\"small\" onClick={() => removeClause(index)}>\n {'Remove'}\n </Button>\n </Stack>\n )\n })}\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={!fieldOptions.length}\n onClick={addClause}\n >\n {'Add a field condition'}\n </Button>\n {fieldDefinitions.ready && !fieldDefinitions.fields.length ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 0.5 }}\n >\n {'This workspace has no custom contact fields yet. Define them ' +\n 'under CRM → Fields to filter on them here.'}\n </Typography>\n ) : null}\n </Box>\n\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Purchase history'}\n </Typography>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Orders at least\"\n value={draft.ordersCountAtLeast}\n onChange={(event) => set('ordersCountAtLeast', event.target.value)}\n sx={{ minWidth: 160 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Spent at least\"\n helperText=\"Lifetime, in your store's currency\"\n value={draft.ltvAtLeast}\n onChange={(event) => set('ltvAtLeast', event.target.value)}\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Bought within (days)\"\n value={draft.lastPurchaseWithinDays}\n onChange={(event) =>\n set('lastPurchaseWithinDays', event.target.value)\n }\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Nothing bought for (days)\"\n helperText=\"Lapsed customers — never bought is not lapsed\"\n value={draft.noPurchaseForDays}\n onChange={(event) => set('noPurchaseForDays', event.target.value)}\n sx={{ minWidth: 220 }}\n />\n </Stack>\n\n <Divider />\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Email engagement'}\n </Typography>\n {/*\n The two quiet fields carry their lean in the helper text, not only in\n the sentences above. They read the OPPOSITE way from \"Nothing bought\n for\", one section up — somebody who never opened anything counts as\n not having opened, where somebody who never bought is not lapsed — and\n a form that put two opposite defaults next to each other without\n saying so is a form that produces the wrong audience quietly.\n */}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Opens and clicks across every email this workspace has sent the ' +\n 'person. Clicks are the stronger signal: mail apps that preload ' +\n 'images record an open the reader never made.'}\n </Typography>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Opened within (days)\"\n value={draft.openedWithinDays}\n onChange={(event) => set('openedWithinDays', event.target.value)}\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Clicked within (days)\"\n value={draft.clickedWithinDays}\n onChange={(event) => set('clickedWithinDays', event.target.value)}\n sx={{ minWidth: 190 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Nothing opened for (days)\"\n helperText=\"Never opened counts\"\n value={draft.notOpenedForDays}\n onChange={(event) => set('notOpenedForDays', event.target.value)}\n sx={{ minWidth: 210 }}\n />\n <TextField\n type=\"number\"\n size=\"small\"\n label=\"Nothing clicked for (days)\"\n helperText=\"Never clicked counts\"\n value={draft.notClickedForDays}\n onChange={(event) => set('notClickedForDays', event.target.value)}\n sx={{ minWidth: 210 }}\n />\n </Stack>\n\n <Divider />\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Other audiences'}\n </Typography>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n <TextField\n select\n size=\"small\"\n label=\"Already on\"\n value={draft.inListIds}\n onChange={(event) => set('inListIds', asArray<string>(event.target.value))}\n slotProps={{ select: { multiple: true } }}\n helperText=\"Members of every audience picked\"\n sx={{ minWidth: 220 }}\n >\n {lists.map((row) => (\n <MenuItem key={row.$id} value={row.$id}>\n {row.name ?? row.$id}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Not on\"\n value={draft.notInListIds}\n onChange={(event) =>\n set('notInListIds', asArray<string>(event.target.value))\n }\n slotProps={{ select: { multiple: true } }}\n helperText=\"Excludes members of any audience picked\"\n sx={{ minWidth: 220 }}\n >\n {lists.map((row) => (\n <MenuItem key={row.$id} value={row.$id}>\n {row.name ?? row.$id}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n </Stack>\n )\n}\nDynamicListRuleFields.displayName = 'DynamicListRuleFields'\n\n/**\n * A draft seeded from a rule that arrives after the form does.\n *\n * The edit page mounts before its list document lands, so the controls are\n * built from an empty rule and filled in once. Seeding ONCE per subject is the\n * whole point: the document is a live listen, and re-seeding on every snapshot\n * would overwrite whatever the reader had typed each time anything on the list\n * changed — including the snapshot the reader's own save produces.\n */\nexport function useRuleDraft(\n rule: DynamicListRule | undefined,\n resetKey: string,\n): [DynamicListRuleDraft, (draft: DynamicListRuleDraft) => void] {\n const [draft, setDraft] = useState<DynamicListRuleDraft>(EMPTY_RULE_DRAFT)\n const seededFor = useRef('')\n useEffect(() => {\n if (!rule || seededFor.current === resetKey) return\n seededFor.current = resetKey\n setDraft(ruleToDraft(rule))\n }, [rule, resetKey])\n return [draft, setDraft]\n}\n\nexport default DynamicListRuleFields\n"],"names":["CONTACT_LIFECYCLE_STAGES","CONTACT_LIFECYCLE_STAGE_LABELS","CONTACT_SOURCE_LABELS","DYNAMIC_LIST_CUSTOM_OPS","normalizeDynamicListRule","Autocomplete","Box","Button","Divider","MenuItem","Stack","TextField","Typography","useEffect","useMemo","useRef","useState","useHostCampaigns","useOrgMemberOptions","CampaignPicker","useOrgCompanyOptions","useOrgContactFields","useOrgContactSegments","useOrgCrmViews","useOrgLists","SOURCE_LABELS","contacts","leads","siteMembers","formSubmissions","SOURCE_PHRASES","dayLabel","ms","Date","toISOString","slice","named","id","names","CUSTOM_OP_LABELS","eq","neq","contains","gt","lt","set","unset","PRESENCE_OPS","Set","describeCustomClause","clause","field","key","fields","verb","op","value","String","describeDimensions","rule","clauses","tags","length","push","join","captureSources","map","source","formNames","campaignIds","campaigns","createdAfterMs","undefined","createdBeforeMs","behavior","ordersCountAtLeast","ltvCentsAtLeast","lastPurchaseWithinDays","noPurchaseForDays","engagement","openedWithinDays","clickedWithinDays","notOpenedForDays","notClickedForDays","inListIds","lists","notInListIds","ownerUids","uid","members","lifecycleStages","stage","companyIds","companies","engagedWithinDays","custom","describeDynamicListRule","sources","segmentId","segments","viewId","views","own","negate","replace","branches","any","group","inner","joined","toLowerCase","filter","Boolean","EMPTY_RULE_DRAFT","match","createdAfter","createdBefore","ltvAtLeast","splitRuleList","split","entry","trim","dayStamp","Number","isFinite","ruleToDraft","blocks","first","read","block","list","found","add","number","ltvCents","seen","Map","JSON","stringify","values","typedNumber","trimmed","parsed","draftDimensions","draft","parse","ltv","Math","round","mergeDimensions","merged","Object","entries","draftToRule","DynamicListRuleFields","props","team","scope","hostId","onChange","listId","segmentDocs","viewDocs","listDocs","siteCampaigns","enabled","fieldDefinitions","companySearch","setCompanySearch","search","selectedIds","rows","some","row","$id","name","crmViews","fromEntries","options","option","label","ownerOptions","known","has","fieldOptions","index","all","findIndex","other","definitionFor","find","addClause","updateClause","patch","at","next","removeClause","_","asArray","contactsOnly","includes","spacing","sx","border","borderColor","borderRadius","p","variant","color","direction","useFlexGap","flexWrap","select","size","event","target","slotProps","multiple","minWidth","helperText","type","inputLabel","shrink","placeholder","flexGrow","maxWidth","empty","ready","emptyText","segment","view","renderValue","selected","error","hits","getOptionLabel","isOptionEqualToValue","chosen","filterOptions","inputValue","onInputChange","_event","reason","loading","searching","noOptionsText","renderInput","params","definition","valueText","alignItems","text","onClick","disabled","display","mt","displayName","useRuleDraft","resetKey","setDraft","seededFor","current"],"mappings":"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"}
|
|
@@ -22,13 +22,13 @@ import { mdiBullhornOutline, mdiEyeOutline } from "@aglyn/shared-data-mdi";
|
|
|
22
22
|
import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
23
23
|
import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
|
|
24
24
|
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
25
|
-
import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
|
|
25
|
+
import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useFirestoreDoc, useOrgDataScope } from "@aglyn/tenant-feature-instance";
|
|
26
26
|
import { Alert, Button, Chip, Divider, Stack, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@mui/material";
|
|
27
27
|
import { collection, doc, documentId, limit, orderBy, query, updateDoc, where } from "firebase/firestore";
|
|
28
28
|
import { useRouter } from "next/navigation";
|
|
29
29
|
import { Suspense, useMemo, useState } from "react";
|
|
30
30
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
31
|
-
import { CAMPAIGN_SEND_CONTAINER_FIELD } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
|
|
31
|
+
import { CAMPAIGN_SEND_CONTAINER_FIELD, campaignSendVisibleTo, orgCampaignSendsPath } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
|
|
32
32
|
import { emailSendTimeMs } from "@aglyn/shared-ui-email-campaigns/model/email-record";
|
|
33
33
|
import { campaignSendDisplay } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
|
|
34
34
|
import { templateProvenance } from "../model/template-provenance.js";
|
|
@@ -157,14 +157,29 @@ const templateDocsHelp = pluginDocsHelp('designedEmails', {
|
|
|
157
157
|
* carries `sentAt`, a scheduled one carries `sendAtMs`, and no writer
|
|
158
158
|
* stamps a `createdAt` — so `orderBy` on either would not mis-sort this
|
|
159
159
|
* list, it would DROP every message written by the other branch. Ordering
|
|
160
|
-
* on the document id is the one ordering every message satisfies,
|
|
161
|
-
*
|
|
162
|
-
*
|
|
160
|
+
* on the document id is the one ordering every message satisfies, and the
|
|
161
|
+
* rows are sorted by date below over a window this component already holds.
|
|
162
|
+
* The index the filters need is `visibleTo` + `templateScreenId`, declared
|
|
163
|
+
* in `cloud/firebase-firestore.indexes.json`.
|
|
163
164
|
*
|
|
164
165
|
* One MORE than the ceiling, so truncation is a fact rather than a guess.
|
|
165
|
-
|
|
166
|
+
*
|
|
167
|
+
* The sends are the ORGANIZATION'S, each stamped with the one site it is
|
|
168
|
+
* sent as. The `visibleTo` clause names this site: it keeps a sibling
|
|
169
|
+
* site's sends of a same-id design out, and it is what makes the read
|
|
170
|
+
* provable for a collaborator scoped to this site — the rules evaluate a
|
|
171
|
+
* list per document, and an unfiltered one is refused whole. Nothing is
|
|
172
|
+
* read until the site's org is known.
|
|
173
|
+
*/ const { orgId } = useOrgDataScope({
|
|
174
|
+
hostId
|
|
175
|
+
});
|
|
176
|
+
const sendScope = useMemo(()=>campaignSendVisibleTo(hostId), [
|
|
177
|
+
hostId
|
|
178
|
+
]);
|
|
179
|
+
const { data: messageDocs } = useFirestoreCollection(()=>orgId ? query(collection(firestore, ...orgCampaignSendsPath(orgId)), where('visibleTo', 'array-contains-any', sendScope), where('templateScreenId', '==', screenId), orderBy(documentId()), limit(TEMPLATE_CAMPAIGN_CEILING + 1)) : null, [
|
|
166
180
|
firestore,
|
|
167
|
-
|
|
181
|
+
orgId,
|
|
182
|
+
sendScope,
|
|
168
183
|
screenId
|
|
169
184
|
], {
|
|
170
185
|
idField: '$id'
|