Commit graph

618 commits

Author SHA1 Message Date
Danila Poyarkov 1ceabf7938 feat(components): add local assets panel
- Add a left-sidebar Assets tab for local components and component sets
- Insert component set assets as instances of the default variant
- Showcase local component set insertion in the demo
- Cover search, empty state, standalone insert, and variant switching with Playwright
2026-05-12 17:30:35 +03:00
Danila Poyarkov ddf415ec30 feat(tauri): support desktop file associations
- Register .fig and .pen files with desktop bundles
- Open associated files on startup and via single-instance handoff
- Reuse the existing Tauri file open flow without broad filesystem scopes
2026-05-12 16:12:16 +03:00
Danila Poyarkov 9e013e60e7 fix(mcp): fix stdio handshake and eval return values
Stdio handshake:
Server now sends { type: register, token } to WebSocket clients on
connection, so the stdio bridge receives the token immediately instead
of deadlocking while both sides wait to receive.

Eval return values (REPL-style):
Add wrapEvalCode() that detects when the last non-empty line is a value
expression and promotes it to a return statement. Bare expressions like
JSON.stringify(result) now return the value instead of undefined.
Single implementation in packages/core/src/tools/analyze/eval-wrap.ts
used by both the tool and the automation bridge.

Co-authored-by: stiff <v.stiff@gmail.com>
Co-authored-by: Jais Pedersen <jais@pedersens.net>
2026-05-11 12:05:43 +03:00
Danila Poyarkov c79c9106fb feat(components): add variant inspector UI
Show variant property dropdowns in the design panel when an instance
of a component set is selected. Switching a dropdown value finds the
matching variant component and updates the instance's componentId.

- VariantSection.vue with property dropdowns using AppSelect
- Bridge variant actions to the editor return object
- i18n for en, ru, zh-CN

Ref #239
2026-05-07 19:48:59 +03:00
Danila Poyarkov ad88364562 feat(variables): bind min/max size constraints to variables
Wire VariableScrubInput for minWidth, maxWidth, minHeight, maxHeight
in the size controls panel. Uses the after-variable slot to preserve
the existing select dropdown suffix alongside the variable binding UI.
2026-05-07 14:32:36 +03:00
Danila Poyarkov a6da185e54 refactor: eliminate HTMLInputElement template casts
- Add shared DOM event helpers to Vue SDK (inputValue, inputNumberValue,
  blurTarget, selectTarget) in packages/vue/src/shared/dom-events.ts
- Export from @open-pencil/vue for app consumption
- Replace all template `as HTMLInputElement` casts with helper calls:
  ColorPickerPanel fields, PickerSlider, GradientEditor, ColorInput,
  CollabPanel, AppMenu, ScrubInputRoot
- Fix useInlineRename to use instanceof guards instead of casts
- Fix document name rename to accept Event directly
2026-05-07 10:24:46 +03:00
Danila Poyarkov 0a268f0a65 feat(variables): add inspector bindings for typography properties
- Add lineHeight, letterSpacing, fontWeight, paragraphSpacing,
  paragraphIndent to NumberBindingPath
- Wire lineHeight and letterSpacing inputs in TypographySection
  to VariableScrubInput with variable binding support
2026-05-07 09:50:52 +03:00
Danila Poyarkov 506b5f81e1 refactor(variables): reuse useInlineRename for dialog renaming
- Reuse useInlineRename composable for collection and mode renaming
- Accept Event in useInlineRename.commit to eliminate template casts
- Remove HTMLInputElement casts from PagesPanel and LayerTree blur handlers
- Remove hand-rolled rename state from dialog composable
2026-05-07 00:02:13 +03:00
Danila Poyarkov 107e9f3d51 feat(variables): add mode management and collection deletion
Core:
- Add addMode, removeMode, renameMode, setDefaultMode to SceneGraph
- Add editor actions with full undo: addMode, removeMode, renameMode,
  setDefaultMode, duplicateMode, setActiveMode

Vue SDK:
- Wire all mode and collection actions through SDK helpers
- Add mode rename state to dialog composable
- Remove HTMLInputElement casts from commit handlers

UI:
- Mode management lives in the table column headers (Figma-style):
  double-click to rename, right-click for context menu (rename,
  duplicate, set as default, delete), + button after last mode column
