improvement(workflow): redesigned block cards with edge-following connection swell - #5976
improvement(workflow): redesigned block cards with edge-following connection swell#5976andresdjasso wants to merge 2 commits into
Conversation
Connection anchors: an output now always leaves a card from the right.
The cursor swell lets a drag start on any edge, but the left side is the
input, so anchoring an outgoing edge there drew a line out of the input
port and read as a second input. `normalizeCursorSourceHandleId` resolves
every drag to the right anchor, `normalizePositionedSourceHandleId`
collapses `source-left` alongside the legacy vertical anchors (so data
from the API, an older client, or a stale save self-heals on load), and
only the right-side source anchor is mounted.
Drops in `onConnectEnd` are always source -> target. The branch that
reversed the edge for a drag starting on an input could never run: the
`target` handle is `isConnectableStart={false}` and the positioned side
anchors are `isConnectable={false}`, so React Flow never reports an input
as a drag origin. Removed it and its now-unused imports.
A newly created block is centered once its node mounts and is measured,
so a card added from a drag-release, the block menu, or the toolbar is
never left off-screen or under the editor panel.
The editor panel's block icon uses the same type accent as the card's
badge instead of the block's legacy `bgColor`, which had left the panel
on the old per-integration brand colours.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PR SummaryMedium Risk Overview Connection model changes: outputs always anchor on the right; inputs on the left (or left/right on drop); legacy Error branch: blocks get Card content: natural-language summaries and chip rows for key block types, meta-row icons, type-accent Supporting: preview blocks aligned to the new layout; autolayout handle Y at vertical center; copilot/diff/comparison include Reviewed by Cursor Bugbot for commit 2f51ecc. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryThe PR redesigns workflow cards around an SVG-painted border and horizontal connection affordances.
Confidence Score: 4/5The realtime edge path needs handle canonicalization before merge to prevent duplicate logical connections during mixed-client sessions. The new source-handle normalization collapses legacy left outputs only in selected client, rendering, and persistence paths, while realtime insertion and duplicate comparison retain raw handles and can accept both left and right representations of one connection. Files Needing Attention: packages/workflow-types/src/workflow.ts and apps/realtime/src/database/operations.ts
|
| Filename | Overview |
|---|---|
| packages/workflow-renderer/src/workflow-block/workflow-block-border.tsx | Introduces the sampled-perimeter SVG border renderer, bulge composition, pointer tracking, and spring animation used by workflow cards. |
| packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx | Rebuilds the shared workflow-card view around the painted border, integrated handles, deterministic layout, error output, and action swell. |
| apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx | Integrates the redesigned renderer with live canvas state, connection behavior, block actions, and error-output controls. |
| apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx | Normalizes connection handles, updates connection drag behavior and edge layering, and adds measured-node centering and keyboard navigation. |
| packages/workflow-types/src/workflow.ts | Adds shared handle normalization helpers, but realtime ingress and duplicate checks do not apply the same positioned-handle canonicalization. |
| packages/workflow-persistence/src/load.ts | Normalizes legacy positioned handles while loading workflows, exposing duplicate raw handles accepted during mixed-client realtime sessions. |
| packages/workflow-persistence/src/save.ts | Canonicalizes positioned handles before workflow edges are persisted through this save path. |
| apps/realtime/src/database/operations.ts | Persists realtime edge handles without applying the new positioned-handle canonicalization, allowing logically duplicate left/right outputs. |
| apps/sim/executor/execution/edge-manager.ts | Recognizes positioned source handles as ordinary successful output routes while preserving special routing handles. |
| apps/sim/lib/workflows/blocks/deterministic-dimensions.ts | Updates deterministic card-height calculations to match the redesigned rendered content. |
| packages/emcn/src/components/tooltip/tooltip.tsx | Extends tooltip positioning behavior needed by controls placed above the new action swell. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Create[Create workflow block] --> Store[Workflow store]
Store --> Measure[Mount and measure node]
Measure --> Center[Center canvas on new block]
Store --> Card[Workflow block view]
Card --> Border[SVG border renderer]
Border --> Input[Left or right input knob]
Border --> Output[Right output knob]
Output --> Normalize[Normalize source handle]
Normalize --> Edge[Workflow edge]
Edge --> Persist[Realtime and persistence]
Edge --> Execute[Executor edge routing]
Error[Error-output toggle] --> Card
Error --> Persist
Reviews (1): Last reviewed commit: "fix(workflow): keep outputs on the right..." | Re-trigger Greptile
| export function normalizePositionedSourceHandleId<T extends string | null | undefined>( | ||
| handle: T | ||
| ): T | PositionedSourceHandleId { | ||
| return handle === 'source-top' || handle === 'source-bottom' || handle === 'source-left' | ||
| ? getPositionedSourceHandleId('right') | ||
| : handle |
There was a problem hiding this comment.
Realtime bypasses handle canonicalization
During a mixed-client session, an older client can still submit source-left while a current client submits source-right; the realtime database and duplicate-edge paths retain and compare those raw values, so the same logical connection is accepted twice even though this normalization later renders and reloads both as the right-side output.
Knowledge Base Used:
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2f51ecc. Configure here.
| routerRowCount = 0, | ||
| chipCount = 0, | ||
| sentenceLineCount = 0, | ||
| hasErrorRow = false, |
There was a problem hiding this comment.
Autolayout height ignores new card layout
Medium Severity
estimateWorkflowBlockDimensions still calls calculateWorkflowBlockDimensions without hasErrorRow, chipCount, or sentenceLineCount. Those used to be derived inside the helper from category / displayTriggerMode, but that logic was removed, so autolayout underestimates nearly every non-trigger card and diverges from the painted canvas height.
Reviewed by Cursor Bugbot for commit 2f51ecc. Configure here.
| reactFlowInstance.setCenter(position.x + nodeWidth / 2, position.y + nodeHeight / 2, { | ||
| zoom: targetZoom, | ||
| duration: 500, | ||
| }) |
There was a problem hiding this comment.
Short cards center on inflated height
Medium Severity
focusBlockInView centers using React Flow node.height, which is still clamped to MIN_HEIGHT (100), while painted card height can now be as low as MIN_PAINTED_HEIGHT (48). Header-only and other short cards are therefore centered too low when created, clicked, or arrow-navigated.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 2f51ecc. Configure here.


Redesigns the workflow block card and its connection affordances. The card outline is now drawn by an SVG border renderer instead of CSS, and connection ports are painted into that outline as "knobs" rather than being separate DOM nubs.
Why
The old card exposed each port as a small fixed nub you had to aim at. This replaces that with a single continuous outline where a hover swell follows your cursor along the border, so a connection can be started from anywhere on the card edge.
How the border works
workflow-block-border.tsx(new) walks the rounded rect once and stores samples of the perimeter, each with its arc position, point, outward normal, and tangent. That turns the 2D outline into a 1D number line, so every port and the cursor swell is just a center position plus a bulge profile (plateau, shoulder, peak) along it — corners stop being a special case.Overlapping bulges combine with
max, not sum, so two shapes merge into one silhouette at constant height instead of spiking where they cross. Motion runs on springs stepped by a rAF loop that stops itself when everything is at rest, so an idle canvas costs no frames.Connection semantics
Unchanged from before: a port is either an input (receive-only) or an output (send-only), and fan-in is unlimited — a card can take any number of incoming lines on its input.
What changed is reach:
source-leftcollapses tosource-righton load / render / connect so data from the API, an older client, or a stale save self-heals.target-left/target-right), resolved by which half of the card you drop on.Also in here
bgColor.Testing
bunx vitest run lib/workflows/blocks/ stores/workflows/workflow/ executor/execution/edge-manager.test.ts executor/dag/construction/edges.test.ts— 225 passing.workflow-block-border-mount.test.tsxcovers the border mounting with a coloured knob plus an action-menu port, and pins the handle-normalization rules. It exists because a temporal-dead-zone bug in the knob paint path threw only when a card had a coloured knob — invisible on an idle canvas, fatal the moment a connection was created.Worth a reviewer's eye
horizontalHandlesis now vestigial. It used to flip a card's input/output to top/bottom; the renderer no longer reads it, but the property is still persisted, synced over the realtime protocol, and undo/redo-able. Nothing is visibly broken (no UI sets it, no workflow in use has it), but it should either be finished off or restored deliberately.MIN_PAINTED_HEIGHTfloor rather than a root-cause fix.🤖 Generated with Claude Code