Skip to content

fix(canvas): stop phantom ports and a latched-open action bar - #6688

Merged
waleedlatif1 merged 2 commits into
stagingfrom
fix/canvas-port-hover-affordances
Aug 14, 2026
Merged

fix(canvas): stop phantom ports and a latched-open action bar#6688
waleedlatif1 merged 2 commits into
stagingfrom
fix/canvas-port-hover-affordances

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Two canvas hover bugs from Slack.

Ports that appear where no edge can be made

Hovering a card raises a swell on its border that reads as a connection port. The swell was raised without regard for whether a handle exists behind it:

  • A Response block mounts no source handle, so hovering its edge raised a knob no edge could ever leave from.
  • A trigger mounts no target handle, yet still swelled under a connection dragged from another card — offering a drop it cannot accept.

The two contexts are separate (your own hover is a source affordance; a swell raised while another card is the drag origin is a target affordance), so they are now gated separately on the handle that actually backs each one. A trigger's own swell is additionally limited to its source edge, matching what the subflow start node already does — so it no longer raises a knob on the edge where every other card shows its input.

Action bar staying open after leaving via a port

Leaving a card arms a 100ms retract and installs a pointermove listener to track the pointer across the gap up to the bar, which floats above the card. Re-entering the bar's hover band called openHover() — which cancelled the retract and removed that listener. Since no further pointerleave can arrive once the pointer is off the node, nothing was left to close the bar and it latched open. The listener now stays installed and the retract is re-armed when the pointer moves back out.

Same gesture also left a knob puffed: only the in-band path recomputed the magnetized port, so leaving the tracking band upward onto the action bar pinned the last one at hover amplitude with the pointer nowhere near it.

Type of Change

  • Bug fix

Testing

Added use-action-menu-swell.test.tsx covering both directions of the gap crossing — retracts when the pointer carries on past the bar, stays open when it rests on it. Verified it fails against the old behavior and passes with the fix. Full renderer suite (72) and canvas suite (349) green; type-check and lint clean.

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)

Ports surface on hover from a swell painted on the card border, and that
swell was raised with no regard for whether a handle exists behind it. A
Response block mounts no source handle, so hovering its edge raised a
knob no edge could ever leave from. A trigger mounts no target handle,
yet still swelled under a connection dragged from another card, offering
a drop it cannot accept. Gate each direction on the handle that backs it,
and limit a trigger's own swell to its source edge the way the subflow
start node already does.

The action bar latched open for the same interaction. Leaving the card
arms a retract and installs a pointermove listener to track the pointer
across the gap up to the bar; re-entering the bar's band called
openHover(), which cancelled the retract AND removed that listener. No
further pointerleave can arrive once the pointer is off the node, so
nothing was left to close the bar. Keep the listener installed and re-arm
the retract when the pointer moves back out.

Also clear the magnetized port when the pointer leaves the tracking band
onto the action bar: only the in-band path recomputed it, so the last
knob stayed pinned at hover amplitude with the pointer nowhere near it.
@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Aug 14, 2026 12:49am

Request Review

@cursor

cursor Bot commented Aug 14, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Canvas interaction and visual affordance fixes in the workflow renderer with targeted tests; no auth, data, or API changes.

Overview
Fixes two workflow canvas hover bugs: phantom connection ports on cards without backing handles, and an action bar that stayed open after crossing the gap to it.

Border swells now respect whether a card can actually start or receive connections via new canStartConnection / canReceiveConnection on WorkflowBlockBorder, wired from supportsCursorHandle (e.g. Response) and shouldShowDefaultHandles (e.g. triggers without inputs). Pointer-following swell tracking is disabled when the corresponding capability is false, and leaving the edge band onto the action bar clears the magnetized port so a knob does not stay puffed with the pointer elsewhere.

Action menu swell (useActionMenuSwell) no longer tears down the gap-crossing pointermove listener when the pointer briefly re-enters the bar’s hover band; it cancels only the retract timer and re-arms leave when the pointer moves out again, so the bar closes after the user continues past the bar.

Adds jsdom tests for both behaviors.

Reviewed by Cursor Bugbot for commit 961df01. Configure here.

@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 1 potential issue.

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 ef0e5a0. Configure here.

Comment thread packages/workflow-renderer/src/workflow-block/workflow-block-border.tsx Outdated
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR corrects canvas hover affordances and action-menu retraction behavior.

  • Gates source and target border swells according to the handles each block supports.
  • Keeps pointer tracking active while crossing the action-menu gap and re-arms closure after leaving its hover band.
  • Clears stale magnetized-port state and adds regression coverage for connectability and menu retraction.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/workflow-renderer/src/workflow-block/use-action-menu-swell.ts Separates timeout cancellation from listener cleanup so pointer tracking can close the action menu after leaving its hover band.
packages/workflow-renderer/src/workflow-block/workflow-block-border.tsx Gates cursor swells by connection capability and clears stale magnetized-port state outside the tracking band.
packages/workflow-renderer/src/workflow-block/workflow-block-view.tsx Supplies source and target connection capabilities based on the handles rendered by each block type.
packages/workflow-renderer/src/workflow-block/use-action-menu-swell.test.tsx Adds regression coverage for carrying the pointer past the action bar and resting within its hover band.
packages/workflow-renderer/src/workflow-block/workflow-block-border-connectable.test.tsx Adds coverage ensuring unsupported source and target swells are not offered.

Reviews (2): Last reviewed commit: "fix(canvas): scope the receive gate, cov..." | Re-trigger Greptile

Comment thread packages/workflow-renderer/src/workflow-block/use-action-menu-swell.test.tsx Outdated
Gating the foreign-drag listener also ran the shared pointer-tracking
reset, which belongs to the card's own hover. A trigger sets
canReceiveConnection false while canStartConnection stays true, so the
reset undid the layout effect's :hover bootstrap and left a card that
mounted under the pointer with no source swell until the pointer left
and came back. Skip the listener instead; the effect's own cleanup
already covers a true-to-false flip.

Drop the trigger-only cursorSwellSides restriction. A swell on a
trigger's input edge resolves to a source handle, so an edge genuinely
can be made there — it was a behavior change beyond the bug, not a
phantom.

Cover both directions of the swell gate, and use the shared sleep helper
in the action-bar test. Hoist the constant connection sides out of the
render body so they stop riding the borderPorts dep array.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@cursor review

@waleedlatif1
waleedlatif1 merged commit b9b9c9c into staging Aug 14, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/canvas-port-hover-affordances branch August 14, 2026 00:50
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