QOL: Arrow bending and termination#3717
Conversation
…re where clicked arrows remain highlighted
…fferent types of arrows
There was a problem hiding this comment.
Code Review
This pull request introduces an arrow filtering system for the CSE Machine visualization, allowing users to toggle arrow visibility based on origin (e.g., text, frame, function). It also refines arrow routing logic, improves path rendering with better corner radius calculations, and adds a new arrow type for function descriptions. Key feedback includes addressing performance issues caused by object instantiation within draw methods, replacing any types with proper interfaces for type safety, simplifying redundant routing logic, and moving magic numbers to the configuration file.
| if (this.enclosingFrame) { | ||
| this._arrow = new ArrowFromFn(this).to(this.enclosingFrame) as ArrowFromFn; | ||
| } | ||
| this.createBodyArrow(); |
There was a problem hiding this comment.
Instantiating FnBodyTarget and ArrowFromFnToBody inside the draw method is a significant performance issue. Since draw is called on every render, and GenericArrow creates a new React.createRef() in its constructor, this will cause the arrow component to unmount and remount on every update, leading to flickering and high CPU usage. These objects should be instantiated once (e.g., in the constructor or handleNewReference) and updated only when coordinates change.
| if (Layout.globalEnvNode.frame) { | ||
| this._arrow = new ArrowFromFn(this).to(Layout.globalEnvNode.frame) as ArrowFromFn; | ||
| } | ||
| this.createBodyArrow(); |
| isStringIdentifiable: boolean; | ||
| faded: boolean; | ||
| hidden: boolean; | ||
| parentFrame?: any; // Reference to the frame this text belongs to |
There was a problem hiding this comment.
@RichDom2185 has also advised me in another of my recent PRs to not use the 'any' type in general. The exact comment with his message is here.
It might be outdated! Do expand the "outdated" comment tabs
| if (to instanceof ArrayValue) { | ||
| // Route text-to-array arrows with Manhattan segments, ending at array's left-center. | ||
| steps.push((x, y) => [preTerminalX, y]); | ||
| steps.push((x, y) => [x, targetY]); | ||
| steps.push((x, y) => [to.x(), y]); | ||
| } else { | ||
| // Route all rightward text arrows orthogonally so special cases still stay Manhattan. | ||
| steps.push((x, y) => [preTerminalX, y]); | ||
| steps.push((x, y) => [x, targetY]); | ||
| steps.push((x, y) => [to.x(), y]); | ||
| } |
There was a problem hiding this comment.
The logic for routing arrows to ArrayValue and other values is identical in this block. The code can be simplified by removing the redundant conditional check.
// Route rightward text arrows orthogonally using Manhattan segments.
steps.push((x, y) => [preTerminalX, y]);
steps.push((x, y) => [x, targetY]);
steps.push((x, y) => [to.x(), y]);
| protected isInteractive(): boolean { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Since the explicit check for printableMode was removed from the mouse event handlers, isInteractive should be updated to return false when in printable mode. This ensures that arrows do not respond to hover or click events in the print view, maintaining the previous behavior.
| protected isInteractive(): boolean { | |
| return true; | |
| } | |
| /** Subclasses can disable all pointer interactions for passive arrows. */ | |
| protected isInteractive(): boolean { | |
| return !CseMachine.getPrintableMode(); | |
| } |
There was a problem hiding this comment.
This is extremely important and is similar to what I have already requested the Arrow to fix earlier on Telegram. The exact issue that hasnt been documented was as follows :
doc_2026-03-31_19-30-41.mp4
The arrows permanently become white in the Printable mode once clocked on and only revert back when clicked on some other arrow! This is also the same in the saved image actually which causes the arrow to become invisible.
The above highlighted Gemini's comment dictates a similar aspect of how arrows in general should not be interactive or if they are, the colors should change! It ofcourse should NOT be white.
| this._x = frameX + newReference.frame.width() + Config.FrameMarginX; | ||
|
|
||
| this._y = newReference.y(); | ||
| this._y = newReference.y() + 20; |
| const preTerminalX = Math.max(frameExitX, to.x() - terminalSegmentLength); | ||
|
|
||
| if (to instanceof ArrayValue) { | ||
| // Route text-to-array arrows with Manhattan segments, ending at array's left-center. | ||
| steps.push((x, y) => [preTerminalX, y]); | ||
| steps.push((x, y) => [x, targetY]); | ||
| steps.push((x, y) => [to.x(), y]); |
There was a problem hiding this comment.
Bug: The calculation for preTerminalX can cause an arrow to route past its target horizontally before correcting, creating a U-shaped overshoot when the target is near its parent frame.
Severity: MEDIUM
Suggested Fix
The routing logic should ensure preTerminalX does not exceed to.x(). Consider using different routing logic when the target is located between the text's origin and the calculated frame exit point.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.
Location: src/features/cseMachine/components/arrows/ArrowFromText.tsx#L60-L66
Potential issue: When routing an arrow from text to a target on its right, the
`preTerminalX` coordinate is calculated using `Math.max(frameExitX, to.x() -
terminalSegmentLength)`. If the target is positioned close to or within its parent
frame, `frameExitX` (the frame's right edge plus a buffer) can be greater than the
target's x-coordinate `to.x()`. This causes `preTerminalX` to be set to `frameExitX`,
forcing the arrow path to extend past the target horizontally before turning back,
resulting in a visually incorrect U-shaped overshoot. This is a common scenario for
bindings where the value is near the parent frame.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
For the following code segment or any other that requires a few like functions in the global frame pre computed,
const xs = list(1, 2, 3);
const ys = map(x => x + 1, xs);
display(ys);Even at step 1, when we try to filter the arrows using the new button, the list of pre-executed and shown functions in the global frame just DISAPPEAR! They dont reappear on toggling the filter as well. Only when we change the step do they reappear! This is kind of similar to the issue #3712 which concerns the Layout team too!
The bug is not really “the arrows are wrong”, it is filtering arrows may accidentally wipe cached built-in function names and change what global functions are shown. It is shown below :
Video.Project.11.mp4
@ThatLi and @gigopogo, just out of curiosity, does this concern you? I mean its a gray area as it is triggered on using the filter button that is new and has not been merged yet!
|
Closing because I created a new PR that only has changes to the appearance of the arrows. Filtering is in a separate PR. |
This was before #3712 was resolved in the recent PR #3714 so I'm not sure if the disappearance was due to the code from our side or the filtering done here, since I haven't tested the code here yet. But ideally the pre-defined functions should be there regardless of whether anything is toggled. |

Description
Arrows now have
• standardised radius of curvature (except when there are space constraints)
• Terminating segment length (so there is no abrupt "bent" arrow-head)
The arrows which pointed to arrays from the left were initially straight arrows. Now, they also follow Manhattan routing like the rest of the arrows.
Type of change
How to test
Run any Source program and step through the CSE Machine to see the arrows.
Checklist