@burdenoff/fluidgrids-fe-sdk 2026.1112.3 → 2026.1112.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/{AiStudioPage-C-jio5Bz.js → AiStudioPage-BNmXx6LB.js} +10 -10
  2. package/dist/{ApprovalInboxPage-D8vhnloO.js → ApprovalInboxPage-QAJim81n.js} +3 -2
  3. package/dist/{DataSinkDashboardPage-CRBsNjoS.js → DataSinkDashboardPage-kyzcjpzL.js} +4 -4
  4. package/dist/{DataTablesPage-DoVBkb_T.js → DataTablesPage-Dm-dzVPD.js} +4 -4
  5. package/dist/{EntityTagPicker-Dk3CufFt.js → EntityTagPicker-dxfzv8aO.js} +1 -1
  6. package/dist/{ExecutionDetailPage-Cw4zyaIG.js → ExecutionDetailPage-B9eEep77.js} +977 -509
  7. package/dist/{ExecutionsListPage-BgeqwVr2.js → ExecutionsListPage-DuuPSVEj.js} +831 -146
  8. package/dist/{FailureTriagePanel-BzD4UJU5.js → FailureTriagePanel-D4lJ4zqQ.js} +1 -1
  9. package/dist/{FluidGridsProvider-BFfTxl9m.js → FluidGridsProvider-B1hgCbxG.js} +7 -7
  10. package/dist/{HistoryPage-BdijEOHp.js → HistoryPage-C7NwkVNC.js} +202 -36
  11. package/dist/{HumanRequestCard-mOhr-Ic9.js → HumanRequestCard-lbWDpWd7.js} +4 -216
  12. package/dist/{InsightsPage-2jnYlozA.js → InsightsPage-D9B_qW8e.js} +10 -10
  13. package/dist/{MissingNodesInstallModal-C0wPwR0Q.js → MissingNodesInstallModal-BkkgeFeN.js} +1 -1
  14. package/dist/NavigationV2Provider-DCDt35eG.js +17 -0
  15. package/dist/{NodesPage-Dhg5Q2R5.js → NodesPage-BEeUbIts.js} +4 -4
  16. package/dist/{QuotaExhaustedDialog-BKZ9moP8.js → QuotaExhaustedDialog-Bj7AG_0y.js} +1 -1
  17. package/dist/{RunnersPage-CLCOCclc.js → RunnersPage-D9Bgod_d.js} +4 -4
  18. package/dist/{SettingsPage-BaM98QU_.js → SettingsPage-k21044-Z.js} +16 -16
  19. package/dist/{TagFilterSelect-VC6JMK3F.js → TagFilterSelect-CqhxcDjh.js} +1 -1
  20. package/dist/{TaskQueuesPage-C3R5ZoKs.js → TaskQueuesPage-B86p51gH.js} +2 -2
  21. package/dist/{TemplateCard-DYDVNYo6.js → TemplateCard-DanOe65C.js} +3 -3
  22. package/dist/{TemplateConfigurationModal-BzdkGWim.js → TemplateConfigurationModal-CeMpPEhT.js} +3 -3
  23. package/dist/{TemplateDetailPanel-s4EqQhra.js → TemplateDetailPanel-DnoA6mC3.js} +2 -2
  24. package/dist/{TemplatePreview-B_-zS_Mm.js → TemplatePreview-CBwvi0ga.js} +4 -4
  25. package/dist/{TemplatesPage-OIMd6cIl.js → TemplatesPage-C7f2xq12.js} +14 -14
  26. package/dist/{VariablesPage-67WR_lVE.js → VariablesPage-BtoLjmUA.js} +1 -1
  27. package/dist/{VersionDiffDialog-Cp2doV3f.js → VersionDiffDialog-pLpG_I1r.js} +4 -4
  28. package/dist/{WorkflowBuilder-B2tIOPi0.js → WorkflowBuilder-DDgWgnwH.js} +7 -7
  29. package/dist/{WorkflowBuilder-DD4UIAjC.js → WorkflowBuilder-DmYS5JEY.js} +12 -12
  30. package/dist/{WorkflowProposalCard-U31u1qcw.js → WorkflowProposalCard-BjYAZH3q.js} +1 -1
  31. package/dist/{WorkflowSearchRedirect-BhXzWDj7.js → WorkflowSearchRedirect-iPQBL6WO.js} +1 -1
  32. package/dist/{WorkflowShareDialog-Dv46nAyh.js → WorkflowShareDialog-DB-qJLB2.js} +1 -1
  33. package/dist/{WorkflowsList-BV-2J_gG.js → WorkflowsList-rOxlI49U.js} +13 -13
  34. package/dist/{agentTools-EVdEThES.js → agentTools-B4Gjf8R7.js} +4 -4
  35. package/dist/{api-DwaHAJtA.js → api-Dvitj1aT.js} +1 -1
  36. package/dist/{api-C8WmUejq.js → api-JpNuowX4.js} +1 -1
  37. package/dist/{canvasNoteTools-C5Vf0pxu.js → canvasNoteTools-Bve4Fo7R.js} +3 -3
  38. package/dist/{code-editor-field-o7qp9eTq.js → code-editor-field-yJZEDJBi.js} +2 -2
  39. package/dist/{constants-B2lhWNoN.js → constants-dcxg6zG6.js} +2 -2
  40. package/dist/{definition-canvas-F8DGty5m.js → definition-canvas-Bshn_dSP.js} +1 -1
  41. package/dist/executions.js +2 -2
  42. package/dist/{flow-builder-Dn4PCWuf.js → flow-builder-BPe5YNF6.js} +48 -48
  43. package/dist/{graphql-operation-types-BdU65FNm.js → graphql-operation-types-z2oXfQvM.js} +10 -1
  44. package/dist/history.js +1 -1
  45. package/dist/{index-DbTgk7dK.d.ts → index-u-kM0seP.d.ts} +3 -2
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +43 -41
  48. package/dist/navigation-v2.js +2 -1
  49. package/dist/{node-detail-view-kWiwJEKr.js → node-detail-view-oAotM2EI.js} +18 -18
  50. package/dist/{node-editor-tab-body-C71Y0fRJ.js → node-editor-tab-body-DIcmar84.js} +6 -6
  51. package/dist/{node-endpoint-configs-BrzQMy10.js → node-endpoint-configs-typvqw2c.js} +1 -1
  52. package/dist/{node-operation-selection-icAhaNza.js → node-operation-selection-DAbS6jSY.js} +4 -4
  53. package/dist/nodes.js +1 -1
  54. package/dist/observability.d.ts +1 -1
  55. package/dist/observability.js +2 -2
  56. package/dist/{popover-v7g8TWmm.js → popover-DOeSGQOG.js} +3 -3
  57. package/dist/{quick-config-panel-C84vsDYD.js → quick-config-panel-Pm5nkZVr.js} +6 -6
  58. package/dist/{schedule-panel-BrNKy9DY.js → schedule-panel-Dizqz9eA.js} +29 -29
  59. package/dist/settings.js +1 -1
  60. package/dist/{sidebar-node-editor-D-2lPHwq.js → sidebar-node-editor-BNub2vUq.js} +4 -4
  61. package/dist/templates.js +2 -2
  62. package/dist/{useAgentDefinitions-DSU8Eek4.js → useAgentDefinitions-Uy6nhpKN.js} +1 -1
  63. package/dist/{useDurableParity-fFmfa97r.js → useDurableParity-Cb9nOdE8.js} +191 -4
  64. package/dist/{useEntityTags-C8rPBi6W.js → useEntityTags-D5C0tdLs.js} +1 -1
  65. package/dist/{useExecutions-DNGW4yxG.js → useExecutions-DJXtbA15.js} +59 -48
  66. package/dist/useHumanRequests-ZiR0sSzS.js +218 -0
  67. package/dist/{NavigationV2Provider-D4kj0Gjt.js → useNavigationV2-C2WeqN7d.js} +1 -14
  68. package/dist/{useObservability-u-MZCWUV.js → useObservability-Dfgh4yjv.js} +1 -1
  69. package/dist/{useRunHistory-x9bVUB9T.js → useRunHistory-BZQ3t68Q.js} +2 -2
  70. package/dist/{useWorkflowCodeExport-CV3SZeKm.js → useWorkflowCodeExport-D0zapXXm.js} +2 -2
  71. package/dist/{useWorkflows-CVb5zXKt.js → useWorkflows-CP-d3ygY.js} +2 -2
  72. package/dist/workflow-builder.js +5 -5
  73. package/dist/workflow-host.js +1 -1
  74. package/dist/workflow-pages.d.ts +1 -1
  75. package/dist/workflow-pages.js +19 -19
  76. package/dist/workflow-preview.js +4 -4
  77. package/dist/workflows-list.js +1 -1
  78. package/package.json +1 -1
@@ -1,30 +1,32 @@
1
1
  "use client";
2
2
  import { t as __exportAll } from "./rolldown-runtime-hDbaFkMy.js";
3
3
  import { M as DEFAULT_EXECUTIONS_FILTER, N as useExecutionsStore, m as describeOperationError, n as getFluidGridsClient } from "./client-C9XSzSXr.js";
4
- import { c as OPERATORS_BY_TYPE, i as useExecutionStats, l as buildSearchAttributeFilter, o as useExecutionsWithStore, r as useExecutionActions, s as EMPTY_SEARCH_FILTER, t as useAutoRefresh, u as searchFilterConflicts, y as validateDateRange } from "./useExecutions-DNGW4yxG.js";
4
+ import { b as validateDateRange, c as EMPTY_SEARCH_FILTER, d as searchFilterConflicts, i as useExecutionStats, l as OPERATORS_BY_TYPE, o as useExecutionsWithStore, r as useExecutionActions, t as useAutoRefresh, u as buildSearchAttributeFilter } from "./useExecutions-DJXtbA15.js";
5
5
  import { c as useDurableParityEnabled, f as usePhaseCFlags } from "./feature-flags-DM5myWaD.js";
6
6
  import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
7
7
  import { n as useFluidGridsPageControls } from "./pageControlsBridge-0pX9s-3k.js";
8
8
  import { t as executionsControls } from "./pageControlSpecs-DZJ9K37m.js";
