Commit graph

541 commits

Author SHA1 Message Date
Anton Soldatov 168c25c189
feat: mobile layout, PWA support (#27) (#27)
- Mobile-responsive editor with bottom drawer, ribbon nav, HUD overlay
- Touch support: single-finger tools, two-finger pinch-zoom
- PWA: manifest, service worker, installability
- Extract LayerTree from LayersPanel, shared utils (colorToCSS, initials, toolIcons)
- Constants moved to src/constants.ts, clipboard state to editor store
- reka-ui Popover/DropdownMenu in MobileHud

Co-authored-by: Danila Poyarkov <dev@dannote.net>
2026-03-05 19:51:51 +03:00
Danila Poyarkov 661b83e749 Port analyze/diff tools from figma-use, split tools into domain files
Analyze: colors, typography, spacing, clusters
Diff: diff_create (tree diff), diff_show (preview changes)
Utility: get_components, get_current_page, arrange, node_to_component

Split schema.ts (2600 lines) into read, create, modify, structure,
variables, vector, analyze, registry — each under 600 lines.

Clean up inline types (use Color, Vector, SceneNode from existing defs).
Update AGENTS.md and CONTRIBUTING.md with code quality guidelines.
2026-03-05 19:16:23 +03:00
Danila Poyarkov e7f827c0e3 Fix npm publish: remove manual .npmrc that overwrote setup-node auth 2026-03-05 10:58:55 +03:00
Danila Poyarkov c67c60a42c Fix Windows build: add shell: bash to contributor step 2026-03-05 10:44:32 +03:00
Danila Poyarkov be44b4d404 Release v0.7.0 2026-03-05 10:32:35 +03:00
Danila Poyarkov ed61445e2d
SVG export (#52)
* Add SVG export

Scene graph → SVG serializer with support for rectangles, ellipses,
lines, stars, polygons, vectors (fill/stroke geometry and vector
networks), text with style runs, gradients (linear, radial, angular),
image fills, effects (drop shadow, inner shadow, blur), opacity,
rotation, flips, blend modes, clip paths, and nested groups.

- packages/core: svg-node.ts (minimal XML builder), svg-export.ts
  (serializer), ExportFormat now includes 'SVG', computeContentBounds
  extracted for reuse
- packages/cli: export command accepts --format svg
- UI: SVG added to ExportSection format picker (scale hidden for SVG),
  context menu 'Export as SVG' option
- 47 new tests covering SVGNode builder, geometry blob → path, vector
  network → path, and full node export for all shape types

* Update changelog

* Add export_svg tool to ToolDefs

Available via MCP, AI chat, and CLI eval command.

* Replace standalone export items with Copy/Paste as submenu

Figma-style submenu: Copy as text, Copy as SVG, Copy as PNG (⇧⌘C),
Copy as JSX. Keeps Export as PNG (⇧⌘E) as a separate item.

* Remove standalone Export as PNG from context menu

* Update changelog
2026-03-05 10:31:02 +03:00
Danila Poyarkov b7e5ea2d45 Fix effect ordering test, add contributors to release notes
Fix renderer-effects test: check drawStrokeWithAlign call site instead of
drawNodeStroke helper definition (stroke align refactor moved the method).

Add a step to build.yml that resolves git authors to GitHub usernames via
the compare API and appends a Contributors section to release notes.
2026-03-05 10:04:50 +03:00
Danila Poyarkov 0047a25cae Fix effect ordering test, add contributors to release notes
Fix renderer-effects test: check drawStrokeWithAlign call site instead of
drawNodeStroke helper definition (stroke align refactor moved the method).

Add a step to build.yml that resolves git authors to GitHub usernames via
the compare API and appends a Contributors section to release notes.
2026-03-05 09:56:13 +03:00
Danila Poyarkov 78f249a7c5 Merge remote-tracking branch 'origin/fix-render-jsx-windows'
# Conflicts:
#	CHANGELOG.md
2026-03-05 09:51:26 +03:00
Danila Poyarkov ad96b1c654 Add CJK font fallback for text rendering
Load a system CJK font (PingFang SC, Microsoft YaHei, Noto Sans CJK, etc.)
at startup and pass it as a fallback in CanvasKit's fontFamilies array.
Falls back to Google Fonts (Noto Sans SC) when no system font is available.

Also fixes loadFont to only cache fonts that successfully register with
CanvasKit's TypefaceFontProvider, preventing invalid font data from being
treated as loaded.

Closes #48
2026-03-05 09:48:11 +03:00
Danila Poyarkov 43e0274213 Add changelog entry 2026-03-05 09:20:25 +03:00
Danila Poyarkov 3199abb4e6 Fix render tool on Windows: add .js extension to dynamic imports
ESM dynamic imports require explicit file extensions for resolution on
Windows + Bun. The extensionless '../render/render-jsx' path worked on
macOS/Linux but failed on Windows with 'Cannot find module' error.

Fixes #43
2026-03-05 09:19:15 +03:00
Danila Poyarkov d6922caf00 Add ACP agent package with tests 2026-03-05 09:14:54 +03:00
Danila Poyarkov 1ee0f86af3
Merge pull request #49 from open-pencil/reka-color-picker
Replace custom color picker with Reka UI Color components
2026-03-05 09:05:40 +03:00
Danila Poyarkov 29096fa56b Merge remote-tracking branch 'origin/master' into reka-color-picker
# Conflicts:
#	src/components/HsvColorArea.vue
2026-03-05 09:05:06 +03:00
Danila Poyarkov d060e32784 Merge origin/master (PR #41 data-test-ids) 2026-03-05 09:03:26 +03:00
Danila Poyarkov 38e7fdd4ac Fix hit-testing: scope hover to current page, click-through empty containers
- Scope hover hit-test to current page (was searching all pages including
  internal component page, causing ghost hover outlines)
- Frames/sections without visible fills or strokes are click-through
- Groups are always click-through (only children are hittable)
- Clipping parents reject hits outside their bounds
- Instances/components check children before falling back to fill check
- Switch test fixtures from material3.fig (55MB) / nuxtui.fig (82MB) to
  gold-preview.fig (537KB) for fast dev runs, gate heavy fixtures behind
  BUN_HEAVY_TESTS env var (enabled in CI)
2026-03-05 08:56:35 +03:00
Danila Poyarkov 82df928b56 s/reka-ui/Reka UI/ in changelog 2026-03-05 08:00:49 +03:00
Danila Poyarkov 5b3f0368e5 Update changelog 2026-03-05 07:59:24 +03:00
Danila Poyarkov 69c8096c44 Fix direction-aware vector path rendering for chains without regions
vectorNetworkToPath drew all segments forward (to seg.end), which
broke when buildChains traversed a segment in reverse. Closed shapes
like parallelograms had degenerate last segments.

Fix: track current vertex through the chain and draw each segment in
the correct direction (forward or reverse), swapping bezier control
points when reversed.

Region loops (addLoopToPath) are left unchanged — Figma stores loop
segments pre-oriented so they should always be drawn forward.
2026-03-05 07:55:09 +03:00
Danila Poyarkov a4fa7df45c Code review: restore BFS for DSD propagation, fix preComputedRoot cache key
- Replace O(N²) iterative convergence loop with BFS that correctly
  handles intermediate DSD-targeted clones (push to queue regardless
  of visited, only skip size update for dsdSizeSet nodes)
- Fix preComputedRoot cache lookup: was using childId instead of
  child.componentId as key
- Remove stray blank line
2026-03-05 00:05:45 +03:00
Danila Poyarkov 02b12224cc Fix override resolution for sibling instances of the same component
findNodeByComponentId used root-matching which returned the first
child when multiple siblings shared the same component root. This
caused all symbolOverride guidPaths targeting different siblings
(e.g., 3 _stepper instances inside a Stepper component) to resolve
to the first sibling.

Fix: separate into 3 passes — exact componentId match first, then
unambiguous root match (only when exactly one child shares the root),
then deep recursion.
2026-03-04 23:48:52 +03:00
Danila Poyarkov 829bafa698 Guard geometryBlobToPath against non-Uint8Array blobs
Return empty path if blob is missing or its buffer is not an
ArrayBuffer, preventing DataView constructor crash during rendering.
2026-03-04 23:27:20 +03:00
Danila Poyarkov 7dbba7b2af Apply component property assignments from the instance's own kiwi data
Previously only applied CPA from cloned instance sources (via
componentId chain). Instances created directly from kiwi data (e.g.,
_datepicker with boolValue=false for 'Date Range List') never had
their own CPA processed, leaving children visible that should be
hidden.
2026-03-04 23:10:57 +03:00
Danila Poyarkov 0742f897d8 Fix deep overrides being clobbered by syncChildrenDeep
syncChildrenDeep was syncing all children from source to clone,
overwriting nodes that were directly targeted by symbolOverrides
(e.g., white stroke on a Vector set by _icon-xs SO being overwritten
with dark gray from the check component's default).

Fix: pass the seeds set through to syncChildrenDeep so it skips
nodes that were explicitly overridden.
2026-03-04 23:00:32 +03:00
Danila Poyarkov f6a12cc736 Fix DSD propagation through intermediate DSD-targeted clones
Replace BFS (which skipped DSD-modified nodes in visited set, breaking
the chain for deeper clones) with iterative convergence loop. Track
dsdSizeSet separately from dsdModified so nodes with only position/
geometry changes inherit size from their source.

Fix transitive override propagation: re-clone from source (not component)
to preserve componentId chain for DSD. Add syncChildrenDeep for deep
property propagation (stroke colors on nested vectors).

Reduces vector overflow in gold-preview.fig from 25 to 0 (visible,
unclipped).
2026-03-04 22:47:32 +03:00
Danila Poyarkov ec8fce1b09 Fix DSD clone propagation: always traverse full BFS tree
Intermediate clones may already have correct values but their own
clones (deeper levels) may not. Must always push to queue to reach
the full clone chain.
2026-03-04 21:46:57 +03:00
Danila Poyarkov 5ebfe2388c Clean up instance-overrides: remove stale comment, fix BFS propagation 2026-03-04 21:29:09 +03:00
Danila Poyarkov 7a645d8576 Fix DSD resolution for instance-swapped children
derivedSymbolData guidPaths reference the post-swap tree structure
(e.g., heart Vector inside a swapped home slot), but resolution was
running before overrides modified the tree. After repopulateInstance,
the old children are deleted and replaced with new ones from the
swapped component, making pre-resolved targets stale.

Changes:
- Move DSD resolution to after all overrides (symbolOverrides,
  transitive sync, componentProperties) so guidPaths can reach
  the swapped-in children
- Pre-compute componentId root map while all internal page nodes
  are alive — instance swaps delete intermediate clones, breaking
  componentId chains used by findNodeByComponentId
- Use preComputedRoot in findNodeByComponentId to match across
  clone levels even when intermediate nodes are deleted
- Propagate DSD changes through clone chains via BFS
2026-03-04 21:26:00 +03:00
Danila Poyarkov b3e1bbdb09 Replace custom color picker with reka-ui Color components
Upgrade reka-ui 2.8.2 → 2.9.0 and replace the hand-rolled HSV color
area, hue slider, alpha slider, and hex input with reka-ui's new
ColorArea, ColorSlider, and ColorField primitives.

- ColorAreaRoot + ColorAreaArea handle the 2D saturation/brightness
  picker with proper gradient backgrounds and keyboard navigation
- ColorSliderRoot + ColorSliderTrack for hue and alpha channels with
  automatic gradient rendering
- ColorFieldRoot + ColorFieldInput for hex color text input with
  arrow key increment/decrement support
- Bridge between our Color type (0–1 range) and reka-ui's Color
  (0–255 RGB) via hex string serialization

Removes ~110 lines of custom HSV↔RGB conversion math, pointer capture
handling, and CSS gradient computation.
2026-03-04 20:52:30 +03:00
Danila Poyarkov d55eb7ce9e Replace inline import() type annotations with top-level imports 2026-03-04 20:03:55 +03:00
Danila Poyarkov 872c53dd9b Fix override resolution for deleted internal page nodes
Two fixes for component instances with multi-level nesting:

1. getComponentRoot now falls back to kiwi symbolData when graph
   nodes are deleted (internal page cleanup). Resolves the component
   chain via symbolData.symbolID so findNodeByComponentId can match
   clones whose componentId points to a deleted source node.

2. propagateOverridesTransitively skips nodes that were directly
   targeted by applySymbolOverrides. Previously the sync would
   overwrite override values (e.g. text 'Health' → 'Badge') with
   the source component's defaults.
2026-03-04 20:02:41 +03:00
Danila Poyarkov 8b05fa6ea4 Update changelog with vector rendering and nested property fixes 2026-03-04 19:30:46 +03:00
Danila Poyarkov 8a6746fa9d Merge branch 'fix-vector-rendering' into compatibility-fixes 2026-03-04 19:25:45 +03:00
Danila Poyarkov 8f740ce093 Fix vector rendering with pre-computed geometry and stroke outlines
Use fillGeometry/strokeGeometry blobs from .fig files for pixel-perfect
vector rendering. These pre-computed outlines account for stroke alignment,
caps, joins, and miter limits — eliminating white gaps between adjacent
stroked shapes.

For clipboard paste (where geometry blobs are unavailable), convert
vectorNetwork paths to filled stroke outlines via CanvasKit path.stroke().

Apply derivedSymbolData transforms, sizes, and geometry during import
so instance children render at the correct scale and position. When
derivedSymbolData provides size but no geometry, scale inherited blobs
proportionally.
2026-03-04 19:21:18 +03:00
Danila Poyarkov 41e5c08434 Fix component property assignments for nested instances
Three issues fixed:

1. componentPropAssignments inside symbolOverrides are scoped to the
   nested instance their guidPath resolves to, not the top-level
   instance. Resolve guidPath first, then apply to that subtree.

2. Instance swap values can use guidValue (GUID object) in addition
   to textValue (string). Handle both formats.

3. Transitive clone sync was running BEFORE component properties,
   causing repopulateInstance to wipe children that had just received
   visibility assignments. Reorder: symbolOverrides → transitive sync
   → componentProperties → derivedSymbolData.

Also extracted propagateOverridesTransitively into its own function
and guarded against propagating undefined values that would overwrite
explicitly-set properties.
2026-03-04 18:50:29 +03:00
Danila Poyarkov 5fa8895ef7 Update changelog with component properties and derivedSymbolData fixes 2026-03-04 18:14:23 +03:00
Danila Poyarkov 850490a224 Clean up instance-overrides: extract helpers, remove dead type
- Extract swapInstanceComponent() — reused by symbolOverrides and
  componentPropAssignments
- Extract findPropRefs() — componentId chain walk as standalone fn
- Flatten applyPropAssignments() into a recursive fn instead of
  nested closure inside a loop
- Remove unused ComponentPropDef interface
- Replace per-node Set allocation with depth-limited loop
- Remove unnecessary Record<string, unknown> cast
2026-03-04 18:12:30 +03:00
Danila Poyarkov 311fc76616 Apply derivedSymbolData sizes on import
Figma pre-computes child dimensions for each combination of
component property values and stores them in derivedSymbolData.
Apply these size overrides using the same guidPath resolution
as symbolOverrides so containers shrink correctly when boolean
properties hide children.
2026-03-04 18:05:24 +03:00
Danila Poyarkov a0a93ff500 Apply component property assignments on import
Evaluate componentPropAssignments from instances against
componentPropRefs on cloned children. Handles VISIBLE (boolean
toggle) and OVERRIDDEN_SYMBOL_ID (instance swap) property fields.
Walks componentId chain to find propRefs on source nodes.
2026-03-04 18:00:01 +03:00
Anton Soldatov 3ac016b806
Add data-test-id attributes to all Vue components (#41) 2026-03-04 17:18:26 +03:00
Danila Poyarkov efdb09852f Fix override resolution for nested instance targets
When an override targets an instance node directly (not a descendant),
the node's componentId matches the remapped target. Check the current
node itself before searching descendants. Also prefer exact componentId
match over root-chain match for more precise resolution.
2026-03-04 17:04:49 +03:00
Danila Poyarkov 24207932f2 Fix stdin test: use pipe mode with explicit write/end
Buffer stdin may be consumed before the subprocess is ready on Linux.
Using pipe mode with explicit write/end ensures the data is available.
2026-03-04 16:56:58 +03:00
Danila Poyarkov c365f9f3b9 Fix flaky stdin test: use Buffer instead of ReadableStream 2026-03-04 16:41:26 +03:00
Danila Poyarkov b3b3e6256d Increase test timeout for CLI subprocess tests
CI runners are slower — 5s default timeout isn't enough for tests that
spawn bun subprocesses loading 55MB+ .fig fixtures.
2026-03-04 15:21:57 +03:00
Danila Poyarkov 26c14ac903 Unify instance override logic between .fig import and clipboard paste
Extract populateAndApplyOverrides() into instance-overrides.ts shared
by fig-import.ts and clipboard.ts. The clipboard path previously only
handled single-level guid paths and missed nested overrides, instance
swaps, and transitive propagation.
2026-03-04 15:15:19 +03:00
Danila Poyarkov 185e9e781b Fix CI: enable Git LFS checkout for test fixtures 2026-03-04 14:57:13 +03:00
Danila Poyarkov aa0a1b5def Fix workspace resolution: add bun export condition to core package
Bun picks the "node" condition pointing to dist/index.js which doesn't
exist in CI or fresh checkouts. Explicit "bun" condition ensures Bun
always resolves to source files.
2026-03-04 14:51:44 +03:00
Danila Poyarkov d087eb9b5d Update changelog with instance override fixes and profiler 2026-03-04 14:22:30 +03:00
Danila Poyarkov 74b09d4397 Merge branch 'renderer-profiler' into compatibility-fixes 2026-03-04 14:19:35 +03:00