Editor renders all node types as the same 6px circle — user can't tell corner vs smooth vs symmetric nodes at a glance, even though the type is editable in the right panel #219

Closed
opened 2026-10-04 00:05:07 +00:00 by Mark · 1 comment
Owner

Finding

src/components/editor/canvasRenderer.ts's drawPath (line 81) loops through nodes and draws each as a 6px circle. The shape is the same regardless of node.type ('corner' / 'smooth' / 'symmetric'). The only type-specific render is the Bezier handle: drawn for smooth/symmetric nodes when selected.

grep -B1 -A8 'drawNodes && mode === 'select_nodes'' src/components/editor/canvasRenderer.ts shows the loop body draws the same circle shape for all types.

A user with a 20-node path that's mostly smooth with 4 corners has to:

  • Click each node to see its type in the right panel
  • Or remember which nodes they converted

Standard vector editors distinguish node types visually:

  • Corner: square or filled circle
  • Smooth: hollow circle or smooth dot
  • Symmetric: hollow circle with a "mirrored handle" indicator (a small line through the center)

A user looking at the canvas should be able to spot the corner nodes in a sea of smooth ones.

Where

  • Node draw: src/components/editor/canvasRenderer.ts (in drawPath)
  • Same shape for all types: see the loop body, no type check on the shape
  • Bezier handles only for smooth/symmetric when selected: src/components/editor/canvasRenderer.ts:155

Suggested fix shape

In the loop, branch on node.type:

ctx.save();

if (node.type === 'corner') {
  // Square: small rotated rectangle
  ctx.fillStyle = isNodeSelected ? '#E6A948' : '#F8F2E9';
  ctx.fillRect(pos.x - r, pos.y - r, r * 2, r * 2);
  if (isNodeSelected) {
    ctx.strokeStyle = '#E6A948';
    ctx.lineWidth = 2 * scale;
    ctx.strokeRect(pos.x - r, pos.y - r, r * 2, r * 2);
  }
} else if (node.type === 'smooth') {
  // Filled circle (current behavior)
  ctx.beginPath();
  ctx.arc(pos.x, pos.y, r, 0, Math.PI * 2);
  ctx.fill();
  ctx.stroke();
} else if (node.type === 'symmetric') {
  // Hollow circle with crosshair
  ctx.beginPath();
  ctx.arc(pos.x, pos.y, r, 0, Math.PI * 2);
  ctx.stroke();
  // Crosshair through the node indicating symmetry
  ctx.beginPath();
  ctx.moveTo(pos.x - r * 0.5, pos.y);
  ctx.lineTo(pos.x + r * 0.5, pos.y);
  ctx.moveTo(pos.x, pos.y - r * 0.5);
  ctx.lineTo(pos.x, pos.y + r * 0.5);
  ctx.stroke();
}

ctx.restore();

The user can now see at a glance which nodes are corners (squares), which are smooth (circles), and which are symmetric (circles with a crosshair). The right panel's type selector still works to change the type.

Optionally: the right panel's type label can include a small icon matching the visual (square for corner, circle for smooth, etc.) so the right panel and the canvas are in sync.

Severity

Low. The user can read the type in the right panel after selecting. Visual differentiation is a polish improvement.

Discovered by

Gap analysis (2026-10-02) of editor node visualization.

  • #170 (closed) — Snap toggles. Snap is a different feature; node visual is a different concern.
  • #211 (open) — Bezier handle numeric input. The Bezier handles are already drawn; the user wants to edit them numerically.
