Commit graph

33 commits

Author SHA1 Message Date
Anton Soldatov 3ac016b806
Add data-test-id attributes to all Vue components (#41) 2026-03-04 17:18:26 +03:00
Danila Poyarkov af9b9fd0fa Replace store thunks with a late-binding proxy
useEditorStore() now returns a Proxy that delegates to the current
active store at access time. No more store() wrapper functions,
try/catch guards, or passing stores through function parameters.
2026-03-02 18:36:27 +03:00
Danila Poyarkov c59d5fc397 Add Code tab with JSX export and syntax highlighting
- sceneNodeToJsx() in @open-pencil/core: converts SceneNode subtree to JSX
- CodePanel.vue with Prism.js highlighting, line numbers, copy button
- Third tab in properties panel: Design | Code | AI
- 14 new tests covering shapes, text, layout, effects, multi-selection
2026-03-01 12:12:45 +03:00
Danila Poyarkov f06631489f Wire AI tools to editor store with valibot schemas
10 tools: create_shape, set_fill, set_stroke, update_node, set_layout,
delete_node, select_nodes, get_page_tree, get_selection, rename_node.

Uses @ai-sdk/valibot for schema integration with ToolLoopAgent.
2026-03-01 11:45:27 +03:00
Danila Poyarkov f6434bc4f9 Fix chat reactivity (markRaw), add Playwright tests with mock transport
Use markRaw to prevent Vue from deep-proxying the Chat class
instance, which broke its internal Vue refs. Add dedent for
multiline prompts.

Tests use mock transport by default. Set TEST_REAL_LLM=1 and
OPENROUTER_API_KEY to run against real OpenRouter.
2026-03-01 11:30:30 +03:00
Danila Poyarkov 2145af5280 Use reka-ui Tabs/ScrollArea/Collapsible/Tooltip, tw-animate-css, vue-stream-markdown 2026-03-01 11:03:51 +03:00
Danila Poyarkov b10785fdee Extract DesignPanel from PropertiesPanel 2026-03-01 10:55:52 +03:00
Danila Poyarkov bf5c905134 Move AI chat to Design/AI tab in properties panel 2026-03-01 10:54:51 +03:00
Danila Poyarkov 5c4c6d7289 Move variables UI to dialog opened from page properties
Match Figma's pattern: Variables section in page-level properties
panel with settings icon that opens a full dialog (reka-ui Dialog +
Tabs + Editable). Remove sidebar panel approach.
2026-03-01 01:54:57 +03:00
Danila Poyarkov d30ab4c5d5 Add variables support: collections, modes, bindings, UI panel
Core:
- Variable, VariableCollection types on SceneGraph
- resolveVariable with alias chain resolution + cycle detection
- Mode switching (activeMode per collection)
- Bind/unbind variables to node properties (fills/strokes colors)
- Renderer resolves variable bindings before painting

Import:
- Parse VARIABLE type NodeChanges from .fig files
- Extract paint variable bindings (fills/strokes)

UI:
- VariablesPanel with reka-ui Tabs (collections) + Editable (names/values)
- FillSection: variable picker (reka-ui Popover + Combobox)
- Bound fills show purple variable name badge with detach button
- Toggle variables panel from bottom of layers sidebar

7 new unit tests (83 total)
2026-03-01 01:47:52 +03:00
Danila Poyarkov 63cefe651c Add CSS containment to side panels to reduce repaints from canvas 2026-03-01 00:45:20 +03:00
Danila Poyarkov 55b0b37789 Add image export (PNG/JPG/WEBP) with shared rendering
Extract renderThumbnail and renderNodesToImage into render-image.ts,
reusing the same offscreen CanvasKit surface approach that fig-export
used for thumbnails.

- ExportSection in properties panel with scale/format selectors,
  multi-export support, and live preview with checkerboard background
- Export via context menu and ⇧⌘E shortcut
- Tauri save dialog, File System Access API, and download fallback
- JPG renders with white background, PNG/WEBP with transparency
2026-02-28 23:59:39 +03:00
Danila Poyarkov 70c88dd72b Replace relative imports with @/ alias
Add @/ → src/ alias in vite.config.ts and tsconfig.json.
Rewrite all ../ imports across 32 files to use @/ paths.
Same-directory ./ imports kept as-is.
2026-02-28 23:08:45 +03:00
Danila Poyarkov 046f23b138 Format codebase with oxfmt 2026-02-28 22:41:34 +03:00
Danila Poyarkov a89e886981 Make left and right panels resizable with reka-ui Splitter
Uses SplitterGroup/SplitterPanel/SplitterResizeHandle. Panels
default to 15% width, min 10%, max 30%. Layout persists across
reloads via auto-save-id. Resize handle highlights blue on hover.
2026-02-28 22:22:39 +03:00
Danila Poyarkov daa949e9e3 Add Effects section to properties panel
Support adding, removing, and editing drop shadows, inner shadows,
layer blurs, background blurs, and foreground blurs. Each effect row
has visibility toggle, type dropdown, and remove button. Expanded
view shows offset, blur, spread, and color controls for shadows,
or just blur radius for blur effects.
2026-02-28 20:12:37 +03:00
Danila Poyarkov a862cc6ca6 Remove 'Create Instance' button from properties panel
Figma doesn't have this — instances are created by dragging from
the Assets panel or duplicating an existing instance.
2026-02-28 20:05:06 +03:00
Danila Poyarkov 3c5795f90b Add components, component sets, and instances
- SceneNode: componentId and overrides fields for instance tracking
- SceneGraph: cloneTree, createInstance, detachInstance, getMainComponent,
  getInstances methods
- Editor store: createComponentFromSelection (converts frame/group or wraps
  selection), createComponentSetFromComponents (groups multiple components),
  createInstanceFromComponent, detachInstance, goToMainComponent
- Renderer: purple (#9747ff) selection outlines, hover highlights, size pills,
  and name labels for component types; dashed purple border for component sets
- Layers panel: diamond icons for components/instances with purple tint
- Properties panel: component type indicator, Go to Main Component and
  Detach Instance buttons for instances, Create Instance for components
- Keyboard shortcuts: Ctrl+Alt+K (create component), Ctrl+Shift+K
  (create component set), Ctrl+Alt+B (detach instance)
- Clipboard: preserve COMPONENT/COMPONENT_SET/INSTANCE types
- Fig export: emit proper Kiwi node types instead of FRAME
2026-02-28 17:46:28 +03:00
Danila Poyarkov ffd305c125 Page properties panel with canvas background color picker
When nothing is selected, the right panel shows Page section with
a color picker for the canvas background (like Figma). The page
color is stored in reactive state and passed to the renderer.
2026-02-28 11:42:13 +03:00
Danila Poyarkov de83a716cd Remove prop drilling from property sections
All sections now get selected node from useNodeProps() composable
instead of receiving it as a prop from PropertiesPanel.
2026-02-28 09:27:03 +03:00
Danila Poyarkov 2af2a280c6 Appearance section: opacity + corner radius + visibility toggle
- Opacity with blend icon, corner radius with radius icon
- Eye/eye-off toggle in section header
- Layers panel: hidden layers dimmed (opacity-50) with permanent
  eye-off indicator
- Removed duplicate corner radius from LayoutSection
2026-02-28 09:20:31 +03:00
Danila Poyarkov 7e9cd46e0b Split PropertiesPanel into sections, add Typography panel
- PropertiesPanel.vue: 513 → 67 lines (composition only)
- Extracted: PositionSection, LayoutSection, AppearanceSection,
  FillSection, StrokeSection, TypographySection
- Shared useNodeProps() composable for update/commit logic
- Typography panel: font family picker with search (queryLocalFonts),
  weight select, font size, line height, letter spacing, text alignment
- Font picker shows each family in its own typeface
- Loads selected font into both CanvasKit and browser on pick
2026-02-28 09:00:25 +03:00
Danila Poyarkov f0d817c747 Refactor: vendor kiwi-schema, extract types/constants, culori colors
- Fork kiwi-schema from source TypeScript (evanw/kiwi), drop npm dep + patch
- Deduplicate GUID/Color types into src/types.ts
- Extract UI colors and default fills into src/constants.ts
- Replace hand-rolled hex parsing with culori library
- Extract demo shapes from App.vue into src/demo.ts
- useUrlSearchParams from VueUse for test mode detection
- Move fig-file/fig-import into kiwi/ (format layer)
- Delete PoC files (poc-yoga.ts, poc-test.ts)
2026-02-28 08:08:04 +03:00
Danila Poyarkov 179acd0edb Wire undo/redo into all editor operations
Undoable operations:
- Create shape (undo deletes it)
- Delete (undo restores with original z-order)
- Move (was already wired, cleaned up)
- Resize (commitResize on mouseUp)
- Rotate (commitRotation on mouseUp)
- Duplicate (undo deletes copies, restores selection)
- Paste (undo deletes pasted nodes)
- Text editing (undo restores previous text)
- Wrap in auto layout (undo unwraps and restores positions)
- Set layout mode (undo restores all layout properties)
- Fill/stroke add/remove/toggle (via updateNodeWithUndo)
- Alignment and padding changes
- Properties panel scrub inputs: emit commit event on
  pointerup/blur with previous value for undo push
- UndoManager.push() added for entries where forward was
  already applied
2026-02-28 06:28:17 +03:00
Danila Poyarkov 509dd4fab4 Migrate to Tailwind CSS 4.2
- @tailwindcss/vite plugin for zero-config build
- @theme tokens: panel, canvas, border, hover, accent, surface, muted, input
- All scoped CSS replaced with Tailwind utility classes
- Only remaining scoped CSS: ColorPicker range slider thumbs
- Removed conflicting * { padding: 0 } reset (Tailwind preflight handles it)
2026-02-28 00:54:38 +03:00
Danila Poyarkov 98104cee58 Add ScrubInput component with drag-to-scrub values
Figma-style scrubby inputs: hover icon shows ew-resize cursor,
drag left/right to change value. Click the number to type.
Replaces all raw number inputs in PropertiesPanel.
Uses VueUse useEventListener for automatic cleanup.
2026-02-28 00:27:38 +03:00
Danila Poyarkov b6ae7d40d0 Replace opacity slider with number input
Slider was overlapping the label text. Figma uses a number input
with % suffix for opacity, not a slider.
2026-02-28 00:22:18 +03:00
Danila Poyarkov bc68d660b9 Replace native range input with Reka UI Slider
Accessible, unstyled slider with custom dark theme styling:
thin 3px track, blue range fill, 10px white circular thumb with
hover/focus ring.
2026-02-28 00:17:25 +03:00
Danila Poyarkov 2e91959cc5 Style opacity range slider to match dark theme 2026-02-28 00:15:16 +03:00
Danila Poyarkov 759940194e Figma-style auto-layout properties panel
- 9-dot alignment grid for simultaneous primary + counter axis
- Direction buttons with SVG icons (vertical/horizontal/wrap)
- W/H sizing mode dropdowns (Fixed/Hug/Fill) with click-outside
- Uniform padding input with expand toggle for per-side padding
- Gap input with visual icon
- + / − to add/remove auto layout instead of triple toggle
- Sizing mode awareness: shows Fill option when inside auto-layout
2026-02-28 00:11:01 +03:00
Danila Poyarkov 4de83542d3 Auto-layout with Yoga WASM integration
Layout engine (src/engine/layout.ts):
- Yoga WASM adapter mapping SceneNode layout props to Yoga nodes
- Supports vertical/horizontal direction, padding (per-side), gap,
  primary/counter axis alignment, HUG/FIXED/FILL sizing, wrap,
  absolute positioning, flex grow, stretch
- Bottom-up computation: nested auto-layout frames resolve correctly
- Layout runs automatically on any property change to affected frames

Scene graph:
- New layout properties on SceneNode: layoutMode, itemSpacing,
  padding (4 sides), primaryAxisAlign, counterAxisAlign,
  primaryAxisSizing, counterAxisSizing, layoutWrap, layoutGrow,
  layoutPositioning, layoutAlignSelf, counterAxisSpacing

Properties panel:
- Auto-layout section for frames: direction toggles (none/vertical/
  horizontal), gap, padding (4 inputs), primary and counter axis
  alignment dropdowns

Import/export:
- .fig import maps stackMode/stackSpacing/stackPadding* fields
- Clipboard paste maps all Figma stack* properties
- Clipboard copy exports layout as Kiwi stackMode/stackSpacing/etc.
2026-02-27 23:51:21 +03:00
Danila Poyarkov 94c4635907 Add rotation, multi-select bbox, snap guides, text tool, color picker
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
2026-02-27 22:15:35 +03:00
Danila Poyarkov 323a1f90c4 Migrate React → Vue 3 + VueUse + Reka UI
- Vue 3 SFC components with <script setup>
- Reactive store (Vue reactivity) replacing React refs/state
- Composables: useCanvas, useCanvasInput, useKeyboard
- Components: EditorCanvas, LayersPanel, PropertiesPanel, Toolbar
- Floating toolbar (Figma UI3 style) with pill shape + shadow
- Properties panel with Design/Prototype tabs + zoom display
- Sections: Position, Rotation, Layout, Appearance, Fill, Stroke, Effects, Export
- CSS custom properties for theming (--panel-bg, --border, --accent, etc.)
- Add Figma UI3 CDP inspection findings (toolbar, panels, sections)
2026-02-27 21:25:31 +03:00