Commit graph

34 commits

Author SHA1 Message Date
Danila Poyarkov 00da8bde1b Add E2E test for fill color change (#100)
The fill color bug was caused by a missing `ref` import in FillPicker.vue,
introduced in 37da83a and fixed in 3b32b4d. Add a regression test that
opens the color picker, clicks the color area, and verifies the node's
fill color updates.
2026-03-13 13:40:12 +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 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 01c8753f6e Merge PR #70: E2E test coverage + mutation unit tests
48 new E2E tests across 9 spec files, 26 mutation unit tests,
store/canvas test helpers, data-test-id attributes.

Fixes: explicit error in canvasBounds(), remove any casts,
trim trailing blank line, condense changelog entries.
2026-03-07 23:47:00 +03:00
Anton A S d7dc55264b Improve E2E test quality: stronger assertions, fix bold button test, improve snap guide comparison 2026-03-07 23:06:51 +03:00
Anton A S c3c49fcb2f Fix E2E tests: waitForInit loading guard, selector fixes, bold/italic via node-level toggle 2026-03-07 22:48:33 +03:00
Anton A S 83c72b94a0 Add E2E test coverage for canvas, toolbar, panels, properties, auto-layout, variables, export 2026-03-07 22:11:25 +03:00
Danila Poyarkov 14b796a40d Add 8 E2E test suites for release sanity coverage
83 new tests across 8 files:
- design-panel: selection → properties panel, fill/stroke/effect add, visibility toggle, multi-select
- code-panel: JSX output, OpenPencil/Tailwind toggle, copy button, empty state
- components: create component (⌘⌥K), create instance, propagation, detach
- copy-paste: store copy/paste, ⌘D duplicate, cut, multi-select duplicate
- multi-page: add/switch/delete pages, rename, last-page protection
- text-editing: text tool, text node creation, typography section, frame tool
- keyboard-shortcuts: all 9 tool keys, ⌘A/Escape/Backspace, ]/[, ⌘G/⌘⇧G, ⌘\, ⌘Z/⌘⇧Z
- context-menu: right-click items, duplicate/visibility/lock/delete/group/component, Copy as submenu
2026-03-07 13:23:32 +03:00
Danila Poyarkov 7fbe21c8e1 Fix flaky E2E tests: layers panel, zoom-to-fit, snapshot stability
- layers-panel: navigate to /demo so demo layers exist
- app-menu zoom-to-fit: zoom in first so zoom-to-fit has something to change
- playwright.config: force workers=1 and colorScheme=dark for consistent
  WebGL/SwiftShader snapshot rendering across runs
- Update empty-canvas snapshot for current dark-mode canvas appearance
2026-03-07 11:08:22 +03:00
Danila Poyarkov a9a1f9ac1f
Shadow rendering performance: per-node SkPicture cache (#25)
* Replace saveLayer with MaskFilter for drop shadows

Drop shadows now draw directly with MaskFilter.MakeBlur instead of
saveLayer + ImageFilter. Eliminates per-shadow offscreen buffer
allocation, significantly improving render throughput for scenes
with many shadowed nodes.

Text shadows still use saveLayer (MaskFilter can't be applied to
text glyphs individually).

Benchmark: 50 shadow nodes render at 1.55ms/frame (scene change).

* Cache per-node SkPictures for effect rendering

Nodes with visible effects (shadows, blurs) are recorded into
individual SkPictures and replayed on subsequent scene redraws.
Only the modified node re-records; unchanged shadow nodes replay
from cache.

- MaskFilter.MakeBlur for drop shadows instead of saveLayer
- Reusable effectLayerPaint and cached ImageFilters/MaskFilters
- invalidateNodePicture() called on node update
- invalidateAllPictures() on font load; invalidateScenePicture()
  preserves node cache for scene-only invalidation
- Shadow benchmark: 50 nodes at 1.47ms/frame (scene change)

* Update changelog with shadow rendering performance improvements
2026-03-03 11:37:01 +03:00
Danila Poyarkov 8ab7148294
Fix effects rendering: shadows, blur, spread (#24)
* Fix shadow rendering, implement blur effects, add spread support

- Fix drop shadow rendering order: draw shadows before fills so they
  appear behind opaque shapes instead of on top (#23.1)
- Implement shadow spread: expand/contract shadow shape by spread value
  for both drop shadow and inner shadow (#23.2)
- Implement background blur: clip to node shape and apply blur filter
  to content behind the node (#23.3)
- Fix text shadows: use saveLayer with ColorFilter.MakeBlend to apply
  shadows to text glyphs instead of the bounding box (#23.4)
- Implement layer blur with actual saveLayer + blur filter instead of
  the no-op comment (#23.3)
- Implement foreground blur: clip to node shape and blur content in
  front (#23.7)
- Add inner shadow spread support with rounded rect handling
- Add makeRRectWithSpread and makeRRectWithOffset helpers

Closes #23

* Add Effects section to demo showcasing shadow and blur fixes

Drop shadows (subtle, medium, heavy, with spread), inner shadows
(inset, with spread, on ellipse, combined), text shadows on glyphs,
layer blur, and glassmorphism background blur card.

* Fix drop shadow, layer blur, and background blur rendering

Drop shadow: use MakeDropShadowOnly to produce shadow-only output
that doesn't bleed through opaque fills. The sigma is radius/2 to
match Figma's blur convention.

Layer blur: wrap entire node content in a saveLayer with blur filter
at the renderNode level instead of trying to blur after-the-fact
in renderEffects.

Background/foreground blur: extract clipNodeShape helper to reduce
duplication in blur effect rendering.

Update tests to match new rendering approach.

* Add visual regression tests for effects rendering

9 snapshot tests covering drop shadow, inner shadow, spread,
ellipse shapes, text glyphs, layer blur, and combined effects.

* Make Effects demo section background white

* Clean up renderer code and tests

- Extract applyClippedBlur to deduplicate background/foreground blur
- Remove comments that repeat what code does
- Format with oxfmt

* Add undo/redo support for effect property changes

ScrubInput controls (offset, radius, spread, opacity) now use
scrubEffect for live preview and commitEffect on release to create
a single undo entry per interaction.
2026-03-03 09:53:39 +03:00
Danila Poyarkov 16db9feafa Add zoom/pan E2E tests and pipeline benchmark
Tests cover:
- Wheel zoom updates viewport (ctrlKey + wheel → applyZoom)
- Wheel pan updates viewport (plain wheel → pan)
- Rapid wheel events coalesce without errors (50 events in one frame)
- shallowReactive selection replace triggers UI update
- useRafFn loop picks up renderVersion changes (fill color change)
- useRafFn loop picks up selection changes (selection border)
- Pipeline throughput benchmark (500 iterations each)
2026-03-02 23:43:39 +03:00
Danila Poyarkov ebb85e8f20 Improve scene cache regression test for font load race 2026-03-02 15:51:02 +03:00
Danila Poyarkov af8b90a939 Fix text disappearing after hover: invalidate SkPicture on font load
The first render records an SkPicture before fonts are loaded (fallback
drawText path). When fonts finish loading asynchronously, the cached
picture was never invalidated — subsequent renders replayed stale picture
without paragraph text. Hovering a node forced a full re-render (volatile
overlay path), making text reappear. Un-hovering replayed the stale cache.

Fix: call invalidateScenePicture() at the end of loadFonts() so the next
render records a fresh picture with proper paragraph text.
2026-03-02 15:45:58 +03:00
Danila Poyarkov 527be98f2a Re-apply SkPicture scene caching with visual regression tests 2026-03-02 11:33:45 +03:00
Danila Poyarkov 1ee8e8e5af Add render performance benchmark test 2026-03-01 22:35:16 +03:00
Danila Poyarkov ca20bfb2ac Fix layers-panel tests: match current demo shapes (Components, App Preview) 2026-03-01 15:28:13 +03:00
Danila Poyarkov 20290f61b5 Add integration tests for app menu and autosave
9 app-menu tests: menu visibility, all 6 submenus content, Undo/Duplicate/
Zoom to fit via menu actions.
2 autosave tests: write triggers after scene change with mock file handle,
no write without file handle.
2026-03-01 15:26:10 +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 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 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 27f57da340 Improved demo: section with Desktop/Mobile frames, color swatches
- 'Design System' section containing Desktop and Mobile frames
- Desktop: header, hero card, avatar, two content cards
- Mobile: status bar, banner, list items, FAB button
- Color palette swatches (Primary, Success, Warning, Danger, Purple)
- Updated E2E tests for new demo structure
- Reverted section title pill to always use fill color (not blue)
2026-02-28 14:02:46 +03:00
Danila Poyarkov 1d79473bc6 Pages support: multi-page documents like Figma
Document structure: Document (root) → Pages (CANVAS) → Nodes.
Each page has its own viewport (pan/zoom) and background color.

- CANVAS node type for pages
- SceneGraph: addPage(), getPages(), getAbsolutePosition stops at CANVAS
- Store: currentPageId, switchPage(), addPage(), deletePage(), renamePage()
- All operations (create, select, paste, hit test, render) scoped to current page
- Per-page viewport state saved/restored on page switch
- Pages list in LayersPanel with add/switch/double-click rename
- .fig import creates proper pages from DOCUMENT→CANVAS hierarchy
- Renderer takes pageId to render correct page's children
- Unit test for pages, updated E2E tests for page-scoped graph
2026-02-28 13:12:27 +03:00
Danila Poyarkov fd0b02642f Fix layers panel reactivity and accidental reparenting on click
- Force TreeRoot re-mount via :key when tree structure changes,
  fixing Reka UI not reacting to items prop updates
- Only reparent into frames on mouseup when user actually dragged,
  preventing click-to-select from moving nodes into overlapping frames
- Expose store on window for test introspection
- 6 new E2E tests for layers panel
2026-02-28 11:15:19 +03:00
Danila Poyarkov 9d5c85667f Canvas rulers with selection highlight
Horizontal and vertical rulers rendered directly in CanvasKit:
- Dark background, tick marks at adaptive intervals
- Coordinate labels scale with zoom level
- Blue highlight showing selected node extent on each axis
- Corner square at ruler intersection
2026-02-28 09:35:50 +03:00
Danila Poyarkov e37d2b216a Fix silent rendering crash, no-chrome test mode, error detection
- Fix this.this.selColor() typo in renderer (6 occurrences) — broke
  selection/handles rendering since the constants extraction refactor
- Add preserveDrawingBuffer to WebGL surface for reliable screenshots
- Add ?no-chrome URL param to hide panels/toolbar in tests
- CanvasHelper collects pageerror + console.error, assertNoErrors()
  fails tests on any browser-side exception
2026-02-28 08:39:19 +03:00
Danila Poyarkov bcd8bf6bca Add IconsResolver for auto-import, explicit imports only for dynamic maps 2026-02-28 08:29:39 +03:00
Danila Poyarkov 3ddc671bc5 Replace emoji icons with Lucide via Iconify (unplugin-icons)
- unplugin-icons + @iconify-json/lucide for compile-time icon bundling
- Toolbar: proper cursor, frame, square, pen, type, hand icons
- Layers panel: per-type node icons + chevron-right for expand/collapse
- Remove icon field from ToolDef (icons mapped in components)
2026-02-28 08:24:42 +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 c1fbf57df2 Skip stability check: toMatchSnapshot, 2.1s → 1.3s 2026-02-28 07:44:20 +03:00
Danila Poyarkov e8c01fb93c Speed up E2E tests: reuse page, 14s → 4.6s 2026-02-28 07:35:52 +03:00
Danila Poyarkov 40f7b3a648 Playwright visual regression testing with Figma CDP reference
- Playwright E2E tests for OpenPencil: draw, move, delete, undo shapes
- Visual screenshot baselines with toHaveScreenshot()
- SwiftShader for headless WebGL/CanvasKit rendering
- Figma CDP helper for reference tests (connect to Figma Desktop)
- ?test URL param for clean canvas without demo shapes
- Scripts: test, test:update, test:figma, figma:debug
2026-02-28 07:32:15 +03:00