## Finding `src/components/editor/canvasRenderer.ts`'s `drawPath` (line 81) loops through nodes and draws each as a 6px circle. The shape is the same regardless of `node.type` ('corner' / 'smooth' / 'symmetric'). The only type-specific render is the Bezier handle: drawn for smooth/symmetric nodes when selected. `grep -B1 -A8 'drawNodes && mode === 'select_nodes'' src/components/editor/canvasRenderer.ts` shows the loop body draws the same circle shape for all types. A user with a 20-node path that's mostly smooth with 4 corners has to: - Click each node to see its type in the right panel - Or remember which nodes they converted Standard vector editors distinguish node types visually: - **Corner**: square or filled circle - **Smooth**: hollow circle or smooth dot - **Symmetric**: hollow circle with a "mirrored handle" indicator (a small line through the center) A user looking at the canvas should be able to spot the corner nodes in a sea of smooth ones. ## Where - Node draw: `src/components/editor/canvasRenderer.ts` (in `drawPath`) - Same shape for all types: see the loop body, no type check on the shape - Bezier handles only for smooth/symmetric when selected: `src/components/editor/canvasRenderer.ts:155` ## Suggested fix shape In the loop, branch on `node.type`: ```ts ctx.save(); if (node.type === 'corner') { // Square: small rotated rectangle ctx.fillStyle = isNodeSelected ? '#E6A948' : '#F8F2E9'; ctx.fillRect(pos.x - r, pos.y - r, r * 2, r * 2); if (isNodeSelected) { ctx.strokeStyle = '#E6A948'; ctx.lineWidth = 2 * scale; ctx.strokeRect(pos.x - r, pos.y - r, r * 2, r * 2); } } else if (node.type === 'smooth') { // Filled circle (current behavior) ctx.beginPath(); ctx.arc(pos.x, pos.y, r, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); } else if (node.type === 'symmetric') { // Hollow circle with crosshair ctx.beginPath(); ctx.arc(pos.x, pos.y, r, 0, Math.PI * 2); ctx.stroke(); // Crosshair through the node indicating symmetry ctx.beginPath(); ctx.moveTo(pos.x - r * 0.5, pos.y); ctx.lineTo(pos.x + r * 0.5, pos.y); ctx.moveTo(pos.x, pos.y - r * 0.5); ctx.lineTo(pos.x, pos.y + r * 0.5); ctx.stroke(); } ctx.restore(); ``` The user can now see at a glance which nodes are corners (squares), which are smooth (circles), and which are symmetric (circles with a crosshair). The right panel's type selector still works to change the type. Optionally: the right panel's type label can include a small icon matching the visual (square for corner, circle for smooth, etc.) so the right panel and the canvas are in sync. ## Severity Low. The user can read the type in the right panel after selecting. Visual differentiation is a polish improvement. ## Discovered by Gap analysis (2026-10-02) of editor node visualization. ## Related - #170 (closed) — Snap toggles. Snap is a different feature; node visual is a different concern. - #211 (open) — Bezier handle numeric input. The Bezier handles are already drawn; the user wants to edit them numerically.
Author
Owner

Shipped via commit f666780 on main. The editor now distinguishes node types at a glance:

  • corner → filled square (was: filled circle, indistinguishable)
  • smooth → filled circle (unchanged from previous behavior)
  • symmetric → hollow circle with a small crosshair through the center (was: filled circle)

drawPath in src/components/editor/canvasRenderer.ts branches on node.type in the node-draw loop. Selection styling (amber fill, white border, drop shadow) is unchanged so a node's type remains readable when it's also selected. Right-panel type selector (Corner / Smooth / Symmetric) keeps changing the type as before.

Added 5 canvasRenderer tests covering each type independently, a mixed-type path, and the drawNodes: false skip case. 1076 tests pass; tsc -b clean; vite build clean.

Shipped via commit f666780 on `main`. The editor now distinguishes node types at a glance: - **corner** → filled square (was: filled circle, indistinguishable) - **smooth** → filled circle (unchanged from previous behavior) - **symmetric** → hollow circle with a small crosshair through the center (was: filled circle) `drawPath` in `src/components/editor/canvasRenderer.ts` branches on `node.type` in the node-draw loop. Selection styling (amber fill, white border, drop shadow) is unchanged so a node's type remains readable when it's also selected. Right-panel type selector (Corner / Smooth / Symmetric) keeps changing the type as before. Added 5 canvasRenderer tests covering each type independently, a mixed-type path, and the `drawNodes: false` skip case. 1076 tests pass; `tsc -b` clean; `vite build` clean.
Mark closed this issue 2026-10-04 04:40:39 +00:00
Sign in to join this conversation.
No description provided.