Skip to content

feat(workflow-renderer): extract pure WorkflowBlockView + SubBlockRowView#5267

Merged
waleedlatif1 merged 2 commits into
stagingfrom
feat/workflow-block-view
Jun 29, 2026
Merged

feat(workflow-renderer): extract pure WorkflowBlockView + SubBlockRowView#5267
waleedlatif1 merged 2 commits into
stagingfrom
feat/workflow-block-view

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Summary

Completes the workflow renderer by extracting the canvas block node — the most complex one — into pure, props-driven Views in @sim/workflow-renderer, matching the edge/subflow/note pattern from #5263.

  • SubBlockRowView — the collapsed block's subblock summary row ({ title, displayValue?, isMonospace? }). The ~9 selector-name hydration hooks (credentials, knowledge bases, tables, MCP servers/tools, sub-workflows, skills, variables) stay in the row container behind its memo comparator; the view receives only resolved strings.
  • WorkflowBlockView — the block shell (header, icon, all badges, dynamic condition/router/error handles, ring). The editor node becomes a thin container that resolves stores/hooks/permissions, builds the subblock rows + actionBar slots, and binds wouldCreateConnectionCycle (reads the edge store fresh per call, preserving cycle-prevention for collaborators). config.icon/bgColor, the webhook provider name, and every visual flag cross as props. Container drops 1137 → 829 lines.

Visual output and behavior are unchanged — verified by an independent adversarial byte-identical audit (every handle id/class/style/offset, all four badges with their guards, isValidConnection polarity, and the ring all confirmed identical).

Type of Change

  • Refactor (no functional/visual change)

Testing

  • apps/sim + @sim/workflow-renderer typecheck 0 errors; monorepo boundaries + biome clean. The View imports only react/@sim/emcn/reactflow/../dimensions/../types — nothing app-coupled.
  • Independent byte-identical audit of the View + container vs the original render: zero behavior/visual differences.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…s summary row

Splits the canvas block's collapsed subblock summary row into a pure SubBlockRowView (title + resolved displayValue + monospace flag) in @sim/workflow-renderer. The ~9 selector-name hydration hooks stay in the SubBlockRow container behind its memo comparator; the view receives only resolved strings. Byte-identical row JSX. First step toward the full WorkflowBlockView.
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 29, 2026 7:21pm

Request Review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@cursor

cursor Bot commented Jun 29, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large UI move touching React Flow handle wiring and cycle checks on the main canvas block; behavior should be identical but regressions would affect editing and connections.

Overview
Moves workflow canvas block presentation into @sim/workflow-renderer, following the same props-driven pattern as edges, subflows, and notes.

SubBlockRowView holds the collapsed subblock title/value row markup; the editor SubBlockRow container still runs selector hydration and passes resolved strings only. WorkflowBlockView owns the block shell—header, badges, React Flow handles (including condition/router/error topology), and slots for actionBar and rows. WorkflowBlock now wires stores, hooks, permissions, and the wouldCreateConnectionCycle guard (fresh edge reads per validation) into that view.

Exports are added from packages/workflow-renderer/src/index.ts. Intended as a structural move with unchanged visuals and connection behavior.

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

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

Moves the canvas block's render (header, badges, dynamic handles, ring) into a pure WorkflowBlockView in @sim/workflow-renderer. WorkflowBlock becomes a thin container that resolves all stores/hooks/permissions, builds the subblock rows + actionBar slots, and binds wouldCreateConnectionCycle (reads the edge store fresh per call to preserve cycle prevention). getHandleClasses/getHandleStyle move into the view; config.icon/bgColor, the webhook provider name, and every visual flag cross as props. Byte-identical JSX — every handle id/class/style/offset and badge guard preserved (verified by an independent adversarial audit). Container drops from 1137 to 829 lines.
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts WorkflowBlockView and SubBlockRowView as pure, props-driven renderers into @sim/workflow-renderer, matching the prior edge/subflow/note extraction pattern. The editor container thins from ~1137 to ~829 lines by delegating all JSX to the view layer.

  • SubBlockRowView is a simple stateless row renderer; the container's SubBlockRow memo now delegates to it, passing only resolved strings.
  • WorkflowBlockView receives every visual flag, badge state, handle topology, and slot (rows, actionBar) as props. Cycle-prevention is handled by wouldCreateConnectionCycle, which reads fresh edge state per call via useWorkflowStore.getState().edges — the double-negation bug noted in prior review was corrected in HEAD and the logic is now identical to the original.
  • Package boundaries are respected: the new view imports only react, @sim/emcn, reactflow, ../dimensions, and ../types.

