@parallelworks/ui 0.20.0 → 0.21.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 (154) hide show
  1. package/dist/ai/adapter/openai/createOpenAIStream.js +3 -2
  2. package/dist/ai/agent/diff.d.ts +0 -1
  3. package/dist/ai/agent/diff.js +3 -2
  4. package/dist/ai/agent/toolfmt.js +2 -3
  5. package/dist/ai/ai.css +5 -0
  6. package/dist/ai/components/AllocationSelector.js +3 -2
  7. package/dist/ai/components/AttachmentManager.js +1 -1
  8. package/dist/ai/components/BranchNavigator.js +4 -2
  9. package/dist/ai/components/ChatInput.d.ts +2 -5
  10. package/dist/ai/components/ChatInput.js +10 -7
  11. package/dist/ai/components/ChatMessage.js +7 -7
  12. package/dist/ai/components/ChatSidebar.d.ts +0 -1
  13. package/dist/ai/components/ChatSidebar.js +4 -6
  14. package/dist/ai/components/ChatThread.js +5 -8
  15. package/dist/ai/components/ModelSelector.js +3 -2
  16. package/dist/ai/components/ShareDialog.js +6 -3
  17. package/dist/ai/components/SlashMenu.d.ts +0 -1
  18. package/dist/ai/components/agent/ActivityLine.d.ts +0 -1
  19. package/dist/ai/components/agent/ActivityLine.js +2 -2
  20. package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -26
  21. package/dist/ai/components/agent/AgentMessageParts.js +4 -5
  22. package/dist/ai/core/ChatProvider.d.ts +2 -6
  23. package/dist/ai/core/ChatProvider.js +42 -67
  24. package/dist/ai/core/KeyboardShortcutsProvider.js +1 -1
  25. package/dist/ai/core/chatReducer.d.ts +0 -6
  26. package/dist/ai/core/chatReducer.js +1 -15
  27. package/dist/ai/core/useKeyboardShortcuts.d.ts +3 -1
  28. package/dist/ai/core/useKeyboardShortcuts.js +9 -11
  29. package/dist/ai/core/useSSEStream.d.ts +0 -1
  30. package/dist/ai/core/useSSEStream.js +5 -17
  31. package/dist/ai/strings.d.ts +0 -1
  32. package/dist/ai/strings.js +0 -1
  33. package/dist/ai/types.d.ts +1 -1
  34. package/dist/ai/ui/Avatar.d.ts +1 -2
  35. package/dist/ai/ui/Avatar.js +7 -18
  36. package/dist/ai/ui/Markdown.d.ts +1 -3
  37. package/dist/ai/ui/Markdown.js +1 -2
  38. package/dist/ai/ui/sidebar.d.ts +0 -1
  39. package/dist/ai/ui/sidebar.js +3 -1
  40. package/dist/ai/utils.js +1 -1
  41. package/dist/components/Avatar.js +4 -2
  42. package/dist/components/BareModal.js +1 -5
  43. package/dist/components/Breadcrumbs.js +21 -14
  44. package/dist/components/CollapsiblePanel.d.ts +1 -3
  45. package/dist/components/CollapsiblePanel.js +5 -5
  46. package/dist/components/Dropdown.d.ts +0 -4
  47. package/dist/components/Dropdown.js +5 -6
  48. package/dist/components/Listbox.js +14 -18
  49. package/dist/components/MultiSelect.js +5 -8
  50. package/dist/components/Pagination.js +1 -1
  51. package/dist/components/Provider.d.ts +0 -8
  52. package/dist/components/SwitchToggle.js +3 -5
  53. package/dist/components/Table.d.ts +1 -3
  54. package/dist/components/dropdownUtils.js +5 -6
  55. package/dist/components/relativeTime.d.ts +0 -7
  56. package/dist/components/relativeTime.js +1 -1
  57. package/dist/duration.js +1 -1
  58. package/dist/editor/Editor.d.ts +1 -1
  59. package/dist/editor/Editor.js +1 -1
  60. package/dist/editor/EditorField.d.ts +0 -1
  61. package/dist/editor/EditorField.js +8 -8
  62. package/dist/editor/FormCodePanel.js +14 -20
  63. package/dist/editor/yamlLanguage.js +2 -2
  64. package/dist/file-explorer/FileExplorer.js +56 -59
  65. package/dist/file-explorer/FileExplorerContext.d.ts +1 -2
  66. package/dist/file-explorer/FileExplorerContext.js +92 -125
  67. package/dist/file-explorer/FilePreview/Inline.d.ts +1 -2
  68. package/dist/file-explorer/FilePreview/Inline.js +2 -2
  69. package/dist/file-explorer/FilePreview/Viewer.js +0 -4
  70. package/dist/file-explorer/FilePreview/index.d.ts +1 -2
  71. package/dist/file-explorer/FilePreview/index.js +3 -3
  72. package/dist/file-explorer/FilePreview/parse.d.ts +2 -1
  73. package/dist/file-explorer/FilePreview/useObjectBlobUrl.d.ts +5 -4
  74. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +23 -36
  75. package/dist/file-explorer/FilePreview/useObjectText.d.ts +1 -2
  76. package/dist/file-explorer/FilePreview/useObjectText.js +5 -7
  77. package/dist/file-explorer/FileRow.js +1 -1
  78. package/dist/file-explorer/FileRowsWindow.d.ts +5 -12
  79. package/dist/file-explorer/FileTable.d.ts +2 -6
  80. package/dist/file-explorer/LoadMoreRow.d.ts +8 -0
  81. package/dist/file-explorer/TreeView.d.ts +0 -1
  82. package/dist/file-explorer/TreeView.js +6 -6
  83. package/dist/file-explorer/helpers.js +2 -4
  84. package/dist/file-explorer/lib/fileExplorer.d.ts +0 -2
  85. package/dist/file-explorer/lib/types.d.ts +0 -1
  86. package/dist/file-explorer/lib/utils.js +15 -20
  87. package/dist/file-explorer/toasts.js +36 -34
  88. package/dist/form/Fields/BooleanField.js +5 -4
  89. package/dist/form/Fields/CheckboxGroupField.js +5 -4
  90. package/dist/form/Fields/ColorField.js +5 -4
  91. package/dist/form/Fields/DropdownField.d.ts +0 -1
  92. package/dist/form/Fields/DropdownField.js +10 -13
  93. package/dist/form/Fields/DurationField.js +2 -2
  94. package/dist/form/Fields/LabelField.js +5 -5
  95. package/dist/form/Fields/MultiDropdownField.d.ts +1 -1
  96. package/dist/form/Fields/MultiDropdownField.js +8 -11
  97. package/dist/form/Fields/NumberField.js +6 -5
  98. package/dist/form/Fields/ObjectField.d.ts +1 -2
  99. package/dist/form/Fields/ObjectField.js +22 -21
  100. package/dist/form/Fields/RadioField.js +4 -3
  101. package/dist/form/Fields/RangeField.js +7 -6
  102. package/dist/form/Fields/StringField.js +2 -1
  103. package/dist/form/Fields/TextAreaField.js +2 -1
  104. package/dist/form/Form.d.ts +7 -8
  105. package/dist/form/Form.js +52 -78
  106. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +0 -2
  107. package/dist/form/SpecialTypes/DropdownWrapper.js +1 -3
  108. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +1 -3
  109. package/dist/form/SpecialTypes/FormikCustomDropdown.js +4 -7
  110. package/dist/form/SpecialTypes/FormikCustomInput.js +13 -11
  111. package/dist/form/Wizard/WizardContainer.js +8 -25
  112. package/dist/form/Wizard/WizardNavigation.d.ts +1 -1
  113. package/dist/form/Wizard/WizardNavigation.js +5 -6
  114. package/dist/form/Wizard/WizardStepContent.d.ts +1 -1
  115. package/dist/form/Wizard/WizardStepContent.js +2 -2
  116. package/dist/form/Wizard/WizardStepIndicator.js +7 -17
  117. package/dist/form/Wizard/types.d.ts +4 -46
  118. package/dist/form/Wizard/useWizardState.d.ts +2 -10
  119. package/dist/form/Wizard/useWizardState.js +42 -92
  120. package/dist/form/Wizard/utils.js +6 -11
  121. package/dist/form/lib.js +28 -54
  122. package/dist/form/types/fieldTypes.d.ts +4 -5
  123. package/dist/form/utils/dependentOptions.d.ts +6 -0
  124. package/dist/form/utils/dependentOptions.js +21 -0
  125. package/dist/form/utils/getParentValue.d.ts +2 -1
  126. package/dist/form/utils/getParentValue.js +10 -15
  127. package/dist/form/utils/getValueUsingPath.js +1 -1
  128. package/dist/graph/AnnotationBanner.d.ts +1 -1
  129. package/dist/graph/AnnotationBanner.js +1 -1
  130. package/dist/graph/Collapse.d.ts +3 -3
  131. package/dist/graph/Collapse.js +3 -8
  132. package/dist/graph/DependencyGraph.d.ts +5 -4
  133. package/dist/graph/DependencyGraph.js +172 -222
  134. package/dist/graph/JobSummary.d.ts +10 -8
  135. package/dist/graph/JobSummary.js +13 -13
  136. package/dist/graph/MatrixGroup.d.ts +5 -17
  137. package/dist/graph/MatrixGroup.js +11 -22
  138. package/dist/graph/TreeView.d.ts +1 -3
  139. package/dist/graph/TreeView.js +104 -491
  140. package/dist/graph/types.d.ts +3 -2
  141. package/dist/graph/util.d.ts +5 -0
  142. package/dist/graph/util.js +18 -0
  143. package/dist/icons.d.ts +2 -2
  144. package/dist/icons.js +2 -2
  145. package/dist/index.d.ts +1 -1
  146. package/dist/list/ListViewControls.js +17 -35
  147. package/dist/list/RowContextMenu.js +7 -7
  148. package/dist/logviewer/LogViewer.js +7 -9
  149. package/dist/logviewer/segments.js +2 -2
  150. package/dist/styles/theme.css +1 -0
  151. package/dist/styles.css +1 -1
  152. package/dist/theme/index.js +5 -5
  153. package/package.json +28 -30
  154. package/dist/ai/ui/buttons.d.ts +0 -1
