@parallelworks/ui 0.62.0 → 0.64.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.
@@ -6,8 +6,8 @@ import { Avatar } from "@base-ui/react/avatar";
6
6
  //#region src/components/Avatar.tsx
7
7
  var SIZE_CLASSES = {
8
8
  xs: "size-4 text-[9px]",
9
- sm: "size-6 text-xs",
10
- md: "size-8 text-sm",
9
+ sm: "size-5 text-[9px]",
10
+ md: "size-7 text-xs",
11
11
  lg: "size-10 text-base",
12
12
  xl: "size-12 text-lg",
13
13
  "2xl": "size-16 text-xl"
@@ -58,7 +58,7 @@ function Avatar$1({ src, name, colorKey, size = "md", status, className }) {
58
58
  })]
59
59
  }), status && /* @__PURE__ */ jsx("span", {
60
60
  "data-testid": "online-indicator",
61
- className: cx("absolute right-0 bottom-0 block rounded-full ring-2 ring-(--theme-app-bg)", STATUS_CLASSES[status], size === "xs" || size === "sm" ? "size-1.5" : "size-2.5")
61
+ className: cx("absolute right-0 bottom-0 block rounded-full ring-2 ring-(--theme-app-bg)", STATUS_CLASSES[status], size === "xs" || size === "sm" ? "size-1.5" : size === "md" ? "size-2" : "size-2.5")
62
62
  })]
63
63
  });
64
64
  }
@@ -25,6 +25,8 @@ export interface CardHeaderProps {
25
25
  title: string;
26
26
  description?: ReactNode;
27
27
  className?: string;
28
+ /** `left` starts the title on the same edge as the labels and fields under it; `center`
29
+ * suits a card whose content is centred too. */
28
30
  align?: 'center' | 'left';
29
31
  /** The title's level in the page's outline: 1 for a card that is the page, such
30
32
  * as sign-in; 2 or 3 for a card among others under the page's own title. */
@@ -19,12 +19,12 @@ function Card({ children, size = "md", elevated = false, render: renderProp, hre
19
19
  return useRender({
20
20
  render: render ?? /* @__PURE__ */ jsx("div", {}),
21
21
  props: {
22
- className: cx("block w-full rounded-2xl border border-(--theme-border) bg-(--theme-panel-bg) backdrop-blur-xl", elevated ? "shadow-(--theme-shadow-xl)" : "shadow-(--theme-shadow)", sizeClasses[size], render && "cursor-pointer text-left text-inherit no-underline transition-[background-color,box-shadow] duration-150 hover:bg-(--theme-panel-hover) hover:shadow-(--theme-shadow-lg) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--theme-link) motion-reduce:transition-none", className),
22
+ className: cx("block w-full rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) backdrop-blur-xl", elevated ? "shadow-(--theme-shadow-xl)" : "shadow-(--theme-shadow)", sizeClasses[size], render && "cursor-pointer text-left text-inherit no-underline transition-[background-color,box-shadow] duration-150 hover:bg-(--theme-panel-hover) hover:shadow-(--theme-shadow-lg) focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--theme-link) motion-reduce:transition-none", className),
23
23
  children
24
24
  }
25
25
  });
26
26
  }
27
- function CardHeader({ badge, title, description, className = "", align = "center", headingLevel = 1 }) {
27
+ function CardHeader({ badge, title, description, className = "", align = "left", headingLevel = 1 }) {
28
28
  const Heading = `h${headingLevel}`;
29
29
  return /* @__PURE__ */ jsxs("div", {
30
30
  className: cx(align === "center" ? "text-center" : "text-left", "mb-6", className),
@@ -92,7 +92,7 @@ function Drawer$1({ open, onClose, title, description, onBack, backLabel, footer
92
92
  children
93
93
  }),
94
94
  footer && /* @__PURE__ */ jsx("div", {
95
- className: cx("flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-5 py-3", "px-4"),
95
+ className: cx("flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3", "px-4"),
96
96
  children: footer
97
97
  })
98
98
  ]
@@ -42,9 +42,9 @@ var labelSizes = {
42
42
  },
43
43
  md: {
44
44
  text: "text-sm text-(--theme-app)",
45
- inlineHeight: "min-h-9",
45
+ inlineHeight: "min-h-8",
46
46
  stackGap: "gap-1.5",
47
- inlineLine: "py-2"
47
+ inlineLine: "py-1.5"
48
48
  }
49
49
  };
50
50
  var labelClassName = "flex items-center gap-1 font-medium";
@@ -295,7 +295,7 @@ function FieldValue({ size = "md", className, children, ...props }) {
295
295
  "aria-labelledby": control?.["aria-labelledby"],
296
296
  "aria-describedby": control?.["aria-describedby"],
297
297
  ...props,
298
- className: cx("flex min-w-0 items-center break-words text-(--theme-app)", size === "sm" ? "min-h-7 text-xs" : "min-h-9 text-sm", className),
298
+ className: cx("flex min-w-0 items-center break-words text-(--theme-app)", size === "sm" ? "min-h-7 text-xs" : "min-h-8 text-sm", className),
299
299
  children
300
300
  });
301
301
  }
@@ -36,7 +36,7 @@ function Modal({ open, onClose, title, description, footer, size, alert = false,
36
36
  className: cx(panel && "rounded-xl border border-(--theme-border) bg-(--theme-app-bg) text-(--theme-app) shadow-2xl", size && [sizeClasses[size], "max-w-full"], laidOut && "flex max-h-full flex-col", "outline-none", className),
37
37
  children: laidOut ? /* @__PURE__ */ jsxs(Fragment, { children: [
38
38
  header && /* @__PURE__ */ jsxs("div", {
39
- className: cx("shrink-0 px-5 pt-5", !body && "pb-5"),
39
+ className: cx("shrink-0 px-4 pt-4", !body && "pb-4"),
40
40
  children: [title !== void 0 && /* @__PURE__ */ jsx(ModalTitle, {
41
41
  className: "m-0 text-lg font-semibold",
42
42
  children: title
@@ -46,11 +46,11 @@ function Modal({ open, onClose, title, description, footer, size, alert = false,
46
46
  })]
47
47
  }),
48
48
  body && /* @__PURE__ */ jsx("div", {
49
- className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-5 py-4",
49
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-4",
50
50
  children
51
51
  }),
52
52
  footer !== void 0 && /* @__PURE__ */ jsx("div", {
53
- className: "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-5 py-3",
53
+ className: "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3",
54
54
  children: footer
55
55
  })
56
56
  ] }) : children
@@ -32,7 +32,7 @@ function OneTimeCodeInput({ length = 6, size = "md", className, "aria-label": ar
32
32
  ...ariaInvalid && { "aria-invalid": true },
33
33
  ...ariaRequired && { "aria-required": true },
34
34
  autoFocus: autoFocus && index === 0,
35
- className: cx(fieldBox({ size }), "px-0 text-center font-mono tabular-nums", size === "sm" ? "w-7" : "w-9 text-base")
35
+ className: cx(fieldBox({ size }), "px-0 text-center font-mono tabular-nums", size === "sm" ? "w-7" : "w-8 text-base")
36
36
  }, index))]
37
37
  });
38
38
  }
@@ -135,7 +135,7 @@ function HeaderLayout({ breadcrumbs, titleActions, actions, toolbar, compact = f
135
135
  return /* @__PURE__ */ jsxs("div", {
136
136
  className: cx("@container w-full shrink-0 bg-(--theme-app-bg)", className),
137
137
  children: [/* @__PURE__ */ jsxs("div", {
138
- className: cx("flex items-center justify-between gap-4 border-b border-(--theme-border) px-4", compact ? "h-8" : "h-11"),
138
+ className: cx("flex items-center justify-between gap-4 border-b border-(--theme-border) px-4", compact ? "h-8" : "h-12"),
139
139
  children: [/* @__PURE__ */ jsxs("div", {
140
140
  className: "flex min-w-0 flex-1 items-center gap-2",
141
141
  children: [
@@ -234,6 +234,8 @@ export interface UIStrings {
234
234
  stepOf: (position: number, count: number) => string;
235
235
  summaryLoadFailed: string;
236
236
  summaryTab: string;
237
+ /** Names the cards under the graph, one for each step that wrote a summary. */
238
+ stepSummaries: string;
237
239
  logsTab: string;
238
240
  scriptTab: string;
239
241
  openSubworkflow: string;
@@ -239,10 +239,11 @@ var DEFAULTS = {
239
239
  noLogFound: "No log found",
240
240
  logLoadFailed: "Log could not be loaded",
241
241
  loadingSummary: "Loading summary",
242
- hasSummary: "Has a summary",
242
+ hasSummary: "Step summary",
243
243
  stepOf: (position, count) => `Step ${position} of ${count}`,
244
244
  summaryLoadFailed: "Summary could not be loaded",
245
245
  summaryTab: "Summary",
246
+ stepSummaries: "Step summaries",
246
247
  logsTab: "Logs",
247
248
  scriptTab: "Script",
248
249
  openSubworkflow: "Open Subworkflow",
@@ -10,13 +10,13 @@ var rootClasses = {
10
10
  card: "panel mb-1 flex w-full flex-col"
11
11
  };
12
12
  var headerClasses = {
13
- plain: "mb-2 flex w-full items-center gap-2 border-b border-(--theme-border) pb-1",
14
- card: "flex w-full items-center gap-2 px-3 py-3"
13
+ plain: "mb-4 flex w-full items-center gap-2 border-b border-(--theme-border) pb-1",
14
+ card: "flex w-full items-center gap-2 p-4"
15
15
  };
16
- var cardBodyClassName = "px-3 pb-3";
16
+ var cardBodyClassName = "px-4 pb-4";
17
17
  var titleClasses = {
18
- plain: "text-lg font-semibold",
19
- card: "text-sm font-bold"
18
+ plain: "text-base font-semibold",
19
+ card: "text-base font-semibold"
20
20
  };
21
21
  var foldedTitleClasses = {
22
22
  plain: "",
@@ -8,7 +8,7 @@ import { useId } from "react";
8
8
  function SettingModeOption({ value, label, description, disabled }) {
9
9
  const id = useId();
10
10
  return /* @__PURE__ */ jsxs("label", {
11
- className: cx("flex items-start gap-3 px-5 py-4", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", "border-t border-(--theme-border) first:border-t-0"),
11
+ className: cx("flex items-start gap-3 p-4", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", "border-t border-(--theme-border) first:border-t-0"),
12
12
  children: [/* @__PURE__ */ jsx(Radio, {
13
13
  value,
14
14
  disabled,
@@ -12,11 +12,11 @@ function SettingsCard({ title, description, action, actionAlign = "beside", head
12
12
  children: /* @__PURE__ */ jsxs("section", {
13
13
  className: cx("mt-10 first:mt-0", className),
14
14
  children: [(title || action) && /* @__PURE__ */ jsxs("div", {
15
- className: "mb-3 px-0.5",
15
+ className: "mb-4 px-0.5",
16
16
  children: [/* @__PURE__ */ jsxs("div", {
17
17
  className: cx("flex items-center gap-1.5", actionAlign === "end" && "justify-between"),
18
18
  children: [title && /* @__PURE__ */ jsx(Heading, {
19
- className: "text-[15px] leading-none font-semibold theme-text",
19
+ className: "text-base leading-none font-semibold theme-text",
20
20
  children: title
21
21
  }), action]
22
22
  }), description && /* @__PURE__ */ jsx("p", {
@@ -34,7 +34,7 @@ function SettingsCard({ title, description, action, actionAlign = "beside", head
34
34
  function SettingsHeading({ children }) {
35
35
  const Heading = `h${useContext(HeadingLevel)}`;
36
36
  return /* @__PURE__ */ jsx(Heading, {
37
- className: "px-5 py-3 text-sm font-semibold tracking-wider uppercase theme-muted-panel theme-muted-text",
37
+ className: "px-4 py-3 text-sm font-semibold tracking-wider uppercase theme-muted-panel theme-muted-text",
38
38
  children
39
39
  });
40
40
  }
@@ -44,7 +44,7 @@ function SettingRow({ label, htmlFor, helpText, fill = false, onClick, children
44
44
  return /* @__PURE__ */ jsxs("div", {
45
45
  role: "none",
46
46
  onClick,
47
- className: cx("grid grid-cols-1 gap-x-8 gap-y-2 px-5 py-5", fill ? "md:grid-cols-[200px_1fr]" : "md:grid-cols-[1fr_auto]", onClick && "cursor-pointer transition-colors select-none hover:theme-hover"),
47
+ className: cx("grid grid-cols-1 gap-x-8 gap-y-2 p-4", fill ? "md:grid-cols-[200px_1fr]" : "md:grid-cols-[1fr_auto]", onClick && "cursor-pointer transition-colors select-none hover:theme-hover"),
48
48
  children: [/* @__PURE__ */ jsxs("div", {
49
49
  className: "flex flex-col gap-y-1",
50
50
  children: [/* @__PURE__ */ jsx("label", {
@@ -60,7 +60,7 @@ function Table({ density = "comfortable", borderless = false, panel = true, clas
60
60
  className: "flex h-32 items-center justify-center",
61
61
  children: /* @__PURE__ */ jsx(Loader, {})
62
62
  }) : /* @__PURE__ */ jsx("div", {
63
- className: cx("inline-block min-w-full align-middle", !stickyHeader && "overflow-x-auto", panel && "theme-panel", !borderless && "border theme-border", rounded && "rounded-2xl", roundedBottom && "rounded-b-2xl", roundedTop && "rounded-t-2xl"),
63
+ className: cx("inline-block min-w-full align-middle", !stickyHeader && "overflow-x-auto", panel && "theme-panel", !borderless && "border theme-border", rounded && "rounded-xl", roundedBottom && "rounded-b-xl", roundedTop && "rounded-t-xl"),
64
64
  children: /* @__PURE__ */ jsxs("table", {
65
65
  className: cx("min-w-full divide-y theme-border", className),
66
66
  ...tableProps,
@@ -8,5 +8,5 @@ interface CloseDetails {
8
8
  * press on a toast never does, since the toast sits above the backdrop. */
9
9
  export declare function closeHandler(dismissible: boolean, onClose: () => void): (next: boolean, details: CloseDetails) => void;
10
10
  /** The row of a dialog's actions along its bottom edge, ruled off from its body. */
11
- export declare const dialogFooterClassName = "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-5 py-3";
11
+ export declare const dialogFooterClassName = "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3";
12
12
  export {};
@@ -14,6 +14,6 @@ function closeHandler(dismissible, onClose) {
14
14
  };
15
15
  }
16
16
  /** The row of a dialog's actions along its bottom edge, ruled off from its body. */
17
- var dialogFooterClassName = "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-5 py-3";
17
+ var dialogFooterClassName = "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3";
18
18
  //#endregion
19
19
  export { closeHandler, dialogFooterClassName };
@@ -9,10 +9,10 @@ var sizes = {
9
9
  text: "text-xs"
10
10
  },
11
11
  md: {
12
- height: "h-9",
13
- minHeight: "min-h-9",
12
+ height: "h-8",
13
+ minHeight: "min-h-8",
14
14
  px: "px-3",
15
- py: "py-2",
15
+ py: "py-1.5",
16
16
  text: "text-sm"
17
17
  }
18
18
  };
@@ -1,4 +1,3 @@
1
- import { type ReactNode } from 'react';
2
1
  import type { GraphLayout } from '../editing.js';
3
2
  import type { DependencyGraphEditor } from './editorApi.js';
4
3
  import { type RunLink, type WorkflowJob } from './types.js';
@@ -18,7 +17,7 @@ interface GraphRun {
18
17
  executedJobs?: Record<string, unknown> | undefined;
19
18
  links?: Record<string, RunLink> | undefined;
20
19
  }
21
- export default function DependencyGraph({ run, preview, removeBorder, viewMode, setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, openStep, onCloseLog, sidebarStickyTop, children, }: {
20
+ export default function DependencyGraph({ run, preview, removeBorder, viewMode, setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, openStep, onCloseLog, sidebarStickyTop, }: {
22
21
  run: GraphRun;
23
22
  preview?: boolean;
24
23
  removeBorder?: boolean | undefined;
@@ -41,8 +40,6 @@ export default function DependencyGraph({ run, preview, removeBorder, viewMode,
41
40
  onCloseLog?: (() => void) | undefined;
42
41
  /** Pins the job list this many pixels below the top of the page's scroll area, under the host's own pinned bars. */
43
42
  sidebarStickyTop?: number | undefined;
44
- /** Shown under the annotations, above the graph. */
45
- children?: ReactNode;
46
43
  }): import("react").JSX.Element | undefined;
47
44
  export declare function DependencyGraphPreview(inputs: {
48
45
  yml?: Record<string, unknown>;
@@ -28,6 +28,7 @@ import { routeConnectors, sameBoxes } from "./connectorGeometry.js";
28
28
  import { isJobRecord } from "./types.js";
29
29
  import { computeGraphLayout, displayJobsOf, hopDistances, positionsOf } from "./graphLayout.js";
30
30
  import { MatrixGroupNode, MatrixGroupSummaryItem } from "./MatrixGroup.js";
31
+ import { StepSummaries } from "./StepSummaries.js";
31
32
  import { StepSummary } from "./StepSummary.js";
32
33
  import TreeView from "./TreeView.js";
33
34
  import { FIT_MAX_SCALE, MIN_SCALE, ZOOM_STEP, fitMargin, fitView, intoView, wheelFactor, zoomAround, zoomLimit } from "./viewport.js";
@@ -166,12 +167,12 @@ var addCleanupSteps = ({ jobs }) => {
166
167
  }
167
168
  return clonedJobs;
168
169
  };
169
- var PREVIEW_PADDING = 24;
170
+ var FIT_PADDING = 24;
170
171
  function outerNodes(content) {
171
172
  const top = content.querySelectorAll(":scope > div > div > div > div[id^=\"node_\"]");
172
173
  return top.length > 0 ? top : content.querySelectorAll("[id^=\"node_\"]");
173
174
  }
174
- function measurePreviewHeight(content, viewRef) {
175
+ function measureFitHeight(content, viewRef) {
175
176
  const nodes = content ? outerNodes(content) : void 0;
176
177
  const transform = content?.parentElement ? getComputedStyle(content.parentElement).transform : "none";
177
178
  if (!content || !nodes?.length || transform === "none") return null;
@@ -185,7 +186,7 @@ function measurePreviewHeight(content, viewRef) {
185
186
  const { scale, offsetY } = viewRef.current;
186
187
  let bottom = 0;
187
188
  for (const node of nodes) bottom = Math.max(bottom, offsetWithin(node, content).y + node.offsetHeight);
188
- return Math.ceil(bottom * scale + offsetY + PREVIEW_PADDING);
189
+ return Math.ceil(bottom * scale + offsetY + FIT_PADDING);
189
190
  }
190
191
  function offsetWithin(el, ancestor) {
191
192
  let x = 0;
@@ -220,6 +221,7 @@ function jobHandlers({ engine, jobs, displayJobs, pathPrefix, stepsOpen, toggleS
220
221
  const isStepsOpen = (j) => stepsOpen.has(pathPrefix + j);
221
222
  const subworkflowKey = (j, s) => `${pathPrefix}${j}:${s}`;
222
223
  const openLog = (j, s) => setSublogOpen(engine.stepLogPath(pathPrefix, j, s));
224
+ const openSummary = (j, s) => setSublogOpen(fileBeside(engine.stepLogPath(pathPrefix, j, s), "summary.md"));
223
225
  return {
224
226
  isStepsOpen,
225
227
  onJobClick: (j) => {
@@ -238,6 +240,7 @@ function jobHandlers({ engine, jobs, displayJobs, pathPrefix, stepsOpen, toggleS
238
240
  else openLog(j, s);
239
241
  },
240
242
  onStepLogClick: openLog,
243
+ onStepSummaryClick: openSummary,
241
244
  onSubworkflowIcon: (j, s) => openInNewGraph(pathPrefix, j, s),
242
245
  renderSubworkflow: (j, s) => {
243
246
  const subJobs = jobs[j]?.steps?.[s]?.subworkflow?.jobs;
@@ -696,7 +699,7 @@ function graphBounds(content) {
696
699
  }
697
700
  return bounds;
698
701
  }
699
- function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag", setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, openStep, onCloseLog, sidebarStickyTop, children }) {
702
+ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag", setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, openStep, onCloseLog, sidebarStickyTop }) {
700
703
  const engine = useWorkflowEngine();
701
704
  const animT = .125;
702
705
  const rootJobs = isJobRecord(run.executedJobs) ? run.executedJobs : {};
@@ -750,23 +753,24 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
750
753
  }
751
754
  }, [preview]);
752
755
  const containerHeight = topOffset !== null ? Math.max(windowSize.h - topOffset - 16, 160) : null;
753
- const [previewHeight, setPreviewHeight] = useState(null);
754
- const previewViewRef = useRef(null);
756
+ const [fitHeight, setFitHeight] = useState(null);
757
+ const fitViewRef = useRef(null);
755
758
  useEffect(() => {
756
- const container = containerRef.current;
757
- if (!preview || !container || fixedHeight) return;
759
+ const container = containerEl;
760
+ fitViewRef.current = null;
761
+ if (!container || fixedHeight) return;
758
762
  let raf = 0;
759
763
  let attempts = 0;
760
764
  const measure = () => {
761
765
  cancelAnimationFrame(raf);
762
766
  raf = requestAnimationFrame(() => {
763
- const next = measurePreviewHeight(graphContentRef.current, previewViewRef);
767
+ const next = measureFitHeight(graphContentRef.current, fitViewRef);
764
768
  if (next === null) {
765
769
  if (attempts++ < 120) measure();
766
770
  return;
767
771
  }
768
772
  attempts = 0;
769
- setPreviewHeight((prev) => prev === next ? prev : next);
773
+ setFitHeight((prev) => prev === next ? prev : next);
770
774
  });
771
775
  };
772
776
  measure();
@@ -779,7 +783,7 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
779
783
  observer.disconnect();
780
784
  cancelAnimationFrame(raf);
781
785
  };
782
- }, [preview, fixedHeight]);
786
+ }, [containerEl, fixedHeight]);
783
787
  const { common, dag: dagStrings } = useStrings();
784
788
  const stepHeaderRef = useRef(null);
785
789
  const slots = useSlots();
@@ -959,8 +963,8 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
959
963
  await Promise.allSettled(running.map((transition) => transition.finished));
960
964
  running = sizingTransitions(content);
961
965
  }
962
- const panelHeight = preview && !fixedHeight ? measurePreviewHeight(content, previewViewRef) : null;
963
- if (panelHeight !== null) flushSync(() => setPreviewHeight(panelHeight));
966
+ const panelHeight = fixedHeight ? null : measureFitHeight(content, fitViewRef);
967
+ if (panelHeight !== null) flushSync(() => setFitHeight(panelHeight));
964
968
  await sizesSettled([
965
969
  container,
966
970
  content,
@@ -971,7 +975,6 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
971
975
  }, [
972
976
  fitGraph,
973
977
  invalidateConnectors,
974
- preview,
975
978
  fixedHeight
976
979
  ]);
977
980
  const centerOn = useCallback((el) => {
@@ -1361,7 +1364,6 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
1361
1364
  ]
1362
1365
  }),
1363
1366
  !preview && /* @__PURE__ */ jsx(AnnotationBanner, { executedJobs: jobs }),
1364
- children,
1365
1367
  viewMode === "tree" && !preview ? /* @__PURE__ */ jsx("div", {
1366
1368
  className: "panel w-full border",
1367
1369
  children: /* @__PURE__ */ jsx(TreeView, {
@@ -1375,8 +1377,8 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
1375
1377
  tabIndex: editor ? -1 : void 0,
1376
1378
  className: cx("panel w-full overflow-hidden relative", removeBorder ? "border-none" : "border", editor && "outline-none"),
1377
1379
  style: {
1378
- height: fixedHeight ?? (containerHeight !== null ? `${containerHeight}px` : previewHeight !== null ? `${Math.min(previewHeight, windowSize.h * .62)}px` : "62vh"),
1379
- minHeight: previewHeight !== null && !fixedHeight ? 0 : 160
1380
+ height: fixedHeight ?? (fitHeight !== null ? `${Math.min(fitHeight, containerHeight ?? windowSize.h * .62)}px` : containerHeight !== null ? `${containerHeight}px` : "62vh"),
1381
+ minHeight: preview && fitHeight !== null && !fixedHeight ? 0 : 160
1380
1382
  },
1381
1383
  children: [/* @__PURE__ */ jsx(TransformWrapper, {
1382
1384
  ref: transformRef,
@@ -1444,6 +1446,11 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
1444
1446
  })
1445
1447
  }) })] })
1446
1448
  }), editorOverlays]
1449
+ }),
1450
+ !preview && /* @__PURE__ */ jsx(StepSummaries, {
1451
+ runSlug,
1452
+ jobs: rootJobs,
1453
+ onOpenLog: ({ prefix, job, index }) => setSublogOpen(engine.stepLogPath(prefix, job, index))
1447
1454
  })
1448
1455
  ]
1449
1456
  }),
@@ -6,6 +6,7 @@ export interface JobHandlers {
6
6
  onJobClick: (jobName: string) => void;
7
7
  onStepClick: (jobName: string, stepIndex: number) => void;
8
8
  onStepLogClick: (jobName: string, stepIndex: number) => void;
9
+ onStepSummaryClick: (jobName: string, stepIndex: number) => void;
9
10
  onSubworkflowIcon: (jobName: string, stepIndex: number) => void;
10
11
  renderSubworkflow: (jobName: string, stepIndex: number) => ReactNode;
11
12
  }
@@ -68,57 +68,63 @@ function Joblist(inputs) {
68
68
  onClick: () => inputs.onStepClick(jobName, i),
69
69
  className: cx(inputs.preview ? "ml-1.5" : "ml-4", "gap-x-1.5 flex items-center text-left"),
70
70
  "data-testid": stepLabel,
71
- children: [
72
- inputs.preview ? "↳" : /* @__PURE__ */ jsx(RunStatusIcon, { status: step.status }),
73
- /* @__PURE__ */ jsx("div", { children: stepLabel }),
74
- step.summary && !inputs.preview && /* @__PURE__ */ jsx(Tooltip, {
75
- content: dag.hasSummary,
76
- children: /* @__PURE__ */ jsx(SummaryIcon, {
77
- "aria-label": dag.hasSummary,
78
- className: "w-[1.1em] h-[1.1em] shrink-0 text-(--theme-muted-text-color)"
79
- })
80
- })
81
- ]
71
+ children: [inputs.preview ? "↳" : /* @__PURE__ */ jsx(RunStatusIcon, { status: step.status }), /* @__PURE__ */ jsx("div", { children: stepLabel })]
82
72
  });
83
73
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
84
74
  className: cx("flex items-center", step.status === "skipped" || step.status === "skipped-failed" ? "cursor-default" : "cursor-pointer"),
85
- children: [(inputs.editable || inputs.editableSteps) && !step.linkedStep ? /* @__PURE__ */ jsx(EditableStepRow, {
86
- job: job?._matrix?.originaljob ?? jobName,
87
- index: i,
88
- label: stepLabel,
89
- children: stepButton
90
- }) : stepButton, step.subworkflow && /* @__PURE__ */ jsxs(Fragment, { children: [
91
- /* @__PURE__ */ jsx(Tooltip, {
92
- content: jobActions.openSubworkflowLogs,
75
+ children: [
76
+ (inputs.editable || inputs.editableSteps) && !step.linkedStep ? /* @__PURE__ */ jsx(EditableStepRow, {
77
+ job: job?._matrix?.originaljob ?? jobName,
78
+ index: i,
79
+ label: stepLabel,
80
+ children: stepButton
81
+ }) : stepButton,
82
+ step.summary && !inputs.preview && /* @__PURE__ */ jsx(Tooltip, {
83
+ content: dag.hasSummary,
93
84
  children: /* @__PURE__ */ jsx("button", {
94
85
  type: "button",
95
- "aria-label": jobActions.openSubworkflowLogs,
86
+ "aria-label": dag.hasSummary,
96
87
  onClick: (e) => {
97
88
  e.stopPropagation();
98
- inputs.onStepLogClick(jobName, i);
89
+ inputs.onStepSummaryClick(jobName, i);
99
90
  },
100
- className: "link ml-1",
101
- children: /* @__PURE__ */ jsx(FileTextIcon, { className: "w-[1.2em] h-[1.2em]" })
91
+ className: "ml-1.5 shrink-0 text-(--theme-muted-text-color) hover:text-(--theme-link)",
92
+ children: /* @__PURE__ */ jsx(SummaryIcon, { className: "w-[1.1em] h-[1.1em]" })
102
93
  })
103
94
  }),
104
- /* @__PURE__ */ jsx(UsesIcon, {
105
- uses: step.uses,
106
- subworkflow: step.subworkflow
107
- }),
108
- /* @__PURE__ */ jsx(Tooltip, {
109
- content: jobActions.openInNewGraph,
110
- children: /* @__PURE__ */ jsx("button", {
111
- type: "button",
112
- "aria-label": jobActions.openInNewGraph,
113
- onClick: (e) => {
114
- e.stopPropagation();
115
- inputs.onSubworkflowIcon(jobName, i);
116
- },
117
- className: "link ml-1",
118
- children: /* @__PURE__ */ jsx(OpenInNewGraphIcon, { className: "w-[1.2em] h-[1.2em]" })
95
+ step.subworkflow && /* @__PURE__ */ jsxs(Fragment, { children: [
96
+ /* @__PURE__ */ jsx(Tooltip, {
97
+ content: jobActions.openSubworkflowLogs,
98
+ children: /* @__PURE__ */ jsx("button", {
99
+ type: "button",
100
+ "aria-label": jobActions.openSubworkflowLogs,
101
+ onClick: (e) => {
102
+ e.stopPropagation();
103
+ inputs.onStepLogClick(jobName, i);
104
+ },
105
+ className: "link ml-1",
106
+ children: /* @__PURE__ */ jsx(FileTextIcon, { className: "w-[1.2em] h-[1.2em]" })
107
+ })
108
+ }),
109
+ /* @__PURE__ */ jsx(UsesIcon, {
110
+ uses: step.uses,
111
+ subworkflow: step.subworkflow
112
+ }),
113
+ /* @__PURE__ */ jsx(Tooltip, {
114
+ content: jobActions.openInNewGraph,
115
+ children: /* @__PURE__ */ jsx("button", {
116
+ type: "button",
117
+ "aria-label": jobActions.openInNewGraph,
118
+ onClick: (e) => {
119
+ e.stopPropagation();
120
+ inputs.onSubworkflowIcon(jobName, i);
121
+ },
122
+ className: "link ml-1",
123
+ children: /* @__PURE__ */ jsx(OpenInNewGraphIcon, { className: "w-[1.2em] h-[1.2em]" })
124
+ })
119
125
  })
120
- })
121
- ] })]
126
+ ] })
127
+ ]
122
128
  }), canExpand && inputs.renderSubworkflow(jobName, i)] }, stepKey);
123
129
  }), inputs.editable && /* @__PURE__ */ jsx(AddStepButton, { job: jobName })]
124
130
  })
@@ -0,0 +1,22 @@
1
+ import type { WorkflowJob, WorkflowStep } from './types.js';
2
+ export interface StepSummarySource {
3
+ /** The `subworkflows/<job>/step_<n>/` chain of a step inside a subworkflow; empty at the top level. */
4
+ prefix: string;
5
+ job: string;
6
+ /** The job's name, after the names of the jobs whose subworkflows it runs in. */
7
+ jobNames: string[];
8
+ index: number;
9
+ step: WorkflowStep;
10
+ }
11
+ /** The steps, subworkflows' included, that wrote a summary, in the order their jobs completed. */
12
+ export declare function stepSummarySources(jobs: Record<string, WorkflowJob>): StepSummarySource[];
13
+ /**
14
+ * A run's step summaries, each on its own card headed by the job and step that wrote it, so a run
15
+ * reads like a report under its graph. The job list beside the graph already shows the run's tree,
16
+ * so the cards are flat.
17
+ */
18
+ export declare function StepSummaries({ runSlug, jobs, onOpenLog, }: {
19
+ runSlug: string;
20
+ jobs: Record<string, WorkflowJob>;
21
+ onOpenLog: (source: StepSummarySource) => void;
22
+ }): import("react").JSX.Element | null;