@aglyn/plugins-workflows 1.0.0-beta.231 → 1.0.0-beta.233

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-workflows",
3
- "version": "1.0.0-beta.231",
3
+ "version": "1.0.0-beta.233",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,17 +25,17 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.231",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.231",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.231",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.231",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.231",
33
- "@aglyn/shared-util-email": "1.0.0-beta.231",
34
- "@aglyn/shared-util-http": "1.0.0-beta.231",
35
- "@aglyn/shared-util-timestamp": "1.0.0-beta.231",
36
- "@aglyn/tenant-data-admin": "1.0.0-beta.231",
37
- "@aglyn/tenant-feature-instance": "1.0.0-beta.231",
38
- "@aglyn/tenant-runtime": "1.0.0-beta.231",
28
+ "@aglyn/aglyn": "1.0.0-beta.233",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.233",
30
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.233",
31
+ "@aglyn/shared-ui-next": "1.0.0-beta.233",
32
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.233",
33
+ "@aglyn/shared-util-email": "1.0.0-beta.233",
34
+ "@aglyn/shared-util-http": "1.0.0-beta.233",
35
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.233",
36
+ "@aglyn/tenant-data-admin": "1.0.0-beta.233",
37
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.233",
38
+ "@aglyn/tenant-runtime": "1.0.0-beta.233",
39
39
  "@swc/helpers": "0.5.23"
40
40
  },
41
41
  "peerDependencies": {
@@ -26,9 +26,10 @@ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/compon
26
26
  import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
27
27
  import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
28
28
  import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
29
+ import { useListColumnSort } from "@aglyn/shared-ui-jsx/hooks/use-list-column-sort";
29
30
  import { Chip, Stack, Tooltip, Typography } from "@mui/material";
30
31
  import { collection } from "firebase/firestore";
31
- import { useMemo } from "react";
32
+ import { useMemo, useState } from "react";
32
33
  import { useFirestore } from "@aglyn/tenant-feature-instance";
33
34
  import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
34
35
  import { pluginDocsHelp } from "@aglyn/aglyn";
@@ -54,6 +55,12 @@ const RESULT_LABEL = {
54
55
  }))
55
56
  };
56
57
  const RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS);
58
+ /** Who set a run off is read from its actor fields: it sorts the page (AGL-3680). */ const RUN_PAGE_SORTS = {
59
+ who: (row)=>row.who
60
+ };
61
+ const RUN_PAGE_SORT_HEADERS = {
62
+ who: 'Who'
63
+ };
57
64
  /**
58
65
  * The run-history table `/product/workflows` advertises (AGL-2171):
59
66
  * `Time | Trigger | Result | What happened`.
@@ -94,6 +101,10 @@ const RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS);
94
101
  const gridFilter = useListGridFilter({
95
102
  selectFields: RUN_SELECT_FIELDS
96
103
  });
104
+ /*
105
+ * EVERY HEADER SORTS (AGL-3680): Time, Trigger, Result and What happened
106
+ * on the query (`RUN_HISTORY_QUERY.sorts`), Who over the page.
107
+ */ const [askedSort, setAskedSort] = useState(null);
97
108
  const activity = useMemo(()=>hostId ? collection(firestore, 'hosts', hostId, 'activity') : null, [
98
109
  firestore,
99
110
  hostId
@@ -111,7 +122,8 @@ const RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS);
111
122
  request: {
112
123
  clauses: gridFilter.clauses,
113
124
  search: gridFilter.searchWords,
114
- base: runHistoryBase(targetId, gridFilter.clauses)
125
+ base: runHistoryBase(targetId, gridFilter.clauses),
126
+ sort: askedSort
115
127
  },
116
128
  deps: [
117
129
  firestore,
@@ -140,6 +152,16 @@ const RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS);
140
152
  }), [
141
153
  entries
142
154
  ]);
155
+ const columnSort = useListColumnSort({
156
+ sorts: RUN_HISTORY_QUERY.sorts,
157
+ defaultSort: RUN_HISTORY_QUERY.sorts[0],
158
+ sort: askedSort,
159
+ onSortChange: setAskedSort,
160
+ orderBy: plan.orderBy,
161
+ rows: runs,
162
+ pageSorts: RUN_PAGE_SORTS,
163
+ headers: RUN_PAGE_SORT_HEADERS
164
+ });
143
165
  const refused = useMemo(()=>listQueryRefusals(plan.refused, {
144
166
  fields: RUN_HISTORY_QUERY.fields,
145
167
  headers: RUN_HISTORY_HEADERS,
@@ -245,19 +267,21 @@ const RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS);
245
267
  }),
246
268
  /*#__PURE__*/ _jsx(ListQueryNotices, {
247
269
  refused: refused,
248
- notices: plan.notices
270
+ notices: [
271
+ ...plan.notices,
272
+ ...columnSort.notices
273
+ ]
249
274
  }),
