@aglyn/aglyn 1.0.0-beta.203 → 1.0.0-beta.205

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +11 -11
  2. package/src/lib/app-utils/activity-presenter.d.ts +21 -0
  3. package/src/lib/app-utils/activity-presenter.js +22 -0
  4. package/src/lib/app-utils/activity-presenter.js.map +1 -1
  5. package/src/lib/app-utils/console-routes.d.ts +4 -0
  6. package/src/lib/app-utils/console-routes.js +4 -0
  7. package/src/lib/app-utils/console-routes.js.map +1 -1
  8. package/src/lib/app-utils/csv-import.d.ts +12 -0
  9. package/src/lib/app-utils/csv-import.js +13 -3
  10. package/src/lib/app-utils/csv-import.js.map +1 -1
  11. package/src/lib/app-utils/docs-help.generated.d.ts +1 -1
  12. package/src/lib/app-utils/docs-help.generated.js +2 -0
  13. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  14. package/src/lib/app-utils/docs-index.generated.js +78 -8
  15. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  16. package/src/lib/app-utils/health-report.js +10 -0
  17. package/src/lib/app-utils/health-report.js.map +1 -1
  18. package/src/lib/app-utils/notifications.d.ts +13 -1
  19. package/src/lib/app-utils/notifications.js +20 -1
  20. package/src/lib/app-utils/notifications.js.map +1 -1
  21. package/src/lib/app-utils/recipient-email-render.d.ts +21 -1
  22. package/src/lib/app-utils/recipient-email-render.js +34 -5
  23. package/src/lib/app-utils/recipient-email-render.js.map +1 -1
  24. package/src/lib/app-utils/server.d.ts +1 -1
  25. package/src/lib/app-utils/server.js +1 -1
  26. package/src/lib/app-utils/server.js.map +1 -1
  27. package/src/lib/app-utils/site-email-chrome.d.ts +52 -0
  28. package/src/lib/app-utils/site-email-chrome.js +82 -0
  29. package/src/lib/app-utils/site-email-chrome.js.map +1 -0
  30. package/src/lib/app-utils/system-email-chrome.d.ts +6 -0
  31. package/src/lib/app-utils/system-email-chrome.js.map +1 -1
  32. package/src/lib/plugin-manager/plugin-contact-capture.d.ts +11 -0
  33. package/src/lib/plugin-manager/plugin-contact-capture.js.map +1 -1
  34. package/src/lib/plugin-manager/plugin-record-email-state.d.ts +7 -0
  35. package/src/lib/plugin-manager/plugin-record-email-state.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.203",
3
+ "version": "1.0.0-beta.205",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -33,16 +33,16 @@
33
33
  "./package.json": "./package.json"
34
34
  },
