Commit graph

25 commits

Author SHA1 Message Date
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