Confidence Score: 5/5

Safe to merge — pure structural refactor with no behavioral changes; cycle-prevention logic is correctly ported and the prior double-negation was fixed in HEAD.

Every isValidConnection callback in the view applies !wouldCreateConnectionCycle(...), and the container defines wouldCreateConnectionCycle as a direct forward to wouldCreateCycle(...) with no extra negation — identical to the original inlined calls. All badge state (schedule, webhook, child-deploy) is preserved faithfully. Package boundaries are clean.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx Container refactored to delegate all rendering to WorkflowBlockView; wouldCreateConnectionCycle correctly forwards to wouldCreateCycle without extra negation, preserving cycle-prevention semantics.
packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx New pure renderer for workflow block shells; isValidConnection callbacks correctly negate wouldCreateConnectionCycle (which itself returns true when a cycle would form), matching the original behaviour exactly.
packages/workflow-renderer/src/workflow-block/sub-block-row-view.tsx Minimal stateless row renderer extracted from SubBlockRow; faithful pixel-identical port of the original JSX with no coupling to stores or queries.
packages/workflow-renderer/src/index.ts Exports SubBlockRowView and WorkflowBlockView with their prop types; straightforward barrel update.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    WB["WorkflowBlock (container)\nmemo + shouldSkipBlockRender"]

    subgraph stores["Zustand / Hooks"]
        WFS["useWorkflowStore\n(edges, subBlockState, …)"]
        PERMS["useUserPermissions"]
        SCHED["useScheduleInfo"]
        WEBHOOK["useWebhookInfo"]
        CHILD["useChildWorkflow"]
    end

    subgraph computed["Computed in container"]
        CYCLE["wouldCreateConnectionCycle\n(source, target) →\nwouldCreateCycle(freshEdges, …)"]
        ROWS["rows JSX\n(SubBlockRow elements)"]
        ABAR["actionBar slot\n(ActionBar | undefined)"]
        BADGES["badge state\n(scheduleIsDisabled,\nwebhookProviderName, …)"]
    end

    subgraph view["@sim/workflow-renderer (pure)"]
        WBV["WorkflowBlockView\nprops-driven, no stores"]
        SBRV["SubBlockRowView\n(title, displayValue, isMonospace)"]
        HANDLES["Handle topology\n(target / source / condition /\nrouter / error handles)"]
    end

    WB --> stores
    stores --> computed
    computed -->|"props"| WBV
    WBV --> HANDLES
    WBV -->|"renders"| SBRV
    ROWS -->|"rows slot"| WBV
    ABAR -->|"actionBar slot"| WBV
    CYCLE -->|"wouldCreateConnectionCycle prop"| WBV
    HANDLES -->|"isValidConnection:\n!wouldCreateConnectionCycle(src, tgt)"| WFS
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    WB["WorkflowBlock (container)\nmemo + shouldSkipBlockRender"]

    subgraph stores["Zustand / Hooks"]
        WFS["useWorkflowStore\n(edges, subBlockState, …)"]
        PERMS["useUserPermissions"]
        SCHED["useScheduleInfo"]
        WEBHOOK["useWebhookInfo"]
        CHILD["useChildWorkflow"]
    end

    subgraph computed["Computed in container"]
        CYCLE["wouldCreateConnectionCycle\n(source, target) →\nwouldCreateCycle(freshEdges, …)"]
        ROWS["rows JSX\n(SubBlockRow elements)"]
        ABAR["actionBar slot\n(ActionBar | undefined)"]
        BADGES["badge state\n(scheduleIsDisabled,\nwebhookProviderName, …)"]
    end

    subgraph view["@sim/workflow-renderer (pure)"]
        WBV["WorkflowBlockView\nprops-driven, no stores"]
        SBRV["SubBlockRowView\n(title, displayValue, isMonospace)"]
        HANDLES["Handle topology\n(target / source / condition /\nrouter / error handles)"]
    end

    WB --> stores
    stores --> computed
    computed -->|"props"| WBV
    WBV --> HANDLES
    WBV -->|"renders"| SBRV
    ROWS -->|"rows slot"| WBV
    ABAR -->|"actionBar slot"| WBV
    CYCLE -->|"wouldCreateConnectionCycle prop"| WBV
    HANDLES -->|"isValidConnection:\n!wouldCreateConnectionCycle(src, tgt)"| WFS
Loading

