Commit graph

261 commits

Author SHA1 Message Date
Danila Poyarkov 2a3e848e3b Deduplicate AI tools: use FigmaAPI.toJSON() and core parseColor
- get_page_tree: replace hand-rolled nodeToJSON with FigmaAPI.wrapNode().toJSON()
- get_selection: same — use FigmaAPI proxy serialization
- resolveColor: delegate hex parsing to core parseColor() (culori) instead of manual parseInt
2026-03-01 13:20:01 +03:00
Danila Poyarkov b6e792846f Harden FigmaAPI: hide internals via symbols, freeze arrays, fix layoutSizing
- Replace _id/_graph/_api with Symbol-keyed properties so eval scripts
  can't access SceneGraph internals directly
- fills/strokes/effects return frozen structuredClone copies
- Fix currentPage getter to define selection only once per proxy
- Fix layoutSizingHorizontal/Vertical: respect parent layout direction
  even when the node itself is an auto-layout frame
- Add 6 new tests: layoutSizing, frozen arrays, internals not exposed
2026-03-01 13:12:26 +03:00
Danila Poyarkov 9a7d170887 Add 17 CLI integration tests for eval command
Tests cover: page name, primitives, findAll queries, node creation,
auto-layout, text, top-level await, stdin piping, JSON output,
syntax/runtime errors, no-code error, undefined output, --output write,
array serialization, getNodeById on real .fig files.
2026-03-01 13:07:06 +03:00
Danila Poyarkov 0cba4fb4b7 Add FigmaAPI + eval CLI command with 60 unit tests
FigmaAPI in @open-pencil/core provides a Figma Plugin API-compatible
interface backed by SceneGraph. Covers node creation, property access,
tree operations, auto-layout, text, traversal, grouping, components,
selection, and serialization.

CLI: bun open-pencil eval <file> --code 'figma.createFrame()'
2026-03-01 12:21:07 +03:00
Danila Poyarkov 63748ecee6 Plan: eval command with Figma-compatible plugin API 2026-03-01 12:13:31 +03:00
Danila Poyarkov bf8636c677 Merge chat-panel: AI chat with OpenRouter, tool execution, model selector 2026-03-01 11:55:57 +03:00
Danila Poyarkov 91a6f599fa Remove chat avatars 2026-03-01 11:54:04 +03:00
Danila Poyarkov 921a9cd261 Fix tool name extraction from UIMessage parts, add tool call e2e test
Tool part type is 'tool-create_shape' (name embedded in type field),
not a separate toolName property. Extract via part.type.replace('tool-', '').

Mock transport now emits proper UI message stream protocol:
tool-input-start → tool-input-delta → tool-input-available → tool-output-available.
2026-03-01 11:51:38 +03:00
Danila Poyarkov 873671baeb Fix valibot transform incompatible with JSON Schema serialization
Move hex→RGBA conversion from v.transform() (which can't be serialized
to JSON Schema for the LLM) to a resolveColor() helper called in execute.
2026-03-01 11:46:52 +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 4032108be0 Move AI models to @open-pencil/core constants, add Kimi K2.5 (vision+code), benchmark-ranked tags 2026-03-01 11:38:40 +03:00
Danila Poyarkov 6268d8b119 Fix double/triple-click text selection
- Track click count with timing/proximity in onMouseDown
- Double-click in text: select word (was only handled in dblclick)
- Triple-click in text: select all
- Double-click to enter editing: select word at click position
- Add selectLine/selectLineAt to TextEditor
2026-03-01 11:36:49 +03:00
Danila Poyarkov 9b59d8e816 Update model list to current versions (Claude 4.6, Gemini 3.1, GPT-5.3, DeepSeek V3.2, Qwen 3.5) 2026-03-01 11:34:38 +03:00
Danila Poyarkov 299b668e7b Show selected model name in selector trigger 2026-03-01 11:33:12 +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 20827a27d3 Deduplicate fig-kiwi parsing, font weight mapping, layout, style runs, text editor
- Extract buildFigKiwi, parseFigKiwiChunks, decompressFigKiwiDataAsync to kiwi-serialize.ts
- Unify styleToWeight across fonts.ts, fig-import.ts, src/engine/fonts.ts
- Merge applyYogaLayout + applyYogaLayoutNested into single recursive fn
- Extract expandRuns helper in style-runs.ts
- Add prepareMove helper to TextEditor for selection extension boilerplate

