@parallelworks/ui 0.12.1 → 0.14.0

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 (84) hide show
  1. package/dist/chunks/{BareModal-DBa84JZO.js → BareModal-BqroLbZ5.js} +1 -0
  2. package/dist/chunks/{CodePreviewEditor-hwjZof0M.js → CodePreviewEditor-67A_SKgE.js} +1 -1
  3. package/dist/chunks/{CreateModal-DLCC8t_x.js → CreateModal-DoA2WsXf.js} +28 -8
  4. package/dist/chunks/{Monaco-DCwRDrPm.js → Monaco-H56KyEdb.js} +22 -12
  5. package/dist/chunks/{PageHeader-dyoL44ue.js → PageHeader-CxxbidPp.js} +44 -10
  6. package/dist/chunks/{Provider-CfFV-vc5.js → Provider-CbFVFB7O.js} +8 -2
  7. package/dist/chunks/RequiredMark-DfLdYbYZ.js +11 -0
  8. package/dist/chunks/{StatusBadge-P6C2tv7Z.js → StatusBadge-RRUI78iS.js} +4 -4
  9. package/dist/chunks/{SwitchToggle-DcPCXkqL.js → SwitchToggle-DbW0Q__L.js} +49 -32
  10. package/dist/chunks/{Table-ByziK93x.js → Table-CutP1bRt.js} +22 -6
  11. package/dist/chunks/{Tooltip-WgbiC_nd.js → Tooltip-n9w7Tkt1.js} +28 -3
  12. package/dist/chunks/{editor-BH81U5-C.js → editor-BZ9Buwi4.js} +34 -23
  13. package/dist/chunks/lib-DyJvmbzj.js +256 -0
  14. package/dist/chunks/{list-CWVR3dfZ.js → list-CBZpAYP7.js} +35 -15
  15. package/dist/components/Breadcrumbs.d.ts +9 -0
  16. package/dist/components/CollapsiblePanel.d.ts +4 -4
  17. package/dist/components/CreateModal.d.ts +8 -8
  18. package/dist/components/Dropdown.d.ts +5 -3
  19. package/dist/components/Input.d.ts +1 -1
  20. package/dist/components/Listbox.d.ts +7 -4
  21. package/dist/components/Loader.d.ts +1 -1
  22. package/dist/components/MultiSelect.d.ts +8 -5
  23. package/dist/components/PageHeader.d.ts +4 -3
  24. package/dist/components/Pagination.d.ts +2 -2
  25. package/dist/components/Provider.d.ts +9 -3
  26. package/dist/components/RequiredMark.d.ts +1 -0
  27. package/dist/components/SettingRow.d.ts +1 -1
  28. package/dist/components/SwitchToggle.d.ts +3 -2
  29. package/dist/components/Table.d.ts +5 -3
  30. package/dist/components/Tooltip.d.ts +7 -1
  31. package/dist/components/dropdownUtils.d.ts +6 -6
  32. package/dist/editor/FormCodePanel.d.ts +3 -3
  33. package/dist/editor/Monaco.d.ts +2 -2
  34. package/dist/editor/index.js +1 -1
  35. package/dist/file-explorer/FileExplorer.d.ts +2 -2
  36. package/dist/file-explorer/helpers.d.ts +2 -2
  37. package/dist/file-explorer/index.js +46 -18
  38. package/dist/file-explorer/lib/fileExplorer.d.ts +1 -1
  39. package/dist/file-explorer/lib/types.d.ts +21 -21
  40. package/dist/form/FieldWrapper.d.ts +6 -1
  41. package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
  42. package/dist/form/Fields/StepField.d.ts +1 -1
  43. package/dist/form/Form.d.ts +28 -27
  44. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +16 -3
  45. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +11 -11
  46. package/dist/form/SpecialTypes/FormikCustomInput.d.ts +12 -10
  47. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +1 -1
  48. package/dist/form/SpecialTypes/FormikNumericInput.d.ts +2 -2
  49. package/dist/form/Wizard/WizardContainer.d.ts +1 -1
  50. package/dist/form/Wizard/WizardStepContent.d.ts +2 -2
  51. package/dist/form/Wizard/types.d.ts +21 -14
  52. package/dist/form/Wizard/utils.d.ts +1 -1
  53. package/dist/form/fieldContext.d.ts +19 -0
  54. package/dist/form/index.d.ts +2 -1
  55. package/dist/form/index.js +191 -106
  56. package/dist/form/lib.d.ts +3 -3
  57. package/dist/form/types/fieldComponentTypes.d.ts +2 -2
  58. package/dist/form/useParsedOpts.d.ts +1 -1
  59. package/dist/form/utils/getParentValue.d.ts +3 -1
  60. package/dist/form/utils/getValueUsingPath.d.ts +1 -1
  61. package/dist/form/utils/pwResolver.d.ts +4 -4
  62. package/dist/icons.d.ts +8 -8
  63. package/dist/icons.js +4 -4
  64. package/dist/index.d.ts +4 -3
  65. package/dist/index.js +51 -21
  66. package/dist/list/ActionToolbar.d.ts +4 -4
  67. package/dist/list/HoverCard.d.ts +3 -1
  68. package/dist/list/ListPage.d.ts +3 -3
  69. package/dist/list/ListTable.d.ts +1 -1
  70. package/dist/list/RowContextMenu.d.ts +20 -20
  71. package/dist/list/index.js +2 -2
  72. package/dist/logviewer/index.js +24 -7
  73. package/dist/styles/theme.css +6 -6
  74. package/dist/styles.css +1 -1
  75. package/dist/theme/index.js +4 -3
  76. package/dist/workflow/AnnotationBanner.d.ts +2 -1
  77. package/dist/workflow/DependencyGraph.d.ts +15 -3
  78. package/dist/workflow/JobSummary.d.ts +2 -25
  79. package/dist/workflow/MatrixGroup.d.ts +3 -2
  80. package/dist/workflow/TreeView.d.ts +1 -32
  81. package/dist/workflow/index.js +235 -138
  82. package/dist/workflow/types.d.ts +48 -0
  83. package/package.json +2 -6
  84. package/dist/chunks/lib-DTgL8klR.js +0 -191
@@ -1,13 +1,13 @@
1
1
  import { ArrowLeftIcon, ArrowRightIcon, ChevronDownIcon, ChevronRightIcon, DocumentIcon, ErrorIcon, InfoIcon, LoaderIcon, OpenInNewGraphIcon, WarningIcon } from "../icons.js";
2
- import { c as useStrings, o as useRunFile, r as useNavigation, s as useSlots } from "../chunks/Provider-CfFV-vc5.js";
3
- import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-WgbiC_nd.js";
2
+ import { c as useStrings, o as useRunFile, r as useNavigation, s as useSlots } from "../chunks/Provider-CbFVFB7O.js";
3
+ import { n as TOOLTIP_ID, r as TooltipInfo } from "../chunks/Tooltip-n9w7Tkt1.js";
4
4
  import { t as Loader } from "../chunks/Loader-BXoXTZGc.js";
5
- import { n as modalPanelClasses, t as BareModal } from "../chunks/BareModal-DBa84JZO.js";
5
+ import { n as modalPanelClasses, t as BareModal } from "../chunks/BareModal-BqroLbZ5.js";
6
6
  import { t as Indicator } from "../chunks/Indicator-hBSIxOPK.js";
7
7
  import { LogViewer } from "../logviewer/index.js";
8
8
  import cx from "classnames";
9
9
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
- import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
10
+ import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
11
11
  import { flushSync } from "react-dom";
12
12
  import { DateTime } from "luxon";
13
13
  import { buildSubworkflowLogPath, collapseMatrixGroupsInLayout, collectExpansionKeys, computeMatrixAggStatus, detectMatrixGroups, dumpYaml, getAllDeps, getStepLabel, isRunActive, toAbsHumanDuration } from "@parallelworks/workflow-parser";
