Rotation:
- Rotation handle above selection (line + circle)
- Drag to rotate, Shift snaps to 15° increments
- Live preview during drag, applied on release
- Rotated nodes render correctly with canvas.rotate()
Multi-select bounding box:
- Individual outlines per node + unified group bbox
- Group resize handles on the unified bounds
- Handles rotated AABB for rotated nodes
Snap guides:
- Magenta guide lines when moving nodes near edges/centers
- Snaps to left/right/top/bottom/center of other nodes
- 5px threshold, guides clear on mouse up
Text tool:
- T shortcut or toolbar to activate
- Click to place text node (200×24, default 'Text')
- CanvasKit drawText with default font (Inter to come)
Color picker (Reka UI Popover):
- HSV saturation/brightness area with pointer drag
- Hue slider (rainbow gradient)
- Alpha slider
- Hex input + alpha percentage
- Live color updates to fill/stroke
Properties panel:
- Add/remove fills and strokes with +/× buttons
- Toggle fill visibility
- Stroke weight editing
- Multi-select shows layer count + shared opacity
- ColorPicker integrated for fill and stroke colors
Frame clipping:
- Frame children now clip to parent bounds
The WebGL surface is bound to the framebuffer size at creation time.
Resizing the canvas element without recreating the surface left dark
empty space. Now createSurface() is called on every resize.
- Resize by dragging selection handles (8 positions: corners + edges)
- Shift+resize maintains aspect ratio
- Marquee selection: drag on empty canvas to select nodes in rectangle
- Alt+drag to duplicate and move selected nodes
- Cmd+D to duplicate in place (+20px offset)
- Cmd+A to select all
- Shift+draw constrains to square/circle
- Cursor changes to resize arrows on handle hover
- Marquee drawn as blue transparent rect with selection stroke
Draw selection outlines and resize handles in screen space (CSS pixels)
instead of canvas coordinates. Skia renders via dpr-scaled transform
so strokes stay 1px regardless of zoom level.