9
- import { t as TagFilterSelect } from "./TagFilterSelect-VC6JMK3F.js";
10
- import { a as useRunStatusCounts, c as runStatusLabels, l as RUN_STATUS_CONFIG, o as ExecutionStatusBadge } from "./useDurableParity-fFmfa97r.js";
9
+ import { t as TagFilterSelect } from "./TagFilterSelect-CqhxcDjh.js";
10
+ import { n as useNavigationV2 } from "./useNavigationV2-C2WeqN7d.js";
11
+ import { a as useRunStatusCounts, c as TechnicalDetails, d as errorAdvice, g as RUN_STATUS_CONFIG, h as runStatusLabels, l as Disclosure, o as ConfirmStopRunDialog, p as ExecutionStatusBadge, s as RunActionsMenu, u as ADVICE_ERROR_TYPES } from "./useDurableParity-Cb9nOdE8.js";
11
12
  import { n as formatRunVersion } from "./run-display-BycKYSU0.js";
12
- import { dn as runDurationMs, ln as formatElapsedMs } from "./FluidGridsProvider-BFfTxl9m.js";
13
+ import { dn as runDurationMs, ln as formatElapsedMs } from "./FluidGridsProvider-B1hgCbxG.js";
14
+ import { r as useRunHumanRequests } from "./useHumanRequests-ZiR0sSzS.js";
13
15
  import { n as runListLinkKey, t as runListFilterFromSearch } from "./run-list-links-eUhZB1ju.js";
14
- import { h as useWorkflowDefinitions } from "./useWorkflows-CVb5zXKt.js";
16
+ import { h as useWorkflowDefinitions } from "./useWorkflows-CP-d3ygY.js";
15
17
  import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
16
- import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-B2lhWNoN.js";
17
- import { C as jsonArg, F as startBatchOperation, I as startBatchTerminate, P as startBatchExport, m as fetchBatchOperations, n as cancelBatchOperation, p as fetchBatchOperation, x as fetchSearchAttributes } from "./api-DwaHAJtA.js";
18
+ import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-dcxg6zG6.js";
19
+ import { C as jsonArg, F as startBatchOperation, I as startBatchTerminate, P as startBatchExport, m as fetchBatchOperations, n as cancelBatchOperation, p as fetchBatchOperation, x as fetchSearchAttributes } from "./api-Dvitj1aT.js";
18
20
  import { a as RUN_CONTROL_DIALOG_STYLE, c as parseJsonInput, i as NativeSelect, o as StatusPill, r as JsonField, t as FieldError, u as useResource } from "./shared-CMfn30xT.js";
19
- import { Cr as WorkflowBatchOperationUpdatedDocument } from "./graphql-operation-types-BdU65FNm.js";
21
+ import { Cr as WorkflowBatchOperationUpdatedDocument } from "./graphql-operation-types-z2oXfQvM.js";
20
22
  import { $F as WorkflowBatchOperationType, vI as WorkflowResetType } from "./graphql-types-v6CTGwA0.js";
21
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
23
+ import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
22
24
  import { useNavigate, useSearchParams } from "react-router";
23
25
  import { Icon } from "@iconify/react";
24
26
  import { cn } from "@burdenoff/fe-libs/utils";
25
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
27
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
26
28
  import { toast } from "sonner";
27
- import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Input, Label, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@burdenoff/fe-libs/ui";
29
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, EmptyState, Input, Label, ResponsiveTable, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@burdenoff/fe-libs/ui";
28
30
  import { Button as Button$1 } from "@burdenoff/fe-libs/button";
29
31
 
30
32
  // src/modules/executions/utils/active-filters.ts
@@ -78,6 +80,7 @@ function FailedNodeTypeChip({ nodeType, onClear }) {
78
80
  }
79
81
  function ExecutionFilters({ workflows = [], onRefresh }) {
80
82
  const tr = useTr();
83
+ const v2 = useNavigationV2();
81
84
  const statusOptions = useMemo(() => [
82
85
  {
83
86
  value: "all",
@@ -112,7 +115,7 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
112
115
  label: tr("fluidgrids.executions.executionFilters.statusTerminated", "Terminated")
113
116
  }
114
117
  ], [tr]);
115
- const errorOptions = [
118
+ const rawErrorOptions = [
116
119
  {
117
120
  value: "TIMEOUT",
118
121
  label: tr("fluidgrids.executions.executionFilters.errorTimeout", "TIMEOUT")
@@ -142,6 +145,10 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
142
145
  label: tr("fluidgrids.executions.executionFilters.errorUnknown", "UNKNOWN")
143
146
  }
144
147
  ];
148
+ const errorOptions = v2 ? ADVICE_ERROR_TYPES.map((value) => ({
149
+ value,
150
+ label: errorAdvice(tr, value)
151
+ })) : rawErrorOptions;
145
152
  const datePresets = [
146
153
  {
147
154
  value: "all",
@@ -173,6 +180,10 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
173
180
  }
174
181
  ];
175
182
  const { filter, setStatusFilter, setWorkflowFilter, setDateRange, setSearchQuery, setTriggerTypeFilter, setDurationFilter, setErrorTypeFilter, setNodeFilter, setFailedNodeTypeFilter, setTagFilter, resetFilters, autoRefresh, setAutoRefresh } = useExecutionsStore();
183
+ const levelOneDatePresets = v2 ? datePresets.filter(({ value }) => value !== "custom" || filter.dateRange.preset === "custom") : datePresets;
184
+ const MORE_STATUSES = ["PENDING", "TERMINATED"];
185
+ const primaryStatusOptions = v2 ? statusOptions.filter(({ value }) => value === "all" || !MORE_STATUSES.includes(value)) : statusOptions;
186
+ const moreStatusOptions = v2 ? statusOptions.filter(({ value }) => MORE_STATUSES.includes(value)) : [];
176
187
  const [showCustomDate, setShowCustomDate] = useState(() => filter.dateRange.preset === "custom");
177
188
  const handleDatePresetChange = useCallback((preset) => {
178
189
  const now = /* @__PURE__ */ new Date();
@@ -215,6 +226,7 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
215
226
  }, [setDateRange]);
216
227
  const parityEnabled = useDurableParityEnabled();
217
228
  const activeFiltersCount = countActiveFilters(filter, parityEnabled);
229
+ const hasMoreFiltersSet = filter.tagId !== "" || filter.triggerType !== "all" || filter.errorType !== "all" || filter.nodeId !== "" || filter.failedNodeType !== "" || filter.durationMinSeconds !== null || filter.durationMaxSeconds !== null || filter.dateRange.preset === "custom";
218
230
  const assistantControls = useMemo(() => executionsControls({
219
231
  page: tr("fluidgrids.executions.executionsList.title", "Executions"),
220
232
  search: setSearchQuery,
@@ -277,15 +289,21 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
277
289
  })
278
290
  ]
279
291
  }),
280
- /* @__PURE__ */ jsx("select", {
292
+ /* @__PURE__ */ jsxs("select", {
281
293
  value: filter.status,
282
294
  onChange: (e) => setStatusFilter(e.target.value),
283
295
  className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
284
- children: statusOptions.map((option) => /* @__PURE__ */ jsx("option", {
296
+ children: [primaryStatusOptions.map((option) => /* @__PURE__ */ jsx("option", {
285
297
  value: option.value,
286
298
  className: "bg-bg-surface text-text-primary",
287
299
  children: option.label
288
- }, option.value))
300
+ }, option.value)), moreStatusOptions.length > 0 && /* @__PURE__ */ jsx("optgroup", {
301
+ label: tr("fluidgrids.executions.executionFilters.moreStatuses", "More statuses"),
302
+ children: moreStatusOptions.map((option) => /* @__PURE__ */ jsx("option", {
303
+ value: option.value,
304
+ children: option.label
305
+ }, option.value))
306
+ })]
289
307
  }),
290
308
  workflows.length > 0 && /* @__PURE__ */ jsxs("select", {
291
309
  value: filter.workflowKey,
@@ -301,7 +319,7 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
301
319
  children: wf.name
302
320
  }, wf.workflowKey))]
303
321
  }),