250
275
  /*#__PURE__*/ _jsx(ListTable, {
251
276
  "aria-label": "Run history",
252
- rows: runs,
277
+ rows: columnSort.rows,
253
278
  columns: columns,
254
279
  filterMode: "server",
255
280
  filterModel: gridFilter.filterModel,
256
281
  onFilterModelChange: gridFilter.onFilterModelChange,
257
282
  quickFilter: true,
258
- // Newest first is the query's one order.
259
- sortingMode: "server",
260
- disableColumnSorting: true,
283
+ // The headers order the query, Who the page (`columnSort`).
284
+ columnSort: columnSort,
261
285
  // A failed run carries what other plugins add under its summary.
262
286
  getRowHeight: ()=>'auto',
263
287
  // `ListPagination` below pages the query.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-run-history-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 actionRunResult,\n actionRunSummary,\n actionTriggerLabel,\n runTriggeredByLabel,\n} from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { HOST_EVENT_TYPES } from '@aglyn/aglyn/app-utils/host-events'\nimport { AUTOMATION_RUN_ZONE, type AutomationTarget } from './workflow-zones'\nimport { CardDisplay, type HelpTipContent } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n RUN_HISTORY_HEADERS,\n RUN_HISTORY_QUERY,\n runHistoryBase,\n} from '../model/run-history'\n\nexport interface HostRunHistoryCardProps {\n hostId: string\n /** The org the page names, for what other plugins add to a failed run. */\n orgId?: string\n /** Show only runs of this action/workflow. */\n targetId?: string\n /**\n * What `targetId` names, and its name. A run row records its automation's id\n * but not whether it is an action or a workflow, so the card that opened the\n * history says; without it, a failed run offers nothing beside its summary.\n */\n targetType?: AutomationTarget['type']\n targetName?: string\n header?: string\n /** Overrides the default help affordance on the card header. */\n help?: HelpTipContent\n}\n\nconst RESULT_COLOR = {\n succeeded: 'success',\n failed: 'error',\n skipped: 'warning',\n} as const\n\nconst RESULT_LABEL = {\n succeeded: 'Succeeded',\n failed: 'Failed',\n skipped: 'Skipped',\n} as const\n\ntype RunResult = keyof typeof RESULT_LABEL\n\n/** One run as the grid holds it: the entry, and what it is shown by. */\ninterface RunRow {\n $id: string\n entry: any\n result: RunResult\n trigger: string\n triggerLabel: string\n /** Who set the run off (AGL-3376). */\n who: string\n summary: string\n createdAtMs: number | null\n}\n\n/** Trigger and Result are picked: the event list and the three verdicts. */\nconst RUN_FILTER_OPTIONS = {\n trigger: HOST_EVENT_TYPES.map((event) => ({\n value: event,\n label: actionTriggerLabel(event),\n })),\n result: (Object.keys(RESULT_LABEL) as RunResult[]).map((result) => ({\n value: result,\n label: RESULT_LABEL[result],\n })),\n}\nconst RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS)\n\n/**\n * The run-history table `/product/workflows` advertises (AGL-2171):\n * `Time | Trigger | Result | What happened`.\n *\n * A separate component from `HostActivityCard` on purpose. That card is\n * the site's general feed — publishes, media saves, member changes — and\n * a run is a different record with different columns. Pointing the Runs\n * dialog at the general feed is what produced a list of\n * `Action ran on formSubmission — My automation` where the mockup shows\n * four columns.\n *\n * EVERY FILTER IS ON THE QUERY (AGL-3321). The history is one Firestore\n * query over `hosts/{hostId}/activity` — this automation's entries, runs\n * only, newest first — with each Filters-panel clause and the search word\n * as another predicate on it (`RUN_HISTORY_QUERY`), and it pages by that\n * query. A filtered page is a page of matches from the whole history, not\n * the matches among entries already read; what one query cannot hold is\n * said above the grid and not applied.\n */\nexport function HostRunHistoryCard(props: HostRunHistoryCardProps) {\n const {\n hostId,\n orgId,\n targetId,\n targetType,\n targetName = '',\n header = 'Run history',\n help = pluginDocsHelp('buildAWorkflow', {\n anchor: '#4-save-and-test',\n excerpt:\n 'Every run of this automation, including the ones a trigger ' +\n 'condition skipped — which is the answer to \"why did it not fire?\".',\n }),\n } = props\n const firestore = useFirestore()\n /**\n * The shell's zone renderer (AGL-2919), for what other plugins add to a\n * failed run; `null` outside the console shell.\n */\n const RunZone = useConsoleWidgetSlot()\n /** The automation a failed run belongs to, as the `automationRun` zone names it. */\n const zoneTarget = useMemo<AutomationTarget | null>(\n () =>\n targetId && targetType\n ? { type: targetType, id: targetId, name: targetName }\n : null,\n [targetId, targetType, targetName],\n )\n const gridFilter = useListGridFilter({ selectFields: RUN_SELECT_FIELDS })\n const activity = useMemo(\n () => (hostId ? collection(firestore, 'hosts', hostId, 'activity') : null),\n [firestore, hostId],\n )\n /*\n * `target.id` equality keeps the read proportional to the card — this\n * automation's rows, not the site's — and `result` keeps it to runs.\n * Both, and every clause, are served by the `activity` composites the\n * index file carries for `RUN_HISTORY_QUERY` beneath `createdAt` DESC.\n * A different workflow is a different history, and a new plan a new\n * query: the pager starts over on either.\n */\n const {\n rows: entries,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n } = useListQuery<any>({\n collection: activity,\n declaration: RUN_HISTORY_QUERY,\n request: {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n base: runHistoryBase(targetId, gridFilter.clauses),\n },\n deps: [firestore, hostId, targetId],\n idField: '$id',\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0\n\n const runs = useMemo(\n () =>\n entries.map((entry): RunRow => ({\n $id: String(entry.$id),\n entry,\n // Every entry the query returns carries a verdict; the presenter\n // still reads it, so a row renders the way the feed names it.\n result: actionRunResult(entry) ?? 'succeeded',\n trigger: String(entry.trigger ?? ''),\n // `formSubmission` → `Form submitted`.\n triggerLabel: actionTriggerLabel(entry.trigger),\n who: runTriggeredByLabel(entry),\n summary: actionRunSummary(entry),\n createdAtMs:\n typeof entry.createdAt?.seconds === 'number'\n ? entry.createdAt.seconds * 1000\n : null,\n })),\n [entries],\n )\n const refused = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: RUN_HISTORY_QUERY.fields,\n headers: RUN_HISTORY_HEADERS,\n options: RUN_FILTER_OPTIONS,\n }),\n [plan],\n )\n\n const columns = useMemo(\n () =>\n listFilterGridColumns(\n [\n {\n field: 'createdAtMs',\n headerName: 'Time',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => {\n const at = row.entry.createdAt?.toDate?.()\n return (\n <Tooltip title={at ? at.toLocaleString() : ''}>\n <span>{at ? at.toLocaleTimeString() : '--'}</span>\n </Tooltip>\n )\n },\n },\n {\n field: 'trigger',\n headerName: 'Trigger',\n width: 180,\n renderCell: ({ row }: { row: RunRow }) => row.triggerLabel,\n },\n {\n field: 'who',\n headerName: 'Who',\n description: 'Who set this run off: the person, visitor, key or system whose act fired the trigger.',\n width: 200,\n renderCell: ({ row }: { row: RunRow }) => row.who,\n },\n {\n field: 'result',\n headerName: 'Result',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={RESULT_COLOR[row.result]}\n label={RESULT_LABEL[row.result]}\n />\n ),\n },\n {\n field: 'summary',\n headerName: 'What happened',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }: { row: RunRow }) => (\n <Stack sx={{ minWidth: 0, py: 0.75 }}>\n <Typography variant=\"body2\">{row.summary}</Typography>\n {row.entry.durationMs != null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${row.entry.durationMs}ms`}\n </Typography>\n ) : null}\n {/*\n What other plugins add to a failed run (AGL-2919): the\n `automationRun` zone, through the shell's gated slot.\n */}\n {RunZone && zoneTarget && row.result === 'failed' ? (\n <RunZone\n slot={AUTOMATION_RUN_ZONE.id}\n hostId={hostId}\n orgId={orgId}\n target={zoneTarget}\n runId={row.$id}\n />\n ) : null}\n </Stack>\n ),\n },\n ] as GridColDef[],\n RUN_HISTORY_QUERY.fields,\n RUN_FILTER_OPTIONS,\n RUN_HISTORY_HEADERS,\n ),\n [RunZone, zoneTarget, hostId, orgId],\n )\n\n return (\n <CardDisplay\n header={header}\n help={help}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!filtering && page === 0 && runs.length === 0 && status === 'success' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No runs yet — every run of this automation is logged here, ' +\n 'including the ones a condition skipped.'}\n </Typography>\n ) : (\n <Stack spacing={1.5}>\n <ListFilterChips\n fields={RUN_HISTORY_QUERY.fields}\n headers={RUN_HISTORY_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={RUN_FILTER_OPTIONS}\n />\n <ListQueryNotices refused={refused} notices={plan.notices} />\n <ListTable\n aria-label=\"Run history\"\n rows={runs}\n columns={columns}\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // Newest first is the query's one order.\n sortingMode=\"server\"\n disableColumnSorting\n // A failed run carries what other plugins add under its summary.\n getRowHeight={() => 'auto'}\n // `ListPagination` below pages the query.\n hideFooter\n noRowsLabel=\"No runs match these filters\"\n />\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={runs.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n )}\n </CardDisplay>\n )\n}\nHostRunHistoryCard.displayName = 'HostRunHistoryCard'\n\nexport default HostRunHistoryCard\n"],"names":["actionRunResult","actionRunSummary","actionTriggerLabel","runTriggeredByLabel","useConsoleWidgetSlot","HOST_EVENT_TYPES","AUTOMATION_RUN_ZONE","CardDisplay","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListTable","listFilterGridColumns","useListGridFilter","Chip","Stack","Tooltip","Typography","collection","useMemo","useFirestore","useListQuery","pluginDocsHelp","RUN_HISTORY_HEADERS","RUN_HISTORY_QUERY","runHistoryBase","RESULT_COLOR","succeeded","failed","skipped","RESULT_LABEL","RUN_FILTER_OPTIONS","trigger","map","event","value","label","result","Object","keys","RUN_SELECT_FIELDS","HostRunHistoryCard","props","hostId","orgId","targetId","targetType","targetName","header","help","anchor","excerpt","firestore","RunZone","zoneTarget","type","id","name","gridFilter","selectFields","activity","rows","entries","status","hasMore","page","setPage","pageSize","setPageSize","plan","declaration","request","clauses","search","searchWords","base","deps","idField","filtering","length","runs","entry","$id","String","triggerLabel","who","summary","createdAtMs","createdAt","seconds","refused","fields","headers","options","columns","field","headerName","width","renderCell","row","at","toDate","title","toLocaleString","span","toLocaleTimeString","description","size","variant","color","flex","minWidth","sx","py","durationMs","slot","target","runId","contentGutterX","contentGutterY","contentBordered","spacing","onChange","setClauses","notices","aria-label","filterMode","filterModel","onFilterModelChange","quickFilter","sortingMode","disableColumnSorting","getRowHeight","hideFooter","noRowsLabel","rowCount","onPageChange","onPageSizeChange","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-run-history-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 actionRunResult,\n actionRunSummary,\n actionTriggerLabel,\n runTriggeredByLabel,\n} from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { HOST_EVENT_TYPES } from '@aglyn/aglyn/app-utils/host-events'\nimport { AUTOMATION_RUN_ZONE, type AutomationTarget } from './workflow-zones'\nimport { CardDisplay, type HelpTipContent } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport type { ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n RUN_HISTORY_HEADERS,\n RUN_HISTORY_QUERY,\n runHistoryBase,\n} from '../model/run-history'\n\nexport interface HostRunHistoryCardProps {\n hostId: string\n /** The org the page names, for what other plugins add to a failed run. */\n orgId?: string\n /** Show only runs of this action/workflow. */\n targetId?: string\n /**\n * What `targetId` names, and its name. A run row records its automation's id\n * but not whether it is an action or a workflow, so the card that opened the\n * history says; without it, a failed run offers nothing beside its summary.\n */\n targetType?: AutomationTarget['type']\n targetName?: string\n header?: string\n /** Overrides the default help affordance on the card header. */\n help?: HelpTipContent\n}\n\nconst RESULT_COLOR = {\n succeeded: 'success',\n failed: 'error',\n skipped: 'warning',\n} as const\n\nconst RESULT_LABEL = {\n succeeded: 'Succeeded',\n failed: 'Failed',\n skipped: 'Skipped',\n} as const\n\ntype RunResult = keyof typeof RESULT_LABEL\n\n/** One run as the grid holds it: the entry, and what it is shown by. */\ninterface RunRow {\n $id: string\n entry: any\n result: RunResult\n trigger: string\n triggerLabel: string\n /** Who set the run off (AGL-3376). */\n who: string\n summary: string\n createdAtMs: number | null\n}\n\n/** Trigger and Result are picked: the event list and the three verdicts. */\nconst RUN_FILTER_OPTIONS = {\n trigger: HOST_EVENT_TYPES.map((event) => ({\n value: event,\n label: actionTriggerLabel(event),\n })),\n result: (Object.keys(RESULT_LABEL) as RunResult[]).map((result) => ({\n value: result,\n label: RESULT_LABEL[result],\n })),\n}\nconst RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS)\n\n/** Who set a run off is read from its actor fields: it sorts the page (AGL-3680). */\nconst RUN_PAGE_SORTS = { who: (row: RunRow) => row.who }\nconst RUN_PAGE_SORT_HEADERS = { who: 'Who' }\n\n/**\n * The run-history table `/product/workflows` advertises (AGL-2171):\n * `Time | Trigger | Result | What happened`.\n *\n * A separate component from `HostActivityCard` on purpose. That card is\n * the site's general feed — publishes, media saves, member changes — and\n * a run is a different record with different columns. Pointing the Runs\n * dialog at the general feed is what produced a list of\n * `Action ran on formSubmission — My automation` where the mockup shows\n * four columns.\n *\n * EVERY FILTER IS ON THE QUERY (AGL-3321). The history is one Firestore\n * query over `hosts/{hostId}/activity` — this automation's entries, runs\n * only, newest first — with each Filters-panel clause and the search word\n * as another predicate on it (`RUN_HISTORY_QUERY`), and it pages by that\n * query. A filtered page is a page of matches from the whole history, not\n * the matches among entries already read; what one query cannot hold is\n * said above the grid and not applied.\n */\nexport function HostRunHistoryCard(props: HostRunHistoryCardProps) {\n const {\n hostId,\n orgId,\n targetId,\n targetType,\n targetName = '',\n header = 'Run history',\n help = pluginDocsHelp('buildAWorkflow', {\n anchor: '#4-save-and-test',\n excerpt:\n 'Every run of this automation, including the ones a trigger ' +\n 'condition skipped — which is the answer to \"why did it not fire?\".',\n }),\n } = props\n const firestore = useFirestore()\n /**\n * The shell's zone renderer (AGL-2919), for what other plugins add to a\n * failed run; `null` outside the console shell.\n */\n const RunZone = useConsoleWidgetSlot()\n /** The automation a failed run belongs to, as the `automationRun` zone names it. */\n const zoneTarget = useMemo<AutomationTarget | null>(\n () =>\n targetId && targetType\n ? { type: targetType, id: targetId, name: targetName }\n : null,\n [targetId, targetType, targetName],\n )\n const gridFilter = useListGridFilter({ selectFields: RUN_SELECT_FIELDS })\n /*\n * EVERY HEADER SORTS (AGL-3680): Time, Trigger, Result and What happened\n * on the query (`RUN_HISTORY_QUERY.sorts`), Who over the page.\n */\n const [askedSort, setAskedSort] = useState<ListQuerySort | null>(null)\n const activity = useMemo(\n () => (hostId ? collection(firestore, 'hosts', hostId, 'activity') : null),\n [firestore, hostId],\n )\n /*\n * `target.id` equality keeps the read proportional to the card — this\n * automation's rows, not the site's — and `result` keeps it to runs.\n * Both, and every clause, are served by the `activity` composites the\n * index file carries for `RUN_HISTORY_QUERY` beneath `createdAt` DESC.\n * A different workflow is a different history, and a new plan a new\n * query: the pager starts over on either.\n */\n const {\n rows: entries,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n } = useListQuery<any>({\n collection: activity,\n declaration: RUN_HISTORY_QUERY,\n request: {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n base: runHistoryBase(targetId, gridFilter.clauses),\n sort: askedSort,\n },\n deps: [firestore, hostId, targetId],\n idField: '$id',\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0\n\n const runs = useMemo(\n () =>\n entries.map((entry): RunRow => ({\n $id: String(entry.$id),\n entry,\n // Every entry the query returns carries a verdict; the presenter\n // still reads it, so a row renders the way the feed names it.\n result: actionRunResult(entry) ?? 'succeeded',\n trigger: String(entry.trigger ?? ''),\n // `formSubmission` → `Form submitted`.\n triggerLabel: actionTriggerLabel(entry.trigger),\n who: runTriggeredByLabel(entry),\n summary: actionRunSummary(entry),\n createdAtMs:\n typeof entry.createdAt?.seconds === 'number'\n ? entry.createdAt.seconds * 1000\n : null,\n })),\n [entries],\n )\n const columnSort = useListColumnSort<RunRow>({\n sorts: RUN_HISTORY_QUERY.sorts,\n defaultSort: RUN_HISTORY_QUERY.sorts[0],\n sort: askedSort,\n onSortChange: setAskedSort,\n orderBy: plan.orderBy,\n rows: runs,\n pageSorts: RUN_PAGE_SORTS,\n headers: RUN_PAGE_SORT_HEADERS,\n })\n const refused = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: RUN_HISTORY_QUERY.fields,\n headers: RUN_HISTORY_HEADERS,\n options: RUN_FILTER_OPTIONS,\n }),\n [plan],\n )\n\n const columns = useMemo(\n () =>\n listFilterGridColumns(\n [\n {\n field: 'createdAtMs',\n headerName: 'Time',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => {\n const at = row.entry.createdAt?.toDate?.()\n return (\n <Tooltip title={at ? at.toLocaleString() : ''}>\n <span>{at ? at.toLocaleTimeString() : '--'}</span>\n </Tooltip>\n )\n },\n },\n {\n field: 'trigger',\n headerName: 'Trigger',\n width: 180,\n renderCell: ({ row }: { row: RunRow }) => row.triggerLabel,\n },\n {\n field: 'who',\n headerName: 'Who',\n description: 'Who set this run off: the person, visitor, key or system whose act fired the trigger.',\n width: 200,\n renderCell: ({ row }: { row: RunRow }) => row.who,\n },\n {\n field: 'result',\n headerName: 'Result',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={RESULT_COLOR[row.result]}\n label={RESULT_LABEL[row.result]}\n />\n ),\n },\n {\n field: 'summary',\n headerName: 'What happened',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }: { row: RunRow }) => (\n <Stack sx={{ minWidth: 0, py: 0.75 }}>\n <Typography variant=\"body2\">{row.summary}</Typography>\n {row.entry.durationMs != null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${row.entry.durationMs}ms`}\n </Typography>\n ) : null}\n {/*\n What other plugins add to a failed run (AGL-2919): the\n `automationRun` zone, through the shell's gated slot.\n */}\n {RunZone && zoneTarget && row.result === 'failed' ? (\n <RunZone\n slot={AUTOMATION_RUN_ZONE.id}\n hostId={hostId}\n orgId={orgId}\n target={zoneTarget}\n runId={row.$id}\n />\n ) : null}\n </Stack>\n ),\n },\n ] as GridColDef[],\n RUN_HISTORY_QUERY.fields,\n RUN_FILTER_OPTIONS,\n RUN_HISTORY_HEADERS,\n ),\n [RunZone, zoneTarget, hostId, orgId],\n )\n\n return (\n <CardDisplay\n header={header}\n help={help}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!filtering && page === 0 && runs.length === 0 && status === 'success' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No runs yet — every run of this automation is logged here, ' +\n 'including the ones a condition skipped.'}\n </Typography>\n ) : (\n <Stack spacing={1.5}>\n <ListFilterChips\n fields={RUN_HISTORY_QUERY.fields}\n headers={RUN_HISTORY_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={RUN_FILTER_OPTIONS}\n />\n <ListQueryNotices refused={refused} notices={[...plan.notices, ...columnSort.notices]} />\n <ListTable\n aria-label=\"Run history\"\n rows={columnSort.rows}\n columns={columns}\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // The headers order the query, Who the page (`columnSort`).\n columnSort={columnSort}\n // A failed run carries what other plugins add under its summary.\n getRowHeight={() => 'auto'}\n // `ListPagination` below pages the query.\n hideFooter\n noRowsLabel=\"No runs match these filters\"\n />\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={runs.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n )}\n </CardDisplay>\n )\n}\nHostRunHistoryCard.displayName = 'HostRunHistoryCard'\n\nexport default HostRunHistoryCard\n"],"names":["actionRunResult","actionRunSummary","actionTriggerLabel","runTriggeredByLabel","useConsoleWidgetSlot","HOST_EVENT_TYPES","AUTOMATION_RUN_ZONE","CardDisplay","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListTable","listFilterGridColumns","useListGridFilter","useListColumnSort","Chip","Stack","Tooltip","Typography","collection","useMemo","useState","useFirestore","useListQuery","pluginDocsHelp","RUN_HISTORY_HEADERS","RUN_HISTORY_QUERY","runHistoryBase","RESULT_COLOR","succeeded","failed","skipped","RESULT_LABEL","RUN_FILTER_OPTIONS","trigger","map","event","value","label","result","Object","keys","RUN_SELECT_FIELDS","RUN_PAGE_SORTS","who","row","RUN_PAGE_SORT_HEADERS","HostRunHistoryCard","props","hostId","orgId","targetId","targetType","targetName","header","help","anchor","excerpt","firestore","RunZone","zoneTarget","type","id","name","gridFilter","selectFields","askedSort","setAskedSort","activity","rows","entries","status","hasMore","page","setPage","pageSize","setPageSize","plan","declaration","request","clauses","search","searchWords","base","sort","deps","idField","filtering","length","runs","entry","$id","String","triggerLabel","summary","createdAtMs","createdAt","seconds","columnSort","sorts","defaultSort","onSortChange","orderBy","pageSorts","headers","refused","fields","options","columns","field","headerName","width","renderCell","at","toDate","title","toLocaleString","span","toLocaleTimeString","description","size","variant","color","flex","minWidth","sx","py","durationMs","slot","target","runId","contentGutterX","contentGutterY","contentBordered","spacing","onChange","setClauses","notices","aria-label","filterMode","filterModel","onFilterModelChange","quickFilter","getRowHeight","hideFooter","noRowsLabel","rowCount","onPageChange","onPageSizeChange","displayName"],"mappings":"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"}
@@ -31,7 +31,7 @@ import { useFirestore, useFirestoreCollection, useFirestoreDoc, useHostResourceA
31
31
  * Firestore hooks, and a query builder imported through that barrel disappears
32
32
  * under the mock. Neither of these is a hook.
33
33
  */ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
34
- import { WEBHOOK_URL_PATTERN } from "../model/webhooks.js";
34
+ import { WEBHOOK_URL_PATTERN, maskedWebhookUrl } from "../model/webhooks.js";
35
35
  import { WEBHOOK_MAX_PER_HOST } from "@aglyn/aglyn/plugin-manager/plugin-host-resources";
36
36
  /**
37
37
  * How many webhook documents the card reads.
@@ -264,7 +264,7 @@ function generateSecret() {
264
264
  variant: "caption",
265
265
  color: "text.secondary",
266
266
  noWrap: true,
267
- children: hook.direction === 'outbound' ? `outbound · ${hook.url}` : `inbound · ${siteBase}/api/hooks/${hostId}/${hook.$id}` + ` → ${hook.workflowName}`
267
+ children: hook.direction === 'outbound' ? `outbound · ${maskedWebhookUrl(hook.url)}` : `inbound · ${siteBase}/api/hooks/${hostId}/${hook.$id}` + ` → ${hook.workflowName}`
268
268
  })
269
269
  ]
270
270
  }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-webhooks-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 type AglynOrgBilling,\n checkEntitlement,\n createResourceUid,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { hostPublicOrigin } from '@aglyn/aglyn/app-utils/host-naming'\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 MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, updateDoc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useHostResourceApi,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — the specs that render\n * this card mock `@aglyn/tenant-feature-instance` wholesale to stage their\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { WEBHOOK_URL_PATTERN } from '../model/webhooks'\nimport { WEBHOOK_MAX_PER_HOST } from '@aglyn/aglyn/plugin-manager/plugin-host-resources'\n\n/**\n * How many webhook documents the card reads.\n *\n * Above `WEBHOOK_MAX_PER_HOST` on purpose. The cap counts LIVE hooks and the\n * collection keeps soft-deleted ones, so a site that has created and deleted\n * its five several times holds more documents than it may have hooks.\n */\nconst WEBHOOK_CEILING = 20\n\n/**\n * How many workflows the inbound-endpoint picker offers.\n *\n * Paid for only while the editor is open, which is what makes a ceiling this\n * size affordable: it is one operator mid-edit rather than every visitor to\n * the page.\n */\nconst WORKFLOW_OPTION_CEILING = 100\n\nfunction generateSecret(): string {\n const bytes = new Uint8Array(24)\n crypto.getRandomValues(bytes)\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(\n '',\n )\n}\n\ninterface WebhookDraft {\n id: string | null\n name: string\n direction: 'outbound' | 'inbound'\n url: string\n workflowName: string\n secret: string\n}\n\n/**\n * Webhooks manager (AGL-149): outbound targets (used by the actions\n * builder's \"Send a webhook\" step, HMAC-signed) and inbound endpoints\n * (`/api/hooks/{hostId}/{hookId}`, secret-verified, run a workflow with\n * the payload in scope). Business tier.\n */\nexport function HostWebhooksCard(props: {\n hostId: string\n org?: Partial<AglynOrgBilling>\n}) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createResource = useHostResourceApi()\n\n\n const [draft, setDraft] = useState<WebhookDraft | 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 workflow picker below keys its\n * listeners on it. Tracking the dialog would tear that subscription 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 window 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 * ORDERED AND CEILINGED (AGL-2501). A bare `limit` is answered in\n * DOCUMENT-ID order, so an unnamed window is an arbitrary twenty that the\n * `localeCompare` below would arrange alphabetically. `collectionCeiling`\n * does not change WHICH twenty; it NAMES the order, and the probe row it\n * adds is what lets the card say when the ceiling bites.\n */\n const { data: webhookRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'webhooks'),\n WEBHOOK_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: readWebhooks, truncated: webhooksTruncated } =\n ceilingedWindow<any>(webhookRead, WEBHOOK_CEILING)\n /*\n * READ WHEN THE EDITOR OPENS, not when the card mounts. The only thing that\n * consumes these rows is the inbound-endpoint select inside the dialog — the\n * table renders a hook's stored `workflowName` — so an unconditional\n * listener would charge every visitor to this page a hundred workflows to\n * fill a control most of them never see, and one that only appears for the\n * inbound half of the form at that.\n */\n const { data: workflowRead } = useFirestoreCollection<any>(\n () =>\n editorOpened\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'workflows'),\n WORKFLOW_OPTION_CEILING,\n )\n : null,\n [firestore, hostId, editorOpened],\n { idField: '$id' },\n )\n const { rows: readWorkflows, truncated: workflowOptionsTruncated } =\n ceilingedWindow<any>(workflowRead, WORKFLOW_OPTION_CEILING)\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n { idField: '$id' },\n )\n // Sorting the whole ceiling, not a page of it.\n const webhooks = [...readWebhooks]\n .filter((hook: any) => !hook.deletedAt)\n .sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n const workflowNames = readWorkflows\n .filter((workflow: any) => !workflow.deletedAt && workflow.name)\n .map((workflow: any) => workflow.name as string)\n .sort()\n // `hostPublicOrigin` (AGL-2195) — this base is printed into the webhook\n // sample payload an operator copies into a third-party system.\n const siteBase = hostPublicOrigin(host) ?? ''\n\n const handleAdd = useCallback(() => {\n if (!checkEntitlement(org, 'webhooks')) {\n return void enqueueSnackbar(\n 'Webhooks require a Business plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n }\n // An affordance, NOT the limit (AGL-1360). `webhooks` comes from a\n // Firestore listener and the console runs `persistentLocalCache`, so this\n // count can be arbitrarily stale — it is here to fail fast when it\n // happens to be right, never to authorise the create. The cap that holds\n // is counted server-side in /api/hosts/resources.\n if (webhooks.length >= WEBHOOK_MAX_PER_HOST) {\n return void enqueueSnackbar(\n `Webhooks are capped at ${WEBHOOK_MAX_PER_HOST} per site`,\n { variant: 'warning', persist: false },\n )\n }\n setDraft({\n id: null,\n name: '',\n direction: 'outbound',\n url: '',\n workflowName: '',\n secret: generateSecret(),\n })\n }, [org, webhooks.length, enqueueSnackbar])\n\n const handleSave = useCallback(async () => {\n if (!draft || !draft.name.trim()) return\n if (draft.direction === 'outbound') {\n if (!WEBHOOK_URL_PATTERN.test(draft.url.trim())) {\n return void enqueueSnackbar(\n 'Outbound URLs must be public https addresses',\n { variant: 'warning', persist: false },\n )\n }\n } else if (!draft.workflowName.trim()) {\n return void enqueueSnackbar('Pick the workflow this endpoint runs', {\n variant: 'warning',\n persist: false,\n })\n }\n try {\n // Creates go through /api/hosts/resources (AGL-1360), which counts the\n // live webhooks with the Admin SDK before allowing one more; Firestore\n // rules deny client `create` on this collection. The card has no edit\n // path — every save is a create — so there is no update branch here.\n await createResource({\n hostId,\n resource: 'webhook',\n id: createResourceUid(),\n data: {\n name: draft.name.trim().slice(0, 60),\n direction: draft.direction,\n ...(draft.direction === 'outbound'\n ? { url: draft.url.trim() }\n : { workflowName: draft.workflowName.trim() }),\n secret: draft.secret,\n enabled: true,\n },\n })\n setDraft(null)\n enqueueSnackbar('Webhook saved', { variant: 'success', persist: false })\n } catch (error: any) {\n console.error(error)\n // The route's message is the useful one — it names the cap, the\n // entitlement or the role that refused.\n enqueueSnackbar(error?.message || 'An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [draft, createResource, hostId, enqueueSnackbar])\n\n const handleDelete = useCallback(\n (hook: any) => async () => {\n const confirmed = await confirm({\n title: 'Delete this webhook?',\n description: `\"${hook.name}\" stops ${\n hook.direction === 'outbound' ? 'delivering' : 'accepting calls'\n } immediately.`,\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, 'webhooks', hook.$id), {\n deletedAt: Timestamp.now(),\n })\n },\n [confirm, firestore, hostId],\n )\n\n return (\n <CardDisplay\n header={'Webhooks'}\n help={pluginDocsHelp('webhooks', { anchor: '#outbound-webhooks' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Send signed JSON to outside systems from the actions builder, ' +\n 'or accept calls that run a workflow. Business plans.'}\n </Typography>\n {webhooks.map((hook: any) => (\n <Stack\n key={hook.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {hook.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {hook.direction === 'outbound'\n ? `outbound · ${hook.url}`\n : `inbound · ${siteBase}/api/hooks/${hostId}/${hook.$id}` +\n ` → ${hook.workflowName}`}\n </Typography>\n </Stack>\n {hook.direction === 'inbound' ? (\n <Button\n size=\"small\"\n onClick={() => {\n void navigator.clipboard.writeText(\n `${siteBase}/api/hooks/${hostId}/${hook.$id}`,\n )\n enqueueSnackbar(\n 'Endpoint URL copied — send the secret in x-aglyn-secret',\n { variant: 'success', persist: false },\n )\n }}\n >\n {'Copy URL'}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n onClick={() => {\n void navigator.clipboard.writeText(hook.secret ?? '')\n enqueueSnackbar('Secret copied', {\n variant: 'success',\n persist: false,\n })\n }}\n >\n {'Secret'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(hook)}>\n {'Delete'}\n </Button>\n </Stack>\n ))}\n {webhooksTruncated ? (\n <Alert severity=\"info\">\n {`Showing the first ${WEBHOOK_CEILING} webhook documents on this ` +\n 'site, ordered by id. There are more, so a hook may be missing ' +\n 'from this list.'}\n </Alert>\n ) : null}\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={handleAdd}\n >\n {'Add webhook'}\n </Button>\n </Stack>\n\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Add webhook'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n label=\"Name\"\n value={draft?.name ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, name: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <TextField\n select\n label=\"Direction\"\n value={draft?.direction ?? 'outbound'}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? { ...prev, direction: event.target.value as any }\n : prev,\n )\n }\n size=\"small\"\n >\n <MenuItem value=\"outbound\">\n {'Outbound — send data to a URL'}\n </MenuItem>\n <MenuItem value=\"inbound\">\n {'Inbound — receive data, run a workflow'}\n </MenuItem>\n </TextField>\n {draft?.direction === 'outbound' ? (\n <TextField\n label=\"Delivery URL\"\n placeholder=\"https://example.com/hooks/aglyn\"\n value={draft?.url ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, url: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n ) : (\n <TextField\n select\n label=\"Workflow to run\"\n helperText={\n workflowOptionsTruncated\n ? `Showing ${WORKFLOW_OPTION_CEILING} workflows, ordered ` +\n 'by id. This site has more, so one of them is not ' +\n 'offered here.'\n : undefined\n }\n value={draft?.workflowName ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, workflowName: event.target.value } : prev,\n )\n }\n size=\"small\"\n >\n {workflowNames.map((name) => (\n <MenuItem key={name} value={name}>\n {name}\n </MenuItem>\n ))}\n </TextField>\n )}\n <TextField\n label=\"Secret\"\n helperText={\n draft?.direction === 'outbound'\n ? 'Signs deliveries (X-Aglyn-Signature, HMAC-SHA256)'\n : 'Callers send this in the x-aglyn-secret header'\n }\n value={draft?.secret ?? ''}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n />\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 webhook'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nHostWebhooksCard.displayName = 'HostWebhooksCard'\n\nexport default HostWebhooksCard\n"],"names":["checkEntitlement","createResourceUid","pluginDocsHelp","CardDisplay","useConfirmationContext","hostPublicOrigin","useSnackbar","Timestamp","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","collection","doc","updateDoc","useCallback","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","useHostResourceApi","ceilingedWindow","collectionCeiling","WEBHOOK_URL_PATTERN","WEBHOOK_MAX_PER_HOST","WEBHOOK_CEILING","WORKFLOW_OPTION_CEILING","generateSecret","bytes","Uint8Array","crypto","getRandomValues","Array","from","byte","toString","padStart","join","HostWebhooksCard","props","hostId","org","firestore","enqueueSnackbar","confirm","createResource","draft","setDraft","editorOpened","setEditorOpened","data","webhookRead","idField","rows","readWebhooks","truncated","webhooksTruncated","workflowRead","readWorkflows","workflowOptionsTruncated","host","webhooks","filter","hook","deletedAt","sort","a","b","String","name","localeCompare","workflowNames","workflow","map","siteBase","handleAdd","variant","persist","length","id","direction","url","workflowName","secret","handleSave","trim","test","resource","slice","enabled","error","console","message","allowDuplicate","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","$id","now","header","help","anchor","contentGutterX","contentGutterY","spacing","sx","alignItems","flex","minWidth","noWrap","size","onClick","navigator","clipboard","writeText","severity","alignSelf","open","Boolean","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","label","value","onChange","event","prev","target","autoFocus","mt","select","placeholder","helperText","undefined","slotProps","input","readOnly","disabled","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-webhooks-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 type AglynOrgBilling,\n checkEntitlement,\n createResourceUid,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { hostPublicOrigin } from '@aglyn/aglyn/app-utils/host-naming'\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 MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, updateDoc } from 'firebase/firestore'\nimport { useCallback, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n useHostResourceApi,\n} from '@aglyn/tenant-feature-instance'\n/*\n * The MODULE, not the barrel, for the two PURE helpers — the specs that render\n * this card mock `@aglyn/tenant-feature-instance` wholesale to stage their\n * Firestore hooks, and a query builder imported through that barrel disappears\n * under the mock. Neither of these is a hook.\n */\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { WEBHOOK_URL_PATTERN, maskedWebhookUrl } from '../model/webhooks'\nimport { WEBHOOK_MAX_PER_HOST } from '@aglyn/aglyn/plugin-manager/plugin-host-resources'\n\n/**\n * How many webhook documents the card reads.\n *\n * Above `WEBHOOK_MAX_PER_HOST` on purpose. The cap counts LIVE hooks and the\n * collection keeps soft-deleted ones, so a site that has created and deleted\n * its five several times holds more documents than it may have hooks.\n */\nconst WEBHOOK_CEILING = 20\n\n/**\n * How many workflows the inbound-endpoint picker offers.\n *\n * Paid for only while the editor is open, which is what makes a ceiling this\n * size affordable: it is one operator mid-edit rather than every visitor to\n * the page.\n */\nconst WORKFLOW_OPTION_CEILING = 100\n\nfunction generateSecret(): string {\n const bytes = new Uint8Array(24)\n crypto.getRandomValues(bytes)\n return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(\n '',\n )\n}\n\ninterface WebhookDraft {\n id: string | null\n name: string\n direction: 'outbound' | 'inbound'\n url: string\n workflowName: string\n secret: string\n}\n\n/**\n * Webhooks manager (AGL-149): outbound targets (used by the actions\n * builder's \"Send a webhook\" step, HMAC-signed) and inbound endpoints\n * (`/api/hooks/{hostId}/{hookId}`, secret-verified, run a workflow with\n * the payload in scope). Business tier.\n */\nexport function HostWebhooksCard(props: {\n hostId: string\n org?: Partial<AglynOrgBilling>\n}) {\n const { hostId, org } = props\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createResource = useHostResourceApi()\n\n\n const [draft, setDraft] = useState<WebhookDraft | 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 workflow picker below keys its\n * listeners on it. Tracking the dialog would tear that subscription 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 window 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 * ORDERED AND CEILINGED (AGL-2501). A bare `limit` is answered in\n * DOCUMENT-ID order, so an unnamed window is an arbitrary twenty that the\n * `localeCompare` below would arrange alphabetically. `collectionCeiling`\n * does not change WHICH twenty; it NAMES the order, and the probe row it\n * adds is what lets the card say when the ceiling bites.\n */\n const { data: webhookRead } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'webhooks'),\n WEBHOOK_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: readWebhooks, truncated: webhooksTruncated } =\n ceilingedWindow<any>(webhookRead, WEBHOOK_CEILING)\n /*\n * READ WHEN THE EDITOR OPENS, not when the card mounts. The only thing that\n * consumes these rows is the inbound-endpoint select inside the dialog — the\n * table renders a hook's stored `workflowName` — so an unconditional\n * listener would charge every visitor to this page a hundred workflows to\n * fill a control most of them never see, and one that only appears for the\n * inbound half of the form at that.\n */\n const { data: workflowRead } = useFirestoreCollection<any>(\n () =>\n editorOpened\n ? collectionCeiling(\n collection(firestore, 'hosts', hostId, 'workflows'),\n WORKFLOW_OPTION_CEILING,\n )\n : null,\n [firestore, hostId, editorOpened],\n { idField: '$id' },\n )\n const { rows: readWorkflows, truncated: workflowOptionsTruncated } =\n ceilingedWindow<any>(workflowRead, WORKFLOW_OPTION_CEILING)\n const { data: host } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId),\n [firestore, hostId],\n { idField: '$id' },\n )\n // Sorting the whole ceiling, not a page of it.\n const webhooks = [...readWebhooks]\n .filter((hook: any) => !hook.deletedAt)\n .sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n const workflowNames = readWorkflows\n .filter((workflow: any) => !workflow.deletedAt && workflow.name)\n .map((workflow: any) => workflow.name as string)\n .sort()\n // `hostPublicOrigin` (AGL-2195) — this base is printed into the webhook\n // sample payload an operator copies into a third-party system.\n const siteBase = hostPublicOrigin(host) ?? ''\n\n const handleAdd = useCallback(() => {\n if (!checkEntitlement(org, 'webhooks')) {\n return void enqueueSnackbar(\n 'Webhooks require a Business plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n }\n // An affordance, NOT the limit (AGL-1360). `webhooks` comes from a\n // Firestore listener and the console runs `persistentLocalCache`, so this\n // count can be arbitrarily stale — it is here to fail fast when it\n // happens to be right, never to authorise the create. The cap that holds\n // is counted server-side in /api/hosts/resources.\n if (webhooks.length >= WEBHOOK_MAX_PER_HOST) {\n return void enqueueSnackbar(\n `Webhooks are capped at ${WEBHOOK_MAX_PER_HOST} per site`,\n { variant: 'warning', persist: false },\n )\n }\n setDraft({\n id: null,\n name: '',\n direction: 'outbound',\n url: '',\n workflowName: '',\n secret: generateSecret(),\n })\n }, [org, webhooks.length, enqueueSnackbar])\n\n const handleSave = useCallback(async () => {\n if (!draft || !draft.name.trim()) return\n if (draft.direction === 'outbound') {\n if (!WEBHOOK_URL_PATTERN.test(draft.url.trim())) {\n return void enqueueSnackbar(\n 'Outbound URLs must be public https addresses',\n { variant: 'warning', persist: false },\n )\n }\n } else if (!draft.workflowName.trim()) {\n return void enqueueSnackbar('Pick the workflow this endpoint runs', {\n variant: 'warning',\n persist: false,\n })\n }\n try {\n // Creates go through /api/hosts/resources (AGL-1360), which counts the\n // live webhooks with the Admin SDK before allowing one more; Firestore\n // rules deny client `create` on this collection. The card has no edit\n // path — every save is a create — so there is no update branch here.\n await createResource({\n hostId,\n resource: 'webhook',\n id: createResourceUid(),\n data: {\n name: draft.name.trim().slice(0, 60),\n direction: draft.direction,\n ...(draft.direction === 'outbound'\n ? { url: draft.url.trim() }\n : { workflowName: draft.workflowName.trim() }),\n secret: draft.secret,\n enabled: true,\n },\n })\n setDraft(null)\n enqueueSnackbar('Webhook saved', { variant: 'success', persist: false })\n } catch (error: any) {\n console.error(error)\n // The route's message is the useful one — it names the cap, the\n // entitlement or the role that refused.\n enqueueSnackbar(error?.message || 'An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n }, [draft, createResource, hostId, enqueueSnackbar])\n\n const handleDelete = useCallback(\n (hook: any) => async () => {\n const confirmed = await confirm({\n title: 'Delete this webhook?',\n description: `\"${hook.name}\" stops ${\n hook.direction === 'outbound' ? 'delivering' : 'accepting calls'\n } immediately.`,\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, 'webhooks', hook.$id), {\n deletedAt: Timestamp.now(),\n })\n },\n [confirm, firestore, hostId],\n )\n\n return (\n <CardDisplay\n header={'Webhooks'}\n help={pluginDocsHelp('webhooks', { anchor: '#outbound-webhooks' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Send signed JSON to outside systems from the actions builder, ' +\n 'or accept calls that run a workflow. Business plans.'}\n </Typography>\n {webhooks.map((hook: any) => (\n <Stack\n key={hook.$id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {hook.name}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {hook.direction === 'outbound'\n ? `outbound · ${maskedWebhookUrl(hook.url)}`\n : `inbound · ${siteBase}/api/hooks/${hostId}/${hook.$id}` +\n ` → ${hook.workflowName}`}\n </Typography>\n </Stack>\n {hook.direction === 'inbound' ? (\n <Button\n size=\"small\"\n onClick={() => {\n void navigator.clipboard.writeText(\n `${siteBase}/api/hooks/${hostId}/${hook.$id}`,\n )\n enqueueSnackbar(\n 'Endpoint URL copied — send the secret in x-aglyn-secret',\n { variant: 'success', persist: false },\n )\n }}\n >\n {'Copy URL'}\n </Button>\n ) : null}\n <Button\n size=\"small\"\n onClick={() => {\n void navigator.clipboard.writeText(hook.secret ?? '')\n enqueueSnackbar('Secret copied', {\n variant: 'success',\n persist: false,\n })\n }}\n >\n {'Secret'}\n </Button>\n <Button size=\"small\" color=\"error\" onClick={handleDelete(hook)}>\n {'Delete'}\n </Button>\n </Stack>\n ))}\n {webhooksTruncated ? (\n <Alert severity=\"info\">\n {`Showing the first ${WEBHOOK_CEILING} webhook documents on this ` +\n 'site, ordered by id. There are more, so a hook may be missing ' +\n 'from this list.'}\n </Alert>\n ) : null}\n <Button\n size=\"small\"\n color=\"primary\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={handleAdd}\n >\n {'Add webhook'}\n </Button>\n </Stack>\n\n <Dialog\n open={Boolean(draft)}\n onClose={() => setDraft(null)}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Add webhook'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 1.5, pt: 1 }}\n >\n <TextField\n label=\"Name\"\n value={draft?.name ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, name: event.target.value } : prev,\n )\n }\n size=\"small\"\n autoFocus\n sx={{ mt: 1 }}\n />\n <TextField\n select\n label=\"Direction\"\n value={draft?.direction ?? 'outbound'}\n onChange={(event) =>\n setDraft((prev) =>\n prev\n ? { ...prev, direction: event.target.value as any }\n : prev,\n )\n }\n size=\"small\"\n >\n <MenuItem value=\"outbound\">\n {'Outbound — send data to a URL'}\n </MenuItem>\n <MenuItem value=\"inbound\">\n {'Inbound — receive data, run a workflow'}\n </MenuItem>\n </TextField>\n {draft?.direction === 'outbound' ? (\n <TextField\n label=\"Delivery URL\"\n placeholder=\"https://example.com/hooks/aglyn\"\n value={draft?.url ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, url: event.target.value } : prev,\n )\n }\n size=\"small\"\n />\n ) : (\n <TextField\n select\n label=\"Workflow to run\"\n helperText={\n workflowOptionsTruncated\n ? `Showing ${WORKFLOW_OPTION_CEILING} workflows, ordered ` +\n 'by id. This site has more, so one of them is not ' +\n 'offered here.'\n : undefined\n }\n value={draft?.workflowName ?? ''}\n onChange={(event) =>\n setDraft((prev) =>\n prev ? { ...prev, workflowName: event.target.value } : prev,\n )\n }\n size=\"small\"\n >\n {workflowNames.map((name) => (\n <MenuItem key={name} value={name}>\n {name}\n </MenuItem>\n ))}\n </TextField>\n )}\n <TextField\n label=\"Secret\"\n helperText={\n draft?.direction === 'outbound'\n ? 'Signs deliveries (X-Aglyn-Signature, HMAC-SHA256)'\n : 'Callers send this in the x-aglyn-secret header'\n }\n value={draft?.secret ?? ''}\n size=\"small\"\n slotProps={{ input: { readOnly: true } }}\n />\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 webhook'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nHostWebhooksCard.displayName = 'HostWebhooksCard'\n\nexport default HostWebhooksCard\n"],"names":["checkEntitlement","createResourceUid","pluginDocsHelp","CardDisplay","useConfirmationContext","hostPublicOrigin","useSnackbar","Timestamp","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","Stack","TextField","Typography","collection","doc","updateDoc","useCallback","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","useHostResourceApi","ceilingedWindow","collectionCeiling","WEBHOOK_URL_PATTERN","maskedWebhookUrl","WEBHOOK_MAX_PER_HOST","WEBHOOK_CEILING","WORKFLOW_OPTION_CEILING","generateSecret","bytes","Uint8Array","crypto","getRandomValues","Array","from","byte","toString","padStart","join","HostWebhooksCard","props","hostId","org","firestore","enqueueSnackbar","confirm","createResource","draft","setDraft","editorOpened","setEditorOpened","data","webhookRead","idField","rows","readWebhooks","truncated","webhooksTruncated","workflowRead","readWorkflows","workflowOptionsTruncated","host","webhooks","filter","hook","deletedAt","sort","a","b","String","name","localeCompare","workflowNames","workflow","map","siteBase","handleAdd","variant","persist","length","id","direction","url","workflowName","secret","handleSave","trim","test","resource","slice","enabled","error","console","message","allowDuplicate","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","$id","now","header","help","anchor","contentGutterX","contentGutterY","spacing","sx","alignItems","flex","minWidth","noWrap","size","onClick","navigator","clipboard","writeText","severity","alignSelf","open","Boolean","onClose","maxWidth","fullWidth","display","flexDirection","gap","pt","label","value","onChange","event","prev","target","autoFocus","mt","select","placeholder","helperText","undefined","slotProps","input","readOnly","disabled","displayName"],"mappings":"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"}
@@ -559,7 +559,7 @@ import { stepRunsAfterWait } from "../model/host-actions.js";
559
559
  /*#__PURE__*/ _jsx(Typography, {
560
560
  variant: "body2",
561
561
  color: "text.secondary",
562
- children: 'Chain your functions into multi-step pipelines — each step ' + 'feeds the next. Site-event triggers are coming next.'
562
+ children: 'Run steps in order when something happens on your site — a ' + 'form submission, a booking, a new member — or chain your ' + 'functions, each step feeding the next.'
563
563
  }),
564
564
  automationsZone
565
565
  ]
@@ -747,6 +747,7 @@ import { stepRunsAfterWait } from "../model/host-actions.js";
747
747
  afterWait: stepRunsAfterWait(draft == null ? void 0 : draft.steps, index)
748
748
  },
749
749
  fields: call ? /*#__PURE__*/ _jsxs(Stack, {
750
+ useFlexGap: true,
750
751
  direction: "row",
751
752
  spacing: 1,
752
753
  sx: {