jscpd: 18 → 9 clones, 1.7% → 0.62%
2026-03-01 11:29:26 +03:00
Danila Poyarkov c18ced5dcf Rename ApiKeySetup → APIKeySetup 2026-03-01 11:12:37 +03:00
Danila Poyarkov c08cbe5c0f Split ChatPanel into ChatMessage, ChatInput, ApiKeySetup 2026-03-01 11:11:46 +03:00
Danila Poyarkov e3ae61e99d Consolidate font weight mapping and FIG_KIWI_VERSION 2026-03-01 11:08:59 +03:00
Danila Poyarkov f0c482ac5c Add model selector with curated list (Claude, Gemini, GPT, DeepSeek, Llama) 2026-03-01 11:08:47 +03:00
Danila Poyarkov 61cd018353 Fix empty state vertical centering in chat panel 2026-03-01 11:05:09 +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 f92a8835f4 Deduplicate kiwi and serialization code
Replace src/kiwi/ copies with re-exports from @open-pencil/core.
Extract shared sceneNodeToKiwi into kiwi-serialize.ts, used by
both fig-export.ts and clipboard.ts.

jscpd: 15.6% → 1.7% duplication (35 → 18 clones, -4459 lines)
2026-03-01 10:59:35 +03:00
Danila Poyarkov 9235a75e6f Report app name to OpenRouter via X-OpenRouter-Title header 2026-03-01 10:58:22 +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 46bb9230d3 Add jscpd for copy-paste detection 2026-03-01 10:49:09 +03:00
Danila Poyarkov a32d80f448 Deduplicate render internals: shared resolveToTree, remove dead code 2026-03-01 10:45:39 +03:00
Danila Poyarkov 13f562adc6 JSX renderer in @open-pencil/core
TreeNode builder functions (Frame, Text, Rectangle, etc.) produce a
lightweight tree structure. Two rendering paths:

- renderTreeNode(): tree → scene graph (browser, no deps)
- renderJsx(): JSX string → esbuild → tree → scene graph (CLI/headless)

Tailwind-like shorthand props: w/h, bg, rounded, flex, gap, p/px/py,
justify, items, shadow, blur, etc.

