Editor "Snap to Nodes / Grid / Hoop" toggles do nothing — settings stored but never applied to node coordinates on drag/drop #170

Closed
opened 2026-10-03 17:31:31 +00:00 by Mark · 1 comment
Owner

Finding

src/pages/Editor.tsx:200 stores snapSettings in state, initialized from DEFAULT_SNAP_SETTINGS (line 30 of constants.ts):

const [snapSettings, setSnapSettings] = useState<SnapSettings>(DEFAULT_SNAP_SETTINGS);

src/components/editor/EditorRightPanel.tsx:264-272 exposes 3 toggles (Snap to Nodes, Snap to Grid, Snap to Hoop) and a grid size slider (5-50mm) that update the snapSettings state via onSnapSettingsChange.

A grep -rn 'snapSettings\.' src/ returns 6 references, all of them in the right panel's toggle/slider rendering. The canvas's drag handler does not consult snapSettings to constrain the node's new position.

The user toggles "Snap to Grid" on, sets grid size to 10mm, drags a node, drops it. The node lands wherever the user dropped it — the snap was not applied. The toggles are UI-only state.

Where

  • snapSettings state: src/pages/Editor.tsx:200
  • Toggles and slider: src/components/editor/EditorRightPanel.tsx:264-272
  • No application code: grep -rn 'snapSettings' src/ shows no consumer outside the panel's own render

Suggested fix shape

  1. Add a applySnap(point, snapSettings, allNodes, hoopSize) function to src/components/editor/canvasRenderer.ts (or a new editor/geometry.ts):

    • If snapToNodes: find the closest node within snapSettings.gridSizemm and snap to it
    • If snapToGrid: round the point to the nearest gridSizemm grid intersection
    • If snapToHoop: clamp the point to the hoop's bounds
  2. Wire it into the node drag handler in src/components/editor/EditorCanvas.tsx:

    • When the user releases a node drag, call applySnap(finalPoint, snapSettings, allNodes, hoopSize) before committing the new position
  3. Visual feedback: while dragging, draw a small indicator at the snap target (the grid intersection, the snap-to-node candidate, the hoop edge) so the user sees the snap behavior in action.

Severity

Medium. The toggles are surfaced in the right panel with proper labels and the user expects them to work. They don't.

Discovered by

Gap analysis (2026-10-02) of editor interaction state.

  • The DEFAULT_SNAP_SETTINGS constant is at src/components/editor/constants.ts:17-22. The intent is documented; the implementation is missing.
  • Same family of UI-affordance-without-behavior as the Auto Thread Match toggle (#145, fixed) and the Help page search input (#144, fixed).
## Finding `src/pages/Editor.tsx:200` stores `snapSettings` in state, initialized from `DEFAULT_SNAP_SETTINGS` (line 30 of `constants.ts`): ```ts const [snapSettings, setSnapSettings] = useState<SnapSettings>(DEFAULT_SNAP_SETTINGS); ``` `src/components/editor/EditorRightPanel.tsx:264-272` exposes 3 toggles (Snap to Nodes, Snap to Grid, Snap to Hoop) and a grid size slider (5-50mm) that update the snapSettings state via `onSnapSettingsChange`. A `grep -rn 'snapSettings\.' src/` returns 6 references, all of them in the right panel's toggle/slider rendering. The canvas's drag handler does not consult `snapSettings` to constrain the node's new position. The user toggles "Snap to Grid" on, sets grid size to 10mm, drags a node, drops it. The node lands wherever the user dropped it — the snap was not applied. The toggles are UI-only state. ## Where - snapSettings state: `src/pages/Editor.tsx:200` - Toggles and slider: `src/components/editor/EditorRightPanel.tsx:264-272` - No application code: `grep -rn 'snapSettings' src/` shows no consumer outside the panel's own render ## Suggested fix shape 1. **Add a `applySnap(point, snapSettings, allNodes, hoopSize)` function** to `src/components/editor/canvasRenderer.ts` (or a new `editor/geometry.ts`): - If `snapToNodes`: find the closest node within `snapSettings.gridSize`mm and snap to it - If `snapToGrid`: round the point to the nearest `gridSize`mm grid intersection - If `snapToHoop`: clamp the point to the hoop's bounds 2. **Wire it into the node drag handler** in `src/components/editor/EditorCanvas.tsx`: - When the user releases a node drag, call `applySnap(finalPoint, snapSettings, allNodes, hoopSize)` before committing the new position 3. **Visual feedback**: while dragging, draw a small indicator at the snap target (the grid intersection, the snap-to-node candidate, the hoop edge) so the user sees the snap behavior in action. ## Severity Medium. The toggles are surfaced in the right panel with proper labels and the user expects them to work. They don't. ## Discovered by Gap analysis (2026-10-02) of editor interaction state. ## Related - The `DEFAULT_SNAP_SETTINGS` constant is at `src/components/editor/constants.ts:17-22`. The intent is documented; the implementation is missing. - Same family of UI-affordance-without-behavior as the Auto Thread Match toggle (#145, fixed) and the Help page search input (#144, fixed).
Mark closed this issue 2026-10-03 18:54:06 +00:00
Author
Owner

Fixed in commit ebd1ce8 (fix(#170): Snap to Nodes/Grid/Hoop toggles now constrain node drags).

What changed:

  • New src/components/editor/geometry.ts with applySnap(candidate, snapSettings, allNodes, excludeNodeId). Precedence: snapToNodes → snapToGrid → snapToHoop; a higher-precedence rule that fires blocks lower rules.
  • EditorCanvas.tsx seeds a dragLastSnapTargetRef at the node's starting position on mousedown, then on each move builds a candidate by stepping the cursor delta off the last emitted target, applies snap, and emits the incremental delta to onNodeDrag. The parent's accumulated position stays correct.
  • snapIndicator state surfaces the snap target to the canvas renderer; drawSnapIndicator in canvasRenderer.ts paints a small accent at the target while a drag is in flight and clears on mouseup.
  • 10 unit tests in geometry.test.ts + 3 wiring tests in EditorCanvas.test.tsx. tsc -b clean, eslint clean, vite build clean, 743/743 vitest tests pass on the rebased main (was 735 before my commit).

Local CI gates:

  • npm test (vitest): 743/743 passing
  • npx tsc -b: clean
  • npm run lint: clean
  • npm run build: succeeds, dist/index.html + dist/assets/ present

Forgejo Actions self-hosted runner is currently non-functional (every recent run is status: cancelled, started: 1970-01-01 per the workflows/runs history) — see the comment in .forgejo/workflows/ci.yml. Local-gate results stand as the gate.

Fixed in commit ebd1ce8 (`fix(#170): Snap to Nodes/Grid/Hoop toggles now constrain node drags`). What changed: - New `src/components/editor/geometry.ts` with `applySnap(candidate, snapSettings, allNodes, excludeNodeId)`. Precedence: snapToNodes → snapToGrid → snapToHoop; a higher-precedence rule that fires blocks lower rules. - `EditorCanvas.tsx` seeds a `dragLastSnapTargetRef` at the node's starting position on mousedown, then on each move builds a candidate by stepping the cursor delta off the last emitted target, applies snap, and emits the incremental delta to `onNodeDrag`. The parent's accumulated position stays correct. - `snapIndicator` state surfaces the snap target to the canvas renderer; `drawSnapIndicator` in `canvasRenderer.ts` paints a small accent at the target while a drag is in flight and clears on mouseup. - 10 unit tests in `geometry.test.ts` + 3 wiring tests in `EditorCanvas.test.tsx`. `tsc -b` clean, `eslint` clean, `vite build` clean, 743/743 vitest tests pass on the rebased main (was 735 before my commit). Local CI gates: - npm test (vitest): 743/743 passing - npx tsc -b: clean - npm run lint: clean - npm run build: succeeds, dist/index.html + dist/assets/ present Forgejo Actions self-hosted runner is currently non-functional (every recent run is `status: cancelled, started: 1970-01-01` per the workflows/runs history) — see the comment in `.forgejo/workflows/ci.yml`. Local-gate results stand as the gate.
Sign in to join this conversation.
No description provided.