Skip to content

Commit 36a4b6a

Browse files
authored
fix(canvas): repair React Flow v12 and dependency upgrade regressions (#7355)
* fix(canvas): repair React Flow v12 and dependency upgrade regressions * fix(trigger): keep pdfjs-dist external to the worker bundle
1 parent d7b31a8 commit 36a4b6a

26 files changed

Lines changed: 195 additions & 45 deletions

File tree

.devcontainer/post-create.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ fi
9494
# Generate schema and run database migrations
9595
echo "🗃️ Running database schema generation and migrations..."
9696
echo "Generating schema..."
97-
cd apps/sim
98-
bunx drizzle-kit generate
97+
cd packages/db
98+
bun run db:generate
9999
cd ../..
100100

101101
echo "Waiting for database to be ready..."
@@ -105,8 +105,8 @@ echo "Waiting for database to be ready..."
105105
while [ $timeout -gt 0 ]; do
106106
if PGPASSWORD=postgres psql -h db -U postgres -c '\q' 2>/dev/null; then
107107
echo "Database is ready!"
108-
cd apps/sim
109-
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio bunx drizzle-kit push
108+
cd packages/db
109+
DATABASE_URL=postgresql://postgres:postgres@db:5432/simstudio bun run db:push
110110
cd ../..
111111
break
112112
fi

.devcontainer/sim-commands.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
alias sim-start="cd /workspace && bun run dev:full"
88
alias sim-app="cd /workspace && bun run dev"
99
alias sim-sockets="cd /workspace && bun run dev:sockets"
10-
alias sim-migrate="cd /workspace/apps/sim && bunx drizzle-kit push"
11-
alias sim-generate="cd /workspace/apps/sim && bunx drizzle-kit generate"
10+
alias sim-migrate="cd /workspace/packages/db && bun run db:push"
11+
alias sim-generate="cd /workspace/packages/db && bun run db:generate"
1212
alias sim-rebuild="cd /workspace && bun run build && bun run start"
1313
alias docs-dev="cd /workspace/apps/docs && bun run dev"
1414

apps/docs/components/workflow-preview/block-preview.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
'use client'
22

33
import { useMemo } from 'react'
4+
import { CANVAS_Z_INDEX_MODE } from '@sim/workflow-renderer'
45
import { type NodeTypes, ReactFlow, ReactFlowProvider } from '@xyflow/react'
56
import { domAnimation, LazyMotion } from 'framer-motion'
67
import '@xyflow/react/dist/style.css'
78
import { BLOCK_DISPLAY_WORKFLOWS } from '@/components/workflow-preview/block-display-workflows'
89
import { DocsBlockNode } from '@/components/workflow-preview/docs-block-node'
10+
import { usePreviewColorMode } from '@/components/workflow-preview/use-preview-color-mode'
911
import { toReactFlowElements } from '@/components/workflow-preview/workflow-data'
1012

1113
/** The hero mounts the same node type the canvas uses, so it can never drift. */
@@ -28,6 +30,7 @@ interface BlockPreviewProps {
2830
* `block-display-workflows.ts`.
2931
*/
3032
export function BlockPreview({ type }: BlockPreviewProps) {
33+
const colorMode = usePreviewColorMode()
3134
const workflow = BLOCK_DISPLAY_WORKFLOWS[type]
3235

3336
const elements = useMemo(() => (workflow ? toReactFlowElements(workflow) : null), [workflow])
@@ -42,6 +45,8 @@ export function BlockPreview({ type }: BlockPreviewProps) {
4245
<LazyMotion features={domAnimation}>
4346
<ReactFlowProvider>
4447
<ReactFlow
48+
colorMode={colorMode}
49+
zIndexMode={CANVAS_Z_INDEX_MODE}
4550
nodes={elements.nodes}
4651
edges={elements.edges}
4752
nodeTypes={NODE_TYPES}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use client'
2+
3+
import type { ColorMode } from '@xyflow/react'
4+
import { useTheme } from 'next-themes'
5+
6+
/**
7+
* Resolves the React Flow `colorMode` from the docs theme so the canvas
8+
* wrapper's color-mode class (and the `--xy-*` palette it selects) follows
9+
* dark mode instead of React Flow's default `light`.
10+
*/
11+
export function usePreviewColorMode(): ColorMode {
12+
const { resolvedTheme } = useTheme()
13+
// Before next-themes mounts, resolvedTheme is undefined; 'system' lets React
14+
// Flow follow the OS preference instead of flashing a light-classed frame.
15+
if (resolvedTheme === undefined) return 'system'
16+
return resolvedTheme === 'dark' ? 'dark' : 'light'
17+
}

apps/docs/components/workflow-preview/workflow-preview.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
44
import { Expand, X } from '@sim/emcn/icons'
5+
import { CANVAS_Z_INDEX_MODE } from '@sim/workflow-renderer'
56
import {
67
applyEdgeChanges,
78
applyNodeChanges,
@@ -20,6 +21,7 @@ import { BLOCK_DISPLAY_WORKFLOWS } from '@/components/workflow-preview/block-dis
2021
import { BlockInspector } from '@/components/workflow-preview/block-inspector'
2122
import { DocsBlockNode } from '@/components/workflow-preview/docs-block-node'
2223
import { DocsContainerNode } from '@/components/workflow-preview/docs-container-node'
24+
import { usePreviewColorMode } from '@/components/workflow-preview/use-preview-color-mode'
2325
import {
2426
EASE_OUT,
2527
type PreviewBlock,
@@ -177,6 +179,7 @@ function PreviewFlow({
177179
[workflow, animate, highlightBlock, highlightEdge, selectedBlock]
178180
)
179181

182+
const colorMode = usePreviewColorMode()
180183
const [nodes, setNodes] = useState<PreviewNode[]>(initialNodes)
181184
const [edges, setEdges] = useState<PreviewFlowEdge[]>(initialEdges)
182185

@@ -206,6 +209,8 @@ function PreviewFlow({
206209

207210
return (
208211
<ReactFlow<PreviewNode, PreviewFlowEdge>
212+
colorMode={colorMode}
213+
zIndexMode={CANVAS_Z_INDEX_MODE}
209214
nodes={nodes}
210215
edges={edges}
211216
onNodesChange={onNodesChange}

apps/sim/app/(landing)/demo/components/demo-scheduler/cal-config.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,18 @@ export function resolveCalLink(configuredLink?: string): URL {
2020
return url
2121
}
2222

23-
const calLinkUrl = resolveCalLink(process.env.NEXT_PUBLIC_CAL_LINK)
23+
/**
24+
* Resolved at module scope on an eagerly-imported path, so a malformed
25+
* NEXT_PUBLIC_CAL_LINK degrades to the default link instead of taking the
26+
* whole /demo page down.
27+
*/
28+
const calLinkUrl = (() => {
29+
try {
30+
return resolveCalLink(process.env.NEXT_PUBLIC_CAL_LINK)
31+
} catch {
32+
return resolveCalLink(undefined)
33+
}
34+
})()
2435

2536
/** Exact origin used for iframe navigation, preconnect, and postMessage validation. */
2637
export const CAL_ORIGIN = calLinkUrl.origin

apps/sim/app/_styles/globals.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,8 @@ input[type="search"]::-ms-clear {
11151115
border-radius: 8px !important;
11161116
}
11171117

1118-
.react-flow__node[data-parent-node-id] .react-flow__handle {
1118+
/* React Flow v12 no longer emits data-parent-node-id; the app stamps
1119+
.subflow-child-node (SUBFLOW_CHILD_NODE_CLASS) on nested nodes instead. */
1120+
.react-flow__node.subflow-child-node .react-flow__handle {
11191121
z-index: 30;
11201122
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ import type { BlockState } from '@/stores/workflows/workflow/types'
77

88
export const SUBFLOW_DROP_TARGET_CLASS = 'subflow-node-drop-target'
99

10+
/**
11+
* Marks canvas nodes nested inside a subflow container. React Flow v11 emitted
12+
* `data-parent-node-id` for this; v12 emits no parent attribute, so the app
13+
* stamps its own class for the handle z-lift in `globals.css`.
14+
*/
15+
export const SUBFLOW_CHILD_NODE_CLASS = 'subflow-child-node'
16+
1017
export function getNodeDataDimension(
1118
node: Pick<Node, 'data'>,
1219
dimension: 'width' | 'height',
@@ -55,11 +62,10 @@ function reconcileById<T extends { id: string }>(
5562
}
5663

5764
/**
58-
* Subset comparison, deliberately asymmetric: React Flow writes `width`,
59-
* `height`, `positionAbsolute` and `dragging` onto the node objects it owns, so
60-
* a symmetric `isEqual` against a freshly derived node would never match and no
61-
* node would ever be reused. Only the keys the derivation itself produces are
62-
* compared.
65+
* Subset comparison, deliberately asymmetric: React Flow writes `measured` and
66+
* `dragging` onto the node objects it owns, so a symmetric `isEqual` against a
67+
* freshly derived node would never match and no node would ever be reused. Only
68+
* the keys the derivation itself produces are compared.
6369
*/
6470
function containsDerivedValues<T extends object>(current: T, derived: T): boolean {
6571
for (const key of Object.keys(derived) as (keyof T)[]) {
@@ -68,12 +74,28 @@ function containsDerivedValues<T extends object>(current: T, derived: T): boolea
6874
return true
6975
}
7076

71-
/** Reuses unchanged React Flow node objects while carrying local selection forward. */
72-
export function reconcileCanvasNodes(currentNodes: Node[], derivedNodes: Node[]): Node[] {
77+
/**
78+
* Reuses unchanged React Flow node objects while carrying local selection and
79+
* measured dimensions forward. `measured` must survive re-derivation: React
80+
* Flow resets a node's cached handle bounds and re-measures whenever a node
81+
* object arrives without it, which snaps connected edges for a frame.
82+
*
83+
* @param selectedIds - When provided, overrides selection instead of carrying
84+
* it forward (e.g. the pending selection applied after paste/duplicate)
85+
*/
86+
export function reconcileCanvasNodes(
87+
currentNodes: Node[],
88+
derivedNodes: Node[],
89+
selectedIds?: ReadonlySet<string>
90+
): Node[] {
7391
return reconcileById(
7492
currentNodes,
7593
derivedNodes,
76-
(derivedNode, currentNode) => ({ ...derivedNode, selected: currentNode?.selected ?? false }),
94+
(derivedNode, currentNode) => ({
95+
...derivedNode,
96+
measured: currentNode?.measured,
97+
selected: selectedIds ? selectedIds.has(derivedNode.id) : (currentNode?.selected ?? false),
98+
}),
7799
containsDerivedValues
78100
)
79101
}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type { SubflowNodeData } from '@sim/workflow-renderer'
2323
import {
2424
BLOCK_DIMENSIONS,
2525
BLOCK_Z_BASE,
26+
CANVAS_Z_INDEX_MODE,
2627
CONNECTION_PICKER_Z,
2728
CONTAINER_CHILD_Z_BASE,
2829
CONTAINER_DIMENSIONS,
@@ -102,6 +103,7 @@ import {
102103
reconcileCanvasEdges,
103104
reconcileCanvasNodes,
104105
resolveSelectionConflicts,
106+
SUBFLOW_CHILD_NODE_CLASS,
105107
SUBFLOW_DROP_TARGET_CLASS,
106108
shouldHighlightContainerDropTarget,
107109
validateTriggerPaste,
@@ -136,6 +138,7 @@ import {
136138
isFolderOrAncestorLocked,
137139
} from '@/hooks/queries/utils/folder-tree'
138140
import { useUpdateWorkflow, useWorkflowMap } from '@/hooks/queries/workflows'
141+
import { useCanvasColorMode } from '@/hooks/use-canvas-color-mode'
139142
import { useCanvasViewport } from '@/hooks/use-canvas-viewport'
140143
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
141144
import { useOAuthReturnForWorkflow } from '@/hooks/use-oauth-return'
@@ -342,6 +345,7 @@ const WorkflowContent = React.memo(
342345

343346
const params = useParams()
344347
const router = useRouter()
348+
const colorMode = useCanvasColorMode()
345349
const reactFlowInstance = useReactFlow()
346350
const { screenToFlowPosition, getNodes, setNodes } = reactFlowInstance
347351
const { fitViewToBounds, getViewportCenter } = useCanvasViewport(reactFlowInstance, {
@@ -2869,6 +2873,7 @@ const WorkflowContent = React.memo(
28692873
type: 'subflowNode',
28702874
position: block.position,
28712875
parentId: block.data?.parentId,
2876+
className: block.data?.parentId ? SUBFLOW_CHILD_NODE_CLASS : undefined,
28722877
extent: block.data?.extent || undefined,
28732878
dragHandle: '.workflow-drag-handle',
28742879
draggable: !workflowReadOnly && !isBlockProtected(block.id, blocks),
@@ -2907,20 +2912,21 @@ const WorkflowContent = React.memo(
29072912
// level as a subflow container and below the edge band. A card inside a
29082913
// container starts higher still, so it clears the parent's interactive
29092914
// body area (which needs pointer-events for click-to-select).
2910-
const cardZIndex = block.data?.parentId ? CONTAINER_CHILD_Z_BASE : BLOCK_Z_BASE
2915+
const parentId = block.data?.parentId as string | undefined
2916+
const cardZIndex = parentId ? CONTAINER_CHILD_Z_BASE : BLOCK_Z_BASE
29112917

29122918
// Create stable node object - React Flow will handle shallow comparison
29132919
nodeArray.push({
29142920
id: block.id,
29152921
type: nodeType,
29162922
position,
2917-
parentId: block.data?.parentId,
2923+
parentId,
2924+
className: parentId ? SUBFLOW_CHILD_NODE_CLASS : undefined,
29182925
dragHandle,
29192926
draggable: !workflowReadOnly && !isBlockProtected(block.id, blocks),
29202927
zIndex: cardZIndex,
29212928
extent: (() => {
29222929
// Clamp children to subflow body (exclude header)
2923-
const parentId = block.data?.parentId as string | undefined
29242930
if (!parentId) return block.data?.extent || undefined
29252931

29262932
// Constrain the top and left to the container's own gutter, the same
@@ -2949,11 +2955,13 @@ const WorkflowContent = React.memo(
29492955
onSetErrorOutputEnabled: collaborativeSetBlockErrorEnabled,
29502956
onRemoveEdges: collaborativeBatchRemoveEdges,
29512957
},
2952-
// Include dynamic dimensions for container resizing calculations (must match rendered size)
2953-
// Both note and workflow blocks calculate dimensions deterministically via useBlockDimensions
2954-
// Use estimated dimensions for blocks without measured height to ensure selection bounds are correct
2955-
width: getRegularBlockWidth(block.type),
2956-
height: block.height
2958+
// Seed dimensions so selection bounds and container-resize math are
2959+
// valid before the first measurement. These must stay `initial*`: in
2960+
// React Flow v12 top-level `width`/`height` become fixed inline
2961+
// styles that clamp the node, while `initialWidth`/`initialHeight`
2962+
// only stand in until the rendered content is measured.
2963+
initialWidth: getRegularBlockWidth(block.type),
2964+
initialHeight: block.height
29572965
? block.type === 'note'
29582966
? block.height
29592967
: Math.max(block.height, BLOCK_DIMENSIONS.MIN_HEIGHT)
@@ -3003,12 +3011,12 @@ const WorkflowContent = React.memo(
30033011
clearPendingSelection()
30043012

30053013
// Apply pending selection and resolve parent-child conflicts
3006-
const withSelection = derivedNodes.map((node) => ({
3007-
...node,
3008-
selected: pendingSet.has(node.id),
3009-
}))
3010-
const resolved = resolveSelectionConflicts(withSelection, blocks)
3011-
setDisplayNodes(resolved)
3014+
setDisplayNodes((currentNodes) =>
3015+
resolveSelectionConflicts(
3016+
reconcileCanvasNodes(currentNodes, derivedNodes, pendingSet),
3017+
blocks
3018+
)
3019+
)
30123020
return
30133021
}
30143022

@@ -5120,6 +5128,8 @@ const WorkflowContent = React.memo(
51205128
{isWorkflowReady && (
51215129
<>
51225130
<ReactFlow
5131+
colorMode={colorMode}
5132+
zIndexMode={CANVAS_Z_INDEX_MODE}
51235133
nodes={nodesForRender}
51245134
edges={edgesForRender}
51255135
onNodesChange={onNodesChange}
@@ -5197,7 +5207,6 @@ const WorkflowContent = React.memo(
51975207
draggable={false}
51985208
noWheelClassName='allow-scroll'
51995209
edgesFocusable={!embedded}
5200-
edgesReconnectable={!embedded && effectivePermissions.canEdit}
52015210
className={`workflow-container h-full bg-[var(--bg)] transition-opacity duration-150 ${reactFlowStyles} ${canvasOpacityClass} ${isHandMode ? 'canvas-mode-hand' : 'canvas-mode-cursor'}`}
52025211
onNodeDrag={effectivePermissions.canEdit ? onNodeDrag : undefined}
52035212
onNodeDragStop={

apps/sim/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/preview-workflow.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { createLogger } from '@sim/logger'
1919
import {
2020
BLOCK_DIMENSIONS,
2121
BLOCK_Z_BASE,
22+
CANVAS_Z_INDEX_MODE,
2223
CONTAINER_CHILD_Z_BASE,
2324
CONTAINER_DIMENSIONS,
2425
EDGE_Z_BASE,
@@ -27,10 +28,14 @@ import {
2728
} from '@sim/workflow-renderer'
2829
import { normalizeWorkflowEdgeHandles } from '@sim/workflow-types/workflow'
2930
import { WorkflowEdge } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge'
30-
import { estimateBlockDimensions } from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
31+
import {
32+
estimateBlockDimensions,
33+
SUBFLOW_CHILD_NODE_CLASS,
34+
} from '@/app/workspace/[workspaceId]/w/[workflowId]/utils'
3135
import { PreviewBlock } from '@/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/block'
3236
import { PreviewSubflow } from '@/app/workspace/[workspaceId]/w/components/preview/components/preview-workflow/components/subflow'
3337
import { useWorkflowMap } from '@/hooks/queries/workflows'
38+
import { useCanvasColorMode } from '@/hooks/use-canvas-color-mode'
3439
import type { BlockState, WorkflowState } from '@/stores/workflows/workflow/types'
3540

3641
const logger = createLogger('PreviewWorkflow')
@@ -251,6 +256,7 @@ export function PreviewWorkflow({
251256
// placeholder map must not mislabel valid workflows as deleted.
252257
const workflowLabelsReady = isWorkflowMapLoaded && !isWorkflowMapPlaceholderData
253258
const containerRef = useRef<HTMLDivElement>(null)
259+
const colorMode = useCanvasColorMode()
254260
const nodeTypes = previewNodeTypes
255261
const isValidWorkflowState = workflowState?.blocks && workflowState.edges
256262

@@ -429,6 +435,7 @@ export function PreviewWorkflow({
429435
type: 'subflowNode',
430436
position: block.position,
431437
parentId,
438+
className: parentId ? SUBFLOW_CHILD_NODE_CLASS : undefined,
432439
extent: block.data?.extent || undefined,
433440
draggable: false,
434441
zIndex: nestingDepth,
@@ -472,6 +479,7 @@ export function PreviewWorkflow({
472479
type: nodeType,
473480
position: block.position,
474481
parentId,
482+
className: parentId ? SUBFLOW_CHILD_NODE_CLASS : undefined,
475483
extent: block.data?.extent || undefined,
476484
draggable: false,
477485
zIndex: parentId ? CONTAINER_CHILD_Z_BASE : BLOCK_Z_BASE,
@@ -654,6 +662,8 @@ export function PreviewWorkflow({
654662
.preview-mode.interactive-nodes .react-flow__node * { cursor: pointer !important; }
655663
`}</style>
656664
<ReactFlow
665+
colorMode={colorMode}
666+
zIndexMode={CANVAS_Z_INDEX_MODE}
657667
nodes={nodes}
658668
edges={edges}
659669
nodeTypes={nodeTypes}

0 commit comments

Comments
 (0)