@aglyn/plugins-workflows 1.0.0-beta.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +52 -0
- package/package.json +53 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +19 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/automation-step-fields.component.d.ts +74 -0
- package/src/lib/components/automation-step-fields.component.js +1210 -0
- package/src/lib/components/automation-step-fields.component.js.map +1 -0
- package/src/lib/components/host-actions-card.component.d.ts +15 -0
- package/src/lib/components/host-actions-card.component.js +1229 -0
- package/src/lib/components/host-actions-card.component.js.map +1 -0
- package/src/lib/components/host-activity-card.component.d.ts +84 -0
- package/src/lib/components/host-activity-card.component.js +280 -0
- package/src/lib/components/host-activity-card.component.js.map +1 -0
- package/src/lib/components/host-run-history-card.component.d.ts +40 -0
- package/src/lib/components/host-run-history-card.component.js +284 -0
- package/src/lib/components/host-run-history-card.component.js.map +1 -0
- package/src/lib/components/host-webhooks-card.component.d.ts +15 -0
- package/src/lib/components/host-webhooks-card.component.js +421 -0
- package/src/lib/components/host-webhooks-card.component.js.map +1 -0
- package/src/lib/components/host-workflows-card.component.d.ts +18 -0
- package/src/lib/components/host-workflows-card.component.js +952 -0
- package/src/lib/components/host-workflows-card.component.js.map +1 -0
- package/src/lib/components/run-quota-line.component.d.ts +27 -0
- package/src/lib/components/run-quota-line.component.js +72 -0
- package/src/lib/components/run-quota-line.component.js.map +1 -0
- package/src/lib/components/use-automation-step-pickers.d.ts +27 -0
- package/src/lib/components/use-automation-step-pickers.js +205 -0
- package/src/lib/components/use-automation-step-pickers.js.map +1 -0
- package/src/lib/components/workflow-zones.d.ts +36 -0
- package/src/lib/components/workflow-zones.js +19 -0
- package/src/lib/components/workflow-zones.js.map +1 -0
- package/src/lib/components/workflows-console-page.d.ts +25 -0
- package/src/lib/components/workflows-console-page.js +112 -0
- package/src/lib/components/workflows-console-page.js.map +1 -0
- package/src/lib/components/workflows-console-sections.d.ts +34 -0
- package/src/lib/components/workflows-console-sections.js +50 -0
- package/src/lib/components/workflows-console-sections.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/declarations.server.d.ts +41 -0
- package/src/lib/declarations.server.js +54 -0
- package/src/lib/declarations.server.js.map +1 -0
- package/src/lib/engine/crm-action-steps.d.ts +129 -0
- package/src/lib/engine/crm-action-steps.js +432 -0
- package/src/lib/engine/crm-action-steps.js.map +1 -0
- package/src/lib/engine/flow-enrollments.d.ts +264 -0
- package/src/lib/engine/flow-enrollments.js +386 -0
- package/src/lib/engine/flow-enrollments.js.map +1 -0
- package/src/lib/engine/run-event-actions.d.ts +232 -0
- package/src/lib/engine/run-event-actions.js +1392 -0
- package/src/lib/engine/run-event-actions.js.map +1 -0
- package/src/lib/engine/run-event-automations.d.ts +28 -0
- package/src/lib/engine/run-event-automations.js +37 -0
- package/src/lib/engine/run-event-automations.js.map +1 -0
- package/src/lib/engine/run-event-workflows.d.ts +75 -0
- package/src/lib/engine/run-event-workflows.js +311 -0
- package/src/lib/engine/run-event-workflows.js.map +1 -0
- package/src/lib/engine/workflow-steps.d.ts +107 -0
- package/src/lib/engine/workflow-steps.js +124 -0
- package/src/lib/engine/workflow-steps.js.map +1 -0
- package/src/lib/plugin.d.ts +33 -0
- package/src/lib/plugin.js +94 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server-automation-drafts.d.ts +90 -0
- package/src/lib/server-automation-drafts.js +429 -0
- package/src/lib/server-automation-drafts.js.map +1 -0
- package/src/lib/server-console.d.ts +27 -0
- package/src/lib/server-console.js +35 -0
- package/src/lib/server-console.js.map +1 -0
- package/src/lib/server.d.ts +29 -0
- package/src/lib/server.js +298 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-actions-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n ACTION_MAX_CONDITIONS,\n ACTION_MAX_STEPS,\n type AglynOrgBilling,\n checkEntitlement,\n createResourceUid,\n CRM_ACTION_RECIPES,\n type CrmActionRecipe,\n type CrmActionRecipeId,\n ELEMENT_SCOPED_SITE_EVENTS,\n HOST_ACTION_STEP_LABELS,\n HOST_EVENT_TYPES,\n type HostAction,\n hostActionDocument,\n hostActionRecipeId,\n type HostActionStepType,\n hostEventLabel,\n hostEventPayloadHint,\n isSiteEventType,\n normalizeTriggerConditions,\n pluginDocsHelp,\n SITE_EVENT_TYPES,\n type TriggerCombinator,\n type TriggerConditionOp,\n validateHostAction,\n} from '@aglyn/aglyn'\nimport {\n automationPlaceholders,\n describeAutomationPlaceholder,\n} from '@aglyn/aglyn/app-utils/automation-placeholders'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport type { ConsoleAutomationTarget } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Timestamp } from '@aglyn/shared-util-timestamp'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n ListItemText,\n Menu,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n doc,\n setDoc,\n updateDoc,\n} from 'firebase/firestore'\nimport { useCallback, useMemo, useRef, useState } from 'react'\nimport {\n ceilingedWindow,\n collectionCeiling,\n useFirestore,\n useFirestoreCollection,\n useHostResourceApi,\n writeGuardedBySeed,\n} from '@aglyn/tenant-feature-instance'\nimport {\n AutomationStepFields,\n type AutomationStepKind,\n defaultStep,\n placeholderState,\n} from './automation-step-fields.component'\nimport HostRunHistoryCard from './host-run-history-card.component'\nimport {\n EDITOR_OPTION_CEILING,\n useAutomationStepPickers,\n} from './use-automation-step-pickers'\n\nconst CUSTOM_EVENT_VALUE = '__custom__'\n\n/**\n * How many action documents the card reads.\n *\n * A CEILING, not a page size — see the query, which explains why this one\n * collection cannot be sliced by the server without making the count beneath\n * the list a per-page number.\n *\n * `ACTIONS_MAX_PER_HOST` is 500, so the ceiling really can bite and the probe\n * beside it is what says when it has.\n */\nconst ACTION_CEILING = 100\n\n/** One editable condition row (AGL-565); a lone row with an empty op\n * means \"always run\" and clears the stored conditions. */\ninterface ConditionRowDraft {\n op: '' | TriggerConditionOp\n field: string\n value: string\n}\n\nconst EMPTY_CONDITION_ROW: ConditionRowDraft = { op: '', field: '', value: '' }\n\ninterface ActionDraft extends HostAction {\n id: string | null\n /** Raw custom-event text when the trigger select is on \"custom\". */\n customEvent: string\n // Structured payload conditions (AGL-557), chainable with AND/OR\n // (AGL-565) — held as rows like the steps list; legacy\n // single-`condition` docs hydrate through normalizeTriggerConditions.\n conditionRows: ConditionRowDraft[]\n conditionCombinator: TriggerCombinator\n /**\n * The recipe this draft started from (AGL-2626), so the editor can say\n * so, and what Save writes back as the action's stamp (AGL-2639): null\n * for an action begun blank, and `undefined` for a stored action from\n * before the stamp existed — which Save leaves as it found it, so an\n * edit never turns \"unknown\" into \"no recipe\".\n */\n recipe: CrmActionRecipeId | null | undefined\n}\n\n\n/** Every Actions step, as the builder's \"Do\" picker offers it. */\nconst ACTION_STEP_KINDS: AutomationStepKind[] = Object.entries(\n HOST_ACTION_STEP_LABELS,\n).map(([value, label]) => ({ value, label }))\n\n\n/**\n * A stored action — or one a recipe just built — as the editor holds it.\n *\n * One conversion for both doors, because they must agree: an action that\n * round-trips through Edit and one a recipe hands over are the same shape\n * to the editor, and a field the Edit path hydrated that the recipe path\n * forgot would be a recipe whose conditions vanished on open. Site events\n * are first-class (AGL-256/266): their selector/threshold/path config is\n * kept through an edit. Structured conditions (AGL-557; chained AGL-565)\n * become rows; a legacy single-condition doc normalizes to one row.\n */\nfunction draftFromAction(\n action: Record<string, any>,\n id: string | null,\n): ActionDraft {\n const builtIn =\n HOST_EVENT_TYPES.includes(action.trigger?.event) ||\n isSiteEventType(String(action.trigger?.event ?? ''))\n const rows = normalizeTriggerConditions(action.trigger).map(\n (condition): ConditionRowDraft => ({\n op: condition.op ?? '',\n field: condition.field ?? '',\n value: condition.value ?? '',\n }),\n )\n return {\n id,\n name: action.name ?? '',\n trigger: {\n event: builtIn ? action.trigger.event : CUSTOM_EVENT_VALUE,\n filter: action.trigger?.filter ?? '',\n selector: action.trigger?.selector ?? '',\n threshold: action.trigger?.threshold,\n pathPattern: action.trigger?.pathPattern ?? '',\n oncePerVisitor: action.trigger?.oncePerVisitor === true,\n oncePerSession: action.trigger?.oncePerSession === true,\n everyTime: action.trigger?.everyTime === true,\n ...(Number(action.trigger?.cooldownMinutes) >= 1\n ? { cooldownMinutes: Number(action.trigger?.cooldownMinutes) }\n : {}),\n },\n steps: action.steps ?? [],\n enabled: action.enabled !== false,\n customEvent: builtIn ? '' : (action.trigger?.event ?? ''),\n conditionRows: rows.length ? rows : [EMPTY_CONDITION_ROW],\n conditionCombinator: action.trigger?.combinator === 'or' ? 'or' : 'and',\n // A recipe stamps what it builds, and a stored action carries what it\n // was saved with — one reader for both doors, unknown kept unknown.\n recipe: hostActionRecipeId(action),\n }\n}\n\n/** A trigger bound to one element, which makes the row an interaction. */\nconst LEAF_SELECTOR = /^\\[data-aglyn=\"leaf:.+\"\\]$/\n\n/**\n * Actions builder (AGL-148): HubSpot-style enrollment — trigger event →\n * optional filter → ordered steps (run workflow, site alert, custom\n * event, dataset append). Runs server-side via runEventActions; paid\n * feature (`actions` flag, Pro+, `actionRunsPerMonth` metered).\n */\nexport function HostActionsCard(props: {\n hostId: string\n org?: Partial<AglynOrgBilling>\n}) {\n const { hostId, org } = props\n // Org-shared data root (AGL-237). Null until the org lookup settles\n // (AGL-1061), and for a host with no owning org — the pre-migration host\n // path is gone (AGL-1050), so the dataset and list pickers stay empty\n // instead of offering rows from a path nothing else reads.\n const firestore = useFirestore()\n // Action creation is server-owned since AGL-2266 (the cap); every other\n // action write on this surface stays client-direct.\n const createResource = useHostResourceApi()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n /**\n * The shell's zone renderer, for what other plugins add to automations\n * (AGL-2919); `null` outside the console shell, where there is no workspace\n * to gate on.\n */\n const ExtensionZone = useConsoleWidgetSlot()\n\n const {\n data: actionDocs,\n status: actionsStatus,\n /**\n * The action rows this card's editor is seeded from are unconfirmed by\n * the server (AGL-1358). Editing an action copies the whole stored row\n * into `draft` — name, trigger and every step — and writes all of it\n * back, so `merge: true` protects nothing and a cached seed reverts a\n * colleague's newer version of the steps nobody here touched.\n *\n * The same shape the interaction builder guards (AGL-1066); this is its\n * card twin, and it was the one that never got the guard.\n */\n fromCache: actionsFromCache,\n } = useFirestoreCollection<any>(\n /*\n * ORDERED AND CEILINGED, deliberately not paged by the query (AGL-2501) —\n * the same decision as the workflows card beside it.\n *\n * `limit(100)` alone is answered in DOCUMENT-ID order, so the window was a\n * pseudo-random hundred that the `localeCompare` below arranged\n * alphabetically, and nothing said the list was bounded.\n *\n * `collectionCeiling` does not change WHICH hundred — document-id order is\n * what the bare cap already returned. What it changes is that the order is\n * NAMED, so the obvious next edit is caught: ordering on `name` would HIDE\n * every action written without one rather than mis-sorting the list, and\n * `/api/hosts/resources` validates no field for presence while\n * `IMPORTABLE_FIELDS.actions` copies one only if the export carried it.\n *\n * The QUERY is not paged because this collection holds TWO audiences. A\n * row whose trigger names a leaf selector is an element interaction, which\n * belongs to its document and is reported below as a count rather than\n * listed; the rest are the site's actions. Both partitions are derived\n * from the rows in hand, so a server page would make \"3 interactions are\n * set up on their own elements\" mean \"3 on this page\" — a count that is a\n * window length, which is the defect this sweep keeps finding rather than\n * a shape it should add.\n */\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'actions'),\n ACTION_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: readActions, truncated: actionsTruncated } =\n ceilingedWindow<any>(actionDocs, ACTION_CEILING)\n const [draft, setDraft] = useState<ActionDraft | null>(null)\n /**\n * The editor has been opened at least once in this session.\n *\n * A LATCH rather than `draft` itself, because the pickers below key their\n * listeners on it. Tracking the dialog would tear those subscriptions down\n * on Cancel and pay for them again on the next Edit, so a merchant working\n * through ten actions would buy the same six windows ten times — worse than\n * the mount-time read this replaces. Latched, a reader who never edits pays\n * nothing and a reader who edits pays once.\n */\n const [editorOpened, setEditorOpened] = useState(false)\n if (draft && !editorOpened) setEditorOpened(true)\n /*\n * The step editor's six option lists, read on the same latch as the\n * Workflows builder's — one hook, because the two editors offer one set of\n * steps and a second copy would be a second read cost to keep in agreement.\n */\n const { pickers: stepPickers, truncated: truncatedPickers } =\n useAutomationStepPickers(hostId, editorOpened)\n const liveActions = readActions.filter((action: any) => !action.deletedAt)\n /*\n * Opens a listed action in the editor, for a widget in the `hostAutomations`\n * zone — one that drafted an action and offers to open it. Read through a\n * ref so the callback keeps one identity while the list changes beneath it,\n * and answers `false` for an id the list has not read yet.\n */\n const listedRef = useRef<any[]>(liveActions)\n listedRef.current = liveActions\n const openAction = useCallback((actionId: string) => {\n const action = listedRef.current.find((row: any) => row.$id === actionId)\n if (!action) return false\n setDraft(draftFromAction(action, action.$id))\n return true\n }, [])\n /**\n * Element interactions are not listed here.\n *\n * An ACTION is something the SITE does — an order was placed, a form was\n * submitted. An INTERACTION is something an ELEMENT does, and it belongs to\n * the document that holds the element: it publishes and rolls back with it,\n * travels with it, and is deleted with it. This page is the site's\n * automation list, and a nav menu's hover timing sitting in it beside \"when\n * an order is placed\" was the tell that the two had been conflated.\n *\n * These rows are the ones written BEFORE the move, still bound by selector.\n * They keep running and stay editable — on their element, in the besigner,\n * which is now the only place they appear. Counted rather than hidden\n * outright, so the page can say where they went instead of appearing to\n * have lost them.\n */\n const elementScoped = liveActions.filter((action: any) =>\n LEAF_SELECTOR.test(String(action?.trigger?.selector ?? '')),\n )\n // Sorting is safe here in a way it is not on a paged list: these rows are\n // the whole collection below the ceiling, not a slice of one.\n const actions = liveActions\n .filter((action: any) => !elementScoped.includes(action))\n .sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n // The page is a SLICE: the rows are already in hand, and both the element\n // interaction count and the alphabetical order need all of them.\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const visibleActions = useMemo(\n () => actions.slice(page * pageSize, page * pageSize + pageSize),\n [actions, page, pageSize],\n )\n\n const patch = useCallback(\n (updater: (previous: ActionDraft) => ActionDraft) =>\n setDraft((previous) => (previous ? updater(previous) : previous)),\n [],\n )\n\n /** The saved action the editor has open, as the `automationEditor` zone names it. */\n const draftId = draft?.id ?? null\n const draftName = draft?.name ?? ''\n const editorTarget = useMemo<ConsoleAutomationTarget | null>(\n () => (draftId ? { type: 'action', id: draftId, name: draftName } : null),\n // The name the editor opened with: the zone reads the automation as it is\n // stored, so a rename being typed does not make it a different one.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [draftId],\n )\n\n /**\n * Whether this workspace may open the editor at all. One gate for the\n * blank action and the recipes: a recipe is an action, so the plan that\n * carries one carries the other, and the refusal reads the same.\n */\n const actionsEntitled = useCallback(() => {\n if (checkEntitlement(org, 'actions')) return true\n enqueueSnackbar(\n 'The actions builder requires a Pro plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n return false\n }, [org, enqueueSnackbar])\n\n const handleAdd = useCallback(() => {\n if (!actionsEntitled()) return\n setDraft({\n id: null,\n name: '',\n trigger: { event: 'formSubmission', filter: '' },\n steps: [defaultStep('siteAlert')],\n enabled: true,\n customEvent: '',\n conditionRows: [EMPTY_CONDITION_ROW],\n conditionCombinator: 'and',\n recipe: null,\n })\n }, [actionsEntitled])\n\n /*\n * THE RECIPES (AGL-2626).\n *\n * A recipe opens the editor prefilled and writes nothing: the draft it\n * builds is the one Save would persist, and until then it is state in\n * this card and nowhere else. Three recipes open at once; \"Tag by form\"\n * needs a form first, and the form is this site's — `hosts/{hostId}/forms`\n * — so the picker is the one host-scoped piece of the feature, held here\n * rather than in the catalog.\n */\n const [recipesAnchor, setRecipesAnchor] = useState<HTMLElement | null>(null)\n const [formPickFor, setFormPickFor] = useState<CrmActionRecipe | null>(null)\n const [pickedFormId, setPickedFormId] = useState('')\n /**\n * The form picker has been opened at least once — the `editorOpened`\n * latch's twin, and for the same reason: the forms window is read for\n * this picker only, and a reader who never opens it pays nothing.\n */\n const [formPickerOpened, setFormPickerOpened] = useState(false)\n if (formPickFor && !formPickerOpened) setFormPickerOpened(true)\n const { data: formRead } = useFirestoreCollection<any>(\n () =>\n formPickerOpened\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'forms'),\n EDITOR_OPTION_CEILING,\n )\n : null,\n [firestore, hostId, formPickerOpened],\n { idField: '$id' },\n )\n const { rows: formDocs, truncated: formsTruncated } = ceilingedWindow<any>(\n formRead,\n EDITOR_OPTION_CEILING,\n )\n // An archived form collects nothing, so a recipe keyed on it would never\n // fire; the name falls back to the id the way every form list's does.\n const formOptions = (formDocs ?? [])\n .filter((form: any) => !form.archivedAt)\n .map((form: any) => ({\n id: form.$id as string,\n name: (String(form.displayName ?? '').trim() || form.$id) as string,\n }))\n .sort((a: { name: string }, b: { name: string }) =>\n a.name.localeCompare(b.name),\n )\n\n const handleRecipe = useCallback(\n (recipe: CrmActionRecipe) => {\n setRecipesAnchor(null)\n if (!actionsEntitled()) return\n if (recipe.needs === 'form') {\n setPickedFormId('')\n setFormPickFor(recipe)\n return\n }\n setDraft(draftFromAction(recipe.build(), null))\n },\n [actionsEntitled],\n )\n\n const handleFormPicked = useCallback(() => {\n const recipe = formPickFor\n const form = formOptions.find((option) => option.id === pickedFormId)\n if (!recipe || !form) return\n setFormPickFor(null)\n setDraft(draftFromAction(recipe.build({ form }), null))\n }, [formPickFor, formOptions, pickedFormId])\n\n // Run log + test runs (AGL-266).\n const [runsFor, setRunsFor] = useState<any | null>(null)\n const handleTestRun = useCallback(\n async (action: any) => {\n try {\n const response = await fetch('/api/events/dispatch', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n actionId: action.$id,\n event: action.trigger?.event,\n payload: { path: '/console-test', test: 'true' },\n }),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n return void enqueueSnackbar(payload?.error ?? 'Test run failed', {\n variant: 'warning',\n persist: false,\n })\n }\n const alerts = Array.isArray(payload?.alerts) ? payload.alerts : []\n enqueueSnackbar(\n alerts.length\n ? `Test ran — first alert: ${alerts[0].message}`\n : 'Test ran — server steps executed (see Runs)',\n { variant: 'success', persist: false },\n )\n } catch (error) {\n console.error(error)\n enqueueSnackbar('Test run failed', { variant: 'error' })\n }\n },\n [hostId, enqueueSnackbar],\n )\n\n const handleSave = useCallback(async () => {\n if (!draft) return\n const isCustom = draft.trigger.event === CUSTOM_EVENT_VALUE\n const candidate: HostAction = {\n name: draft.name.trim().slice(0, 60),\n trigger: {\n event: isCustom ? draft.customEvent.trim() : draft.trigger.event,\n ...(draft.trigger.filter?.trim()\n ? { filter: draft.trigger.filter.trim() }\n : {}),\n // Site-event config (AGL-256).\n ...(draft.trigger.selector?.trim()\n ? { selector: draft.trigger.selector.trim() }\n : {}),\n ...(Number(draft.trigger.threshold) > 0\n ? { threshold: Number(draft.trigger.threshold) }\n : {}),\n ...(draft.trigger.pathPattern?.trim()\n ? { pathPattern: draft.trigger.pathPattern.trim() }\n : {}),\n // Frequency caps (AGL-274).\n ...(draft.trigger.oncePerVisitor === true\n ? { oncePerVisitor: true }\n : {}),\n ...(draft.trigger.oncePerSession === true\n ? { oncePerSession: true }\n : {}),\n ...(Number(draft.trigger.cooldownMinutes) >= 1\n ? { cooldownMinutes: Number(draft.trigger.cooldownMinutes) }\n : {}),\n ...(draft.trigger.everyTime === true ? { everyTime: true } : {}),\n // Structured payload conditions (AGL-557; chained AGL-565):\n // rows keep their blank fields so validateHostAction surfaces\n // the miss. Saves always write the list shape — the legacy\n // single `condition` is only ever read, never written back.\n ...(draft.conditionRows.some((row) => row.op)\n ? {\n conditions: draft.conditionRows\n .filter((row) => row.op)\n .map((row) => ({\n field: row.field.trim(),\n op: row.op as TriggerConditionOp,\n ...(row.op !== 'notEmpty' ? { value: row.value.trim() } : {}),\n })),\n combinator: draft.conditionCombinator,\n }\n : {}),\n },\n steps: draft.steps,\n enabled: draft.enabled !== false,\n // The stamp travels only when the draft knows one way or the other;\n // an older action's silence is kept (see hostActionRecipeId).\n ...(draft.recipe !== undefined ? { recipe: draft.recipe } : {}),\n }\n const problem = validateHostAction(candidate)\n if (problem) {\n return void enqueueSnackbar(problem, {\n variant: 'warning',\n persist: false,\n })\n }\n try {\n const id = draft.id ?? createResourceUid()\n /**\n * Refuse an EDIT whose seed the server never confirmed (AGL-1358).\n *\n * The payload is the whole action. `merge: true` is here so the\n * frequency caps and conditions can be nulled explicitly, not to\n * protect untouched fields — there are none, they are all in the\n * payload. So a cached seed does not lose one edit, it reverts the\n * trigger and every step to whatever the cache last held, on an\n * automation that is running on a live site.\n *\n * Only the edit path. A NEW action is built from `EMPTY_*` defaults at\n * a fresh uid and can overwrite nothing; the first snapshot of any\n * listener is `fromCache: true`, so guarding a create would refuse a\n * save that was never unsafe.\n *\n * The guard WRAPS the write — the interaction builder's early return\n * is the shape you can keep while losing the protection.\n */\n /**\n * The action DOCUMENT is created by the server (AGL-2266).\n *\n * `hosts/{hostId}/actions` was in none of the host catch-all's exclusion\n * lists, so any editor could create one client-direct on any plan and\n * nothing counted them. /api/hosts/resources now owns the create and\n * holds `ACTIONS_MAX_PER_HOST` from a server read.\n *\n * Only the create moves, and it writes a SHELL. The merge-set below is\n * untouched and still carries the whole candidate — trigger, steps,\n * conditions, the explicit nulls AGL-274/557 depend on — so no field can\n * be lost to the route's allow-list, and for an existing action this\n * path does not run at all.\n */\n if (!draft.id) {\n try {\n await createResource({\n hostId,\n resource: 'action',\n id,\n data: { name: candidate.name ?? draft.name ?? 'Untitled action' },\n })\n } catch (error: any) {\n return void enqueueSnackbar(\n error?.message ?? 'Could not create the interaction',\n { variant: 'error' },\n )\n }\n }\n const verdict = await writeGuardedBySeed(\n {\n subject: 'action',\n unreadable: Boolean(draft.id) && actionsStatus === 'error',\n fromCache: Boolean(draft.id) && actionsFromCache,\n },\n async () => {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'actions', id),\n {\n // The stored shape (AGL-2639), shared with the recipe-install\n // route: frequency caps overwrite explicitly (AGL-274) because\n // a merge-set keeps omitted keys, so switching one off must\n // write it out; conditions follow suit (AGL-557/565) — the\n // list + combinator write null when cleared, and the legacy\n // single `condition` is always nulled.\n ...hostActionDocument(candidate),\n updatedAt: Timestamp.now(),\n ...(draft.id ? {} : { createdAt: Timestamp.now() }),\n },\n { merge: true },\n )\n },\n )\n // A refusal keeps the editor open with everything that was typed.\n if (!verdict.ok) {\n return void enqueueSnackbar(verdict.message, {\n variant: 'warning',\n persist: false,\n })\n }\n setDraft(null)\n enqueueSnackbar('Action saved', { variant: 'success', persist: false })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [\n draft,\n firestore,\n createResource,\n hostId,\n enqueueSnackbar,\n actionsFromCache,\n actionsStatus,\n ])\n\n const handleDelete = useCallback(\n (action: any) => async () => {\n const confirmed = await confirm({\n title: 'Delete this action?',\n description: `\"${action.name}\" stops running on its trigger.`,\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await updateDoc(doc(firestore, 'hosts', hostId, 'actions', action.$id), {\n deletedAt: Timestamp.now(),\n })\n },\n [confirm, firestore, hostId],\n )\n\n const handleToggle = useCallback(\n (action: any) => async (event: { target: { checked: boolean } }) => {\n const enabled = event.target.checked\n /*\n * A placeholder is a value nobody has supplied yet. Switched on, the\n * step holding it fails every run and a condition holding one never\n * matches, so turning such an automation on is confirmed, naming what\n * is still missing.\n */\n const missing = enabled ? automationPlaceholders(action) : []\n if (missing.length) {\n const confirmed = await confirm({\n title: 'Switch on with placeholders?',\n description:\n `\"${action.name}\" still has ${missing.length === 1 ? 'a placeholder' : `${missing.length} placeholders`} ` +\n `to fill in: ${missing.slice(0, 3).map(describeAutomationPlaceholder).join('; ')}` +\n `${missing.length > 3 ? '; …' : ''}. Open it with Edit to fill ` +\n `${missing.length === 1 ? 'it' : 'them'} in first.`,\n confirmationText: 'Switch on anyway',\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n await updateDoc(doc(firestore, 'hosts', hostId, 'actions', action.$id), {\n enabled,\n })\n },\n [confirm, firestore, hostId],\n )\n\n return (\n <CardDisplay\n header={'Actions'}\n help={pluginDocsHelp('actionsBuilder', { anchor: '#create-an-action' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'When a site event fires, run automations in order — trigger a ' +\n 'workflow, show the visitor an alert, chain a custom event, or ' +\n 'write to a dataset. Pro plans and up.'}\n </Typography>\n {visibleActions.map((action: any) => {\n const missing = automationPlaceholders(action).length\n return (\n <Stack\n key={action.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Switch\n size=\"small\"\n checked={action.enabled !== false}\n onChange={handleToggle(action)}\n />\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {action.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {`on ${hostEventLabel(action.trigger?.event)}` +\n ` · ${(action.steps ?? [])\n .map(\n (step: any) =>\n HOST_ACTION_STEP_LABELS[\n step.type as HostActionStepType\n ] ?? step.type,\n )\n .join(' → ')}`}\n </Typography>\n {missing ? (\n <Typography variant=\"caption\" color=\"warning.main\" noWrap>\n {missing === 1\n ? '1 placeholder to fill in'\n : `${missing} placeholders to fill in`}\n </Typography>\n ) : null}\n </Stack>\n <Button\n size=\"small\"\n onClick={() => setDraft(draftFromAction(action, action.$id))}\n >\n {'Edit'}\n </Button>\n {isSiteEventType(String(action.trigger?.event ?? '')) ? (\n // Test run (AGL-266): server steps only, via the dispatch API.\n <Button size=\"small\" onClick={() => void handleTestRun(action)}>\n {'Test'}\n </Button>\n ) : null}\n <Button size=\"small\" onClick={() => setRunsFor(action)}>\n {'Runs'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(action)}>\n {'Delete'}\n </Button>\n </Stack>\n )\n })}\n {actions.length === 0 ? null : (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={visibleActions.length}\n // The actions the card HOLDS, which is what the reader is paging\n // through — not the collection, whose other half is the element\n // interactions counted below.\n count={actions.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )}\n {actionsTruncated ? (\n <Alert severity=\"info\">\n {`Showing the first ${ACTION_CEILING} rows of this site’s ` +\n 'automations, ordered by id. There are more — both the list ' +\n 'above and the interaction count below describe only what was ' +\n 'read.'}\n </Alert>\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ alignSelf: 'flex-start' }}>\n <Button size=\"small\" color=\"primary\" onClick={handleAdd}>\n {'Add action'}\n </Button>\n <Button\n size=\"small\"\n color=\"primary\"\n aria-haspopup=\"menu\"\n aria-controls={recipesAnchor ? 'host-action-recipes' : undefined}\n aria-expanded={recipesAnchor ? true : undefined}\n onClick={(event) => setRecipesAnchor(event.currentTarget)}\n >\n {'Recipes'}\n </Button>\n {/*\n Other ways to start an automation, from plugins (AGL-2919): the\n `hostAutomations` zone, drawn through the shell's own gated slot. A\n plugin page cannot mount that slot itself, so the shell hands it\n down, and a widget here passes the gates a console page's would.\n */}\n {ExtensionZone ? (\n <ExtensionZone\n slot=\"hostAutomations\"\n hostId={hostId}\n orgId={org?.$id}\n openAction={openAction}\n />\n ) : null}\n </Stack>\n <Menu\n id=\"host-action-recipes\"\n anchorEl={recipesAnchor}\n open={Boolean(recipesAnchor)}\n onClose={() => setRecipesAnchor(null)}\n >\n {CRM_ACTION_RECIPES.map((recipe) => (\n <MenuItem key={recipe.id} onClick={() => handleRecipe(recipe)}>\n <ListItemText\n primary={recipe.title}\n secondary={recipe.description}\n slotProps={{ secondary: { sx: { whiteSpace: 'normal', maxWidth: 360 } } }}\n />\n </MenuItem>\n ))}\n </Menu>\n {/* Says where they went. A list that silently drops rows\n a reader saw last week is a list they stop trusting; this is the\n one sentence that makes the absence deliberate. */}\n {elementScoped.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {elementScoped.length === 1\n ? '1 interaction is set up on its own element — '\n : `${elementScoped.length} interactions are set up on their own elements — `}\n {'open the element in the besigner to edit it. Interactions ' +\n 'belong to the page they are on, so they publish and roll back ' +\n 'with it.'}\n </Typography>\n ) : null}\n </Stack>\n\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{draft?.id ? 'Edit action' : 'Add action'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n {ExtensionZone && editorTarget ? (\n <ExtensionZone\n slot=\"automationEditor\"\n hostId={hostId}\n orgId={org?.$id}\n target={editorTarget}\n />\n ) : null}\n {draft?.recipe ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Started from the “${\n CRM_ACTION_RECIPES.find((recipe) => recipe.id === draft.recipe)\n ?.title ?? 'recipe'\n }” recipe — change anything, then save.`}\n </Typography>\n ) : null}\n {/*\n A short picker is worse than an empty one, because it looks\n complete: the target an author cannot find reads as deleted, and\n the step gets pointed somewhere else.\n */}\n {truncatedPickers.length > 0 ? (\n <Alert severity=\"info\" sx={{ mt: 1 }}>\n {`Offering the first ${EDITOR_OPTION_CEILING} rows, ordered by ` +\n `id, for: ${truncatedPickers.join(', ')}. This site has ` +\n 'more, so a step target may not be listed below.'}\n </Alert>\n ) : null}\n <TextField\n label=\"Name\"\n value={draft?.name ?? ''}\n onChange={(event) =>\n patch((previous) => ({ ...previous, name: event.target.value }))\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <Stack direction=\"row\" spacing={1}>\n <TextField\n select\n label=\"Trigger event\"\n value={draft?.trigger.event ?? ''}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n trigger: { ...previous.trigger, event: event.target.value },\n }))\n }\n size=\"small\"\n sx={{ minWidth: 180 }}\n >\n {HOST_EVENT_TYPES.map((eventType) => (\n <MenuItem key={eventType} value={eventType}>\n {hostEventLabel(eventType)}\n </MenuItem>\n ))}\n {SITE_EVENT_TYPES.map((eventType) => (\n <MenuItem key={eventType} value={eventType}>\n {`${eventType} (on page)`}\n </MenuItem>\n ))}\n <MenuItem value={CUSTOM_EVENT_VALUE}>{'Custom event…'}</MenuItem>\n </TextField>\n {draft?.trigger.event === CUSTOM_EVENT_VALUE ? (\n <TextField\n label=\"Custom event name\"\n value={draft?.customEvent ?? ''}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n customEvent: event.target.value,\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n ) : (\n <TextField\n label=\"Filter (optional)\"\n placeholder={'path == \"/pricing\"'}\n // What the expression — and the conditions below — can name\n // for this event; nothing for an event whose payload is not\n // written down, rather than a guess.\n helperText={hostEventPayloadHint(draft?.trigger.event) ?? undefined}\n value={draft?.trigger.filter ?? ''}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n trigger: {\n ...previous.trigger,\n filter: event.target.value,\n },\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n )}\n </Stack>\n {/* Structured payload conditions (AGL-557): the no-code sibling\n of the filter — e.g. only when `subscribe` is not empty.\n Chainable with AND/OR (AGL-565), rows styled like steps. */}\n {(draft?.conditionRows ?? []).map((row, index) => (\n <Stack\n key={index}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n {index > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {draft?.conditionCombinator === 'or' ? 'or' : 'and'}\n </Typography>\n ) : null}\n <TextField\n select\n label={index === 0 ? 'Only run when' : 'Condition'}\n value={row.op}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n conditionRows: previous.conditionRows.map(\n (previousRow, index2) =>\n index2 === index\n ? {\n ...previousRow,\n op: event.target.value as ConditionRowDraft['op'],\n }\n : previousRow,\n ),\n }))\n }\n size=\"small\"\n sx={{ minWidth: 180 }}\n >\n {/* \"Always\" only exists while this is the sole row —\n multi-row chains clear by removing rows instead. */}\n {(draft?.conditionRows.length ?? 0) === 1 ? (\n <MenuItem value=\"\">{'Always (no condition)'}</MenuItem>\n ) : null}\n <MenuItem value=\"notEmpty\">{'A field is not empty'}</MenuItem>\n <MenuItem value=\"equals\">{'A field equals…'}</MenuItem>\n <MenuItem value=\"contains\">{'A field contains…'}</MenuItem>\n </TextField>\n {row.op ? (\n <TextField\n label=\"Field\"\n placeholder=\"subscribe\"\n value={row.field}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n conditionRows: previous.conditionRows.map(\n (previousRow, index2) =>\n index2 === index\n ? { ...previousRow, field: event.target.value }\n : previousRow,\n ),\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n ) : null}\n {row.op === 'equals' || row.op === 'contains' ? (\n <TextField\n label=\"Value\"\n placeholder=\"Yes\"\n {...placeholderState(row.value, 'Replace the placeholder with the value to match')}\n value={row.value}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n conditionRows: previous.conditionRows.map(\n (previousRow, index2) =>\n index2 === index\n ? { ...previousRow, value: event.target.value }\n : previousRow,\n ),\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n ) : null}\n {(draft?.conditionRows.length ?? 0) > 1 ? (\n <IconButton\n size=\"small\"\n aria-label=\"remove condition\"\n onClick={() =>\n patch((previous) => ({\n ...previous,\n conditionRows:\n previous.conditionRows.length > 1\n ? previous.conditionRows.filter(\n (_, index2) => index2 !== index,\n )\n : [EMPTY_CONDITION_ROW],\n }))\n }\n >\n {'×'}\n </IconButton>\n ) : null}\n </Stack>\n ))}\n {draft?.conditionRows.every((row) => row.op) ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n disabled={\n (draft?.conditionRows.length ?? 0) >= ACTION_MAX_CONDITIONS\n }\n onClick={() =>\n patch((previous) => ({\n ...previous,\n conditionRows: [\n ...previous.conditionRows,\n // New rows start on the simplest operator so the\n // field input is immediately visible.\n { op: 'notEmpty', field: '', value: '' },\n ],\n }))\n }\n >\n {'Add condition'}\n </Button>\n {(draft?.conditionRows.length ?? 0) >= 2 ? (\n // AND/OR combinator (AGL-565); applies to every row.\n <TextField\n select\n label=\"Match\"\n value={draft?.conditionCombinator ?? 'and'}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n conditionCombinator: event.target\n .value as TriggerCombinator,\n }))\n }\n size=\"small\"\n sx={{ minWidth: 220 }}\n >\n <MenuItem value=\"and\">\n {'All conditions match (AND)'}\n </MenuItem>\n <MenuItem value=\"or\">{'Any condition matches (OR)'}</MenuItem>\n </TextField>\n ) : null}\n </Stack>\n ) : null}\n {isSiteEventType(draft?.trigger.event ?? '') ? (\n // Site-event config (AGL-256): what/where the trigger watches.\n <Stack direction=\"row\" spacing={1}>\n {(ELEMENT_SCOPED_SITE_EVENTS as readonly string[]).includes(\n draft?.trigger.event ?? '',\n ) ? (\n <TextField\n label=\"CSS selector\"\n placeholder=\"#pricing-table\"\n value={draft?.trigger.selector ?? ''}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n trigger: {\n ...previous.trigger,\n selector: event.target.value,\n },\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n ) : null}\n {['scrollDepth', 'timeOnPage'].includes(\n draft?.trigger.event ?? '',\n ) ? (\n <TextField\n type=\"number\"\n label={\n draft?.trigger.event === 'scrollDepth'\n ? 'Scroll %'\n : 'Seconds'\n }\n value={draft?.trigger.threshold ?? ''}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n trigger: {\n ...previous.trigger,\n threshold: Number(event.target.value),\n },\n }))\n }\n size=\"small\"\n sx={{ width: 120 }}\n />\n ) : null}\n <TextField\n label=\"Only on pages (optional)\"\n placeholder=\"/pricing or /blog/*\"\n value={draft?.trigger.pathPattern ?? ''}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n trigger: {\n ...previous.trigger,\n pathPattern: event.target.value,\n },\n }))\n }\n size=\"small\"\n sx={{ flex: 1 }}\n />\n {/* Frequency caps (AGL-274). */}\n <TextField\n select\n label=\"Frequency\"\n size=\"small\"\n sx={{ minWidth: 170 }}\n value={\n draft?.trigger.oncePerVisitor\n ? 'visitor'\n : draft?.trigger.oncePerSession\n ? 'session'\n : Number(draft?.trigger.cooldownMinutes) >= 1\n ? 'cooldown'\n : draft?.trigger.everyTime\n ? 'every'\n : ''\n }\n onChange={(event) => {\n const mode = event.target.value\n patch((previous) => ({\n ...previous,\n trigger: {\n ...previous.trigger,\n // Every occurrence (AGL-562): repeatable UI\n // choreography (menu/drawer toggles).\n everyTime: mode === 'every',\n oncePerVisitor: mode === 'visitor',\n oncePerSession: mode === 'session',\n cooldownMinutes:\n mode === 'cooldown'\n ? Number(previous.trigger.cooldownMinutes) >= 1\n ? Number(previous.trigger.cooldownMinutes)\n : 60\n : undefined,\n },\n }))\n }}\n >\n <MenuItem value=\"\">{'Every matching pageview'}</MenuItem>\n <MenuItem value=\"every\">\n {'Every occurrence (repeatable)'}\n </MenuItem>\n <MenuItem value=\"session\">{'Once per session'}</MenuItem>\n <MenuItem value=\"visitor\">{'Once per visitor'}</MenuItem>\n <MenuItem value=\"cooldown\">{'With a cooldown'}</MenuItem>\n </TextField>\n {Number(draft?.trigger.cooldownMinutes) >= 1 &&\n !draft?.trigger.oncePerVisitor &&\n !draft?.trigger.oncePerSession ? (\n <TextField\n type=\"number\"\n label=\"Cooldown (minutes)\"\n size=\"small\"\n sx={{ width: 150 }}\n value={draft?.trigger.cooldownMinutes ?? 60}\n onChange={(event) =>\n patch((previous) => ({\n ...previous,\n trigger: {\n ...previous.trigger,\n cooldownMinutes: Number(event.target.value),\n },\n }))\n }\n />\n ) : null}\n </Stack>\n ) : null}\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Steps (run in order)'}\n </Typography>\n {(draft?.steps ?? []).map((step, index) => (\n <AutomationStepFields\n key={index}\n step={step}\n index={index}\n kind={step.type}\n kinds={ACTION_STEP_KINDS}\n stepForKind={(value) => defaultStep(value as HostActionStepType)}\n pickers={stepPickers}\n onSteps={(update) =>\n patch((previous) => ({\n ...previous,\n steps: update(previous.steps),\n }))\n }\n />\n ))}\n <Button\n size=\"small\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={(draft?.steps.length ?? 0) >= ACTION_MAX_STEPS}\n onClick={() =>\n patch((previous) => ({\n ...previous,\n steps: [...previous.steps, defaultStep('siteAlert')],\n }))\n }\n >\n {'Add step'}\n </Button>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setDraft(null)}>{'Cancel'}</Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!draft?.name.trim()}\n onClick={handleSave}\n >\n {'Save action'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={Boolean(formPickFor)}\n onClose={() => setFormPickFor(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{formPickFor?.title ?? ''}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Pick the form whose new contacts get the tag. The action opens ' +\n 'ready to edit; nothing is saved until you save it.'}\n </Typography>\n {formsTruncated ? (\n <Alert severity=\"info\">\n {`Offering the first ${EDITOR_OPTION_CEILING} forms, ordered by ` +\n 'id. This site has more, so the form you want may not be listed.'}\n </Alert>\n ) : null}\n {formOptions.length ? (\n <TextField\n select\n label=\"Form\"\n value={pickedFormId}\n onChange={(event) => setPickedFormId(event.target.value)}\n size=\"small\"\n sx={{ mt: 1 }}\n >\n {formOptions.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {option.name}\n </MenuItem>\n ))}\n </TextField>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This site has no forms yet. Add one in the besigner, then come back.'}\n </Typography>\n )}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setFormPickFor(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!pickedFormId}\n onClick={handleFormPicked}\n >\n {'Use recipe'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={Boolean(runsFor)}\n onClose={() => setRunsFor(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{`Runs — ${runsFor?.name ?? ''}`}</DialogTitle>\n <DialogContent>\n {runsFor ? (\n <HostRunHistoryCard\n hostId={hostId}\n orgId={org?.$id}\n targetId={runsFor.$id}\n targetType=\"action\"\n targetName={runsFor.name ?? ''}\n header=\"Recent runs\"\n />\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setRunsFor(null)}>\n {'Close'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nHostActionsCard.displayName = 'HostActionsCard'\n\nexport default HostActionsCard\n"],"names":["ACTION_MAX_CONDITIONS","ACTION_MAX_STEPS","checkEntitlement","createResourceUid","CRM_ACTION_RECIPES","ELEMENT_SCOPED_SITE_EVENTS","HOST_ACTION_STEP_LABELS","HOST_EVENT_TYPES","hostActionDocument","hostActionRecipeId","hostEventLabel","hostEventPayloadHint","isSiteEventType","normalizeTriggerConditions","pluginDocsHelp","SITE_EVENT_TYPES","validateHostAction","automationPlaceholders","describeAutomationPlaceholder","useConsoleWidgetSlot","CardDisplay","useConfirmationContext","ListPagination","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Timestamp","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","ListItemText","Menu","MenuItem","Stack","Switch","TextField","Typography","collection","doc","setDoc","updateDoc","useCallback","useMemo","useRef","useState","ceilingedWindow","collectionCeiling","useFirestore","useFirestoreCollection","useHostResourceApi","writeGuardedBySeed","AutomationStepFields","defaultStep","placeholderState","HostRunHistoryCard","EDITOR_OPTION_CEILING","useAutomationStepPickers","CUSTOM_EVENT_VALUE","ACTION_CEILING","EMPTY_CONDITION_ROW","op","field","value","ACTION_STEP_KINDS","Object","entries","map","label","draftFromAction","action","id","builtIn","includes","trigger","event","String","rows","condition","name","filter","selector","threshold","pathPattern","oncePerVisitor","oncePerSession","everyTime","Number","cooldownMinutes","steps","enabled","customEvent","conditionRows","length","conditionCombinator","combinator","recipe","LEAF_SELECTOR","HostActionsCard","props","runsFor","hostId","org","firestore","createResource","enqueueSnackbar","confirm","ExtensionZone","data","actionDocs","status","actionsStatus","fromCache","actionsFromCache","idField","readActions","truncated","actionsTruncated","draft","setDraft","editorOpened","setEditorOpened","pickers","stepPickers","truncatedPickers","liveActions","deletedAt","listedRef","current","openAction","actionId","find","row","$id","elementScoped","test","actions","sort","a","b","localeCompare","page","setPage","pageSize","setPageSize","visibleActions","slice","patch","updater","previous","draftId","draftName","editorTarget","type","actionsEntitled","variant","persist","handleAdd","recipesAnchor","setRecipesAnchor","formPickFor","setFormPickFor","pickedFormId","setPickedFormId","formPickerOpened","setFormPickerOpened","formRead","formDocs","formsTruncated","formOptions","form","archivedAt","displayName","trim","handleRecipe","needs","build","handleFormPicked","option","setRunsFor","handleTestRun","response","fetch","method","headers","body","JSON","stringify","payload","path","json","catch","ok","error","alerts","Array","isArray","message","console","handleSave","isCustom","candidate","some","conditions","undefined","problem","resource","verdict","subject","unreadable","Boolean","updatedAt","now","createdAt","merge","allowDuplicate","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","handleToggle","target","checked","missing","join","header","help","anchor","contentGutterX","contentGutterY","spacing","direction","sx","alignItems","size","onChange","flex","minWidth","noWrap","step","onClick","rowCount","count","onPageChange","onPageSizeChange","severity","alignSelf","aria-haspopup","aria-controls","aria-expanded","currentTarget","slot","orgId","anchorEl","open","onClose","primary","secondary","slotProps","whiteSpace","maxWidth","fullWidth","display","flexDirection","gap","pt","mt","autoFocus","select","eventType","placeholder","helperText","index","previousRow","index2","aria-label","_","every","disabled","width","mode","kind","kinds","stepForKind","onSteps","update","targetId","targetType","targetName"],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { type HelpTipContent } from '@aglyn/shared-ui-jsx';
|
|
2
|
+
export interface HostActivityCardProps {
|
|
3
|
+
hostId: string;
|
|
4
|
+
/** Show only entries for this target id (e.g. a screen detail page). */
|
|
5
|
+
targetId?: string;
|
|
6
|
+
header?: string;
|
|
7
|
+
/** When set, a "View all activity" link renders under the list (AGL-249). */
|
|
8
|
+
viewAllHref?: string;
|
|
9
|
+
/** Overrides the default help affordance on the card header. */
|
|
10
|
+
help?: HelpTipContent;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Recent user activity (AGL-118): newest-first feed from
|
|
14
|
+
* `hosts/{hostId}/activity`, optionally filtered to one target so detail
|
|
15
|
+
* pages show just their own history.
|
|
16
|
+
*
|
|
17
|
+
* ## The "Page Activity" card reported nothing on a page that had changed
|
|
18
|
+
*
|
|
19
|
+
* This is AGL-2292 — fixed in the sibling `OrgActivityCard`, never applied
|
|
20
|
+
* here. The query was `limit(200)` with NO `orderBy`. Firestore then returns
|
|
21
|
+
* documents in DOCUMENT-ID order, `useHostActivityLogger` appends with
|
|
22
|
+
* `addDoc()`, so the ids are effectively random: the window was a
|
|
23
|
+
* pseudo-random SAMPLE of the collection rather than its newest page, and the
|
|
24
|
+
* client sort below dutifully ordered that sample and sliced the top off it —
|
|
25
|
+
* which made the result look right and be wrong.
|
|
26
|
+
*
|
|
27
|
+
* Measured on a production host: 996 activity documents, so a 200-row window
|
|
28
|
+
* covers the lexicographically-lowest ids and ends at `ClSZ…`. A screen whose
|
|
29
|
+
* five entries have ids beginning `Y`, `h`, `j`, `k`, `t` has every one of
|
|
30
|
+
* them past that boundary — the card fetches 200 rows, matches 0, and says
|
|
31
|
+
* "No activity yet" about a page edited that morning.
|
|
32
|
+
*
|
|
33
|
+
* ## Why the targeted query filters SERVER-side
|
|
34
|
+
*
|
|
35
|
+
* Adding `orderBy` alone is not the fix here, and the difference only shows up
|
|
36
|
+
* on the per-target card. The window is host-wide, so 200 newest host-wide
|
|
37
|
+
* entries still have to contain the target's own. On that same host it would
|
|
38
|
+
* have surfaced 1 of the screen's 5 entries, and 40 of the 108 targets with
|
|
39
|
+
* real activity would STILL have rendered "No activity yet" — a quieter
|
|
40
|
+
* version of the same lie. A target that has not been touched recently is
|
|
41
|
+
* exactly the one whose history you open the card to read.
|
|
42
|
+
*
|
|
43
|
+
* So `targetId` becomes a `where` and spends the window on the target instead
|
|
44
|
+
* of on the host. That predicate needs no new index: an equality filter with
|
|
45
|
+
* no `orderBy` is served by the automatic single-field index, which is
|
|
46
|
+
* deliberate — a composite (`target.id` ASC, `createdAt` DESC) would be
|
|
47
|
+
* FILE-ONLY until someone ran `firebase deploy --only firestore:indexes` by
|
|
48
|
+
* hand, and every read in between would throw `failed-precondition`. The
|
|
49
|
+
* ordering is done by the client sort below, which is safe at this size: the
|
|
50
|
+
* busiest target in production holds 55 entries against a 200-row window.
|
|
51
|
+
*
|
|
52
|
+
* The un-targeted (dashboard) query has no such excuse and takes the AGL-2292
|
|
53
|
+
* one-liner: `orderBy('createdAt', 'desc')`, single-field, no config change.
|
|
54
|
+
*
|
|
55
|
+
* ## "Found nothing" vs "could not look"
|
|
56
|
+
*
|
|
57
|
+
* Both used to render the empty state, and so did the first paint before any
|
|
58
|
+
* snapshot arrived. A refused or failed listen now says so and offers a retry
|
|
59
|
+
* (the `used-by-card` shape), because an audit feed that invents a confident
|
|
60
|
+
* zero is worse than one that admits it is blind.
|
|
61
|
+
*
|
|
62
|
+
* ## Why the "Show N more" expander became the shared footer
|
|
63
|
+
*
|
|
64
|
+
* It was a FOURTH pagination grammar for the same act — beside the `DataGrid`
|
|
65
|
+
* footer, the shared `ListPagination`, and the "Load more" the DAM grid keeps —
|
|
66
|
+
* and it escaped the guard that exists to stop exactly that, on spelling: the
|
|
67
|
+
* check looks for the literal `'Load more'` and this button said
|
|
68
|
+
* `Show ${more} more`.
|
|
69
|
+
*
|
|
70
|
+
* On its own terms it was also the weakest of the four. It only ever grew, so
|
|
71
|
+
* a reader who opened forty rows could not get back to ten without remounting
|
|
72
|
+
* the card; it offered no size control, which made it one of the two grammars
|
|
73
|
+
* that never let a reader choose; it could not state a total even though every
|
|
74
|
+
* row was already in hand; and it said nothing when `WINDOW` bit, so a busy
|
|
75
|
+
* target read as though two hundred entries were all there were.
|
|
76
|
+
*
|
|
77
|
+
* The footer answers all four, and the rows stay a client SLICE — see `sorted`,
|
|
78
|
+
* where paging is free because the window is already fetched.
|
|
79
|
+
*/
|
|
80
|
+
export declare function HostActivityCard(props: HostActivityCardProps): import("react").JSX.Element;
|
|
81
|
+
export declare namespace HostActivityCard {
|
|
82
|
+
var displayName: string;
|
|
83
|
+
}
|
|
84
|
+
export default HostActivityCard;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
19
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
20
|
+
import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
21
|
+
import { Alert, Button, Link, List, ListItem, ListItemText, Stack, Typography } from "@mui/material";
|
|
22
|
+
import { collection, limit, orderBy, query, where } from "firebase/firestore";
|
|
23
|
+
import { useParams } from "next/navigation";
|
|
24
|
+
import { useEffect, useMemo, useState } from "react";
|
|
25
|
+
import { ceilingedWindow, useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
|
|
26
|
+
import { activityActorLabel, activityHref, activityPrimaryText } from "@aglyn/aglyn/app-utils/activity-presenter";
|
|
27
|
+
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
28
|
+
/**
|
|
29
|
+
* Rows fetched before the client-side sort and page slice.
|
|
30
|
+
*
|
|
31
|
+
* A CEILING, and the read asks for one document more than it so the card can
|
|
32
|
+
* say when the window was not the whole story — `length >= WINDOW` is wrong at
|
|
33
|
+
* exactly the collection size that equals the ceiling.
|
|
34
|
+
*/ const WINDOW = 200;
|
|
35
|
+
/**
|
|
36
|
+
* Recent user activity (AGL-118): newest-first feed from
|
|
37
|
+
* `hosts/{hostId}/activity`, optionally filtered to one target so detail
|
|
38
|
+
* pages show just their own history.
|
|
39
|
+
*
|
|
40
|
+
* ## The "Page Activity" card reported nothing on a page that had changed
|
|
41
|
+
*
|
|
42
|
+
* This is AGL-2292 — fixed in the sibling `OrgActivityCard`, never applied
|
|
43
|
+
* here. The query was `limit(200)` with NO `orderBy`. Firestore then returns
|
|
44
|
+
* documents in DOCUMENT-ID order, `useHostActivityLogger` appends with
|
|
45
|
+
* `addDoc()`, so the ids are effectively random: the window was a
|
|
46
|
+
* pseudo-random SAMPLE of the collection rather than its newest page, and the
|
|
47
|
+
* client sort below dutifully ordered that sample and sliced the top off it —
|
|
48
|
+
* which made the result look right and be wrong.
|
|
49
|
+
*
|
|
50
|
+
* Measured on a production host: 996 activity documents, so a 200-row window
|
|
51
|
+
* covers the lexicographically-lowest ids and ends at `ClSZ…`. A screen whose
|
|
52
|
+
* five entries have ids beginning `Y`, `h`, `j`, `k`, `t` has every one of
|
|
53
|
+
* them past that boundary — the card fetches 200 rows, matches 0, and says
|
|
54
|
+
* "No activity yet" about a page edited that morning.
|
|
55
|
+
*
|
|
56
|
+
* ## Why the targeted query filters SERVER-side
|
|
57
|
+
*
|
|
58
|
+
* Adding `orderBy` alone is not the fix here, and the difference only shows up
|
|
59
|
+
* on the per-target card. The window is host-wide, so 200 newest host-wide
|
|
60
|
+
* entries still have to contain the target's own. On that same host it would
|
|
61
|
+
* have surfaced 1 of the screen's 5 entries, and 40 of the 108 targets with
|
|
62
|
+
* real activity would STILL have rendered "No activity yet" — a quieter
|
|
63
|
+
* version of the same lie. A target that has not been touched recently is
|
|
64
|
+
* exactly the one whose history you open the card to read.
|
|
65
|
+
*
|
|
66
|
+
* So `targetId` becomes a `where` and spends the window on the target instead
|
|
67
|
+
* of on the host. That predicate needs no new index: an equality filter with
|
|
68
|
+
* no `orderBy` is served by the automatic single-field index, which is
|
|
69
|
+
* deliberate — a composite (`target.id` ASC, `createdAt` DESC) would be
|
|
70
|
+
* FILE-ONLY until someone ran `firebase deploy --only firestore:indexes` by
|
|
71
|
+
* hand, and every read in between would throw `failed-precondition`. The
|
|
72
|
+
* ordering is done by the client sort below, which is safe at this size: the
|
|
73
|
+
* busiest target in production holds 55 entries against a 200-row window.
|
|
74
|
+
*
|
|
75
|
+
* The un-targeted (dashboard) query has no such excuse and takes the AGL-2292
|
|
76
|
+
* one-liner: `orderBy('createdAt', 'desc')`, single-field, no config change.
|
|
77
|
+
*
|
|
78
|
+
* ## "Found nothing" vs "could not look"
|
|
79
|
+
*
|
|
80
|
+
* Both used to render the empty state, and so did the first paint before any
|
|
81
|
+
* snapshot arrived. A refused or failed listen now says so and offers a retry
|
|
82
|
+
* (the `used-by-card` shape), because an audit feed that invents a confident
|
|
83
|
+
* zero is worse than one that admits it is blind.
|
|
84
|
+
*
|
|
85
|
+
* ## Why the "Show N more" expander became the shared footer
|
|
86
|
+
*
|
|
87
|
+
* It was a FOURTH pagination grammar for the same act — beside the `DataGrid`
|
|
88
|
+
* footer, the shared `ListPagination`, and the "Load more" the DAM grid keeps —
|
|
89
|
+
* and it escaped the guard that exists to stop exactly that, on spelling: the
|
|
90
|
+
* check looks for the literal `'Load more'` and this button said
|
|
91
|
+
* `Show ${more} more`.
|
|
92
|
+
*
|
|
93
|
+
* On its own terms it was also the weakest of the four. It only ever grew, so
|
|
94
|
+
* a reader who opened forty rows could not get back to ten without remounting
|
|
95
|
+
* the card; it offered no size control, which made it one of the two grammars
|
|
96
|
+
* that never let a reader choose; it could not state a total even though every
|
|
97
|
+
* row was already in hand; and it said nothing when `WINDOW` bit, so a busy
|
|
98
|
+
* target read as though two hundred entries were all there were.
|
|
99
|
+
*
|
|
100
|
+
* The footer answers all four, and the rows stay a client SLICE — see `sorted`,
|
|
101
|
+
* where paging is free because the window is already fetched.
|
|
102
|
+
*/ export function HostActivityCard(props) {
|
|
103
|
+
const { hostId, targetId, header = 'Recent Activity', viewAllHref, help = pluginDocsHelp('consoleTour', {
|
|
104
|
+
anchor: '#a-sites-dashboard',
|
|
105
|
+
excerpt: 'Changes made to this site from the console — publishes, media ' + 'saves, member changes — newest first.'
|
|
106
|
+
}) } = props;
|
|
107
|
+
const { orgSlug, host } = useParams();
|
|
108
|
+
const firestore = useFirestore();
|
|
109
|
+
const [attempt, setAttempt] = useState(0);
|
|
110
|
+
const { data: entries, status } = useFirestoreCollection(()=>{
|
|
111
|
+
// `strictNullChecks` is off repo-wide, so an absent hostId would reach
|
|
112
|
+
// `collection()` as a falsy path segment. Refuse to build the query
|
|
113
|
+
// instead, and report it as unreadable below — never as "no activity".
|
|
114
|
+
if (!hostId) return null;
|
|
115
|
+
const base = collection(firestore, 'hosts', hostId, 'activity');
|
|
116
|
+
// `WINDOW + 1` on both: the extra document is the PROBE, and it is what
|
|
117
|
+
// turns "there may be more" into a fact the card can state. It is never
|
|
118
|
+
// rendered — `ceilingedWindow` drops it below.
|
|
119
|
+
return targetId ? query(base, where('target.id', '==', targetId), limit(WINDOW + 1)) : query(base, orderBy('createdAt', 'desc'), limit(WINDOW + 1));
|
|
120
|
+
}, [
|
|
121
|
+
firestore,
|
|
122
|
+
hostId,
|
|
123
|
+
targetId,
|
|
124
|
+
attempt
|
|
125
|
+
], {
|
|
126
|
+
idField: '$id'
|
|
127
|
+
});
|
|
128
|
+
/*
|
|
129
|
+
* The client sort survives the server-side ordering rather than being
|
|
130
|
+
* replaced by it. It is what orders the TARGETED query (which cannot carry
|
|
131
|
+
* an `orderBy` without a composite index — see above), and on the
|
|
132
|
+
* un-targeted one it still catches a just-written entry whose timestamp the
|
|
133
|
+
* local snapshot has not resolved. Sorting 200 items costs nothing.
|
|
134
|
+
*/ /**
|
|
135
|
+
* Paging is FREE here, which is why it is a slice and not a second query
|
|
136
|
+
* (AGL-2486). The read above already fetches `WINDOW` rows and this then
|
|
137
|
+
* threw all but a page of them away — 190 documents paid for and discarded
|
|
138
|
+
* on every render of this card. Showing more of what is already in hand
|
|
139
|
+
* costs nothing; only running past `WINDOW` would cost a read, and the
|
|
140
|
+
* "View all" link exists for that.
|
|
141
|
+
*
|
|
142
|
+
* Sorting is safe here in a way it is not on a server-paged list: these rows
|
|
143
|
+
* are the whole window, not a slice of one.
|
|
144
|
+
*/ const { rows: windowRows, truncated } = ceilingedWindow(entries, WINDOW);
|
|
145
|
+
const sorted = useMemo(()=>[
|
|
146
|
+
...windowRows
|
|
147
|
+
].sort((a, b)=>{
|
|
148
|
+
var _ref, _ref1;
|
|
149
|
+
var _b_createdAt, _a_createdAt;
|
|
150
|
+
return ((_ref = (_b_createdAt = b.createdAt) == null ? void 0 : _b_createdAt.seconds) != null ? _ref : 0) - ((_ref1 = (_a_createdAt = a.createdAt) == null ? void 0 : _a_createdAt.seconds) != null ? _ref1 : 0);
|
|
151
|
+
}), // `windowRows` is a fresh array each render; the snapshot behind it is not.
|
|
152
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
153
|
+
[
|
|
154
|
+
entries
|
|
155
|
+
]);
|
|
156
|
+
const [page, setPage] = useState(0);
|
|
157
|
+
const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
|
|
158
|
+
// Back to the first page when the card is pointed at a different subject —
|
|
159
|
+
// otherwise a previous screen's page three opens the next one three pages
|
|
160
|
+
// in, over a history that may not have three.
|
|
161
|
+
useEffect(()=>setPage(0), [
|
|
162
|
+
targetId,
|
|
163
|
+
hostId
|
|
164
|
+
]);
|
|
165
|
+
const items = useMemo(()=>sorted.slice(page * pageSize, page * pageSize + pageSize), [
|
|
166
|
+
sorted,
|
|
167
|
+
page,
|
|
168
|
+
pageSize
|
|
169
|
+
]);
|
|
170
|
+
// Three states, not two: a read that never happened must not be reported as
|
|
171
|
+
// an empty history.
|
|
172
|
+
const unreadable = status === 'error' || !hostId;
|
|
173
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
174
|
+
header: header,
|
|
175
|
+
help: help,
|
|
176
|
+
contentGutterX: true,
|
|
177
|
+
contentGutterY: true,
|
|
178
|
+
contentBordered: "all",
|
|
179
|
+
children: [
|
|
180
|
+
unreadable ? /*#__PURE__*/ _jsxs(Stack, {
|
|
181
|
+
spacing: 1.5,
|
|
182
|
+
sx: {
|
|
183
|
+
alignItems: 'flex-start'
|
|
184
|
+
},
|
|
185
|
+
children: [
|
|
186
|
+
/*#__PURE__*/ _jsx(Alert, {
|
|
187
|
+
severity: "warning",
|
|
188
|
+
sx: {
|
|
189
|
+
width: '100%'
|
|
190
|
+
},
|
|
191
|
+
children: 'Could not read the activity log. This is NOT the same as ' + 'nothing having happened — do not treat this as a record ' + 'that the page is unchanged.'
|
|
192
|
+
}),
|
|
193
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
194
|
+
size: "small",
|
|
195
|
+
onClick: ()=>setAttempt((n)=>n + 1),
|
|
196
|
+
children: 'Try again'
|
|
197
|
+
})
|
|
198
|
+
]
|
|
199
|
+
}) : status === 'loading' ? /*#__PURE__*/ _jsx(Typography, {
|
|
200
|
+
variant: "body2",
|
|
201
|
+
color: "text.secondary",
|
|
202
|
+
children: 'Loading activity…'
|
|
203
|
+
}) : sorted.length === 0 ? /*
|
|
204
|
+
* The WINDOW's length, not the page's. A page past the end of a
|
|
205
|
+
* history that shrank while it was open holds no rows and is not an
|
|
206
|
+
* empty history, and this card must never confuse the two.
|
|
207
|
+
*/ /*#__PURE__*/ _jsx(Typography, {
|
|
208
|
+
variant: "body2",
|
|
209
|
+
color: "text.secondary",
|
|
210
|
+
children: 'No activity yet — changes made in the console appear here.'
|
|
211
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
212
|
+
children: [
|
|
213
|
+
/*#__PURE__*/ _jsx(List, {
|
|
214
|
+
dense: true,
|
|
215
|
+
disablePadding: true,
|
|
216
|
+
children: items.map((entry)=>{
|
|
217
|
+
var _ref;
|
|
218
|
+
var _entry_createdAt_toDate, _entry_createdAt;
|
|
219
|
+
const href = activityHref(entry, {
|
|
220
|
+
orgSlug,
|
|
221
|
+
host
|
|
222
|
+
});
|
|
223
|
+
const label = activityPrimaryText(entry);
|
|
224
|
+
return /*#__PURE__*/ _jsx(ListItem, {
|
|
225
|
+
disableGutters: true,
|
|
226
|
+
dense: true,
|
|
227
|
+
children: /*#__PURE__*/ _jsx(ListItemText, {
|
|
228
|
+
primary: href ? /*#__PURE__*/ _jsx(AppLink, {
|
|
229
|
+
href: href,
|
|
230
|
+
color: "inherit",
|
|
231
|
+
underline: "hover",
|
|
232
|
+
children: label
|
|
233
|
+
}) : label,
|
|
234
|
+
secondary: `${activityActorLabel(entry)} · ${(_ref = (_entry_createdAt = entry.createdAt) == null ? void 0 : (_entry_createdAt_toDate = _entry_createdAt.toDate) == null ? void 0 : _entry_createdAt_toDate.call(_entry_createdAt).toLocaleString()) != null ? _ref : ''}` + // Run-log entries carry duration (wave v6).
|
|
235
|
+
(entry.durationMs != null ? ` · ${entry.durationMs}ms` : ''),
|
|
236
|
+
slotProps: entry.status === 'error' ? {
|
|
237
|
+
primary: {
|
|
238
|
+
color: 'error'
|
|
239
|
+
}
|
|
240
|
+
} : undefined
|
|
241
|
+
})
|
|
242
|
+
}, entry.$id);
|
|
243
|
+
})
|
|
244
|
+
}),
|
|
245
|
+
/*#__PURE__*/ _jsx(ListPagination, {
|
|
246
|
+
page: page,
|
|
247
|
+
pageSize: pageSize,
|
|
248
|
+
rowCount: items.length,
|
|
249
|
+
// The window the card HOLDS, which it knows exactly. `truncated`
|
|
250
|
+
// below is what says the history is larger than that.
|
|
251
|
+
count: sorted.length,
|
|
252
|
+
onPageChange: setPage,
|
|
253
|
+
onPageSizeChange: setPageSize
|
|
254
|
+
}),
|
|
255
|
+
truncated ? /*#__PURE__*/ _jsx(Typography, {
|
|
256
|
+
variant: "caption",
|
|
257
|
+
color: "text.secondary",
|
|
258
|
+
children: targetId ? `Showing ${WINDOW} entries for this item. It has more — ` + 'these are not necessarily the newest; open the full ' + 'activity log for the ordered history.' : `Showing this site’s ${WINDOW} most recent entries. There ` + 'is more history than that — open the full activity log to ' + 'read further back.'
|
|
259
|
+
}) : null
|
|
260
|
+
]
|
|
261
|
+
}),
|
|
262
|
+
viewAllHref ? /*#__PURE__*/ _jsx(Typography, {
|
|
263
|
+
variant: "body2",
|
|
264
|
+
sx: {
|
|
265
|
+
mt: 1
|
|
266
|
+
},
|
|
267
|
+
children: /*#__PURE__*/ _jsx(Link, {
|
|
268
|
+
href: viewAllHref,
|
|
269
|
+
color: "primary",
|
|
270
|
+
underline: "hover",
|
|
271
|
+
children: 'View all activity'
|
|
272
|
+
})
|
|
273
|
+
}) : null
|
|
274
|
+
]
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
HostActivityCard.displayName = 'HostActivityCard';
|
|
278
|
+
export default HostActivityCard;
|
|
279
|
+
|
|
280
|
+
//# sourceMappingURL=host-activity-card.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-activity-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { AppLink, CardDisplay, type HelpTipContent } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport {\n Alert,\n Button,\n Link,\n List,\n ListItem,\n ListItemText,\n Stack,\n Typography,\n} from '@mui/material'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useParams } from 'next/navigation'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n ceilingedWindow,\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport {\n activityActorLabel,\n activityHref,\n activityPrimaryText,\n} from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface HostActivityCardProps {\n hostId: string\n /** Show only entries for this target id (e.g. a screen detail page). */\n targetId?: string\n header?: string\n /** When set, a \"View all activity\" link renders under the list (AGL-249). */\n viewAllHref?: string\n /** Overrides the default help affordance on the card header. */\n help?: HelpTipContent\n}\n\n/**\n * Rows fetched before the client-side sort and page slice.\n *\n * A CEILING, and the read asks for one document more than it so the card can\n * say when the window was not the whole story — `length >= WINDOW` is wrong at\n * exactly the collection size that equals the ceiling.\n */\nconst WINDOW = 200\n\n/**\n * Recent user activity (AGL-118): newest-first feed from\n * `hosts/{hostId}/activity`, optionally filtered to one target so detail\n * pages show just their own history.\n *\n * ## The \"Page Activity\" card reported nothing on a page that had changed\n *\n * This is AGL-2292 — fixed in the sibling `OrgActivityCard`, never applied\n * here. The query was `limit(200)` with NO `orderBy`. Firestore then returns\n * documents in DOCUMENT-ID order, `useHostActivityLogger` appends with\n * `addDoc()`, so the ids are effectively random: the window was a\n * pseudo-random SAMPLE of the collection rather than its newest page, and the\n * client sort below dutifully ordered that sample and sliced the top off it —\n * which made the result look right and be wrong.\n *\n * Measured on a production host: 996 activity documents, so a 200-row window\n * covers the lexicographically-lowest ids and ends at `ClSZ…`. A screen whose\n * five entries have ids beginning `Y`, `h`, `j`, `k`, `t` has every one of\n * them past that boundary — the card fetches 200 rows, matches 0, and says\n * \"No activity yet\" about a page edited that morning.\n *\n * ## Why the targeted query filters SERVER-side\n *\n * Adding `orderBy` alone is not the fix here, and the difference only shows up\n * on the per-target card. The window is host-wide, so 200 newest host-wide\n * entries still have to contain the target's own. On that same host it would\n * have surfaced 1 of the screen's 5 entries, and 40 of the 108 targets with\n * real activity would STILL have rendered \"No activity yet\" — a quieter\n * version of the same lie. A target that has not been touched recently is\n * exactly the one whose history you open the card to read.\n *\n * So `targetId` becomes a `where` and spends the window on the target instead\n * of on the host. That predicate needs no new index: an equality filter with\n * no `orderBy` is served by the automatic single-field index, which is\n * deliberate — a composite (`target.id` ASC, `createdAt` DESC) would be\n * FILE-ONLY until someone ran `firebase deploy --only firestore:indexes` by\n * hand, and every read in between would throw `failed-precondition`. The\n * ordering is done by the client sort below, which is safe at this size: the\n * busiest target in production holds 55 entries against a 200-row window.\n *\n * The un-targeted (dashboard) query has no such excuse and takes the AGL-2292\n * one-liner: `orderBy('createdAt', 'desc')`, single-field, no config change.\n *\n * ## \"Found nothing\" vs \"could not look\"\n *\n * Both used to render the empty state, and so did the first paint before any\n * snapshot arrived. A refused or failed listen now says so and offers a retry\n * (the `used-by-card` shape), because an audit feed that invents a confident\n * zero is worse than one that admits it is blind.\n *\n * ## Why the \"Show N more\" expander became the shared footer\n *\n * It was a FOURTH pagination grammar for the same act — beside the `DataGrid`\n * footer, the shared `ListPagination`, and the \"Load more\" the DAM grid keeps —\n * and it escaped the guard that exists to stop exactly that, on spelling: the\n * check looks for the literal `'Load more'` and this button said\n * `Show ${more} more`.\n *\n * On its own terms it was also the weakest of the four. It only ever grew, so\n * a reader who opened forty rows could not get back to ten without remounting\n * the card; it offered no size control, which made it one of the two grammars\n * that never let a reader choose; it could not state a total even though every\n * row was already in hand; and it said nothing when `WINDOW` bit, so a busy\n * target read as though two hundred entries were all there were.\n *\n * The footer answers all four, and the rows stay a client SLICE — see `sorted`,\n * where paging is free because the window is already fetched.\n */\nexport function HostActivityCard(props: HostActivityCardProps) {\n const {\n hostId,\n targetId,\n header = 'Recent Activity',\n viewAllHref,\n help = pluginDocsHelp('consoleTour', {\n anchor: '#a-sites-dashboard',\n excerpt:\n 'Changes made to this site from the console — publishes, media ' +\n 'saves, member changes — newest first.',\n }),\n } = props\n const { orgSlug, host } = useParams<{ orgSlug: string; host: string }>()\n const firestore = useFirestore()\n const [attempt, setAttempt] = useState(0)\n const { data: entries, status } = useFirestoreCollection<any>(\n () => {\n // `strictNullChecks` is off repo-wide, so an absent hostId would reach\n // `collection()` as a falsy path segment. Refuse to build the query\n // instead, and report it as unreadable below — never as \"no activity\".\n if (!hostId) return null\n const base = collection(firestore, 'hosts', hostId, 'activity')\n // `WINDOW + 1` on both: the extra document is the PROBE, and it is what\n // turns \"there may be more\" into a fact the card can state. It is never\n // rendered — `ceilingedWindow` drops it below.\n return targetId\n ? query(base, where('target.id', '==', targetId), limit(WINDOW + 1))\n : query(base, orderBy('createdAt', 'desc'), limit(WINDOW + 1))\n },\n [firestore, hostId, targetId, attempt],\n { idField: '$id' },\n )\n /*\n * The client sort survives the server-side ordering rather than being\n * replaced by it. It is what orders the TARGETED query (which cannot carry\n * an `orderBy` without a composite index — see above), and on the\n * un-targeted one it still catches a just-written entry whose timestamp the\n * local snapshot has not resolved. Sorting 200 items costs nothing.\n */\n /**\n * Paging is FREE here, which is why it is a slice and not a second query\n * (AGL-2486). The read above already fetches `WINDOW` rows and this then\n * threw all but a page of them away — 190 documents paid for and discarded\n * on every render of this card. Showing more of what is already in hand\n * costs nothing; only running past `WINDOW` would cost a read, and the\n * \"View all\" link exists for that.\n *\n * Sorting is safe here in a way it is not on a server-paged list: these rows\n * are the whole window, not a slice of one.\n */\n const { rows: windowRows, truncated } = ceilingedWindow<any>(entries, WINDOW)\n const sorted = useMemo(\n () =>\n [...windowRows].sort(\n (a, b) => (b.createdAt?.seconds ?? 0) - (a.createdAt?.seconds ?? 0),\n ),\n // `windowRows` is a fresh array each render; the snapshot behind it is not.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [entries],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n // Back to the first page when the card is pointed at a different subject —\n // otherwise a previous screen's page three opens the next one three pages\n // in, over a history that may not have three.\n useEffect(() => setPage(0), [targetId, hostId])\n const items = useMemo(\n () => sorted.slice(page * pageSize, page * pageSize + pageSize),\n [sorted, page, pageSize],\n )\n // Three states, not two: a read that never happened must not be reported as\n // an empty history.\n const unreadable = status === 'error' || !hostId\n\n return (\n <CardDisplay\n header={header}\n help={help}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {unreadable ? (\n <Stack spacing={1.5} sx={{ alignItems: 'flex-start' }}>\n <Alert severity=\"warning\" sx={{ width: '100%' }}>\n {'Could not read the activity log. This is NOT the same as ' +\n 'nothing having happened — do not treat this as a record ' +\n 'that the page is unchanged.'}\n </Alert>\n <Button size=\"small\" onClick={() => setAttempt((n) => n + 1)}>\n {'Try again'}\n </Button>\n </Stack>\n ) : status === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading activity…'}\n </Typography>\n ) : sorted.length === 0 ? (\n /*\n * The WINDOW's length, not the page's. A page past the end of a\n * history that shrank while it was open holds no rows and is not an\n * empty history, and this card must never confuse the two.\n */\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No activity yet — changes made in the console appear here.'}\n </Typography>\n ) : (\n <>\n <List dense disablePadding>\n {items.map((entry) => {\n const href = activityHref(entry, { orgSlug, host })\n const label = activityPrimaryText(entry)\n return (\n <ListItem key={entry.$id} disableGutters dense>\n <ListItemText\n primary={\n href ? (\n <AppLink href={href} color=\"inherit\" underline=\"hover\">\n {label}\n </AppLink>\n ) : (\n label\n )\n }\n secondary={\n `${activityActorLabel(entry)} · ${\n entry.createdAt?.toDate?.().toLocaleString() ?? ''\n }` +\n // Run-log entries carry duration (wave v6).\n (entry.durationMs != null\n ? ` · ${entry.durationMs}ms`\n : '')\n }\n slotProps={\n entry.status === 'error'\n ? { primary: { color: 'error' } }\n : undefined\n }\n />\n </ListItem>\n )\n })}\n </List>\n {/* Free paging: these rows are already fetched — see `sorted`. */}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={items.length}\n // The window the card HOLDS, which it knows exactly. `truncated`\n // below is what says the history is larger than that.\n count={sorted.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n {truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {/*\n * Two different sentences because the two queries answer\n * differently. The un-targeted one carries `orderBy('createdAt')`\n * and its window really is the newest entries; the targeted one\n * has no `orderBy` — deliberately, so it needs no composite\n * index — so its window is a document-id SAMPLE of the target's\n * history, and calling it \"most recent\" would be the same lie\n * AGL-2292 was.\n */}\n {targetId\n ? `Showing ${WINDOW} entries for this item. It has more — ` +\n 'these are not necessarily the newest; open the full ' +\n 'activity log for the ordered history.'\n : `Showing this site’s ${WINDOW} most recent entries. There ` +\n 'is more history than that — open the full activity log to ' +\n 'read further back.'}\n </Typography>\n ) : null}\n </>\n )}\n {viewAllHref ? (\n <Typography variant=\"body2\" sx={{ mt: 1 }}>\n <Link href={viewAllHref} color=\"primary\" underline=\"hover\">\n {'View all activity'}\n </Link>\n </Typography>\n ) : null}\n </CardDisplay>\n )\n}\nHostActivityCard.displayName = 'HostActivityCard'\n\nexport default HostActivityCard\n"],"names":["AppLink","CardDisplay","ListPagination","TABLE_PAGE_SIZE_DEFAULT","Alert","Button","Link","List","ListItem","ListItemText","Stack","Typography","collection","limit","orderBy","query","where","useParams","useEffect","useMemo","useState","ceilingedWindow","useFirestore","useFirestoreCollection","activityActorLabel","activityHref","activityPrimaryText","pluginDocsHelp","WINDOW","HostActivityCard","props","hostId","targetId","header","viewAllHref","help","anchor","excerpt","orgSlug","host","firestore","attempt","setAttempt","data","entries","status","base","idField","rows","windowRows","truncated","sorted","sort","a","b","createdAt","seconds","page","setPage","pageSize","setPageSize","items","slice","unreadable","contentGutterX","contentGutterY","contentBordered","spacing","sx","alignItems","severity","width","size","onClick","n","variant","color","length","dense","disablePadding","map","entry","href","label","disableGutters","primary","underline","secondary","toDate","toLocaleString","durationMs","slotProps","undefined","$id","rowCount","count","onPageChange","onPageSizeChange","mt","displayName"],"mappings":"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"}
|