@@ -3,31 +3,29 @@ import { ChevronRightIcon, LoaderIcon } from "../icons.js";
3
3
  import { Indicator } from "../components/Indicator.js";
4
4
  import { LogViewer } from "../logviewer/LogViewer.js";
5
5
  import { toAbsHumanDuration } from "../duration.js";
6
+ import { Collapse } from "./Collapse.js";
7
+ import { jobLabel, toggled } from "./util.js";
6
8
  import cx from "classnames";
7
9
  import { useCallback, useId, useMemo, useState } from "react";
8
10
  import { jsx, jsxs } from "react/jsx-runtime";
9
11
  import { DateTime } from "luxon";
10
12
  import { AnimatePresence, motion } from "framer-motion";
11
13
  //#region src/graph/TreeView.tsx
12
- function getStepDuration(step) {
13
- if (!step.startedAt) return "";
14
- const start = DateTime.fromISO(step.startedAt);
15
- const end = step.completedAt ? DateTime.fromISO(step.completedAt) : DateTime.now();
16
- return toAbsHumanDuration(start, end);
14
+ function elapsed({ startedAt, completedAt }) {
15
+ if (!startedAt) return "";
16
+ const end = completedAt ? DateTime.fromISO(completedAt) : DateTime.now();
17
+ return toAbsHumanDuration(DateTime.fromISO(startedAt), end);
17
18
  }
18
- function getJobDuration(job) {
19
- if (!job.startedAt) return "";
20
- const start = DateTime.fromISO(job.startedAt);
21
- const end = job.completedAt ? DateTime.fromISO(job.completedAt) : DateTime.now();
22
- return toAbsHumanDuration(start, end);
23
- }
24
- function formatJobLabel(jobName, job) {
25
- const matrix = job?._matrix;
26
- if (matrix?.originaljob) return `${matrix.originaljob[0].toUpperCase() + matrix.originaljob.slice(1)} (${(matrix.index ?? 0) + 1}/${matrix.totalingroup ?? "?"})`;
27
- return (jobName[0].toUpperCase() + jobName.slice(1)).replace(/_/g, " ");
19
+ function Chevron({ open, hidden = false }) {
20
+ return /* @__PURE__ */ jsx(motion.div, {
21
+ animate: { rotate: open ? 90 : 0 },
22
+ transition: { duration: .15 },
23
+ className: cx("flex-shrink-0", hidden ? "invisible" : "text-[var(--theme-muted-text-color)]"),
24
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", open && "text-[var(--theme-element)]") })
25
+ });
28
26
  }
29
- function InlineLogViewer({ slug, jobName, stepIndex }) {
30
- const logPath = useWorkflowEngine().stepLogPath("", jobName, stepIndex);
27
+ function StepLogs({ slug, prefix, jobName, stepIndex }) {
28
+ const logPath = useWorkflowEngine().stepLogPath(prefix, jobName, stepIndex);
31
29
  const { data: logs, isLoading, error } = useRunFile(slug, logPath, { refreshInterval: 2e3 });
32
30
  if (isLoading && !logs) return /* @__PURE__ */ jsxs("div", {
33
31
  className: "flex items-center gap-2 px-5 py-4 text-sm text-[var(--theme-muted-text-color)]",
@@ -38,7 +36,7 @@ function InlineLogViewer({ slug, jobName, stepIndex }) {
38
36
  children: "No logs available"
39
37
  });
40
38
  return /* @__PURE__ */ jsx("div", {
41
- className: "border-t border-[var(--theme-border)] ",
39
+ className: "border-t border-[var(--theme-border)]",
42
40
  children: /* @__PURE__ */ jsx(LogViewer, {
43
41
  log: logs,
44
42
  width: "100%",
@@ -49,9 +47,10 @@ function InlineLogViewer({ slug, jobName, stepIndex }) {
49
47
  })
50
48
  });
51
49
  }
52
- function JobRow({ jobName, job, isExpanded, onToggle, dependencies, workflowName, runNumber, slug, expandedJobs, onJobToggle, expandedSteps, onStepToggle }) {
53
- const hasSteps = job.steps && job.steps.length > 0;
54
- const duration = getJobDuration(job);
50
+ function JobRow({ jobName, job, isExpanded, onToggle, prefix, state }) {
51
+ const hasSteps = !!job.steps?.length;
52
+ const dependencies = Array.isArray(job.needs) ? job.needs : [];
53
+ const duration = elapsed(job);
55
54
  const panelId = useId();
56
55
  return /* @__PURE__ */ jsxs("div", {
57
56
  className: "border-b border-[var(--theme-border)] last:border-b-0",
@@ -62,26 +61,24 @@ function JobRow({ jobName, job, isExpanded, onToggle, dependencies, workflowName
62
61
  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)]"),
63
62
  onClick: onToggle,
64
63
  children: [
65
- /* @__PURE__ */ jsx(motion.div, {
66
- animate: { rotate: isExpanded ? 90 : 0 },
67
- transition: { duration: .15 },
68
- className: cx("flex-shrink-0", hasSteps ? "text-[var(--theme-muted-text-color)]" : "invisible"),
69
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
64
+ /* @__PURE__ */ jsx(Chevron, {
65
+ open: isExpanded,
66
+ hidden: !hasSteps
70
67
  }),
71
68
  /* @__PURE__ */ jsx(Indicator, { status: job.status }),
72
69
  /* @__PURE__ */ jsxs("div", {
73
70
  className: "flex flex-col gap-1 min-w-0 flex-1",
74
71
  children: [/* @__PURE__ */ jsx("span", {
75
72
  className: "font-mono text-sm text-[var(--theme-app)] truncate",
76
- children: formatJobLabel(jobName, job)
77
- }), dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
73
+ children: jobLabel(jobName, job)
74
+ }), dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
78
75
  className: "flex items-center gap-1.5",
79
76
  children: [/* @__PURE__ */ jsx("span", {
80
77
  className: "text-[10px] text-[var(--theme-muted-text-color)]",
81
78
  children: "depends on:"
82
79
  }), dependencies.map((dep) => /* @__PURE__ */ jsx("span", {
83
80
  className: "text-[10px] px-1.5 py-0.5 rounded bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]",
84
- children: formatJobLabel(dep)
81
+ children: jobLabel(dep)
85
82
  }, dep))]
86
83
  })]
87
84
  }),
@@ -91,75 +88,37 @@ function JobRow({ jobName, job, isExpanded, onToggle, dependencies, workflowName
91
88
  children: duration
92
89
  })
93
90
  ]
94
- }), /* @__PURE__ */ jsx(AnimatePresence, {
95
- initial: false,
96
- children: isExpanded && hasSteps && /* @__PURE__ */ jsx(motion.div, {
97
- initial: {
98
- height: 0,
99
- opacity: 0
100
- },
101
- animate: {
102
- height: "auto",
103
- opacity: 1
104
- },
105
- exit: {
106
- height: 0,
107
- opacity: 0
108
- },
109
- transition: {
110
- duration: .2,
111
- ease: [
112
- .25,
113
- .1,
114
- .25,
115
- 1
116
- ]
117
- },
118
- className: "overflow-hidden",
119
- id: panelId,
120
- children: /* @__PURE__ */ jsx("div", {
121
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
122
- children: job.steps.map((step, index) => {
123
- const stepKey = `${jobName}:${index}`;
124
- const isStepExpanded = expandedSteps.has(stepKey);
125
- return /* @__PURE__ */ jsx(StepRow, {
126
- step,
127
- index,
128
- isExpanded: isStepExpanded,
129
- onToggle: () => onStepToggle(stepKey),
130
- workflowName,
131
- runNumber,
132
- slug,
133
- jobName,
134
- expandedJobs,
135
- onJobToggle,
136
- expandedSteps,
137
- onStepToggle
138
- }, stepKey);
139
- })
91
+ }), /* @__PURE__ */ jsx(Collapse, {
92
+ expanded: isExpanded && hasSteps,
93
+ id: panelId,
94
+ children: /* @__PURE__ */ jsx("div", {
95
+ className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
96
+ children: job.steps?.map((step, index) => {
97
+ const stepKey = `${prefix}${jobName}:${index}`;
98
+ return /* @__PURE__ */ jsx(StepRow, {
99
+ step,
100
+ index,
101
+ isExpanded: state.expandedSteps.has(stepKey),
102
+ onToggle: () => state.onStepToggle(stepKey),
103
+ prefix,
104
+ jobName,
105
+ state
106
+ }, stepKey);
140
107
  })
141
108
  })
142
109
  })]
143
110
  });
144
111
  }
145
- function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, slug, jobName, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth = 0 }) {
112
+ function StepRow({ step, index, isExpanded, onToggle, prefix, jobName, state }) {
146
113
  const engine = useWorkflowEngine();
147
114
  const slots = useSlots();
148
- const duration = getStepDuration(step);
115
+ const duration = elapsed(step);
149
116
  const label = engine.stepLabel(step, index);
150
- const isSubworkflow = !!step.subworkflow;
117
+ const subworkflow = step.subworkflow;
151
118
  const panelId = useId();
152
- const subworkflowJobs = step.subworkflow?.jobs ?? null;
153
- const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name]?.if !== false) : [];
154
- const subworkflowDependenciesMap = useMemo(() => {
155
- if (!subworkflowJobs) return {};
156
- const map = {};
157
- for (const subJobName of subworkflowJobNames) {
158
- const job = subworkflowJobs[subJobName];
159
- if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
160
- }
161
- return map;
162
- }, [subworkflowJobs, subworkflowJobNames]);
119
+ const subworkflowJobs = subworkflow?.jobs ?? {};
120
+ const subworkflowJobNames = Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name]?.if !== false);
121
+ const subworkflowPrefix = `${prefix}subworkflows/${jobName}/step_${index}/`;
163
122
  return /* @__PURE__ */ jsxs("div", { children: [
164
123
  /* @__PURE__ */ jsxs("button", {
165
124
  type: "button",
@@ -168,21 +127,16 @@ function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, s
168
127
  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)]"),
169
128
  onClick: onToggle,
170
129
  children: [
171
- /* @__PURE__ */ jsx(motion.div, {
172
- animate: { rotate: isExpanded ? 90 : 0 },
173
- transition: { duration: .15 },
174
- className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
175
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
176
- }),
130
+ /* @__PURE__ */ jsx(Chevron, { open: isExpanded }),
177
131
  /* @__PURE__ */ jsx(Indicator, { status: step.status }),
178
- isSubworkflow && slots.workflowIcon?.({
179
- image: step.subworkflow?.image,
180
- type: step.subworkflow?.type,
132
+ subworkflow && slots.workflowIcon?.({
133
+ image: subworkflow.image,
134
+ type: subworkflow.type,
181
135
  className: "h-4 w-4 flex-shrink-0"
182
136
  }),
183
137
  /* @__PURE__ */ jsxs("span", {
184
138
  className: "font-mono text-sm text-[var(--theme-app)] truncate flex-1",
185
- children: [label, isSubworkflow && /* @__PURE__ */ jsx("span", {
139
+ children: [label, subworkflow && /* @__PURE__ */ jsx("span", {
186
140
  className: "ml-2 text-xs text-[var(--theme-muted-text-color)]",
187
141
  children: "(subworkflow)"
188
142
  })]
@@ -193,351 +147,39 @@ function StepRow({ step, index, isExpanded, onToggle, workflowName, runNumber, s
193
147
  })
194
148
  ]
195
149
  }),
196
- /* @__PURE__ */ jsx(AnimatePresence, {
197
- initial: false,
198
- children: isExpanded && !isSubworkflow && /* @__PURE__ */ jsx(motion.div, {
199
- initial: {
200
- height: 0,
201
- opacity: 0
202
- },
203
- animate: {
204
- height: "auto",
205
- opacity: 1
206
- },
207
- exit: {
208
- height: 0,
209
- opacity: 0
210
- },
211
- transition: {
212
- duration: .2,
213
- ease: [
214
- .25,
215
- .1,
216
- .25,
217
- 1
218
- ]
219
- },
220
- className: "overflow-hidden",
221
- id: panelId,
222
- children: /* @__PURE__ */ jsx(InlineLogViewer, {
223
- slug,
224
- jobName,
225
- stepIndex: index
226
- })
150
+ /* @__PURE__ */ jsx(Collapse, {
151
+ expanded: isExpanded && !subworkflow,
152
+ id: panelId,
153
+ children: /* @__PURE__ */ jsx(StepLogs, {
154
+ slug: state.slug,
155
+ prefix,
156
+ jobName,
157
+ stepIndex: index
227
158
  })
228
159
  }),
229
- /* @__PURE__ */ jsx(AnimatePresence, {
230
- initial: false,
231
- children: isExpanded && isSubworkflow && subworkflowJobNames.length > 0 && /* @__PURE__ */ jsx(motion.div, {
232
- initial: {
233
- height: 0,
234
- opacity: 0
235
- },
236
- animate: {
237
- height: "auto",
238
- opacity: 1
239
- },
240
- exit: {
241
- height: 0,
242
- opacity: 0
243
- },
244
- transition: {
245
- duration: .2,
246
- ease: [
247
- .25,
248
- .1,
249
- .25,
250
- 1
251
- ]
252
- },
253
- className: "overflow-hidden",
254
- id: panelId,
255
- children: /* @__PURE__ */ jsx("div", {
256
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
257
- children: subworkflowJobNames.map((subJobName) => {
258
- const subJob = subworkflowJobs?.[subJobName];
259
- if (!subJob) return null;
260
- const subJobKey = `${jobName}:${index}:${subJobName}`;
261
- const isSubJobExpanded = expandedJobs.has(subJobKey);
262
- return /* @__PURE__ */ jsx(SubworkflowJobRow, {
263
- jobName: subJobName,
264
- job: subJob,
265
- isExpanded: isSubJobExpanded,
266
- onToggle: () => onJobToggle(subJobKey),
267
- dependencies: subworkflowDependenciesMap[subJobName],
268
- slug,
269
- parentJobName: jobName,
270
- parentStepIndex: index,
271
- expandedJobs,
272
- onJobToggle,
273
- expandedSteps,
274
- onStepToggle,
275
- depth: depth + 1
276
- }, subJobName);
277
- })
160
+ /* @__PURE__ */ jsx(Collapse, {
161
+ expanded: isExpanded && !!subworkflow && subworkflowJobNames.length > 0,
162
+ id: panelId,
163
+ children: /* @__PURE__ */ jsx("div", {
164
+ className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
165
+ children: subworkflowJobNames.map((subJobName) => {
166
+ const subJob = subworkflowJobs[subJobName];
167
+ if (!subJob) return null;
168
+ const subJobKey = `${prefix}${jobName}:${index}:${subJobName}`;
169
+ return /* @__PURE__ */ jsx(JobRow, {
170
+ jobName: subJobName,
171
+ job: subJob,
172
+ isExpanded: state.expandedJobs.has(subJobKey),
173
+ onToggle: () => state.onJobToggle(subJobKey),
174
+ prefix: subworkflowPrefix,
175
+ state
176
+ }, subJobName);
278
177
  })
279
178
  })
280
179
  })
281
180
  ] });
282
181
  }
283
- function SubworkflowJobRow({ jobName, job, isExpanded, onToggle, dependencies, slug, parentJobName, parentStepIndex, subworkflowPrefix, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth }) {
284
- const hasSteps = job.steps && job.steps.length > 0;
285
- const duration = getJobDuration(job);
286
- const nestedSubworkflowPrefix = `${subworkflowPrefix || ""}subworkflows/${parentJobName}/step_${parentStepIndex}/`;
287
- const panelId = useId();
288
- return /* @__PURE__ */ jsxs("div", {
289
- className: "border-b border-[var(--theme-border)] last:border-b-0",
290
- children: [/* @__PURE__ */ jsxs("button", {
291
- type: "button",
292
- "aria-expanded": isExpanded,
293
- "aria-controls": hasSteps ? panelId : void 0,
294
- 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)]"),
295
- onClick: onToggle,
296
- children: [
297
- /* @__PURE__ */ jsx(motion.div, {
298
- animate: { rotate: isExpanded ? 90 : 0 },
299
- transition: { duration: .15 },
300
- className: cx("flex-shrink-0", hasSteps ? "text-[var(--theme-muted-text-color)]" : "invisible"),
301
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
302
- }),
303
- /* @__PURE__ */ jsx(Indicator, { status: job.status }),
304
- /* @__PURE__ */ jsxs("div", {
305
- className: "flex flex-col gap-1 min-w-0 flex-1",
306
- children: [/* @__PURE__ */ jsx("span", {
307
- className: "font-mono text-sm text-[var(--theme-app)] truncate",
308
- children: formatJobLabel(jobName, job)
309
- }), dependencies && dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
310
- className: "flex items-center gap-1.5",
311
- children: [/* @__PURE__ */ jsx("span", {
312
- className: "text-[10px] text-[var(--theme-muted-text-color)]",
313
- children: "depends on:"
314
- }), dependencies.map((dep) => /* @__PURE__ */ jsx("span", {
315
- className: "text-[10px] px-1.5 py-0.5 rounded bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]",
316
- children: formatJobLabel(dep)
317
- }, dep))]
318
- })]
319
- }),
320
- /* @__PURE__ */ jsx("div", { className: "flex-1" }),
321
- duration && /* @__PURE__ */ jsx("span", {
322
- className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
323
- children: duration
324
- })
325
- ]
326
- }), /* @__PURE__ */ jsx(AnimatePresence, {
327
- initial: false,
328
- children: isExpanded && hasSteps && /* @__PURE__ */ jsx(motion.div, {
329
- initial: {
330
- height: 0,
331
- opacity: 0
332
- },
333
- animate: {
334
- height: "auto",
335
- opacity: 1
336
- },
337
- exit: {
338
- height: 0,
339
- opacity: 0
340
- },
341
- transition: {
342
- duration: .2,
343
- ease: [
344
- .25,
345
- .1,
346
- .25,
347
- 1
348
- ]
349
- },
350
- className: "overflow-hidden",
351
- id: panelId,
352
- children: /* @__PURE__ */ jsx("div", {
353
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
354
- children: job.steps.map((step, index) => {
355
- const stepKey = `${nestedSubworkflowPrefix}${jobName}:${index}`;
356
- const isStepExpanded = expandedSteps.has(stepKey);
357
- return /* @__PURE__ */ jsx(SubworkflowStepRow, {
358
- step,
359
- index,
360
- isExpanded: isStepExpanded,
361
- onToggle: () => onStepToggle(stepKey),
362
- slug,
363
- subworkflowPrefix: nestedSubworkflowPrefix,
364
- jobName,
365
- expandedJobs,
366
- onJobToggle,
367
- expandedSteps,
368
- onStepToggle,
369
- depth
370
- }, stepKey);
371
- })
372
- })
373
- })
374
- })]
375
- });
376
- }
377
- function SubworkflowStepRow({ step, index, isExpanded, onToggle, slug, subworkflowPrefix, jobName, expandedJobs, onJobToggle, expandedSteps, onStepToggle, depth }) {
378
- const engine = useWorkflowEngine();
379
- const slots = useSlots();
380
- const duration = getStepDuration(step);
381
- const label = engine.stepLabel(step, index);
382
- const isSubworkflow = !!step.subworkflow;
383
- const panelId = useId();
384
- const subworkflowJobs = step.subworkflow?.jobs ?? null;
385
- const subworkflowJobNames = subworkflowJobs ? Object.keys(subworkflowJobs).filter((name) => subworkflowJobs[name]?.if !== false) : [];
386
- const subworkflowDependenciesMap = useMemo(() => {
387
- if (!subworkflowJobs) return {};
388
- const map = {};
389
- for (const subJobName of subworkflowJobNames) {
390
- const job = subworkflowJobs[subJobName];
391
- if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[subJobName] = job.needs;
392
- }
393
- return map;
394
- }, [subworkflowJobs, subworkflowJobNames]);
395
- return /* @__PURE__ */ jsxs("div", { children: [
396
- /* @__PURE__ */ jsxs("button", {
397
- type: "button",
398
- "aria-expanded": isExpanded,
399
- "aria-controls": panelId,
400
- 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)]"),
401
- onClick: onToggle,
402
- children: [
403
- /* @__PURE__ */ jsx(motion.div, {
404
- animate: { rotate: isExpanded ? 90 : 0 },
405
- transition: { duration: .15 },
406
- className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
407
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isExpanded && "text-[var(--theme-element)]") })
408
- }),
409
- /* @__PURE__ */ jsx(Indicator, { status: step.status }),
410
- isSubworkflow && slots.workflowIcon?.({
411
- image: step.subworkflow?.image,
412
- type: step.subworkflow?.type,
413
- className: "h-4 w-4 flex-shrink-0"
414
- }),
415
- /* @__PURE__ */ jsxs("span", {
416
- className: "font-mono text-sm text-[var(--theme-app)] truncate flex-1",
417
- children: [label, isSubworkflow && /* @__PURE__ */ jsx("span", {
418
- className: "ml-2 text-xs text-[var(--theme-muted-text-color)]",
419
- children: "(subworkflow)"
420
- })]
421
- }),
422
- duration && /* @__PURE__ */ jsx("span", {
423
- className: "font-mono text-xs text-[var(--theme-muted-text-color)] flex-shrink-0",
424
- children: duration
425
- })
426
- ]
427
- }),
428
- /* @__PURE__ */ jsx(AnimatePresence, {
429
- initial: false,
430
- children: isExpanded && !isSubworkflow && /* @__PURE__ */ jsx(motion.div, {
431
- initial: {
432
- height: 0,
433
- opacity: 0
434
- },
435
- animate: {
436
- height: "auto",
437
- opacity: 1
438
- },
439
- exit: {
440
- height: 0,
441
- opacity: 0
442
- },
443
- transition: {
444
- duration: .2,
445
- ease: [
446
- .25,
447
- .1,
448
- .25,
449
- 1
450
- ]
451
- },
452
- className: "overflow-hidden",
453
- id: panelId,
454
- children: /* @__PURE__ */ jsx(SubworkflowInlineLogViewer, {
455
- slug,
456
- subworkflowPrefix,
457
- jobName,
458
- stepIndex: index
459
- })
460
- })
461
- }),
462
- /* @__PURE__ */ jsx(AnimatePresence, {
463
- initial: false,
464
- children: isExpanded && isSubworkflow && subworkflowJobNames.length > 0 && /* @__PURE__ */ jsx(motion.div, {
465
- initial: {
466
- height: 0,
467
- opacity: 0
468
- },
469
- animate: {
470
- height: "auto",
471
- opacity: 1
472
- },
473
- exit: {
474
- height: 0,
475
- opacity: 0
476
- },
477
- transition: {
478
- duration: .2,
479
- ease: [
480
- .25,
481
- .1,
482
- .25,
483
- 1
484
- ]
485
- },
486
- className: "overflow-hidden",
487
- id: panelId,
488
- children: /* @__PURE__ */ jsx("div", {
489
- className: "ml-[38px] border-l-2 border-[var(--theme-border)]",
490
- children: subworkflowJobNames.map((subJobName) => {
491
- const subJob = subworkflowJobs?.[subJobName];
492
- if (!subJob) return null;
493
- const subJobKey = `${subworkflowPrefix}${jobName}:${index}:${subJobName}`;
494
- const isSubJobExpanded = expandedJobs.has(subJobKey);
495
- return /* @__PURE__ */ jsx(SubworkflowJobRow, {
496
- jobName: subJobName,
497
- job: subJob,
498
- isExpanded: isSubJobExpanded,
499
- onToggle: () => onJobToggle(subJobKey),
500
- dependencies: subworkflowDependenciesMap[subJobName],
501
- slug,
502
- parentJobName: jobName,
503
- parentStepIndex: index,
504
- subworkflowPrefix,
505
- expandedJobs,
506
- onJobToggle,
507
- expandedSteps,
508
- onStepToggle,
509
- depth: depth + 1
510
- }, subJobName);
511
- })
512
- })
513
- })
514
- })
515
- ] });
516
- }
517
- function SubworkflowInlineLogViewer({ slug, subworkflowPrefix, jobName, stepIndex }) {
518
- const logPath = useWorkflowEngine().stepLogPath(subworkflowPrefix, jobName, stepIndex);
519
- const { data: logs, isLoading, error } = useRunFile(slug, logPath, { refreshInterval: 2e3 });
520
- if (isLoading && !logs) return /* @__PURE__ */ jsxs("div", {
521
- className: "flex items-center gap-2 px-5 py-4 text-sm text-[var(--theme-muted-text-color)]",
522
- children: [/* @__PURE__ */ jsx(LoaderIcon, { className: "w-4 h-4" }), /* @__PURE__ */ jsx("span", { children: "Loading logs..." })]
523
- });
524
- if (error || !logs) return /* @__PURE__ */ jsx("div", {
525
- className: "px-5 py-4 text-sm text-[var(--theme-muted-text-color)] italic",
526
- children: "No logs available"
527
- });
528
- return /* @__PURE__ */ jsx("div", {
529
- className: "border-t border-[var(--theme-border)]",
530
- children: /* @__PURE__ */ jsx(LogViewer, {
531
- log: logs,
532
- width: "100%",
533
- height: "unset",
534
- inline: true,
535
- contentClassName: "bg-[var(--theme-app-bg)]",
536
- enableWorkflowCommands: true
537
- })
538
- });
539
- }
540
- function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpandedJobs }) {
182
+ function TreeView({ jobs, slug, expandedJobs, setExpandedJobs }) {
541
183
  const engine = useWorkflowEngine();
542
184
  const visibleJobNames = useMemo(() => {
543
185
  return Object.keys(jobs).filter((jobName) => jobs[jobName]?.if !== false);
@@ -558,22 +200,13 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
558
200
  setExpandedJobs,
559
201
  jobs
560
202
  ]);
561
- const toggleSubworkflowJob = useCallback((jobKey) => {
562
- setExpandedSubworkflowJobs((prev) => {
563
- const newSet = new Set(prev);
564
- if (newSet.has(jobKey)) newSet.delete(jobKey);
565
- else newSet.add(jobKey);
566
- return newSet;
567
- });
568
- }, []);
569
- const toggleStep = useCallback((stepKey) => {
570
- setExpandedSteps((prev) => {
571
- const newSet = new Set(prev);
572
- if (newSet.has(stepKey)) newSet.delete(stepKey);
573
- else newSet.add(stepKey);
574
- return newSet;
575
- });
576
- }, []);
203
+ const rowState = {
204
+ slug,
205
+ expandedJobs: expandedSubworkflowJobs,
206
+ onJobToggle: (jobKey) => setExpandedSubworkflowJobs((prev) => toggled(prev, jobKey)),
207
+ expandedSteps,
208
+ onStepToggle: (stepKey) => setExpandedSteps((prev) => toggled(prev, stepKey))
209
+ };
577
210
  const expandAll = useCallback(() => {
578
211
  setExpandedJobs(new Set(visibleJobNames));
579
212
  }, [visibleJobNames, setExpandedJobs]);
@@ -583,14 +216,6 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
583
216
  setExpandedSubworkflowJobs(/* @__PURE__ */ new Set());
584
217
  }, [setExpandedJobs]);
585
218
  const allExpanded = expandedJobs.size === visibleJobNames.length;
586
- const dependenciesMap = useMemo(() => {
587
- const map = {};
588
- for (const jobName of visibleJobNames) {
589
- const job = jobs[jobName];
590
- if (job?.needs && Array.isArray(job.needs) && job.needs.length > 0) map[jobName] = job.needs;
591
- }
592
- return map;
593
- }, [jobs, visibleJobNames]);
594
219
  return /* @__PURE__ */ jsxs("div", {
595
220
  className: "flex flex-col",
596
221
  children: [/* @__PURE__ */ jsx("div", {
@@ -621,7 +246,9 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
621
246
  const rendered = [];
622
247
  const seenMatrixGroups = /* @__PURE__ */ new Set();
623
248
  for (const jobName of visibleJobNames) {
624
- const matrixOrigin = jobs[jobName]?._matrix?.originaljob;
249
+ const job = jobs[jobName];
250
+ if (!job) continue;
251
+ const matrixOrigin = job._matrix?.originaljob;
625
252
  if (matrixOrigin) {
626
253
  if (seenMatrixGroups.has(matrixOrigin)) continue;
627
254
  seenMatrixGroups.add(matrixOrigin);
@@ -638,12 +265,7 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
638
265
  className: "flex items-center gap-3 px-5 py-3 cursor-pointer transition-colors w-full text-left",
639
266
  onClick: () => toggleJob(`matrix:${matrixOrigin}`),
640
267
  children: [
641
- /* @__PURE__ */ jsx(motion.div, {
642
- animate: { rotate: isGroupExpanded ? 90 : 0 },
643
- transition: { duration: .15 },
644
- className: "flex-shrink-0 text-[var(--theme-muted-text-color)]",
645
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: cx("w-4 h-4", isGroupExpanded && "text-[var(--theme-element)]") })
646
- }),
268
+ /* @__PURE__ */ jsx(Chevron, { open: isGroupExpanded }),
647
269
  /* @__PURE__ */ jsx(Indicator, { status: aggStatus }),
648
270
  /* @__PURE__ */ jsxs("span", {
649
271
  className: "font-mono text-sm text-[var(--theme-app)]",
@@ -672,35 +294,26 @@ function TreeView({ jobs, workflowName, runNumber, slug, expandedJobs, setExpand
672
294
  transition: { duration: .15 },
673
295
  className: "overflow-hidden pl-4",
674
296
  id: `matrix-panel-${matrixOrigin}`,
675
- children: group.members.map((memberName) => /* @__PURE__ */ jsx(JobRow, {
676
- jobName: memberName,
677
- job: jobs[memberName],
678
- isExpanded: expandedJobs.has(memberName),
679
- onToggle: () => toggleJob(memberName),
680
- dependencies: dependenciesMap[memberName],
681
- workflowName,
682
- runNumber,
683
- slug,
684
- expandedJobs: expandedSubworkflowJobs,
685
- onJobToggle: toggleSubworkflowJob,
686
- expandedSteps,
687
- onStepToggle: toggleStep
688
- }, memberName))
297
+ children: group.members.map((memberName) => {
298
+ const member = jobs[memberName];
299
+ return member ? /* @__PURE__ */ jsx(JobRow, {
300
+ jobName: memberName,
301
+ job: member,
302
+ isExpanded: expandedJobs.has(memberName),
303
+ onToggle: () => toggleJob(memberName),
304
+ prefix: "",
305
+ state: rowState
306
+ }, memberName) : null;
307
+ })
689
308
  }) })]
690
309
  }, `matrix-${matrixOrigin}`));
691
310
  } else rendered.push(/* @__PURE__ */ jsx(JobRow, {
692
311
  jobName,
693
- job: jobs[jobName],
312
+ job,
694
313
  isExpanded: expandedJobs.has(jobName),
695
314
  onToggle: () => toggleJob(jobName),
696
- dependencies: dependenciesMap[jobName],
697
- workflowName,
698
- runNumber,
699
- slug,
700
- expandedJobs: expandedSubworkflowJobs,
701
- onJobToggle: toggleSubworkflowJob,
702
- expandedSteps,
703
- onStepToggle: toggleStep
315
+ prefix: "",
316
+ state: rowState
704
317
  }, jobName));
705
318
  }
706
319
  return rendered;