Skip to content

improvement(workflow): redesigned block cards with edge-following connection swell - #5976

Open
andresdjasso wants to merge 2 commits into
stagingfrom
workflow-updates
Open

improvement(workflow): redesigned block cards with edge-following connection swell#5976
andresdjasso wants to merge 2 commits into
stagingfrom
workflow-updates

Conversation

@andresdjasso

Copy link
Copy Markdown
Contributor

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:

  • A temporary source handle tracks the cursor, so a drag can start anywhere on the border rather than only at the right-hand nub.
  • Outputs always leave from the right. The swell lets a drag start on the left, but the left side is the input, so anchoring an output there would draw an outgoing line out of the input port. Every drag resolves to the right anchor, and source-left collapses to source-right on load / render / connect so data from the API, an older client, or a stale save self-heals.
  • Inputs can terminate on either side (target-left / target-right), resolved by which half of the card you drop on.
  • Connections are horizontal-only; top and bottom are not anchor sides. The error output is the one exception, pinned to the bottom edge under its toggle.

Also in here

  • Error output moved into the card as a row with a toggle; the knob sits beneath it and is styled like every other connection knob.
  • Deterministic card heights so the painted silhouette matches the rendered content exactly.
  • Action bar redesigned as a swell that opens above the card, drawn over any knob or line beneath it.
  • 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 badge instead of the legacy per-integration 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.
  • New workflow-block-border-mount.test.tsx covers 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.
  • App, renderer, and types typecheck clean; biome clean.
  • Exercised on a 50-block canvas covering every card variant (router, multi-branch conditions, loop and parallel containers, error branches, notes).

Worth a reviewer's eye

  • horizontalHandles is 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.
  • The border renderer produces no path below roughly 48px of card height, worked around with a MIN_PAINTED_HEIGHT floor rather than a root-cause fix.

🤖 Generated with Claude Code

andresdjasso and others added 2 commits July 24, 2026 17:07
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>
@andresdjasso
andresdjasso requested a review from a team as a code owner July 27, 2026 17:37
@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches workflow persistence, realtime sync, and execution edge routing alongside a large canvas UI refactor; legacy handle normalization should self-heal but affects how existing edges render and route.

Overview
Redesigns workflow block cards so the outline is drawn by a new SVG border renderer with edge-following connection swells and an action bar in a swell variant above the card, instead of floating CSS nubs and a separate horizontal/vertical port toggle (removed from the action bar and context menu).

Connection model changes: outputs always anchor on the right; inputs on the left (or left/right on drop); legacy source-top/source-left etc. are normalized on connect, load, and save. Edges highlight when an endpoint is selected or open in the editor; z-index no longer lifts lines above cards. Canvas adds focus-on-create/click, arrow-key block navigation, and updated selection/connection-line styling.

Error branch: blocks get errorEnabled (store, API, realtime UPDATE_ERROR_ENABLED, persistence in block data). The card exposes a toggle; disabling removes error edges. The error port stays bottom-centered; executor/edge-manager treat positioned source handles like success paths (!output.error).

Card content: natural-language summaries and chip rows for key block types, meta-row icons, type-accent ChipTag badges (editor header, preview, search), stricter “only show rows with values,” and updated deterministic heights (chips, sentence lines, error row).

Supporting: preview blocks aligned to the new layout; autolayout handle Y at vertical center; copilot/diff/comparison include errorEnabled; new border mount tests and edge-manager tests for positioned handles.

Reviewed by Cursor Bugbot for commit 2f51ecc. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR redesigns workflow cards around an SVG-painted border and horizontal connection affordances.

  • Adds cursor-following border swells, integrated connection knobs, error-output controls, and a swell-based action bar.
  • Normalizes positioned output handles to the right while retaining left/right input anchors across rendering and persistence.
  • Introduces deterministic card dimensions and updates preview, editor, autolayout, and edge rendering to match.
  • Centers newly added blocks after measurement and adds keyboard navigation between workflow blocks.
  • Extends shared workflow types and renderer components supporting the redesigned cards.

Confidence Score: 4/5

The 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

Important Files Changed

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
Loading

Reviews (1): Last reviewed commit: "fix(workflow): keep outputs on the right..." | Re-trigger Greptile

Comment on lines +286 to +291
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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:

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2f51ecc. Configure here.

reactFlowInstance.setCenter(position.x + nodeWidth / 2, position.y + nodeHeight / 2, {
zoom: targetZoom,
duration: 500,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2f51ecc. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant