diff --git a/frontend/src/lib/core/axis.js b/frontend/src/lib/core/axis.js index 97f0dc2..bbd5c15 100644 --- a/frontend/src/lib/core/axis.js +++ b/frontend/src/lib/core/axis.js @@ -274,8 +274,10 @@ export function reduceAxisState(state, action) { case 'rotate': { const angle = action.angle; if (angle === 0) return state; - const newAxis = rotateAxis(state.axis, angle); - return { ...state, axis: newAxis, approved: false }; + // Rotate the RECTANGLE, not a separate axis vector. The rect's angle + // changes, and the axis is re-derived from the rotated rect. + const newRect = { ...state.rect, angle: state.rect.angle + angle }; + return { ...state, rect: newRect, axis: axisFromRect(newRect), approved: false }; } case 'approve': { return { ...state, approved: true }; diff --git a/frontend/tests/unit/axis.test.js b/frontend/tests/unit/axis.test.js index 8ac01af..467f738 100644 --- a/frontend/tests/unit/axis.test.js +++ b/frontend/tests/unit/axis.test.js @@ -319,6 +319,25 @@ describe('reduceAxisState', () => { expect(s.axis.y).toBeCloseTo(1, 6); }); + it('rotate changes the rect angle (not just a separate axis vector)', () => { + let s = createAxisState(rect); + const originalAngle = s.rect.angle; + s = reduceAxisState(s, { type: 'rotate', angle: Math.PI / 4 }); + // The rect itself must have rotated + expect(s.rect.angle).toBeCloseTo(originalAngle + Math.PI / 4, 6); + // And the axis is re-derived from the rotated rect + expect(s.axis).toEqual(axisFromRect(s.rect)); + }); + + it('rotate by 90 deg: rect angle changes, axis is perpendicular to original', () => { + let s = createAxisState(rect); + s = reduceAxisState(s, { type: 'rotate', angle: Math.PI / 2 }); + expect(s.rect.angle).toBeCloseTo(Math.PI / 2, 6); + // Original axis was +x (1,0). After 90 deg rect rotation, axis is +y (0,1) + expect(s.axis.x).toBeCloseTo(0, 6); + expect(s.axis.y).toBeCloseTo(1, 6); + }); + it('rotate composes (rotates from current axis)', () => { let s = createAxisState(rect); s = reduceAxisState(s, { type: 'rotate', angle: Math.PI / 2 });