- Collection ⋯ dropdown menu with rename and delete
- Use shared menu UI styles throughout
- i18n for en, ru, zh-CN
2026-05-06 21:36:04 +03:00
Danila Poyarkov 738d199e8a refactor: remove ugly type intersection casts
- Add optional nodeEditState, cursorCanvas, and pen resume fields to core EditorState
- Add parent field to LintNode instead of repeated intersection casts
- Use typed LintPathNode for node path traversal
- Replace autosave useDebounceFn cancel hack with watchDebounced
- Simplify AppEditorState to extend EditorState without Omit+intersection
- Remove PenState intersection type alias
- Use in-operator check for optional setViewportSize
2026-05-06 16:08:36 +03:00
Danila Poyarkov 7dce333200 docs: document event bus and wire app consumers
- Update CHANGELOG.md with event bus, test restructuring, and lint hardening
- Document EditorEvents table and conventions in AGENTS.md
- Wire collab graph sync to editor event bus instead of SceneGraph.onNodeEvents
- Wire collab awareness zoom to viewport:changed instead of Vue watcher
- Wire collab selection broadcasting to selection:changed instead of Vue watcher
2026-05-06 15:38:45 +03:00
Danila Poyarkov 1835903e56 feat(editor): add event bus for editor lifecycle events
Wire nanoevents-based event bus to the editor core:

- EditorEvents type with render, graph, selection, tool, page, viewport events
- All node mutations (create/update/delete/reparent/reorder) forwarded from SceneGraph
- Selection changes routed through setSelectedIds() with diff detection
- Tool changes routed through setActiveTool() with change detection
- Page switches emit page:changed
- Viewport pan/zoom/fit/zoomTo emit viewport:changed
- Graph replacement emits graph:replaced
- Typed onEditorEvent() subscription on the Editor return object
- useEditorEvent() Vue composable with automatic scope cleanup
- Replace all direct state.selectedIds/state.activeTool mutations in core, app, and SDK
2026-05-06 15:25:27 +03:00
Danila Poyarkov bbf122a5c5 chore(tests): route store access through browser bridge
- Expose window.openPencil.getStore() instead of a direct store property
- Update E2E helpers and specs to use the bridge getter
- Add lint coverage preventing direct window.openPencil.store access
2026-05-06 12:19:39 +03:00
Danila Poyarkov f7b20a8f38 chore(tests): namespace browser test hooks
- Move test-only write-count, mock-handle, and saved-open globals under window.openPencil.test
- Keep external Tauri and Vite injected globals separate from app-owned browser bridge hooks
- Validate check plus autosave and chat E2E coverage
2026-05-06 10:49:46 +03:00
Danila Poyarkov 8719274cc9 chore(app): clarify browser bridge naming
- Rename window-api to browser-bridge to describe the app-to-browser test hook boundary
- Rename the chat transport hook to exposeChatTransportOverride and window.openPencil.setChatTransport
2026-05-06 10:43:15 +03:00
Danila Poyarkov 8ec70330b4 chore(lint): centralize OpenPencil window API
- Route browser globals through src/app/window-api.ts instead of private __OPEN_PENCIL fields
- Move E2E tests to the public window.openPencil test API
- Add an oxlint rule banning direct window.__OPEN_PENCIL* access
2026-05-06 10:38:03 +03:00
Danila Poyarkov c3f5189f8f style: tighten import grouping
- Configure oxfmt custom import groups for workspace, app, package, and test aliases
- Keep type imports grouped with their matching source category instead of one global tail group
- Expand the format script to cover formatter config, Vite files, and scripts
2026-05-06 02:22:08 +03:00
Danila Poyarkov ddb1db8175 style: format imports 2026-05-06 02:16:37 +03:00
Danila Poyarkov 792f0e87cf fix(app): use color parser for theme CSS colors
- Remove hand-rolled rgb/rgba parsing from shell theme handling
- Parse ruler CSS custom properties through the shared core color helper
2026-05-06 02:15:39 +03:00
Danila Poyarkov bef03e3804 fix(app): avoid pre-init theme warnings
- Add a nullable active editor store accessor for optional app-bound updates
- Skip canvas theme repainting until an editor store exists
- Guard FontPicker focus handling against non-element component refs
2026-05-06 02:14:33 +03:00
Danila Poyarkov 51eae14e4b chore(tests): remove targeted non-null assertions
- Add shared test helpers for required nodes and child ids
- Clean non-null assertions from noisy render, nudge, text undo, and fig roundtrip tests
- Enforce the non-null assertion ban for the cleaned test files
- Remove stale eslint disables around empty functions and broad mock transport typing
2026-05-06 01:14:50 +03:00
Danila Poyarkov fae190a673 chore(lint): remove unsafe test casts
- Enforce explicit-any and TypeScript suppression bans in tests
- Remove remaining broad any casts from engine and e2e coverage
- Split markdown ambient declarations from typed browser globals
2026-05-06 00:42:45 +03:00
Danila Poyarkov 68d15440b1 feat(app): add variable type creation menu 2026-05-05 23:49:20 +03:00
Danila Poyarkov 3bd01095eb test(app): cover remaining Tauri integrations 2026-05-05 22:45:14 +03:00
Danila Poyarkov 057b3a02b7 test(app): cover Tauri process helpers 2026-05-05 22:32:24 +03:00
Danila Poyarkov 020b55a8ae test(app): cover Tauri file actions 2026-05-05 22:24:57 +03:00
Danila Poyarkov cc216c79db test(app): cover font settings UI 2026-05-05 22:12:33 +03:00
Danila Poyarkov 0f9b69d051 test(app): expand Tauri helper coverage 2026-05-05 22:07:02 +03:00
Danila Poyarkov dac6f171e3 feat(app): add font cache management hooks 2026-05-05 21:50:31 +03:00
Danila Poyarkov 768b527155 feat(core): add font fallback manifest 2026-05-05 21:35:12 +03:00
Danila Poyarkov 25ea361fd7
feat(app): cache downloaded fonts
- Add a downloaded font cache hook to FontManager
- Store Tauri font downloads under AppLocalData with manifest validation
- Prefer cached downloads before system font lookup on desktop
- Skip opt-in heavy .fig parsing tests in PR CI
2026-05-05 21:14:38 +03:00
Danila Poyarkov f29da6c5cd refactor(vue): clean up primitive UI APIs
- Replace one-off class props with typed ui objects

