Skip to content

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

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

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

Conversation

@andresdjasso

@andresdjasso andresdjasso commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Redesigns the workflow block card: a single SVG border that follows the cursor along the card edge, connection knobs that swell out of that border, and an action menu that rises out of the top edge instead of floating above it.

What changed

Border renderer (workflow-block-border.tsx, new) — the card outline is one path. The rounded-rect perimeter is flattened into a 1D arc-length space; every port is a bulge on that line. Bulges combine with max (union), never sum, so overlapping swells merge instead of spiking. Springs drive the cursor swell and the action-menu tab; the rAF loop self-terminates at rest.

Connection model — outputs always leave from the right. A drag begun on the left edge still starts a connection, but its anchor normalizes to source-right; anchoring one on the left would put an outgoing line on the input port. Inputs remain unlimited fan-in. Legacy source-top/source-bottom/source-left collapse to source-right on persistence.

Error output moved to the bottom edge, centered under the error toggle.

Type tags now use fixed brand values (hex, not derived) with two inks — #F8F8F8 on dark fills, #1A1A1A on light. Tone names match what they render.

Deploy / Run take two new Button variants (graphiteSubtle, graphite) rather than className overrides, so tertiary stays green everywhere else.

Card height bug (latest commit)

The Start card intermittently collapsed after load, squashing the action-menu tab so its icon row sat over the card.

