From 144f3ebb742adf6c2bf4deb20c20df50b3d91b48 Mon Sep 17 00:00:00 2001 From: Blake Gentry Date: Wed, 20 Aug 2025 22:27:23 -0500 Subject: [PATCH] fix workflow diagram zoom issues, style minimap --- CHANGELOG.md | 5 +++ src/components/WorkflowDiagram.tsx | 50 +++++++++++++++++++++++++----- src/components/WorkflowNode.tsx | 10 ++++-- src/components/reactflow-base.css | 1 + vite.config.ts | 4 +-- 5 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 src/components/reactflow-base.css diff --git a/CHANGELOG.md b/CHANGELOG.md index e290966a..03946926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Workflow detail: prevent the viewport from getting stuck zoomed into empty space after navigating between the workflow list and detail pages by remounting the diagram per workflow and enabling fit-to-view on mount. [PR #408](https://github.com/riverqueue/riverui/pull/408) +- Workflow detail: make MiniMap render nodes correctly and respect dark mode by setting explicit node bounds and theme-aware colors. [PR #408](https://github.com/riverqueue/riverui/pull/408) + ## [v0.12.2] - 2025-08-16 No changes from v0.12.0 except fixes to the release process. v0.12.0 is not usable, this version should be used instead. diff --git a/src/components/WorkflowDiagram.tsx b/src/components/WorkflowDiagram.tsx index 0eede588..b8eef93d 100644 --- a/src/components/WorkflowDiagram.tsx +++ b/src/components/WorkflowDiagram.tsx @@ -8,8 +8,9 @@ import type { } from "@xyflow/react"; import WorkflowNode, { WorkflowNodeData } from "@components/WorkflowNode"; + +import "./reactflow-base.css"; import dagre from "@dagrejs/dagre"; -import "@xyflow/react/dist/style.css"; import { JobWithKnownMetadata } from "@services/jobs"; import { JobState } from "@services/types"; import { MiniMap, ReactFlow } from "@xyflow/react"; @@ -87,7 +88,8 @@ const edgeColorsDark = { const depStatusFromJob = (job: JobWithKnownMetadata) => { switch (job.state) { - case (JobState.Cancelled, JobState.Discarded): + case JobState.Cancelled: + case JobState.Discarded: return "failed"; case JobState.Completed: return "unblocked"; @@ -114,6 +116,27 @@ export default function WorkflowDiagram({ const minimapMaskColor = resolvedTheme === "dark" ? "rgb(5, 5, 5, 0.5)" : "rgb(250, 250, 250, 0.5)"; + const getMiniMapNodeClassName = ( + node: Node, + ): string => { + const state = node.data?.job?.state; + switch (state) { + case JobState.Available: + case JobState.Pending: + case JobState.Retryable: + case JobState.Scheduled: + return "fill-amber-300/60 stroke-amber-500/60 dark:fill-amber-700/50 dark:stroke-amber-400/50 stroke-1"; + case JobState.Cancelled: + case JobState.Discarded: + return "fill-red-300/60 stroke-red-500/60 dark:fill-red-700/50 dark:stroke-red-400/50 stroke-1"; + case JobState.Completed: + return "fill-green-300/60 stroke-green-500/60 dark:fill-green-500/70 dark:stroke-green-300/70 stroke-1"; + case JobState.Running: + return "fill-blue-300/60 stroke-blue-500/60 dark:fill-blue-700/50 dark:stroke-blue-400/50 stroke-1"; + default: + return "fill-slate-300/60 stroke-slate-600/60 dark:fill-slate-700/50 dark:stroke-slate-400/50 stroke-1"; + } + }; // TODO: not ideal to iterate through this list so many times. Should probably // do that once and save all results at the same time. @@ -139,10 +162,12 @@ export default function WorkflowDiagram({ hasUpstreamDeps: job.metadata.deps?.length > 0, job, }, + height: nodeHeight, id: job.id.toString(), position: { x: 0, y: 0 }, selected: selectedJobId === job.id, type: "workflowNode", + width: nodeWidth, })), [tasks, selectedJobId, tasksWithDownstreamDeps], ); @@ -184,6 +209,10 @@ export default function WorkflowDiagram({ "LR", ); + // Use workflow id to scope/reset the ReactFlow instance between navigations + const workflowIdForInstance = + tasks[0]?.metadata.workflow_id ?? "unknown-workflow"; + const isNodeSelectionChange = ( change: NodeChange, ): change is NodeSelectionChange => { @@ -211,8 +240,12 @@ export default function WorkflowDiagram({ {}} @@ -220,12 +253,15 @@ export default function WorkflowDiagram({ proOptions={{ hideAttribution: true }} > diff --git a/src/components/WorkflowNode.tsx b/src/components/WorkflowNode.tsx index 1015b349..f98d825c 100644 --- a/src/components/WorkflowNode.tsx +++ b/src/components/WorkflowNode.tsx @@ -3,10 +3,10 @@ import type { Node, NodeProps } from "@xyflow/react"; import { TaskStateIcon } from "@components/TaskStateIcon"; import { JobWithKnownMetadata } from "@services/jobs"; import { JobState } from "@services/types"; -import { Handle, Position } from "@xyflow/react"; +import { Handle, Position, useUpdateNodeInternals } from "@xyflow/react"; import clsx from "clsx"; import { differenceInSeconds } from "date-fns"; -import { memo, useMemo } from "react"; +import { memo, useEffect, useMemo } from "react"; import { useTime } from "react-time-sync"; export type WorkflowNodeData = { @@ -20,6 +20,12 @@ type WorkflowNode = Node; const WorkflowNode = memo( ({ data, isConnectable, selected }: NodeProps) => { const { hasDownstreamDeps, hasUpstreamDeps, job } = data; + const updateNodeInternals = useUpdateNodeInternals(); + + // Ask xyflow to re-measure this custom node after mount/updates so MiniMap gets correct bounds + useEffect(() => { + updateNodeInternals(String(job.id)); + }, [job.id, updateNodeInternals]); return (