Commit graph

229 commits

Author SHA1 Message Date
Danila Poyarkov fe755fbb85 Release v0.9.0 2026-03-09 17:43:11 +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 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 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 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 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 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 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 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 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 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
Danila Poyarkov bfa4b7de8b 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).
2026-03-08 18:25:35 +03:00
Danila Poyarkov e5b147148e 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
2026-03-08 18:25:35 +03:00
Danila Poyarkov 45df90c5c7
CSS Grid layout mode (#73)
* Switch to @open-pencil/yoga-layout with CSS Grid support

Use our fork (open-pencil/yoga, grid branch) which cherry-picks the
upstream CSS Grid PRs (#1893–#1898) onto current main. The JS bindings
are ported from the old embind approach to the new wasm_bridge.c API.

npm:@open-pencil/yoga-layout alias keeps all imports as 'yoga-layout'.

Also handle FinalizationRegistry change (upstream #1908) — node.free()
no longer exists, nodes are garbage collected automatically.

* Add CSS Grid layout mode

Scene graph:
- LayoutMode gains 'GRID' option alongside HORIZONTAL/VERTICAL
- GridTrack type (sizing: FIXED/FR/AUTO, value) for track definitions
- GridPosition type (column, row, columnSpan, rowSpan) for children
- New node props: gridTemplateColumns/Rows, gridColumnGap, gridRowGap,
  gridPosition

Layout engine:
- buildGridTree() sets Display.Grid and maps GridTrack[] to Yoga's
  setGridTemplateColumns/Rows API with FR/Points/Auto track types
- Grid children use gridPosition for column/row placement with span
- Flex layout path unchanged

Store:
- setLayoutMode('GRID') auto-creates NxM track grid based on child
  count (sqrt heuristic), defaults to 1fr tracks

UI (LayoutSection.vue):
- Grid button (grid-2x2 icon) added to flow direction row
- Columns/Rows track editors: ScrubInput for value + AppSelect for
  sizing mode (Fill fr / Fixed px / Auto), add/remove buttons
- Separate column gap and row gap ScrubInputs
- Wrap button hidden when grid is active
- Flex alignment grid hidden when grid is active

Kiwi serialization skips GRID mode for now (no .fig codec support).

* Add grid layout integration tests

12 new test scenarios covering:
- Basic 2x2 grid, fixed columns, mixed fr/fixed, unequal fr weights
- Column gap, row gap, both gaps combined
- Padding offsets
- Explicit gridPosition placement, column span, row span
- Absolute children skipped
- Hidden children collapsed
- Nested grid inside flex parent (computeAllLayouts)

Also fix: grid frames as children of flex parents now correctly use
Display.Grid via configureChildAsGrid() instead of falling through
to configureChildAsAutoLayout() which set FlexDirection.

* Deduplicate grid yoga configuration

Extract configureAsGrid() and createGridChildNode() shared by
buildGridTree (root-level grid) and configureChildAsGrid (grid
nested inside flex parent).

* Add grid layout to changelog

* Add grid support to JSX and Tailwind CSS export

OpenPencil format: grid → columns/rows/columnGap/rowGap props,
child colStart/rowStart/colSpan/rowSpan.

Tailwind format: grid grid-cols-N grid-rows-N gap-x-* gap-y-*,
mixed tracks use arbitrary values (grid-cols-[200px_1fr_auto]),
child col-start-*/row-start-*/col-span-*/row-span-*.

Both formats: padding emitted for grid frames (shared with flex),
flex-only props (justify/items/wrap/gap) scoped to isFlex.

* Remove duplicate gridTrackToTw, use shared formatTrack

Also drop unused GridTrackSizing import from LayoutSection.vue.

* Fix grid icon: use layout-grid instead of grid-2x2

unplugin-icons can't resolve icon names with digits after hyphens
(grid-2x2 → grid2x2 which doesn't exist).

* Polish layout UI and fix auto-layout behavior

- Replace text labels with compact icons: ↔/↕ for gap, ☐ for uniform padding, T/R/B/L for per-side padding
- Direction-aware gap icon: ↕ for vertical, ↔ for horizontal
- Pin +/− padding toggle button right of gap input
- Fix alignment grid axes for vertical layout (transpose primary/counter)
- Fix grid switch: set FIXED sizing, compute frame size from children
- Remove hardcoded white fill from Shift+A wrap
- Auto-detect horizontal vs vertical from selection bounds

* Add flex-to-grid switch integration test

Verify HUG frame expands and children are placed in 2x2 grid
when switching from vertical flex to grid layout.

* Update changelog for grid layout
2026-03-08 18:23:53 +03:00
Danila Poyarkov 52b9ebc5c2 Add Russian locale (ru) to docs 2026-03-08 11:40:19 +03:00
Danila Poyarkov e67dd3fbd9 Rewrite docs: landing page, features, architecture — all 6 locales
- Landing page: reorder features (Figma-compatible first), add collaboration card, fix app size
- Features: cut implementation details, group into clear sections, add MCP/CLI/Homebrew
- Architecture: replace ASCII diagram with mermaid, add RPC bridge, update tool counts
- Remove ~500 lines of internal implementation prose from user-facing docs
2026-03-08 11:04:26 +03:00
Danila Poyarkov 0109d33846 Add AI agent skill to README and docs
- README: new 'AI Agent Skill' section with install command
- MCP tools reference: skill section, fix tool count 78→90,
  add 10 missing tools (export_image, export_svg, flatten_nodes,
  list_fonts, page_bounds, set_layout_child, set_text_properties,
  viewport_get, viewport_set, viewport_zoom_to_fit)
- Features page: mention skill with install command
- All 6 locales updated
2026-03-08 01:08:28 +03:00
Danila Poyarkov 5ac70a5976 Update docs: tool counts 75/78 → 87/90, schema.ts → tools/ directory, add RPC bridge 2026-03-08 00:28:11 +03:00
Danila Poyarkov 9882375b4f Release v0.8.0 2026-03-08 00:20:47 +03:00
Danila Poyarkov cbff284e78 Merge branch 'add-vitepress-docs' 2026-03-07 23:14:25 +03:00
Danila Poyarkov 2b525b816b Fix eval response unwrapping and export_jsx page selection
- eval CLI was typing rpc result as {ok, result} but the bridge
  already unwraps to the inner value — use rpc<unknown> directly
- export_jsx defaults to current page children, not first page
- Fix import ordering in server.ts (doc comment above imports)
2026-03-07 20:51:53 +03:00
Danila Poyarkov b6c922e335 Fix all code review issues in RPC bridge
Bugs fixed:
- Export response now wrapped in {ok, result} like all other commands
- btoa(String.fromCharCode(...data)) replaced with loop to avoid
  RangeError on large images
- JSX export uses dedicated export_jsx RPC command instead of eval
  with undefined selectionToJSX reference

Issues fixed:
- connectAutomation cleanup on component unmount via onUnmounted
- Bridge state encapsulated in startAutomationBridge closure instead
  of module-level lets
- Previous browser WS closed on new connection to prevent leaks
- walkNodes returns boolean for early termination, find stops at limit
- Unused imports removed from node.ts (fmtList, nodeToData)
- Hand-rolled hex in node.ts replaced with colorToHex from core
- makeFigmaFromStore extracted to shared figma-factory.ts, used by
  both ai/tools.ts and automation/server.ts
- Unused limit param removed from AnalyzeColorsArgs
- Unused groupBy param removed from AnalyzeTypographyArgs
2026-03-07 20:42:37 +03:00
Danila Poyarkov 70c25cddf5 Fix render tool in automation: use ssrLoadModule for JSX transform
The render tool needs esbuild to transform JSX → createElement calls, but
esbuild only runs in Node, not in the browser where the automation server
executes commands. The bridge now:

1. Intercepts render tool calls with JSX
2. Uses Vite's ssrLoadModule to import @open-pencil/core (handles TS source)
3. Transforms JSX → tree on the Node side via buildComponent + resolveToTree
4. Sends the tree to the browser which calls renderTreeNode directly

Also fixes @open-pencil/core exports: remove "node" condition that pointed
to non-existent dist/ during development. Published consumers get dist/ via
publishConfig.exports override.
2026-03-07 20:16:52 +03:00
Danila Poyarkov 39af21ff06 Pre-process render JSX in bridge where esbuild is available
The render tool needs esbuild to transform JSX, but esbuild only runs in
Node — not in the browser where the automation server executes. The bridge
now intercepts render tool calls, converts JSX to a tree on the Node side
using buildComponent + resolveToTree from core, and sends the tree to the
browser which calls renderTreeNode directly.

Also exports createElement and resolveToTree from core for bridge use.
2026-03-07 20:10:48 +03:00
Anton A S 513e0b6f4a Add SEO: OG tags, Twitter Card, hreflang, JSON-LD, sitemap
- config.ts: og:site_name, og:image (+w/h/alt), twitter:card/site/image
- config.ts: transformPageData — per-page canonical, og:url, og:locale,
  og:locale:alternate, hreflang for all 6 locales + x-default,
  og:title/description and twitter:title/description from frontmatter
- config.ts: sitemap with xhtml:link alternates for all 174 pages × 6 locales
- SchemaOrg.vue: SoftwareApplication JSON-LD on EN homepage only
- HomeLayout.vue: render SchemaOrg in home-features-after slot
- index.md + de/fr/es/it/pl index.md: add translated title to frontmatter
2026-03-07 19:49:24 +03:00
Anton A S 940d0de5b6 Sync docs with v0.7.0 and unreleased features
- Update comparison tables: 78 tools (was 75), SVG export no longer a Penpot advantage,
  renderer LOC updated to ~3,200 after split into 10 files
- Update figma-comparison: Rename layers ✅, SVG export ✅, +Copy/Paste as, +Tailwind export,
  +Stroke align, +per-side stroke weights; coverage 88/152 → 94/158
- Add What's Next roadmap to architecture.md: AI providers, figma-use tool set,
  CI tooling, prototyping, CSS Grid, PDF export, .fig fidelity
- Update user-guide: SVG in export format table, Copy/Paste as submenu,
  stroke align + per-side weights, layer inline rename, mobile drawer, Code tab format toggle
- Fix keyboard shortcuts: ⌘N/⌘T/⌘W ✅, ⌘\ ✅, add ⇧⌘C ✅
- Expand MCP tools reference from ~25 to all 78 tools in full category tables
- Add Homebrew tap install to Getting Started
- Add new features.md sections: SVG export, Copy/Paste as, stroke align, mobile/PWA,
  Tailwind JSX export, Google Fonts fallback, auto-save toggle, Homebrew tap,
  layer rename, renderer profiler
- Full locale sync for DE, ES, FR, IT, PL: all 7 files updated in each locale
2026-03-07 19:25:41 +03:00
Anton A S e828185d9e Merge branch 'master' into add-vitepress-docs 2026-03-07 19:01:09 +03:00
Danila Poyarkov 2ec382fc9d Fix RPC bridge review issues: centralize colors, ports, remove non-null assertions
- Use colorToHex/colorDistance from color.ts instead of hand-rolled hex math
  in rpc/commands.ts (removes toHex, hexToRgb, colorDistance duplicates)
- Extract AUTOMATION_HTTP_PORT/AUTOMATION_WS_PORT to core constants, import
  in bridge.ts, server.ts, app-client.ts instead of duplicating magic numbers
- Add requireFile() helper in app-client.ts, replace all file! non-null
  assertions across 12 CLI command files
- Document same-machine trust security model in bridge.ts header comment
- Replace (globalThis as any).Bun with typed isBunRuntime() helper
- Add changelog entry for CLI-to-app RPC bridge feature
2026-03-07 15:11:31 +03:00
Danila Poyarkov 99365384df Merge remote-tracking branch 'origin/master' into fix-cli-rpc
# Conflicts:
#	packages/core/src/index.ts
2026-03-07 15:03:40 +03:00
Danila Poyarkov bd5cdc8eee Centralize all color utilities in color.ts
- Add colorToHex8 (hex with alpha via culori formatHex8)
- Add colorToCSSCompact (no-space rgba for Tailwind shadow classes)
- Add normalizeColor (partial Color → full Color with defaults)
- Add colorDistance (Euclidean RGB via culori differenceEuclidean)
- Replace colorToCSS body with culori formatRgb
- Change rgba255ToColor to positional args (r, g, b, a)
- Remove duplicate formatColor from svg-export.ts and export-jsx.ts
- Remove duplicate colorDistance from tools/analyze.ts
- Remove duplicate convertColor from kiwi/kiwi-convert.ts
- Replace hand-rolled hex+alpha in HsvColorArea.vue with colorToHex8
- Replace hand-rolled rgba255→Color in HsvColorArea.vue with rgba255ToColor
- Expand color.test.ts from 16 to 34 tests
2026-03-07 10:02:11 +03:00
Danila Poyarkov 9784544f1f Clean up tools/schema, add geometry.ts, isMixed helper, fix imports, add tests
- Strip tools/schema.ts from 1831 to 65 lines (remove duplicate tool defs)
- Fix tools/index.ts to export ALL_TOOLS from registry instead of schema
- Fix renderJsx → renderJSX typo in tools/create.ts and structure.ts
- Add geometry.ts with shared rotation/coordinate math utilities
- Add isArrayMixed() helper to use-multi-props.ts, deduplicate FillSection/StrokeSection/EffectsSection
- Fix broken test imports: use @open-pencil/core instead of relative paths into packages/core/src/
- Merge duplicate import statements across all test files
- Export profiler internals and style-run helpers from core index.ts
- Add 6 new test files: color, undo, snap, vector, style-runs, text-editor (98 new tests)
- Update renderer-effects test for renderer/ directory split
2026-03-06 22:33:10 +03:00
Danila Poyarkov e7e5f53b57 Add CLI-to-app RPC bridge for live document access
- Extract shared RPC command handlers into packages/core/src/rpc/
  (info, pages, tree, find, node, variables, analyze colors/typography/spacing/clusters)
- Add automation bridge (src/automation/bridge.ts) that runs in Vite/Bun process,
  listens on localhost:7600 (HTTP) and :7601 (WebSocket)
- Add browser-side handler (src/automation/server.ts) that connects via WebSocket,
  executes commands against live EditorStore with requestRender()/flashNodes()
- Supports eval, tool execution, and image export via RPC
- Refactor all CLI commands: file arg is now optional — omit it to connect
  to the running app instead of loading a .fig file
- Bearer token auth generated per session, exposed via /health endpoint
- Vite plugin starts the bridge automatically during dev
2026-03-06 22:29:49 +03:00
Danila Poyarkov 859143149e Split renderer.ts into renderer/ directory 2026-03-06 22:25:59 +03:00
Danila Poyarkov 31326e8e5e Eliminate as Record<string, unknown> casts
- Add index signature to NodeChange for Kiwi codec fields
- Add blobs field to FigmaMessage
- Use 'in' type guards instead of Record casts in ai-adapter
- Use Object.fromEntries for Partial<SceneNode> property copying
- Use satisfies instead of as for handle position map
- Type node() to accept { children?: unknown; [key: string]: unknown }
- Remove ext() helper in kiwi-convert (direct property access)

37 occurrences → 1 (Kiwi codec boundary only).
2026-03-06 21:43:32 +03:00
Danila Poyarkov c33e4152df Fix PWA manifest error in dev, handle invalid font data
Remove hardcoded manifest link from index.html (VitePWA injects
it in production builds). Catch FontFace.load() rejections from
invalid font data with a warning.
2026-03-06 21:43:16 +03:00
Danila Poyarkov b4698d7a63 Use colorToCSS() instead of hand-rolled rgba conversions
Fix colorToCSS to include alpha channel. Replace all manual
Math.round(c.r * 255) patterns with colorToCSS() from core.
2026-03-06 21:43:03 +03:00
Danila Poyarkov 6ec8fc518b Move demo to /demo route, add @unhead/vue for title management
- / shows empty canvas
- /demo loads demo content (for app.openpencil.dev landing link)
- Title template: '{page} — OpenPencil' via useHead
- 'Try it online' links in README and docs point to /demo
2026-03-06 20:54:48 +03:00