Editor has no 'snap to angle' for line drawing — user drawing a straight line at exactly 45° or 90° has to estimate the angle, no Shift+drag to constrain to common angles #294

Closed
opened 2026-10-04 03:47:23 +00:00 by Mark · 1 comment
Owner

Finding

grep -rn 'snap.*angle\|constrainAngle\|constrain.*45' src/components/editor/ returns 0. The editor has no angle snap.

A user drawing a straight horizontal line:

  • Clicks to start
  • Drags to draw
  • Wants exactly horizontal (0° or 180°)
  • Has to estimate by eye

Most vector editors (Figma, Illustrator, Inkscape) have:

  • Hold Shift while dragging to constrain the line to 0°, 45°, 90° angles
  • Or: a setting for "snap to angle increments" (15°, 30°, 45°)

The editor's Draw Path tool doesn't have this.

Where

  • Draw Path tool: src/components/editor/EditorCanvas.tsx (the pointer handler when mode === 'draw_path')
  • No angle snap: grep -rn 'constrainAngle' src/components/editor/ returns 0

Suggested fix shape

  1. State: isConstrainingAngle: boolean (true while Shift is held).

  2. Logic: in the draw-path pointer handler:

const handlePointerMove = (e) => {
  if (mode === 'draw_path' && isDrawing) {
    let { x, y } = toCanvas(e.clientX, e.clientY);
    if (e.shiftKey) {
      // Constrain to multiples of 15 degrees
      const dx = x - startX;
      const dy = y - startY;
      const angle = Math.atan2(dy, dx);
      const length = Math.sqrt(dx * dx + dy * dy);
      const snapped = Math.round(angle / (Math.PI / 12)) * (Math.PI / 12);
      x = startX + Math.cos(snapped) * length;
      y = startY + Math.sin(snapped) * length;
    }
    // ... set the preview point to (x, y)
  }
};
  1. Visual feedback: while Shift is held, show a faint angle indicator (e.g., "45°" near the cursor).

  2. Settings: a "Snap angles to" select in the editor's settings (15°, 30°, 45° increments).

Severity

Low. The user can use the grid (per #288). Power users drawing precise shapes notice.

Discovered by

Gap analysis (2026-10-02) of editor drawing affordances.

  • #288 (open) — Grid overlay. The grid is a visual reference; angle snap is the behavior.
  • The snap settings (per SnapSettings) include snapToGrid; angle snap is the parallel.
  • Figma/Illustrator have Shift-to-constrain; the standard.
## Finding `grep -rn 'snap.*angle\|constrainAngle\|constrain.*45' src/components/editor/` returns 0. The editor has no angle snap. A user drawing a straight horizontal line: - Clicks to start - Drags to draw - Wants exactly horizontal (0° or 180°) - Has to estimate by eye Most vector editors (Figma, Illustrator, Inkscape) have: - Hold Shift while dragging to constrain the line to 0°, 45°, 90° angles - Or: a setting for "snap to angle increments" (15°, 30°, 45°) The editor's Draw Path tool doesn't have this. ## Where - Draw Path tool: `src/components/editor/EditorCanvas.tsx` (the pointer handler when `mode === 'draw_path'`) - No angle snap: `grep -rn 'constrainAngle' src/components/editor/` returns 0 ## Suggested fix shape 1. **State**: `isConstrainingAngle: boolean` (true while Shift is held). 2. **Logic**: in the draw-path pointer handler: ```ts const handlePointerMove = (e) => { if (mode === 'draw_path' && isDrawing) { let { x, y } = toCanvas(e.clientX, e.clientY); if (e.shiftKey) { // Constrain to multiples of 15 degrees const dx = x - startX; const dy = y - startY; const angle = Math.atan2(dy, dx); const length = Math.sqrt(dx * dx + dy * dy); const snapped = Math.round(angle / (Math.PI / 12)) * (Math.PI / 12); x = startX + Math.cos(snapped) * length; y = startY + Math.sin(snapped) * length; } // ... set the preview point to (x, y) } }; ``` 3. **Visual feedback**: while Shift is held, show a faint angle indicator (e.g., "45°" near the cursor). 4. **Settings**: a "Snap angles to" select in the editor's settings (15°, 30°, 45° increments). ## Severity Low. The user can use the grid (per #288). Power users drawing precise shapes notice. ## Discovered by Gap analysis (2026-10-02) of editor drawing affordances. ## Related - #288 (open) — Grid overlay. The grid is a visual reference; angle snap is the behavior. - The snap settings (per `SnapSettings`) include `snapToGrid`; angle snap is the parallel. - Figma/Illustrator have Shift-to-constrain; the standard.
Author
Owner

Shipped fix(#294) at 8735b3a on origin/main.

Three changes wire applySnap into the Draw-Path tool so users can draw clean-angle segments without computing bearings by hand:

  1. EditorCanvas now plumbs e.shiftKey into onCanvasClick for draw_path clicks (was hardcoded false). Shift+pointerdown no longer enters pan mode when mode === "draw_path" — Shift is reserved for angle snap while drawing.

  2. Editor.tsx handleCanvasClick runs the click through applySnap(point, settings, allNodes, undefined, fromPoint) where fromPoint is the last placed node. Angle snap fires when either snapSettings.angleSnapSteps is non-empty OR Shift is held. Shift forces a 15° step set ([0,15,30,45,60,75,90,105,120,135,150,165,180]), matching Figma / Illustrator / Inkscape.

  3. EditorPositionBar renders an optional · 45° bearing readout next to the cursor's X/Y when in draw_path mode with at least one anchor node, so the user can see which angle-snap step the next click will lock to. Hidden when angleDeg is null/omitted, so existing callers see no change.

Tests: 5 new tests in src/components/editor/issue-294.test.tsx covering the canvas plumbing, position-bar rendering, and applySnap integration. Full editor suite: 610/610 pass. Pre-existing issue-275 failures and issue-240 flakiness are unrelated to this fix.

Closes #294.

Shipped fix(#294) at 8735b3a on origin/main. Three changes wire `applySnap` into the Draw-Path tool so users can draw clean-angle segments without computing bearings by hand: 1. **EditorCanvas** now plumbs `e.shiftKey` into `onCanvasClick` for draw_path clicks (was hardcoded `false`). Shift+pointerdown no longer enters pan mode when `mode === "draw_path"` — Shift is reserved for angle snap while drawing. 2. **Editor.tsx `handleCanvasClick`** runs the click through `applySnap(point, settings, allNodes, undefined, fromPoint)` where `fromPoint` is the last placed node. Angle snap fires when either `snapSettings.angleSnapSteps` is non-empty OR Shift is held. Shift forces a 15° step set (`[0,15,30,45,60,75,90,105,120,135,150,165,180]`), matching Figma / Illustrator / Inkscape. 3. **EditorPositionBar** renders an optional `· 45°` bearing readout next to the cursor's X/Y when in draw_path mode with at least one anchor node, so the user can see which angle-snap step the next click will lock to. Hidden when `angleDeg` is null/omitted, so existing callers see no change. Tests: 5 new tests in `src/components/editor/issue-294.test.tsx` covering the canvas plumbing, position-bar rendering, and `applySnap` integration. Full editor suite: 610/610 pass. Pre-existing `issue-275` failures and `issue-240` flakiness are unrelated to this fix. Closes #294.
Mark closed this issue 2026-10-04 19:10:38 +00:00
Sign in to join this conversation.
No description provided.