- Group renderless slot actions and remove DOM ref callback plumbing

- Make local font access and typography font loading explicit capability objects
2026-05-05 20:57:13 +03:00
Danila Poyarkov 9c11c5b075
refactor(core): centralize font loading
- Replace loose font service functions with a FontManager instance
- Route renderer provider lifecycle and font caches through the manager
- Request browser local font access from the font picker interaction
2026-05-05 18:59:54 +03:00
Joseph Cumines c69e3884cf
fix(core): critical codec and canvas fixes
- Deduplicate plugin data and relaunch data during .fig parsing/export
- Unify shared plugin data into the pluginData storage path
- Refactor shadow rendering and harden CanvasKit font provider teardown
- Expand engine and renderer coverage
2026-05-05 14:58:02 +03:00
Danila Poyarkov 7252b450cf fix(app): fix variable picker click and layout spacing
- ScrubInput: skip scrub drag when pointerdown target is inside a button
- Size fields: always show chevron icon instead of hover-swap to prevent layout jump
- Variable picker: add right padding when no chevron follows, none when chevron present
2026-05-04 14:39:49 +03:00
Danila Poyarkov 8458334fec fix(app): prevent ScrubInput scrub drag from swallowing button clicks
The outer ScrubInput container captured all pointerdown events and
started a scrub drag, preventing embedded button clicks (like the
variable picker diamond icon) from ever reaching their handlers.

Now checks if the click target is inside a <button> before starting
the scrub.
2026-05-04 14:05:45 +03:00
Danila Poyarkov 6cc8b9d07c feat(app): extend variable binding to more inspector fields
Add VariableScrubInput — reusable wrapper that combines ScrubInput
with variable binding picker and detach button. Wire it into:

- Corner radius (uniform + 4 independent corners)
- Opacity
- Auto-layout gap (primary + cross axis)
- Padding (horizontal/vertical + 4 individual sides)
- Font size

All fields get bind, detach, create, search, and detach-on-edit
behavior automatically through the shared component.
2026-05-04 13:43:33 +03:00
Danila Poyarkov ab1ba02c94 feat(app): extend variable binding to more inspector fields
Add VariableScrubInput — reusable wrapper that combines ScrubInput
with variable binding picker and detach button. Wire it into:

- Corner radius (uniform + 4 independent corners)
- Opacity
- Auto-layout gap (primary + cross axis)
- Padding (horizontal/vertical + 4 individual sides)
- Font size

All fields get bind, detach, create, search, and detach-on-edit
behavior automatically through the shared component.
2026-05-04 12:47:38 +03:00
Danila Poyarkov 93160b1bfb feat(ai): add DeepSeek as first-class provider
Use @ai-sdk/deepseek instead of openai-compatible, which properly
handles reasoning_content in conversation history. DeepSeek V4
requires reasoning_content on every assistant turn — the dedicated
provider backfills it automatically.