@@ -246,13 +246,15 @@ function UsesIcon({ uses, subworkflow }) {
246
246
  const { jobActions } = useStrings();
247
247
  if (!uses || !slots.workflowIcon) return null;
248
248
  const open = () => {
249
- if (uses.startsWith("workflow/")) navigation.toWorkflow(uses.split("/")[1]);
249
+ if (uses.startsWith("workflow/")) navigation.toWorkflow(uses.split("/")[1] ?? "");
250
250
  else if (uses.startsWith("github/")) {
251
251
  const [ownerRepo, ref] = uses.replace("github/", "").split("@");
252
252
  navigation.openExternal("https://github.com/" + ownerRepo + "/tree/" + ref);
253
- } else navigation.toMarketItem(uses.split("/")[1]);
253
+ } else navigation.toMarketItem(uses.split("/")[1] ?? "");
254
254
  };
255
- return /* @__PURE__ */ jsx("div", {
255
+ return /* @__PURE__ */ jsx("button", {
256
+ type: "button",
257
+ "aria-label": jobActions.openOriginalWorkflow,
256
258
  "data-tooltip-id": TOOLTIP_ID,
257
259
  "data-tooltip-content": jobActions.openOriginalWorkflow,
258
260
  onClick: open,
@@ -273,9 +275,11 @@ function Joblist(inputs) {
273
275
  const jobLabel = matrixMeta?.originaljob ? `${matrixMeta.originaljob[0].toUpperCase() + matrixMeta.originaljob.slice(1)} (${(matrixMeta.index ?? 0) + 1}/${matrixMeta.totalingroup ?? "?"})` : (jobName[0].toUpperCase() + jobName.slice(1)).replace(/_/g, " ");
274
276
  return /* @__PURE__ */ jsxs("div", {
275
277
  className: "py-1 select-none",
276
- children: [/* @__PURE__ */ jsxs("div", {
278
+ children: [/* @__PURE__ */ jsxs("button", {
279
+ type: "button",
280
+ "aria-expanded": inputs.isStepsOpen(jobName),
277
281
  onClick: () => inputs.onJobClick(jobName),
278
- className: cx("flex items-center gap-x-1.5", jobStatus === "skipped" || jobStatus === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
282
+ className: cx("flex items-center gap-x-1.5 w-full text-left", jobStatus === "skipped" || jobStatus === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
279
283
  children: [!inputs.preview && /* @__PURE__ */ jsx(Indicator, { status: inputs.jobs[jobName]?.status }), /* @__PURE__ */ jsx("div", {
280
284
  className: "font-medium",
281
285
  children: jobLabel
@@ -291,9 +295,10 @@ function Joblist(inputs) {
291
295
  const canExpand = !!inputs.renderSubworkflow && !!subJobs && Object.keys(subJobs).length > 0;
292
296
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
293
297
  className: cx("flex items-center", step.status === "skipped" || step.status === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
294
- children: [/* @__PURE__ */ jsxs("div", {
298
+ children: [/* @__PURE__ */ jsxs("button", {
299
+ type: "button",
295
300
  onClick: () => inputs.onStepClick(jobName, i),
296
- className: cx(inputs.preview ? "ml-1.5" : "ml-4", "gap-x-1.5 flex items-center"),
301
+ className: cx(inputs.preview ? "ml-1.5" : "ml-4", "gap-x-1.5 flex items-center text-left"),
297
302
  "data-testid": stepLabel,
298
303
  children: [inputs.preview ? "↳" : /* @__PURE__ */ jsx(Indicator, { status: step.status }), /* @__PURE__ */ jsx("div", { children: stepLabel })]
299
304
  }), step.subworkflow && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -373,10 +378,14 @@ function InlineLogViewer({ slug, jobName, stepIndex }) {
373
378
  function JobRow({ jobName, job, isExpanded, onToggle, dependencies, workflowName, runNumber, slug, expandedJobs, onJobToggle, expandedSteps, onStepToggle }) {
374
379
  const hasSteps = job.steps && job.steps.length > 0;
375
380
  const duration = getJobDuration(job);
381
+ const panelId = useId();
376
382
  return /* @__PURE__ */ jsxs("div", {
377
383
  className: "border-b border-[var(--theme-border)] last:border-b-0",
378
- children: [/* @__PURE__ */ jsxs("div", {
379
- className: cx("flex items-center gap-3 px-5 py-3 transition-colors", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
384
+ children: [/* @__PURE__ */ jsxs("button", {
385
+ type: "button",
386
+ "aria-expanded": isExpanded,
387
+ "aria-controls": hasSteps ? panelId : void 0,
388
+ className: cx("flex items-center gap-3 px-5 py-3 transition-colors w-full text-left", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
380
389
  onClick: onToggle,
381
390
  children: [
382
391
  /* @__PURE__ */ jsx(motion.div, {
@@ -433,6 +442,7 @@ function JobRow({ jobName, job, isExpanded, onToggle, dependencies, workflowName
433
442
  ]
434
443
  },
435
444
  className: "overflow-hidden",
445
+ id: panelId,
436
446
  children: /* @__PURE__ */ jsx("div", {
437
447
  className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
438
448
  children: job.steps.map((step, index) => {
@@ -462,20 +472,24 @@ function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, s
462
472
  const duration = getStepDuration(step);
463
473
  const label = getStepLabel(step, index);
464
474
  const isSubworkflow = !!step.subworkflow;
465
- const subworkflowJobs = isSubworkflow ? step.subworkflow.jobs : null;
466
- const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name].if !== false) : [];
475
+ const panelId = useId();
476
+ const subworkflowJobs = step.subworkflow?.jobs ?? null;
477
+ const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name]?.if !== false) : [];
467
478
  const subworkflowDependenciesMap = useMemo(() => {
468
479
  if (!subworkflowJobs) return {};
469
480
  const map = {};
470
481
  for (const subJobName of subworkflowJobNames) {
471
482
  const job = subworkflowJobs[subJobName];
472
- if (job.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
483
+ if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
473
484
  }
474
485
  return map;
475
486
  }, [subworkflowJobs, subworkflowJobNames]);
476
487
  return /* @__PURE__ */ jsxs("div", { children: [
477
- /* @__PURE__ */ jsxs("div", {
478
- className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
488
+ /* @__PURE__ */ jsxs("button", {
489
+ type: "button",
490
+ "aria-expanded": isExpanded,
491
+ "aria-controls": panelId,
492
+ className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors w-full text-left", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
479
493
  onClick: onToggle,
480
494
  children: [
481
495
  /* @__PURE__ */ jsx(motion.div, {
@@ -486,8 +500,8 @@ function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, s
486
500
  }),
487
501
  /* @__PURE__ */ jsx(Indicator, { status: step.status }),
488
502
  isSubworkflow && slots.workflowIcon?.({
489
- image: step.subworkflow.image,
490
- type: step.subworkflow.type,
503
+ image: step.subworkflow?.image,
504
+ type: step.subworkflow?.type,
491
505
  className: "h-4 w-4 flex-shrink-0"
492
506
  }),
493
507
  /* @__PURE__ */ jsxs("span", {
@@ -528,6 +542,7 @@ function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, s
528
542
  ]
529
543
  },
530
544
  className: "overflow-hidden",
545
+ id: panelId,
531
546
  children: /* @__PURE__ */ jsx(InlineLogViewer, {
532
547
  slug,
533
548
  jobName,
@@ -560,15 +575,17 @@ function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, s
560
575
  ]
561
576
  },
562
577
  className: "overflow-hidden",
578
+ id: panelId,
563
579
  children: /* @__PURE__ */ jsx("div", {
564
580
  className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
565
581
  children: subworkflowJobNames.map((subJobName) => {
582
+ const subJob = subworkflowJobs?.[subJobName];
583
+ if (!subJob) return null;
566
584
  const subJobKey = `${jobName}:${index}:${subJobName}`;
567
- const isSubJobExpanded = expandedJobs.has(subJobKey);
568
585
  return /* @__PURE__ */ jsx(SubworkflowJobRow, {
569
586
  jobName: subJobName,
570
- job: subworkflowJobs[subJobName],
571
- isExpanded: isSubJobExpanded,
587
+ job: subJob,
588
+ isExpanded: expandedJobs.has(subJobKey),
572
589
  onToggle: () => onJobToggle(subJobKey),
573
590
  dependencies: subworkflowDependenciesMap[subJobName],
574
591
  slug,
@@ -590,10 +607,14 @@ function SubworkflowJobRow({ jobName, job, isExpanded, onToggle, dependencies, s
590
607
  const hasSteps = job.steps && job.steps.length > 0;
591
608
  const duration = getJobDuration(job);
592
609
  const nestedSubworkflowPrefix = `${subworkflowPrefix || ""}subworkflows/${parentJobName}/step_${parentStepIndex}/`;
610
+ const panelId = useId();
593
611
  return /* @__PURE__ */ jsxs("div", {
594
612
  className: "border-b border-[var(--theme-border)] last:border-b-0",
595
- children: [/* @__PURE__ */ jsxs("div", {
596
- className: cx("flex items-center gap-3 px-5 py-3 transition-colors", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
613
+ children: [/* @__PURE__ */ jsxs("button", {
614
+ type: "button",
615
+ "aria-expanded": isExpanded,
616
+ "aria-controls": hasSteps ? panelId : void 0,
617
+ className: cx("flex items-center gap-3 px-5 py-3 transition-colors w-full text-left", job.status === "skipped" || job.status === "skipped-failed" ? "cursor-default" : "cursor-pointer", isExpanded && "bg-[var(--theme-panel-bg)]"),
597
618
  onClick: onToggle,
598
619
  children: [
599
620
  /* @__PURE__ */ jsx(motion.div, {
@@ -650,6 +671,7 @@ function SubworkflowJobRow({ jobName, job, isExpanded, onToggle, dependencies, s
650
671
  ]
651
672
  },
652
673
  className: "overflow-hidden",
674
+ id: panelId,
653
675
  children: /* @__PURE__ */ jsx("div", {
654
676
  className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
655
677
  children: job.steps.map((step, index) => {
@@ -679,20 +701,24 @@ function SubworkflowStepRow({ step, index, isExpanded, onToggle, slug, subworkfl
679
701
  const duration = getStepDuration(step);
680
702
  const label = getStepLabel(step, index);
681
703
  const isSubworkflow = !!step.subworkflow;
682
- const subworkflowJobs = isSubworkflow ? step.subworkflow.jobs : null;
683
- const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name].if !== false) : [];
704
+ const panelId = useId();
705
+ const subworkflowJobs = step.subworkflow?.jobs ?? null;
706
+ const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name]?.if !== false) : [];
684
707
  const subworkflowDependenciesMap = useMemo(() => {
685
708
  if (!subworkflowJobs) return {};
686
709
  const map = {};
687
710
  for (const subJobName of subworkflowJobNames) {
688
711
  const job = subworkflowJobs[subJobName];
689
- if (job.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
712
+ if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
690
713
  }
691
714
  return map;
692
715
  }, [subworkflowJobs, subworkflowJobNames]);
693
716
  return /* @__PURE__ */ jsxs("div", { children: [
694
- /* @__PURE__ */ jsxs("div", {
695
- className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
717
+ /* @__PURE__ */ jsxs("button", {
718
+ type: "button",
719
+ "aria-expanded": isExpanded,
720
+ "aria-controls": panelId,
721
+ className: cx("flex items-center gap-3 px-5 py-2.5 cursor-pointer transition-colors w-full text-left", isExpanded ? "" : "hover:bg-[var(--theme-hover)]"),
696
722
  onClick: onToggle,
697
723
  children: [
698
724
  /* @__PURE__ */ jsx(motion.div, {
@@ -703,8 +729,8 @@ function SubworkflowStepRow({ step, index, isExpanded, onToggle, slug, subworkfl
703
729
  }),
704
730
  /* @__PURE__ */ jsx(Indicator, { status: step.status }),
705
731
  isSubworkflow && slots.workflowIcon?.({
706
- image: step.subworkflow.image,
707
- type: step.subworkflow.type,
732
+ image: step.subworkflow?.image,
733
+ type: step.subworkflow?.type,
708
734
  className: "h-4 w-4 flex-shrink-0"
709
735
  }),
710
736
  /* @__PURE__ */ jsxs("span", {
@@ -745,6 +771,7 @@ function SubworkflowStepRow({ step, index, isExpanded, onToggle, slug, subworkfl
745
771
  ]
746
772
  },
747
773
  className: "overflow-hidden",
774
+ id: panelId,
748
775
  children: /* @__PURE__ */ jsx(SubworkflowInlineLogViewer, {
749
776
  slug,
750
777
  subworkflowPrefix,
@@ -778,15 +805,17 @@ function SubworkflowStepRow({ step, index, isExpanded, onToggle, slug, subworkfl
778
805
  ]
779
806
  },
780
807
  className: "overflow-hidden",
808
+ id: panelId,
781
809
  children: /* @__PURE__ */ jsx("div", {
782
810
  className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
783
811
  children: subworkflowJobNames.map((subJobName) => {
812
+ const subJob = subworkflowJobs?.[subJobName];
813
+ if (!subJob) return null;
784
814
  const subJobKey = `${subworkflowPrefix}${jobName}:${index}:${subJobName}`;
785
- const isSubJobExpanded = expandedJobs.has(subJobKey);
786
815
  return /* @__PURE__ */ jsx(SubworkflowJobRow, {
787
816
  jobName: subJobName,
788
- job: subworkflowJobs[subJobName],
789
- isExpanded: isSubJobExpanded,
817
+ job: subJob,
818
+ isExpanded: expandedJobs.has(subJobKey),
790
819
  onToggle: () => onJobToggle(subJobKey),
791
820
  dependencies: subworkflowDependenciesMap[subJobName],
792
821
  slug,
@@ -829,7 +858,7 @@ function SubworkflowInlineLogViewer({ slug, subworkflowPrefix, jobName, stepInde
829
858
  }
830
859
  function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpandedJobs }) {
831
860
  const visibleJobNames = useMemo(() => {
832
- return Object.keys(jobs).filter((jobName) => jobs[jobName].if !== false);
861
+ return Object.keys(jobs).filter((jobName) => jobs[jobName]?.if !== false);
833
862
  }, [jobs]);
834
863
  const [expandedSteps, setExpandedSteps] = useState(/* @__PURE__ */ new Set());
835
864
  const [expandedSubworkflowJobs, setExpandedSubworkflowJobs] = useState(/* @__PURE__ */ new Set());
@@ -876,7 +905,7 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
876
905
  const map = {};
877
906
  for (const jobName of visibleJobNames) {
878
907
  const job = jobs[jobName];
879
- if (job.needs && Array.isArray(job.needs) && job.needs.length > 0) map[jobName] = job.needs;
908
+ if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[jobName] = job.needs;
880
909
  }
881
910
  return map;
882
911
  }, [jobs, visibleJobNames]);
@@ -894,13 +923,10 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
894
923
  const matrixGroups = {};
895
924
  for (const jobName of visibleJobNames) {
896
925
  const matrix = jobs[jobName]?._matrix;
897
- if (matrix?.originaljob) {
898
- if (!matrixGroups[matrix.originaljob]) matrixGroups[matrix.originaljob] = {
899
- originaljob: matrix.originaljob,
900
- members: []
901
- };
902
- matrixGroups[matrix.originaljob].members.push(jobName);
903
- }
926
+ if (matrix?.originaljob) (matrixGroups[matrix.originaljob] ??= {
927
+ originaljob: matrix.originaljob,
928
+ members: []
929
+ }).members.push(jobName);
904
930
  }
905
931
  for (const group of Object.values(matrixGroups)) group.members.sort((a, b) => (jobs[a]?._matrix?.index ?? 0) - (jobs[b]?._matrix?.index ?? 0));
906
932
  const rendered = [];
@@ -911,12 +937,16 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
911
937
  if (seenMatrixGroups.has(matrixOrigin)) continue;
912
938
  seenMatrixGroups.add(matrixOrigin);
913
939
  const group = matrixGroups[matrixOrigin];
940
+ if (!group) continue;
914
941
  const isGroupExpanded = expandedJobs.has(`matrix:${matrixOrigin}`);
915
942
  const { aggStatus } = computeMatrixAggStatus(group.members.map((m) => jobs[m]?.status));
916
943
  rendered.push(/* @__PURE__ */ jsxs("div", {
917
944
  className: "border-b border-[var(--theme-border)]",
918
- children: [/* @__PURE__ */ jsxs("div", {
919
- className: "flex items-center gap-3 px-5 py-3 cursor-pointer transition-colors",
945
+ children: [/* @__PURE__ */ jsxs("button", {
946
+ type: "button",
947
+ "aria-expanded": isGroupExpanded,
948
+ "aria-controls": `matrix-panel-${matrixOrigin}`,
949
+ className: "flex items-center gap-3 px-5 py-3 cursor-pointer transition-colors w-full text-left",
920
950
  onClick: () => toggleJob(`matrix:${matrixOrigin}`),
921
951
  children: [
922
952
  /* @__PURE__ */ jsx(motion.div, {
@@ -952,6 +982,7 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
952
982
  },
953
983
  transition: { duration: .15 },
954
984
  className: "overflow-hidden pl-4",
985
+ id: `matrix-panel-${matrixOrigin}`,
955
986
  children: group.members.map((memberName) => /* @__PURE__ */ jsx(JobRow, {
956
987
  jobName: memberName,
957
988
  job: jobs[memberName],
@@ -993,9 +1024,11 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
993
1024
  function MatrixGroupSummaryItem({ matrixGroup, status, isExpanded, onToggle, jobs, isStepsOpen, onJobClick, onStepClick, onStepLogClick, onSubworkflowIcon, isSubworkflowExpanded, renderSubworkflow, preview }) {
994
1025
  return /* @__PURE__ */ jsxs("div", {
995
1026
  className: "py-1.5",
996
- children: [/* @__PURE__ */ jsxs("div", {
1027
+ children: [/* @__PURE__ */ jsxs("button", {
1028
+ type: "button",
1029
+ "aria-expanded": isExpanded,
997
1030
  onClick: onToggle,
998
- className: "cursor-pointer flex items-center gap-x-1.5",
1031
+ className: "cursor-pointer flex items-center gap-x-1.5 w-full text-left",
999
1032
  children: [/* @__PURE__ */ jsx(Indicator, { status }), /* @__PURE__ */ jsxs("div", {
1000
1033
  className: "font-semibold",
1001
1034
  children: [
@@ -1038,18 +1071,23 @@ function MatrixGroupNode({ matrixGroup, jobNames, idPrefix, isExpanded, onToggle
1038
1071
  const { aggStatus, statusLabel } = computeMatrixAggStatus(matrixMembers.map((m) => jobs[m]?.status));
1039
1072
  return /* @__PURE__ */ jsxs("div", {
1040
1073
  id: "node_" + idPrefix + jobNames[0],
1074
+ role: "none",
1041
1075
  className: "relative m-24",
1042
1076
  style: { zIndex: (activeDists?.has(jobNames[0]) ? 25 : 1) + zBase },
1043
1077
  onMouseEnter,
1044
1078
  onMouseLeave,
1079
+ onFocus: onMouseEnter,
1080
+ onBlur: onMouseLeave,
1045
1081
  children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsxs("div", {
1046
1082
  className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
1047
1083
  style: {
1048
1084
  opacity: hoveredRelated && !hoveredRelated.has(jobNames[0]) ? .5 : 1,
1049
1085
  transition: `opacity ${animT}s`
1050
1086
  },
1051
- children: [/* @__PURE__ */ jsxs("div", {
1052
- className: "cursor-pointer",
1087
+ children: [/* @__PURE__ */ jsxs("button", {
1088
+ type: "button",
1089
+ "aria-expanded": isExpanded,
1090
+ className: "cursor-pointer w-full text-left",
1053
1091
  onClick: onToggle,
1054
1092
  children: [/* @__PURE__ */ jsxs("div", {
1055
1093
  className: "font-semibold text-md p-0.5",
@@ -1086,6 +1124,11 @@ function MatrixGroupNode({ matrixGroup, jobNames, idPrefix, isExpanded, onToggle
1086
1124
  }, "node_" + jobNames[0]);
1087
1125
  }
1088
1126
  //#endregion
1127
+ //#region src/workflow/types.ts
1128
+ function isJobRecord(value) {
1129
+ return typeof value === "object" && value !== null;
1130
+ }
1131
+ //#endregion
1089
1132
  //#region src/workflow/DependencyGraph.tsx
1090
1133
  function ViewToggle({ viewMode, setViewMode }) {
1091
1134
  return /* @__PURE__ */ jsxs("div", {
@@ -1106,36 +1149,46 @@ function ViewToggle({ viewMode, setViewMode }) {
1106
1149
  var addCleanupSteps = ({ jobs }) => {
1107
1150
  const clonedJobs = structuredClone(jobs);
1108
1151
  for (const job in jobs) {
1109
- if (Array.isArray(jobs[job].steps)) jobs[job].steps.slice().reverse().forEach((step, index) => {
1110
- if (!step.cleanup) return;
1111
- const linkedName = step.name ? step.name : `Step ${jobs[job].steps.length - index - 1}`;
1112
- const newStep = {
1113
- ...step,
1114
- name: `POST ${linkedName}`,
1115
- run: step.cleanup,
1116
- linkedStep: `${jobs[job].steps.length - index - 1}`
1117
- };
1118
- delete newStep.cleanup;
1119
- delete clonedJobs[job].steps[index].cleanup;
1120
- clonedJobs[job].steps.push(newStep);
1121
- });
1122
- if (Array.isArray(jobs[job].cleanup)) {
1123
- jobs[job].cleanup.slice().reverse().forEach((step, index) => {
1152
+ const source = jobs[job];
1153
+ const cloned = clonedJobs[job];
1154
+ if (!source || !cloned) continue;
1155
+ if (Array.isArray(source.steps)) {
1156
+ const sourceSteps = source.steps;
1157
+ sourceSteps.slice().reverse().forEach((step, index) => {
1124
1158
  if (!step.cleanup) return;
1125
- const linkedName = step.name ? step.name : `Cleanup ${jobs[job].cleanup.length - index - 1}`;
1159
+ const linkedName = step.name ? step.name : `Step ${sourceSteps.length - index - 1}`;
1126
1160
  const newStep = {
1127
1161
  ...step,
1128
1162
  name: `POST ${linkedName}`,
1129
1163
  run: step.cleanup,
1130
- linkedStep: `${jobs[job].cleanup.length - index - 1 + clonedJobs[job].steps.length}`
1164
+ linkedStep: `${sourceSteps.length - index - 1}`
1131
1165
  };
1132
1166
  delete newStep.cleanup;
1133
- delete clonedJobs[job].cleanup[index].cleanup;
1134
- clonedJobs[job].cleanup.push(newStep);
1167
+ const clonedStep = cloned.steps[index];
1168
+ if (clonedStep) delete clonedStep.cleanup;
1169
+ cloned.steps.push(newStep);
1135
1170
  });
1136
- for (let i = 0; i < clonedJobs[job].cleanup.length; i++) clonedJobs[job].cleanup[i].linkedStep = ".";
1137
- clonedJobs[job].steps = [...clonedJobs[job].steps, ...clonedJobs[job].cleanup];
1138
- delete clonedJobs[job].cleanup;
1171
+ }
1172
+ const sourceCleanup = source.cleanup;
1173
+ const clonedCleanup = cloned.cleanup;
1174
+ if (Array.isArray(sourceCleanup) && clonedCleanup) {
1175
+ sourceCleanup.slice().reverse().forEach((step, index) => {
1176
+ if (!step.cleanup) return;
1177
+ const linkedName = step.name ? step.name : `Cleanup ${sourceCleanup.length - index - 1}`;
1178
+ const newStep = {
1179
+ ...step,
1180
+ name: `POST ${linkedName}`,
1181
+ run: step.cleanup,
1182
+ linkedStep: `${sourceCleanup.length - index - 1 + cloned.steps.length}`
1183
+ };
1184
+ delete newStep.cleanup;
1185
+ const clonedStep = clonedCleanup[index];
1186
+ if (clonedStep) delete clonedStep.cleanup;
1187
+ clonedCleanup.push(newStep);
1188
+ });
1189
+ for (const clonedStep of clonedCleanup) clonedStep.linkedStep = ".";
1190
+ cloned.steps = [...cloned.steps, ...clonedCleanup];
1191
+ delete cloned.cleanup;
1139
1192
  }
1140
1193
  }
1141
1194
  return clonedJobs;
@@ -1157,22 +1210,27 @@ function offsetWithin(el, ancestor) {
1157
1210
  function parsePrefixToPath(prefix) {
1158
1211
  const parts = prefix.split("/").filter(Boolean);
1159
1212
  const path = [];
1160
- for (let i = 0; i + 2 < parts.length; i += 3) if (parts[i] === "subworkflows" && parts[i + 1] && parts[i + 2]?.startsWith("step_")) path.push(parts[i + 1], "steps", Number(parts[i + 2].slice(5)), "subworkflow", "jobs");
1213
+ for (let i = 0; i + 2 < parts.length; i += 3) {
1214
+ const name = parts[i + 1];
1215
+ const step = parts[i + 2];
1216
+ if (parts[i] === "subworkflows" && name && step?.startsWith("step_")) path.push(name, "steps", Number(step.slice(5)), "subworkflow", "jobs");
1217
+ }
1161
1218
  return path;
1162
1219
  }
1163
1220
  function computeGraphLayout(jobs, matrixGroups) {
1164
1221
  const visibleJobs = structuredClone(jobs);
1165
- for (const jobName of Object.keys(jobs ?? {})) if (jobs[jobName].if === false) delete visibleJobs[jobName];
1222
+ for (const jobName of Object.keys(jobs ?? {})) if (jobs[jobName]?.if === false) delete visibleJobs[jobName];
1166
1223
  collapseMatrixGroupsInLayout(matrixGroups, visibleJobs);
1167
1224
  const allDeps = {};
1168
1225
  for (const jobName of Object.keys(visibleJobs)) allDeps[jobName] = getAllDeps(jobName, visibleJobs, allDeps);
1169
1226
  const filteredDeps = {};
1170
1227
  for (const jobName of Object.keys(visibleJobs)) {
1171
- const oldNeeds = visibleJobs[jobName]?.needs && Array.isArray(visibleJobs[jobName].needs) ? visibleJobs[jobName].needs : [];
1228
+ const needs = visibleJobs[jobName]?.needs;
1229
+ const oldNeeds = Array.isArray(needs) ? needs : [];
1172
1230
  filteredDeps[jobName] = oldNeeds.filter((dep) => {
1173
1231
  return !oldNeeds.some((other) => {
1174
1232
  if (other === dep) return false;
1175
- return allDeps[other].has(dep);
1233
+ return allDeps[other]?.has(dep) === true;
1176
1234
  });
1177
1235
  }).sort();
1178
1236
  }
@@ -1187,10 +1245,11 @@ function computeGraphLayout(jobs, matrixGroups) {
1187
1245
  if (depended_on[jobName]) depended_on[jobName].sort();
1188
1246
  });
1189
1247
  const unfinished = structuredClone(visibleJobs);
1190
- const deps = [[]];
1248
+ const rootLevel = [];
1249
+ const deps = [rootLevel];
1191
1250
  Object.keys(visibleJobs).map((jobName) => {
1192
- if (filteredDeps[jobName].length === 0) {
1193
- deps[0].push(jobName);
1251
+ if (filteredDeps[jobName]?.length === 0) {
1252
+ rootLevel.push(jobName);
1194
1253
  delete unfinished[jobName];
1195
1254
  }
1196
1255
  });
@@ -1200,7 +1259,7 @@ function computeGraphLayout(jobs, matrixGroups) {
1200
1259
  const unfinishedArr = Object.keys(unfinished);
1201
1260
  prevLen = unfinishedArr.length;
1202
1261
  unfinishedArr.map((jobName) => {
1203
- if (filteredDeps[jobName].every((dep) => !unfinishedArr.includes(dep))) {
1262
+ if ((filteredDeps[jobName] ?? []).every((dep) => !unfinishedArr.includes(dep))) {
1204
1263
  toAppend.push(jobName);
1205
1264
  delete unfinished[jobName];
1206
1265
  }
@@ -1211,17 +1270,19 @@ function computeGraphLayout(jobs, matrixGroups) {
1211
1270
  deps.map((col) => {
1212
1271
  const toAppend = [];
1213
1272
  while (col.length > 0) {
1214
- const grouped = [col[0]];
1273
+ const head = col[0];
1274
+ if (head === void 0) break;
1275
+ const grouped = [head];
1215
1276
  const toRm = [0];
1216
1277
  col.slice(1).map((jobName, j) => {
1217
- if (visibleJobs[col[0]]?._matrixGroup || visibleJobs[jobName]?._matrixGroup) return;
1218
- const depsA = depended_on[col[0]];
1278
+ if (visibleJobs[head]?._matrixGroup || visibleJobs[jobName]?._matrixGroup) return;
1279
+ const depsA = depended_on[head];
1219
1280
  const depsB = depended_on[jobName];
1220
- const depsA2 = filteredDeps[col[0]];
1281
+ const depsA2 = filteredDeps[head];
1221
1282
  const depsB2 = filteredDeps[jobName];
1222
1283
  if (!depsA && !depsB || depsA && depsB && depsA.length === depsB.length) {
1223
1284
  if (!depsA2 && !depsB2 || depsA2 && depsB2 && depsA2.length === depsB2.length) {
1224
- if ((!depsA || depsA.every((dep, i) => dep === depsB[i])) && (!depsA2 || depsA2.every((dep, i) => dep === depsB2[i]))) {
1285
+ if ((!depsA || depsA.every((dep, i) => dep === depsB?.[i])) && (!depsA2 || depsA2.every((dep, i) => dep === depsB2?.[i]))) {
1225
1286
  grouped.push(jobName);
1226
1287
  toRm.push(j + 1);
1227
1288
  }
@@ -1233,9 +1294,15 @@ function computeGraphLayout(jobs, matrixGroups) {
1233
1294
  }
1234
1295
  finalDeps.push(toAppend);
1235
1296
  });
1236
- while (finalDeps.length > 0 && (finalDeps[0].length === 0 || finalDeps[0][0].length === 0)) {
1237
- if (finalDeps[0].length === 0) finalDeps.splice(0, 1);
1238
- while (finalDeps.length > 0 && finalDeps[0].length > 0 && finalDeps[0][0].length === 0) finalDeps[0].splice(0, 1);
1297
+ while (finalDeps.length > 0) {
1298
+ const first = finalDeps[0];
1299
+ if (!first) break;
1300
+ if (first.length === 0) {
1301
+ finalDeps.splice(0, 1);
1302
+ continue;
1303
+ }
1304
+ if (first[0]?.length !== 0) break;
1305
+ while (first.length > 0 && first[0]?.length === 0) first.splice(0, 1);
1239
1306
  }
1240
1307
  const ancestorDists = {};
1241
1308
  const descendantDists = {};
@@ -1388,21 +1455,21 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1388
1455
  const onStepClick = (j, s) => {
1389
1456
  const stepStatus = jobs[j]?.steps?.[+s]?.status;
1390
1457
  if (stepStatus === "skipped" || stepStatus === "skipped-failed") return;
1391
- if (jobs[j].steps[+s].subworkflow) expandInline(j, +s);
1458
+ if (jobs[j]?.steps[+s]?.subworkflow) expandInline(j, +s);
1392
1459
  else setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1393
1460
  };
1394
1461
  const onStepLogClick = (j, s) => setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1395
1462
  const onSubworkflowIcon = (j, s) => openNew(j, s);
1396
1463
  const isSubworkflowExpanded = (j, s) => expandedSubworkflows.has(`${pathPrefix}${j}:${s}`);
1397
1464
  const renderSubworkflow = (j, s) => {
1398
- const sub = jobs[j]?.steps?.[s]?.subworkflow;
1399
- if (!sub?.jobs) return null;
1465
+ const subJobs = jobs[j]?.steps?.[s]?.subworkflow?.jobs;
1466
+ if (!subJobs) return null;
1400
1467
  return /* @__PURE__ */ jsx(SubworkflowWell, {
1401
1468
  expanded: isSubworkflowExpanded(j, s),
1402
1469
  onSettled: requestFit,
1403
1470
  className: "my-2 rounded-lg border-4 border-(--theme-border)",
1404
1471
  children: /* @__PURE__ */ jsx(Subgraph, {
1405
- jobs: sub.jobs,
1472
+ jobs: subJobs,
1406
1473
  pathPrefix: `${pathPrefix}subworkflows/${j}/step_${s}/`,
1407
1474
  depth: depth + 1,
1408
1475
  preview,
@@ -1449,10 +1516,13 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1449
1516
  }, "node_" + jobNames[0]);
1450
1517
  return /* @__PURE__ */ jsxs("div", {
1451
1518
  id: "node_" + pathPrefix + jobNames[0],
1519
+ role: "none",
1452
1520
  className: "relative m-24",
1453
1521
  style: { zIndex: (activeDists?.has(jobNames[0]) ? 25 : 1) + zBase },
1454
1522
  onMouseEnter: () => setHoveredJob(jobNames[0]),
1455
1523
  onMouseLeave: () => setHoveredJob(null),
1524
+ onFocus: () => setHoveredJob(jobNames[0]),
1525
+ onBlur: () => setHoveredJob(null),
1456
1526
  children: [/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-2xl bg-(--theme-panel-bg)" }), /* @__PURE__ */ jsx("div", {
1457
1527
  className: "relative border-solid shadow py-4 px-8 rounded-xl border-4 whitespace-nowrap bg-(--theme-panel-bg) text-2xl",
1458
1528
  style: {
@@ -1474,7 +1544,7 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1474
1544
  })
1475
1545
  })]
1476
1546
  }, "node_" + jobNames[0]);
1477
- }) }, col[0][0]);
1547
+ }) }, col[0]?.[0]);
1478
1548
  })
1479
1549
  });
1480
1550
  }, [
@@ -1512,8 +1582,8 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1512
1582
  const nodeConnectorY = (el) => offsetWithin(el, wrapper).y + 37;
1513
1583
  let colGap = 32;
1514
1584
  if (dependencyCols.length >= 2) {
1515
- const col0 = gid(dependencyCols[0][0][0]);
1516
- const col1 = gid(dependencyCols[1][0][0]);
1585
+ const col0 = gid(dependencyCols[0]?.[0]?.[0] ?? "");
1586
+ const col1 = gid(dependencyCols[1]?.[0]?.[0] ?? "");
1517
1587
  if (col0 && col1) colGap = ox(col1) - (ox(col0) + col0.offsetWidth);
1518
1588
  }
1519
1589
  let minAdy = Number.POSITIVE_INFINITY;
@@ -1581,6 +1651,7 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1581
1651
  }
1582
1652
  return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1583
1653
  /* @__PURE__ */ jsxs("svg", {
1654
+ "aria-hidden": "true",
1584
1655
  overflow: "visible",
1585
1656
  className: "absolute pointer-events-none",
1586
1657
  style: { zIndex: (isHighlighted || activeDists?.has(dep) && activeDists?.has(jobNames[0]) ? 30 : 10) + zBase },
@@ -1622,6 +1693,7 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1622
1693
  ]
1623
1694
  }),
1624
1695
  /* @__PURE__ */ jsxs("svg", {
1696
+ "aria-hidden": "true",
1625
1697
  overflow: "visible",
1626
1698
  className: "absolute pointer-events-none",
1627
1699
  style: { zIndex: (isHighlighted ? 20 : -1) + zBase },
@@ -1642,6 +1714,7 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1642
1714
  })]
1643
1715
  }),
1644
1716
  /* @__PURE__ */ jsxs("svg", {
1717
+ "aria-hidden": "true",
1645
1718
  overflow: "visible",
1646
1719
  className: "absolute pointer-events-none",
1647
1720
  style: { zIndex: 20 + zBase },
@@ -1677,7 +1750,7 @@ function Subgraph({ jobs, pathPrefix, depth, preview, animT, expandedSubworkflow
1677
1750
  })()]
1678
1751
  })
1679
1752
  ] }, dep);
1680
- }) }, jobNames[0])) }, col[0][0]));
1753
+ }) }, jobNames[0])) }, col[0]?.[0]));
1681
1754
  })(), hasLiveNestedGraph ? buildBoxLayer() : memoizedBoxLayer]
1682
1755
  });
1683
1756
  }
@@ -1702,21 +1775,21 @@ function SidebarJobs({ jobs, pathPrefix, preview, stepsOpen, toggleSteps, expand
1702
1775
  const onStepClick = (j, s) => {
1703
1776
  const stepStatus = jobs[j]?.steps?.[+s]?.status;
1704
1777
  if (stepStatus === "skipped" || stepStatus === "skipped-failed") return;
1705
- if (jobs[j].steps[+s].subworkflow) expandInline(j, +s);
1778
+ if (jobs[j]?.steps[+s]?.subworkflow) expandInline(j, +s);
1706
1779
  else setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1707
1780
  };
1708
1781
  const onStepLogClick = (j, s) => setSublogOpen(buildSubworkflowLogPath(pathPrefix, j, s));
1709
1782
  const onSubworkflowIcon = (j, s) => openNew(j, s);
1710
1783
  const isSubworkflowExpanded = (j, s) => expandedSubworkflows.has(`${pathPrefix}${j}:${s}`);
1711
1784
  const renderSubworkflow = (j, s) => {
1712
- const sub = jobs[j]?.steps?.[s]?.subworkflow;
1713
- if (!sub?.jobs) return null;
1785
+ const subJobs = jobs[j]?.steps?.[s]?.subworkflow?.jobs;
1786
+ if (!subJobs) return null;
1714
1787
  return /* @__PURE__ */ jsx(SidebarWell, {
1715
1788
  expanded: isSubworkflowExpanded(j, s),
1716
1789
  children: /* @__PURE__ */ jsx("div", {
1717
1790
  className: "ml-6 mt-0.5 border-l border-(--theme-border) pl-4",
1718
1791
  children: /* @__PURE__ */ jsx(SidebarJobs, {
1719
- jobs: sub.jobs,
1792
+ jobs: subJobs,
1720
1793
  pathPrefix: `${pathPrefix}subworkflows/${j}/step_${s}/`,
1721
1794
  preview,
1722
1795
  stepsOpen,
@@ -1742,7 +1815,7 @@ function SidebarJobs({ jobs, pathPrefix, preview, stepsOpen, toggleSteps, expand
1742
1815
  const mg = displayJobs[jobName]?._matrixGroup;
1743
1816
  if (mg) return /* @__PURE__ */ jsx(MatrixGroupSummaryItem, {
1744
1817
  matrixGroup: mg,
1745
- status: displayJobs[jobName]?.status,
1818
+ status: displayJobs[jobName]?.status ?? "",
1746
1819
  isExpanded: matrixOpen.has(pathPrefix + mg.originaljob),
1747
1820
  onToggle: () => toggleMatrix(pathPrefix + mg.originaljob),
1748
1821
  jobs,
@@ -1769,14 +1842,39 @@ function SidebarJobs({ jobs, pathPrefix, preview, stepsOpen, toggleSteps, expand
1769
1842
  }, "job-" + pathPrefix + jobName);
1770
1843
  }) });
1771
1844
  }
1845
+ var RUN_STATUSES = [
1846
+ "started",
1847
+ "running",
1848
+ "completed",
1849
+ "error",
1850
+ "failed",
1851
+ "canceled",
1852
+ "skipped",
1853
+ "faulted",
1854
+ "canceling",
1855
+ "skipped-failed",
1856
+ ""
1857
+ ];
1858
+ function toStatus(value) {
1859
+ return RUN_STATUSES.find((status) => status === value) ?? "";
1860
+ }
1861
+ function walkJobPath(root, path) {
1862
+ let cursor = root;
1863
+ for (const key of path) {
1864
+ if (typeof cursor !== "object" || cursor === null) return {};
1865
+ cursor = Object.getOwnPropertyDescriptor(cursor, String(key))?.value;
1866
+ }
1867
+ return isJobRecord(cursor) ? cursor : {};
1868
+ }
1772
1869
  function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder, viewMode = "dag", setViewMode }) {
1773
1870
  const animT = .125;
1774
1871
  const minScale = .2;
1775
1872
  const maxScale = .6;
1776
- let jobs = run.executedJobs;
1777
- const runNumber = run.number;
1778
- const runSlug = run.slug;
1779
- const workflowName = run.workflowName;
1873
+ const rootJobs = isJobRecord(run.executedJobs) ? run.executedJobs : {};
1874
+ const runNumber = run.number ?? 0;
1875
+ const runSlug = run.slug ?? "";
1876
+ const workflowName = run.workflowName ?? "";
1877
+ const runStatus = toStatus(run.status);
1780
1878
  const [sublogOpen, setSublogOpen] = useState("");
1781
1879
  const [stepsOpen, setStepsOpen] = useState(/* @__PURE__ */ new Set());
1782
1880
  const [expandedSubworkflows, setExpandedSubworkflows] = useState(/* @__PURE__ */ new Set());
@@ -1834,9 +1932,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
1834
1932
  const timer = setInterval(() => setNow(DateTime.now()), 1e3);
1835
1933
  return () => clearInterval(timer);
1836
1934
  }, [isRunFinished]);
1837
- if (executedJobsPath[executedJobsPathIdx].length > 0) executedJobsPath[executedJobsPathIdx].map((prop) => {
1838
- jobs = jobs[prop];
1839
- });
1935
+ const jobs = walkJobPath(rootJobs, executedJobsPath[executedJobsPathIdx] ?? []);
1840
1936
  const resetGraph = useCallback(() => {
1841
1937
  setSublogOpen("");
1842
1938
  setStepsOpen(/* @__PURE__ */ new Set());
@@ -1891,10 +1987,10 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
1891
1987
  sublogsLoading
1892
1988
  ]);
1893
1989
  const jobAndStep = sublogOpen.split("/").slice(-3, -1);
1894
- if (jobAndStep.length > 1) jobAndStep[1] = jobAndStep[1].split("_")[1];
1895
- const logName = jobAndStep.length > 1 ? "Job: " + (jobAndStep[0][0].toUpperCase() + jobAndStep[0].slice(1)).replace(/_/g, " ") + " \nStep: " + (jobs[jobAndStep[0]]?.steps[jobAndStep[1]]?.name || "Step " + jobAndStep[1]) : "";
1896
- const sublogIsSubworkflow = jobAndStep.length > 1 && jobs[jobAndStep[0]]?.steps[jobAndStep[1]]?.subworkflow;
1897
- const currentSublog = sublogOpen === "" ? "" : preview ? dumpYaml(jobs[jobAndStep[0]]?.steps[jobAndStep[1]]) : sublogsError || !sublogs ? sublogIsSubworkflow ? "Subworkflows generally have no logs/scripts at this level.\nClick on the \"Open in new graph\" icon on the subworkflow step to open the subworkflow graph." : "No log found" : sublogs;
1990
+ if (jobAndStep.length > 1) jobAndStep[1] = jobAndStep[1].split("_")[1] ?? "";
1991
+ const sublogIsStep = jobAndStep.length > 1;
1992
+ const logName = jobAndStep.length > 1 ? "Job: " + (jobAndStep[0][0].toUpperCase() + jobAndStep[0].slice(1)).replace(/_/g, " ") + " \nStep: " + (jobs[jobAndStep[0]]?.steps[Number(jobAndStep[1])]?.name || "Step " + jobAndStep[1]) : "";
1993
+ const sublogIsSubworkflow = jobAndStep.length > 1 && jobs[jobAndStep[0]]?.steps[Number(jobAndStep[1])]?.subworkflow;
1898
1994
  const matrixGroups = useMemo(() => detectMatrixGroups(jobs), [jobs]);
1899
1995
  const { dependencyCols } = useMemo(() => computeGraphLayout(jobs, matrixGroups), [jobs, matrixGroups]);
1900
1996
  const fitGraph = useCallback((animationMs = 0) => {
@@ -1950,18 +2046,20 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
1950
2046
  windowSize,
1951
2047
  executedJobsPathIdx
1952
2048
  ]);
1953
- const currentPath = executedJobsPath[executedJobsPathIdx];
2049
+ const currentPath = executedJobsPath[executedJobsPathIdx] ?? [];
1954
2050
  const nestingDepth = currentPath.length / 5;
1955
2051
  let pathAppend = "";
1956
- for (let i = 0; i < nestingDepth; i++) pathAppend += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2].toString()}/`;
2052
+ for (let i = 0; i < nestingDepth; i++) pathAppend += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2]?.toString()}/`;
1957
2053
  let generalLogPath = "logs.out";
1958
2054
  if (nestingDepth > 0) {
1959
2055
  let parentPrefix = "";
1960
- for (let i = 0; i < nestingDepth - 1; i++) parentPrefix += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2].toString()}/`;
1961
- const lastJob = currentPath[5 * (nestingDepth - 1)];
1962
- const lastStep = currentPath[5 * (nestingDepth - 1) + 2];
2056
+ for (let i = 0; i < nestingDepth - 1; i++) parentPrefix += `subworkflows/${currentPath[5 * i]}/step_${currentPath[5 * i + 2]?.toString()}/`;
2057
+ const lastJob = String(currentPath[5 * (nestingDepth - 1)] ?? "");
2058
+ const lastStep = currentPath[5 * (nestingDepth - 1) + 2] ?? "";
1963
2059
  generalLogPath = buildSubworkflowLogPath(parentPrefix, lastJob, lastStep);
1964
2060
  }
2061
+ const missingLogText = sublogIsSubworkflow ? "Subworkflows generally have no logs/scripts at this level.\nClick on the \"Open in new graph\" icon on the subworkflow step to open the subworkflow graph." : sublogsError ? dagStrings.logLoadFailed : (sublogOpen === generalLogPath ? dagStrings.statusReason(run.statusReason) : void 0) ?? dagStrings.noLogFound;
2062
+ const currentSublog = sublogOpen === "" ? "" : preview ? dumpYaml(jobs[jobAndStep[0]]?.steps[Number(jobAndStep[1])]) : sublogsError || !sublogs ? missingLogText : sublogs;
1965
2063
  const expansionKeys = useMemo(() => collectExpansionKeys(jobs, pathAppend), [jobs, pathAppend]);
1966
2064
  const hasExpandable = expansionKeys.subworkflowKeys.length > 0 || expansionKeys.matrixKeys.length > 0;
1967
2065
  const allExpanded = hasExpandable && expansionKeys.subworkflowKeys.every((k) => expandedSubworkflows.has(k)) && expansionKeys.matrixKeys.every((k) => matrixOpen.has(k));
@@ -1982,20 +2080,20 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
1982
2080
  };
1983
2081
  const nextStep = useCallback((backward) => {
1984
2082
  const sublogSplit = sublogOpen.split("/");
1985
- let job = sublogSplit[sublogSplit.length - 3];
2083
+ let job = sublogSplit[sublogSplit.length - 3] ?? "";
1986
2084
  let step = parseInt(sublogSplit[sublogSplit.length - 2].slice(5), 10);
1987
2085
  const jobArr = Object.keys(jobs);
1988
2086
  let idx = jobArr.indexOf(job);
1989
2087
  if (backward) if (0 < step) step -= 1;
1990
2088
  else {
1991
2089
  if (idx === 0) idx = jobArr.length;
1992
- job = jobArr[idx - 1];
1993
- step = jobs[job].steps.length - 1;
2090
+ job = jobArr[idx - 1] ?? "";
2091
+ step = (jobs[job]?.steps.length ?? 0) - 1;
1994
2092
  }
1995
- else if (jobs[job].steps.length - 1 > step) step += 1;
2093
+ else if ((jobs[job]?.steps.length ?? 0) - 1 > step) step += 1;
1996
2094
  else {
1997
2095
  if (idx === jobArr.length - 1) idx = -1;
1998
- job = jobArr[idx + 1];
2096
+ job = jobArr[idx + 1] ?? "";
1999
2097
  step = 0;
2000
2098
  }
2001
2099
  setSublogOpen(sublogSplit.slice(0, -3).join("/") + `/${job}/step_${step}/` + sublogSplit.slice(-1)[0]);
@@ -2005,14 +2103,10 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2005
2103
  else if (e.key === "ArrowLeft") nextStep(true);
2006
2104
  }, [nextStep]);
2007
2105
  useEffect(() => {
2008
- if ([`${pathAppend}/logs.out`, ""].includes(sublogOpen)) return;
2106
+ if (!sublogIsStep) return;
2009
2107
  window.addEventListener("keydown", handleKeyDown);
2010
2108
  return () => window.removeEventListener("keydown", handleKeyDown);
2011
- }, [
2012
- handleKeyDown,
2013
- sublogOpen,
2014
- pathAppend
2015
- ]);
2109
+ }, [handleKeyDown, sublogIsStep]);
2016
2110
  const summarySidebar = useMemo(() => /* @__PURE__ */ jsx(SidebarJobs, {
2017
2111
  jobs,
2018
2112
  pathPrefix: pathAppend,
@@ -2040,8 +2134,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2040
2134
  if (!Array.isArray(dependencyCols) || dependencyCols.length === 0 || !Array.isArray(dependencyCols[0]) || dependencyCols[0].length === 0 || !Array.isArray(dependencyCols[0][0]) || dependencyCols[0][0].length === 0) return;
2041
2135
  const startDt = run.createdAt ? DateTime.fromISO(run.createdAt) : now;
2042
2136
  const runtime = toAbsHumanDuration(startDt, run.completedAt ? DateTime.fromISO(run.completedAt) : now);
2043
- const notGeneralLogs = `${pathAppend}/logs.out` !== sublogOpen;
2044
- const additionalBottomLeftBarComponents = notGeneralLogs ? /* @__PURE__ */ jsxs(Fragment, { children: [
2137
+ const additionalBottomLeftBarComponents = sublogIsStep ? /* @__PURE__ */ jsxs(Fragment, { children: [
2045
2138
  /* @__PURE__ */ jsxs("button", {
2046
2139
  type: "button",
2047
2140
  className: "btn btn-neutral",
@@ -2059,7 +2152,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2059
2152
  children: logName
2060
2153
  })
2061
2154
  ] }) : void 0;
2062
- const additionalBottomRightBarComponents = (!preview || sublogIsSubworkflow) && sublogOpen && notGeneralLogs ? /* @__PURE__ */ jsx("button", {
2155
+ const additionalBottomRightBarComponents = (!preview || sublogIsSubworkflow) && sublogOpen && sublogIsStep ? /* @__PURE__ */ jsx("button", {
2063
2156
  type: "button",
2064
2157
  className: "btn btn-neutral",
2065
2158
  onClick: () => {
@@ -2079,8 +2172,8 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2079
2172
  slots.runSessions && /* @__PURE__ */ jsx("div", {
2080
2173
  className: "mb-4 overflow-y-auto",
2081
2174
  children: slots.runSessions({
2082
- runId: run.id,
2083
- runNumber: run.number,
2175
+ runId: run.id ?? "",
2176
+ runNumber,
2084
2177
  runActive: isRunActive(run.status)
2085
2178
  })
2086
2179
  }),
@@ -2136,7 +2229,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2136
2229
  children: /* @__PURE__ */ jsxs(TooltipInfo, {
2137
2230
  className: "flex items-center gap-x-1",
2138
2231
  text: dagStrings.statusReason(run.statusReason) ?? "",
2139
- children: [/* @__PURE__ */ jsx(Indicator, { status: run.status }), dagStrings.statusLabel(run.status)]
2232
+ children: [/* @__PURE__ */ jsx(Indicator, { status: runStatus }), dagStrings.statusLabel(runStatus)]
2140
2233
  })
2141
2234
  })]
2142
2235
  }),
@@ -2173,7 +2266,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2173
2266
  })
2174
2267
  ]
2175
2268
  }),
2176
- !preview && /* @__PURE__ */ jsx(AnnotationBanner, { executedJobs: run.executedJobs }),
2269
+ !preview && /* @__PURE__ */ jsx(AnnotationBanner, { executedJobs: jobs }),
2177
2270
  viewMode === "tree" && !preview ? /* @__PURE__ */ jsx("div", {
2178
2271
  className: "panel w-full border",
2179
2272
  children: /* @__PURE__ */ jsx(TreeView, {
@@ -2216,10 +2309,11 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2216
2309
  children: allExpanded ? dagStrings.collapseAll : dagStrings.expandAll
2217
2310
  }),
2218
2311
  /* @__PURE__ */ jsx("button", {
2312
+ type: "button",
2219
2313
  className: "btn btn-info w-21 cursor-pointer h-6",
2220
2314
  "aria-label": "Reset view",
2221
2315
  onClick: () => {
2222
- if (executedJobsPath[executedJobsPathIdx].length > 0) {
2316
+ if ((executedJobsPath[executedJobsPathIdx]?.length ?? 0) > 0) {
2223
2317
  resetGraph();
2224
2318
  setExecutedJobsPath([[], ...executedJobsPath.slice(executedJobsPathIdx, 10)]);
2225
2319
  setExecutedJobsPathIdx(0);
@@ -2230,6 +2324,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2230
2324
  executedJobsPath.length > 1 && /* @__PURE__ */ jsxs("div", {
2231
2325
  className: "flex",
2232
2326
  children: [/* @__PURE__ */ jsx("button", {
2327
+ type: "button",
2233
2328
  onClick: () => {
2234
2329
  if (executedJobsPath.length > executedJobsPathIdx + 1) {
2235
2330
  resetGraph();
@@ -2239,6 +2334,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2239
2334
  className: "btn btn-info mr-1 cursor-pointer w-10 h-6",
2240
2335
  children: "<"
2241
2336
  }), /* @__PURE__ */ jsx("button", {
2337
+ type: "button",
2242
2338
  onClick: () => {
2243
2339
  if (executedJobsPathIdx - 1 >= 0) {
2244
2340
  resetGraph();
@@ -2285,7 +2381,7 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2285
2381
  children: /* @__PURE__ */ jsx("div", {
2286
2382
  className: "flex w-full",
2287
2383
  children: sublogsLoading && (!sublogs || !runNumber) ? /* @__PURE__ */ jsx(Loader, {
2288
- text: "Loading logs",
2384
+ text: dagStrings.loadingLogs,
2289
2385
  size: 70,
2290
2386
  textSize: 18
2291
2387
  }) : preview ? /* @__PURE__ */ jsxs("div", { children: [slots.yamlViewer?.({
@@ -2324,9 +2420,10 @@ function DependencyGraph({ run, preview = false, initialScale = 1, removeBorder,
2324
2420
  });
2325
2421
  }
2326
2422
  function DependencyGraphPreview(inputs) {
2423
+ const ymlJobs = inputs.yml?.["jobs"];
2327
2424
  return /* @__PURE__ */ jsx(DependencyGraph, {
2328
2425
  run: {
2329
- executedJobs: addCleanupSteps({ jobs: inputs.yml?.jobs || {} }),
2426
+ executedJobs: addCleanupSteps({ jobs: isJobRecord(ymlJobs) ? ymlJobs : {} }),
2330
2427
  number: 0,
2331
2428
  workflowName: ""
2332
2429
  },