27 tests covering all node types, layout props, effects, and nesting.
2026-03-01 10:42:06 +03:00
Danila Poyarkov 6bc3f7c50d Use git push --no-verify for LFS, drop custom alias 2026-03-01 10:41:16 +03:00
Danila Poyarkov dc602b0731 Document git qpush alias for fast LFS-skipping pushes 2026-03-01 10:39:57 +03:00
Danila Poyarkov cae260c178 Document LFS skipverify setting 2026-03-01 10:36:00 +03:00
Danila Poyarkov ca422147b4 Add .fig roundtrip tests: real file parsing, property invariants, encode/decode cycle 2026-03-01 10:33:58 +03:00
Danila Poyarkov 5d23c3e834 AI chat panel with ⌘J toggle, DirectChatTransport, OpenRouter
- ChatPanel.vue: message list, tool timeline, typing indicator, API key setup
- use-chat.ts: Chat + DirectChatTransport + ToolLoopAgent (no backend)
- App.vue: toggle between PropertiesPanel and ChatPanel in same splitter slot
- ⌘J keyboard shortcut to open/close chat
2026-03-01 10:31:57 +03:00
Danila Poyarkov cc5dda29d9 AI chat: no backend, direct OpenRouter from browser, Stronghold for key storage 2026-03-01 10:23:53 +03:00
Danila Poyarkov 7dcc5822f8 Merge cli-commands: analyze, node, pages, variables commands 2026-03-01 10:19:46 +03:00
Danila Poyarkov 9873580a04 Reference plan.md from AGENTS.md 2026-03-01 10:19:38 +03:00
Danila Poyarkov f1806f6f33 Add test:coverage script 2026-03-01 10:19:38 +03:00
Danila Poyarkov 36e3f14ac4 CLI: add analyze, node, pages, and variables commands
- analyze colors: color palette usage with clustering
- analyze typography: font/size/weight distribution
- analyze spacing: gap/padding values with grid check
- analyze clusters: repeated patterns (potential components)
- node: detailed properties by ID
- pages: list pages with node counts
- variables: list design variables and collections
2026-03-01 10:19:38 +03:00
Danila Poyarkov 9b97346aa2 Remove .env from tracking, add to .gitignore 2026-03-01 10:14:48 +03:00
Danila Poyarkov 017b0aa3c1 Move AI chat design into plan.md Phase 5 2026-03-01 10:14:40 +03:00
Danila Poyarkov af6a98ca56 AI chat: JSX-first rendering, guardrail tools, workflow from figma-use 2026-03-01 10:12:01 +03:00
Danila Poyarkov 4fb929073a AI chat design document 2026-03-01 10:09:09 +03:00
Danila Poyarkov 0df5492542 Document kiwi decoder benchmark: JS vs Rust findings 2026-03-01 10:08:14 +03:00
Danila Poyarkov f24f6e0dc6 Add .fig test fixtures (LFS) and fix O(n²) import bottleneck
- Add Material 3 Design Kit and Nuxt UI v4 community files as test fixtures
- Track tests/fixtures/*.fig with Git LFS
- Fix O(n²) getChildren() in fig-import: build children index upfront
  - material3.fig (87K nodes): 37s → 535ms (69x faster)
  - nuxtui.fig (314K nodes): minutes → 2.3s
- Optimize kiwi ByteBuffer: inline readVarUint, use TextDecoder for strings
2026-03-01 09:55:07 +03:00
Danila Poyarkov 88bfacdc9e Rich text style runs with per-selection bold/italic/underline
StyleRun model: array of {start, length, style} with
CharacterStyleOverride (fontWeight, italic, textDecoration, etc.).

Renderer uses ParagraphBuilder.pushStyle/pop for mixed-style text.
TextEditor delegates paragraph building to renderer.buildParagraph().

⌘B/I/U apply to selection range when text is selected, or toggle
whole-node style when no selection. Style runs adjust on
insert/delete to preserve formatting across edits.

Fig roundtrip: import characterStyleIDs + styleOverrideTable from
TextData, export back with deduped style table.

B/I/U/S toggle buttons in TypographySection.
2026-03-01 09:03:38 +03:00
Danila Poyarkov 33d3defcef Canvas-native text editing with phantom textarea
TextEditor class in core — cursor positioning, selection, word
boundaries, line navigation via CanvasKit Paragraph API
(getGlyphPositionAtCoordinate, getRectsForRange).

Renderer draws selection highlights and blinking caret directly
on the canvas. Blue outline around text node during editing.

Phantom textarea captures input, IME composition, clipboard,
arrow keys, Home/End, word/line movement (⌥/⌘ modifiers).

Replaces the old visible <textarea> overlay.
2026-03-01 03:46:43 +03:00
Danila Poyarkov 4e03d262c0 Register system font faces for CSS preview, fix picker highlight width 2026-03-01 03:25:50 +03:00
Danila Poyarkov 621e38e178 Font picker: start with empty search, scroll to current font 2026-03-01 02:59:17 +03:00
Danila Poyarkov 326f5d194d Preload system fonts on app startup
Cache font list in Rust via OnceLock, fire preload request from
main.ts so the font list is ready before user opens the picker.
2026-03-01 02:57:58 +03:00