.workflow-drag-handle is the host the border measures, and both it and the header row took their height from blockHeight && blockHeight > 0. blockHeight is already floored at MIN_PAINTED_HEIGHT (48) by the deterministic-dimensions pass — but it is absent on the first frames, and with no floor the host collapsed to its natural content height (25.5px for a header-only trigger, exactly the title's line box). The border builds its perimeter from host.offsetHeight, so that window painted a sub-floor card with too little straight edge left for the action-menu tab.

Whether you saw it depended purely on whether the dimension publish had landed — which is why it reproduced on one workflow and not another. Floored in all three places.

Known accessibility exceptions

green (2.55:1) and orange (3.15:1) sit under WCAG AA against their paired ink. Both are deliberate brand decisions, documented in chip-tag.tsx. The label is always duplicated by an icon and the block name beside it, so the tag is a redundant cue rather than the sole carrier — but these pairings should not be reused anywhere a label stands alone.

Needs review / hand-off

  1. Merge conflict in apps/sim/lib/workflows/autolayout/core.ts — the only real conflict; everything else auto-merges. Staging refactored getSourceHandleYOffset to take a GraphNode and read node.metrics.height instead of raw block.height. This branch changed the same function to anchor side ports at the block's vertical center and to return the full height for the error handle (which moved to the bottom edge, so there is no ERROR_BOTTOM_OFFSET to subtract).

    Recommended resolution: take staging's signature and height source, then apply it to this branch's semantics — node.metrics.height / 2 for centered side ports, node.metrics.height for the error handle, subflow containers keeping DEFAULT_Y_OFFSET. Left unresolved deliberately: a wrong call here silently mis-places autolayout edges, and that is best verified by whoever owns autolayout.

  2. Canvas performance — the border renderer runs a spring loop per card. It self-terminates at rest and the foreign-drag listener is frame-coalesced, but this has not been profiled on a large graph under sustained interaction. Worth a pass on drag, multi-select, and hover across a few hundred blocks.

  3. horizontalHandles is vestigial — still persisted, socket-synced and undo/redo-able, but the renderer ignores it. The baseline honored it. Either wire it back up or remove the field.

  4. Sub-48px cardsMIN_PAINTED_HEIGHT is a floor, not a fix. The border produces no usable path below it. Root-causing that would remove the need for the floor entirely.

  5. Toolbar and search-modal block colors are unchanged — they read bgColor from each block definition and draw a white glyph, so adopting the new palette needs the fill+ink pairing rather than a swap (three of the new colors are light).

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

High Risk
Large cross-cutting change to workflow editing, persistence, realtime sync, and execution routing; connection-handle normalization and error-branch behavior can affect existing graphs and collaborative edits.

Overview
Redesigns workflow block cards around an SVG border renderer with edge-following connection swells and knobs, replacing fixed handle nubs. Connections normalize to horizontal-only semantics (outputs on the right, inputs on left/right drop targets, error on the bottom), with legacy vertical/source-left handles collapsed on load and connect.

Adds a persisted errorEnabled flag with realtime UPDATE_ERROR_ENABLED, an in-card error row/toggle, and automatic removal of error edges when the branch is turned off. Executor and run-path logic treat positioned source handles like success paths.

Collapsed-node preview gains type-accent ChipTags, natural-language sentence summaries with inline chips, meta rows, and tighter height math. The action bar moves to a swell variant on the card; flip handles is removed from the bar and context menu. Selection rings and connected edges use neutral --text-secondary highlighting (including editor-open endpoints); edges no longer stack above cards.

Canvas behavior: camera focus on create/click, arrow-key block navigation, connection-line styling via data attributes, and deploy/run graphite button variants. Tooltips support preferAbove for bars near card tops.

Reviewed by Cursor Bugbot for commit ace5467. 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:

Comment thread apps/sim/lib/workflows/blocks/deterministic-dimensions.ts
Comment thread apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
The Start card intermittently collapsed after load, squashing the
action-menu tab so its icon row sat over the card.

`.workflow-drag-handle` is the host the border renderer measures, and both
it and the header row took their height from `blockHeight && blockHeight >
0`. `blockHeight` comes from the deterministic-dimensions pass and is
already floored at MIN_PAINTED_HEIGHT (48), but it is absent on the first
frames — and with no floor the host collapsed to its natural content
height (25.5px for a header-only trigger, exactly the title's line box).
The border builds its perimeter from `host.offsetHeight`, so that window
painted a sub-floor card: too little straight edge remained on the
vertical runs for the action-menu tab, which collapsed into the corner
arcs. Whether you saw it depended purely on whether the dimension publish
had landed, which is why it reproduced on one workflow and not another.

Floor all three: the host, the header row (so `items-center` centres the
title and type tag rather than pinning them to the top), and the border's
own `offsetHeight` read.

Also raise ACTION_MENU_CONTENT_READY_THRESHOLD to 0.9. At 0.8 the 24px
icon row was revealed while the swell had only reached 22.4px of its 28px
— shorter than the row it contains. Secondary to the above, but a real
overflow window on its own. The test now pins the ratio rather than the
constant.

Tag palette moves to fixed brand values (hex, not derived oklch) with two
inks — #F8F8F8 on dark fills, #1A1A1A on light. Tones are renamed to match
what they render. `green` (2.55:1) and `orange` (3.15:1) sit under WCAG AA
against their paired ink; both are deliberate brand decisions and are
documented in the component.

Deploy and Run take two new Button variants rather than className
overrides, so `tertiary` stays green everywhere else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@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 high 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.

Reviewed by Cursor Bugbot for commit ace5467. Configure here.

}
},

setBlockErrorEnabled: (id: string, errorEnabled: boolean) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Error toggle lost on paste

Medium Severity

errorEnabled is not carried through duplicate/paste. batchAddBlocks rebuilds each block without copying it, setBlockErrorEnabled updates only the top-level flag and not data.errorEnabled, and server BATCH_ADD writes data without merging the top-level value the way full save does. A block with Error output on loses that setting when duplicated or pasted.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ace5467. Configure here.

if (!handleId?.startsWith(prefix)) return handleId

return getPositionedSourceHandleId('right')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicate success edge handles

Medium Severity

Success outputs can be stored under both source and source-right. The interactive knob still emits source, while cursor-swell drags normalize to source-right. Uniqueness treats those as different handles, so the same source-to-target success link can be created twice and both fire on success.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ace5467. 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