Skip to content

feat(web): final UI polish for run controls and node connectors#15

Merged
matheusgalvao1 merged 4 commits intomainfrom
codex/ui-final-polish
Feb 26, 2026
Merged

feat(web): final UI polish for run controls and node connectors#15
matheusgalvao1 merged 4 commits intomainfrom
codex/ui-final-polish

Conversation

@matheusgalvao1
Copy link
Collaborator

@matheusgalvao1 matheusgalvao1 commented Feb 26, 2026

Purpose

Polish workflow editor UX for run controls and node connectors.

What changed

  • Added/updated cancel-run tooltip behavior and replaced the cancel icon.
  • Aligned connector positions with node header/body sections (including approval and collapsed condition behavior).
  • Fixed approval node collapsed preview to update live while typing.
  • Changed approval message input from single-line text input to textarea.
  • Added explicit connector tooltips across connector types and set side behavior: in opens left, out opens right.
  • Updated subagent connector labels to "Add subagent" and "Set as subagent".

Risk / Impact

UI-only changes in the workflow editor (apps/web) affecting node rendering, connector placement, and tooltip behavior.

Validation

  • npm --workspace apps/web run build

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

This PR modifies the workflow editor's visual presentation and port layout system. The changes include: replacing the Cancel Run button's icon class in the HTML template, introducing dynamic layout helper methods for node positioning instead of fixed offsets, expanding the port creation API to support optional tooltips and positioning parameters, adding tooltip styling rules for ports and the cancel button via CSS, and enhancing port labels and approval node UI components with updated text and textarea input formatting.

Possibly related PRs

  • PR #9: Modifies the same createPort signature and port rendering surface in workflow-editor.ts, directly overlapping with port API changes introduced in this PR.
  • PR #10: Updates port positioning helpers, approval node UI, and tooltip/run-button hint behavior with overlapping changes to cancel button tooltip flows and port-position calculations.
  • PR #6: Modifies cancel/run button tooltip and title handling, providing context for the tooltip hint mechanisms expanded in this PR.

Suggested reviewers

  • maria-codesignal
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: UI polish for run controls (cancel button tooltip and icon) and node connectors (positioning, tooltips, and labels).
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing specific modifications across all three modified files and their purposes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/src/app/workflow-editor.ts (1)

2415-2418: Avoid stacking native and custom tooltips on ports.

Line 2416 sets title while Lines 2417–2418 already use custom tooltip (data-tooltip) + accessibility label. Keeping both can produce duplicate/flickering tooltip behavior on hover.