304
- /* @__PURE__ */ jsx(TagFilterSelect, {
322
+ !v2 && /* @__PURE__ */ jsx(TagFilterSelect, {
305
323
  value: filter.tagId,
306
324
  onChange: setTagFilter,
307
325
  className: "max-w-[200px]"
@@ -310,7 +328,7 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
310
328
  value: filter.dateRange.preset,
311
329
  onChange: (e) => handleDatePresetChange(e.target.value),
312
330
  className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
313
- children: datePresets.map((option) => /* @__PURE__ */ jsx("option", {
331
+ children: levelOneDatePresets.map((option) => /* @__PURE__ */ jsx("option", {
314
332
  value: option.value,
315
333
  className: "bg-bg-surface text-text-primary",
316
334
  children: option.label
@@ -318,7 +336,119 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
318
336
  })
319
337
  ]
320
338
  }),
321
- /* @__PURE__ */ jsxs("div", {
339
+ v2 ? /* @__PURE__ */ jsx(Disclosure, {
340
+ closedLabel: tr("fluidgrids.executions.executionFilters.moreFilters", "More filters"),
341
+ openLabel: tr("fluidgrids.executions.executionFilters.fewerFilters", "Fewer filters"),
342
+ defaultOpen: hasMoreFiltersSet,
343
+ "data-testid": "runs-more-filters",
344
+ children: /* @__PURE__ */ jsxs("div", {
345
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5",
346
+ children: [
347
+ /* @__PURE__ */ jsx(TagFilterSelect, {
348
+ value: filter.tagId,
349
+ onChange: setTagFilter,
350
+ className: "max-w-[200px]"
351
+ }),
352
+ /* @__PURE__ */ jsxs("select", {
353
+ "aria-label": tr("fluidgrids.executions.executionFilters.triggerType", "Trigger type"),
354
+ value: filter.triggerType,
355
+ onChange: (e) => setTriggerTypeFilter(e.target.value),
356
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
357
+ children: [
358
+ /* @__PURE__ */ jsx("option", {
359
+ value: "all",
360
+ children: tr("fluidgrids.executions.executionFilters.allTriggers", "All trigger types")
361
+ }),
362
+ /* @__PURE__ */ jsx("option", {
363
+ value: "MANUAL",
364
+ children: tr("fluidgrids.executions.executionFilters.manual", "Manual")
365
+ }),
366
+ /* @__PURE__ */ jsx("option", {
367
+ value: "WEBHOOK",
368
+ children: tr("fluidgrids.executions.executionFilters.webhook", "Webhook")
369
+ }),
370
+ /* @__PURE__ */ jsx("option", {
371
+ value: "SCHEDULE",
372
+ children: tr("fluidgrids.executions.executionFilters.schedule", "Schedule")
373
+ }),
374
+ /* @__PURE__ */ jsx("option", {
375
+ value: "EVENT",
376
+ children: tr("fluidgrids.executions.executionFilters.event", "Event")
377
+ })
378
+ ]
379
+ }),
380
+ /* @__PURE__ */ jsxs("select", {
381
+ "aria-label": tr("fluidgrids.executions.executionFilters.errorType", "Error type"),
382
+ value: filter.errorType,
383
+ onChange: (e) => setErrorTypeFilter(e.target.value),
384
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg scheme-normal dark:scheme-dark",
385
+ children: [
386
+ /* @__PURE__ */ jsx("option", {
387
+ value: "all",
388
+ children: tr("fluidgrids.executions.executionFilters.allErrors", "All error types")
389
+ }),
390
+ errorOptions.map(({ value, label }) => /* @__PURE__ */ jsx("option", {
391
+ value,
392
+ children: label
393
+ }, value)),
394
+ /* @__PURE__ */ jsx(LinkedErrorTypeOption, {
395
+ errorType: filter.errorType,
396
+ offered: errorOptions
397
+ })
398
+ ]
399
+ }),
400
+ /* @__PURE__ */ jsx(FailedNodeTypeChip, {
401
+ nodeType: filter.failedNodeType,
402
+ onClear: () => setFailedNodeTypeFilter("")
403
+ }),
404
+ /* @__PURE__ */ jsx("input", {
405
+ type: "text",
406
+ value: filter.nodeId,
407
+ onChange: (e) => setNodeFilter(e.target.value),
408
+ placeholder: tr("fluidgrids.executions.executionFilters.nodePlaceholder", "Node ID"),
409
+ "aria-label": tr("fluidgrids.executions.executionFilters.nodeId", "Node ID"),
410
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
411
+ }),
412
+ /* @__PURE__ */ jsx("input", {
413
+ type: "number",
414
+ min: 0,
415
+ value: filter.durationMinSeconds ?? "",
416
+ onChange: (e) => setDurationFilter(e.target.value ? Number(e.target.value) : null, filter.durationMaxSeconds),
417
+ placeholder: tr("fluidgrids.executions.executionFilters.durationMin", "Min seconds"),
418
+ "aria-label": tr("fluidgrids.executions.executionFilters.durationMin", "Minimum duration in seconds"),
419
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
420
+ }),
421
+ /* @__PURE__ */ jsx("input", {
422
+ type: "number",
423
+ min: 0,
424
+ value: filter.durationMaxSeconds ?? "",
425
+ onChange: (e) => setDurationFilter(filter.durationMinSeconds, e.target.value ? Number(e.target.value) : null),
426
+ placeholder: tr("fluidgrids.executions.executionFilters.durationMax", "Max seconds"),
427
+ "aria-label": tr("fluidgrids.executions.executionFilters.durationMax", "Maximum duration in seconds"),
428
+ className: "px-3 py-2 bg-bg-surface border border-border-default rounded-lg text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
429
+ }),
430
+ /* @__PURE__ */ jsx("button", {
431
+ type: "button",
432
+ onClick: () => handleDatePresetChange("custom"),
433
+ className: "px-3 py-2 border border-border-default rounded-lg text-text-primary text-left hover:bg-background-tertiary",
434
+ "data-testid": "runs-custom-range",
435
+ children: tr("fluidgrids.executions.executionFilters.dateCustomRange", "Custom Range")
436
+ }),
437
+ /* @__PURE__ */ jsxs("label", {
438
+ className: "inline-flex items-center gap-2 cursor-pointer",
439
+ children: [/* @__PURE__ */ jsx("input", {
440
+ type: "checkbox",
441
+ checked: autoRefresh,
442
+ onChange: (e) => setAutoRefresh(e.target.checked),
443
+ className: "w-4 h-4 rounded border-border-secondary text-action-primary-bg focus:ring-action-primary-bg"
444
+ }), /* @__PURE__ */ jsx("span", {
445
+ className: "text-sm text-text-secondary",
446
+ children: tr("fluidgrids.executions.executionFilters.liveUpdates", "Live updates")
447
+ })]
448
+ })
449
+ ]
450
+ })
451
+ }) : /* @__PURE__ */ jsxs("div", {
322
452
  className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5",
323
453
  children: [
324
454
  /* @__PURE__ */ jsxs("select", {
@@ -453,7 +583,7 @@ function ExecutionFilters({ workflows = [], onRefresh }) {
453
583
  className: "flex flex-wrap items-center justify-between gap-3",
454
584
  children: [/* @__PURE__ */ jsxs("div", {
455
585
  className: "flex items-center gap-3",
456
- children: [/* @__PURE__ */ jsxs("label", {
586
+ children: [!v2 && /* @__PURE__ */ jsxs("label", {
457
587
  className: "inline-flex items-center gap-2 cursor-pointer",
458
588
  children: [/* @__PURE__ */ jsx("input", {
459
589
  type: "checkbox",
@@ -539,11 +669,12 @@ function StatCard({ label, value, icon, color, trend, onClick }) {
539
669
  ]
540
670
  });
541
671
  }
542
- function ExecutionStats({ stats, loading, onFilterByStatus }) {
672
+ function ExecutionStats({ stats, loading, scoped = false, onFilterByStatus }) {
543
673
  const tr = useTr();
674
+ const v2 = useNavigationV2();
544
675
  if (loading) return /* @__PURE__ */ jsx("div", {
545
676
  className: "grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-6 gap-4",
546
- children: [...Array(6)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-24 bg-background-secondary rounded-lg border border-border-secondary animate-pulse" }, i))
677
+ children: [...Array(v2 ? 4 : 6)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-24 bg-background-secondary rounded-lg border border-border-secondary animate-pulse" }, i))
547
678
  });
548
679
  if (!stats) return null;
549
680
  const formatDuration = (seconds) => {
@@ -551,6 +682,81 @@ function ExecutionStats({ stats, loading, onFilterByStatus }) {
551
682
  if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
552
683
  return `${Math.round(seconds / 3600)}h`;
553
684
  };
685
+ if (v2) {
686
+ const words = runStatusLabels(tr);
687
+ return /* @__PURE__ */ jsxs("div", {
688
+ className: "space-y-3",
689
+ "data-testid": "runs-stats",
690
+ children: [/* @__PURE__ */ jsxs("div", {
691
+ className: "grid grid-cols-2 lg:grid-cols-4 gap-4",
692
+ children: [
693
+ /* @__PURE__ */ jsx(StatCard, {
694
+ label: tr("fluidgrids.executions.executionStats.totalRuns", "Total Runs"),
695
+ value: stats.total.toLocaleString(),
696
+ icon: "mdi:play-box-multiple",
697
+ color: "text-text-primary",
698
+ onClick: () => onFilterByStatus?.("all")
699
+ }),
700
+ /* @__PURE__ */ jsx(StatCard, {
701
+ label: words.RUNNING,
702
+ value: stats.running,
703
+ icon: "mdi:play-circle",
704
+ color: "text-[var(--color-status-info-text)]",
705
+ onClick: () => onFilterByStatus?.("RUNNING")
706
+ }),
707
+ /* @__PURE__ */ jsx(StatCard, {
708
+ label: words.COMPLETED,
709
+ value: stats.completed.toLocaleString(),
710
+ icon: "mdi:check-circle",
711
+ color: "text-[var(--color-status-success-text)]",
712
+ onClick: () => onFilterByStatus?.("COMPLETED")
713
+ }),
714
+ /* @__PURE__ */ jsx(StatCard, {
715
+ label: words.FAILED,
716
+ value: stats.failed,
717
+ icon: "mdi:alert-circle",
718
+ color: "text-[var(--color-status-error-text)]",
719
+ onClick: () => onFilterByStatus?.("FAILED")
720
+ })
721
+ ]
722
+ }), /* @__PURE__ */ jsx(Disclosure, {
723
+ closedLabel: tr("fluidgrids.executions.executionStats.moreNumbers", "More numbers"),
724
+ openLabel: tr("fluidgrids.executions.executionStats.fewerNumbers", "Fewer numbers"),
725
+ "data-testid": "runs-more-numbers",
726
+ children: /* @__PURE__ */ jsxs("div", {
727
+ className: "grid grid-cols-2 lg:grid-cols-4 gap-4",
728
+ children: [
729
+ /* @__PURE__ */ jsx(StatCard, {
730
+ label: tr("fluidgrids.executions.executionStats.successRate", "Success Rate"),
731
+ value: `${(stats.successRate ?? 0).toFixed(1)}%`,
732
+ icon: "mdi:percent",
733
+ color: "text-accent"
734
+ }),
735
+ !scoped && /* @__PURE__ */ jsx(StatCard, {
736
+ label: tr("fluidgrids.executions.executionStats.avgDuration", "Avg Duration"),
737
+ value: formatDuration(stats.avgDuration ?? 0),
738
+ icon: "mdi:timer",
739
+ color: "text-[var(--color-status-warning-text)]"
740
+ }),
741
+ /* @__PURE__ */ jsx(StatCard, {
742
+ label: words.CANCELLED,
743
+ value: stats.cancelled,
744
+ icon: "mdi:cancel",
745
+ color: "text-[var(--color-status-warning-text)]",
746
+ onClick: () => onFilterByStatus?.("CANCELLED")
747
+ }),
748
+ /* @__PURE__ */ jsx(StatCard, {
749
+ label: words.WAITING,
750
+ value: stats.waiting,
751
+ icon: "mdi:timer-sand",
752
+ color: "text-text-primary",
753
+ onClick: () => onFilterByStatus?.("WAITING")
754
+ })
755
+ ]
756
+ })
757
+ })]
758
+ });
759
+ }
554
760
  return /* @__PURE__ */ jsxs("div", {
555
761
  className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4",
556
762
  children: [
@@ -598,8 +804,70 @@ function ExecutionStats({ stats, loading, onFilterByStatus }) {
598
804
  });
599
805
  }
600
806
 
807
+ // src/modules/executions/utils/run-actions.ts
808
+ /** Run statuses a bulk or row "Retry" (a new run of the same version) applies to. */
809
+ const RETRYABLE_STATUSES = ["FAILED"];
810
+ /** How many selected runs one bulk retry starts. */
811
+ const BULK_RETRY_LIMIT = 10;
812
+ /**
813
+ * The quick actions for a run, in display order.
814
+ *
815
+ * `resumeOffered` is the answer to "would the run page offer Resume?": a run
816
+ * that is still waiting on a person's answer cannot be resumed (the service
817
+ * refuses), so the list must not offer it either.
818
+ */
819
+ function runQuickActions(status, { resumeOffered }) {
820
+ switch (status) {
821
+ case "RUNNING": return ["pause", "cancel"];
822
+ case "WAITING": return resumeOffered ? ["resume", "cancel"] : ["cancel"];
823
+ case "FAILED": return ["retry"];
824
+ case "PENDING": return ["cancel"];
825
+ default: return [];
826
+ }
827
+ }
828
+ /**
829
+ * Which of the selected runs a bulk retry starts: only runs that failed (a
830
+ * retry of a succeeded run would silently run it again), and at most the first
831
+ * {@link BULK_RETRY_LIMIT}. What was left out is counted so the page can say so.
832
+ */
833
+ function planBulkRetry(selectedIds, runs) {
834
+ const statusById = new Map(runs.map((run) => [run.id, run.status]));
835
+ const failed = selectedIds.filter((id) => {
836
+ const status = statusById.get(id);
837
+ return status !== void 0 && RETRYABLE_STATUSES.includes(status);
838
+ });
839
+ return {
840
+ runIds: failed.slice(0, 10),
841
+ skippedNotFailed: selectedIds.length - failed.length,
842
+ skippedOverLimit: Math.max(0, failed.length - 10)
843
+ };
844
+ }
845
+
846
+ // src/modules/executions/hooks/useResumeOffered.ts
847
+ /**
848
+ * Whether the Runs list may offer "Resume" on a row. The run page offers it
849
+ * only once the run's asks are known to be answered (a run waiting on a person
850
+ * continues only with their answer, and the service refuses a generic resume),
851
+ * so the list asks the same question for WAITING rows and nothing otherwise
852
+ * (BOFF-11846). Until the asks have been read, Resume is not offered.
853
+ */
854
+ function useResumeOffered(run) {
855
+ const waiting = run.status === "WAITING";
856
+ const { items, loaded } = useRunHumanRequests(waiting ? run.id : void 0);
857
+ return waiting && loaded && !items.some((item) => item.status === "PENDING");
858
+ }
859
+
860
+ // src/modules/executions/components/WithResumeOffered.tsx
861
+ /**
862
+ * Lets a list row (which is rendered inside a `.map`, where hooks cannot be
863
+ * called) ask whether Resume may be offered for its run (BOFF-11846).
864
+ */
865
+ function WithResumeOffered({ run, children }) {
866
+ return /* @__PURE__ */ jsx(Fragment$1, { children: children(useResumeOffered(run)) });
867
+ }
868
+
601
869
  // src/modules/executions/components/ExecutionTable.tsx
602
- function ExecutionTable({ executions, workflowNames, loading, onRowClick, onAction }) {
870
+ function ExecutionTable({ executions, workflowNames, loading, canExecute = true, onRowClick, onAction }) {
603
871
  const tr = useTr();
604
872
  const navigate = useNavigate();
605
873
  const { selection, toggleSelectRun, selectAll, expandedRows, toggleRowExpanded } = useExecutionsStore();
@@ -619,47 +887,29 @@ function ExecutionTable({ executions, workflowNames, loading, onRowClick, onActi
619
887
  minute: "2-digit"
620
888
  });
621
889
  };
622
- const getRowActions = (execution) => {
623
- const actions = [];
624
- switch (execution.status) {
625
- case "RUNNING":
626
- actions.push({
627
- icon: "mdi:pause",
628
- label: tr("fluidgrids.executions.executionTable.actionPause", "Pause"),
629
- action: "pause"
630
- });
631
- actions.push({
632
- icon: "mdi:stop",
633
- label: tr("fluidgrids.executions.executionTable.actionCancel", "Cancel"),
634
- action: "cancel"
635
- });
636
- break;
637
- case "WAITING":
638
- actions.push({
639
- icon: "mdi:play",
640
- label: tr("fluidgrids.executions.executionTable.actionResume", "Resume"),
641
- action: "resume"
642
- });
643
- actions.push({
644
- icon: "mdi:stop",
645
- label: tr("fluidgrids.executions.executionTable.actionCancel", "Cancel"),
646
- action: "cancel"
647
- });
648
- break;
649
- case "FAILED":
650
- actions.push({
651
- icon: "mdi:refresh",
652
- label: tr("fluidgrids.executions.executionTable.actionRetry", "Retry"),
653
- action: "retry"
654
- });
655
- break;
656
- case "PENDING": actions.push({
890
+ const getRowActions = (execution, resumeOffered) => {
891
+ const labels = {
892
+ pause: {
893
+ icon: "mdi:pause",
894
+ label: tr("fluidgrids.executions.executionTable.actionPause", "Pause")
895
+ },
896
+ cancel: {
657
897
  icon: "mdi:stop",
658
- label: tr("fluidgrids.executions.executionTable.actionCancel", "Cancel"),
659
- action: "cancel"
660
- });
661
- }
662
- return actions;
898
+ label: tr("fluidgrids.executions.executionTable.actionCancel", "Cancel")
899
+ },
900
+ resume: {
901
+ icon: "mdi:play",
902
+ label: tr("fluidgrids.executions.executionTable.actionResume", "Resume")
903
+ },
904
+ retry: {
905
+ icon: "mdi:refresh",
906
+ label: tr("fluidgrids.executions.executionTable.actionRetry", "Retry")
907
+ }
908
+ };
909
+ return runQuickActions(execution.status, { resumeOffered }).map((action) => ({
910
+ action,
911
+ ...labels[action]
912
+ }));
663
913
  };
664
914
  if (loading && executions.length === 0) return /* @__PURE__ */ jsx("div", {
665
915
  className: "bg-background-secondary rounded-lg border border-border-secondary overflow-hidden",
@@ -780,7 +1030,6 @@ function ExecutionTable({ executions, workflowNames, loading, onRowClick, onActi
780
1030
  children: executions.map((execution) => {
781
1031
  const isSelected = selection.runIds.includes(execution.id);
782
1032
  const isExpanded = expandedRows.has(execution.id);
783
- const actions = getRowActions(execution);
784
1033
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("tr", {
785
1034
  className: `
786
1035
  hover:bg-background-tertiary/50 cursor-pointer transition-colors
@@ -866,27 +1115,36 @@ function ExecutionTable({ executions, workflowNames, loading, onRowClick, onActi
866
1115
  className: "flex items-center gap-1",
867
1116
  children: [
868
1117
  /* @__PURE__ */ jsx("button", {
1118
+ type: "button",
869
1119
  onClick: () => toggleRowExpanded(execution.id),
1120
+ "aria-expanded": isExpanded,
1121
+ "aria-label": isExpanded ? tr("fluidgrids.executions.executionTable.collapseRow", "Hide run details") : tr("fluidgrids.executions.executionTable.expandRow", "Show run details"),
870
1122
  className: "p-1.5 rounded hover:bg-background-tertiary text-text-secondary",
871
1123
  children: /* @__PURE__ */ jsx(Icon, {
872
1124
  icon: isExpanded ? "mdi:chevron-up" : "mdi:chevron-down",
873
1125
  width: 18
874
1126
  })
875
1127
  }),
876
- actions.slice(0, 2).map((action) => /* @__PURE__ */ jsx("button", {
877
- "data-agent": "confirm",
878
- onClick: () => onAction?.(action.action, execution),
879
- className: "p-1.5 rounded hover:bg-background-tertiary text-text-secondary",
880
- title: action.label,
881
- children: /* @__PURE__ */ jsx(Icon, {
882
- icon: action.icon,
883
- width: 18
884
- })
885
- }, action.action)),
1128
+ /* @__PURE__ */ jsx(WithResumeOffered, {
1129
+ run: execution,
1130
+ children: (resumeOffered) => (canExecute ? getRowActions(execution, resumeOffered) : []).slice(0, 2).map((action) => /* @__PURE__ */ jsx("button", {
1131
+ "data-agent": "confirm",
1132
+ type: "button",
1133
+ onClick: () => onAction?.(action.action, execution),
1134
+ className: "p-1.5 rounded hover:bg-background-tertiary text-text-secondary",
1135
+ title: action.label,
1136
+ "aria-label": action.label,
1137
+ children: /* @__PURE__ */ jsx(Icon, {
1138
+ icon: action.icon,
1139
+ width: 18
1140
+ })
1141
+ }, action.action))
1142
+ }),
886
1143
  /* @__PURE__ */ jsx("button", {
887
1144
  onClick: () => navigate(`../runs/${execution.id}`),
888
1145
  className: "p-1.5 rounded hover:bg-background-tertiary text-text-secondary",
889
1146
  title: tr("fluidgrids.executions.executionTable.viewDetails", "View Details"),
1147
+ "aria-label": tr("fluidgrids.executions.executionTable.viewDetails", "View Details"),
890
1148
  children: /* @__PURE__ */ jsx(Icon, {
891
1149
  icon: "mdi:arrow-right",
892
1150
  width: 18
@@ -960,7 +1218,7 @@ function ExecutionTable({ executions, workflowNames, loading, onRowClick, onActi
960
1218
  })] })
961
1219
  ]
962
1220
  })
963
- }) }, `${execution.id}-expanded`)] });
1221
+ }) }, `${execution.id}-expanded`)] }, execution.id);
964
1222
  })
965
1223
  })]
966
1224
  })
@@ -988,6 +1246,29 @@ function formatRelativeTime(dateStr) {
988
1246
  if (diffHours < 24) return `${diffHours}h ago`;
989
1247
  return `${Math.floor(diffHours / 24)}d ago`;
990
1248
  }
1249
+ /** Icon, label and hover colour of each card action. */
1250
+ const CARD_ACTIONS = (tr) => ({
1251
+ pause: {
1252
+ icon: "mdi:pause",
1253
+ label: tr("fluidgrids.executions.executionCards.pauseButton", "Pause"),
1254
+ hover: "hover:bg-status-warning-bg-subtle hover:text-status-warning-text"
1255
+ },
1256
+ cancel: {
1257
+ icon: "mdi:stop",
1258
+ label: tr("fluidgrids.executions.executionCards.cancelButton", "Cancel"),
1259
+ hover: "hover:bg-status-error-bg-subtle hover:text-status-error-text"
1260
+ },
1261
+ resume: {
1262
+ icon: "mdi:play",
1263
+ label: tr("fluidgrids.executions.executionTable.actionResume", "Resume"),
1264
+ hover: "hover:bg-status-info-bg-subtle hover:text-status-info-text"
1265
+ },
1266
+ retry: {
1267
+ icon: "mdi:refresh",
1268
+ label: tr("fluidgrids.executions.executionTable.actionRetry", "Retry"),
1269
+ hover: "hover:bg-status-info-bg-subtle hover:text-status-info-text"
1270
+ }
1271
+ });
991
1272
  function getProgressColor(status) {
992
1273
  switch (status) {
993
1274
  case "COMPLETED": return "bg-status-success-bg";
@@ -1040,8 +1321,6 @@ function ExecutionCards({ executions, workflowNames, loading, onCardClick, onAct
1040
1321
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
1041
1322
  children: executions.map((execution) => {
1042
1323
  const config = RUN_STATUS_CONFIG[execution.status];
1043
- const canCancel = execution.status === "RUNNING" || execution.status === "PENDING";
1044
- const canPause = execution.status === "RUNNING";
1045
1324
  return /* @__PURE__ */ jsxs("div", {
1046
1325
  onClick: () => onCardClick(execution),
1047
1326
  className: `
@@ -1125,39 +1404,35 @@ function ExecutionCards({ executions, workflowNames, loading, onCardClick, onAct
1125
1404
  }), formatElapsedMs(runDurationMs(execution), "--")]
1126
1405
  }), /* @__PURE__ */ jsx("span", { children: formatRelativeTime(execution.startedAt ?? execution.createdAt) })]
1127
1406
  }),
1128
- (canCancel || canPause) && /* @__PURE__ */ jsxs("div", {
1129
- className: "flex gap-1 mt-2 pt-2 border-t border-border-secondary",
1130
- children: [canPause && /* @__PURE__ */ jsxs("button", {
1131
- type: "button",
1132
- onClick: (e) => {
1133
- e.stopPropagation();
1134
- onAction("pause", execution);
1135
- },
1136
- className: "flex items-center gap-1 text-xs px-2 py-1 rounded bg-background-tertiary hover:bg-status-warning-bg-subtle text-text-secondary hover:text-status-warning-text transition-colors",
1137
- children: [
1138
- /* @__PURE__ */ jsx(Icon, {
1139
- icon: "mdi:pause",
1140
- width: 12
1141
- }),
1142
- " ",
1143
- tr("fluidgrids.executions.executionCards.pauseButton", "Pause")
1144
- ]
1145
- }), canCancel && /* @__PURE__ */ jsxs("button", {
1146
- type: "button",
1147
- onClick: (e) => {
1148
- e.stopPropagation();
1149
- onAction("cancel", execution);
1150
- },
1151
- className: "flex items-center gap-1 text-xs px-2 py-1 rounded bg-background-tertiary hover:bg-status-error-bg-subtle text-text-secondary hover:text-status-error-text transition-colors",
1152
- children: [
1153
- /* @__PURE__ */ jsx(Icon, {
1154
- icon: "mdi:stop",
1155
- width: 12
1156
- }),
1157
- " ",
1158
- tr("fluidgrids.executions.executionCards.cancelButton", "Cancel")
1159
- ]
1160
- })]
1407
+ /* @__PURE__ */ jsx(WithResumeOffered, {
1408
+ run: execution,
1409
+ children: (resumeOffered) => {
1410
+ const actions = runQuickActions(execution.status, { resumeOffered });
1411
+ if (actions.length === 0) return null;
1412
+ return /* @__PURE__ */ jsx("div", {
1413
+ className: "flex gap-1 mt-2 pt-2 border-t border-border-secondary",
1414
+ children: actions.map((action) => {
1415
+ const { icon, label, hover } = CARD_ACTIONS(tr)[action];
1416
+ return /* @__PURE__ */ jsxs("button", {
1417
+ type: "button",
1418
+ "data-agent": "confirm",
1419
+ onClick: (e) => {
1420
+ e.stopPropagation();
1421
+ onAction(action, execution);
1422
+ },
1423
+ className: `flex items-center gap-1 text-xs px-2 py-1 rounded bg-background-tertiary text-text-secondary transition-colors ${hover}`,
1424
+ children: [
1425
+ /* @__PURE__ */ jsx(Icon, {
1426
+ icon,
1427
+ width: 12
1428
+ }),
1429
+ " ",
1430
+ label
1431
+ ]
1432
+ }, action);
1433
+ })
1434
+ });
1435
+ }
1161
1436
  })
1162
1437
  ]
1163
1438
  }, execution.id);
@@ -1208,7 +1483,7 @@ function groupByDate(executions) {
1208
1483
  }
1209
1484
  return groups;
1210
1485
  }
1211
- function ExecutionTimeline({ executions, loading, onItemClick, onAction }) {
1486
+ function ExecutionTimeline({ executions, workflowNames, loading, onItemClick, onAction }) {
1212
1487
  const tr = useTr();
1213
1488
  if (loading) return /* @__PURE__ */ jsx("div", {
1214
1489
  className: "space-y-6 pl-8",
@@ -1295,10 +1570,10 @@ function ExecutionTimeline({ executions, loading, onItemClick, onAction }) {
1295
1570
  className: "flex items-center gap-2",
1296
1571
  children: [/* @__PURE__ */ jsx("span", {
1297
1572
  className: "text-sm font-semibold text-text-primary truncate",
1298
- children: execution.workflowKey
1299
- }), /* @__PURE__ */ jsxs("span", {
1573
+ children: execution.workflow?.name || workflowNames?.[execution.workflowKey] || execution.workflowKey
1574
+ }), /* @__PURE__ */ jsx("span", {
1300
1575
  className: "text-xs text-text-tertiary",
1301
- children: ["v", execution.version]
1576
+ children: formatRunVersion(execution.version)
1302
1577
  })]
1303
1578
  }),
1304
1579
  /* @__PURE__ */ jsxs("div", {
@@ -1350,8 +1625,9 @@ function ExecutionTimeline({ executions, loading, onItemClick, onAction }) {
1350
1625
  e.stopPropagation();
1351
1626
  onAction("cancel", execution);
1352
1627
  },
1353
- className: "opacity-0 group-hover:opacity-100 p-1 rounded hover:bg-status-error-bg-subtle text-text-tertiary hover:text-status-error-text transition-all",
1628
+ className: "p-1 rounded opacity-60 group-hover:opacity-100 focus-visible:opacity-100 hover:bg-status-error-bg-subtle text-text-tertiary hover:text-status-error-text transition-all",
1354
1629
  title: tr("fluidgrids.executions.executionTimeline.cancelTooltip", "Cancel execution"),
1630
+ "aria-label": tr("fluidgrids.executions.executionTimeline.cancelTooltip", "Cancel execution"),
1355
1631
  children: /* @__PURE__ */ jsx(Icon, {
1356
1632
  icon: "mdi:stop",
1357
1633
  width: 14
@@ -1504,6 +1780,261 @@ function Pagination({ totalItems, showPageSize = true }) {
1504
1780
  });
1505
1781
  }
1506
1782
 
1783
+ // src/modules/executions/components/RunsPlainTable.tsx
1784
+ /**
1785
+ * The Runs list as one plain table (BOFF-11846, Navigation v2 only): Status,
1786
+ * Workflow (with a one-line plain error under a failed run), Started and
1787
+ * Progress, with Run again / Stop / View on each row, and Pause / Resume under
1788
+ * "⋯". Phones get cards (fe-libs `ResponsiveTable`). Run ids, versions,
1789
+ * durations, the expand row and the selection checkboxes are in the full table
1790
+ * behind "Show run details" on the list page.
1791
+ */
1792
+ /** Run statuses a row offers "Run again" on. */
1793
+ const RERUNNABLE = /* @__PURE__ */ new Set([
1794
+ "FAILED",
1795
+ "CANCELLED",
1796
+ "TERMINATED"
1797
+ ]);
1798
+ /** Run statuses a row offers "Stop" on. */
1799
+ const STOPPABLE = /* @__PURE__ */ new Set([
1800
+ "RUNNING",
1801
+ "WAITING",
1802
+ "PENDING"
1803
+ ]);
1804
+ /** The page's language (the app sets it), else the browser's. */
1805
+ function pageLocale() {
1806
+ if (typeof document === "undefined") return void 0;
1807
+ return document.documentElement.lang || void 0;
1808
+ }
1809
+ /** One relative-time formatter per page language, built once (not per row). */
1810
+ const relativeFormats = /* @__PURE__ */ new Map();
1811
+ function relativeFormat(locale) {
1812
+ const cacheKey = locale ?? "";
1813
+ let format = relativeFormats.get(cacheKey);
1814
+ if (!format) {
1815
+ format = new Intl.RelativeTimeFormat(locale, {
1816
+ numeric: "always",
1817
+ style: "narrow"
1818
+ });
1819
+ relativeFormats.set(cacheKey, format);
1820
+ }
1821
+ return format;
1822
+ }
1823
+ /**
1824
+ * "Just now", then "5m ago" / "3h ago" / "2d ago" in the page's language
1825
+ * (Intl's narrow relative time: the same English as before, translated in the
1826
+ * other locales), then a short date.
1827
+ */
1828
+ function formatStarted(tr, dateStr) {
1829
+ if (!dateStr) return "-";
1830
+ const date = new Date(dateStr);
1831
+ const diff = Date.now() - date.getTime();
1832
+ if (diff < 6e4) return tr("fluidgrids.executions.executionTable.justNow", "Just now");
1833
+ const locale = pageLocale();
1834
+ const relative = relativeFormat(locale);
1835
+ if (diff < 36e5) return relative.format(-Math.round(diff / 6e4), "minute");
1836
+ if (diff < 864e5) return relative.format(-Math.round(diff / 36e5), "hour");
1837
+ if (diff < 6048e5) return relative.format(-Math.round(diff / 864e5), "day");
1838
+ return date.toLocaleDateString(locale, {
1839
+ month: "short",
1840
+ day: "numeric",
1841
+ hour: "2-digit",
1842
+ minute: "2-digit"
1843
+ });
1844
+ }
1845
+ function RowActions({ run, canExecute, onAction }) {
1846
+ const tr = useTr();
1847
+ const resumeOffered = useResumeOffered(run);
1848
+ const menuItems = [];
1849
+ if (canExecute && run.status === "RUNNING") menuItems.push({
1850
+ id: "pause",
1851
+ icon: "mdi:pause",
1852
+ label: tr("fluidgrids.executions.executionTable.actionPause", "Pause"),
1853
+ onSelect: () => onAction("pause", run)
1854
+ });
1855
+ if (canExecute && run.status === "WAITING" && resumeOffered) menuItems.push({
1856
+ id: "resume",
1857
+ icon: "mdi:play",
1858
+ label: tr("fluidgrids.executions.executionTable.actionResume", "Resume"),
1859
+ onSelect: () => onAction("resume", run)
1860
+ });
1861
+ const viewLabel = tr("fluidgrids.executions.executionTable.viewDetails", "View Details");
1862
+ return /* @__PURE__ */ jsxs("span", {
1863
+ className: "flex items-center gap-1",
1864
+ onClick: (event) => event.stopPropagation(),
1865
+ children: [
1866
+ canExecute && RERUNNABLE.has(run.status) && /* @__PURE__ */ jsxs(Button$1, {
1867
+ "data-agent": "confirm",
1868
+ variant: "outline",
1869
+ size: "sm",
1870
+ onClick: () => onAction("retry", run),
1871
+ "data-testid": "run-row-run-again",
1872
+ children: [/* @__PURE__ */ jsx(Icon, {
1873
+ icon: "mdi:refresh",
1874
+ className: "mr-1",
1875
+ width: 14
1876
+ }), tr("fluidgrids.executions.executionTable.actionRetry", "Retry")]
1877
+ }),
1878
+ canExecute && STOPPABLE.has(run.status) && /* @__PURE__ */ jsxs(Button$1, {
1879
+ "data-agent": "confirm",
1880
+ variant: "outline",
1881
+ size: "sm",
1882
+ onClick: () => onAction("cancel", run),
1883
+ "data-testid": "run-row-stop",
1884
+ children: [/* @__PURE__ */ jsx(Icon, {
1885
+ icon: "mdi:stop",
1886
+ className: "mr-1",
1887
+ width: 14
1888
+ }), tr("fluidgrids.executions.executionTable.actionCancel", "Cancel")]
1889
+ }),
1890
+ /* @__PURE__ */ jsx(RunActionsMenu, {
1891
+ items: menuItems,
1892
+ label: tr("fluidgrids.executions.executionTable.moreActions", "More actions"),
1893
+ "data-testid": `run-row-menu-${run.id}`
1894
+ }),
1895
+ /* @__PURE__ */ jsx(Button$1, {
1896
+ variant: "ghost",
1897
+ size: "sm",
1898
+ onClick: () => onAction("view", run),
1899
+ "aria-label": viewLabel,
1900
+ title: viewLabel,
1901
+ children: /* @__PURE__ */ jsx(Icon, {
1902
+ icon: "mdi:arrow-right",
1903
+ width: 18
1904
+ })
1905
+ })
1906
+ ]
1907
+ });
1908
+ }
1909
+ function RunsPlainTable({ executions, workflowNames, loading, canExecute = true, onRowClick, onAction }) {
1910
+ const tr = useTr();
1911
+ if (loading && executions.length === 0) return /* @__PURE__ */ jsx("div", {
1912
+ className: "space-y-2",
1913
+ "aria-busy": "true",
1914
+ children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-12 rounded-lg bg-background-tertiary animate-pulse" }, i))
1915
+ });
1916
+ return /* @__PURE__ */ jsx("div", {
1917
+ "data-testid": "runs-plain-table",
1918
+ children: /* @__PURE__ */ jsx(ResponsiveTable, {
1919
+ data: executions,
1920
+ rowKey: (run) => run.id,
1921
+ onRowClick,
1922
+ rowActionsLabel: tr("fluidgrids.executions.executionTable.colActions", "Actions"),
1923
+ rowActions: (run) => /* @__PURE__ */ jsx(RowActions, {
1924
+ run,
1925
+ canExecute,
1926
+ onAction
1927
+ }),
1928
+ columns: [
1929
+ {
1930
+ key: "status",
1931
+ header: tr("fluidgrids.executions.executionTable.colStatus", "Status"),
1932
+ priority: "primary",
1933
+ cell: (run) => /* @__PURE__ */ jsxs("span", {
1934
+ className: "inline-flex items-center gap-2",
1935
+ children: [/* @__PURE__ */ jsx("span", {
1936
+ title: run.status,
1937
+ children: /* @__PURE__ */ jsx(ExecutionStatusBadge, {
1938
+ status: run.status,
1939
+ size: "sm"
1940
+ })
1941
+ }), run.mode === "TEST" && /* @__PURE__ */ jsx("span", {
1942
+ className: "px-1.5 py-0.5 text-xs border border-border-default text-text-secondary rounded",
1943
+ "data-testid": "run-row-test-badge",
1944
+ children: tr("fluidgrids.executions.executionTable.testBadge", "Test")
1945
+ })]
1946
+ })
1947
+ },
1948
+ {
1949
+ key: "workflow",
1950
+ header: tr("fluidgrids.executions.executionTable.colWorkflow", "Workflow"),
1951
+ priority: "primary",
1952
+ cell: (run) => /* @__PURE__ */ jsxs("div", {
1953
+ className: "flex flex-col",
1954
+ children: [/* @__PURE__ */ jsx("span", {
1955
+ className: "text-sm font-medium text-text-primary",
1956
+ children: run.workflow?.name || workflowNames?.[run.workflowKey] || run.workflowKey
1957
+ }), run.status === "FAILED" && /* @__PURE__ */ jsx("span", {
1958
+ className: "text-xs text-[var(--color-status-error-text)]",
1959
+ "data-testid": "run-row-error",
1960
+ children: errorAdvice(tr, run.errorType)
1961
+ })]
1962
+ })
1963
+ },
1964
+ {
1965
+ key: "started",
1966
+ header: tr("fluidgrids.executions.executionTable.colStarted", "Started"),
1967
+ priority: "primary",
1968
+ cell: (run) => /* @__PURE__ */ jsx("span", {
1969
+ className: "text-sm text-text-secondary",
1970
+ children: formatStarted(tr, run.startedAt || run.createdAt)
1971
+ })
1972
+ },
1973
+ {
1974
+ key: "progress",
1975
+ header: tr("fluidgrids.executions.executionTable.colProgress", "Progress"),
1976
+ priority: "secondary",
1977
+ cell: (run) => /* @__PURE__ */ jsxs("span", {
1978
+ className: "text-xs text-text-secondary",
1979
+ children: [
1980
+ run.completedNodes ?? 0,
1981
+ "/",
1982
+ run.totalNodes ?? 0
1983
+ ]
1984
+ })
1985
+ }
1986
+ ]
1987
+ })
1988
+ });
1989
+ }
1990
+
1991
+ // src/modules/executions/components/RunsEmptyState.tsx
1992
+ /**
1993
+ * The Runs list's two empty states (BOFF-11846, Navigation v2 only): "no runs
1994
+ * yet" (the workspace has never run anything: point to My Workflows and
1995
+ * templates) versus "no matches" (filters hide everything: offer Clear
1996
+ * filters). Today's list says the filters are wrong to a person with none.
1997
+ */
1998
+ function RunsEmptyState({ filtered, onClearFilters, onOpenWorkflows, onBrowseTemplates }) {
1999
+ const tr = useTr();
2000
+ if (filtered) return /* @__PURE__ */ jsx("div", {
2001
+ "data-testid": "runs-empty-no-matches",
2002
+ children: /* @__PURE__ */ jsx(EmptyState, {
2003
+ icon: /* @__PURE__ */ jsx(Icon, {
2004
+ icon: "mdi:filter-off-outline",
2005
+ width: 48
2006
+ }),
2007
+ title: tr("fluidgrids.executions.executionTable.noMatchesTitle", "No matches"),
2008
+ description: tr("fluidgrids.executions.executionTable.noMatchesDescription", "No runs match these filters."),
2009
+ action: /* @__PURE__ */ jsx(Button$1, {
2010
+ variant: "outline",
2011
+ onClick: onClearFilters,
2012
+ children: tr("fluidgrids.executions.executionFilters.clearFilters", "Clear Filters")
2013
+ })
2014
+ })
2015
+ });
2016
+ return /* @__PURE__ */ jsx("div", {
2017
+ "data-testid": "runs-empty-no-runs",
2018
+ children: /* @__PURE__ */ jsx(EmptyState, {
2019
+ icon: /* @__PURE__ */ jsx(Icon, {
2020
+ icon: "mdi:play-box-outline",
2021
+ width: 48
2022
+ }),
2023
+ title: tr("fluidgrids.executions.executionTable.noRunsTitle", "No runs yet"),
2024
+ description: tr("fluidgrids.executions.executionTable.noRunsDescription", "Your workflows haven't run yet. Open one and press Run, or start from a template."),
2025
+ action: /* @__PURE__ */ jsx(Button$1, {
2026
+ onClick: onOpenWorkflows,
2027
+ children: tr("fluidgrids.executions.executionTable.myWorkflows", "My Workflows")
2028
+ }),
2029
+ secondaryAction: /* @__PURE__ */ jsx(Button$1, {
2030
+ variant: "outline",
2031
+ onClick: onBrowseTemplates,
2032
+ children: tr("fluidgrids.executions.executionTable.browseTemplates", "Browse templates")
2033
+ })
2034
+ })
2035
+ });
2036
+ }
2037
+
1507
2038
  // src/modules/run-control/components/SearchAttributeFilterBar.tsx
1508
2039
  /**
1509
2040
  * The Runs list's search-attribute filter builder (BOFF-9906, spec code-first
@@ -1939,7 +2470,7 @@ function BatchOperationDialog({ type, input, selection, onClose, onStarted }) {
1939
2470
  /* @__PURE__ */ jsxs("div", {
1940
2471
  className: "space-y-3",
1941
2472
  children: [
1942
- type === "SIGNAL" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
2473
+ type === "SIGNAL" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
1943
2474
  className: "space-y-1.5",
1944
2475
  children: [/* @__PURE__ */ jsx(Label, {
1945
2476
  htmlFor: "batch-signal-name",
@@ -2298,7 +2829,7 @@ function RunsParityBar({ workflowId, onWorkflowIdChange, total, counts, countsEr
2298
2829
  " ",
2299
2830
  countsError
2300
2831
  ]
2301
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [total !== null && /* @__PURE__ */ jsx("button", {
2832
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [total !== null && /* @__PURE__ */ jsx("button", {
2302
2833
  type: "button",
2303
2834
  onClick: () => onSelectStatus("all"),
2304
2835
  className: cn("rounded-full border px-2.5 py-1 text-xs", selectedStatus === "all" ? "border-action-primary-bg bg-action-primary-bg text-action-primary-text" : "border-border-secondary text-text-secondary hover:text-text-primary"),
@@ -2325,8 +2856,11 @@ var ExecutionsListPage_exports = /* @__PURE__ */ __exportAll({ ExecutionsListPag
2325
2856
  function ExecutionsListPage() {
2326
2857
  const tr = useTr();
2327
2858
  const navigate = useNavigate();
2328
- const { viewMode, setViewMode, setSelectedExecution, selection, clearSelection, setStatusFilter, setWorkflowFilter, setWorkflowIdFilter, updateFilter, filter, autoRefresh, autoRefreshInterval } = useExecutionsStore();
2329
- const [searchParams] = useSearchParams();
2859
+ const v2 = useNavigationV2();
2860
+ const [showDetails, setShowDetails] = useState(false);
2861
+ const [confirmStop, setConfirmStop] = useState(null);
2862
+ const { viewMode, setViewMode, setSelectedExecution, selection, clearSelection, setStatusFilter, setWorkflowFilter, setWorkflowIdFilter, updateFilter, resetFilters, filter, autoRefresh, autoRefreshInterval } = useExecutionsStore();
2863
+ const [searchParams, setSearchParams] = useSearchParams();
2330
2864
  const workflowKeyParam = searchParams.get("workflowKey") ?? "";
2331
2865
  const lastWorkflowKeyParam = useRef(null);
2332
2866
  const linkKey = runListLinkKey(searchParams);
@@ -2377,21 +2911,42 @@ function ExecutionsListPage() {
2377
2911
  canDeleteRuns
2378
2912
  ]);
2379
2913
  const statusCounts = useRunStatusCounts(input, parityEnabled);
2914
+ const scopedKey = v2 ? filter.workflowKey : "";
2915
+ const scopedCounts = useRunStatusCounts({ workflowKey: scopedKey }, scopedKey !== "");
2380
2916
  const refetchCounts = statusCounts.refetch;
2917
+ const refetchScopedCounts = scopedCounts.refetch;
2381
2918
  const { stats, loading: statsLoading, refetch: refetchStats } = useExecutionStats();
2382
2919
  const { workflows } = useWorkflowDefinitions({ limit: 100 });
2383
- const { cancelExecution, pauseExecution, resumeExecution, retryExecution, retryExecutions } = useExecutionActions();
2920
+ const { controlExecution, retryExecution, retryExecutions } = useExecutionActions();
2384
2921
  useAutoRefresh(useCallback(() => {
2385
2922
  refetch();
2386
2923
  refetchStats();
2387
2924
  refetchCounts();
2925
+ refetchScopedCounts();
2388
2926
  }, [
2389
2927
  refetch,
2390
2928
  refetchStats,
2391
- refetchCounts
2929
+ refetchCounts,
2930
+ refetchScopedCounts
2392
2931
  ]), autoRefresh, autoRefreshInterval);
2393
2932
  const executionStats = useMemo(() => {
2394
2933
  if (!stats) return null;
2934
+ if (scopedKey !== "" && scopedCounts.total !== null && !scopedCounts.error) {
2935
+ const count = (status) => scopedCounts.counts.find((entry) => entry.status === status)?.count ?? 0;
2936
+ const completed = count("COMPLETED");
2937
+ const failed = count("FAILED");
2938
+ return {
2939
+ total: scopedCounts.total,
2940
+ running: count("RUNNING"),
2941
+ completed,
2942
+ failed,
2943
+ cancelled: count("CANCELLED"),
2944
+ pending: count("PENDING"),
2945
+ waiting: count("WAITING"),
2946
+ successRate: completed + failed > 0 ? completed / (completed + failed) * 100 : 0,
2947
+ avgDuration: stats.avgWorkflowExecutionTimeSeconds
2948
+ };
2949
+ }
2395
2950
  return {
2396
2951
  total: stats.runsCountTotal,
2397
2952
  running: stats.runsRunningCount,
@@ -2403,7 +2958,14 @@ function ExecutionsListPage() {
2403
2958
  successRate: stats.workflowSuccessRate,
2404
2959
  avgDuration: stats.avgWorkflowExecutionTimeSeconds
2405
2960
  };
2406
- }, [stats]);
2961
+ }, [
2962
+ stats,
2963
+ scopedKey,
2964
+ scopedCounts.total,
2965
+ scopedCounts.counts,
2966
+ scopedCounts.error
2967
+ ]);
2968
+ const statsScoped = scopedKey !== "" && scopedCounts.total !== null && !scopedCounts.error;
2407
2969
  const workflowOptions = useMemo(() => workflows.map((w) => ({
2408
2970
  workflowKey: w.workflowKey,
2409
2971
  name: w.name
@@ -2426,16 +2988,32 @@ function ExecutionsListPage() {
2426
2988
  toast.success(tr("fluidgrids.executions.executionsList.retryStarted", "New run started"));
2427
2989
  navigate(`../runs/${result.run.runId}`);
2428
2990
  };
2991
+ const controlFailedTitle = (action) => {
2992
+ switch (action) {
2993
+ case "pause": return tr("fluidgrids.executions.executionsList.pauseFailed", "Could not pause this run");
2994
+ case "resume": return tr("fluidgrids.executions.executionsList.resumeFailed", "Could not resume this run");
2995
+ case "cancel": return tr("fluidgrids.executions.executionsList.cancelFailed", "Could not cancel this run");
2996
+ }
2997
+ };
2998
+ const handleControl = async (action, execution) => {
2999
+ const result = await controlExecution(action, execution.id);
3000
+ if (!result.ok) toast.error(controlFailedTitle(action), { description: result.message });
3001
+ };
2429
3002
  const handleAction = async (action, execution) => {
2430
3003
  switch (action) {
2431
3004
  case "cancel":
2432
- await cancelExecution(execution.id);
3005
+ if (v2) {
3006
+ setConfirmStop({
3007
+ kind: "row",
3008
+ run: execution
3009
+ });
3010
+ return;
3011
+ }
3012
+ await handleControl(action, execution);
2433
3013
  break;
2434
3014
  case "pause":
2435
- await pauseExecution(execution.id);
2436
- break;
2437
3015
  case "resume":
2438
- await resumeExecution(execution.id);
3016
+ await handleControl(action, execution);
2439
3017
  break;
2440
3018
  case "retry":
2441
3019
  await handleRetry(execution);
@@ -2449,30 +3027,73 @@ function ExecutionsListPage() {
2449
3027
  };
2450
3028
  const handleBulkRetry = async () => {
2451
3029
  if (selection.runIds.length === 0) return;
2452
- const retried = await retryExecutions(selection.runIds.slice(0, 10));
3030
+ const plan = planBulkRetry(selection.runIds, executions);
3031
+ if (plan.runIds.length === 0) {
3032
+ toast.info(tr("fluidgrids.executions.executionsList.bulkRetryNoneFailed", "None of the selected runs failed, so there is nothing to run again."));
3033
+ return;
3034
+ }
3035
+ const retried = await retryExecutions(plan.runIds);
2453
3036
  if (retried.length === 0) {
2454
3037
  toast.error(tr("fluidgrids.executions.executionsList.bulkRetryFailed", "Could not retry the selected runs"));
2455
3038
  return;
2456
3039
  }
2457
3040
  toast.success(tr("fluidgrids.executions.executionsList.bulkRetryStarted", `${retried.length} new run${retried.length === 1 ? "" : "s"} started`));
3041
+ if (plan.skippedNotFailed > 0 || plan.skippedOverLimit > 0) toast.info(tr("fluidgrids.executions.executionsList.bulkRetrySkipped", "Only failed runs are run again, and at most {{limit}} at a time. Left out: {{notFailed}} that did not fail, {{overLimit}} over the limit.", {
3042
+ limit: 10,
3043
+ notFailed: plan.skippedNotFailed,
3044
+ overLimit: plan.skippedOverLimit
3045
+ }));
2458
3046
  clearSelection();
2459
3047
  refetch();
2460
3048
  };
3049
+ const handleConfirmedStop = async () => {
3050
+ const pending = confirmStop;
3051
+ setConfirmStop(null);
3052
+ if (!pending) return;
3053
+ if (pending.kind === "row") {
3054
+ await handleControl("cancel", pending.run);
3055
+ refetch();
3056
+ } else await handleBulkAction("cancel");
3057
+ };
2461
3058
  const handleBulkAction = async (action) => {
2462
3059
  if (selection.runIds.length === 0) return;
2463
- const promises = selection.runIds.map((id) => {
2464
- switch (action) {
2465
- case "cancel": return cancelExecution(id);
2466
- case "pause": return pauseExecution(id);
2467
- case "resume": return resumeExecution(id);
2468
- }
2469
- });
2470
- const results = await Promise.allSettled(promises);
2471
- const failures = results.filter((r) => r.status === "rejected");
2472
- if (failures.length > 0) console.error(`Bulk action partially failed: ${failures.length}/${results.length} failed`);
3060
+ const results = await Promise.all(selection.runIds.map((id) => controlExecution(action, id)));
3061
+ const failed = results.filter((result) => !result.ok);
3062
+ if (failed.length > 0) toast.error(tr("fluidgrids.executions.executionsList.bulkActionFailed", "{{failed}} of {{total}} runs could not be changed", {
3063
+ failed: failed.length,
3064
+ total: results.length
3065
+ }));
2473
3066
  clearSelection();
2474
3067
  refetch();
2475
3068
  };
3069
+ const viewLabel = (mode) => mode === "table" ? tr("fluidgrids.executions.executionsList.tableViewTooltip", "Table View") : mode === "cards" ? tr("fluidgrids.executions.executionsList.cardViewTooltip", "Card View") : tr("fluidgrids.executions.executionsList.timelineViewTooltip", "Timeline View");
3070
+ const viewMenuItems = [
3071
+ "table",
3072
+ "cards",
3073
+ "timeline"
3074
+ ].map((mode) => ({
3075
+ id: `view-${mode}`,
3076
+ icon: viewMode === mode ? "mdi:check" : void 0,
3077
+ label: viewLabel(mode),
3078
+ onSelect: () => setViewMode(mode)
3079
+ }));
3080
+ const viewMenuToggles = [{
3081
+ id: "details",
3082
+ label: tr("fluidgrids.executions.executionsList.showRunDetails", "Show run details and bulk actions"),
3083
+ checked: showDetails,
3084
+ onCheckedChange: setShowDetails
3085
+ }];
3086
+ const filtersActive = countActiveFilters(filter, parityEnabled) > 0 || searchFilter !== null;
3087
+ const showEmptyState = v2 && !loading && !error && executions.length === 0;
3088
+ const scopedWorkflowName = workflowKeyParam ? workflowNames[workflowKeyParam] ?? workflowKeyParam : null;
3089
+ const showAllWorkflows = () => {
3090
+ setWorkflowFilter("");
3091
+ setSearchParams((params) => {
3092
+ const next = new URLSearchParams(params);
3093
+ next.delete("workflowKey");
3094
+ return next;
3095
+ }, { replace: true });
3096
+ };
2476
3097
  return /* @__PURE__ */ jsxs("div", {
2477
3098
  className: "p-4 sm:p-6 bg-background-primary min-h-screen",
2478
3099
  children: [
@@ -2494,11 +3115,18 @@ function ExecutionsListPage() {
2494
3115
  })
2495
3116
  ] }), /* @__PURE__ */ jsxs("div", {
2496
3117
  className: "flex flex-wrap items-center gap-3",
2497
- children: [/* @__PURE__ */ jsxs("div", {
3118
+ children: [v2 ? /* @__PURE__ */ jsx(RunActionsMenu, {
3119
+ items: viewMenuItems,
3120
+ toggles: viewMenuToggles,
3121
+ label: tr("fluidgrids.executions.executionsList.moreOptions", "More options"),
3122
+ "data-testid": "runs-view-menu"
3123
+ }) : /* @__PURE__ */ jsxs("div", {
2498
3124
  className: "flex items-center bg-background-secondary rounded-lg border border-border-secondary p-1",
2499
3125
  children: [
2500
3126
  /* @__PURE__ */ jsx("button", {
2501
3127
  onClick: () => setViewMode("table"),
3128
+ "aria-pressed": viewMode === "table",
3129
+ "aria-label": tr("fluidgrids.executions.executionsList.tableViewTooltip", "Table View"),
2502
3130
  className: `p-2 rounded ${viewMode === "table" ? "bg-background-tertiary text-text-primary" : "text-text-secondary hover:text-text-primary"}`,
2503
3131
  title: tr("fluidgrids.executions.executionsList.tableViewTooltip", "Table View"),
2504
3132
  children: /* @__PURE__ */ jsx(Icon, {
@@ -2508,6 +3136,8 @@ function ExecutionsListPage() {
2508
3136
  }),
2509
3137
  /* @__PURE__ */ jsx("button", {
2510
3138
  onClick: () => setViewMode("cards"),
3139
+ "aria-pressed": viewMode === "cards",
3140
+ "aria-label": tr("fluidgrids.executions.executionsList.cardViewTooltip", "Card View"),
2511
3141
  className: `p-2 rounded ${viewMode === "cards" ? "bg-background-tertiary text-text-primary" : "text-text-secondary hover:text-text-primary"}`,
2512
3142
  title: tr("fluidgrids.executions.executionsList.cardViewTooltip", "Card View"),
2513
3143
  children: /* @__PURE__ */ jsx(Icon, {
@@ -2517,6 +3147,8 @@ function ExecutionsListPage() {
2517
3147
  }),
2518
3148
  /* @__PURE__ */ jsx("button", {
2519
3149
  onClick: () => setViewMode("timeline"),
3150
+ "aria-pressed": viewMode === "timeline",
3151
+ "aria-label": tr("fluidgrids.executions.executionsList.timelineViewTooltip", "Timeline View"),
2520
3152
  className: `p-2 rounded ${viewMode === "timeline" ? "bg-background-tertiary text-text-primary" : "text-text-secondary hover:text-text-primary"}`,
2521
3153
  title: tr("fluidgrids.executions.executionsList.timelineViewTooltip", "Timeline View"),
2522
3154
  children: /* @__PURE__ */ jsx(Icon, {
@@ -2536,12 +3168,27 @@ function ExecutionsListPage() {
2536
3168
  })]
2537
3169
  })]
2538
3170
  }),
3171
+ v2 && scopedWorkflowName && /* @__PURE__ */ jsxs("p", {
3172
+ className: "mb-3 text-sm text-text-secondary",
3173
+ "data-testid": "runs-of-workflow",
3174
+ children: [
3175
+ tr("fluidgrids.executions.executionsList.runsOfWorkflow", "Runs of {{workflow}}", { workflow: scopedWorkflowName }),
3176
+ " · ",
3177
+ /* @__PURE__ */ jsx("button", {
3178
+ type: "button",
3179
+ onClick: showAllWorkflows,
3180
+ className: "text-text-link hover:text-text-linkHover hover:underline",
3181
+ children: tr("fluidgrids.executions.executionsList.showAll", "Show all")
3182
+ })
3183
+ ]
3184
+ }),
2539
3185
  /* @__PURE__ */ jsx("div", {
2540
3186
  className: "mb-6",
2541
3187
  "data-tour": "fluidgrids-runs-stats",
2542
3188
  children: /* @__PURE__ */ jsx(ExecutionStats, {
2543
3189
  stats: executionStats,
2544
- loading: statsLoading,
3190
+ loading: v2 ? statsLoading && !executionStats : statsLoading,
3191
+ scoped: statsScoped,
2545
3192
  onFilterByStatus: handleFilterByStatus
2546
3193
  })
2547
3194
  }),
@@ -2581,7 +3228,7 @@ function ExecutionsListPage() {
2581
3228
  }), tr("fluidgrids.runControl.batch.jobs", "Batch jobs")]
2582
3229
  })
2583
3230
  }),
2584
- selection.runIds.length > 0 && /* @__PURE__ */ jsxs("div", {
3231
+ selection.runIds.length > 0 && (!v2 || canExecuteRuns) && /* @__PURE__ */ jsxs("div", {
2585
3232
  className: "mb-4 p-3 bg-action-primary-bg/10 rounded-lg flex flex-wrap items-center gap-3",
2586
3233
  children: [
2587
3234
  /* @__PURE__ */ jsxs("span", {
@@ -2610,7 +3257,7 @@ function ExecutionsListPage() {
2610
3257
  "data-agent": "confirm",
2611
3258
  variant: "outline",
2612
3259
  size: "sm",
2613
- onClick: () => handleBulkAction("cancel"),
3260
+ onClick: () => v2 ? setConfirmStop({ kind: "bulk" }) : handleBulkAction("cancel"),
2614
3261
  children: [/* @__PURE__ */ jsx(Icon, {
2615
3262
  icon: "mdi:stop",
2616
3263
  className: "mr-1",
@@ -2641,7 +3288,7 @@ function ExecutionsListPage() {
2641
3288
  })
2642
3289
  ]
2643
3290
  }),
2644
- phaseC.runControl && /* @__PURE__ */ jsxs(Fragment, { children: [selection.allSelected && total > selection.runIds.length && /* @__PURE__ */ jsx("button", {
3291
+ phaseC.runControl && /* @__PURE__ */ jsxs(Fragment$1, { children: [selection.allSelected && total > selection.runIds.length && /* @__PURE__ */ jsx("button", {
2645
3292
  type: "button",
2646
3293
  onClick: () => setAllMatching((current) => !current),
2647
3294
  className: "text-sm text-action-primary-text hover:underline",
@@ -2678,7 +3325,14 @@ function ExecutionsListPage() {
2678
3325
  }),
2679
3326
  /* @__PURE__ */ jsx("p", {
2680
3327
  className: "mt-1 text-sm text-[var(--color-status-error-text)]",
2681
- children: error.message
3328
+ children: tr("fluidgrids.executions.executionsList.loadErrorDescription", "We couldn't load your runs. Try again in a moment.")
3329
+ }),
3330
+ /* @__PURE__ */ jsx(TechnicalDetails, {
3331
+ className: "mt-2 text-[var(--color-status-error-text)]",
3332
+ children: /* @__PURE__ */ jsx("p", {
3333
+ className: "text-sm font-mono break-words",
3334
+ children: error.message
3335
+ })
2682
3336
  }),
2683
3337
  /* @__PURE__ */ jsx(Button$1, {
2684
3338
  variant: "outline",
@@ -2689,27 +3343,58 @@ function ExecutionsListPage() {
2689
3343
  })
2690
3344
  ]
2691
3345
  }),
2692
- viewMode === "table" && /* @__PURE__ */ jsx(ExecutionTable, {
3346
+ showEmptyState && /* @__PURE__ */ jsx(RunsEmptyState, {
3347
+ filtered: filtersActive,
3348
+ onClearFilters: () => {
3349
+ setSearchFilter(null);
3350
+ resetFilters();
3351
+ },
3352
+ onOpenWorkflows: () => navigate("../list"),
3353
+ onBrowseTemplates: () => navigate("../templates")
3354
+ }),
3355
+ !showEmptyState && viewMode === "table" && v2 && !showDetails && /* @__PURE__ */ jsx(RunsPlainTable, {
2693
3356
  workflowNames,
2694
3357
  executions,
2695
3358
  loading,
3359
+ canExecute: canExecuteRuns,
2696
3360
  onRowClick: handleRowClick,
2697
3361
  onAction: handleAction
2698
3362
  }),
2699
- viewMode === "cards" && /* @__PURE__ */ jsx(ExecutionCards, {
3363
+ !showEmptyState && viewMode === "table" && (!v2 || showDetails) && /* @__PURE__ */ jsx(ExecutionTable, {
3364
+ workflowNames,
3365
+ executions,
3366
+ loading,
3367
+ canExecute: !v2 || canExecuteRuns,
3368
+ onRowClick: handleRowClick,
3369
+ onAction: handleAction
3370
+ }),
3371
+ !showEmptyState && viewMode === "cards" && /* @__PURE__ */ jsx(ExecutionCards, {
2700
3372
  workflowNames,
2701
3373
  executions,
2702
3374
  loading,
2703
3375
  onCardClick: handleRowClick,
2704
3376
  onAction: handleAction
2705
3377
  }),
2706
- viewMode === "timeline" && /* @__PURE__ */ jsx(ExecutionTimeline, {
3378
+ !showEmptyState && viewMode === "timeline" && /* @__PURE__ */ jsx(ExecutionTimeline, {
3379
+ workflowNames,
2707
3380
  executions,
2708
3381
  loading,
2709
3382
  onItemClick: handleRowClick,
2710
3383
  onAction: handleAction
2711
3384
  }),
2712
- /* @__PURE__ */ jsx(Pagination, { totalItems: total }),
3385
+ /* @__PURE__ */ jsx(Pagination, {
3386
+ totalItems: total,
3387
+ showPageSize: !v2 || showDetails
3388
+ }),
3389
+ v2 && /* @__PURE__ */ jsx(ConfirmStopRunDialog, {
3390
+ open: confirmStop !== null,
3391
+ workflowName: confirmStop?.kind === "row" ? confirmStop.run.workflow?.name || workflowNames[confirmStop.run.workflowKey] || confirmStop.run.workflowKey : null,
3392
+ count: selection.runIds.length,
3393
+ onConfirm: () => void handleConfirmedStop(),
3394
+ onOpenChange: (open) => {
3395
+ if (!open) setConfirmStop(null);
3396
+ }
3397
+ }),
2713
3398
  batchType && /* @__PURE__ */ jsx(BatchOperationDialog, {
2714
3399
  type: batchType,
2715
3400
  input,