35
35
  "dependencies": {
36
- "@aglyn/shared-data-enums": "1.0.0-beta.203",
37
- "@aglyn/shared-data-mdi": "1.0.0-beta.203",
38
- "@aglyn/shared-data-types": "1.0.0-beta.203",
39
- "@aglyn/shared-util-email": "1.0.0-beta.203",
40
- "@aglyn/shared-util-first-touch": "1.0.0-beta.203",
41
- "@aglyn/shared-util-http": "1.0.0-beta.203",
42
- "@aglyn/shared-util-logger": "1.0.0-beta.203",
43
- "@aglyn/shared-util-timestamp": "1.0.0-beta.203",
44
- "@aglyn/shared-util-tools": "1.0.0-beta.203",
45
- "@aglyn/shared-util-vendor": "1.0.0-beta.203",
36
+ "@aglyn/shared-data-enums": "1.0.0-beta.205",
37
+ "@aglyn/shared-data-mdi": "1.0.0-beta.205",
38
+ "@aglyn/shared-data-types": "1.0.0-beta.205",
39
+ "@aglyn/shared-util-email": "1.0.0-beta.205",
40
+ "@aglyn/shared-util-first-touch": "1.0.0-beta.205",
41
+ "@aglyn/shared-util-http": "1.0.0-beta.205",
42
+ "@aglyn/shared-util-logger": "1.0.0-beta.205",
43
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.205",
44
+ "@aglyn/shared-util-tools": "1.0.0-beta.205",
45
+ "@aglyn/shared-util-vendor": "1.0.0-beta.205",
46
46
  "@data-driven-forms/react-form-renderer": "^4.2.0",
47
47
  "@msgpack/msgpack": "^3.1.3",
48
48
  "@types/unist": "^3.0.3",
@@ -125,6 +125,27 @@ export declare function apiKeyActorLabel(name: string): string;
125
125
  * (AGL-3369).
126
126
  */
127
127
  export declare function activityActorLabel(entry: ActivityEntryLike): string;
128
+ /**
129
+ * Who set a run off (AGL-3376), as a run row stores it: the actor the event's
130
+ * door named, carried through the engine. `webhook` is an inbound webhook's
131
+ * caller, named by the hook.
132
+ */
133
+ export interface RunTriggeredByLike {
134
+ kind?: string;
135
+ uid?: string;
136
+ email?: string;
137
+ apiKeyName?: string;
138
+ name?: string;
139
+ }
140
+ /**
141
+ * The run history's Who: whoever set the run off, never the automation
142
+ * itself — every row in a run history is that automation, so naming it
143
+ * again would answer nothing. A run written before its trigger was recorded
144
+ * says so rather than guessing from the event's name.
145
+ */
146
+ export declare function runTriggeredByLabel(entry: {
147
+ triggeredBy?: RunTriggeredByLike | null;
148
+ }): string;
128
149
  /**
129
150
  * A deep link to the resource an entry points at, or `undefined` when it
130
151
  * cannot be built (missing context, or a type with nowhere sensible to go).
@@ -156,6 +156,28 @@ import { pluginActivityActionLabel, pluginActivityGroupForAction, pluginActivity
156
156
  }
157
157
  return PLATFORM_BRAND_NAME;
158
158
  }
159
+ /**
160
+ * The run history's Who: whoever set the run off, never the automation
161
+ * itself — every row in a run history is that automation, so naming it
162
+ * again would answer nothing. A run written before its trigger was recorded
163
+ * says so rather than guessing from the event's name.
164
+ */ export function runTriggeredByLabel(entry) {
165
+ const by = entry.triggeredBy;
166
+ switch(by == null ? void 0 : by.kind){
167
+ case 'member':
168
+ return by.email || (by.uid ? `Account ${by.uid}` : 'A member');
169
+ case 'apiKey':
170
+ return by.apiKeyName ? apiKeyActorLabel(by.apiKeyName) : 'API key';
171
+ case 'visitor':
172
+ return by.email ? `Site visitor (${by.email})` : 'Site visitor';
173
+ case 'webhook':
174
+ return by.name ? `Inbound webhook ${by.name}` : 'Inbound webhook';
175
+ case 'platform':
176
+ return PLATFORM_BRAND_NAME;
177
+ default:
178
+ return 'Not recorded';
179
+ }
180
+ }
159
181
  /**
160
182
  * A deep link to the resource an entry points at, or `undefined` when it
161
183
  * cannot be built (missing context, or a type with nowhere sensible to go).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/activity-presenter.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// Activity-feed presenter (AGL-810). The `hosts/{id}/activity` and\n// `orgs/{id}/activity` logs store a free-text `action` plus a `{ type, id,\n// name }` target, written from ~40 scattered call sites. The three renderers\n// (host card, host table, org card) used to each format entries their own\n// way — one dropped the target entirely, one printed the raw Firestore doc\n// id when no name was recorded. This module is the single place that turns a\n// stored entry into something a human reads, and into a deep link to the\n// thing that changed. It is deliberately dependency-free (only the string\n// route table) so plugin libs and the console app can both import it via\n// `@aglyn/aglyn/app-utils/activity-presenter`.\n\nimport { Route, buildRoute } from './console-routes'\nimport {\n CRM_RECORD_SECTIONS,\n crmRecordHref,\n crmSectionHref,\n type CrmRecordKind,\n} from './console-record-links'\nimport { PLATFORM_BRAND_NAME } from './platform-brand'\nimport { hostEventLabel } from './workflows'\nimport { duplicateActivityActionLabel } from './duplicate-resource'\nimport {\n pluginActivityActionLabel,\n pluginActivityGroupForAction,\n pluginActivityTargetNoun,\n} from '../plugin-manager/plugin-activity-actions'\n\n/** The stored `target` sub-object, read defensively (any field may be absent). */\nexport interface ActivityTargetLike {\n type?: string\n id?: string\n name?: string\n /** Present on screen saves so the deep link can hit the exact version. */\n versionId?: string\n}\n\n/** A stored activity document, read defensively. */\nexport interface ActivityEntryLike {\n action?: string\n target?: ActivityTargetLike | null\n // Some early org entries carried the type/target id at the top level rather\n // than nested under `target` — tolerate both so no entry renders blank.\n type?: string\n targetId?: string\n /**\n * The acting uid: a person's, the literal `'api'` for a key, a\n * `system:` id for a platform process, or null when no person acted.\n */\n actorId?: string | null\n /** The address the actor had when the entry was written; null for a key. */\n actorEmail?: string | null\n /**\n * The address `actorId` holds TODAY, looked up on read by a route with the\n * Admin SDK, for an entry that recorded a uid and no address.\n */\n actorEmailNow?: string | null\n /**\n * For a STAFF reader only: the staff member behind an entry the workspace\n * did not perform (a staff cancellation), resolved by the route.\n */\n staffActorEmail?: string | null\n /** The name of the API key that wrote the entry, when a key did (AGL-2632). */\n apiKeyName?: string | null\n}\n\n/**\n * Who a host activity entry is attributed to — the writer's half of the\n * contract whose reader is {@link activityActorLabel}.\n *\n * A person is their verified uid and the address they had at the time. An\n * integration is the literal uid `'api'`, no address, and the name the\n * organization gave its key — carried because a feed that says \"Someone\"\n * about every write a key made is an audit trail with a hole in it: three\n * Zapier zaps and a nightly import all read as the same nobody.\n */\nexport interface HostActivityActor {\n uid: string\n email?: string | null\n /** The API key's name, for an entry an integration wrote. */\n apiKeyName?: string | null\n}\n\n/**\n * What the presenter needs to build a link. `host` is the `[host]` URL\n * segment — the site SLUG the console routes by (AGL-622), NOT the host doc\n * id — so pass `useParams().host`, not the doc id. When it is present the\n * entry is treated as host-scoped, otherwise org-scoped — which\n * disambiguates `member`, the one type both logs use.\n */\nexport interface ActivityLinkContext {\n orgSlug?: string\n host?: string\n}\n\n/** Human, singular noun for a target type. Falls back to the raw type. */\nconst TYPE_LABELS: Record<string, string> = {\n host: 'Site',\n screen: 'Screen',\n layout: 'Layout',\n theme: 'Theme',\n media: 'Media',\n content: 'Content',\n variable: 'Variable',\n function: 'Function',\n workflow: 'Workflow',\n member: 'Member',\n component: 'Component',\n template: 'Template',\n org: 'Organization',\n invite: 'Invitation',\n subscription: 'Subscription',\n // A custom role's AI permission flipped (AGL-2929).\n role: 'Role',\n // A generation job (AGL-2904): the org feed names the job, and each of its\n // outputs names the resource it produced.\n aiJob: 'AI generation',\n // CRM records (AGL-2622): a person added, a company or a deal created or\n // deleted, a lead converted — logged into the feed of the site whose\n // console did it, and linked to the record the way a screen is.\n contact: 'Contact',\n lead: 'Lead',\n company: 'Company',\n deal: 'Deal',\n}\n\n/** The activity target types that are CRM records, for the link switch. */\nconst CRM_TARGET_KINDS = new Set<string>(Object.keys(CRM_RECORD_SECTIONS))\n\n/**\n * The org-level hub's section for each record kind an org feed entry may\n * name (AGL-2634): the four record kinds, and tasks, which have a section\n * and no page of their own.\n */\nconst ORG_CRM_SECTIONS: Record<string, string> = {\n ...CRM_RECORD_SECTIONS,\n task: 'tasks',\n}\n\n/**\n * Human noun for a target type, e.g. `'screen'` → `'Screen'`. A plugin's\n * own resource is stored as `pluginId:noun` (AGL-2978), and a reader sees\n * the noun: `acme-mail:mailbox` → `Mailbox`.\n */\nexport function activityTypeLabel(type: string | undefined): string {\n if (!type) return 'Item'\n const known = TYPE_LABELS[type]\n if (known) return known\n const noun = pluginActivityTargetNoun(type) ?? type\n return noun.charAt(0).toUpperCase() + noun.slice(1)\n}\n\n/**\n * A label for the target column/link. Prefers the recorded name; when none\n * was written (older entries, id-only saves) it degrades to the humanized\n * type — NEVER the raw doc id, which is meaningless to a reader.\n */\nexport function activityTargetLabel(\n target: ActivityTargetLike | null | undefined,\n): string {\n if (target?.name) return target.name\n return activityTypeLabel(target?.type)\n}\n\n/**\n * What the action column reads (AGL-2929). Most actions are the sentence\n * their writer stored; a plugin's rows store a code (`ai.job.output`) so\n * that three surfaces can recognize them without parsing prose, and the\n * code is translated here — once, from the plugin-declared catalog\n * (AGL-2940) — rather than shown to a reader as a dotted path.\n */\nexport function activityActionLabel(action: string | undefined): string {\n const stored = action?.trim() ?? ''\n return (\n pluginActivityActionLabel(stored) ??\n duplicateActivityActionLabel(stored) ??\n stored\n )\n}\n\n/**\n * The filter group an entry's code belongs to — the chip that keeps it —\n * or `undefined` for a prose action no plugin declared.\n */\nexport function activityEntryGroupId(entry: ActivityEntryLike): string | undefined {\n return pluginActivityGroupForAction(entry.action?.trim())?.id\n}\n\n/**\n * The primary line for a feed entry: the action, suffixed with the target\n * name when one is known, so \"Saved the screen\" becomes \"Saved the screen —\n * Home\". A missing action degrades to the target label rather than blank.\n */\nexport function activityPrimaryText(entry: ActivityEntryLike): string {\n const action = activityActionLabel(entry.action)\n const name = entry.target?.name?.trim()\n if (action && name) return `${action} — ${name}`\n if (action) return action\n return activityTargetLabel(entry.target)\n}\n\n/** How an integration's key is named wherever an actor is: `API key Zapier`. */\nexport function apiKeyActorLabel(name: string): string {\n return `API key ${name}`\n}\n\n/**\n * The \"who\" line for an entry — always someone a reader can narrow down.\n *\n * In order: the address recorded when the entry was written; the key that\n * wrote it, by name; the address the recorded uid holds now, when the writer\n * held a uid and no address (the billing webhook, before it carried the\n * address stamped at the console act); else the uid itself, for an account\n * that no longer resolves.\n *\n * An entry with no uid at all is not an anonymous one. Every writer that\n * leaves `actorId` null is a platform process, so the feed names it: a\n * workflow run by its workflow, anything else — the billing webhook,\n * inbound CRM mail, a background AI job, a staff cancellation — as the\n * platform; a staff reader is also told which staff member. Never \"Someone\", which read as a person nobody could identify\n * (AGL-3369).\n */\nexport function activityActorLabel(entry: ActivityEntryLike): string {\n if (entry.actorEmail) return entry.actorEmail\n if (entry.apiKeyName) return apiKeyActorLabel(entry.apiKeyName)\n const actorId = entry.actorId?.trim()\n if (actorId === 'api') return 'API key'\n if (actorId && !actorId.startsWith('system:')) {\n return entry.actorEmailNow || `Account ${actorId}`\n }\n const target = entry.target ?? undefined\n if ((target?.type ?? entry.type) === 'workflow') {\n const name = target?.name?.trim()\n return name ? `Workflow ${name}` : 'Workflow'\n }\n if (entry.staffActorEmail) {\n return `${PLATFORM_BRAND_NAME} staff (${entry.staffActorEmail})`\n }\n return PLATFORM_BRAND_NAME\n}\n\n/**\n * A deep link to the resource an entry points at, or `undefined` when it\n * cannot be built (missing context, or a type with nowhere sensible to go).\n * `buildRoute` emits `<orgSlug?>` placeholders for missing params, so every\n * branch guards its inputs and returns `undefined` rather than a dead link.\n */\nexport function activityHref(\n entry: ActivityEntryLike,\n ctx: ActivityLinkContext,\n): string | undefined {\n const { orgSlug, host } = ctx\n if (!orgSlug) return undefined\n const target = entry.target ?? undefined\n const type = target?.type ?? entry.type\n const id = target?.id ?? entry.targetId\n const versionId = target?.versionId\n\n // Host-scoped entries: every host route needs both orgSlug and host.\n if (host) {\n switch (type) {\n case 'host':\n return buildRoute(Route.HOST_DASHBOARD, { orgSlug, host })\n case 'screen':\n // The screen detail page is version-addressed; without a versionId\n // there is no \"current\" pointer to route through, so land on the list.\n return id && versionId\n ? buildRoute(Route.SCREEN_DETAILS, {\n orgSlug,\n host,\n screenId: id,\n versionId,\n })\n : buildRoute(Route.HOST_SCREENS, { orgSlug, host })\n case 'component':\n return id\n ? buildRoute(Route.COMPONENT_DETAILS, {\n orgSlug,\n host,\n componentId: id,\n })\n : buildRoute(Route.HOST_COMPONENTS, { orgSlug, host })\n case 'template':\n return id\n ? buildRoute(Route.TEMPLATE_DETAILS, {\n orgSlug,\n host,\n templateId: id,\n })\n : buildRoute(Route.HOST_TEMPLATES, { orgSlug, host })\n case 'layout':\n return id\n ? buildRoute(Route.LAYOUT_DETAILS, { orgSlug, host, layoutId: id })\n : buildRoute(Route.HOST_LAYOUTS, { orgSlug, host })\n case 'theme':\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n case 'media':\n return buildRoute(Route.HOST_MEDIA, { orgSlug, host })\n case 'content':\n return buildRoute(Route.HOST_CONTENT, { orgSlug, host })\n case 'variable':\n return buildRoute(Route.HOST_DATA, { orgSlug, host })\n case 'function':\n return buildRoute(Route.HOST_LOGIC, { orgSlug, host })\n case 'workflow':\n return buildRoute(Route.HOST_AUTOMATION, { orgSlug, host })\n case 'member':\n // Site team is managed on the Setup page — there is no per-member\n // host route.\n return buildRoute(Route.HOST_SETUP, { orgSlug, host })\n default:\n // A CRM record's own page, or its list when the entry named no id —\n // a deleted record still has a list to land on. The CRM is a plugin\n // hub, so its addresses come from the shared builder rather than\n // the route table, the same door the console's search uses.\n if (type && CRM_TARGET_KINDS.has(type)) {\n const kind = type as CrmRecordKind\n return id\n ? crmRecordHref({ orgSlug, host }, kind, id)\n : crmSectionHref({ orgSlug, host }, CRM_RECORD_SECTIONS[kind])\n }\n return undefined\n }\n }\n\n // Org-scoped entries.\n switch (type) {\n case 'org':\n return buildRoute(Route.ORG_SETTINGS, { orgSlug })\n case 'member':\n return id\n ? buildRoute(Route.MANAGE_TEAM_MEMBER, { orgSlug, uid: id })\n : buildRoute(Route.MANAGE_TEAM, { orgSlug })\n case 'invite':\n return buildRoute(Route.MANAGE_TEAM, { orgSlug })\n default: {\n // A CRM record acted on at the ORGANIZATION level (AGL-2634) — a deal\n // moved from the org board, a merge, a bulk bar's action — opens in\n // the org-level hub. A lead's org address needs its site, which the\n // entry does not carry, and a bulk line names no record: both land\n // on the section.\n const section = type ? ORG_CRM_SECTIONS[type] : undefined\n if (!section) return undefined\n const hub = `${buildRoute(Route.ORG_CRM, { orgSlug })}/${section}`\n return id && type !== 'lead' && type !== 'task'\n ? `${hub}/${encodeURIComponent(id)}`\n : hub\n }\n }\n}\n\n/**\n * How an automation run ended (AGL-2171).\n *\n * `skipped` is a first-class outcome, not an absence. An unmet condition\n * used to be a bare `continue` that wrote nothing, so \"why didn't my\n * automation fire?\" — the most common support question about automations —\n * had no answer anywhere in the product, while `/product/workflows`\n * advertises an amber `Skipped` row that answers it.\n */\nexport type ActionRunResult = 'succeeded' | 'failed' | 'skipped'\n\n/** A stored run entry: an activity document with the run fields on it. */\nexport interface ActionRunEntryLike extends ActivityEntryLike {\n result?: string\n trigger?: string\n summary?: string\n}\n\n/**\n * `formSubmission` → `Form submitted`.\n *\n * The console rendered the raw camelCase identifier in the trigger select\n * and in every run row. The words are `HOST_EVENT_LABELS` in `workflows.ts`\n * — one map, so the picker an author chose from and the run row they read\n * afterwards cannot name the same event differently.\n */\nexport function actionTriggerLabel(event: string | undefined): string {\n return hostEventLabel(event)\n}\n\n/**\n * The run's outcome, tolerating rows written before it was recorded.\n *\n * A legacy entry carries only the prose `action` string, so it is read\n * back: `with errors:` means it failed and `Action ran on` means it\n * succeeded. Anything else is not a run at all — the activity collection\n * also holds publishes, media saves and member changes, and calling one of\n * those `succeeded` would put it in the run history under a verdict that\n * means nothing for it.\n */\nexport function actionRunResult(\n entry: ActionRunEntryLike,\n): ActionRunResult | undefined {\n const stored = String(entry.result ?? '').trim()\n if (stored === 'succeeded' || stored === 'failed' || stored === 'skipped') {\n return stored\n }\n const action = String(entry.action ?? '')\n if (!action.startsWith('Action ran on')) return undefined\n return action.includes('with errors:') ? 'failed' : 'succeeded'\n}\n\n/**\n * The `What happened` column: `Sent email · saved to Leads · webhook 200`.\n *\n * Falls back to the legacy prose, minus its `Action ran on <event>` prefix\n * — that fact is the Trigger column now, and repeating it in the summary\n * would make every historic row say the same thing twice.\n */\nexport function actionRunSummary(entry: ActionRunEntryLike): string {\n const summary = String(entry.summary ?? '').trim()\n if (summary) return summary\n const action = String(entry.action ?? '').trim()\n const errors = action.match(/with errors:\\s*(.+)$/)\n if (errors) return errors[1]\n // A pre-AGL-2171 success recorded nothing but the fact that it ran.\n if (action.startsWith('Action ran on')) return 'Ran'\n return action\n}\n"],"names":["Route","buildRoute","CRM_RECORD_SECTIONS","crmRecordHref","crmSectionHref","PLATFORM_BRAND_NAME","hostEventLabel","duplicateActivityActionLabel","pluginActivityActionLabel","pluginActivityGroupForAction","pluginActivityTargetNoun","TYPE_LABELS","host","screen","layout","theme","media","content","variable","function","workflow","member","component","template","org","invite","subscription","role","aiJob","contact","lead","company","deal","CRM_TARGET_KINDS","Set","Object","keys","ORG_CRM_SECTIONS","task","activityTypeLabel","type","known","noun","charAt","toUpperCase","slice","activityTargetLabel","target","name","activityActionLabel","action","stored","trim","activityEntryGroupId","entry","id","activityPrimaryText","apiKeyActorLabel","activityActorLabel","actorEmail","apiKeyName","actorId","startsWith","actorEmailNow","undefined","staffActorEmail","activityHref","ctx","orgSlug","targetId","versionId","HOST_DASHBOARD","SCREEN_DETAILS","screenId","HOST_SCREENS","COMPONENT_DETAILS","componentId","HOST_COMPONENTS","TEMPLATE_DETAILS","templateId","HOST_TEMPLATES","LAYOUT_DETAILS","layoutId","HOST_LAYOUTS","HOST_SETUP_THEME","HOST_MEDIA","HOST_CONTENT","HOST_DATA","HOST_LOGIC","HOST_AUTOMATION","HOST_SETUP","has","kind","ORG_SETTINGS","MANAGE_TEAM_MEMBER","uid","MANAGE_TEAM","section","hub","ORG_CRM","encodeURIComponent","actionTriggerLabel","event","actionRunResult","String","result","includes","actionRunSummary","summary","errors","match"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/activity-presenter.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// Activity-feed presenter (AGL-810). The `hosts/{id}/activity` and\n// `orgs/{id}/activity` logs store a free-text `action` plus a `{ type, id,\n// name }` target, written from ~40 scattered call sites. The three renderers\n// (host card, host table, org card) used to each format entries their own\n// way — one dropped the target entirely, one printed the raw Firestore doc\n// id when no name was recorded. This module is the single place that turns a\n// stored entry into something a human reads, and into a deep link to the\n// thing that changed. It is deliberately dependency-free (only the string\n// route table) so plugin libs and the console app can both import it via\n// `@aglyn/aglyn/app-utils/activity-presenter`.\n\nimport { Route, buildRoute } from './console-routes'\nimport {\n CRM_RECORD_SECTIONS,\n crmRecordHref,\n crmSectionHref,\n type CrmRecordKind,\n} from './console-record-links'\nimport { PLATFORM_BRAND_NAME } from './platform-brand'\nimport { hostEventLabel } from './workflows'\nimport { duplicateActivityActionLabel } from './duplicate-resource'\nimport {\n pluginActivityActionLabel,\n pluginActivityGroupForAction,\n pluginActivityTargetNoun,\n} from '../plugin-manager/plugin-activity-actions'\n\n/** The stored `target` sub-object, read defensively (any field may be absent). */\nexport interface ActivityTargetLike {\n type?: string\n id?: string\n name?: string\n /** Present on screen saves so the deep link can hit the exact version. */\n versionId?: string\n}\n\n/** A stored activity document, read defensively. */\nexport interface ActivityEntryLike {\n action?: string\n target?: ActivityTargetLike | null\n // Some early org entries carried the type/target id at the top level rather\n // than nested under `target` — tolerate both so no entry renders blank.\n type?: string\n targetId?: string\n /**\n * The acting uid: a person's, the literal `'api'` for a key, a\n * `system:` id for a platform process, or null when no person acted.\n */\n actorId?: string | null\n /** The address the actor had when the entry was written; null for a key. */\n actorEmail?: string | null\n /**\n * The address `actorId` holds TODAY, looked up on read by a route with the\n * Admin SDK, for an entry that recorded a uid and no address.\n */\n actorEmailNow?: string | null\n /**\n * For a STAFF reader only: the staff member behind an entry the workspace\n * did not perform (a staff cancellation), resolved by the route.\n */\n staffActorEmail?: string | null\n /** The name of the API key that wrote the entry, when a key did (AGL-2632). */\n apiKeyName?: string | null\n}\n\n/**\n * Who a host activity entry is attributed to — the writer's half of the\n * contract whose reader is {@link activityActorLabel}.\n *\n * A person is their verified uid and the address they had at the time. An\n * integration is the literal uid `'api'`, no address, and the name the\n * organization gave its key — carried because a feed that says \"Someone\"\n * about every write a key made is an audit trail with a hole in it: three\n * Zapier zaps and a nightly import all read as the same nobody.\n */\nexport interface HostActivityActor {\n uid: string\n email?: string | null\n /** The API key's name, for an entry an integration wrote. */\n apiKeyName?: string | null\n}\n\n/**\n * What the presenter needs to build a link. `host` is the `[host]` URL\n * segment — the site SLUG the console routes by (AGL-622), NOT the host doc\n * id — so pass `useParams().host`, not the doc id. When it is present the\n * entry is treated as host-scoped, otherwise org-scoped — which\n * disambiguates `member`, the one type both logs use.\n */\nexport interface ActivityLinkContext {\n orgSlug?: string\n host?: string\n}\n\n/** Human, singular noun for a target type. Falls back to the raw type. */\nconst TYPE_LABELS: Record<string, string> = {\n host: 'Site',\n screen: 'Screen',\n layout: 'Layout',\n theme: 'Theme',\n media: 'Media',\n content: 'Content',\n variable: 'Variable',\n function: 'Function',\n workflow: 'Workflow',\n member: 'Member',\n component: 'Component',\n template: 'Template',\n org: 'Organization',\n invite: 'Invitation',\n subscription: 'Subscription',\n // A custom role's AI permission flipped (AGL-2929).\n role: 'Role',\n // A generation job (AGL-2904): the org feed names the job, and each of its\n // outputs names the resource it produced.\n aiJob: 'AI generation',\n // CRM records (AGL-2622): a person added, a company or a deal created or\n // deleted, a lead converted — logged into the feed of the site whose\n // console did it, and linked to the record the way a screen is.\n contact: 'Contact',\n lead: 'Lead',\n company: 'Company',\n deal: 'Deal',\n}\n\n/** The activity target types that are CRM records, for the link switch. */\nconst CRM_TARGET_KINDS = new Set<string>(Object.keys(CRM_RECORD_SECTIONS))\n\n/**\n * The org-level hub's section for each record kind an org feed entry may\n * name (AGL-2634): the four record kinds, and tasks, which have a section\n * and no page of their own.\n */\nconst ORG_CRM_SECTIONS: Record<string, string> = {\n ...CRM_RECORD_SECTIONS,\n task: 'tasks',\n}\n\n/**\n * Human noun for a target type, e.g. `'screen'` → `'Screen'`. A plugin's\n * own resource is stored as `pluginId:noun` (AGL-2978), and a reader sees\n * the noun: `acme-mail:mailbox` → `Mailbox`.\n */\nexport function activityTypeLabel(type: string | undefined): string {\n if (!type) return 'Item'\n const known = TYPE_LABELS[type]\n if (known) return known\n const noun = pluginActivityTargetNoun(type) ?? type\n return noun.charAt(0).toUpperCase() + noun.slice(1)\n}\n\n/**\n * A label for the target column/link. Prefers the recorded name; when none\n * was written (older entries, id-only saves) it degrades to the humanized\n * type — NEVER the raw doc id, which is meaningless to a reader.\n */\nexport function activityTargetLabel(\n target: ActivityTargetLike | null | undefined,\n): string {\n if (target?.name) return target.name\n return activityTypeLabel(target?.type)\n}\n\n/**\n * What the action column reads (AGL-2929). Most actions are the sentence\n * their writer stored; a plugin's rows store a code (`ai.job.output`) so\n * that three surfaces can recognize them without parsing prose, and the\n * code is translated here — once, from the plugin-declared catalog\n * (AGL-2940) — rather than shown to a reader as a dotted path.\n */\nexport function activityActionLabel(action: string | undefined): string {\n const stored = action?.trim() ?? ''\n return (\n pluginActivityActionLabel(stored) ??\n duplicateActivityActionLabel(stored) ??\n stored\n )\n}\n\n/**\n * The filter group an entry's code belongs to — the chip that keeps it —\n * or `undefined` for a prose action no plugin declared.\n */\nexport function activityEntryGroupId(entry: ActivityEntryLike): string | undefined {\n return pluginActivityGroupForAction(entry.action?.trim())?.id\n}\n\n/**\n * The primary line for a feed entry: the action, suffixed with the target\n * name when one is known, so \"Saved the screen\" becomes \"Saved the screen —\n * Home\". A missing action degrades to the target label rather than blank.\n */\nexport function activityPrimaryText(entry: ActivityEntryLike): string {\n const action = activityActionLabel(entry.action)\n const name = entry.target?.name?.trim()\n if (action && name) return `${action} — ${name}`\n if (action) return action\n return activityTargetLabel(entry.target)\n}\n\n/** How an integration's key is named wherever an actor is: `API key Zapier`. */\nexport function apiKeyActorLabel(name: string): string {\n return `API key ${name}`\n}\n\n/**\n * The \"who\" line for an entry — always someone a reader can narrow down.\n *\n * In order: the address recorded when the entry was written; the key that\n * wrote it, by name; the address the recorded uid holds now, when the writer\n * held a uid and no address (the billing webhook, before it carried the\n * address stamped at the console act); else the uid itself, for an account\n * that no longer resolves.\n *\n * An entry with no uid at all is not an anonymous one. Every writer that\n * leaves `actorId` null is a platform process, so the feed names it: a\n * workflow run by its workflow, anything else — the billing webhook,\n * inbound CRM mail, a background AI job, a staff cancellation — as the\n * platform; a staff reader is also told which staff member. Never \"Someone\", which read as a person nobody could identify\n * (AGL-3369).\n */\nexport function activityActorLabel(entry: ActivityEntryLike): string {\n if (entry.actorEmail) return entry.actorEmail\n if (entry.apiKeyName) return apiKeyActorLabel(entry.apiKeyName)\n const actorId = entry.actorId?.trim()\n if (actorId === 'api') return 'API key'\n if (actorId && !actorId.startsWith('system:')) {\n return entry.actorEmailNow || `Account ${actorId}`\n }\n const target = entry.target ?? undefined\n if ((target?.type ?? entry.type) === 'workflow') {\n const name = target?.name?.trim()\n return name ? `Workflow ${name}` : 'Workflow'\n }\n if (entry.staffActorEmail) {\n return `${PLATFORM_BRAND_NAME} staff (${entry.staffActorEmail})`\n }\n return PLATFORM_BRAND_NAME\n}\n\n/**\n * Who set a run off (AGL-3376), as a run row stores it: the actor the event's\n * door named, carried through the engine. `webhook` is an inbound webhook's\n * caller, named by the hook.\n */\nexport interface RunTriggeredByLike {\n kind?: string\n uid?: string\n email?: string\n apiKeyName?: string\n name?: string\n}\n\n/**\n * The run history's Who: whoever set the run off, never the automation\n * itself — every row in a run history is that automation, so naming it\n * again would answer nothing. A run written before its trigger was recorded\n * says so rather than guessing from the event's name.\n */\nexport function runTriggeredByLabel(entry: {\n triggeredBy?: RunTriggeredByLike | null\n}): string {\n const by = entry.triggeredBy\n switch (by?.kind) {\n case 'member':\n return by.email || (by.uid ? `Account ${by.uid}` : 'A member')\n case 'apiKey':\n return by.apiKeyName ? apiKeyActorLabel(by.apiKeyName) : 'API key'\n case 'visitor':\n return by.email ? `Site visitor (${by.email})` : 'Site visitor'\n case 'webhook':\n return by.name ? `Inbound webhook ${by.name}` : 'Inbound webhook'\n case 'platform':\n return PLATFORM_BRAND_NAME\n default:\n return 'Not recorded'\n }\n}\n\n/**\n * A deep link to the resource an entry points at, or `undefined` when it\n * cannot be built (missing context, or a type with nowhere sensible to go).\n * `buildRoute` emits `<orgSlug?>` placeholders for missing params, so every\n * branch guards its inputs and returns `undefined` rather than a dead link.\n */\nexport function activityHref(\n entry: ActivityEntryLike,\n ctx: ActivityLinkContext,\n): string | undefined {\n const { orgSlug, host } = ctx\n if (!orgSlug) return undefined\n const target = entry.target ?? undefined\n const type = target?.type ?? entry.type\n const id = target?.id ?? entry.targetId\n const versionId = target?.versionId\n\n // Host-scoped entries: every host route needs both orgSlug and host.\n if (host) {\n switch (type) {\n case 'host':\n return buildRoute(Route.HOST_DASHBOARD, { orgSlug, host })\n case 'screen':\n // The screen detail page is version-addressed; without a versionId\n // there is no \"current\" pointer to route through, so land on the list.\n return id && versionId\n ? buildRoute(Route.SCREEN_DETAILS, {\n orgSlug,\n host,\n screenId: id,\n versionId,\n })\n : buildRoute(Route.HOST_SCREENS, { orgSlug, host })\n case 'component':\n return id\n ? buildRoute(Route.COMPONENT_DETAILS, {\n orgSlug,\n host,\n componentId: id,\n })\n : buildRoute(Route.HOST_COMPONENTS, { orgSlug, host })\n case 'template':\n return id\n ? buildRoute(Route.TEMPLATE_DETAILS, {\n orgSlug,\n host,\n templateId: id,\n })\n : buildRoute(Route.HOST_TEMPLATES, { orgSlug, host })\n case 'layout':\n return id\n ? buildRoute(Route.LAYOUT_DETAILS, { orgSlug, host, layoutId: id })\n : buildRoute(Route.HOST_LAYOUTS, { orgSlug, host })\n case 'theme':\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n case 'media':\n return buildRoute(Route.HOST_MEDIA, { orgSlug, host })\n case 'content':\n return buildRoute(Route.HOST_CONTENT, { orgSlug, host })\n case 'variable':\n return buildRoute(Route.HOST_DATA, { orgSlug, host })\n case 'function':\n return buildRoute(Route.HOST_LOGIC, { orgSlug, host })\n case 'workflow':\n return buildRoute(Route.HOST_AUTOMATION, { orgSlug, host })\n case 'member':\n // Site team is managed on the Setup page — there is no per-member\n // host route.\n return buildRoute(Route.HOST_SETUP, { orgSlug, host })\n default:\n // A CRM record's own page, or its list when the entry named no id —\n // a deleted record still has a list to land on. The CRM is a plugin\n // hub, so its addresses come from the shared builder rather than\n // the route table, the same door the console's search uses.\n if (type && CRM_TARGET_KINDS.has(type)) {\n const kind = type as CrmRecordKind\n return id\n ? crmRecordHref({ orgSlug, host }, kind, id)\n : crmSectionHref({ orgSlug, host }, CRM_RECORD_SECTIONS[kind])\n }\n return undefined\n }\n }\n\n // Org-scoped entries.\n switch (type) {\n case 'org':\n return buildRoute(Route.ORG_SETTINGS, { orgSlug })\n case 'member':\n return id\n ? buildRoute(Route.MANAGE_TEAM_MEMBER, { orgSlug, uid: id })\n : buildRoute(Route.MANAGE_TEAM, { orgSlug })\n case 'invite':\n return buildRoute(Route.MANAGE_TEAM, { orgSlug })\n default: {\n // A CRM record acted on at the ORGANIZATION level (AGL-2634) — a deal\n // moved from the org board, a merge, a bulk bar's action — opens in\n // the org-level hub. A lead's org address needs its site, which the\n // entry does not carry, and a bulk line names no record: both land\n // on the section.\n const section = type ? ORG_CRM_SECTIONS[type] : undefined\n if (!section) return undefined\n const hub = `${buildRoute(Route.ORG_CRM, { orgSlug })}/${section}`\n return id && type !== 'lead' && type !== 'task'\n ? `${hub}/${encodeURIComponent(id)}`\n : hub\n }\n }\n}\n\n/**\n * How an automation run ended (AGL-2171).\n *\n * `skipped` is a first-class outcome, not an absence. An unmet condition\n * used to be a bare `continue` that wrote nothing, so \"why didn't my\n * automation fire?\" — the most common support question about automations —\n * had no answer anywhere in the product, while `/product/workflows`\n * advertises an amber `Skipped` row that answers it.\n */\nexport type ActionRunResult = 'succeeded' | 'failed' | 'skipped'\n\n/** A stored run entry: an activity document with the run fields on it. */\nexport interface ActionRunEntryLike extends ActivityEntryLike {\n result?: string\n trigger?: string\n summary?: string\n}\n\n/**\n * `formSubmission` → `Form submitted`.\n *\n * The console rendered the raw camelCase identifier in the trigger select\n * and in every run row. The words are `HOST_EVENT_LABELS` in `workflows.ts`\n * — one map, so the picker an author chose from and the run row they read\n * afterwards cannot name the same event differently.\n */\nexport function actionTriggerLabel(event: string | undefined): string {\n return hostEventLabel(event)\n}\n\n/**\n * The run's outcome, tolerating rows written before it was recorded.\n *\n * A legacy entry carries only the prose `action` string, so it is read\n * back: `with errors:` means it failed and `Action ran on` means it\n * succeeded. Anything else is not a run at all — the activity collection\n * also holds publishes, media saves and member changes, and calling one of\n * those `succeeded` would put it in the run history under a verdict that\n * means nothing for it.\n */\nexport function actionRunResult(\n entry: ActionRunEntryLike,\n): ActionRunResult | undefined {\n const stored = String(entry.result ?? '').trim()\n if (stored === 'succeeded' || stored === 'failed' || stored === 'skipped') {\n return stored\n }\n const action = String(entry.action ?? '')\n if (!action.startsWith('Action ran on')) return undefined\n return action.includes('with errors:') ? 'failed' : 'succeeded'\n}\n\n/**\n * The `What happened` column: `Sent email · saved to Leads · webhook 200`.\n *\n * Falls back to the legacy prose, minus its `Action ran on <event>` prefix\n * — that fact is the Trigger column now, and repeating it in the summary\n * would make every historic row say the same thing twice.\n */\nexport function actionRunSummary(entry: ActionRunEntryLike): string {\n const summary = String(entry.summary ?? '').trim()\n if (summary) return summary\n const action = String(entry.action ?? '').trim()\n const errors = action.match(/with errors:\\s*(.+)$/)\n if (errors) return errors[1]\n // A pre-AGL-2171 success recorded nothing but the fact that it ran.\n if (action.startsWith('Action ran on')) return 'Ran'\n return action\n}\n"],"names":["Route","buildRoute","CRM_RECORD_SECTIONS","crmRecordHref","crmSectionHref","PLATFORM_BRAND_NAME","hostEventLabel","duplicateActivityActionLabel","pluginActivityActionLabel","pluginActivityGroupForAction","pluginActivityTargetNoun","TYPE_LABELS","host","screen","layout","theme","media","content","variable","function","workflow","member","component","template","org","invite","subscription","role","aiJob","contact","lead","company","deal","CRM_TARGET_KINDS","Set","Object","keys","ORG_CRM_SECTIONS","task","activityTypeLabel","type","known","noun","charAt","toUpperCase","slice","activityTargetLabel","target","name","activityActionLabel","action","stored","trim","activityEntryGroupId","entry","id","activityPrimaryText","apiKeyActorLabel","activityActorLabel","actorEmail","apiKeyName","actorId","startsWith","actorEmailNow","undefined","staffActorEmail","runTriggeredByLabel","by","triggeredBy","kind","email","uid","activityHref","ctx","orgSlug","targetId","versionId","HOST_DASHBOARD","SCREEN_DETAILS","screenId","HOST_SCREENS","COMPONENT_DETAILS","componentId","HOST_COMPONENTS","TEMPLATE_DETAILS","templateId","HOST_TEMPLATES","LAYOUT_DETAILS","layoutId","HOST_LAYOUTS","HOST_SETUP_THEME","HOST_MEDIA","HOST_CONTENT","HOST_DATA","HOST_LOGIC","HOST_AUTOMATION","HOST_SETUP","has","ORG_SETTINGS","MANAGE_TEAM_MEMBER","MANAGE_TEAM","section","hub","ORG_CRM","encodeURIComponent","actionTriggerLabel","event","actionRunResult","String","result","includes","actionRunSummary","summary","errors","match"],"mappings":"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"}
@@ -63,6 +63,7 @@ export declare enum Route {
63
63
  * (AGL-2839) — outside the CRM, so it is there on every plan.
64
64
  */
65
65
  ORG_SETTINGS_PRIVACY = "/[orgSlug]/settings/privacy",
66
+ ORG_SETTINGS_HOLDS = "/[orgSlug]/settings/holds",
66
67
  ORG_SETTINGS_DELETE = "/[orgSlug]/settings/delete",
67
68
  /**
68
69
  * Billing, section by section (AGL-2501).
@@ -316,6 +317,9 @@ export interface RoutePayload {
316
317
  [Route.ORG_SETTINGS_PRIVACY]: {
317
318
  orgSlug: string;
318
319
  };
320
+ [Route.ORG_SETTINGS_HOLDS]: {
321
+ orgSlug: string;
322
+ };
319
323
  [Route.ORG_SETTINGS_DELETE]: {
320
324
  orgSlug: string;
321
325
  };
@@ -201,6 +201,10 @@ export var Route = /*#__PURE__*/ function(Route) {
201
201
  * Export the people a workspace holds and file a person's erasure
202
202
  * (AGL-2839) — outside the CRM, so it is there on every plan.
203
203
  */ Route["ORG_SETTINGS_PRIVACY"] = "/[orgSlug]/settings/privacy";
204
+ // Holds & reviews (AGL-3368): everything on the workspace that was held,
205
+ // flagged, locked or paused, its status, and the way to request a review.
206
+ // Where every risk notice's "Request a review" lands (`?notice=<id>`).
207
+ Route["ORG_SETTINGS_HOLDS"] = "/[orgSlug]/settings/holds";
204
208
  Route["ORG_SETTINGS_DELETE"] = "/[orgSlug]/settings/delete";
205
209
  /**
206
210
  * Billing, section by section (AGL-2501).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/console-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// URL scheme (AGL-621): the org is a first-class path segment `[orgSlug]`\n// so the URL — not client-side precedence — is the source of truth for the\n// active workspace. Host routes nest under `/[orgSlug]/hosts/[host]`; the\n// org area (settings, team, billing, media, data, plugins, support,\n// marketplace) lives directly under `/[orgSlug]`. User-level `manage/*`,\n// staff `admin/*`, and `auth` routes are NOT org-scoped. Hosts stay keyed\n// by doc id here; AGL-622 swaps `[hostId]` for the subdomain slug.\nexport enum Route {\n ADMIN_ORGS = '/admin/orgs',\n ADMIN_ORG_DETAIL = '/admin/orgs/[orgId]',\n ADMIN_ORG_HOST_DETAIL = '/admin/orgs/[orgId]/host/[hostId]',\n ADMIN_OVERVIEW = '/admin/overview',\n // Staff coupon management (AGL-1105): create discount coupons + promotion\n // codes and read the live net-margin rating before committing to one.\n ADMIN_COUPONS = '/admin/coupons',\n // The panic button (AGL-1501): platform/org/host/user lockdown controls.\n ADMIN_LOCKDOWN = '/admin/lockdown',\n // The narrow lever beside it (AGL-1687): disable or release ONE uploaded\n // file. Its own page rather than a panel on Lockdown because the input is\n // an asset, not a scope — the two forms share no field but the reason.\n ADMIN_MEDIA_QUARANTINE = '/admin/media-quarantine',\n // The INPUT to the two levers above (AGL-1964). Lockdown and quarantine are\n // both things an operator does after learning there is a problem; until\n // this page there was no way for anyone outside the company to say there\n // was one. Reports arrive from the unauthenticated form the tenant runtime\n // serves at /api/report-abuse on every origin.\n ADMIN_ABUSE_REPORTS = '/admin/abuse-reports',\n // The same input, one collection over (AGL-2310). The marketplace's own\n // report button wrote `marketplaceReports` from the day it shipped and\n // nothing read it — every report filed, acknowledged, and unreachable. This\n // is that queue, deliberately shaped like the abuse one rather than as a\n // second vocabulary for the same act.\n // The probes, on a screen (AGL-1900). /api/health/{backups,rate-limits,\n // signups} and /api/admin/email-health each answered a bad-day question to\n // a curl and to nothing else; this is where an operator reads them.\n ADMIN_HEALTH = '/admin/health',\n // The maintenance jobs, on a screen (AGL-1949). audit-archive,\n // reap-plugin-artifacts and reverify-plugin-versions accepted the shared\n // cron secret and nothing else, so the only way to preview or run one was a\n // shell holding the production secret. Separate from Health because Health\n // reads and this acts.\n ADMIN_MAINTENANCE = '/admin/maintenance',\n // The Texas sales tax return (AGL-1900). The figures AGL-1811 computes had\n // only a curl to reach them; filing happens on a fixed quarterly calendar\n // from 2026-09-01, so the return needs a URL a person can bookmark.\n ADMIN_REVENUE = '/admin/revenue',\n // Realised band utilization and the margin that follows from it. Beside\n // Revenue because the two are the halves of one question: that page reports\n // what came in, this one what serving it cost.\n ADMIN_MARGIN_UTILIZATION = '/admin/margin-utilization',\n ADMIN_TAX_RETURN = '/admin/tax-return',\n ADMIN_AUDIT = '/admin/audit',\n ADMIN_USERS = '/admin/users',\n ADMIN_USER_DETAIL = '/admin/users/[uid]',\n ADMIN_FLAGS = '/admin/flags',\n // Platform-wide settings that are not a release flag (AGL-2486). The\n // free-workspace ceiling was the first of them and it opened the\n // Organizations LIST, above the table — a global lever wedged into the\n // screen for browsing individual orgs. Settings that describe the platform\n // rather than one org belong on their own tab, beside the flags.\n ADMIN_SETTINGS = '/admin/settings',\n // One submission or listed plugin in full (AGL-959): the queue index is\n // for scanning, this is where a reviewer reads the manifest, weighs the\n // verifier findings and acts.\n // A page a plugin adds to the staff area (AGL-2939), rendered by the\n // console's generic staff route. The console's own staff routes win the\n // segments they use.\n ADMIN_STAFF_PAGE = '/admin/[staffPage]',\n // Staff support-ticket queue (AGL-849): the operator side of the\n // subscriber `MANAGE_SUPPORT_TICKETS` page — every org's tickets in one place.\n ADMIN_SUPPORT = '/admin/support',\n // Do-not-contact list (AGL-1592): the intake and the queue for Privacy\n // Policy v4 §11's marketing call/text opt-out, and for \"delete the phone\n // number you hold for me\".\n ADMIN_CONTACT_SUPPRESSIONS = '/admin/contact-suppressions',\n ADMIN_EMAILS = '/admin/emails',\n // The one besigner route with no host and no org in it (AGL-749). A system\n // email belongs to the platform, not to a workspace, so it is staff-scoped\n // like the rest of `/admin/*` rather than nested under `[orgSlug]`.\n ADMIN_EMAIL_BESIGNER = '/admin/emails/[templateKey]/versions/[versionId]/besigner',\n ORG_HOME = '/[orgSlug]',\n ORG_MEDIA = '/[orgSlug]/media',\n ORG_DATA = '/[orgSlug]/data',\n /*\n * The organization's address book.\n *\n * Org-scoped rather than a section of a site, because it answers the one\n * question a site page cannot: which of the organization's sites know a\n * given person. A contact document is shared by every site in the org — one\n * human who touched two of them is one row — and until this route there was\n * no surface anywhere that showed the deduped person, so the dedupe the\n * shared address book exists for was invisible and the billing unit\n * (unique people per org) had nothing standing behind it.\n *\n * It shows identity, which sites hold the person, and consent per site. The\n * per-site CRM — notes, tags, timeline, order history — stays on the site's\n * own Contacts surface, because those are the holder's business records and\n * this is the one route designed to cross between holders.\n *\n * A LEGACY ADDRESS since AGL-2630: the page behind it redirects, permanently,\n * to the org-level CRM hub's contacts section. It stays declared so a link\n * kept from before — a bookmark, a docs page, an email — still resolves,\n * and so the redirect has a constant to be built from rather than a string.\n */\n ORG_CONTACTS = '/[orgSlug]/contacts',\n /*\n * The organization-level CRM hub (AGL-2630): the same sections the site hub\n * has — `/[orgSlug]/crm/<section>[/<recordId>]` — mounted with an ORG scope,\n * for an org-wide member who works every site's people at once. A bare\n * `/crm` lands on the first section the reader may open, as the site hub's\n * does. What this route adds over the site hub is the cross-site fact the\n * old address book carried — which sites know a person — and what it takes\n * from it is the site every create must name, since a record is always\n * captured BY a site.\n */\n ORG_CRM = '/[orgSlug]/crm',\n /*\n * The generic ORGANIZATION-level plugin route (AGL-2974): every surface an\n * extension declares in `orgNavItems` is served here, at\n * `/[orgSlug]/<href>[/<section>…]`, by\n * `app/(app)/[orgSlug]/[...pluginSlug]/page.tsx`. The org twin of\n * `HOST_PLUGIN`, and like it the one org route whose leaf segment is data:\n * every named org route in this enum wins over it.\n */\n ORG_PLUGIN = '/[orgSlug]/[pluginSlug]',\n ORG_PLUGINS = '/[orgSlug]/plugins',\n // One plugin, as installed in this workspace (AGL-1007): scope, settings\n // and permissions in one place. The segment takes EITHER identifier\n // (AGL-1010) — a listing id for a marketplace install, a registry id for a\n // first-party plugin — hence `pluginRef` rather than `listingId`.\n ORG_PLUGIN_INSTALLATION = '/[orgSlug]/plugins/[pluginRef]',\n // Org-scope marketplace (AGL-772): the single place to browse, view and\n // install marketplace items. The per-site tab it replaced, and the retired\n // `/[orgSlug]/marketplace` seller area, were removed with the rest of the\n // `marketplace` naming in AGL-975 — the word is being freed for a public\n // forum, and a redirect stub sitting on `/marketplace` would have taken the\n // one path that feature wants.\n /*\n * The marketplace's own SECTIONS are not here any more (AGL-3080). Eight\n * of them — browse, licences, upload, the four seller cards and the\n * publish form — were hand-written console routes, and became an\n * `orgNavItems` declaration whose `sections` name those segments once, in\n * the plugin. Restating them here was a second copy of a list the plugin\n * owns, and every entry had zero callers the moment the routes went.\n *\n * What stays is what the CONSOLE still builds: the surface itself, a\n * listing, a publisher, and the installed tab that the org plugins card\n * links across to. A console page linking into a plugin's surface is the\n * ordinary case; a console page enumerating that surface's sections is\n * not.\n */\n ORG_MARKETPLACE = '/[orgSlug]/marketplace',\n /*\n * Marketplace SECTIONS are routes (AGL-2501), for the reason the settings\n * and account sections are: a panel that is not open should cost nothing,\n * and the seller half of this hub reads the organization's REVENUE.\n *\n * The four seller sections are the reason the layout gates rather than the\n * rail. As tabs they were simply not rendered for a member without\n * `publishToMarketplace`; as routes each one is reachable by typing its URL,\n * so the refusal has to sit above them all rather than in the list that\n * draws them.\n *\n * Every segment here is shorter than the ten characters `createResourceUid`\n * emits, so none of them can ever shadow a real `[listingId]` sitting at the\n * same level. `upload` rather than `publish` because `publish/plugin` is\n * already a route beneath this one, and two directories contributing the\n * same segment is a tree Next cannot resolve a layout for.\n */\n ORG_MARKETPLACE_INSTALLED = '/[orgSlug]/marketplace/installed',\n ORG_MARKETPLACE_LISTING = '/[orgSlug]/marketplace/[listingId]',\n ORG_MARKETPLACE_PUBLISHER = '/[orgSlug]/marketplace/publisher/[handle]',\n // Publishing a plugin is a page, not a modal (AGL-1078): the most\n // consequential thing a publisher does now has a URL, so it can be linked,\n // reloaded, and reached from a listing to ship an update.\n ORG_SETTINGS = '/[orgSlug]/settings',\n /*\n * Settings SECTIONS are routes (AGL-2501). `HubTabs` mounted every panel —\n * `keepMounted`, with `lazy` off by default and passed by nobody — so\n * opening General also mounted the API-keys, SSO and data-export cards and\n * ran every read in them. Routes mount one page and code-split per route.\n * `ORG_SETTINGS` itself redirects to General.\n */\n ORG_SETTINGS_GENERAL = '/[orgSlug]/settings/general',\n ORG_SETTINGS_PROFILE = '/[orgSlug]/settings/profile',\n ORG_SETTINGS_PLUGINS = '/[orgSlug]/settings/plugins',\n ORG_SETTINGS_API_KEYS = '/[orgSlug]/settings/api-keys',\n ORG_SETTINGS_BRANDING = '/[orgSlug]/settings/branding',\n ORG_SETTINGS_SSO = '/[orgSlug]/settings/sso',\n ORG_SETTINGS_OWNERSHIP = '/[orgSlug]/settings/ownership',\n /**\n * Export the people a workspace holds and file a person's erasure\n * (AGL-2839) — outside the CRM, so it is there on every plan.\n */\n ORG_SETTINGS_PRIVACY = '/[orgSlug]/settings/privacy',\n ORG_SETTINGS_DELETE = '/[orgSlug]/settings/delete',\n /**\n * Billing, section by section (AGL-2501).\n *\n * `MANAGE_BILLING` is unchanged and is the PLAN section — it resolves to a\n * page inside a route group, which adds no path segment. That is what makes\n * this split free: every link in the console, every hash anchor\n * (`#addons`, `#collaborator-seats`) and Stripe's own dunning mail all point\n * here already and none of them moves. No redirect, and no `?tab=` shim to\n * carry forever.\n */\n MANAGE_BILLING = '/[orgSlug]/billing',\n MANAGE_BILLING_USAGE = '/[orgSlug]/billing/usage',\n MANAGE_BILLING_INVOICES = '/[orgSlug]/billing/invoices',\n MANAGE_BILLING_SETTINGS = '/[orgSlug]/billing/settings',\n // The ONE billing URL that carries no org (AGL-2430).\n //\n // Everything else on this table is org-scoped, which is correct for a\n // console and wrong for the only caller that cannot template a URL:\n // Stripe's dunning emails. \"Payment method updates\" takes a single static\n // link for every customer in the account — there is no `{{org}}` to\n // interpolate — so a table with no org-agnostic billing entry means those\n // emails can only ever point at a page that is not billing. Today all four\n // of them point at the marketing homepage, which is how a failed card\n // becomes a cancelled subscription with no reachable way to fix it.\n //\n // This resolves the org from the SESSION instead of from the URL: one\n // workspace goes straight through, several offer a choice, none says so.\n // `billing` is a reserved subdomain (`RESERVED_SUBDOMAINS`), so no org can\n // hold this slug and the literal segment can never shadow a real\n // `/[orgSlug]`.\n BILLING_ENTRY = '/billing',\n MANAGE_USER_SETTINGS = '/manage/user',\n /*\n * Account SECTIONS are routes (AGL-2501). `HubTabs` mounted every panel —\n * `keepMounted`, with `lazy` off by default and passed by nobody — so\n * opening Account also mounted the email-addresses, passkeys, recent\n * sign-ins, data-export and close-account cards and ran every read in them.\n * Routes mount one page and code-split per route.\n *\n * The segments are the ids the panels carried, because a transactional\n * email already in people's inboxes links `/manage/user?tab=security`:\n * `MANAGE_USER_SETTINGS` forwards each of those ids to the section that\n * holds it, and the segment matching the id is what keeps that map a\n * one-liner rather than a second naming scheme.\n */\n MANAGE_USER_ACCOUNT = '/manage/user/account',\n MANAGE_USER_EMAILS = '/manage/user/emails',\n MANAGE_USER_PROFILE = '/manage/user/profile',\n MANAGE_USER_BASIC = '/manage/user/basic',\n MANAGE_USER_SECURITY = '/manage/user/security',\n MANAGE_USER_CLOSE = '/manage/user/close',\n MANAGE_NOTIFICATIONS = '/manage/notifications',\n /*\n * The preferences, off the feed (AGL-3226).\n *\n * They lived in the header of the feed itself, which was already the\n * tallest thing on that page before per-channel and per-scope settings\n * multiplied them — and it put an edit form above the list it edits, which\n * the console does nowhere else.\n *\n * A CHILD of the feed rather than a sibling under `/manage`, because the\n * relationship is exactly that: it configures what the page above it shows.\n * That also keeps `/manage/notifications` the address in the transactional\n * mail already in people's inboxes, which keeps working and now carries a\n * button to here.\n */\n MANAGE_NOTIFICATION_SETTINGS = '/manage/notifications/settings',\n /*\n * A URL for the defect channel (AGL-2486). \"Report an issue\" was reachable\n * only by opening the account menu, so nothing outside the console could\n * send anyone to it — the repo's README pointed at GitHub issues, which we\n * do not use, and every other \"tell us about it\" surface had the same\n * problem. User-level rather than org-scoped because the dialog is: it\n * needs no workspace, no role and no paid plan.\n */\n MANAGE_REPORT_ISSUE = '/manage/report-issue',\n AUTH_SIGN_IN = '/signin',\n AUTH_SIGN_OUT = '/signout',\n AUTH_SIGN_UP = '/signup',\n AUTH_VERIFY_EMAIL = '/verify-email',\n HOST_LIST = '/[orgSlug]/hosts',\n HOST_CONTENT = '/[orgSlug]/hosts/[host]/content',\n // The collection is a PATH SEGMENT, not `?collection=` (AGL-2498).\n //\n // It was a query parameter because the manager began as one page with a\n // Select on it, and the Select was the only thing that knew which\n // collection you were looking at. That is the wrong owner: which collection\n // is open is the page's IDENTITY, not a filter applied to it. As a\n // parameter it could be dropped by any link that rebuilt the query, it\n // sorted below `?tab=` in a URL nobody could read, and — the reason this\n // moved — it left the entry beneath it with nowhere to hang: an entry is\n // addressed `collection + entry`, and half of that address cannot live in\n // the path while the other half lives in the query.\n //\n // The segment is the collection's SLUG, not its document id, and that is a\n // correction rather than a preference. Collection document ids are not\n // uniform — the seeded ones were given readable ids (`blog`, `changelog`)\n // and everything created since gets a `createResourceUid`, so routing by id\n // produced `/content/changelog` beside `/content/QgXv7lU_rG` on ONE site.\n //\n // A slug is the right key on the merits too: it is unique per host and kind\n // (claimed in a transaction by /api/hosts/collections), it is the\n // collection's own public address, and it is the thing an author recognises.\n // A document id is still ACCEPTED and rewritten to the slug form, so no\n // existing link breaks.\n //\n // `?collection=` still resolves the same way (AGL-845's DAM deep links are\n // in the wild).\n HOST_CONTENT_COLLECTION = '/[orgSlug]/hosts/[host]/content/[collectionSlug]',\n // One entry's detail page (AGL-2498). `entries` is a LITERAL segment rather\n // than `content/[collectionId]/[entryId]`, and it is load-bearing twice\n // over: it mirrors the Firestore path the entry actually lives at\n // (`collections/{c}/entries/{e}`), and it keeps the id space clear so a\n // later sibling — a collection's own settings page, say — cannot be\n // shadowed by an entry that happens to be called the same thing.\n //\n // `[entryId]` also carries the sentinel `new`, which is a draft that has no\n // document yet. Entry ids come from `createResourceUid`, so nothing real\n // can collide with it.\n CONTENT_ENTRY_DETAILS = '/[orgSlug]/hosts/[host]/content/[collectionSlug]/entries/[entryId]',\n MANAGE_TEAM = '/[orgSlug]/team',\n /*\n * Team SECTIONS are routes, not tabs (AGL-2501). A section's bundle then\n * arrives when a reader opens it and never before, the section is linkable,\n * and the active state is a fact about the URL rather than state that has to\n * be kept in sync with it. `MANAGE_TEAM` itself redirects to members.\n */\n MANAGE_TEAM_MEMBERS = '/[orgSlug]/team/members',\n MANAGE_TEAM_ROLES = '/[orgSlug]/team/roles',\n MANAGE_TEAM_ACTIVITY = '/[orgSlug]/team/activity',\n MANAGE_TEAM_MEMBER = '/[orgSlug]/team/[uid]',\n // Support is an UMBRELLA, not a page (AGL-1158). The two channels beneath\n // it are separate features that were only ever one screen: they are gated\n // differently (tickets need a first-response commitment, from Pro; the\n // forum is open to every tier including Free) and they fail independently.\n // Sharing one route made them share a blast radius — AGL-1157 was one bad\n // line in the common loader that emptied BOTH lists at once.\n //\n // `MANAGE_SUPPORT` itself stays, and stays the nav tab's href: it now\n // forwards to whichever channel the org's tier makes primary, so a\n // forum-only workspace lands on a whole forum instead of a half-empty page\n // beside a ticket card it may not use.\n MANAGE_SUPPORT = '/[orgSlug]/support',\n MANAGE_SUPPORT_TICKETS = '/[orgSlug]/support/tickets',\n MANAGE_SUPPORT_FORUM = '/[orgSlug]/support/forum',\n /*\n * The ONE support URL that carries no org (AGL-3265), and the second member\n * of the apex category `BILLING_ENTRY` opened.\n *\n * Everything a person is handed when they need help — a receipt, a system\n * email footer, a Stripe customer portal, a self-host runbook — stores ONE\n * link for every customer, with no workspace to interpolate. `/[orgSlug]`\n * cannot be templated into any of them, so the only support address those\n * surfaces could carry was one outside the console entirely.\n *\n * This resolves the workspace from the SESSION instead: one workspace goes\n * straight through, several offer a choice, none says so. It forwards to\n * `MANAGE_SUPPORT`, which is itself an umbrella — so the tier decision about\n * tickets versus the forum stays in the one place that already makes it.\n *\n * `support` is a reserved subdomain (`RESERVED_SUBDOMAINS` in\n * `host-naming.ts`), so no org can hold this slug and the literal segment\n * can never shadow a real `/[orgSlug]`. That is the same guarantee\n * `BILLING_ENTRY` rests on, and it is a precondition rather than a\n * coincidence: an apex route whose segment an org could claim would take a\n * workspace's own address away from it.\n */\n SUPPORT_ENTRY = '/support',\n HOST_DASHBOARD = '/[orgSlug]/hosts/[host]',\n // The catch-all console page a plugin's own nav items resolve against\n // (`app/(app)/[orgSlug]/hosts/[host]/[pluginSlug]/page.tsx`). Plugin slugs\n // are open-ended, so this is the one route whose leaf segment is data.\n HOST_PLUGIN = '/[orgSlug]/hosts/[host]/[pluginSlug]',\n HOST_INBOX = '/[orgSlug]/hosts/[host]/inbox',\n HOST_CONTACTS = '/[orgSlug]/hosts/[host]/contacts',\n HOST_MEDIA = '/[orgSlug]/hosts/[host]/media',\n HOST_SETUP = '/[orgSlug]/hosts/[host]/setup',\n /*\n * Setup SECTIONS are routes (AGL-2501), so an unopened one costs neither a\n * read nor a byte. `HOST_SETUP` stays the nav tab's href and redirects to\n * Basic details, honouring the `?tab=` ids these sections were deep-linked\n * by — unlike the settings and marketplace hubs, links holding those ids are\n * demonstrably in the wild and two are built in this repo.\n */\n HOST_SETUP_DETAILS = '/[orgSlug]/hosts/[host]/setup/details',\n HOST_SETUP_SEO = '/[orgSlug]/hosts/[host]/setup/seo',\n HOST_SETUP_TRACKING = '/[orgSlug]/hosts/[host]/setup/tracking',\n HOST_SETUP_THEME = '/[orgSlug]/hosts/[host]/setup/theme',\n HOST_SETUP_EMAILS = '/[orgSlug]/hosts/[host]/setup/emails',\n // Host Admin area (AGL-1014): owner/admin-only controls — per-site plugin\n // enablement and the Danger zone — out of the Setup page collaborators\n // legitimately visit.\n HOST_ADMIN = '/[orgSlug]/hosts/[host]/admin',\n /*\n * Site-admin SECTIONS are routes (AGL-2501), so an unopened one costs neither\n * a read nor a byte. `HOST_ADMIN` redirects to Plugins and still honours the\n * `?tab=` ids these sections were deep-linked by.\n */\n /*\n * The site as an OBJECT: its name in the console and the subdomain it\n * answers to. First in the rail because an address and a name are what\n * identify the thing every other section governs.\n */\n HOST_ADMIN_GENERAL = '/[orgSlug]/hosts/[host]/admin/general',\n HOST_ADMIN_PLUGINS = '/[orgSlug]/hosts/[host]/admin/plugins',\n /*\n * One plugin, as installed on ONE site (AGL-428, AGL-1014) — the\n * site-scoped twin of `ORG_PLUGIN_INSTALLATION`, reached by clicking a row\n * on the site's plugin\n * list exactly as the workspace list opens its own detail page. The segment\n * is `[pluginRef]` and accepts either identifier for the same reason the\n * workspace route does (AGL-1010): a marketplace install is keyed by listing\n * id, a first-party plugin by its registry id, and an admin does not think\n * of the two as different things.\n */\n HOST_ADMIN_PLUGIN = '/[orgSlug]/hosts/[host]/admin/plugins/[pluginRef]',\n HOST_ADMIN_DOMAIN = '/[orgSlug]/hosts/[host]/admin/domain',\n HOST_ADMIN_SECURITY = '/[orgSlug]/hosts/[host]/admin/security',\n /*\n * The designed screen behind each status code, and the switch that shows the\n * 503 one at every address at once (AGL-3178).\n *\n * Beside Security because both answer what a visitor's browser is handed on\n * a page the site did not design for them, and above Activity because it is\n * a control rather than a record.\n */\n HOST_ADMIN_ERROR_PAGES = '/[orgSlug]/hosts/[host]/admin/error-pages',\n HOST_ADMIN_ACTIVITY = '/[orgSlug]/hosts/[host]/admin/activity',\n /*\n * Backup, restore and publishing the site as a template — moving the whole\n * site somewhere else, or bringing it back. Its own section rather than the\n * Danger zone: restore overwrites, but export and publishing a template do\n * not, and filing all three under a destructive heading mislabels two of\n * them.\n */\n HOST_ADMIN_BACKUP = '/[orgSlug]/hosts/[host]/admin/backup',\n HOST_ADMIN_DANGER = '/[orgSlug]/hosts/[host]/admin/danger',\n HOST_THEME = '/[orgSlug]/hosts/[host]/theme',\n /*\n * The automation section: Workflows, Actions and Webhooks under one parent.\n * The parent is named for what the three have in common, so no tab shares a\n * name with the section that contains it.\n */\n HOST_AUTOMATION = '/[orgSlug]/hosts/[host]/automation',\n /**\n * The address the automation section answered to before it had a name of\n * its own. Nothing links here — `HOST_AUTOMATION` is what the nav, the\n * search results and the activity feed build — but the console redirects it\n * permanently, so a held bookmark still lands on the section (and keeps its\n * `?tab=`, which never changed).\n *\n * Kept on the table rather than as a bare literal in `next.config.js`\n * because the redirect and the address it serves are one fact:\n * `automation-section.spec.ts` reads this entry and fails if the rule for\n * it goes missing.\n */\n HOST_WORKFLOWS = '/[orgSlug]/hosts/[host]/workflows',\n HOST_DATA = '/[orgSlug]/hosts/[host]/data',\n HOST_LOGIC = '/[orgSlug]/hosts/[host]/logic',\n HOST_PRODUCTS = '/[orgSlug]/hosts/[host]/products',\n HOST_COMPONENTS = '/[orgSlug]/hosts/[host]/components',\n // Component detail (AGL-693): the listing links here, and the besigner is\n // reached from here — matching SCREEN_DETAILS rather than jumping a row\n // straight into the editor.\n COMPONENT_DETAILS = '/[orgSlug]/hosts/[host]/components/[componentId]',\n HOST_FORMS = '/[orgSlug]/hosts/[host]/forms',\n // A form is a designable document, so its surface is the component one:\n // a list, a detail page that owns the versions, and the besigner reached\n // from there. Deliberately NOT a row that jumps into the editor — a form's\n // routing and consent field are edited on the detail page, and those decide\n // what the design is then checked against.\n FORM_DETAILS = '/[orgSlug]/hosts/[host]/forms/[formId]',\n HOST_TEMPLATES = '/[orgSlug]/hosts/[host]/templates',\n // Template detail (AGL-694), the counterpart to COMPONENT_DETAILS. Note\n // TEMPLATE_BESIGNER carries no versionId — templates version but never\n // publish, so there is no \"current\" pointer to route through.\n TEMPLATE_DETAILS = '/[orgSlug]/hosts/[host]/templates/[templateId]',\n HOST_MARKETING = '/[orgSlug]/hosts/[host]/marketing',\n HOST_BOOKINGS = '/[orgSlug]/hosts/[host]/bookings',\n // Events now come from the events-calendar plugin, served by the generic\n // `[orgSlug]/hosts/[host]/[pluginSlug]` route (AGL-394).\n HOST_REDIRECTS = '/[orgSlug]/hosts/[host]/redirects',\n HOST_USERS = '/[orgSlug]/hosts/[host]/users',\n HOST_ANALYTICS = '/[orgSlug]/hosts/[host]/analytics',\n COMPONENT_BESIGNER = '/[orgSlug]/hosts/[host]/components/[componentId]/versions/[versionId]/besigner',\n FORM_BESIGNER = '/[orgSlug]/hosts/[host]/forms/[formId]/versions/[versionId]/besigner',\n TEMPLATE_BESIGNER = '/[orgSlug]/hosts/[host]/templates/[templateId]/besigner',\n LAYOUT_BESIGNER = '/[orgSlug]/hosts/[host]/layouts/[layoutId]/versions/[versionId]/besigner',\n // Draft preview for every besigner document kind (AGL-1203). Screens had\n // the only preview route; components, layouts and templates showed a\n // Preview button that did nothing. Each renders the same localStorage\n // snapshot through the site renderer, so they work on localhost with no\n // deployment. TEMPLATE_PREVIEW carries no versionId, matching\n // TEMPLATE_BESIGNER.\n COMPONENT_PREVIEW = '/[orgSlug]/hosts/[host]/components/[componentId]/versions/[versionId]/preview',\n FORM_PREVIEW = '/[orgSlug]/hosts/[host]/forms/[formId]/versions/[versionId]/preview',\n TEMPLATE_PREVIEW = '/[orgSlug]/hosts/[host]/templates/[templateId]/preview',\n LAYOUT_PREVIEW = '/[orgSlug]/hosts/[host]/layouts/[layoutId]/versions/[versionId]/preview',\n // The list sits at the bare path, like HOST_COMPONENTS. It used to be\n // `/layouts/list`: the pages router had a `layouts/index.tsx` that\n // redirected to `layouts/list.tsx`, and the App Router migration carried\n // the pair over verbatim. Components, added later, had no such legacy and\n // put its list at the bare path — which is the shape we want everywhere.\n // `/layouts/list` still serves the same page so old links survive — an\n // alias, not a redirect, because the bare path used to answer a cached 308\n // pointing at `/list` and redirecting back would loop. See the comment in\n // `app/(app)/[orgSlug]/hosts/[host]/screens/list/page.tsx`.\n HOST_LAYOUTS = '/[orgSlug]/hosts/[host]/layouts',\n // Layout detail (AGL-695), completing the list → detail → besigner shape\n // across screens, components, templates and layouts.\n LAYOUT_DETAILS = '/[orgSlug]/hosts/[host]/layouts/[layoutId]',\n SCREEN_BESIGNER = '/[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/besigner',\n // Per-site email besigner (AGL-770): a site owner designs a transactional\n // email their site sends. The host-scoped sibling of the staff system-email\n // editor (ADMIN_EMAIL_BESIGNER).\n HOST_EMAIL_BESIGNER = '/[orgSlug]/hosts/[host]/emails/[templateKey]/versions/[versionId]/besigner',\n SCREEN_DETAILS = '/[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/view',\n SCREEN_PREVIEW = '/[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/preview',\n // Bare path, matching HOST_LAYOUTS and HOST_COMPONENTS — see the note on\n // HOST_LAYOUTS. `/screens/list` still serves the same page as an alias.\n HOST_SCREENS = '/[orgSlug]/hosts/[host]/screens',\n}\n\n/**\n * Params required to build each {@link Route}.\n *\n * Deliberately NOT extended from an index signature (AGL-685). It used to be\n * `extends Record<keyof any, any>`, which meant any `Route` missing an entry\n * here silently typed its payload as `any` — `Route.HOST_SETUP` was one of\n * them, and `buildRoute(Route.HOST_SETUP, { hostId })` compiled cleanly while\n * emitting `/<orgSlug?>/hosts/<host?>/setup`. Without the index signature a\n * new `Route` with no entry is a compile error, which is the whole point of\n * the payload being required in the first place.\n */\nexport interface RoutePayload {\n [Route.AUTH_SIGN_UP]: undefined\n [Route.AUTH_SIGN_IN]: undefined\n [Route.AUTH_SIGN_OUT]: undefined\n [Route.AUTH_VERIFY_EMAIL]: undefined\n [Route.SCREEN_BESIGNER]: {\n orgSlug: string\n host: string\n screenId: string\n versionId: string\n }\n [Route.ORG_HOME]: { orgSlug: string }\n [Route.HOST_DASHBOARD]: { orgSlug: string; host: string }\n [Route.ADMIN_ORGS]: undefined\n [Route.ADMIN_ORG_DETAIL]: { orgId: string }\n [Route.ADMIN_ORG_HOST_DETAIL]: { orgId: string; hostId: string }\n [Route.ADMIN_OVERVIEW]: undefined\n [Route.ADMIN_COUPONS]: undefined\n [Route.ADMIN_LOCKDOWN]: undefined\n [Route.ADMIN_MEDIA_QUARANTINE]: undefined\n [Route.ADMIN_ABUSE_REPORTS]: undefined\n [Route.ADMIN_HEALTH]: undefined\n [Route.ADMIN_MAINTENANCE]: undefined\n [Route.ADMIN_REVENUE]: undefined\n [Route.ADMIN_MARGIN_UTILIZATION]: undefined\n [Route.ADMIN_TAX_RETURN]: undefined\n [Route.ADMIN_AUDIT]: undefined\n [Route.ADMIN_USERS]: undefined\n [Route.ADMIN_USER_DETAIL]: { uid: string }\n [Route.ADMIN_FLAGS]: undefined\n [Route.ADMIN_SETTINGS]: undefined\n [Route.ADMIN_STAFF_PAGE]: { staffPage: string }\n [Route.ADMIN_SUPPORT]: undefined\n [Route.ADMIN_CONTACT_SUPPRESSIONS]: undefined\n [Route.ADMIN_EMAILS]: undefined\n [Route.ADMIN_EMAIL_BESIGNER]: { templateKey: string; versionId: string }\n [Route.HOST_EMAIL_BESIGNER]: {\n orgSlug: string\n host: string\n templateKey: string\n versionId: string\n }\n [Route.ORG_MEDIA]: { orgSlug: string }\n [Route.ORG_DATA]: { orgSlug: string }\n [Route.ORG_CONTACTS]: { orgSlug: string }\n [Route.ORG_CRM]: { orgSlug: string }\n [Route.ORG_PLUGIN]: { orgSlug: string; pluginSlug: string }\n [Route.ORG_PLUGINS]: { orgSlug: string }\n [Route.ORG_PLUGIN_INSTALLATION]: { orgSlug: string; pluginRef: string }\n [Route.ORG_MARKETPLACE]: { orgSlug: string }\n [Route.ORG_MARKETPLACE_INSTALLED]: { orgSlug: string }\n [Route.ORG_MARKETPLACE_LISTING]: { orgSlug: string; listingId: string }\n [Route.ORG_MARKETPLACE_PUBLISHER]: { orgSlug: string; handle: string }\n [Route.MANAGE_USER_SETTINGS]: undefined\n [Route.MANAGE_USER_ACCOUNT]: undefined\n [Route.MANAGE_USER_EMAILS]: undefined\n [Route.MANAGE_USER_PROFILE]: undefined\n [Route.MANAGE_USER_BASIC]: undefined\n [Route.MANAGE_USER_SECURITY]: undefined\n [Route.MANAGE_USER_CLOSE]: undefined\n [Route.MANAGE_NOTIFICATIONS]: undefined\n [Route.MANAGE_NOTIFICATION_SETTINGS]: undefined\n [Route.MANAGE_REPORT_ISSUE]: undefined\n [Route.ORG_SETTINGS]: { orgSlug: string }\n [Route.ORG_SETTINGS_GENERAL]: { orgSlug: string }\n [Route.ORG_SETTINGS_PROFILE]: { orgSlug: string }\n [Route.ORG_SETTINGS_PLUGINS]: { orgSlug: string }\n [Route.ORG_SETTINGS_API_KEYS]: { orgSlug: string }\n [Route.ORG_SETTINGS_BRANDING]: { orgSlug: string }\n [Route.ORG_SETTINGS_SSO]: { orgSlug: string }\n [Route.ORG_SETTINGS_OWNERSHIP]: { orgSlug: string }\n [Route.ORG_SETTINGS_PRIVACY]: { orgSlug: string }\n [Route.ORG_SETTINGS_DELETE]: { orgSlug: string }\n [Route.HOST_LIST]: { orgSlug: string }\n [Route.HOST_CONTENT]: { orgSlug: string; host: string }\n [Route.HOST_CONTENT_COLLECTION]: {\n orgSlug: string\n host: string\n /** The collection's slug — see the route. A document id also resolves. */\n collectionSlug: string\n }\n [Route.CONTENT_ENTRY_DETAILS]: {\n orgSlug: string\n host: string\n collectionSlug: string\n /**\n * An entry id, or the literal `new` for a draft with no document yet.\n *\n * An ID here while the collection above is a SLUG, deliberately. An entry\n * slug is unique only WITHIN its collection and it is editable on the\n * detail page — so routing by it would move the console address out from\n * under someone in the middle of typing in the slug field, and a draft has\n * no slug at all to route by. The entry id is also what every activity\n * record and the DAM's \"Used on\" list already carry.\n */\n entryId: string\n }\n [Route.MANAGE_TEAM]: { orgSlug: string }\n [Route.MANAGE_TEAM_MEMBERS]: { orgSlug: string }\n [Route.MANAGE_TEAM_ROLES]: { orgSlug: string }\n [Route.MANAGE_TEAM_ACTIVITY]: { orgSlug: string }\n [Route.MANAGE_TEAM_MEMBER]: { orgSlug: string; uid: string }\n [Route.MANAGE_SUPPORT]: { orgSlug: string }\n [Route.MANAGE_SUPPORT_TICKETS]: { orgSlug: string }\n [Route.MANAGE_SUPPORT_FORUM]: { orgSlug: string }\n [Route.MANAGE_BILLING]: { orgSlug: string }\n [Route.MANAGE_BILLING_USAGE]: { orgSlug: string }\n [Route.MANAGE_BILLING_INVOICES]: { orgSlug: string }\n [Route.MANAGE_BILLING_SETTINGS]: { orgSlug: string }\n [Route.BILLING_ENTRY]: undefined\n [Route.SUPPORT_ENTRY]: undefined\n [Route.HOST_INBOX]: { orgSlug: string; host: string }\n [Route.HOST_CONTACTS]: { orgSlug: string; host: string }\n [Route.HOST_SETUP]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_DETAILS]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_SEO]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_TRACKING]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_THEME]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_EMAILS]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_GENERAL]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_PLUGINS]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_PLUGIN]: { orgSlug: string; host: string; pluginRef: string }\n [Route.HOST_ADMIN_DOMAIN]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_SECURITY]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_ERROR_PAGES]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_ACTIVITY]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_BACKUP]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_DANGER]: { orgSlug: string; host: string }\n [Route.HOST_PLUGIN]: { orgSlug: string; host: string; pluginSlug: string }\n [Route.HOST_MEDIA]: { orgSlug: string; host: string }\n [Route.HOST_THEME]: { orgSlug: string; host: string }\n [Route.HOST_AUTOMATION]: { orgSlug: string; host: string }\n [Route.HOST_WORKFLOWS]: { orgSlug: string; host: string }\n [Route.HOST_DATA]: { orgSlug: string; host: string }\n [Route.HOST_LOGIC]: { orgSlug: string; host: string }\n [Route.HOST_PRODUCTS]: { orgSlug: string; host: string }\n [Route.HOST_COMPONENTS]: { orgSlug: string; host: string }\n [Route.COMPONENT_DETAILS]: {\n orgSlug: string\n host: string\n componentId: string\n }\n [Route.HOST_FORMS]: { orgSlug: string; host: string }\n [Route.FORM_DETAILS]: {\n orgSlug: string\n host: string\n formId: string\n }\n [Route.HOST_TEMPLATES]: { orgSlug: string; host: string }\n [Route.TEMPLATE_DETAILS]: {\n orgSlug: string\n host: string\n templateId: string\n }\n [Route.HOST_MARKETING]: { orgSlug: string; host: string }\n [Route.HOST_BOOKINGS]: { orgSlug: string; host: string }\n [Route.HOST_REDIRECTS]: { orgSlug: string; host: string }\n [Route.HOST_USERS]: { orgSlug: string; host: string }\n [Route.HOST_ANALYTICS]: { orgSlug: string; host: string }\n [Route.COMPONENT_BESIGNER]: {\n orgSlug: string\n host: string\n componentId: string\n versionId: string\n }\n [Route.FORM_BESIGNER]: {\n orgSlug: string\n host: string\n formId: string\n versionId: string\n }\n [Route.TEMPLATE_BESIGNER]: {\n orgSlug: string\n host: string\n templateId: string\n }\n [Route.LAYOUT_BESIGNER]: {\n orgSlug: string\n host: string\n layoutId: string\n versionId: string\n }\n [Route.COMPONENT_PREVIEW]: {\n orgSlug: string\n host: string\n componentId: string\n versionId: string\n }\n [Route.FORM_PREVIEW]: {\n orgSlug: string\n host: string\n formId: string\n versionId: string\n }\n [Route.TEMPLATE_PREVIEW]: {\n orgSlug: string\n host: string\n templateId: string\n }\n [Route.LAYOUT_PREVIEW]: {\n orgSlug: string\n host: string\n layoutId: string\n versionId: string\n }\n [Route.HOST_LAYOUTS]: { orgSlug: string; host: string }\n [Route.LAYOUT_DETAILS]: { orgSlug: string; host: string; layoutId: string }\n [Route.SCREEN_DETAILS]: {\n orgSlug: string\n host: string\n screenId: string\n versionId: string\n }\n [Route.SCREEN_PREVIEW]: {\n orgSlug: string\n host: string\n screenId: string\n versionId: string\n }\n [Route.HOST_SCREENS]: { orgSlug: string; host: string }\n}\n\nexport const routeReplacePattern = /\\[([^\\]]+)\\]/g\n\n/**\n * Builds a concrete path from a {@link Route} template. The payload is\n * REQUIRED whenever the route declares params (e.g. `orgSlug`, `hostId`) and\n * omitted only for param-less routes — so a forgotten `orgSlug` is a compile\n * error, not a `/<orgSlug?>/…` link that breaks at runtime (AGL-621).\n */\nexport function buildRoute<Tmpl extends Route>(\n template: Tmpl,\n ...[payload]: RoutePayload[Tmpl] extends undefined\n ? [payload?: undefined]\n : [payload: RoutePayload[Tmpl]]\n) {\n return template.replace(routeReplacePattern, (match, key) => {\n const value = (payload as Record<string, unknown> | undefined)?.[key]\n return value != null ? String(value) : `<${key}?>`\n })\n}\n"],"names":["Route","routeReplacePattern","buildRoute","template","payload","replace","match","key","value","String"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/console-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// URL scheme (AGL-621): the org is a first-class path segment `[orgSlug]`\n// so the URL — not client-side precedence — is the source of truth for the\n// active workspace. Host routes nest under `/[orgSlug]/hosts/[host]`; the\n// org area (settings, team, billing, media, data, plugins, support,\n// marketplace) lives directly under `/[orgSlug]`. User-level `manage/*`,\n// staff `admin/*`, and `auth` routes are NOT org-scoped. Hosts stay keyed\n// by doc id here; AGL-622 swaps `[hostId]` for the subdomain slug.\nexport enum Route {\n ADMIN_ORGS = '/admin/orgs',\n ADMIN_ORG_DETAIL = '/admin/orgs/[orgId]',\n ADMIN_ORG_HOST_DETAIL = '/admin/orgs/[orgId]/host/[hostId]',\n ADMIN_OVERVIEW = '/admin/overview',\n // Staff coupon management (AGL-1105): create discount coupons + promotion\n // codes and read the live net-margin rating before committing to one.\n ADMIN_COUPONS = '/admin/coupons',\n // The panic button (AGL-1501): platform/org/host/user lockdown controls.\n ADMIN_LOCKDOWN = '/admin/lockdown',\n // The narrow lever beside it (AGL-1687): disable or release ONE uploaded\n // file. Its own page rather than a panel on Lockdown because the input is\n // an asset, not a scope — the two forms share no field but the reason.\n ADMIN_MEDIA_QUARANTINE = '/admin/media-quarantine',\n // The INPUT to the two levers above (AGL-1964). Lockdown and quarantine are\n // both things an operator does after learning there is a problem; until\n // this page there was no way for anyone outside the company to say there\n // was one. Reports arrive from the unauthenticated form the tenant runtime\n // serves at /api/report-abuse on every origin.\n ADMIN_ABUSE_REPORTS = '/admin/abuse-reports',\n // The same input, one collection over (AGL-2310). The marketplace's own\n // report button wrote `marketplaceReports` from the day it shipped and\n // nothing read it — every report filed, acknowledged, and unreachable. This\n // is that queue, deliberately shaped like the abuse one rather than as a\n // second vocabulary for the same act.\n // The probes, on a screen (AGL-1900). /api/health/{backups,rate-limits,\n // signups} and /api/admin/email-health each answered a bad-day question to\n // a curl and to nothing else; this is where an operator reads them.\n ADMIN_HEALTH = '/admin/health',\n // The maintenance jobs, on a screen (AGL-1949). audit-archive,\n // reap-plugin-artifacts and reverify-plugin-versions accepted the shared\n // cron secret and nothing else, so the only way to preview or run one was a\n // shell holding the production secret. Separate from Health because Health\n // reads and this acts.\n ADMIN_MAINTENANCE = '/admin/maintenance',\n // The Texas sales tax return (AGL-1900). The figures AGL-1811 computes had\n // only a curl to reach them; filing happens on a fixed quarterly calendar\n // from 2026-09-01, so the return needs a URL a person can bookmark.\n ADMIN_REVENUE = '/admin/revenue',\n // Realised band utilization and the margin that follows from it. Beside\n // Revenue because the two are the halves of one question: that page reports\n // what came in, this one what serving it cost.\n ADMIN_MARGIN_UTILIZATION = '/admin/margin-utilization',\n ADMIN_TAX_RETURN = '/admin/tax-return',\n ADMIN_AUDIT = '/admin/audit',\n ADMIN_USERS = '/admin/users',\n ADMIN_USER_DETAIL = '/admin/users/[uid]',\n ADMIN_FLAGS = '/admin/flags',\n // Platform-wide settings that are not a release flag (AGL-2486). The\n // free-workspace ceiling was the first of them and it opened the\n // Organizations LIST, above the table — a global lever wedged into the\n // screen for browsing individual orgs. Settings that describe the platform\n // rather than one org belong on their own tab, beside the flags.\n ADMIN_SETTINGS = '/admin/settings',\n // One submission or listed plugin in full (AGL-959): the queue index is\n // for scanning, this is where a reviewer reads the manifest, weighs the\n // verifier findings and acts.\n // A page a plugin adds to the staff area (AGL-2939), rendered by the\n // console's generic staff route. The console's own staff routes win the\n // segments they use.\n ADMIN_STAFF_PAGE = '/admin/[staffPage]',\n // Staff support-ticket queue (AGL-849): the operator side of the\n // subscriber `MANAGE_SUPPORT_TICKETS` page — every org's tickets in one place.\n ADMIN_SUPPORT = '/admin/support',\n // Do-not-contact list (AGL-1592): the intake and the queue for Privacy\n // Policy v4 §11's marketing call/text opt-out, and for \"delete the phone\n // number you hold for me\".\n ADMIN_CONTACT_SUPPRESSIONS = '/admin/contact-suppressions',\n ADMIN_EMAILS = '/admin/emails',\n // The one besigner route with no host and no org in it (AGL-749). A system\n // email belongs to the platform, not to a workspace, so it is staff-scoped\n // like the rest of `/admin/*` rather than nested under `[orgSlug]`.\n ADMIN_EMAIL_BESIGNER = '/admin/emails/[templateKey]/versions/[versionId]/besigner',\n ORG_HOME = '/[orgSlug]',\n ORG_MEDIA = '/[orgSlug]/media',\n ORG_DATA = '/[orgSlug]/data',\n /*\n * The organization's address book.\n *\n * Org-scoped rather than a section of a site, because it answers the one\n * question a site page cannot: which of the organization's sites know a\n * given person. A contact document is shared by every site in the org — one\n * human who touched two of them is one row — and until this route there was\n * no surface anywhere that showed the deduped person, so the dedupe the\n * shared address book exists for was invisible and the billing unit\n * (unique people per org) had nothing standing behind it.\n *\n * It shows identity, which sites hold the person, and consent per site. The\n * per-site CRM — notes, tags, timeline, order history — stays on the site's\n * own Contacts surface, because those are the holder's business records and\n * this is the one route designed to cross between holders.\n *\n * A LEGACY ADDRESS since AGL-2630: the page behind it redirects, permanently,\n * to the org-level CRM hub's contacts section. It stays declared so a link\n * kept from before — a bookmark, a docs page, an email — still resolves,\n * and so the redirect has a constant to be built from rather than a string.\n */\n ORG_CONTACTS = '/[orgSlug]/contacts',\n /*\n * The organization-level CRM hub (AGL-2630): the same sections the site hub\n * has — `/[orgSlug]/crm/<section>[/<recordId>]` — mounted with an ORG scope,\n * for an org-wide member who works every site's people at once. A bare\n * `/crm` lands on the first section the reader may open, as the site hub's\n * does. What this route adds over the site hub is the cross-site fact the\n * old address book carried — which sites know a person — and what it takes\n * from it is the site every create must name, since a record is always\n * captured BY a site.\n */\n ORG_CRM = '/[orgSlug]/crm',\n /*\n * The generic ORGANIZATION-level plugin route (AGL-2974): every surface an\n * extension declares in `orgNavItems` is served here, at\n * `/[orgSlug]/<href>[/<section>…]`, by\n * `app/(app)/[orgSlug]/[...pluginSlug]/page.tsx`. The org twin of\n * `HOST_PLUGIN`, and like it the one org route whose leaf segment is data:\n * every named org route in this enum wins over it.\n */\n ORG_PLUGIN = '/[orgSlug]/[pluginSlug]',\n ORG_PLUGINS = '/[orgSlug]/plugins',\n // One plugin, as installed in this workspace (AGL-1007): scope, settings\n // and permissions in one place. The segment takes EITHER identifier\n // (AGL-1010) — a listing id for a marketplace install, a registry id for a\n // first-party plugin — hence `pluginRef` rather than `listingId`.\n ORG_PLUGIN_INSTALLATION = '/[orgSlug]/plugins/[pluginRef]',\n // Org-scope marketplace (AGL-772): the single place to browse, view and\n // install marketplace items. The per-site tab it replaced, and the retired\n // `/[orgSlug]/marketplace` seller area, were removed with the rest of the\n // `marketplace` naming in AGL-975 — the word is being freed for a public\n // forum, and a redirect stub sitting on `/marketplace` would have taken the\n // one path that feature wants.\n /*\n * The marketplace's own SECTIONS are not here any more (AGL-3080). Eight\n * of them — browse, licences, upload, the four seller cards and the\n * publish form — were hand-written console routes, and became an\n * `orgNavItems` declaration whose `sections` name those segments once, in\n * the plugin. Restating them here was a second copy of a list the plugin\n * owns, and every entry had zero callers the moment the routes went.\n *\n * What stays is what the CONSOLE still builds: the surface itself, a\n * listing, a publisher, and the installed tab that the org plugins card\n * links across to. A console page linking into a plugin's surface is the\n * ordinary case; a console page enumerating that surface's sections is\n * not.\n */\n ORG_MARKETPLACE = '/[orgSlug]/marketplace',\n /*\n * Marketplace SECTIONS are routes (AGL-2501), for the reason the settings\n * and account sections are: a panel that is not open should cost nothing,\n * and the seller half of this hub reads the organization's REVENUE.\n *\n * The four seller sections are the reason the layout gates rather than the\n * rail. As tabs they were simply not rendered for a member without\n * `publishToMarketplace`; as routes each one is reachable by typing its URL,\n * so the refusal has to sit above them all rather than in the list that\n * draws them.\n *\n * Every segment here is shorter than the ten characters `createResourceUid`\n * emits, so none of them can ever shadow a real `[listingId]` sitting at the\n * same level. `upload` rather than `publish` because `publish/plugin` is\n * already a route beneath this one, and two directories contributing the\n * same segment is a tree Next cannot resolve a layout for.\n */\n ORG_MARKETPLACE_INSTALLED = '/[orgSlug]/marketplace/installed',\n ORG_MARKETPLACE_LISTING = '/[orgSlug]/marketplace/[listingId]',\n ORG_MARKETPLACE_PUBLISHER = '/[orgSlug]/marketplace/publisher/[handle]',\n // Publishing a plugin is a page, not a modal (AGL-1078): the most\n // consequential thing a publisher does now has a URL, so it can be linked,\n // reloaded, and reached from a listing to ship an update.\n ORG_SETTINGS = '/[orgSlug]/settings',\n /*\n * Settings SECTIONS are routes (AGL-2501). `HubTabs` mounted every panel —\n * `keepMounted`, with `lazy` off by default and passed by nobody — so\n * opening General also mounted the API-keys, SSO and data-export cards and\n * ran every read in them. Routes mount one page and code-split per route.\n * `ORG_SETTINGS` itself redirects to General.\n */\n ORG_SETTINGS_GENERAL = '/[orgSlug]/settings/general',\n ORG_SETTINGS_PROFILE = '/[orgSlug]/settings/profile',\n ORG_SETTINGS_PLUGINS = '/[orgSlug]/settings/plugins',\n ORG_SETTINGS_API_KEYS = '/[orgSlug]/settings/api-keys',\n ORG_SETTINGS_BRANDING = '/[orgSlug]/settings/branding',\n ORG_SETTINGS_SSO = '/[orgSlug]/settings/sso',\n ORG_SETTINGS_OWNERSHIP = '/[orgSlug]/settings/ownership',\n /**\n * Export the people a workspace holds and file a person's erasure\n * (AGL-2839) — outside the CRM, so it is there on every plan.\n */\n ORG_SETTINGS_PRIVACY = '/[orgSlug]/settings/privacy',\n // Holds & reviews (AGL-3368): everything on the workspace that was held,\n // flagged, locked or paused, its status, and the way to request a review.\n // Where every risk notice's \"Request a review\" lands (`?notice=<id>`).\n ORG_SETTINGS_HOLDS = '/[orgSlug]/settings/holds',\n ORG_SETTINGS_DELETE = '/[orgSlug]/settings/delete',\n /**\n * Billing, section by section (AGL-2501).\n *\n * `MANAGE_BILLING` is unchanged and is the PLAN section — it resolves to a\n * page inside a route group, which adds no path segment. That is what makes\n * this split free: every link in the console, every hash anchor\n * (`#addons`, `#collaborator-seats`) and Stripe's own dunning mail all point\n * here already and none of them moves. No redirect, and no `?tab=` shim to\n * carry forever.\n */\n MANAGE_BILLING = '/[orgSlug]/billing',\n MANAGE_BILLING_USAGE = '/[orgSlug]/billing/usage',\n MANAGE_BILLING_INVOICES = '/[orgSlug]/billing/invoices',\n MANAGE_BILLING_SETTINGS = '/[orgSlug]/billing/settings',\n // The ONE billing URL that carries no org (AGL-2430).\n //\n // Everything else on this table is org-scoped, which is correct for a\n // console and wrong for the only caller that cannot template a URL:\n // Stripe's dunning emails. \"Payment method updates\" takes a single static\n // link for every customer in the account — there is no `{{org}}` to\n // interpolate — so a table with no org-agnostic billing entry means those\n // emails can only ever point at a page that is not billing. Today all four\n // of them point at the marketing homepage, which is how a failed card\n // becomes a cancelled subscription with no reachable way to fix it.\n //\n // This resolves the org from the SESSION instead of from the URL: one\n // workspace goes straight through, several offer a choice, none says so.\n // `billing` is a reserved subdomain (`RESERVED_SUBDOMAINS`), so no org can\n // hold this slug and the literal segment can never shadow a real\n // `/[orgSlug]`.\n BILLING_ENTRY = '/billing',\n MANAGE_USER_SETTINGS = '/manage/user',\n /*\n * Account SECTIONS are routes (AGL-2501). `HubTabs` mounted every panel —\n * `keepMounted`, with `lazy` off by default and passed by nobody — so\n * opening Account also mounted the email-addresses, passkeys, recent\n * sign-ins, data-export and close-account cards and ran every read in them.\n * Routes mount one page and code-split per route.\n *\n * The segments are the ids the panels carried, because a transactional\n * email already in people's inboxes links `/manage/user?tab=security`:\n * `MANAGE_USER_SETTINGS` forwards each of those ids to the section that\n * holds it, and the segment matching the id is what keeps that map a\n * one-liner rather than a second naming scheme.\n */\n MANAGE_USER_ACCOUNT = '/manage/user/account',\n MANAGE_USER_EMAILS = '/manage/user/emails',\n MANAGE_USER_PROFILE = '/manage/user/profile',\n MANAGE_USER_BASIC = '/manage/user/basic',\n MANAGE_USER_SECURITY = '/manage/user/security',\n MANAGE_USER_CLOSE = '/manage/user/close',\n MANAGE_NOTIFICATIONS = '/manage/notifications',\n /*\n * The preferences, off the feed (AGL-3226).\n *\n * They lived in the header of the feed itself, which was already the\n * tallest thing on that page before per-channel and per-scope settings\n * multiplied them — and it put an edit form above the list it edits, which\n * the console does nowhere else.\n *\n * A CHILD of the feed rather than a sibling under `/manage`, because the\n * relationship is exactly that: it configures what the page above it shows.\n * That also keeps `/manage/notifications` the address in the transactional\n * mail already in people's inboxes, which keeps working and now carries a\n * button to here.\n */\n MANAGE_NOTIFICATION_SETTINGS = '/manage/notifications/settings',\n /*\n * A URL for the defect channel (AGL-2486). \"Report an issue\" was reachable\n * only by opening the account menu, so nothing outside the console could\n * send anyone to it — the repo's README pointed at GitHub issues, which we\n * do not use, and every other \"tell us about it\" surface had the same\n * problem. User-level rather than org-scoped because the dialog is: it\n * needs no workspace, no role and no paid plan.\n */\n MANAGE_REPORT_ISSUE = '/manage/report-issue',\n AUTH_SIGN_IN = '/signin',\n AUTH_SIGN_OUT = '/signout',\n AUTH_SIGN_UP = '/signup',\n AUTH_VERIFY_EMAIL = '/verify-email',\n HOST_LIST = '/[orgSlug]/hosts',\n HOST_CONTENT = '/[orgSlug]/hosts/[host]/content',\n // The collection is a PATH SEGMENT, not `?collection=` (AGL-2498).\n //\n // It was a query parameter because the manager began as one page with a\n // Select on it, and the Select was the only thing that knew which\n // collection you were looking at. That is the wrong owner: which collection\n // is open is the page's IDENTITY, not a filter applied to it. As a\n // parameter it could be dropped by any link that rebuilt the query, it\n // sorted below `?tab=` in a URL nobody could read, and — the reason this\n // moved — it left the entry beneath it with nowhere to hang: an entry is\n // addressed `collection + entry`, and half of that address cannot live in\n // the path while the other half lives in the query.\n //\n // The segment is the collection's SLUG, not its document id, and that is a\n // correction rather than a preference. Collection document ids are not\n // uniform — the seeded ones were given readable ids (`blog`, `changelog`)\n // and everything created since gets a `createResourceUid`, so routing by id\n // produced `/content/changelog` beside `/content/QgXv7lU_rG` on ONE site.\n //\n // A slug is the right key on the merits too: it is unique per host and kind\n // (claimed in a transaction by /api/hosts/collections), it is the\n // collection's own public address, and it is the thing an author recognises.\n // A document id is still ACCEPTED and rewritten to the slug form, so no\n // existing link breaks.\n //\n // `?collection=` still resolves the same way (AGL-845's DAM deep links are\n // in the wild).\n HOST_CONTENT_COLLECTION = '/[orgSlug]/hosts/[host]/content/[collectionSlug]',\n // One entry's detail page (AGL-2498). `entries` is a LITERAL segment rather\n // than `content/[collectionId]/[entryId]`, and it is load-bearing twice\n // over: it mirrors the Firestore path the entry actually lives at\n // (`collections/{c}/entries/{e}`), and it keeps the id space clear so a\n // later sibling — a collection's own settings page, say — cannot be\n // shadowed by an entry that happens to be called the same thing.\n //\n // `[entryId]` also carries the sentinel `new`, which is a draft that has no\n // document yet. Entry ids come from `createResourceUid`, so nothing real\n // can collide with it.\n CONTENT_ENTRY_DETAILS = '/[orgSlug]/hosts/[host]/content/[collectionSlug]/entries/[entryId]',\n MANAGE_TEAM = '/[orgSlug]/team',\n /*\n * Team SECTIONS are routes, not tabs (AGL-2501). A section's bundle then\n * arrives when a reader opens it and never before, the section is linkable,\n * and the active state is a fact about the URL rather than state that has to\n * be kept in sync with it. `MANAGE_TEAM` itself redirects to members.\n */\n MANAGE_TEAM_MEMBERS = '/[orgSlug]/team/members',\n MANAGE_TEAM_ROLES = '/[orgSlug]/team/roles',\n MANAGE_TEAM_ACTIVITY = '/[orgSlug]/team/activity',\n MANAGE_TEAM_MEMBER = '/[orgSlug]/team/[uid]',\n // Support is an UMBRELLA, not a page (AGL-1158). The two channels beneath\n // it are separate features that were only ever one screen: they are gated\n // differently (tickets need a first-response commitment, from Pro; the\n // forum is open to every tier including Free) and they fail independently.\n // Sharing one route made them share a blast radius — AGL-1157 was one bad\n // line in the common loader that emptied BOTH lists at once.\n //\n // `MANAGE_SUPPORT` itself stays, and stays the nav tab's href: it now\n // forwards to whichever channel the org's tier makes primary, so a\n // forum-only workspace lands on a whole forum instead of a half-empty page\n // beside a ticket card it may not use.\n MANAGE_SUPPORT = '/[orgSlug]/support',\n MANAGE_SUPPORT_TICKETS = '/[orgSlug]/support/tickets',\n MANAGE_SUPPORT_FORUM = '/[orgSlug]/support/forum',\n /*\n * The ONE support URL that carries no org (AGL-3265), and the second member\n * of the apex category `BILLING_ENTRY` opened.\n *\n * Everything a person is handed when they need help — a receipt, a system\n * email footer, a Stripe customer portal, a self-host runbook — stores ONE\n * link for every customer, with no workspace to interpolate. `/[orgSlug]`\n * cannot be templated into any of them, so the only support address those\n * surfaces could carry was one outside the console entirely.\n *\n * This resolves the workspace from the SESSION instead: one workspace goes\n * straight through, several offer a choice, none says so. It forwards to\n * `MANAGE_SUPPORT`, which is itself an umbrella — so the tier decision about\n * tickets versus the forum stays in the one place that already makes it.\n *\n * `support` is a reserved subdomain (`RESERVED_SUBDOMAINS` in\n * `host-naming.ts`), so no org can hold this slug and the literal segment\n * can never shadow a real `/[orgSlug]`. That is the same guarantee\n * `BILLING_ENTRY` rests on, and it is a precondition rather than a\n * coincidence: an apex route whose segment an org could claim would take a\n * workspace's own address away from it.\n */\n SUPPORT_ENTRY = '/support',\n HOST_DASHBOARD = '/[orgSlug]/hosts/[host]',\n // The catch-all console page a plugin's own nav items resolve against\n // (`app/(app)/[orgSlug]/hosts/[host]/[pluginSlug]/page.tsx`). Plugin slugs\n // are open-ended, so this is the one route whose leaf segment is data.\n HOST_PLUGIN = '/[orgSlug]/hosts/[host]/[pluginSlug]',\n HOST_INBOX = '/[orgSlug]/hosts/[host]/inbox',\n HOST_CONTACTS = '/[orgSlug]/hosts/[host]/contacts',\n HOST_MEDIA = '/[orgSlug]/hosts/[host]/media',\n HOST_SETUP = '/[orgSlug]/hosts/[host]/setup',\n /*\n * Setup SECTIONS are routes (AGL-2501), so an unopened one costs neither a\n * read nor a byte. `HOST_SETUP` stays the nav tab's href and redirects to\n * Basic details, honouring the `?tab=` ids these sections were deep-linked\n * by — unlike the settings and marketplace hubs, links holding those ids are\n * demonstrably in the wild and two are built in this repo.\n */\n HOST_SETUP_DETAILS = '/[orgSlug]/hosts/[host]/setup/details',\n HOST_SETUP_SEO = '/[orgSlug]/hosts/[host]/setup/seo',\n HOST_SETUP_TRACKING = '/[orgSlug]/hosts/[host]/setup/tracking',\n HOST_SETUP_THEME = '/[orgSlug]/hosts/[host]/setup/theme',\n HOST_SETUP_EMAILS = '/[orgSlug]/hosts/[host]/setup/emails',\n // Host Admin area (AGL-1014): owner/admin-only controls — per-site plugin\n // enablement and the Danger zone — out of the Setup page collaborators\n // legitimately visit.\n HOST_ADMIN = '/[orgSlug]/hosts/[host]/admin',\n /*\n * Site-admin SECTIONS are routes (AGL-2501), so an unopened one costs neither\n * a read nor a byte. `HOST_ADMIN` redirects to Plugins and still honours the\n * `?tab=` ids these sections were deep-linked by.\n */\n /*\n * The site as an OBJECT: its name in the console and the subdomain it\n * answers to. First in the rail because an address and a name are what\n * identify the thing every other section governs.\n */\n HOST_ADMIN_GENERAL = '/[orgSlug]/hosts/[host]/admin/general',\n HOST_ADMIN_PLUGINS = '/[orgSlug]/hosts/[host]/admin/plugins',\n /*\n * One plugin, as installed on ONE site (AGL-428, AGL-1014) — the\n * site-scoped twin of `ORG_PLUGIN_INSTALLATION`, reached by clicking a row\n * on the site's plugin\n * list exactly as the workspace list opens its own detail page. The segment\n * is `[pluginRef]` and accepts either identifier for the same reason the\n * workspace route does (AGL-1010): a marketplace install is keyed by listing\n * id, a first-party plugin by its registry id, and an admin does not think\n * of the two as different things.\n */\n HOST_ADMIN_PLUGIN = '/[orgSlug]/hosts/[host]/admin/plugins/[pluginRef]',\n HOST_ADMIN_DOMAIN = '/[orgSlug]/hosts/[host]/admin/domain',\n HOST_ADMIN_SECURITY = '/[orgSlug]/hosts/[host]/admin/security',\n /*\n * The designed screen behind each status code, and the switch that shows the\n * 503 one at every address at once (AGL-3178).\n *\n * Beside Security because both answer what a visitor's browser is handed on\n * a page the site did not design for them, and above Activity because it is\n * a control rather than a record.\n */\n HOST_ADMIN_ERROR_PAGES = '/[orgSlug]/hosts/[host]/admin/error-pages',\n HOST_ADMIN_ACTIVITY = '/[orgSlug]/hosts/[host]/admin/activity',\n /*\n * Backup, restore and publishing the site as a template — moving the whole\n * site somewhere else, or bringing it back. Its own section rather than the\n * Danger zone: restore overwrites, but export and publishing a template do\n * not, and filing all three under a destructive heading mislabels two of\n * them.\n */\n HOST_ADMIN_BACKUP = '/[orgSlug]/hosts/[host]/admin/backup',\n HOST_ADMIN_DANGER = '/[orgSlug]/hosts/[host]/admin/danger',\n HOST_THEME = '/[orgSlug]/hosts/[host]/theme',\n /*\n * The automation section: Workflows, Actions and Webhooks under one parent.\n * The parent is named for what the three have in common, so no tab shares a\n * name with the section that contains it.\n */\n HOST_AUTOMATION = '/[orgSlug]/hosts/[host]/automation',\n /**\n * The address the automation section answered to before it had a name of\n * its own. Nothing links here — `HOST_AUTOMATION` is what the nav, the\n * search results and the activity feed build — but the console redirects it\n * permanently, so a held bookmark still lands on the section (and keeps its\n * `?tab=`, which never changed).\n *\n * Kept on the table rather than as a bare literal in `next.config.js`\n * because the redirect and the address it serves are one fact:\n * `automation-section.spec.ts` reads this entry and fails if the rule for\n * it goes missing.\n */\n HOST_WORKFLOWS = '/[orgSlug]/hosts/[host]/workflows',\n HOST_DATA = '/[orgSlug]/hosts/[host]/data',\n HOST_LOGIC = '/[orgSlug]/hosts/[host]/logic',\n HOST_PRODUCTS = '/[orgSlug]/hosts/[host]/products',\n HOST_COMPONENTS = '/[orgSlug]/hosts/[host]/components',\n // Component detail (AGL-693): the listing links here, and the besigner is\n // reached from here — matching SCREEN_DETAILS rather than jumping a row\n // straight into the editor.\n COMPONENT_DETAILS = '/[orgSlug]/hosts/[host]/components/[componentId]',\n HOST_FORMS = '/[orgSlug]/hosts/[host]/forms',\n // A form is a designable document, so its surface is the component one:\n // a list, a detail page that owns the versions, and the besigner reached\n // from there. Deliberately NOT a row that jumps into the editor — a form's\n // routing and consent field are edited on the detail page, and those decide\n // what the design is then checked against.\n FORM_DETAILS = '/[orgSlug]/hosts/[host]/forms/[formId]',\n HOST_TEMPLATES = '/[orgSlug]/hosts/[host]/templates',\n // Template detail (AGL-694), the counterpart to COMPONENT_DETAILS. Note\n // TEMPLATE_BESIGNER carries no versionId — templates version but never\n // publish, so there is no \"current\" pointer to route through.\n TEMPLATE_DETAILS = '/[orgSlug]/hosts/[host]/templates/[templateId]',\n HOST_MARKETING = '/[orgSlug]/hosts/[host]/marketing',\n HOST_BOOKINGS = '/[orgSlug]/hosts/[host]/bookings',\n // Events now come from the events-calendar plugin, served by the generic\n // `[orgSlug]/hosts/[host]/[pluginSlug]` route (AGL-394).\n HOST_REDIRECTS = '/[orgSlug]/hosts/[host]/redirects',\n HOST_USERS = '/[orgSlug]/hosts/[host]/users',\n HOST_ANALYTICS = '/[orgSlug]/hosts/[host]/analytics',\n COMPONENT_BESIGNER = '/[orgSlug]/hosts/[host]/components/[componentId]/versions/[versionId]/besigner',\n FORM_BESIGNER = '/[orgSlug]/hosts/[host]/forms/[formId]/versions/[versionId]/besigner',\n TEMPLATE_BESIGNER = '/[orgSlug]/hosts/[host]/templates/[templateId]/besigner',\n LAYOUT_BESIGNER = '/[orgSlug]/hosts/[host]/layouts/[layoutId]/versions/[versionId]/besigner',\n // Draft preview for every besigner document kind (AGL-1203). Screens had\n // the only preview route; components, layouts and templates showed a\n // Preview button that did nothing. Each renders the same localStorage\n // snapshot through the site renderer, so they work on localhost with no\n // deployment. TEMPLATE_PREVIEW carries no versionId, matching\n // TEMPLATE_BESIGNER.\n COMPONENT_PREVIEW = '/[orgSlug]/hosts/[host]/components/[componentId]/versions/[versionId]/preview',\n FORM_PREVIEW = '/[orgSlug]/hosts/[host]/forms/[formId]/versions/[versionId]/preview',\n TEMPLATE_PREVIEW = '/[orgSlug]/hosts/[host]/templates/[templateId]/preview',\n LAYOUT_PREVIEW = '/[orgSlug]/hosts/[host]/layouts/[layoutId]/versions/[versionId]/preview',\n // The list sits at the bare path, like HOST_COMPONENTS. It used to be\n // `/layouts/list`: the pages router had a `layouts/index.tsx` that\n // redirected to `layouts/list.tsx`, and the App Router migration carried\n // the pair over verbatim. Components, added later, had no such legacy and\n // put its list at the bare path — which is the shape we want everywhere.\n // `/layouts/list` still serves the same page so old links survive — an\n // alias, not a redirect, because the bare path used to answer a cached 308\n // pointing at `/list` and redirecting back would loop. See the comment in\n // `app/(app)/[orgSlug]/hosts/[host]/screens/list/page.tsx`.\n HOST_LAYOUTS = '/[orgSlug]/hosts/[host]/layouts',\n // Layout detail (AGL-695), completing the list → detail → besigner shape\n // across screens, components, templates and layouts.\n LAYOUT_DETAILS = '/[orgSlug]/hosts/[host]/layouts/[layoutId]',\n SCREEN_BESIGNER = '/[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/besigner',\n // Per-site email besigner (AGL-770): a site owner designs a transactional\n // email their site sends. The host-scoped sibling of the staff system-email\n // editor (ADMIN_EMAIL_BESIGNER).\n HOST_EMAIL_BESIGNER = '/[orgSlug]/hosts/[host]/emails/[templateKey]/versions/[versionId]/besigner',\n SCREEN_DETAILS = '/[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/view',\n SCREEN_PREVIEW = '/[orgSlug]/hosts/[host]/screens/[screenId]/versions/[versionId]/preview',\n // Bare path, matching HOST_LAYOUTS and HOST_COMPONENTS — see the note on\n // HOST_LAYOUTS. `/screens/list` still serves the same page as an alias.\n HOST_SCREENS = '/[orgSlug]/hosts/[host]/screens',\n}\n\n/**\n * Params required to build each {@link Route}.\n *\n * Deliberately NOT extended from an index signature (AGL-685). It used to be\n * `extends Record<keyof any, any>`, which meant any `Route` missing an entry\n * here silently typed its payload as `any` — `Route.HOST_SETUP` was one of\n * them, and `buildRoute(Route.HOST_SETUP, { hostId })` compiled cleanly while\n * emitting `/<orgSlug?>/hosts/<host?>/setup`. Without the index signature a\n * new `Route` with no entry is a compile error, which is the whole point of\n * the payload being required in the first place.\n */\nexport interface RoutePayload {\n [Route.AUTH_SIGN_UP]: undefined\n [Route.AUTH_SIGN_IN]: undefined\n [Route.AUTH_SIGN_OUT]: undefined\n [Route.AUTH_VERIFY_EMAIL]: undefined\n [Route.SCREEN_BESIGNER]: {\n orgSlug: string\n host: string\n screenId: string\n versionId: string\n }\n [Route.ORG_HOME]: { orgSlug: string }\n [Route.HOST_DASHBOARD]: { orgSlug: string; host: string }\n [Route.ADMIN_ORGS]: undefined\n [Route.ADMIN_ORG_DETAIL]: { orgId: string }\n [Route.ADMIN_ORG_HOST_DETAIL]: { orgId: string; hostId: string }\n [Route.ADMIN_OVERVIEW]: undefined\n [Route.ADMIN_COUPONS]: undefined\n [Route.ADMIN_LOCKDOWN]: undefined\n [Route.ADMIN_MEDIA_QUARANTINE]: undefined\n [Route.ADMIN_ABUSE_REPORTS]: undefined\n [Route.ADMIN_HEALTH]: undefined\n [Route.ADMIN_MAINTENANCE]: undefined\n [Route.ADMIN_REVENUE]: undefined\n [Route.ADMIN_MARGIN_UTILIZATION]: undefined\n [Route.ADMIN_TAX_RETURN]: undefined\n [Route.ADMIN_AUDIT]: undefined\n [Route.ADMIN_USERS]: undefined\n [Route.ADMIN_USER_DETAIL]: { uid: string }\n [Route.ADMIN_FLAGS]: undefined\n [Route.ADMIN_SETTINGS]: undefined\n [Route.ADMIN_STAFF_PAGE]: { staffPage: string }\n [Route.ADMIN_SUPPORT]: undefined\n [Route.ADMIN_CONTACT_SUPPRESSIONS]: undefined\n [Route.ADMIN_EMAILS]: undefined\n [Route.ADMIN_EMAIL_BESIGNER]: { templateKey: string; versionId: string }\n [Route.HOST_EMAIL_BESIGNER]: {\n orgSlug: string\n host: string\n templateKey: string\n versionId: string\n }\n [Route.ORG_MEDIA]: { orgSlug: string }\n [Route.ORG_DATA]: { orgSlug: string }\n [Route.ORG_CONTACTS]: { orgSlug: string }\n [Route.ORG_CRM]: { orgSlug: string }\n [Route.ORG_PLUGIN]: { orgSlug: string; pluginSlug: string }\n [Route.ORG_PLUGINS]: { orgSlug: string }\n [Route.ORG_PLUGIN_INSTALLATION]: { orgSlug: string; pluginRef: string }\n [Route.ORG_MARKETPLACE]: { orgSlug: string }\n [Route.ORG_MARKETPLACE_INSTALLED]: { orgSlug: string }\n [Route.ORG_MARKETPLACE_LISTING]: { orgSlug: string; listingId: string }\n [Route.ORG_MARKETPLACE_PUBLISHER]: { orgSlug: string; handle: string }\n [Route.MANAGE_USER_SETTINGS]: undefined\n [Route.MANAGE_USER_ACCOUNT]: undefined\n [Route.MANAGE_USER_EMAILS]: undefined\n [Route.MANAGE_USER_PROFILE]: undefined\n [Route.MANAGE_USER_BASIC]: undefined\n [Route.MANAGE_USER_SECURITY]: undefined\n [Route.MANAGE_USER_CLOSE]: undefined\n [Route.MANAGE_NOTIFICATIONS]: undefined\n [Route.MANAGE_NOTIFICATION_SETTINGS]: undefined\n [Route.MANAGE_REPORT_ISSUE]: undefined\n [Route.ORG_SETTINGS]: { orgSlug: string }\n [Route.ORG_SETTINGS_GENERAL]: { orgSlug: string }\n [Route.ORG_SETTINGS_PROFILE]: { orgSlug: string }\n [Route.ORG_SETTINGS_PLUGINS]: { orgSlug: string }\n [Route.ORG_SETTINGS_API_KEYS]: { orgSlug: string }\n [Route.ORG_SETTINGS_BRANDING]: { orgSlug: string }\n [Route.ORG_SETTINGS_SSO]: { orgSlug: string }\n [Route.ORG_SETTINGS_OWNERSHIP]: { orgSlug: string }\n [Route.ORG_SETTINGS_PRIVACY]: { orgSlug: string }\n [Route.ORG_SETTINGS_HOLDS]: { orgSlug: string }\n [Route.ORG_SETTINGS_DELETE]: { orgSlug: string }\n [Route.HOST_LIST]: { orgSlug: string }\n [Route.HOST_CONTENT]: { orgSlug: string; host: string }\n [Route.HOST_CONTENT_COLLECTION]: {\n orgSlug: string\n host: string\n /** The collection's slug — see the route. A document id also resolves. */\n collectionSlug: string\n }\n [Route.CONTENT_ENTRY_DETAILS]: {\n orgSlug: string\n host: string\n collectionSlug: string\n /**\n * An entry id, or the literal `new` for a draft with no document yet.\n *\n * An ID here while the collection above is a SLUG, deliberately. An entry\n * slug is unique only WITHIN its collection and it is editable on the\n * detail page — so routing by it would move the console address out from\n * under someone in the middle of typing in the slug field, and a draft has\n * no slug at all to route by. The entry id is also what every activity\n * record and the DAM's \"Used on\" list already carry.\n */\n entryId: string\n }\n [Route.MANAGE_TEAM]: { orgSlug: string }\n [Route.MANAGE_TEAM_MEMBERS]: { orgSlug: string }\n [Route.MANAGE_TEAM_ROLES]: { orgSlug: string }\n [Route.MANAGE_TEAM_ACTIVITY]: { orgSlug: string }\n [Route.MANAGE_TEAM_MEMBER]: { orgSlug: string; uid: string }\n [Route.MANAGE_SUPPORT]: { orgSlug: string }\n [Route.MANAGE_SUPPORT_TICKETS]: { orgSlug: string }\n [Route.MANAGE_SUPPORT_FORUM]: { orgSlug: string }\n [Route.MANAGE_BILLING]: { orgSlug: string }\n [Route.MANAGE_BILLING_USAGE]: { orgSlug: string }\n [Route.MANAGE_BILLING_INVOICES]: { orgSlug: string }\n [Route.MANAGE_BILLING_SETTINGS]: { orgSlug: string }\n [Route.BILLING_ENTRY]: undefined\n [Route.SUPPORT_ENTRY]: undefined\n [Route.HOST_INBOX]: { orgSlug: string; host: string }\n [Route.HOST_CONTACTS]: { orgSlug: string; host: string }\n [Route.HOST_SETUP]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_DETAILS]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_SEO]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_TRACKING]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_THEME]: { orgSlug: string; host: string }\n [Route.HOST_SETUP_EMAILS]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_GENERAL]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_PLUGINS]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_PLUGIN]: { orgSlug: string; host: string; pluginRef: string }\n [Route.HOST_ADMIN_DOMAIN]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_SECURITY]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_ERROR_PAGES]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_ACTIVITY]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_BACKUP]: { orgSlug: string; host: string }\n [Route.HOST_ADMIN_DANGER]: { orgSlug: string; host: string }\n [Route.HOST_PLUGIN]: { orgSlug: string; host: string; pluginSlug: string }\n [Route.HOST_MEDIA]: { orgSlug: string; host: string }\n [Route.HOST_THEME]: { orgSlug: string; host: string }\n [Route.HOST_AUTOMATION]: { orgSlug: string; host: string }\n [Route.HOST_WORKFLOWS]: { orgSlug: string; host: string }\n [Route.HOST_DATA]: { orgSlug: string; host: string }\n [Route.HOST_LOGIC]: { orgSlug: string; host: string }\n [Route.HOST_PRODUCTS]: { orgSlug: string; host: string }\n [Route.HOST_COMPONENTS]: { orgSlug: string; host: string }\n [Route.COMPONENT_DETAILS]: {\n orgSlug: string\n host: string\n componentId: string\n }\n [Route.HOST_FORMS]: { orgSlug: string; host: string }\n [Route.FORM_DETAILS]: {\n orgSlug: string\n host: string\n formId: string\n }\n [Route.HOST_TEMPLATES]: { orgSlug: string; host: string }\n [Route.TEMPLATE_DETAILS]: {\n orgSlug: string\n host: string\n templateId: string\n }\n [Route.HOST_MARKETING]: { orgSlug: string; host: string }\n [Route.HOST_BOOKINGS]: { orgSlug: string; host: string }\n [Route.HOST_REDIRECTS]: { orgSlug: string; host: string }\n [Route.HOST_USERS]: { orgSlug: string; host: string }\n [Route.HOST_ANALYTICS]: { orgSlug: string; host: string }\n [Route.COMPONENT_BESIGNER]: {\n orgSlug: string\n host: string\n componentId: string\n versionId: string\n }\n [Route.FORM_BESIGNER]: {\n orgSlug: string\n host: string\n formId: string\n versionId: string\n }\n [Route.TEMPLATE_BESIGNER]: {\n orgSlug: string\n host: string\n templateId: string\n }\n [Route.LAYOUT_BESIGNER]: {\n orgSlug: string\n host: string\n layoutId: string\n versionId: string\n }\n [Route.COMPONENT_PREVIEW]: {\n orgSlug: string\n host: string\n componentId: string\n versionId: string\n }\n [Route.FORM_PREVIEW]: {\n orgSlug: string\n host: string\n formId: string\n versionId: string\n }\n [Route.TEMPLATE_PREVIEW]: {\n orgSlug: string\n host: string\n templateId: string\n }\n [Route.LAYOUT_PREVIEW]: {\n orgSlug: string\n host: string\n layoutId: string\n versionId: string\n }\n [Route.HOST_LAYOUTS]: { orgSlug: string; host: string }\n [Route.LAYOUT_DETAILS]: { orgSlug: string; host: string; layoutId: string }\n [Route.SCREEN_DETAILS]: {\n orgSlug: string\n host: string\n screenId: string\n versionId: string\n }\n [Route.SCREEN_PREVIEW]: {\n orgSlug: string\n host: string\n screenId: string\n versionId: string\n }\n [Route.HOST_SCREENS]: { orgSlug: string; host: string }\n}\n\nexport const routeReplacePattern = /\\[([^\\]]+)\\]/g\n\n/**\n * Builds a concrete path from a {@link Route} template. The payload is\n * REQUIRED whenever the route declares params (e.g. `orgSlug`, `hostId`) and\n * omitted only for param-less routes — so a forgotten `orgSlug` is a compile\n * error, not a `/<orgSlug?>/…` link that breaks at runtime (AGL-621).\n */\nexport function buildRoute<Tmpl extends Route>(\n template: Tmpl,\n ...[payload]: RoutePayload[Tmpl] extends undefined\n ? [payload?: undefined]\n : [payload: RoutePayload[Tmpl]]\n) {\n return template.replace(routeReplacePattern, (match, key) => {\n const value = (payload as Record<string, unknown> | undefined)?.[key]\n return value != null ? String(value) : `<${key}?>`\n })\n}\n"],"names":["Route","routeReplacePattern","buildRoute","template","payload","replace","match","key","value","String"],"mappings":"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"}
@@ -179,6 +179,13 @@ export interface ImportChunkResult<S extends ImportSkippedRow<string>> {
179
179
  dropped: Record<string, number>;
180
180
  /** Owner addresses that matched no member of the organization. */
181
181
  ownersUnresolved: string[];
182
+ /**
183
+ * Of the rows stored, how many carry an address whose domain has no mail
184
+ * server (AGL-3328) — records that now read "Would bounce". Absent for a
185
+ * collection whose rows carry no address, and for a request whose check
186
+ * did not answer in time.
187
+ */
188
+ noMailServer?: number;
182
189
  }
183
190
  /** An empty tally, for the drawer to fold chunk results into. */
184
191
  export declare function emptyImportResult<S extends ImportSkippedRow<string>>(): ImportChunkResult<S>;
@@ -190,3 +197,8 @@ export declare function emptyImportResult<S extends ImportSkippedRow<string>>():
190
197
  * is what the download of skipped rows has to look up.
191
198
  */
192
199
  export declare function mergeImportResults<S extends ImportSkippedRow<string>>(total: ImportChunkResult<S>, chunk: Partial<ImportChunkResult<S>>, offset?: number): ImportChunkResult<S>;
200
+ /**
201
+ * What an import's result says about the addresses that would bounce
202
+ * (AGL-3328), or `null` when none does.
203
+ */
204
+ export declare function importNoMailServerSentence(count: number | null | undefined): string | null;
@@ -250,13 +250,13 @@ export function guessImportMapping(columns, fields, aliasKeys, customFields = []
250
250
  * comes back as its position in the FILE rather than in the request — which
251
251
  * is what the download of skipped rows has to look up.
252
252
  */ export function mergeImportResults(total, chunk, offset = 0) {
253
- var _chunk_dropped, _chunk_received, _chunk_created, _chunk_merged, _chunk_skipped, _chunk_ownersUnresolved;
253
+ var _chunk_dropped, _chunk_received, _chunk_created, _chunk_merged, _chunk_skipped, _chunk_ownersUnresolved, _total_noMailServer, _chunk_noMailServer;
254
254
  const dropped = _extends({}, total.dropped);
255
255
  for (const [field, count] of Object.entries((_chunk_dropped = chunk.dropped) != null ? _chunk_dropped : {})){
256
256
  var _dropped_field;
257
257
  dropped[field] = ((_dropped_field = dropped[field]) != null ? _dropped_field : 0) + Number(count != null ? count : 0);
258
258
  }
259
- return {
259
+ return _extends({
260
260
  received: total.received + Number((_chunk_received = chunk.received) != null ? _chunk_received : 0),
261
261
  created: total.created + Number((_chunk_created = chunk.created) != null ? _chunk_created : 0),
262
262
  merged: total.merged + Number((_chunk_merged = chunk.merged) != null ? _chunk_merged : 0),
@@ -273,7 +273,17 @@ export function guessImportMapping(columns, fields, aliasKeys, customFields = []
273
273
  ...(_chunk_ownersUnresolved = chunk.ownersUnresolved) != null ? _chunk_ownersUnresolved : []
274
274
  ])
275
275
  ]
276
- };
276
+ }, total.noMailServer !== undefined || chunk.noMailServer !== undefined ? {
277
+ noMailServer: Number((_total_noMailServer = total.noMailServer) != null ? _total_noMailServer : 0) + Number((_chunk_noMailServer = chunk.noMailServer) != null ? _chunk_noMailServer : 0)
278
+ } : {});
279
+ }
280
+ /**
281
+ * What an import's result says about the addresses that would bounce
282
+ * (AGL-3328), or `null` when none does.
283
+ */ export function importNoMailServerSentence(count) {
284
+ const n = Math.max(0, Math.floor(Number(count) || 0));
285
+ if (!n) return null;
286
+ return n === 1 ? '1 address has no mail server. Its record reads "Would bounce", and campaigns and sequences skip it.' : `${n.toLocaleString('en-US')} addresses have no mail server. Their records read "Would bounce", ` + 'and campaigns and sequences skip them.';
277
287
  }
278
288
 
279
289
  //# sourceMappingURL=csv-import.js.map