Reviews (4): Last reviewed commit: "feat(workflow-renderer): extract pure Wo..." | Re-trigger Greptile

@waleedlatif1 waleedlatif1 force-pushed the feat/workflow-block-view branch from 16ba04e to 2b56e82 Compare June 29, 2026 19:21
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extracts the workflow canvas block node into two pure, props-driven view components — WorkflowBlockView and SubBlockRowView — inside @sim/workflow-renderer, following the pattern established for edges, subflows, and notes. The container (workflow-block.tsx) shrinks significantly by delegating all rendering to the new views.

  • SubBlockRowView: clean extraction of the collapsed row JSX; no behavioral differences.
  • WorkflowBlockView: faithful extraction of the block shell, handles, and badges. The rows and actionBar slots are injected by the container.
  • wouldCreateConnectionCycle bug: the container wraps wouldCreateCycle in a negation (!wouldCreateCycle(...)), producing a function that returns true when no cycle would be created. The view then negates that result again (!wouldCreateConnectionCycle(...)), so every isValidConnection callback now returns true when a cycle would be created — the exact opposite of the original. Removing the ! from the container definition restores the original semantics.

Confidence Score: 2/5

Not safe to merge — the cycle-prevention guard on every workflow edge handle is now inverted, allowing connections that form cycles and blocking valid ones.

The double-negation in wouldCreateConnectionCycle means users can drag edges between blocks and form circular workflows that would have been rejected before. This affects the default source/target handle, every condition handle, every router handle, and the error handles — all connection points in the canvas. The rest of the extraction (SubBlockRowView, badges, slots) is correct and clean.

workflow-block.tsx lines 697–698: the definition of wouldCreateConnectionCycle incorrectly negates wouldCreateCycle, which is negated a second time inside WorkflowBlockView, reversing the cycle check on every handle.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx Container refactored to delegate rendering to WorkflowBlockView; introduces an inverted cycle-prevention predicate that reverses the isValidConnection logic on every handle.
packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx New pure view component for the block shell; correctly calls !wouldCreateConnectionCycle(...) on every handle — the bug originates in the container's definition of that function, not here.
packages/workflow-renderer/src/workflow-block/sub-block-row-view.tsx New pure view for the collapsed subblock row; faithful extraction of the original JSX with no behavioral changes.
packages/workflow-renderer/src/index.ts Re-exports the two new views; no issues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant RF as ReactFlow
    participant WBV as WorkflowBlockView
    participant WB as WorkflowBlock (container)
    participant Store as useWorkflowStore

    RF->>WBV: isValidConnection(connection)
    WBV->>WBV: "if connection.source/target === id → false"
    WBV->>WB: wouldCreateConnectionCycle(source, target)
    WB->>Store: getState().edges
    Store-->>WB: edges[]
    WB->>WB: !wouldCreateCycle(edges, source, target)
    Note over WB: BUG: returns true when NO cycle exists
    WB-->>WBV: bool (inverted)
    WBV->>WBV: !wouldCreateConnectionCycle(...)
    Note over WBV: double-negation → accepts cycles
    WBV-->>RF: bool (inverted result)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant RF as ReactFlow
    participant WBV as WorkflowBlockView
    participant WB as WorkflowBlock (container)
    participant Store as useWorkflowStore

    RF->>WBV: isValidConnection(connection)
    WBV->>WBV: "if connection.source/target === id → false"
    WBV->>WB: wouldCreateConnectionCycle(source, target)
    WB->>Store: getState().edges
    Store-->>WB: edges[]
    WB->>WB: !wouldCreateCycle(edges, source, target)
    Note over WB: BUG: returns true when NO cycle exists
    WB-->>WBV: bool (inverted)
    WBV->>WBV: !wouldCreateConnectionCycle(...)
    Note over WBV: double-negation → accepts cycles
    WBV-->>RF: bool (inverted result)
Loading

Reviews (2): Last reviewed commit: "feat(workflow-renderer): extract pure Wo..." | Re-trigger Greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

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

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile re-review — the cycle-prevention double-negation was fixed in the current HEAD (2b56e824c0); the prior score reviewed the pre-fix code. The container now returns wouldCreateCycle(...) (no leading !), so each handle's !wouldCreateConnectionCycle(...) composes to the original !wouldCreateCycle(...).

@waleedlatif1 waleedlatif1 merged commit c7bb37d into staging Jun 29, 2026
16 checks passed
@waleedlatif1 waleedlatif1 deleted the feat/workflow-block-view branch June 29, 2026 20:06
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