@aglyn/aglyn 1.0.0-beta.250 → 1.0.0-beta.253
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -11
- package/src/lib/app-utils/activity-labels.js +1 -0
- package/src/lib/app-utils/activity-labels.js.map +1 -1
- package/src/lib/app-utils/ad-audience.d.ts +42 -0
- package/src/lib/app-utils/ad-audience.js +42 -0
- package/src/lib/app-utils/ad-audience.js.map +1 -0
- package/src/lib/app-utils/business-profile.d.ts +4 -0
- package/src/lib/app-utils/business-profile.js +2 -0
- package/src/lib/app-utils/business-profile.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +9 -0
- package/src/lib/app-utils/console-routes.js +6 -0
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/docs-help-sections.generated.js +455 -451
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +27 -3
- package/src/lib/app-utils/docs-help.generated.js +78 -13
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +193 -67
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/email-block-starters.d.ts +39 -0
- package/src/lib/app-utils/email-block-starters.js +122 -0
- package/src/lib/app-utils/email-block-starters.js.map +1 -0
- package/src/lib/app-utils/health-report.js +11 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/image-edit.d.ts +77 -0
- package/src/lib/app-utils/image-edit.js +116 -0
- package/src/lib/app-utils/image-edit.js.map +1 -0
- package/src/lib/app-utils/operator-alerts.js +14 -0
- package/src/lib/app-utils/operator-alerts.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +1 -1
- package/src/lib/app-utils/release-flags.js +10 -0
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +1 -130
- package/src/lib/app-utils/screen-route.js +2 -151
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/screen-slug.d.ts +146 -0
- package/src/lib/app-utils/screen-slug.js +173 -0
- package/src/lib/app-utils/screen-slug.js.map +1 -0
- package/src/lib/app-utils/site-theme.d.ts +11 -0
- package/src/lib/app-utils/site-theme.js +32 -1
- package/src/lib/app-utils/site-theme.js.map +1 -1
- package/src/lib/app-utils/template-slug.d.ts +101 -0
- package/src/lib/app-utils/template-slug.js +150 -0
- package/src/lib/app-utils/template-slug.js.map +1 -0
- package/src/lib/plugin-manager/feature-plugins.d.ts +39 -0
- package/src/lib/plugin-manager/feature-plugins.js +13 -1
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/aglyn",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.253",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -37,16 +37,16 @@
|
|
|
37
37
|
"./package.json": "./package.json"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
42
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
43
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
44
|
-
"@aglyn/shared-util-first-touch": "1.0.0-beta.
|
|
45
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
46
|
-
"@aglyn/shared-util-logger": "1.0.0-beta.
|
|
47
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
48
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
49
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
40
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.253",
|
|
41
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.253",
|
|
42
|
+
"@aglyn/shared-data-types": "1.0.0-beta.253",
|
|
43
|
+
"@aglyn/shared-util-email": "1.0.0-beta.253",
|
|
44
|
+
"@aglyn/shared-util-first-touch": "1.0.0-beta.253",
|
|
45
|
+
"@aglyn/shared-util-http": "1.0.0-beta.253",
|
|
46
|
+
"@aglyn/shared-util-logger": "1.0.0-beta.253",
|
|
47
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.253",
|
|
48
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.253",
|
|
49
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.253",
|
|
50
50
|
"@data-driven-forms/react-form-renderer": "^4.2.0",
|
|
51
51
|
"@msgpack/msgpack": "^3.1.3",
|
|
52
52
|
"@types/unist": "^3.0.3",
|
|
@@ -187,6 +187,7 @@ import { isPluginJobTargetType, pluginActivityActionSentence, pluginActivityTarg
|
|
|
187
187
|
'user.erased': 'Erased the account',
|
|
188
188
|
'user.impersonate': 'Signed in as the account',
|
|
189
189
|
'user.sendPasswordReset': 'Sent a password reset',
|
|
190
|
+
'user.setAdAudienceOptOut': 'Changed the ad audience opt-out',
|
|
190
191
|
'user.setPassword': 'Set the password',
|
|
191
192
|
'user.signOutDevice': 'Signed a device out',
|
|
192
193
|
'user.updateProfile': 'Updated the profile'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/activity-labels.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * ONE LABEL MAP FOR EVERY ACTIVITY AND AUDIT LIST (AGL-3660).\n *\n * Two logs feed the console's lists. The activity logs\n * (`orgs/{id}/activity`, `hosts/{id}/activity`) store a prose sentence or a\n * plugin's code plus a `{ type, id, name }` target. The staff audit trail\n * (`adminAudit`) stores a dotted code (`ai.job.output`, `org.override`) and\n * a Firestore PATH as its target (`orgs/abc/aiJobs/xyz`). The staff tables\n * printed both raw, so a reader saw `ai.job.output` next to\n * `orgs/9fK…/aiJobs/Qe2…` and had to know the schema to read their own\n * audit trail.\n *\n * Every list now draws its words from here: an action as a sentence\n * (`Created page Home with Aglyn AI`), a target as the site and the item,\n * and — for an audit row — why, the credits it spent and what came of it.\n * The raw code and path are kept on the description, for a tooltip and for\n * the staff details dialog, and never as the visible text.\n */\n\nimport {\n activityActionLabel,\n activityPrimaryText,\n activityTypeLabel,\n type ActivityEntryLike,\n type ActivityTargetLike,\n} from './activity-presenter'\nimport { orgOverrideReasonSummary } from './org-override-reason'\nimport {\n isPluginJobTargetType,\n pluginActivityActionSentence,\n pluginActivityTargetLabel,\n pluginStaffAuditActionLabel,\n pluginStaffAuditCollection,\n pluginStaffAuditDescribe,\n} from '../plugin-manager/plugin-activity-actions'\n\n/** What a list row and the details dialog show for one entry. */\nexport interface ActivityDescription {\n /** The act, as a sentence. Never a code. */\n action: string\n /** What it was done to, in words: the site and the item. Never a path. */\n target: string\n /** Why it was done, when the writer recorded a reason. */\n why: string | null\n /** AI credits it spent, when it spent any. */\n credits: number | null\n /** What came of it, when the writer recorded an outcome. */\n result: string | null\n /** The stored code, when the action was one — for a tooltip and staff. */\n code: string | null\n /** The stored target path (audit) or `type/id` (activity) — staff only. */\n path: string | null\n /** The organization the entry concerns, when it names one. */\n orgId: string | null\n /** The site the entry concerns, when it names one. */\n hostId: string | null\n /** The plugin job behind the entry (an AI job), when there is one. */\n jobId: string | null\n /** The account the entry concerns, when it names one. */\n uid: string | null\n}\n\n/** Names a route resolved for the ids an entry carries. */\nexport interface ActivityNames {\n /** Organization name by org id. */\n orgs?: Readonly<Record<string, string | null | undefined>>\n /** Site name by host id. */\n hosts?: Readonly<Record<string, string | null | undefined>>\n /** An account's address (or name) by uid. */\n users?: Readonly<Record<string, string | null | undefined>>\n}\n\n/** `screen` → `page`, `Home` → `page Home`: the noun a sentence uses. */\nexport function activityTargetPhrase(target: ActivityTargetLike | null | undefined): string {\n const noun = activityTypeLabel(target?.type)\n const lower = noun === 'Item' ? 'item' : noun.charAt(0).toLowerCase() + noun.slice(1)\n const name = target?.name?.trim()\n return name ? `${lower} ${name}` : lower\n}\n\n/**\n * An activity row's action as a sentence about its target. A plugin code\n * with a declared sentence fills it (`Created {target} with Aglyn AI`); any\n * other code reads as its label, and a prose action as itself.\n */\nexport function activityActionSentence(\n action: string | undefined,\n target?: ActivityTargetLike | null,\n): string {\n const stored = action?.trim() ?? ''\n const sentence = pluginActivityActionSentence(stored)\n if (sentence) return sentence.replace('{target}', activityTargetPhrase(target))\n return activityActionLabel(stored)\n}\n\n/**\n * A one-column feed's line: the sentence when the code declares one, else\n * the action with its target's name (`Saved the screen — Home`).\n */\nexport function activityRowText(entry: ActivityEntryLike): string {\n const stored = entry.action?.trim() ?? ''\n return pluginActivityActionSentence(stored)\n ? activityActionSentence(stored, entry.target)\n : activityPrimaryText(entry)\n}\n\n/** Whether a stored action is a dotted code rather than a sentence. */\nexport function isActivityCode(action: unknown): action is string {\n return typeof action === 'string' && /^[a-z][\\w-]*(\\.[\\w-]+)+$/i.test(action.trim())\n}\n\n/*==========================================\n * THE STAFF AUDIT TRAIL'S WORDS.\n *\n * Every code core's doors write. A plugin's doors declare their own through\n * `staffAuditLabels` (the AI plugin's `ai.job.*`), and a code neither knows\n * is humanized from its segments rather than shown raw, so a new writer\n * reads as `Billing: invoice paid` until somebody names it properly.\n *==========================================*/\nexport const STAFF_AUDIT_ACTION_LABELS: Readonly<Record<string, string>> = {\n 'account.closed.self': 'Closed their own account',\n 'account.exported.self': 'Exported their own account data',\n 'ai-usage.exported': 'Exported AI usage',\n 'billing.assistOverage.setCap': 'Set the AI overage ceiling',\n 'billing.assistOverage.setHardCap': 'Set the AI stop-at-band switch',\n 'billing.autoLocked': 'Locked automatically for billing',\n 'billing.collaboratorAllocation': 'Changed collaborator seats',\n 'billing.dispute.opened': 'Payment dispute opened',\n 'billing.disputeClosed': 'Payment dispute closed',\n 'billing.disputeOpened': 'Payment dispute opened',\n 'billing.disputeUnattributed': 'Unattributed payment dispute',\n 'billing.invoice': 'Invoice event',\n 'billing.invoice.closed': 'Invoice closed',\n 'billing.invoice.failed': 'Invoice payment failed',\n 'billing.invoice.paid': 'Invoice paid',\n 'billing.manage': 'Opened billing management',\n 'billing.orphanedSubscription': 'Found a subscription with no workspace',\n 'billing.paymentFailed': 'Payment failed',\n 'billing.paymentMethod.changed': 'Changed the payment method',\n 'billing.platformDisputeLost': 'Lost a payment dispute',\n 'billing.platformInvoiceUncollectible': 'Invoice marked uncollectible',\n 'billing.registerAllocation': 'Changed register allocation',\n 'billing.storageOverage.setCap': 'Set the storage overage ceiling',\n 'billing.subscriptionCanceled': 'Subscription canceled',\n 'billing.usage': 'Recorded usage',\n 'billing.usageBudget.set': 'Set the usage budget',\n 'billing.view': 'Viewed billing',\n 'billing.webhookInert': 'Ignored a billing webhook',\n 'billing.webhookSignatureRejected': 'Rejected a billing webhook signature',\n 'broadcast.send': 'Sent a broadcast',\n 'contact.phone.erased': 'Erased a phone number',\n 'contact.suppression.recorded': 'Added a contact suppression',\n 'contact.suppression.released': 'Released a contact suppression',\n 'coupon.create': 'Created a coupon',\n 'coupon.promotion_code.update': 'Updated a promotion code',\n 'data.transfer.export': 'Exported data',\n 'data.transfer.plan': 'Planned a data transfer',\n 'email.history-imported': 'Imported email history',\n 'email.message-viewed': 'Viewed an email message',\n 'email.sending-domains.reap': 'Removed unused sending domains',\n 'email.suppression.release': 'Released an email suppression',\n 'emailSendRate.update': 'Changed the email send rate',\n 'erasure.runBatch': 'Ran an erasure batch',\n 'erasures.replayed': 'Replayed erasures',\n 'firestore.export': 'Exported the database',\n 'firstPartyHosts.update': 'Changed first-party sites',\n 'flags.update': 'Changed feature flags',\n 'freeWorkspaceCap.update': 'Changed the free workspace cap',\n 'host.deleted': 'Deleted a site',\n 'host.domain.released': 'Released a domain',\n 'host.reattach-domain': 'Reattached a domain',\n 'host.set-subdomain': 'Changed a site address',\n 'host.transfer': 'Transferred a site',\n 'lockdown.lock': 'Locked the workspace down',\n 'lockdown.payouts-pause': 'Paused payouts',\n 'lockdown.payouts-restore': 'Restored payouts',\n 'lockdown.renewals-pause': 'Paused renewals',\n 'lockdown.renewals-resume': 'Resumed renewals',\n 'lockdown.resend-notice': 'Resent the lockdown notice',\n 'maintenance.auditArchive.run': 'Archived the audit log',\n 'maintenance.reapArtifacts.run': 'Removed stale build artifacts',\n 'maintenance.reverifyPlugins.run': 'Re-verified plugins',\n 'operatorAlerts.test': 'Sent a test operator alert',\n 'operatorAlerts.update': 'Changed operator alerts',\n 'org.acquisition-viewed': 'Viewed where the organization came from',\n 'org.auditLog': 'Viewed the organization audit log',\n 'org.discount.apply': 'Applied a discount',\n 'org.discount.remove': 'Removed a discount',\n 'org.enterprise.provision': 'Provisioned enterprise billing',\n 'org.erase-failed': 'Organization erasure failed',\n 'org.erased': 'Erased the organization',\n 'org.erasureCanceled': 'Canceled the organization erasure',\n 'org.erasureRequested': 'Requested organization erasure',\n 'org.exported': 'Exported the organization',\n 'org.name': 'Renamed the organization',\n 'org.note': 'Added a staff note',\n 'org.override': 'Overrode the plan',\n 'org.refund': 'Issued a refund',\n 'org.seatAddons.changed': 'Changed seat add-ons',\n 'org.sso.enforceSignInMethods': 'Changed enforced sign-in methods',\n 'org.sso.jit': 'Changed single sign-on provisioning',\n 'org.subscription-cancel': 'Canceled the subscription',\n 'org.subscriptionStarted.settle': 'Settled a new subscription',\n 'org.successManagerCleared': 'Removed the success manager',\n 'org.successManagerSet': 'Assigned a success manager',\n 'org.upgradeProposal.propose': 'Proposed an upgrade',\n 'org.upgradeProposal.withdraw': 'Withdrew an upgrade proposal',\n 'orgs.unverified.reap': 'Removed unverified organizations',\n 'person.erased': 'Erased a person',\n 'person.erasure-requested': 'Requested erasure of a person',\n 'plan.name': 'Renamed a plan',\n 'plugins.artifacts.reap': 'Removed stale plugin builds',\n 'plugins.remoteServer.load': 'Loaded a remote plugin server',\n 'plugins.verification.decline': 'Declined a plugin verification',\n 'plugins.verifier.regression': 'Plugin verifier regression',\n 'security.pageSecurityHold': 'Put a page on security hold',\n 'security.paymentFraudHold': 'Put a workspace on hold for payment fraud',\n 'security.staffGranted': 'Granted staff access',\n 'site.package.apply': 'Applied a site package',\n 'site.package.undo': 'Undid a site package',\n 'sso.domain.driftDetected': 'Single sign-on domain drift detected',\n 'staff.evidence': 'Recorded evidence',\n 'staff.paymentFailed': 'Payment failed (staff notice)',\n 'staff.planChanged': 'Plan changed (staff notice)',\n 'staff.subscriptionCanceled': 'Subscription canceled (staff notice)',\n 'staff.subscriptionStarted': 'Subscription started (staff notice)',\n 'systemEmail.staffSend': 'Sent a system email',\n 'systemEmail.test': 'Sent a test system email',\n 'taxFilingConfig.clear': 'Cleared the tax filing settings',\n 'taxFilingConfig.update': 'Changed the tax filing settings',\n 'taxablePurchases.clear': 'Cleared taxable purchases',\n 'taxablePurchases.update': 'Changed taxable purchases',\n 'user.acquisition-viewed': 'Viewed where the account came from',\n 'user.erased': 'Erased the account',\n 'user.impersonate': 'Signed in as the account',\n 'user.sendPasswordReset': 'Sent a password reset',\n 'user.setPassword': 'Set the password',\n 'user.signOutDevice': 'Signed a device out',\n 'user.updateProfile': 'Updated the profile',\n}\n\n/** Leading namespaces as a reader names them. */\nconst NAMESPACE_LABELS: Readonly<Record<string, string>> = {\n org: 'Organization',\n orgs: 'Organizations',\n user: 'Account',\n host: 'Site',\n}\n\n/** `paymentMethod` → `payment method`, `reattach-domain` → `reattach domain`. */\nfunction words(segment: string): string {\n return segment\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n}\n\n/** A code nobody named, as words: `billing.invoice.paid` → `Billing: invoice paid`. */\nexport function humanizeActivityCode(code: string): string {\n const [head = '', ...rest] = code.trim().split('.').filter(Boolean)\n const lead = NAMESPACE_LABELS[head] ?? words(head).replace(/^./, (c) => c.toUpperCase())\n if (!rest.length) return lead\n return `${lead}: ${rest.map(words).join(' ')}`\n}\n\n/** What a staff audit code reads as, everywhere it is shown. */\nexport function staffAuditActionLabel(action: string | null | undefined): string {\n const code = action?.trim() ?? ''\n if (!code) return '—'\n return (\n STAFF_AUDIT_ACTION_LABELS[code] ??\n pluginStaffAuditActionLabel(code) ??\n (isActivityCode(code) ? humanizeActivityCode(code) : code)\n )\n}\n\n/** The collections an audit path walks, named for a reader. */\nconst COLLECTION_NOUNS: Readonly<Record<string, string>> = {\n orgs: 'Organization',\n hosts: 'Site',\n users: 'Account',\n members: 'Member',\n invites: 'Invitation',\n screens: 'Page',\n layouts: 'Layout',\n components: 'Component',\n templates: 'Template',\n media: 'Media',\n emailDeliveries: 'Email',\n coupons: 'Coupon',\n flags: 'Feature flag',\n plugins: 'Plugin',\n subscriptions: 'Subscription',\n}\n\n/** `orgs/a/aiJobs/b` → `{ orgs: 'a', aiJobs: 'b' }`, in walk order. */\nfunction pathPairs(path: string): Array<[string, string]> {\n const segments = path.split('/').filter(Boolean)\n const pairs: Array<[string, string]> = []\n for (let at = 0; at < segments.length; at += 2) {\n pairs.push([segments[at] ?? '', segments[at + 1] ?? ''])\n }\n return pairs\n}\n\n/** The stored audit row, read defensively. */\nexport interface StaffAuditEntryLike {\n action?: string | null\n target?: string | null\n subjectUid?: string | null\n reason?: string | null\n note?: string | null\n /** The state the act left; AI rows carry `{ label, hostId, credits }`. */\n after?: Record<string, unknown> | null\n}\n\nconst str = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\nconst num = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\n/** What an audit act did, in words, and the words it was about. */\nexport function describeStaffAudit(\n entry: StaffAuditEntryLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const code = str(entry.action)\n const path = str(entry.target)\n const after = entry.after ?? {}\n const pairs = path && path.includes('/') ? pathPairs(path) : []\n const idOf = (collection: string) => pairs.find(([name]) => name === collection)?.[1] || null\n const orgId = idOf('orgs')\n // A plugin's job collection (the AI plugin's jobs) names the job the row\n // links to; core knows it only through the plugin's declaration.\n const jobPair = pairs.find(([name]) => pluginStaffAuditCollection(name)?.job)\n const jobId = jobPair?.[1] || null\n const hostId = str(after['hostId']) ?? idOf('hosts')\n const uid = idOf('users') ?? str(entry.subjectUid)\n const label = str(after['label'])\n // A plugin may word the row from what the act left: the AI plugin names\n // what a job made (`Created page Home with Aglyn AI`).\n const declared = code ? pluginStaffAuditDescribe(code, after) : undefined\n const action = declared?.action ?? staffAuditActionLabel(code)\n\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const org = orgId ? (names.orgs?.[orgId] ?? null) : null\n const person = uid ? (names.users?.[uid] ?? null) : null\n const last = pairs[pairs.length - 1]\n const parts: string[] = []\n if (site) parts.push(site)\n else if (org) parts.push(org)\n if (jobPair) parts.push(label ?? pluginStaffAuditCollection(jobPair[0])?.noun ?? 'Job')\n else if (last && last[0] === 'users') parts.push(person ?? 'Account')\n else if (last && last[0] !== 'orgs' && last[0] !== 'hosts') {\n parts.push(\n COLLECTION_NOUNS[last[0]] ??\n pluginStaffAuditCollection(last[0])?.noun ??\n activityTypeLabel(last[0]),\n )\n }\n if (!parts.length) {\n if (last) parts.push(COLLECTION_NOUNS[last[0]] ?? pluginStaffAuditCollection(last[0])?.noun ?? 'Item')\n else if (path) parts.push(isActivityCode(path) || /^[\\w-]{16,}$/.test(path) ? 'Platform' : path)\n else parts.push('—')\n }\n\n const result = str(after['result']) ?? str(after['status']) ?? declared?.result ?? null\n\n return {\n action,\n target: parts.join(' · '),\n why: orgOverrideReasonSummary(entry.reason ?? null, entry.note ?? null) ?? str(entry.note),\n credits: num(after['credits']),\n result,\n code,\n path,\n orgId,\n hostId,\n jobId,\n uid,\n }\n}\n\n/** A stored activity row, read defensively, with where it was read from. */\nexport interface ActivityRowLike {\n action?: string | null\n target?: (ActivityTargetLike & { jobId?: string; credits?: number }) | null\n /** `host` or `org`: which log the row came from. */\n scopeType?: string\n scopeId?: string\n}\n\n/**\n * An activity row as the lists show it. Where is the site when the row came\n * from a site's log, else the organization.\n */\nexport function describeActivity(\n entry: ActivityRowLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const stored = str(entry.action)\n const code = isActivityCode(stored) ? stored : null\n const target = entry.target ?? null\n const hostId = entry.scopeType === 'host' ? (entry.scopeId ?? null) : null\n const orgId = entry.scopeType === 'org' ? (entry.scopeId ?? null) : null\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const type = str(target?.type)\n const jobId = isPluginJobTargetType(type) ? str(target?.id) : str(target?.jobId)\n const noun = type ? (pluginActivityTargetLabel(type) ?? activityTypeLabel(type)) : null\n const item = str(target?.name) ?? noun\n const parts = [site, item].filter((part): part is string => Boolean(part))\n return {\n action: activityActionSentence(stored ?? undefined, target) || '—',\n target: parts.join(' · ') || '—',\n why: null,\n credits: num(target?.credits),\n result: null,\n code,\n path: type ? `${type}${target?.id ? `/${target.id}` : ''}` : null,\n orgId,\n hostId,\n jobId,\n uid: type === 'member' ? str(target?.id) : null,\n }\n}\n"],"names":["activityActionLabel","activityPrimaryText","activityTypeLabel","orgOverrideReasonSummary","isPluginJobTargetType","pluginActivityActionSentence","pluginActivityTargetLabel","pluginStaffAuditActionLabel","pluginStaffAuditCollection","pluginStaffAuditDescribe","activityTargetPhrase","target","noun","type","lower","charAt","toLowerCase","slice","name","trim","activityActionSentence","action","stored","sentence","replace","activityRowText","entry","isActivityCode","test","STAFF_AUDIT_ACTION_LABELS","NAMESPACE_LABELS","org","orgs","user","host","words","segment","humanizeActivityCode","code","head","rest","split","filter","Boolean","lead","c","toUpperCase","length","map","join","staffAuditActionLabel","COLLECTION_NOUNS","hosts","users","members","invites","screens","layouts","components","templates","media","emailDeliveries","coupons","flags","plugins","subscriptions","pathPairs","path","segments","pairs","at","push","str","value","num","Number","isFinite","describeStaffAudit","names","idOf","label","after","includes","collection","find","orgId","jobPair","job","jobId","hostId","uid","subjectUid","declared","undefined","site","person","last","parts","result","why","reason","note","credits","describeActivity","scopeType","scopeId","id","item","part"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/activity-labels.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * ONE LABEL MAP FOR EVERY ACTIVITY AND AUDIT LIST (AGL-3660).\n *\n * Two logs feed the console's lists. The activity logs\n * (`orgs/{id}/activity`, `hosts/{id}/activity`) store a prose sentence or a\n * plugin's code plus a `{ type, id, name }` target. The staff audit trail\n * (`adminAudit`) stores a dotted code (`ai.job.output`, `org.override`) and\n * a Firestore PATH as its target (`orgs/abc/aiJobs/xyz`). The staff tables\n * printed both raw, so a reader saw `ai.job.output` next to\n * `orgs/9fK…/aiJobs/Qe2…` and had to know the schema to read their own\n * audit trail.\n *\n * Every list now draws its words from here: an action as a sentence\n * (`Created page Home with Aglyn AI`), a target as the site and the item,\n * and — for an audit row — why, the credits it spent and what came of it.\n * The raw code and path are kept on the description, for a tooltip and for\n * the staff details dialog, and never as the visible text.\n */\n\nimport {\n activityActionLabel,\n activityPrimaryText,\n activityTypeLabel,\n type ActivityEntryLike,\n type ActivityTargetLike,\n} from './activity-presenter'\nimport { orgOverrideReasonSummary } from './org-override-reason'\nimport {\n isPluginJobTargetType,\n pluginActivityActionSentence,\n pluginActivityTargetLabel,\n pluginStaffAuditActionLabel,\n pluginStaffAuditCollection,\n pluginStaffAuditDescribe,\n} from '../plugin-manager/plugin-activity-actions'\n\n/** What a list row and the details dialog show for one entry. */\nexport interface ActivityDescription {\n /** The act, as a sentence. Never a code. */\n action: string\n /** What it was done to, in words: the site and the item. Never a path. */\n target: string\n /** Why it was done, when the writer recorded a reason. */\n why: string | null\n /** AI credits it spent, when it spent any. */\n credits: number | null\n /** What came of it, when the writer recorded an outcome. */\n result: string | null\n /** The stored code, when the action was one — for a tooltip and staff. */\n code: string | null\n /** The stored target path (audit) or `type/id` (activity) — staff only. */\n path: string | null\n /** The organization the entry concerns, when it names one. */\n orgId: string | null\n /** The site the entry concerns, when it names one. */\n hostId: string | null\n /** The plugin job behind the entry (an AI job), when there is one. */\n jobId: string | null\n /** The account the entry concerns, when it names one. */\n uid: string | null\n}\n\n/** Names a route resolved for the ids an entry carries. */\nexport interface ActivityNames {\n /** Organization name by org id. */\n orgs?: Readonly<Record<string, string | null | undefined>>\n /** Site name by host id. */\n hosts?: Readonly<Record<string, string | null | undefined>>\n /** An account's address (or name) by uid. */\n users?: Readonly<Record<string, string | null | undefined>>\n}\n\n/** `screen` → `page`, `Home` → `page Home`: the noun a sentence uses. */\nexport function activityTargetPhrase(target: ActivityTargetLike | null | undefined): string {\n const noun = activityTypeLabel(target?.type)\n const lower = noun === 'Item' ? 'item' : noun.charAt(0).toLowerCase() + noun.slice(1)\n const name = target?.name?.trim()\n return name ? `${lower} ${name}` : lower\n}\n\n/**\n * An activity row's action as a sentence about its target. A plugin code\n * with a declared sentence fills it (`Created {target} with Aglyn AI`); any\n * other code reads as its label, and a prose action as itself.\n */\nexport function activityActionSentence(\n action: string | undefined,\n target?: ActivityTargetLike | null,\n): string {\n const stored = action?.trim() ?? ''\n const sentence = pluginActivityActionSentence(stored)\n if (sentence) return sentence.replace('{target}', activityTargetPhrase(target))\n return activityActionLabel(stored)\n}\n\n/**\n * A one-column feed's line: the sentence when the code declares one, else\n * the action with its target's name (`Saved the screen — Home`).\n */\nexport function activityRowText(entry: ActivityEntryLike): string {\n const stored = entry.action?.trim() ?? ''\n return pluginActivityActionSentence(stored)\n ? activityActionSentence(stored, entry.target)\n : activityPrimaryText(entry)\n}\n\n/** Whether a stored action is a dotted code rather than a sentence. */\nexport function isActivityCode(action: unknown): action is string {\n return typeof action === 'string' && /^[a-z][\\w-]*(\\.[\\w-]+)+$/i.test(action.trim())\n}\n\n/*==========================================\n * THE STAFF AUDIT TRAIL'S WORDS.\n *\n * Every code core's doors write. A plugin's doors declare their own through\n * `staffAuditLabels` (the AI plugin's `ai.job.*`), and a code neither knows\n * is humanized from its segments rather than shown raw, so a new writer\n * reads as `Billing: invoice paid` until somebody names it properly.\n *==========================================*/\nexport const STAFF_AUDIT_ACTION_LABELS: Readonly<Record<string, string>> = {\n 'account.closed.self': 'Closed their own account',\n 'account.exported.self': 'Exported their own account data',\n 'ai-usage.exported': 'Exported AI usage',\n 'billing.assistOverage.setCap': 'Set the AI overage ceiling',\n 'billing.assistOverage.setHardCap': 'Set the AI stop-at-band switch',\n 'billing.autoLocked': 'Locked automatically for billing',\n 'billing.collaboratorAllocation': 'Changed collaborator seats',\n 'billing.dispute.opened': 'Payment dispute opened',\n 'billing.disputeClosed': 'Payment dispute closed',\n 'billing.disputeOpened': 'Payment dispute opened',\n 'billing.disputeUnattributed': 'Unattributed payment dispute',\n 'billing.invoice': 'Invoice event',\n 'billing.invoice.closed': 'Invoice closed',\n 'billing.invoice.failed': 'Invoice payment failed',\n 'billing.invoice.paid': 'Invoice paid',\n 'billing.manage': 'Opened billing management',\n 'billing.orphanedSubscription': 'Found a subscription with no workspace',\n 'billing.paymentFailed': 'Payment failed',\n 'billing.paymentMethod.changed': 'Changed the payment method',\n 'billing.platformDisputeLost': 'Lost a payment dispute',\n 'billing.platformInvoiceUncollectible': 'Invoice marked uncollectible',\n 'billing.registerAllocation': 'Changed register allocation',\n 'billing.storageOverage.setCap': 'Set the storage overage ceiling',\n 'billing.subscriptionCanceled': 'Subscription canceled',\n 'billing.usage': 'Recorded usage',\n 'billing.usageBudget.set': 'Set the usage budget',\n 'billing.view': 'Viewed billing',\n 'billing.webhookInert': 'Ignored a billing webhook',\n 'billing.webhookSignatureRejected': 'Rejected a billing webhook signature',\n 'broadcast.send': 'Sent a broadcast',\n 'contact.phone.erased': 'Erased a phone number',\n 'contact.suppression.recorded': 'Added a contact suppression',\n 'contact.suppression.released': 'Released a contact suppression',\n 'coupon.create': 'Created a coupon',\n 'coupon.promotion_code.update': 'Updated a promotion code',\n 'data.transfer.export': 'Exported data',\n 'data.transfer.plan': 'Planned a data transfer',\n 'email.history-imported': 'Imported email history',\n 'email.message-viewed': 'Viewed an email message',\n 'email.sending-domains.reap': 'Removed unused sending domains',\n 'email.suppression.release': 'Released an email suppression',\n 'emailSendRate.update': 'Changed the email send rate',\n 'erasure.runBatch': 'Ran an erasure batch',\n 'erasures.replayed': 'Replayed erasures',\n 'firestore.export': 'Exported the database',\n 'firstPartyHosts.update': 'Changed first-party sites',\n 'flags.update': 'Changed feature flags',\n 'freeWorkspaceCap.update': 'Changed the free workspace cap',\n 'host.deleted': 'Deleted a site',\n 'host.domain.released': 'Released a domain',\n 'host.reattach-domain': 'Reattached a domain',\n 'host.set-subdomain': 'Changed a site address',\n 'host.transfer': 'Transferred a site',\n 'lockdown.lock': 'Locked the workspace down',\n 'lockdown.payouts-pause': 'Paused payouts',\n 'lockdown.payouts-restore': 'Restored payouts',\n 'lockdown.renewals-pause': 'Paused renewals',\n 'lockdown.renewals-resume': 'Resumed renewals',\n 'lockdown.resend-notice': 'Resent the lockdown notice',\n 'maintenance.auditArchive.run': 'Archived the audit log',\n 'maintenance.reapArtifacts.run': 'Removed stale build artifacts',\n 'maintenance.reverifyPlugins.run': 'Re-verified plugins',\n 'operatorAlerts.test': 'Sent a test operator alert',\n 'operatorAlerts.update': 'Changed operator alerts',\n 'org.acquisition-viewed': 'Viewed where the organization came from',\n 'org.auditLog': 'Viewed the organization audit log',\n 'org.discount.apply': 'Applied a discount',\n 'org.discount.remove': 'Removed a discount',\n 'org.enterprise.provision': 'Provisioned enterprise billing',\n 'org.erase-failed': 'Organization erasure failed',\n 'org.erased': 'Erased the organization',\n 'org.erasureCanceled': 'Canceled the organization erasure',\n 'org.erasureRequested': 'Requested organization erasure',\n 'org.exported': 'Exported the organization',\n 'org.name': 'Renamed the organization',\n 'org.note': 'Added a staff note',\n 'org.override': 'Overrode the plan',\n 'org.refund': 'Issued a refund',\n 'org.seatAddons.changed': 'Changed seat add-ons',\n 'org.sso.enforceSignInMethods': 'Changed enforced sign-in methods',\n 'org.sso.jit': 'Changed single sign-on provisioning',\n 'org.subscription-cancel': 'Canceled the subscription',\n 'org.subscriptionStarted.settle': 'Settled a new subscription',\n 'org.successManagerCleared': 'Removed the success manager',\n 'org.successManagerSet': 'Assigned a success manager',\n 'org.upgradeProposal.propose': 'Proposed an upgrade',\n 'org.upgradeProposal.withdraw': 'Withdrew an upgrade proposal',\n 'orgs.unverified.reap': 'Removed unverified organizations',\n 'person.erased': 'Erased a person',\n 'person.erasure-requested': 'Requested erasure of a person',\n 'plan.name': 'Renamed a plan',\n 'plugins.artifacts.reap': 'Removed stale plugin builds',\n 'plugins.remoteServer.load': 'Loaded a remote plugin server',\n 'plugins.verification.decline': 'Declined a plugin verification',\n 'plugins.verifier.regression': 'Plugin verifier regression',\n 'security.pageSecurityHold': 'Put a page on security hold',\n 'security.paymentFraudHold': 'Put a workspace on hold for payment fraud',\n 'security.staffGranted': 'Granted staff access',\n 'site.package.apply': 'Applied a site package',\n 'site.package.undo': 'Undid a site package',\n 'sso.domain.driftDetected': 'Single sign-on domain drift detected',\n 'staff.evidence': 'Recorded evidence',\n 'staff.paymentFailed': 'Payment failed (staff notice)',\n 'staff.planChanged': 'Plan changed (staff notice)',\n 'staff.subscriptionCanceled': 'Subscription canceled (staff notice)',\n 'staff.subscriptionStarted': 'Subscription started (staff notice)',\n 'systemEmail.staffSend': 'Sent a system email',\n 'systemEmail.test': 'Sent a test system email',\n 'taxFilingConfig.clear': 'Cleared the tax filing settings',\n 'taxFilingConfig.update': 'Changed the tax filing settings',\n 'taxablePurchases.clear': 'Cleared taxable purchases',\n 'taxablePurchases.update': 'Changed taxable purchases',\n 'user.acquisition-viewed': 'Viewed where the account came from',\n 'user.erased': 'Erased the account',\n 'user.impersonate': 'Signed in as the account',\n 'user.sendPasswordReset': 'Sent a password reset',\n 'user.setAdAudienceOptOut': 'Changed the ad audience opt-out',\n 'user.setPassword': 'Set the password',\n 'user.signOutDevice': 'Signed a device out',\n 'user.updateProfile': 'Updated the profile',\n}\n\n/** Leading namespaces as a reader names them. */\nconst NAMESPACE_LABELS: Readonly<Record<string, string>> = {\n org: 'Organization',\n orgs: 'Organizations',\n user: 'Account',\n host: 'Site',\n}\n\n/** `paymentMethod` → `payment method`, `reattach-domain` → `reattach domain`. */\nfunction words(segment: string): string {\n return segment\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[-_]+/g, ' ')\n .toLowerCase()\n .trim()\n}\n\n/** A code nobody named, as words: `billing.invoice.paid` → `Billing: invoice paid`. */\nexport function humanizeActivityCode(code: string): string {\n const [head = '', ...rest] = code.trim().split('.').filter(Boolean)\n const lead = NAMESPACE_LABELS[head] ?? words(head).replace(/^./, (c) => c.toUpperCase())\n if (!rest.length) return lead\n return `${lead}: ${rest.map(words).join(' ')}`\n}\n\n/** What a staff audit code reads as, everywhere it is shown. */\nexport function staffAuditActionLabel(action: string | null | undefined): string {\n const code = action?.trim() ?? ''\n if (!code) return '—'\n return (\n STAFF_AUDIT_ACTION_LABELS[code] ??\n pluginStaffAuditActionLabel(code) ??\n (isActivityCode(code) ? humanizeActivityCode(code) : code)\n )\n}\n\n/** The collections an audit path walks, named for a reader. */\nconst COLLECTION_NOUNS: Readonly<Record<string, string>> = {\n orgs: 'Organization',\n hosts: 'Site',\n users: 'Account',\n members: 'Member',\n invites: 'Invitation',\n screens: 'Page',\n layouts: 'Layout',\n components: 'Component',\n templates: 'Template',\n media: 'Media',\n emailDeliveries: 'Email',\n coupons: 'Coupon',\n flags: 'Feature flag',\n plugins: 'Plugin',\n subscriptions: 'Subscription',\n}\n\n/** `orgs/a/aiJobs/b` → `{ orgs: 'a', aiJobs: 'b' }`, in walk order. */\nfunction pathPairs(path: string): Array<[string, string]> {\n const segments = path.split('/').filter(Boolean)\n const pairs: Array<[string, string]> = []\n for (let at = 0; at < segments.length; at += 2) {\n pairs.push([segments[at] ?? '', segments[at + 1] ?? ''])\n }\n return pairs\n}\n\n/** The stored audit row, read defensively. */\nexport interface StaffAuditEntryLike {\n action?: string | null\n target?: string | null\n subjectUid?: string | null\n reason?: string | null\n note?: string | null\n /** The state the act left; AI rows carry `{ label, hostId, credits }`. */\n after?: Record<string, unknown> | null\n}\n\nconst str = (value: unknown): string | null =>\n typeof value === 'string' && value.trim() ? value.trim() : null\nconst num = (value: unknown): number | null =>\n typeof value === 'number' && Number.isFinite(value) ? value : null\n\n/** What an audit act did, in words, and the words it was about. */\nexport function describeStaffAudit(\n entry: StaffAuditEntryLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const code = str(entry.action)\n const path = str(entry.target)\n const after = entry.after ?? {}\n const pairs = path && path.includes('/') ? pathPairs(path) : []\n const idOf = (collection: string) => pairs.find(([name]) => name === collection)?.[1] || null\n const orgId = idOf('orgs')\n // A plugin's job collection (the AI plugin's jobs) names the job the row\n // links to; core knows it only through the plugin's declaration.\n const jobPair = pairs.find(([name]) => pluginStaffAuditCollection(name)?.job)\n const jobId = jobPair?.[1] || null\n const hostId = str(after['hostId']) ?? idOf('hosts')\n const uid = idOf('users') ?? str(entry.subjectUid)\n const label = str(after['label'])\n // A plugin may word the row from what the act left: the AI plugin names\n // what a job made (`Created page Home with Aglyn AI`).\n const declared = code ? pluginStaffAuditDescribe(code, after) : undefined\n const action = declared?.action ?? staffAuditActionLabel(code)\n\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const org = orgId ? (names.orgs?.[orgId] ?? null) : null\n const person = uid ? (names.users?.[uid] ?? null) : null\n const last = pairs[pairs.length - 1]\n const parts: string[] = []\n if (site) parts.push(site)\n else if (org) parts.push(org)\n if (jobPair) parts.push(label ?? pluginStaffAuditCollection(jobPair[0])?.noun ?? 'Job')\n else if (last && last[0] === 'users') parts.push(person ?? 'Account')\n else if (last && last[0] !== 'orgs' && last[0] !== 'hosts') {\n parts.push(\n COLLECTION_NOUNS[last[0]] ??\n pluginStaffAuditCollection(last[0])?.noun ??\n activityTypeLabel(last[0]),\n )\n }\n if (!parts.length) {\n if (last) parts.push(COLLECTION_NOUNS[last[0]] ?? pluginStaffAuditCollection(last[0])?.noun ?? 'Item')\n else if (path) parts.push(isActivityCode(path) || /^[\\w-]{16,}$/.test(path) ? 'Platform' : path)\n else parts.push('—')\n }\n\n const result = str(after['result']) ?? str(after['status']) ?? declared?.result ?? null\n\n return {\n action,\n target: parts.join(' · '),\n why: orgOverrideReasonSummary(entry.reason ?? null, entry.note ?? null) ?? str(entry.note),\n credits: num(after['credits']),\n result,\n code,\n path,\n orgId,\n hostId,\n jobId,\n uid,\n }\n}\n\n/** A stored activity row, read defensively, with where it was read from. */\nexport interface ActivityRowLike {\n action?: string | null\n target?: (ActivityTargetLike & { jobId?: string; credits?: number }) | null\n /** `host` or `org`: which log the row came from. */\n scopeType?: string\n scopeId?: string\n}\n\n/**\n * An activity row as the lists show it. Where is the site when the row came\n * from a site's log, else the organization.\n */\nexport function describeActivity(\n entry: ActivityRowLike,\n names: ActivityNames = {},\n): ActivityDescription {\n const stored = str(entry.action)\n const code = isActivityCode(stored) ? stored : null\n const target = entry.target ?? null\n const hostId = entry.scopeType === 'host' ? (entry.scopeId ?? null) : null\n const orgId = entry.scopeType === 'org' ? (entry.scopeId ?? null) : null\n const site = hostId ? (names.hosts?.[hostId] ?? null) : null\n const type = str(target?.type)\n const jobId = isPluginJobTargetType(type) ? str(target?.id) : str(target?.jobId)\n const noun = type ? (pluginActivityTargetLabel(type) ?? activityTypeLabel(type)) : null\n const item = str(target?.name) ?? noun\n const parts = [site, item].filter((part): part is string => Boolean(part))\n return {\n action: activityActionSentence(stored ?? undefined, target) || '—',\n target: parts.join(' · ') || '—',\n why: null,\n credits: num(target?.credits),\n result: null,\n code,\n path: type ? `${type}${target?.id ? `/${target.id}` : ''}` : null,\n orgId,\n hostId,\n jobId,\n uid: type === 'member' ? str(target?.id) : null,\n }\n}\n"],"names":["activityActionLabel","activityPrimaryText","activityTypeLabel","orgOverrideReasonSummary","isPluginJobTargetType","pluginActivityActionSentence","pluginActivityTargetLabel","pluginStaffAuditActionLabel","pluginStaffAuditCollection","pluginStaffAuditDescribe","activityTargetPhrase","target","noun","type","lower","charAt","toLowerCase","slice","name","trim","activityActionSentence","action","stored","sentence","replace","activityRowText","entry","isActivityCode","test","STAFF_AUDIT_ACTION_LABELS","NAMESPACE_LABELS","org","orgs","user","host","words","segment","humanizeActivityCode","code","head","rest","split","filter","Boolean","lead","c","toUpperCase","length","map","join","staffAuditActionLabel","COLLECTION_NOUNS","hosts","users","members","invites","screens","layouts","components","templates","media","emailDeliveries","coupons","flags","plugins","subscriptions","pathPairs","path","segments","pairs","at","push","str","value","num","Number","isFinite","describeStaffAudit","names","idOf","label","after","includes","collection","find","orgId","jobPair","job","jobId","hostId","uid","subjectUid","declared","undefined","site","person","last","parts","result","why","reason","note","credits","describeActivity","scopeType","scopeId","id","item","part"],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The account-holder ad audience (AGL-3780): the opt-out a staff member
|
|
19
|
+
* records on `users/{uid}` to keep one account out of the operator's own
|
|
20
|
+
* customer-list audiences on Meta, Google and LinkedIn.
|
|
21
|
+
*
|
|
22
|
+
* Client-safe on purpose: the staff user page reads it to draw the switch,
|
|
23
|
+
* and the nightly sync in `tenant-data-admin` reads it to leave the account
|
|
24
|
+
* out. The rules refuse the field to every client, the account holder and
|
|
25
|
+
* staff alike, so the one writer is the audited staff manage route.
|
|
26
|
+
*/
|
|
27
|
+
/** The field on `users/{uid}`. Absent means the account is not opted out. */
|
|
28
|
+
export declare const AD_AUDIENCE_OPT_OUT_FIELD = "adAudienceOptOut";
|
|
29
|
+
/** What the field holds: when, and which staff member recorded it. */
|
|
30
|
+
export interface AdAudienceOptOut {
|
|
31
|
+
atMs: number;
|
|
32
|
+
/** The staff member's uid. */
|
|
33
|
+
by: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The opt-out a profile document holds, or null when it holds none.
|
|
37
|
+
*
|
|
38
|
+
* Any object counts as an opt-out, even one missing its fields: a malformed
|
|
39
|
+
* record is still somebody asking for the account to be left out, and the
|
|
40
|
+
* safe reading of it is "out".
|
|
41
|
+
*/
|
|
42
|
+
export declare function readAdAudienceOptOut(profile: Record<string, unknown> | null | undefined): AdAudienceOptOut | null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The account-holder ad audience (AGL-3780): the opt-out a staff member
|
|
18
|
+
* records on `users/{uid}` to keep one account out of the operator's own
|
|
19
|
+
* customer-list audiences on Meta, Google and LinkedIn.
|
|
20
|
+
*
|
|
21
|
+
* Client-safe on purpose: the staff user page reads it to draw the switch,
|
|
22
|
+
* and the nightly sync in `tenant-data-admin` reads it to leave the account
|
|
23
|
+
* out. The rules refuse the field to every client, the account holder and
|
|
24
|
+
* staff alike, so the one writer is the audited staff manage route.
|
|
25
|
+
*/ /** The field on `users/{uid}`. Absent means the account is not opted out. */ export const AD_AUDIENCE_OPT_OUT_FIELD = 'adAudienceOptOut';
|
|
26
|
+
/**
|
|
27
|
+
* The opt-out a profile document holds, or null when it holds none.
|
|
28
|
+
*
|
|
29
|
+
* Any object counts as an opt-out, even one missing its fields: a malformed
|
|
30
|
+
* record is still somebody asking for the account to be left out, and the
|
|
31
|
+
* safe reading of it is "out".
|
|
32
|
+
*/ export function readAdAudienceOptOut(profile) {
|
|
33
|
+
const raw = profile == null ? void 0 : profile[AD_AUDIENCE_OPT_OUT_FIELD];
|
|
34
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
35
|
+
const record = raw;
|
|
36
|
+
return {
|
|
37
|
+
atMs: typeof record['atMs'] === 'number' && Number.isFinite(record['atMs']) ? record['atMs'] : 0,
|
|
38
|
+
by: typeof record['by'] === 'string' ? record['by'] : ''
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=ad-audience.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/ad-audience.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The account-holder ad audience (AGL-3780): the opt-out a staff member\n * records on `users/{uid}` to keep one account out of the operator's own\n * customer-list audiences on Meta, Google and LinkedIn.\n *\n * Client-safe on purpose: the staff user page reads it to draw the switch,\n * and the nightly sync in `tenant-data-admin` reads it to leave the account\n * out. The rules refuse the field to every client, the account holder and\n * staff alike, so the one writer is the audited staff manage route.\n */\n\n/** The field on `users/{uid}`. Absent means the account is not opted out. */\nexport const AD_AUDIENCE_OPT_OUT_FIELD = 'adAudienceOptOut'\n\n/** What the field holds: when, and which staff member recorded it. */\nexport interface AdAudienceOptOut {\n atMs: number\n /** The staff member's uid. */\n by: string\n}\n\n/**\n * The opt-out a profile document holds, or null when it holds none.\n *\n * Any object counts as an opt-out, even one missing its fields: a malformed\n * record is still somebody asking for the account to be left out, and the\n * safe reading of it is \"out\".\n */\nexport function readAdAudienceOptOut(\n profile: Record<string, unknown> | null | undefined,\n): AdAudienceOptOut | null {\n const raw = profile?.[AD_AUDIENCE_OPT_OUT_FIELD]\n if (!raw || typeof raw !== 'object') return null\n const record = raw as Record<string, unknown>\n return {\n atMs: typeof record['atMs'] === 'number' && Number.isFinite(record['atMs']) ? record['atMs'] : 0,\n by: typeof record['by'] === 'string' ? record['by'] : '',\n }\n}\n"],"names":["AD_AUDIENCE_OPT_OUT_FIELD","readAdAudienceOptOut","profile","raw","record","atMs","Number","isFinite","by"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;CASC,GAED,2EAA2E,GAC3E,OAAO,MAAMA,4BAA4B,mBAAkB;AAS3D;;;;;;CAMC,GACD,OAAO,SAASC,qBACdC,OAAmD;IAEnD,MAAMC,MAAMD,2BAAAA,OAAS,CAACF,0BAA0B;IAChD,IAAI,CAACG,OAAO,OAAOA,QAAQ,UAAU,OAAO;IAC5C,MAAMC,SAASD;IACf,OAAO;QACLE,MAAM,OAAOD,MAAM,CAAC,OAAO,KAAK,YAAYE,OAAOC,QAAQ,CAACH,MAAM,CAAC,OAAO,IAAIA,MAAM,CAAC,OAAO,GAAG;QAC/FI,IAAI,OAAOJ,MAAM,CAAC,KAAK,KAAK,WAAWA,MAAM,CAAC,KAAK,GAAG;IACxD;AACF"}
|
|
@@ -66,11 +66,15 @@ export type BusinessProfileSource = 'owner' | 'start' | 'ai';
|
|
|
66
66
|
export type BusinessProfileOrigin = BusinessProfileSource | 'site' | 'workspace';
|
|
67
67
|
export declare const BUSINESS_TONES: readonly ["friendly", "professional", "playful", "premium", "plain"];
|
|
68
68
|
export type BusinessTone = (typeof BUSINESS_TONES)[number];
|
|
69
|
+
/** The tones as a plain list, in the order the owner picks them, for the native apps' pickers. */
|
|
70
|
+
export declare const BUSINESS_TONE_ORDER: readonly string[];
|
|
69
71
|
/** Each tone as the owner picks it and as a writer is told it. */
|
|
70
72
|
export declare const BUSINESS_TONE_LABELS: Readonly<Record<BusinessTone, string>>;
|
|
71
73
|
/** The fields the profile document stores itself. */
|
|
72
74
|
export declare const BUSINESS_PROFILE_FIELDS: readonly ["whatYouDo", "services", "serviceArea", "audience", "tone", "toneNotes"];
|
|
73
75
|
export type BusinessProfileField = (typeof BUSINESS_PROFILE_FIELDS)[number];
|
|
76
|
+
/** The stored fields as a plain list, for the native apps' writers. */
|
|
77
|
+
export declare const BUSINESS_PROFILE_FIELD_ORDER: readonly string[];
|
|
74
78
|
/** Each field's ceiling, in characters; `services` is per item. */
|
|
75
79
|
export declare const BUSINESS_PROFILE_MAX_CHARS: Readonly<Record<Exclude<BusinessProfileField, 'tone'>, number>>;
|
|
76
80
|
/** At most this many services are kept. */
|
|
@@ -67,6 +67,7 @@ export const BUSINESS_TONES = [
|
|
|
67
67
|
'premium',
|
|
68
68
|
'plain'
|
|
69
69
|
];
|
|
70
|
+
/** The tones as a plain list, in the order the owner picks them, for the native apps' pickers. */ export const BUSINESS_TONE_ORDER = BUSINESS_TONES;
|
|
70
71
|
/** Each tone as the owner picks it and as a writer is told it. */ export const BUSINESS_TONE_LABELS = {
|
|
71
72
|
friendly: 'Friendly and warm',
|
|
72
73
|
professional: 'Professional and clear',
|
|
@@ -82,6 +83,7 @@ export const BUSINESS_TONES = [
|
|
|
82
83
|
'tone',
|
|
83
84
|
'toneNotes'
|
|
84
85
|
];
|
|
86
|
+
/** The stored fields as a plain list, for the native apps' writers. */ export const BUSINESS_PROFILE_FIELD_ORDER = BUSINESS_PROFILE_FIELDS;
|
|
85
87
|
/** Each field's ceiling, in characters; `services` is per item. */ export const BUSINESS_PROFILE_MAX_CHARS = {
|
|
86
88
|
whatYouDo: 200,
|
|
87
89
|
services: 60,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/business-profile.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { siteProfileUrls, type HostSeoEntity } from './content-authors'\nimport { normalizeAreaServed } from './local-business'\nimport { PLATFORM_BRAND_NAME } from './platform-brand'\n\n/**\n * A site's BUSINESS PROFILE (AGL-3661): what the business is, as everything\n * that writes about it needs to know — the site's own pages, the structured\n * data, and any generator a plugin brings.\n *\n * ── Most of it is already stored, so this stores only the rest ─────────────\n *\n * The site's name, its one-line description, its contact details, its hours,\n * the areas it serves and its social profiles all have a home on the host\n * document already: `seo.entity` (the publisher entity the JSON-LD is built\n * from, edited in Setup → SEO) and `business` (the contact card the `host.*`\n * tokens and the footer read, edited in Setup → Basic details). Copying them\n * here would make two places to keep in step, and the copy would drift.\n *\n * So the profile document holds only what has no home yet — the services, the\n * audience, the tone of voice — plus a line about the business and the area it\n * serves for a site whose SEO entity does not say. {@link resolveBusinessProfile}\n * reads the two together into one picture, field by field, and says where\n * each value came from.\n *\n * ── Where it is stored ────────────────────────────────────────────────────\n *\n * `hosts/{hostId}/businessProfile/profile` for a site and\n * `orgs/{orgId}/businessProfile/defaults` for the workspace, whose values a\n * site inherits where it has none of its own. A subcollection document rather\n * than a key on the host: the host document's `business` map is replaced\n * whole by the contact card's save, and a key beside it would be one more\n * thing a partial write could blank.\n *\n * ── Whose words win ───────────────────────────────────────────────────────\n *\n * Every stored value carries its source. What the owner typed is never\n * replaced by anything else; what the guided start was told replaces only\n * what a generator guessed; and a generator's guess fills only what is empty.\n * {@link mergeBusinessProfilePrefill} is the one place that rule is applied.\n */\n\n/** The collection a profile document lives in, under a host or an org. */\nexport const BUSINESS_PROFILE_SUBCOLLECTION = 'businessProfile'\n/** The site's profile document id. */\nexport const BUSINESS_PROFILE_SITE_DOC = 'profile'\n/** The workspace defaults document id. */\nexport const BUSINESS_PROFILE_WORKSPACE_DOC = 'defaults'\n\n/** Who wrote a stored value: the owner, the guided start's answers, or a generator. */\nexport type BusinessProfileSource = 'owner' | 'start' | 'ai'\n\n/**\n * Where a RESOLVED value came from: a stored source, the site's own settings\n * (`seo.entity`, `business`, the display name), or the workspace defaults.\n */\nexport type BusinessProfileOrigin = BusinessProfileSource | 'site' | 'workspace'\n\n/** How much a source's words weigh: a higher rank is never replaced by a lower one. */\nconst SOURCE_RANK: Readonly<Record<BusinessProfileSource, number>> = {\n ai: 1,\n start: 2,\n owner: 3,\n}\n\nexport const BUSINESS_TONES = ['friendly', 'professional', 'playful', 'premium', 'plain'] as const\nexport type BusinessTone = (typeof BUSINESS_TONES)[number]\n\n/** Each tone as the owner picks it and as a writer is told it. */\nexport const BUSINESS_TONE_LABELS: Readonly<Record<BusinessTone, string>> = {\n friendly: 'Friendly and warm',\n professional: 'Professional and clear',\n playful: 'Playful and upbeat',\n premium: 'Premium and refined',\n plain: 'Plain and direct',\n}\n\n/** The fields the profile document stores itself. */\nexport const BUSINESS_PROFILE_FIELDS = [\n 'whatYouDo',\n 'services',\n 'serviceArea',\n 'audience',\n 'tone',\n 'toneNotes',\n] as const\nexport type BusinessProfileField = (typeof BUSINESS_PROFILE_FIELDS)[number]\n\n/** Each field's ceiling, in characters; `services` is per item. */\nexport const BUSINESS_PROFILE_MAX_CHARS: Readonly<Record<Exclude<BusinessProfileField, 'tone'>, number>> = {\n whatYouDo: 200,\n services: 60,\n serviceArea: 120,\n audience: 160,\n toneNotes: 200,\n}\n\n/** At most this many services are kept. */\nexport const BUSINESS_PROFILE_MAX_SERVICES = 12\n\n/** The stored document, at either level. */\nexport interface BusinessProfileDoc {\n whatYouDo?: string\n services?: string[]\n serviceArea?: string\n audience?: string\n tone?: BusinessTone | null\n toneNotes?: string\n /** Who wrote each stored value; a value without one is the owner's. */\n sources?: Partial<Record<BusinessProfileField, BusinessProfileSource>>\n updatedAt?: unknown\n updatedBy?: string | null\n}\n\n/** The values a write carries, before they are sanitized. */\nexport type BusinessProfileValues = Partial<\n Pick<BusinessProfileDoc, 'whatYouDo' | 'services' | 'serviceArea' | 'audience' | 'tone' | 'toneNotes'>\n>\n\n/** The host fields the profile reads. A closed surface, as the host tokens' is. */\nexport interface BusinessProfileHost {\n displayName?: string\n logoUrl?: string\n seo?: { description?: string; entity?: HostSeoEntity | null } | null\n business?: {\n supportEmail?: string\n address?: string\n socialLinks?: Array<{ label?: string; url?: string } | null> | null\n } | null\n}\n\nexport interface ResolvedBusinessField<T> {\n value: T\n origin: BusinessProfileOrigin\n}\n\n/** The picture every reader works from: each field's value and where it came from. */\nexport interface ResolvedBusinessProfile {\n name: ResolvedBusinessField<string> | null\n whatYouDo: ResolvedBusinessField<string> | null\n services: ResolvedBusinessField<string[]> | null\n serviceArea: ResolvedBusinessField<string> | null\n audience: ResolvedBusinessField<string> | null\n tone: ResolvedBusinessField<BusinessTone> | null\n toneNotes: ResolvedBusinessField<string> | null\n /** The schema.org business type the SEO entity names, e.g. `Plumber`. */\n businessType: string | null\n /** Real contact details, each only where the owner entered it in the site's settings. */\n contact: {\n email: string | null\n phone: string | null\n address: string | null\n hours: string | null\n }\n /** The site's social and other profiles, https only. */\n profiles: string[]\n logo: string | null\n}\n\nconst text = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim().slice(0, max) : ''\n\nconst tone = (value: unknown): BusinessTone | null =>\n (BUSINESS_TONES as readonly unknown[]).includes(value) ? (value as BusinessTone) : null\n\nconst services = (value: unknown): string[] => {\n const items = Array.isArray(value) ? value : typeof value === 'string' ? value.split(/[,\\n]/) : []\n const seen = new Set<string>()\n const kept: string[] = []\n for (const item of items) {\n const name = text(item, BUSINESS_PROFILE_MAX_CHARS.services)\n const key = name.toLowerCase()\n if (!name || seen.has(key)) continue\n seen.add(key)\n kept.push(name)\n if (kept.length >= BUSINESS_PROFILE_MAX_SERVICES) break\n }\n return kept\n}\n\n/** One field's stored value, sanitized; empty is `''`, `[]` or `null`. */\nfunction fieldValue(doc: BusinessProfileValues | null | undefined, field: BusinessProfileField): unknown {\n switch (field) {\n case 'services':\n return services(doc?.services)\n case 'tone':\n return tone(doc?.tone)\n default:\n return text(doc?.[field], BUSINESS_PROFILE_MAX_CHARS[field])\n }\n}\n\nconst isEmpty = (value: unknown): boolean =>\n value === null || value === '' || (Array.isArray(value) && value.length === 0)\n\n/** The source a stored, non-empty value carries; the owner's when none is recorded. */\nexport function businessProfileSourceOf(\n doc: BusinessProfileDoc | null | undefined,\n field: BusinessProfileField,\n): BusinessProfileSource | null {\n if (isEmpty(fieldValue(doc, field))) return null\n const source = doc?.sources?.[field]\n return source && source in SOURCE_RANK ? source : 'owner'\n}\n\n/** Every value a write may carry, sanitized; an absent field stays absent. */\nexport function sanitizeBusinessProfileValues(values: BusinessProfileValues): BusinessProfileValues {\n const clean: BusinessProfileValues = {}\n for (const field of BUSINESS_PROFILE_FIELDS) {\n if (!(field in values)) continue\n ;(clean as Record<string, unknown>)[field] = fieldValue(values, field)\n }\n return clean\n}\n\n/**\n * The document a generator's or the guided start's values make of the stored\n * one: each value fills a field that is empty or was written by a source that\n * weighs less, and nothing else. `null` when nothing would change, so a caller\n * writes only when there is something to write.\n */\nexport function mergeBusinessProfilePrefill(\n current: BusinessProfileDoc | null | undefined,\n values: BusinessProfileValues,\n source: Exclude<BusinessProfileSource, 'owner'>,\n): BusinessProfileDoc | null {\n const clean = sanitizeBusinessProfileValues(values)\n const next: BusinessProfileDoc = { ...(current ?? {}), sources: { ...(current?.sources ?? {}) } }\n let changed = false\n for (const field of BUSINESS_PROFILE_FIELDS) {\n if (!(field in clean)) continue\n const value = (clean as Record<string, unknown>)[field]\n if (isEmpty(value)) continue\n // An explicit source counts even on an empty value: a field the owner\n // cleared stays cleared rather than being refilled behind them.\n const recorded = current?.sources?.[field]\n const held = recorded && recorded in SOURCE_RANK ? recorded : businessProfileSourceOf(current, field)\n if (held && SOURCE_RANK[held] >= SOURCE_RANK[source]) continue\n if (JSON.stringify(fieldValue(current, field)) === JSON.stringify(value)) continue\n ;(next as Record<string, unknown>)[field] = value\n next.sources![field] = source\n changed = true\n }\n return changed ? next : null\n}\n\n/**\n * The document the owner's save makes: every field they changed becomes\n * theirs, and a field they left as it was keeps the source it had — so saving\n * the form after correcting one line does not claim every suggestion on it.\n */\nexport function businessProfileOwnerWrite(\n current: BusinessProfileDoc | null | undefined,\n edited: BusinessProfileValues,\n): Pick<BusinessProfileDoc, BusinessProfileField | 'sources'> {\n const clean = sanitizeBusinessProfileValues(edited)\n const next: Record<string, unknown> = {}\n const sources: Partial<Record<BusinessProfileField, BusinessProfileSource>> = {}\n for (const field of BUSINESS_PROFILE_FIELDS) {\n const before = fieldValue(current, field)\n const after = field in clean ? (clean as Record<string, unknown>)[field] : before\n next[field] = after\n const same = JSON.stringify(before) === JSON.stringify(after)\n if (isEmpty(after)) {\n // A suggestion the owner deleted is their decision, and recorded as\n // one, so the next prefill does not put it back.\n if (!same) sources[field] = 'owner'\n else if (current?.sources?.[field] === 'owner') sources[field] = 'owner'\n continue\n }\n sources[field] = same ? (businessProfileSourceOf(current, field) ?? 'owner') : 'owner'\n }\n return { ...(next as Pick<BusinessProfileDoc, BusinessProfileField>), sources }\n}\n\n/** The postal address as one line: the SEO entity's parts, else the contact card's block. */\nfunction addressLine(host: BusinessProfileHost | null | undefined): string {\n const parts = host?.seo?.entity?.address\n const line = [\n parts?.streetAddress,\n parts?.addressLocality,\n parts?.addressRegion,\n parts?.postalCode,\n parts?.addressCountry,\n ]\n .map((part) => text(part, 200))\n .filter(Boolean)\n .join(', ')\n return line || text(host?.business?.address, 400)\n}\n\n/**\n * The profile as every reader sees it, field by field (AGL-3661):\n *\n * 1. what the OWNER typed into the site's profile;\n * 2. what the site's own settings say (the SEO entity, the contact card, the\n * display name), which the owner typed too, elsewhere;\n * 3. what the guided start or a generator stored on the site's profile;\n * 4. the workspace defaults.\n *\n * Contact details, the logo and the profiles come from the site's settings\n * only: nothing but the owner's own entry may say how to reach the business.\n */\nexport function resolveBusinessProfile(input: {\n host: BusinessProfileHost | null | undefined\n site?: BusinessProfileDoc | null\n workspace?: BusinessProfileDoc | null\n}): ResolvedBusinessProfile {\n const { host, site, workspace } = input\n const entity = host?.seo?.entity ?? null\n const fromSite: Partial<Record<BusinessProfileField, unknown>> = {\n whatYouDo: text(entity?.description, BUSINESS_PROFILE_MAX_CHARS.whatYouDo),\n serviceArea: normalizeAreaServed(entity?.areaServed).join(', ').slice(0, BUSINESS_PROFILE_MAX_CHARS.serviceArea),\n }\n\n const resolve = <T>(field: BusinessProfileField): ResolvedBusinessField<T> | null => {\n const stored = businessProfileSourceOf(site, field)\n if (stored === 'owner') return { value: fieldValue(site, field) as T, origin: 'owner' }\n const own = fromSite[field]\n if (own !== undefined && !isEmpty(own)) return { value: own as T, origin: 'site' }\n if (stored) return { value: fieldValue(site, field) as T, origin: stored }\n if (businessProfileSourceOf(workspace, field)) {\n return { value: fieldValue(workspace, field) as T, origin: 'workspace' }\n }\n return null\n }\n\n const name = text(entity?.name, 200) || text(host?.displayName, 200)\n return {\n name: name ? { value: name, origin: 'site' } : null,\n whatYouDo: resolve<string>('whatYouDo'),\n services: resolve<string[]>('services'),\n serviceArea: resolve<string>('serviceArea'),\n audience: resolve<string>('audience'),\n tone: resolve<BusinessTone>('tone'),\n toneNotes: resolve<string>('toneNotes'),\n businessType: text(entity?.businessType, 80) || null,\n contact: {\n email: text(entity?.email, 320) || text(host?.business?.supportEmail, 320) || null,\n phone: text(entity?.telephone, 64) || null,\n address: addressLine(host) || null,\n hours: text(entity?.openingHours, 400) || null,\n },\n profiles: siteProfileUrls(host as Parameters<typeof siteProfileUrls>[0]),\n logo: text(host?.logoUrl, 800) || text(entity?.logo, 800) || null,\n }\n}\n\n/** Each origin as the profile screen labels it. */\nexport const BUSINESS_PROFILE_ORIGIN_LABELS: Readonly<Record<BusinessProfileOrigin, string>> = {\n owner: 'You',\n site: 'Site settings',\n start: 'Your answers when the site was started',\n ai: `Suggested by ${PLATFORM_BRAND_NAME} AI`,\n workspace: 'Workspace default',\n}\n"],"names":["siteProfileUrls","normalizeAreaServed","PLATFORM_BRAND_NAME","BUSINESS_PROFILE_SUBCOLLECTION","BUSINESS_PROFILE_SITE_DOC","BUSINESS_PROFILE_WORKSPACE_DOC","SOURCE_RANK","ai","start","owner","BUSINESS_TONES","BUSINESS_TONE_LABELS","friendly","professional","playful","premium","plain","BUSINESS_PROFILE_FIELDS","BUSINESS_PROFILE_MAX_CHARS","whatYouDo","services","serviceArea","audience","toneNotes","BUSINESS_PROFILE_MAX_SERVICES","text","value","max","replace","trim","slice","tone","includes","items","Array","isArray","split","seen","Set","kept","item","name","key","toLowerCase","has","add","push","length","fieldValue","doc","field","isEmpty","businessProfileSourceOf","source","sources","sanitizeBusinessProfileValues","values","clean","mergeBusinessProfilePrefill","current","next","changed","recorded","held","JSON","stringify","businessProfileOwnerWrite","edited","before","after","same","addressLine","host","parts","seo","entity","address","line","streetAddress","addressLocality","addressRegion","postalCode","addressCountry","map","part","filter","Boolean","join","business","resolveBusinessProfile","input","site","workspace","fromSite","description","areaServed","resolve","stored","origin","own","undefined","displayName","businessType","contact","email","supportEmail","phone","telephone","hours","openingHours","profiles","logo","logoUrl","BUSINESS_PROFILE_ORIGIN_LABELS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/business-profile.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { siteProfileUrls, type HostSeoEntity } from './content-authors'\nimport { normalizeAreaServed } from './local-business'\nimport { PLATFORM_BRAND_NAME } from './platform-brand'\n\n/**\n * A site's BUSINESS PROFILE (AGL-3661): what the business is, as everything\n * that writes about it needs to know — the site's own pages, the structured\n * data, and any generator a plugin brings.\n *\n * ── Most of it is already stored, so this stores only the rest ─────────────\n *\n * The site's name, its one-line description, its contact details, its hours,\n * the areas it serves and its social profiles all have a home on the host\n * document already: `seo.entity` (the publisher entity the JSON-LD is built\n * from, edited in Setup → SEO) and `business` (the contact card the `host.*`\n * tokens and the footer read, edited in Setup → Basic details). Copying them\n * here would make two places to keep in step, and the copy would drift.\n *\n * So the profile document holds only what has no home yet — the services, the\n * audience, the tone of voice — plus a line about the business and the area it\n * serves for a site whose SEO entity does not say. {@link resolveBusinessProfile}\n * reads the two together into one picture, field by field, and says where\n * each value came from.\n *\n * ── Where it is stored ────────────────────────────────────────────────────\n *\n * `hosts/{hostId}/businessProfile/profile` for a site and\n * `orgs/{orgId}/businessProfile/defaults` for the workspace, whose values a\n * site inherits where it has none of its own. A subcollection document rather\n * than a key on the host: the host document's `business` map is replaced\n * whole by the contact card's save, and a key beside it would be one more\n * thing a partial write could blank.\n *\n * ── Whose words win ───────────────────────────────────────────────────────\n *\n * Every stored value carries its source. What the owner typed is never\n * replaced by anything else; what the guided start was told replaces only\n * what a generator guessed; and a generator's guess fills only what is empty.\n * {@link mergeBusinessProfilePrefill} is the one place that rule is applied.\n */\n\n/** The collection a profile document lives in, under a host or an org. */\nexport const BUSINESS_PROFILE_SUBCOLLECTION = 'businessProfile'\n/** The site's profile document id. */\nexport const BUSINESS_PROFILE_SITE_DOC = 'profile'\n/** The workspace defaults document id. */\nexport const BUSINESS_PROFILE_WORKSPACE_DOC = 'defaults'\n\n/** Who wrote a stored value: the owner, the guided start's answers, or a generator. */\nexport type BusinessProfileSource = 'owner' | 'start' | 'ai'\n\n/**\n * Where a RESOLVED value came from: a stored source, the site's own settings\n * (`seo.entity`, `business`, the display name), or the workspace defaults.\n */\nexport type BusinessProfileOrigin = BusinessProfileSource | 'site' | 'workspace'\n\n/** How much a source's words weigh: a higher rank is never replaced by a lower one. */\nconst SOURCE_RANK: Readonly<Record<BusinessProfileSource, number>> = {\n ai: 1,\n start: 2,\n owner: 3,\n}\n\nexport const BUSINESS_TONES = ['friendly', 'professional', 'playful', 'premium', 'plain'] as const\nexport type BusinessTone = (typeof BUSINESS_TONES)[number]\n\n/** The tones as a plain list, in the order the owner picks them, for the native apps' pickers. */\nexport const BUSINESS_TONE_ORDER: readonly string[] = BUSINESS_TONES\n\n/** Each tone as the owner picks it and as a writer is told it. */\nexport const BUSINESS_TONE_LABELS: Readonly<Record<BusinessTone, string>> = {\n friendly: 'Friendly and warm',\n professional: 'Professional and clear',\n playful: 'Playful and upbeat',\n premium: 'Premium and refined',\n plain: 'Plain and direct',\n}\n\n/** The fields the profile document stores itself. */\nexport const BUSINESS_PROFILE_FIELDS = [\n 'whatYouDo',\n 'services',\n 'serviceArea',\n 'audience',\n 'tone',\n 'toneNotes',\n] as const\nexport type BusinessProfileField = (typeof BUSINESS_PROFILE_FIELDS)[number]\n\n/** The stored fields as a plain list, for the native apps' writers. */\nexport const BUSINESS_PROFILE_FIELD_ORDER: readonly string[] = BUSINESS_PROFILE_FIELDS\n\n/** Each field's ceiling, in characters; `services` is per item. */\nexport const BUSINESS_PROFILE_MAX_CHARS: Readonly<Record<Exclude<BusinessProfileField, 'tone'>, number>> = {\n whatYouDo: 200,\n services: 60,\n serviceArea: 120,\n audience: 160,\n toneNotes: 200,\n}\n\n/** At most this many services are kept. */\nexport const BUSINESS_PROFILE_MAX_SERVICES = 12\n\n/** The stored document, at either level. */\nexport interface BusinessProfileDoc {\n whatYouDo?: string\n services?: string[]\n serviceArea?: string\n audience?: string\n tone?: BusinessTone | null\n toneNotes?: string\n /** Who wrote each stored value; a value without one is the owner's. */\n sources?: Partial<Record<BusinessProfileField, BusinessProfileSource>>\n updatedAt?: unknown\n updatedBy?: string | null\n}\n\n/** The values a write carries, before they are sanitized. */\nexport type BusinessProfileValues = Partial<\n Pick<BusinessProfileDoc, 'whatYouDo' | 'services' | 'serviceArea' | 'audience' | 'tone' | 'toneNotes'>\n>\n\n/** The host fields the profile reads. A closed surface, as the host tokens' is. */\nexport interface BusinessProfileHost {\n displayName?: string\n logoUrl?: string\n seo?: { description?: string; entity?: HostSeoEntity | null } | null\n business?: {\n supportEmail?: string\n address?: string\n socialLinks?: Array<{ label?: string; url?: string } | null> | null\n } | null\n}\n\nexport interface ResolvedBusinessField<T> {\n value: T\n origin: BusinessProfileOrigin\n}\n\n/** The picture every reader works from: each field's value and where it came from. */\nexport interface ResolvedBusinessProfile {\n name: ResolvedBusinessField<string> | null\n whatYouDo: ResolvedBusinessField<string> | null\n services: ResolvedBusinessField<string[]> | null\n serviceArea: ResolvedBusinessField<string> | null\n audience: ResolvedBusinessField<string> | null\n tone: ResolvedBusinessField<BusinessTone> | null\n toneNotes: ResolvedBusinessField<string> | null\n /** The schema.org business type the SEO entity names, e.g. `Plumber`. */\n businessType: string | null\n /** Real contact details, each only where the owner entered it in the site's settings. */\n contact: {\n email: string | null\n phone: string | null\n address: string | null\n hours: string | null\n }\n /** The site's social and other profiles, https only. */\n profiles: string[]\n logo: string | null\n}\n\nconst text = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim().slice(0, max) : ''\n\nconst tone = (value: unknown): BusinessTone | null =>\n (BUSINESS_TONES as readonly unknown[]).includes(value) ? (value as BusinessTone) : null\n\nconst services = (value: unknown): string[] => {\n const items = Array.isArray(value) ? value : typeof value === 'string' ? value.split(/[,\\n]/) : []\n const seen = new Set<string>()\n const kept: string[] = []\n for (const item of items) {\n const name = text(item, BUSINESS_PROFILE_MAX_CHARS.services)\n const key = name.toLowerCase()\n if (!name || seen.has(key)) continue\n seen.add(key)\n kept.push(name)\n if (kept.length >= BUSINESS_PROFILE_MAX_SERVICES) break\n }\n return kept\n}\n\n/** One field's stored value, sanitized; empty is `''`, `[]` or `null`. */\nfunction fieldValue(doc: BusinessProfileValues | null | undefined, field: BusinessProfileField): unknown {\n switch (field) {\n case 'services':\n return services(doc?.services)\n case 'tone':\n return tone(doc?.tone)\n default:\n return text(doc?.[field], BUSINESS_PROFILE_MAX_CHARS[field])\n }\n}\n\nconst isEmpty = (value: unknown): boolean =>\n value === null || value === '' || (Array.isArray(value) && value.length === 0)\n\n/** The source a stored, non-empty value carries; the owner's when none is recorded. */\nexport function businessProfileSourceOf(\n doc: BusinessProfileDoc | null | undefined,\n field: BusinessProfileField,\n): BusinessProfileSource | null {\n if (isEmpty(fieldValue(doc, field))) return null\n const source = doc?.sources?.[field]\n return source && source in SOURCE_RANK ? source : 'owner'\n}\n\n/** Every value a write may carry, sanitized; an absent field stays absent. */\nexport function sanitizeBusinessProfileValues(values: BusinessProfileValues): BusinessProfileValues {\n const clean: BusinessProfileValues = {}\n for (const field of BUSINESS_PROFILE_FIELDS) {\n if (!(field in values)) continue\n ;(clean as Record<string, unknown>)[field] = fieldValue(values, field)\n }\n return clean\n}\n\n/**\n * The document a generator's or the guided start's values make of the stored\n * one: each value fills a field that is empty or was written by a source that\n * weighs less, and nothing else. `null` when nothing would change, so a caller\n * writes only when there is something to write.\n */\nexport function mergeBusinessProfilePrefill(\n current: BusinessProfileDoc | null | undefined,\n values: BusinessProfileValues,\n source: Exclude<BusinessProfileSource, 'owner'>,\n): BusinessProfileDoc | null {\n const clean = sanitizeBusinessProfileValues(values)\n const next: BusinessProfileDoc = { ...(current ?? {}), sources: { ...(current?.sources ?? {}) } }\n let changed = false\n for (const field of BUSINESS_PROFILE_FIELDS) {\n if (!(field in clean)) continue\n const value = (clean as Record<string, unknown>)[field]\n if (isEmpty(value)) continue\n // An explicit source counts even on an empty value: a field the owner\n // cleared stays cleared rather than being refilled behind them.\n const recorded = current?.sources?.[field]\n const held = recorded && recorded in SOURCE_RANK ? recorded : businessProfileSourceOf(current, field)\n if (held && SOURCE_RANK[held] >= SOURCE_RANK[source]) continue\n if (JSON.stringify(fieldValue(current, field)) === JSON.stringify(value)) continue\n ;(next as Record<string, unknown>)[field] = value\n next.sources![field] = source\n changed = true\n }\n return changed ? next : null\n}\n\n/**\n * The document the owner's save makes: every field they changed becomes\n * theirs, and a field they left as it was keeps the source it had — so saving\n * the form after correcting one line does not claim every suggestion on it.\n */\nexport function businessProfileOwnerWrite(\n current: BusinessProfileDoc | null | undefined,\n edited: BusinessProfileValues,\n): Pick<BusinessProfileDoc, BusinessProfileField | 'sources'> {\n const clean = sanitizeBusinessProfileValues(edited)\n const next: Record<string, unknown> = {}\n const sources: Partial<Record<BusinessProfileField, BusinessProfileSource>> = {}\n for (const field of BUSINESS_PROFILE_FIELDS) {\n const before = fieldValue(current, field)\n const after = field in clean ? (clean as Record<string, unknown>)[field] : before\n next[field] = after\n const same = JSON.stringify(before) === JSON.stringify(after)\n if (isEmpty(after)) {\n // A suggestion the owner deleted is their decision, and recorded as\n // one, so the next prefill does not put it back.\n if (!same) sources[field] = 'owner'\n else if (current?.sources?.[field] === 'owner') sources[field] = 'owner'\n continue\n }\n sources[field] = same ? (businessProfileSourceOf(current, field) ?? 'owner') : 'owner'\n }\n return { ...(next as Pick<BusinessProfileDoc, BusinessProfileField>), sources }\n}\n\n/** The postal address as one line: the SEO entity's parts, else the contact card's block. */\nfunction addressLine(host: BusinessProfileHost | null | undefined): string {\n const parts = host?.seo?.entity?.address\n const line = [\n parts?.streetAddress,\n parts?.addressLocality,\n parts?.addressRegion,\n parts?.postalCode,\n parts?.addressCountry,\n ]\n .map((part) => text(part, 200))\n .filter(Boolean)\n .join(', ')\n return line || text(host?.business?.address, 400)\n}\n\n/**\n * The profile as every reader sees it, field by field (AGL-3661):\n *\n * 1. what the OWNER typed into the site's profile;\n * 2. what the site's own settings say (the SEO entity, the contact card, the\n * display name), which the owner typed too, elsewhere;\n * 3. what the guided start or a generator stored on the site's profile;\n * 4. the workspace defaults.\n *\n * Contact details, the logo and the profiles come from the site's settings\n * only: nothing but the owner's own entry may say how to reach the business.\n */\nexport function resolveBusinessProfile(input: {\n host: BusinessProfileHost | null | undefined\n site?: BusinessProfileDoc | null\n workspace?: BusinessProfileDoc | null\n}): ResolvedBusinessProfile {\n const { host, site, workspace } = input\n const entity = host?.seo?.entity ?? null\n const fromSite: Partial<Record<BusinessProfileField, unknown>> = {\n whatYouDo: text(entity?.description, BUSINESS_PROFILE_MAX_CHARS.whatYouDo),\n serviceArea: normalizeAreaServed(entity?.areaServed).join(', ').slice(0, BUSINESS_PROFILE_MAX_CHARS.serviceArea),\n }\n\n const resolve = <T>(field: BusinessProfileField): ResolvedBusinessField<T> | null => {\n const stored = businessProfileSourceOf(site, field)\n if (stored === 'owner') return { value: fieldValue(site, field) as T, origin: 'owner' }\n const own = fromSite[field]\n if (own !== undefined && !isEmpty(own)) return { value: own as T, origin: 'site' }\n if (stored) return { value: fieldValue(site, field) as T, origin: stored }\n if (businessProfileSourceOf(workspace, field)) {\n return { value: fieldValue(workspace, field) as T, origin: 'workspace' }\n }\n return null\n }\n\n const name = text(entity?.name, 200) || text(host?.displayName, 200)\n return {\n name: name ? { value: name, origin: 'site' } : null,\n whatYouDo: resolve<string>('whatYouDo'),\n services: resolve<string[]>('services'),\n serviceArea: resolve<string>('serviceArea'),\n audience: resolve<string>('audience'),\n tone: resolve<BusinessTone>('tone'),\n toneNotes: resolve<string>('toneNotes'),\n businessType: text(entity?.businessType, 80) || null,\n contact: {\n email: text(entity?.email, 320) || text(host?.business?.supportEmail, 320) || null,\n phone: text(entity?.telephone, 64) || null,\n address: addressLine(host) || null,\n hours: text(entity?.openingHours, 400) || null,\n },\n profiles: siteProfileUrls(host as Parameters<typeof siteProfileUrls>[0]),\n logo: text(host?.logoUrl, 800) || text(entity?.logo, 800) || null,\n }\n}\n\n/** Each origin as the profile screen labels it. */\nexport const BUSINESS_PROFILE_ORIGIN_LABELS: Readonly<Record<BusinessProfileOrigin, string>> = {\n owner: 'You',\n site: 'Site settings',\n start: 'Your answers when the site was started',\n ai: `Suggested by ${PLATFORM_BRAND_NAME} AI`,\n workspace: 'Workspace default',\n}\n"],"names":["siteProfileUrls","normalizeAreaServed","PLATFORM_BRAND_NAME","BUSINESS_PROFILE_SUBCOLLECTION","BUSINESS_PROFILE_SITE_DOC","BUSINESS_PROFILE_WORKSPACE_DOC","SOURCE_RANK","ai","start","owner","BUSINESS_TONES","BUSINESS_TONE_ORDER","BUSINESS_TONE_LABELS","friendly","professional","playful","premium","plain","BUSINESS_PROFILE_FIELDS","BUSINESS_PROFILE_FIELD_ORDER","BUSINESS_PROFILE_MAX_CHARS","whatYouDo","services","serviceArea","audience","toneNotes","BUSINESS_PROFILE_MAX_SERVICES","text","value","max","replace","trim","slice","tone","includes","items","Array","isArray","split","seen","Set","kept","item","name","key","toLowerCase","has","add","push","length","fieldValue","doc","field","isEmpty","businessProfileSourceOf","source","sources","sanitizeBusinessProfileValues","values","clean","mergeBusinessProfilePrefill","current","next","changed","recorded","held","JSON","stringify","businessProfileOwnerWrite","edited","before","after","same","addressLine","host","parts","seo","entity","address","line","streetAddress","addressLocality","addressRegion","postalCode","addressCountry","map","part","filter","Boolean","join","business","resolveBusinessProfile","input","site","workspace","fromSite","description","areaServed","resolve","stored","origin","own","undefined","displayName","businessType","contact","email","supportEmail","phone","telephone","hours","openingHours","profiles","logo","logoUrl","BUSINESS_PROFILE_ORIGIN_LABELS"],"mappings":"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"}
|
|
@@ -69,6 +69,7 @@ export declare enum Route {
|
|
|
69
69
|
ORG_SETTINGS_HOLDS = "/[orgSlug]/settings/holds",
|
|
70
70
|
ORG_SETTINGS_DATA = "/[orgSlug]/settings/data",
|
|
71
71
|
ORG_SETTINGS_DELETE = "/[orgSlug]/settings/delete",
|
|
72
|
+
ORG_SETTINGS_INTEGRATIONS = "/[orgSlug]/settings/integrations",
|
|
72
73
|
/**
|
|
73
74
|
* Billing, section by section (AGL-2501).
|
|
74
75
|
*
|
|
@@ -122,6 +123,7 @@ export declare enum Route {
|
|
|
122
123
|
HOST_SETUP_THEME = "/[orgSlug]/hosts/[host]/setup/theme",
|
|
123
124
|
HOST_SETUP_BUSINESS = "/[orgSlug]/hosts/[host]/setup/business",
|
|
124
125
|
HOST_SETUP_EMAILS = "/[orgSlug]/hosts/[host]/setup/emails",
|
|
126
|
+
HOST_SETUP_INTEGRATIONS = "/[orgSlug]/hosts/[host]/setup/integrations",
|
|
125
127
|
HOST_ADMIN = "/[orgSlug]/hosts/[host]/admin",
|
|
126
128
|
HOST_ADMIN_GENERAL = "/[orgSlug]/hosts/[host]/admin/general",
|
|
127
129
|
HOST_ADMIN_PLUGINS = "/[orgSlug]/hosts/[host]/admin/plugins",
|
|
@@ -323,6 +325,9 @@ export interface RoutePayload {
|
|
|
323
325
|
[Route.ORG_SETTINGS_DELETE]: {
|
|
324
326
|
orgSlug: string;
|
|
325
327
|
};
|
|
328
|
+
[Route.ORG_SETTINGS_INTEGRATIONS]: {
|
|
329
|
+
orgSlug: string;
|
|
330
|
+
};
|
|
326
331
|
[Route.HOST_LIST]: {
|
|
327
332
|
orgSlug: string;
|
|
328
333
|
};
|
|
@@ -423,6 +428,10 @@ export interface RoutePayload {
|
|
|
423
428
|
orgSlug: string;
|
|
424
429
|
host: string;
|
|
425
430
|
};
|
|
431
|
+
[Route.HOST_SETUP_INTEGRATIONS]: {
|
|
432
|
+
orgSlug: string;
|
|
433
|
+
host: string;
|
|
434
|
+
};
|
|
426
435
|
[Route.HOST_ADMIN]: {
|
|
427
436
|
orgSlug: string;
|
|
428
437
|
host: string;
|
|
@@ -200,6 +200,9 @@ export var Route = /*#__PURE__*/ function(Route) {
|
|
|
200
200
|
// workspace packages, and the history of its imports with undo.
|
|
201
201
|
Route["ORG_SETTINGS_DATA"] = "/[orgSlug]/settings/data";
|
|
202
202
|
Route["ORG_SETTINGS_DELETE"] = "/[orgSlug]/settings/delete";
|
|
203
|
+
// The `orgSettings` plugin zone's own section (AGL-3711): offered only
|
|
204
|
+
// while a plugin has a card for it.
|
|
205
|
+
Route["ORG_SETTINGS_INTEGRATIONS"] = "/[orgSlug]/settings/integrations";
|
|
203
206
|
/**
|
|
204
207
|
* Billing, section by section (AGL-2501).
|
|
205
208
|
*
|
|
@@ -382,6 +385,9 @@ export var Route = /*#__PURE__*/ function(Route) {
|
|
|
382
385
|
Route["HOST_SETUP_THEME"] = "/[orgSlug]/hosts/[host]/setup/theme";
|
|
383
386
|
Route["HOST_SETUP_BUSINESS"] = "/[orgSlug]/hosts/[host]/setup/business";
|
|
384
387
|
Route["HOST_SETUP_EMAILS"] = "/[orgSlug]/hosts/[host]/setup/emails";
|
|
388
|
+
// The `hostSettings` plugin zone's own section (AGL-3711): offered only
|
|
389
|
+
// while a plugin has a card for it.
|
|
390
|
+
Route["HOST_SETUP_INTEGRATIONS"] = "/[orgSlug]/hosts/[host]/setup/integrations";
|
|
385
391
|
// Host Admin area (AGL-1014): owner/admin-only controls — per-site plugin
|
|
386
392
|
// enablement and the Danger zone — out of the Setup page collaborators
|
|
387
393
|
// legitimately visit.
|