Commit graph

704 commits

Author SHA1 Message Date
Danila Poyarkov 5a83f03c4d Remove phantom changelog entry (instance work queue didn't ship) 2026-03-09 20:48:27 +03:00
Danila Poyarkov fe755fbb85 Release v0.9.0 2026-03-09 17:43:11 +03:00
Danila Poyarkov 3b6b526460 Changelog: event bus, collab reorder fix, microtask component sync 2026-03-09 16:30:58 +03:00
Danila Poyarkov 3e102ea55a Event-driven requestRender and component sync via SceneGraph emitter
Move requestRender() and component instance sync from manual call sites
to graph event subscriptions. Removes 79 manual requestRender() calls
(94→22) and all 9 syncIfInsideComponent calls.

Batching: component sync uses queueMicrotask — mutated node IDs
accumulate during a synchronous block, then deduplicate to ancestor
component IDs and call syncInstances once per component. requestRender()
is already rAF-batched (just increments counters checked by animation
frame loop), so multiple event-triggered calls in one sync block
collapse to a single repaint.

Event subscriptions:
- node:updated → invalidate render cache, schedule component sync, requestRender
- node:created/deleted/reparented/reordered → schedule component sync, requestRender
- Re-subscribes when graph instance is replaced (file open/reload)

Also fixes collab sync gap: subscribe to node:reordered in use-collab.ts
(same-parent reorder was never synced to Yjs peers).

Remaining requestRender() calls are for pure UI state changes (pen tool,
viewport pan/zoom, text editing, selection) and methods that bypass
events (detachInstance, bringToFront/sendToBack, restorePageFromSnapshot).
2026-03-09 16:13:03 +03:00
Danila Poyarkov abe108c473
Add event emitter to SceneGraph, refactor collab sync
- Add nanoevents to @open-pencil/core, emit typed events from
  SceneGraph mutation methods: node:created, node:updated,
  node:deleted, node:reparented, node:reordered
- Subscribe to node:updated in editor store for renderer
  invalidation (vector path + node picture cache) instead of
  inlining it in updateNode()
- Re-subscribe to graph events after file open/reload (graph
  instance is replaced)
- Replace monkey-patching of graph.updateNode in use-collab with
  event subscriptions for node:created, node:updated, node:deleted,
  node:reparented — fixes sync gaps for create/delete/reparent that
  the monkey-patch missed
- Clean up event subscriptions on collab disconnect
2026-03-09 16:02:54 +03:00
Danila Poyarkov aad6117522 Update changelog with AI chat fixes from #80 2026-03-09 15:55:03 +03:00
Danila Poyarkov e3ab8dab38 AI chat rendering, undo, visual verification, and provider compatibility
Rendering & layout:
- Fix COUNTER_ALIGN_MAP stretch mapping
- Direction-aware fill sizing based on parent flex direction
- Text without explicit width defaults to WIDTH_AND_HEIGHT auto-resize
- Padding auto-enables vertical auto-layout
- clipsContent propagated as Overflow.Hidden to Yoga
- Text height measurement for HEIGHT auto-resize mode
- Export x/y for absolute children, text w/h per textAutoResize
- Export STRETCH as fill on cross axis

Undo system:
- Undo for auto-layout and layer tree reorder
- Atomic undo for drag + reparent
- Page snapshot undo for AI tool operations
- mouseleave no longer terminates active drag

AI tools & vision:
- Add get_jsx, diff_jsx, and describe tools
- export_image returns image via toModelOutput
- Chunked uint8ArrayToBase64 (stack overflow fix)
- computeAllLayouts after AI tool execution
- onBeforeExecute/onAfterExecute receive ToolDef

Provider & UI:
- Add anthropic-compatible provider
- API type toggle for OpenAI-compatible
- Debug toolbar (dev only)
- Preserve chat on UI toggle
- Enhanced system prompt with JSX reference

Co-authored-by: Anton A S <eddclyde@yandex.ru>
2026-03-09 15:49:29 +03:00
Danila Poyarkov 9890b7c1a1 Enable Tailwind CSS class sorting in oxfmt
Sort classes using the same algorithm as prettier-plugin-tailwindcss,
configured for Tailwind v4 (reads src/app.css for theme).

Also sorts class strings inside twMerge() and tv() calls.

Eliminates 2 nested ternaries that conflicted with the formatter
(oxfmt strips parentheses that the lint rule requires).
2026-03-09 15:46:20 +03:00
Danila Poyarkov f2ef9c976d
XPath query for node selection (#82)
* Add XPath query for node selection

- packages/core/src/xpath.ts: XPath engine using fontoxpath with custom
  DOM facade over SceneGraph nodes
- RPC command: 'query' with selector, page, limit params
- CLI command: 'open-pencil query <file> <xpath>' with --json, --page, --limit
- AI/MCP tool: 'query_nodes' with XPath examples in description
- Queryable attributes: name, width, height, x, y, visible, opacity,
  cornerRadius, fontSize, fontFamily, fontWeight, layoutMode, itemSpacing,
  padding*, strokeWeight, rotation, locked, blendMode, text, lineHeight,
  letterSpacing

* Add tests and docs for XPath query

- 9 unit tests for queryByXPath/matchByXPath core functions
- 6 tool tests for query_nodes AI/MCP tool
- README: query examples in CLI section
- VitePress docs: patterns, queryable attributes, example output
- CHANGELOG: feature entry

* Lazy-load fontoxpath to avoid bundling in desktop app

- Dynamic import() instead of static import for fontoxpath
- queryByXPath/matchByXPath now async (they were sync before)
- RpcCommand.execute allows R | Promise<R> for async commands
- Fix 2 lint errors (unnecessary optional chain, always-truthy)
- Update all tests and CLI to await results
2026-03-09 15:32:27 +03:00
Danila Poyarkov 7f5a388e2b Code review: svg-export/ folder, restore comments, cleanup
- Move svg-export-{defs,paths}.ts into svg-export/ folder
  (defs.ts, paths.ts, index.ts) instead of prefix-based split
- Remove duplicate getChildren lambda in fig-import.ts importPages
- Remove dead dashPattern local in kiwi-convert.ts nodeChangeToProps
- Restore useful explanatory comments in instance-overrides.ts
  (DSD propagation, direct vs cloned assignments, seed skipping)
- Rename applyEffectOverrides → applyShapeAndEffectOverrides
  (handles polygon props + shadow + blur, not just effects)
- Format src/ with oxfmt
2026-03-09 15:22:14 +03:00
Danila Poyarkov 59981709ba Reduce cyclomatic complexity below 20 in all core functions
Refactor 24 complex functions across 14 files by extracting
dispatch branches and property-group handlers into focused helpers.

kiwi/kiwi-convert.ts (4 functions, was 22-81):
- nodeChangeToProps → 6 property-group converters
- convertOverrideToProps → 5 override applicators
- importStyleRuns → 3 style-run helpers
- mapNodeType → NODE_TYPE_MAP lookup table

kiwi/instance-overrides.ts (3 functions, was 28-44):
- applyDerivedSymbolData → resolveDsd* helpers
- applyComponentProperties → assignment/override applicators
- propagateOverridesTransitively → clone/sync helpers

kiwi/fig-import.ts (2 functions, was 34-40):
- importNodeChanges → buildChangeMaps, importPages, etc.
- importVariables → importCollections, resolveVariableType, etc.

render/export-jsx.ts (2 functions, was 70-85):
- collectProps → property-category extractors
- collectTailwindClasses → Tailwind class-group extractors

render/renderer.ts: propsToOverrides (78) → style-category helpers

renderer/scene.ts: renderNode (46), renderShapeUncached (30) →
  phase extractors (transforms, content, children, strokes)

renderer/renderer.ts: buildParagraph (34) → buildTruncateOpts,
  addStyledRuns

svg-export.ts: split into svg-export.ts + svg-export-defs.ts +
  svg-export-paths.ts; renderNode/renderTextNode/nodeShapeElements
  all reduced via helper extraction

kiwi-serialize.ts: sceneNodeToKiwi (44) → 6 property serializers
clipboard.ts: importClipboardNodes (36) → 5 focused helpers
scene-graph.ts: hitTestChildren (31) → containsPoint,
  hitTestOpaqueContainer, hitTestTransparentContainer
tools/analyze.ts: serializeNodeProps (23) → 4 prop serializers
2026-03-09 13:40:57 +03:00
Danila Poyarkov f3eac5ac8b Lint all packages: fix 193 errors across core, mcp, acp
Extend lint coverage from src/ to all packages (178 files, 140 rules).

Core (37 files):
- Remove 98 unnecessary conditions (dead ?./?? on non-nullish values)
- Remove 38 unnecessary type assertions (! and as casts)
- Convert 12 patterns to optional chain (a && a.b → a?.b)
- Fix 10 consistent-type-imports (merge duplicates, inline type keyword)
- Add parentheses to 16 nested ternaries
- Convert 4 indexed for-loops to for-of
- Merge 4 duplicate import statements

MCP (3 files):
- Replace typeof undefined check with 'in' operator
- Capture graph in local const to avoid ! assertions

ACP (1 file):
- Fix 5 TypeScript errors (type predicates, object shapes, dead code)
- Remove unnecessary template expression

Vendored kiwi-schema gets broader rule overrides to avoid touching it.
Core complexity stays as warning (24 functions over limit).
2026-03-09 13:18:08 +03:00
Danila Poyarkov d9154ef29d Lint CLI code: no-raw-console-format rule, fix all violations
Add no-raw-console-format lint rule for CLI commands — bans template
literals and string concatenation in console.log, forcing use of
agentfmt helpers (bold, dim, kv, entity, fmtTree, fmtList, etc.).

Extend lint target to include packages/cli/src/.

Fix all 22 lint errors in CLI code:
- Replace string concatenation with agentfmt (clusters, typography, variables)
- Mark optional CLI args as required: false (citty type accuracy)
- Remove unnecessary type assertions and optional chains
- Merge duplicate @open-pencil/core imports in headless.ts
- Extract exportViaApp/exportFromFile to reduce export.ts complexity
- Remove unused imports (formatType, printError)
- void runMain() for floating promise
2026-03-09 12:54:53 +03:00
Danila Poyarkov 4bdc533e3d Remove self-inflicted bug from changelog 2026-03-09 12:41:00 +03:00
Danila Poyarkov ba8af05f1b Update changelog with layer icons, frame title drag, toolbar fix 2026-03-09 12:40:07 +03:00
Danila Poyarkov 5549a2f46f Auto-layout icons in layer tree, frame title draggable on canvas
Layer tree shows layout-specific icons for auto-layout frames:
vertical → rows-3, horizontal → columns-3, grid → grid-3x3.
Only applies to FRAME nodes — components/instances keep their
purple diamond icon.

Clicking a selected top-level frame's name label on the canvas
now starts a drag instead of deselecting (hitTestFrameTitle).

Fix toolbar disappearing — TOOLS was incorrectly moved into
import type by consistent-type-imports, erasing it from the
runtime bundle.
2026-03-09 12:36:01 +03:00
Danila Poyarkov 96784e2895 Enforce complexity limit as error 2026-03-09 11:31:36 +03:00
Danila Poyarkov 1631d48c40 Reduce cyclomatic complexity below 20 in all functions
Extract dispatch branches into focused helpers:

use-canvas-input.ts (5 functions, was 21–61):
- onMouseDown → handleSelectDown, startPanDrag, tryStartRotation,
  tryStartResize, handleTextEditClick, duplicateAndDrag
- onMouseMove → dragNodes, updatePan, updateRotation,
  extendTextSelection, updateDrawShape, updateMarquee
- onMouseUp → commitDrag, commitDrawing, reparentOutsideNodes
- applyResize → constrainToAspectRatio
- computeAutoLayoutIndicatorForFrame → computeIndicatorPosition

use-keyboard.ts (was 39):
- onEventFired → shouldPreventDefault + Set-based lookups

server.ts (was 34):
- handleRequest → Partial<Record> command dispatch map

LayerTree.vue (was 22):
- updateDropTarget → classifyDropZone, resolveInsertPosition

use-text-edit.ts (was 27):
- onKeyDown → handleDeletion, handleHorizontalArrow, metaKeyActions map
2026-03-09 11:24:56 +03:00
Danila Poyarkov 4abc73eb47 Add max-lines, complexity, and more lint rules 2026-03-09 10:11:11 +03:00
Danila Poyarkov 2f06c65c7c Revert lint plugin to .js (oxlint uses Node.js internally), add new rules
Oxlint's JS plugin loader uses Node.js, not Bun — .ts fails in CI
with ERR_UNKNOWN_FILE_EXTENSION.

New rules:
- no-hand-rolled-color: flags rgba()/rgb() in template literals outside
  color.ts — use colorToCSS()/colorToHex() helpers instead
- typescript/no-unnecessary-boolean-literal-compare
- typescript/no-unnecessary-template-expression
- typescript/no-unnecessary-type-arguments
- unicorn/no-useless-fallback-in-spread
- unicorn/no-length-as-slice-end
- unicorn/no-unnecessary-await
- unicorn/prefer-string-starts-ends-with

Fixed violations: visible !== false → fill.visible in analyze.ts,
removed ?? {} fallbacks in style-runs.ts spread.
2026-03-09 10:05:18 +03:00
Danila Poyarkov df0ccbca48 Fix no-inline-named-types to match required members only
Ignore optional members instead of skipping the entire type.
{ x: number; y: number; label?: string } now correctly flags as
Vector, while { x?: number; y?: number } is still ignored.
2026-03-09 09:48:38 +03:00
Danila Poyarkov 5ef7111998 Rename lint plugin to .ts 2026-03-09 09:46:45 +03:00
Danila Poyarkov cd614ef8f7 Add custom JS lint rules, replace inline types with named imports
Three oxlint JS plugin rules in lint/plugin.js:

- no-inline-named-types: configurable shape→name map catches inline
  { x: number; y: number } etc. and suggests Vector, Color, GUID,
  Rect, Matrix from @open-pencil/core
- no-structuredclone-scene-arrays: flags structuredClone on
  fills/strokes/effects — use typed copy helpers instead
- no-math-random: bans Math.random() in favor of crypto

Fixed 40 violations across 13 files: replaced inline type literals
with named imports, switched figma-api.ts structuredClone calls to
copyFills/copyStrokes/copyEffects.
2026-03-09 09:41:28 +03:00
Danila Poyarkov 4969447669 Add vue/no-multiple-slot-args rule 2026-03-09 09:20:01 +03:00
Danila Poyarkov 17faf4df93 Add strict lint rules to catch AI-generated slop at compile time
New rules:
- typescript/no-unnecessary-condition: catches redundant ?., ??, and
  always-true/false branches (22 violations fixed)
- typescript/consistent-return: mixed return/no-return in functions
- typescript/no-unnecessary-type-parameters: unused generic params
- typescript/prefer-for-of: use for-of instead of indexed loops
- unicorn/no-nested-ternary: forbid nested ternaries
- unicorn/consistent-existence-index-check: !== -1 over >= 0
- no-empty-function: no empty function bodies

Fixed all 32 violations: removed unnecessary optional chaining on
non-nullish values, tightened Record types to Partial<Record> where
index access can return undefined, replaced nested ternary with
lookup table, added exhaustive default cases.
2026-03-09 09:16:07 +03:00
Danila Poyarkov f96fffb83f Harden oxlint: consistent-type-imports, no-duplicates, no-misused-promises, prefer-optional-chain
Add 6 new lint rules: typescript/consistent-type-imports,
typescript/no-unnecessary-type-assertion, typescript/prefer-optional-chain,
typescript/no-misused-promises, import/no-duplicates, import/no-mutable-exports.
Ban @/types shim via no-restricted-imports.

Fix all violations: replace inline import() annotations with proper
type imports, merge duplicate module imports, add void to fire-and-forget
promises, convert && chains to optional chaining.
2026-03-09 09:05:04 +03:00
Danila Poyarkov 33389a95ad Remove re-export shims and import types directly from core
Delete src/types.ts (pure re-export of Color, Rect, etc.) — 9 files
now import directly from @open-pencil/core. Remove dead AI_PROVIDERS
re-export from use-chat.ts and dead font function re-exports from
engine/fonts.ts.
2026-03-09 08:56:47 +03:00
Danila Poyarkov 7c584db1b7 Fix copyFill missing gradientTransform/imageTransform, add copy tests
Deep-copy gradient and image transforms in copyFill to prevent
shared-reference mutations. Add mutation-isolation tests for all
copy helpers. Fix benchmark to use precomputed IDs and actual
copyFills function.
2026-03-09 08:50:28 +03:00
Danila Poyarkov 66c80685bc Add performance improvements to changelog 2026-03-09 02:58:40 +03:00
Danila Poyarkov 2f03f12210 Add performance benchmark for parse/export/node operations
mitata-based benchmarks covering parseFigFile, exportFigFile,
SceneGraph operations, and structuredClone vs typed copy comparison.
2026-03-09 02:58:24 +03:00
Danila Poyarkov 35bb98d1c8 Filter unzip entries during .fig parse to skip non-essential files
Only decompress canvas.fig, canvas, and images/* entries.
Avoids decompressing metadata and other cruft in large .fig files.
2026-03-09 02:58:13 +03:00
Danila Poyarkov cf6747389a Replace structuredClone with typed copy helpers in hot paths
copyFill/copyStroke/copyEffect/copyStyleRun use spread-based shallow
copies instead of structuredClone (~24× faster for fills). Applied in
SceneGraph.copyProp and instance override syncNodeProps.
2026-03-09 02:58:07 +03:00
Danila Poyarkov 3cd0e47fc8
Merge pull request #78 from open-pencil/detach-orphaned-instances
Detach orphaned instances to FRAME on clipboard paste
2026-03-09 00:11:05 +03:00
Danila Poyarkov 97fb51d0ec
Merge pull request #77 from open-pencil/fix-paste-figma
Fix text typography import from Figma clipboard
2026-03-09 00:11:02 +03:00
Danila Poyarkov f5ea2a43cf Detach orphaned instances to FRAME on clipboard paste
When pasting from Figma, instances may reference components that aren't
included in the clipboard data (e.g. components on a different page).
These instances stayed empty and invisible on canvas.

Convert them to FRAME so their own fills, strokes, corner radius, and
layout properties render visually.

Fixes #36
2026-03-09 00:01:04 +03:00
Danila Poyarkov 0f0f2137e5 Fix text typography import from Figma clipboard
- Handle RAW lineHeight units (multiplier × fontSize) — was returning null
- Convert letterSpacing with percent units in style overrides — was using raw value
- Normalize variable font names (e.g. 'Inter Variable' → 'Inter') for Google Fonts and local font lookup

Fixes #36
2026-03-08 23:52:20 +03:00
Danila Poyarkov 4a6dd703c8 Emit derivedTextData and textUserLayoutVersion for text nodes
Figma needs derivedTextData (layoutSize, fontMetaData with font digest)
and textUserLayoutVersion to render text in .fig files. Without these
fields, text nodes appear as empty boxes when opened in Figma.

- Add DerivedTextData message to kiwi schema (field 359)
- Add derivedTextData and textUserLayoutVersion to NodeChange codec
- Compute SHA-1 font digests from loaded font binaries at export time
- Emit textData.lines with lineType: PLAIN for each paragraph
- Expose getLoadedFontData() from fonts module for digest computation

Closes #64
2026-03-08 23:28:54 +03:00
Danila Poyarkov 5d259b95c3 Merge branch 'fix-variables-serialization'
# Conflicts:
#	CHANGELOG.md
2026-03-08 23:10:45 +03:00
Danila Poyarkov 9c110bff92 Add Vue linting rules and fix all violations
Enable 12 vue/ rules in oxlint: lifecycle, props, emits, imports.
Convert 9 components from `const props = defineProps()` to
destructured form (Vue 3.5+ reactive destructuring).
2026-03-08 23:08:27 +03:00
Danila Poyarkov 7a334268d0 Fix all type-aware lint warnings
Add void prefix to 23 fire-and-forget promises across 10 files.
Fix no-base-to-string in bridge.ts by using Buffer.from().toString().
Promote no-floating-promises from warn to error.
2026-03-08 23:04:44 +03:00
Danila Poyarkov fa3d7b8a8f Enable type-aware linting with tsgo in oxlint
Add oxlint-tsgolint dependency and --type-aware --type-check flags.
This replaces the separate tsgo --noEmit step — oxlint now handles
both linting and type checking in a single pass.

Add typescript/no-floating-promises as a warning rule.
2026-03-08 23:02:25 +03:00
Danila Poyarkov 5cd7cc12b2 Fix double-click rename in layer tree and pages panel
Prevent reka-ui TreeItem from toggling expand/collapse on click —
expand is handled by the chevron button with @click.stop.

Match page rename input styling to layer tree: show file icon next
to the input, use the same compact border/padding classes.
2026-03-08 22:58:38 +03:00
Danila Poyarkov cfea96a983 Add variable serialization fix to changelog 2026-03-08 22:23:16 +03:00
Danila Poyarkov 5a3e68eade Extract named types, deduplicate field maps, fix internal canvas GUID
- Define VariableAnyValue, VariableDataEntry, VariableConsumptionEntry,
  VariableDataValuesEntry in codec.ts — remove inline type monsters
- Extract VARIABLE_BINDING_FIELDS / VARIABLE_BINDING_FIELDS_INVERSE to
  kiwi-convert.ts — single source of truth for export and import
- Track internal canvas GUID during page loop instead of recalculating
  with incorrect idx+2 formula (broke when scene nodes existed)
- Remove unused modeGuidToId map, unnecessary as-casts
2026-03-08 22:22:26 +03:00
Danila Poyarkov c51f34689f
Multi-provider AI, sucrase JSX, Figma zoom shortcuts (#72)
* Add multi-provider AI support (Anthropic, OpenAI, Google AI, OpenAI-compatible)

- Add AI_PROVIDERS registry with per-provider model lists, key placeholders, and URLs
- Refactor use-chat composable: provider factory creates the right AI SDK model
- Per-provider API key storage in localStorage with automatic legacy migration
- New ProviderSetup.vue replaces APIKeySetup.vue with provider selector
- New ProviderSettings.vue popover accessible from gear icon in chat input
- OpenAI-compatible provider with custom base URL and model ID fields
- Install @ai-sdk/anthropic and @ai-sdk/google dependencies
- Update E2E tests for new provider setup flow

* Fix reactivity, key masking, missing model ID field, and chat reset on model change

- Use ref instead of computed for apiKey — computed getter over localStorage
  wasn't triggering reactivity when the key was set
- Load stored key for new provider in providerId watcher
- Reset chat on modelId and customModelId change, not just provider switch
- Add custom model ID field to ProviderSetup.vue for OpenAI-compatible
- Simplify ProviderSettings key field — empty input with contextual placeholder
  instead of fragile dot-masking that broke on partial edits
- Use data-test-id locator for model selector in E2E test

* Use useLocalStorage from vueuse instead of manual ref + watch + localStorage

Replaces 5 hand-rolled ref/watch/localStorage sync pairs with useLocalStorage.
API key uses a computed storage key that rebinds when the provider changes.

* Use useLocalStorage for collab name persistence

* Review fixes: changelog placement, remove dead class, cursor-pointer

* Extract ProviderSelect component and uiInput helper

- ProviderSelect.vue: shared select dropdown for AI providers
- ui/input.ts: shared input styling (sm/md sizes)
- Remove 7 duplicated input class strings across chat components

* Use uppercase acronyms: keyURL, customBaseURL, supportsCustomBaseURL

* Match select dropdown width to trigger via --reka-select-trigger-width

Move min-w-[var(--reka-select-trigger-width)] into selectContent base
style so all selects get it by default. Remove per-component overrides.

* Uppercase acronyms in variable names, improve setup form layout

- providerID, modelID, customModelID, setAPIKey, AIProviderID
- Keep apiKey lowercase at start (standard JS convention)
- Compact setup form: single column, full-width Connect button
- Fix grammar: 'a OpenRouter' → 'an OpenRouter'
- Shorter promo text

* Allow text selection in chat panel

* Fix chat E2E test: Save → Connect button text

* Disable mermaid in chat markdown renderer

Alias mermaid and beautiful-mermaid to empty shims in Vite config,
preventing vue-stream-markdown from attempting to load them.

* Interleave text and tool calls in chat messages

Render message parts in order instead of grouping all tool calls
first then all text. Use SDK's isToolUIPart/isTextUIPart/getToolName
instead of custom type guards. Fix error state: output-error, not error.

* Implement figma.viewport.scrollAndZoomIntoView()

Figma Plugin API method that centers the viewport on given nodes.
Reuses the same bounding box logic as the viewport_zoom_to_fit tool.

* Catch tool execution errors and return them to the AI

Instead of crashing with an unhandled exception, tool errors are
caught and returned as { error: message } so the AI can retry or
explain the failure. UI detects error outputs and shows them in red
with the error message when expanded.

* Replace esbuild with sucrase for JSX transform

Sucrase is a pure JS transform (201 KB / 46 KB gzip) that works in
both Node/Bun and the browser. Replaces esbuild (13 MB WASM) which
only worked in Node/Bun.

- buildComponent() and renderJSX() are now synchronous
- render tool works in browser AI chat (no more 'esbuild required')
- Handles full JS expressions (map, ternaries, Array.from, etc.)

* Update system prompt to prioritize render tool with JSX

Document available tags, props, layout, text, and sizing options.
Instruct the AI to use full JS expressions in JSX for complex layouts.

* Fix zoom shortcuts to match Figma

Cmd+0: Zoom to 100% (was incorrectly mapped to Zoom to fit)
Cmd+1: Zoom to fit
Cmd+2: Zoom to selection
Shift+1 / Shift+2: same as Cmd+1 / Cmd+2

Add zoomTo100() and zoomToSelection() to editor store.
Refactor zoomToFit() to use shared zoomToBounds() helper.

* Refactor keyboard shortcuts to useMagicKeys

Replace manual keydown handler with VueUse useMagicKeys + whenever
for declarative shortcut registration.

- mod() helper for cross-platform Meta/Control shortcuts
- plain() helper for modifier-free keys
- Proper modifier exclusion (⌘G vs ⌘⇧G no longer conflict)
- Add E2E tests: duplicate, zoom (⌘0/⌘1/⌘2/⇧1/⇧2), auto-layout (⇧A)
- All 26 keyboard shortcut tests pass

* Fix scrollAndZoomIntoView to actually zoom

Match Figma Plugin API behavior (equivalent to Shift-1): compute
zoom level that fits all nodes with padding, capped at 100%.
Previously only set center without adjusting zoom.
2026-03-08 22:05:18 +03:00
Danila Poyarkov d9fbc6fb92 Serialize variables, collections, and bindings to .fig files
Add VARIABLE_SET node type, variable-related messages (VariableSetMode,
VariableSetID, VariableDataValues, VariableScope, VariableDataMap, etc.)
to the Kiwi schema.

Export: emit VARIABLE_SET and VARIABLE nodeChanges on an internal-only
canvas page, include variableConsumptionMap on nodes with boundVariables.

Import: rewrite variable import to use variableSetModes, variableSetID,
variableResolvedType, and variableDataValues (multi-mode COLOR/FLOAT/
BOOLEAN/STRING/ALIAS). Import variableConsumptionMap bindings.

Closes #65
2026-03-08 21:44:01 +03:00
Danila Poyarkov 9752f33486 Merge branch 'master' of github.com:open-pencil/open-pencil 2026-03-08 19:59:27 +03:00
Danila Poyarkov 845e3aa8b9 Merge branch 'docs-ai-automation' 2026-03-08 19:59:06 +03:00
Danila Poyarkov c714feb44a
Add "AI & Automation" docs section, CLI reference, formatting fixes (#75)
* Add AI & Automation docs section, CLI reference, formatting fixes

New 'AI & Automation' top-level nav section with 9 pages:
- AI Chat: setup, 87 tools, example prompts
- Collaboration: room sharing, cursors, follow mode
- JSX Renderer: elements, style props, visual diffing
- CLI (4 pages): inspecting, exporting, analyzing, scripting
- MCP Server: moved from Reference, setup + 90 tools

New CLI reference page in Reference with all 12 commands,
every option, alias, and default value.

Formatting fixes across all 7 locales:
- Wrap enum values in backticks (FRAME, SOLID, ROUND, etc.)
- Fix single-backtick code blocks → triple backticks
- Use <kbd> for all keyboard shortcuts
- Remove implementation details from user guide
- Delete stale eval-command.md and mcp-tools.md (14 files)
- Update dead links to new locations

* Translate AI & Automation + CLI reference to all 6 locales

48 translated pages (de, es, fr, it, pl, ru) for the Programmable
section: overview, AI Chat, Collaboration, JSX Renderer, CLI
(inspecting, exporting, analyzing, scripting).

CLI reference and MCP server pages copied as-is (mostly code/tables).

* Update README and docs for CSS Grid support

- Add 'Auto layout & CSS Grid' to README features
- Update architecture, tech-stack, features, figma-comparison, comparison
- Mark grid as ✅ in all 7 locales
- Replace 'blocked on upstream' with Yoga fork links
2026-03-08 18:30:40 +03:00
Danila Poyarkov c98ac0f230 Update README and docs for CSS Grid support
- Add 'Auto layout & CSS Grid' to README features
- Update architecture, tech-stack, features, figma-comparison, comparison
- Mark grid as ✅ in all 7 locales
- Replace 'blocked on upstream' with Yoga fork links
2026-03-08 18:29:44 +03:00