🧹 Minimal cleanup
         if (title) {
-            port.title = title;
             port.setAttribute('data-tooltip', title);
             port.setAttribute('aria-label', title);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/workflow-editor.ts` around lines 2415 - 2418, The code is
creating both a native tooltip and a custom tooltip for port elements; remove
the native tooltip assignment by deleting the port.title = title line so only
the custom tooltip (port.setAttribute('data-tooltip', title)) and accessibility
label (port.setAttribute('aria-label', title)) remain, ensuring tooltips don't
duplicate or flicker when hovering over ports.
apps/web/src/workflow-editor.css (1)

564-567: Tokenize hardcoded tooltip spacing values.

These lines introduce hardcoded pixel spacing for tooltip arrows and viewport offsets (6px, 20px, 32px, 48px). Please switch these to design-system spacing tokens (or local CSS vars derived from those tokens) for consistency and theming compliance.

♻️ Suggested tokenized pattern
+:root {
+    --tooltip-arrow-size: var(--UI-Spacing-spacing-xs);
+    --tooltip-edge-offset: var(--UI-Spacing-spacing-ms);
+    --tooltip-viewport-gutter: calc(var(--UI-Spacing-spacing-ms) * 2);
+    --tooltip-viewport-gutter-wide: calc(var(--UI-Spacing-spacing-ms) * 3);
+}
...
-    border-left: 6px solid var(--Colors-Backgrounds-Main-Top);
+    border-left: var(--tooltip-arrow-size) solid var(--Colors-Backgrounds-Main-Top);
...
-    border-right: 6px solid var(--Colors-Backgrounds-Main-Top);
+    border-right: var(--tooltip-arrow-size) solid var(--Colors-Backgrounds-Main-Top);
...
-    border-bottom: 6px solid var(--Colors-Backgrounds-Main-Top);
+    border-bottom: var(--tooltip-arrow-size) solid var(--Colors-Backgrounds-Main-Top);
...
-    border-top: 6px solid var(--Colors-Backgrounds-Main-Top);
+    border-top: var(--tooltip-arrow-size) solid var(--Colors-Backgrounds-Main-Top);
...
-    max-width: min(320px, calc(100vw - 32px));
+    max-width: min(320px, calc(100vw - var(--tooltip-viewport-gutter)));
...
-    max-width: min(320px, calc(100vw - 48px));
+    max-width: min(320px, calc(100vw - var(--tooltip-viewport-gutter-wide)));
...
-    right: 20px;
+    right: var(--tooltip-edge-offset);

As per coding guidelines, apps/web/**/*.css: Consume design system spacing tokens (--UI-Spacing-spacing-*) instead of hardcoded pixel values when styling.

Also applies to: 582-583, 603-605, 622-625, 1507-1508, 1540-1540, 1554-1554, 1560-1560

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/workflow-editor.css` around lines 564 - 567, Replace hardcoded
spacing values (e.g., "6px", "20px", "32px", "48px") in
apps/web/src/workflow-editor.css with design-system spacing tokens or local CSS
variables derived from them; locate the rules that set
border-top/bottom/right/left for tooltip arrows (the block using border-left:
6px solid var(--Colors-Backgrounds-Main-Top)) and update those properties to use
tokens like --UI-Spacing-spacing-1 (or a new local var such as
--tooltip-arrow-size) instead of literal px, and do the same for the other
occurrences listed (lines around 582-583, 603-605, 622-625, 1507-1508, 1540,
1554, 1560), ensuring consistent token naming and fallback values where
appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/app/workflow-editor.ts`:
- Around line 985-1009: These helpers (getNodeHeaderCenterYOffset,
getNodeHeaderPortTop, getNodeSecondaryCenterYOffset, getNodeSecondaryPortTop)
are causing layout thrash by repeatedly reading DOM metrics; fix by introducing
a per-render-pass cache (e.g., a Map keyed by node.id) that stores computed
geometry {headerCenterY, secondaryCenterY} and is populated the first time a
node is measured during the current render cycle, then returned for subsequent
calls; ensure the cache is cleared at the start of the connection-render pass
(where connections are drawn) or whenever node layout changes so measurements
are refreshed; update those four methods to read/write the cache instead of
querying the DOM every call.

---

Nitpick comments:
In `@apps/web/src/app/workflow-editor.ts`:
- Around line 2415-2418: The code is creating both a native tooltip and a custom
tooltip for port elements; remove the native tooltip assignment by deleting the
port.title = title line so only the custom tooltip
(port.setAttribute('data-tooltip', title)) and accessibility label
(port.setAttribute('aria-label', title)) remain, ensuring tooltips don't
duplicate or flicker when hovering over ports.

In `@apps/web/src/workflow-editor.css`:
- Around line 564-567: Replace hardcoded spacing values (e.g., "6px", "20px",
"32px", "48px") in apps/web/src/workflow-editor.css with design-system spacing
tokens or local CSS variables derived from them; locate the rules that set
border-top/bottom/right/left for tooltip arrows (the block using border-left:
6px solid var(--Colors-Backgrounds-Main-Top)) and update those properties to use
tokens like --UI-Spacing-spacing-1 (or a new local var such as
--tooltip-arrow-size) instead of literal px, and do the same for the other
occurrences listed (lines around 582-583, 603-605, 622-625, 1507-1508, 1540,
1554, 1560), ensuring consistent token naming and fallback values where
appropriate.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff75502 and 50bf8b7.

📒 Files selected for processing (3)
  • apps/web/index.html
  • apps/web/src/app/workflow-editor.ts
  • apps/web/src/workflow-editor.css

Comment on lines +985 to 1009
getNodeHeaderCenterYOffset(node: EditorNode): number {
const nodeEl = document.getElementById(node.id);
const headerEl = nodeEl?.querySelector('.node-header');
if (!(headerEl instanceof HTMLElement)) return DEFAULT_HEADER_CENTER_Y;
return Math.round(headerEl.offsetTop + (headerEl.offsetHeight / 2));
}

getNodeHeaderPortTop(node: EditorNode): number {
return this.getNodeHeaderCenterYOffset(node) - PORT_RADIUS;
}

getNodeSecondaryCenterYOffset(node: EditorNode): number {
const nodeEl = document.getElementById(node.id);
const headerEl = nodeEl?.querySelector('.node-header');
if (!(nodeEl instanceof HTMLElement) || !(headerEl instanceof HTMLElement)) {
return DEFAULT_SECONDARY_CENTER_Y;
}
const bodyTop = headerEl.offsetTop + headerEl.offsetHeight;
const bodyHeight = Math.max(nodeEl.offsetHeight - bodyTop, PORT_RADIUS * 2);
return Math.round(bodyTop + (bodyHeight / 2));
}

getNodeSecondaryPortTop(node: EditorNode): number {
return this.getNodeSecondaryCenterYOffset(node) - PORT_RADIUS;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cache node geometry per render pass to avoid layout thrash.

Lines 985–1009 read live layout metrics, and those helpers are hit repeatedly while rendering connections (Line 2570+). On dense graphs this can trigger noticeable drag/reconnect jank due to repeated sync layout reads.

⚡ Suggested direction (cache metrics once per render cycle)
+type NodePortMetrics = { headerCenterY: number; secondaryCenterY: number };
+
+private getNodePortMetrics(node: EditorNode, cache: Map<string, NodePortMetrics>): NodePortMetrics {
+    const cached = cache.get(node.id);
+    if (cached) return cached;
+    const metrics = {
+        headerCenterY: this.getNodeHeaderCenterYOffset(node),
+        secondaryCenterY: this.getNodeSecondaryCenterYOffset(node)
+    };
+    cache.set(node.id, metrics);
+    return metrics;
+}
...
 renderConnections(refreshSelectedForm = true) {
     if (!this.connectionsLayer) return;
+    const nodeMetrics = new Map<string, NodePortMetrics>();
...
     this.connections.forEach((conn: any, index: any) => {
         const sourceNode = this.nodes.find((n: any) => n.id === conn.source);
         const targetNode = this.nodes.find((n: any) => n.id === conn.target);
         if (!sourceNode || !targetNode) return;
+        const sourceMetrics = this.getNodePortMetrics(sourceNode, nodeMetrics);
+        const targetMetrics = this.getNodePortMetrics(targetNode, nodeMetrics);
-        const startPoint = this.getConnectionStartPoint(sourceNode, conn.sourceHandle);
-        const endPoint = this.getConnectionEndPoint(targetNode, conn.sourceHandle);
+        const startPoint = this.getConnectionStartPoint(sourceNode, conn.sourceHandle, sourceMetrics);
+        const endPoint = this.getConnectionEndPoint(targetNode, conn.sourceHandle, targetMetrics);

Also applies to: 2570-2602

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/workflow-editor.ts` around lines 985 - 1009, These helpers
(getNodeHeaderCenterYOffset, getNodeHeaderPortTop,
getNodeSecondaryCenterYOffset, getNodeSecondaryPortTop) are causing layout
thrash by repeatedly reading DOM metrics; fix by introducing a per-render-pass
cache (e.g., a Map keyed by node.id) that stores computed geometry
{headerCenterY, secondaryCenterY} and is populated the first time a node is
measured during the current render cycle, then returned for subsequent calls;
ensure the cache is cleared at the start of the connection-render pass (where
connections are drawn) or whenever node layout changes so measurements are
refreshed; update those four methods to read/write the cache instead of querying
the DOM every call.

@matheusgalvao1 matheusgalvao1 merged commit 1d7016b into main Feb 26, 2026
2 checks passed
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