Adds DeepSeek V3 and R1 models to the provider list.
2026-05-04 11:48:45 +03:00
Danila Poyarkov ac15fb143b feat(io): add PDF export
Vector PDF output via jsPDF + svg2pdf.js (lazy-loaded, 124KB gzipped).
Reuses existing SVG export pipeline — text stays selectable, paths
stay sharp at any zoom.

- packages/core/src/io/formats/pdf/ — SVG→PDF conversion module
- export_pdf tool for MCP/AI
- PDF format in IO registry and app export panel
- CLI: bun open-pencil export file.fig --format pdf
2026-05-04 00:18:54 +03:00
Danila Poyarkov 36f2990e78 refactor(vector): address PR #238 review issues
- Extract centerline algorithm to packages/core/src/vector/centerline.ts
  to stay under max-lines (index.ts 441 lines, centerline.ts 289 lines)
- Fix PathEffect.MakeDash leak: store reference and .delete() after use
- Add aria-label to dash toggle button with i18n (strokeDash)
- Read dash/gap values directly from dashPattern array in template
  instead of calling dashState() 4 times
- Add unit tests for fitCircleArc and isClosedThinCrescent
  (collinear, non-circular, annular wedge, rectangle, hexagon, open path)
2026-05-03 22:49:12 +03:00
TKman 6651d88b87 fix(vector): render dashPattern strokes correctly on closed crescents
Vector nodes with stroke.dashPattern were rendering as solid lines, and
even when the dash was applied through brute outline-stroke conversion,
closed crescent shapes (e.g. annular wedges) showed two parallel dashed
arcs instead of a single arc along the centerline.

Root cause:

- drawVectorPathStrokes converted stroke to a fill outline before drawing,
  leaving no place for PathEffect.MakeDash to apply.
- A closed crescent is a single closed path traced around both the outer
  and inner arcs, so dashing the path produces two visible arcs.

Fix:

- packages/core/src/canvas/scene.ts: in drawVectorPathStrokes, when the
  stroke has a dashPattern, skip the outline conversion and draw with
  strokePaint + PathEffect.MakeDash directly. In renderShapeUncached,
  dashed VECTOR nodes with a vectorNetwork now route through a new
  centerline path instead of the precomputed strokeGeometry.
- packages/core/src/vector/index.ts: new vectorNetworkToCenterlinePath.
  For closed thin crescents (cycle of even length where pairwise opposite
  vertex distance is small relative to perimeter and consistent), detect
  the two cap segments via vertex direction-change angles, split the
  cycle into outer/inner subchains, pair vertices by angle, and emit a
  smooth Path.addArc when the midpoints fit a circle (fitCircleArc with
  3-point construction + radius tolerance), falling back to a midpoint
  polyline otherwise. Open paths use the existing chain walk.

JSX prop wiring so users can author dashed Vectors:

- packages/core/src/design-jsx/tree.ts: add strokeDash?: number[] | boolean.
- packages/core/src/design-jsx/renderer.ts: applyStrokeOverrides reads
  strokeDash and writes Stroke.dashPattern; true expands to [w*2, w*2].
- packages/core/src/io/formats/jsx/export.ts: solidStroke serializes
  dashPattern back to strokeDash on JSX export.

Editor UI:

- src/components/properties/StrokeSection.vue: dash toggle button plus
  ScrubInputs for dash and gap length, writing strokes[0].dashPattern.

bun run check passes. Pre-existing test failures (drop shadow / auto
layout) are unrelated to this change and reproduce on a clean
upstream/master.
2026-05-03 22:44:42 +03:00
Danila Poyarkov b85805e9c7 chore: format sources with oxfmt 2026-05-03 21:03:40 +03:00
Danila Poyarkov 7199774177 feat(app): bind size fields to variables 2026-05-01 17:20:45 +03:00
Danila Poyarkov c7db8c6004 feat(app): add color variable binding picker 2026-05-01 17:11:50 +03:00
Danila Poyarkov b248e0c53a feat(tauri): add signed updater 2026-05-01 13:00:50 +03:00
Danila Poyarkov 438a2bf901 fix(app): clarify layout spacing icons 2026-05-01 11:39:28 +03:00
Danila Poyarkov 9a1e67bbe3 fix(app): avoid duplicate tabs on hot reload 2026-05-01 11:12:19 +03:00
Danila Poyarkov 796815b65f feat(app): refine auto layout padding controls 2026-05-01 11:04:43 +03:00
Danila Poyarkov 7b0ae8fa2a fix(app): add size limit dropdowns 2026-05-01 10:50:25 +03:00