Commit graph

39 commits

Author SHA1 Message Date
Danila Poyarkov 1a03c0e8e8 refactor(app): reuse menu shortcut metadata 2026-05-17 14:44:37 +03:00
Danila Poyarkov 883601f548 fix(ui): format shortcut tooltips by platform 2026-05-17 14:31:29 +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 020b55a8ae test(app): cover Tauri file actions 2026-05-05 22:24:57 +03:00
Danila Poyarkov 0f9b69d051 test(app): expand Tauri helper coverage 2026-05-05 22:07:02 +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 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 a54c94c6a8 fix(app): use theme warning colors 2026-04-30 16:18:43 +03:00
Danila Poyarkov 6545f20c53
Refactor architecture boundaries across core, app, and packages (#234)
* refactor(core): decompose editor factory and action modules

Split the monolithic editor factory and large action modules into focused
domain helpers:

- create.ts assembles context through bridge modules (clipboard,
  components, structure, undo) and delegates to graph-reads, graph-events,
  layout-runner, component-sync, and state factory
- structure.ts delegates to group, container-wrap, auto-layout-wrap,
  reorder, and state-toggle helpers
- selection.ts delegates to hit-test, overlays, container navigation,
  and read helpers
- clipboard.ts delegates to subtree-history, images, export, copy,
  fonts, and placement helpers
- shapes.ts delegates to pen actions and section-adopt
- components.ts delegates to focus and instances helpers
- alignment.ts delegates to flip-rotate helper
- text.ts uses explicit TextEditSession for snapshot comparison

New focused modules: nudge, variable-bindings, layout-mode,
page-viewports, tool-registry, color-space

Undo: history/position and history/snapshot helpers, hardened
batch/rollback with nested batch support and configurable limit

* refactor(core): split tool definitions by domain

Split the monolithic tool registry into domain-specific modules:

- read/ — selection, find, pages, fonts, components, nodes, query, jsx
- create/ — basic shapes, components, vector, JSX render
- modify/ — paint, effects, geometry, layout, state, text, update
- structure/ — basic, arrange, batch, hierarchy, replace, tree
- variables/ — bindings, collections, read, values
- vector/ — boolean, path, export, viewport
- analyze/ — colors, typography, spacing, clusters, diff, eval
- describe/ — summaries, tree, roles, layout-issues
- stock-photo/ — providers, requests, apply
- codegen/ — component-map, tokens

Split registry into core/extended tiers; refine schema and AI adapter

* refactor(core): restructure kiwi codec and instance overrides

Reorganize the Kiwi .fig codec into domain subdirectories:

- binary/ — codec, schema, protocol
- fig/ — file, import, parse (core, worker, transfer)
- node-change/ — convert, export-node, serialize, plugin-data
- instance-overrides/ — constraints, dsd, populate, props, resolve,
  symbol-overrides, symbol-props, sync, types

Vendored kiwi-schema/ left isolated

* refactor(core): split profiler, icons, IO, and add subpath exports

Profiler: speedscope-export, capture-session, hud-controller
Icons: api, svg, types, render, create-icons tool
IO: format registry and subpath exports
Canvas/color/text/vector: targeted cleanup

Add deliberate subpath exports: random, xpath, vector, color, canvas,
scene-graph, kiwi, design-jsx, io, tools, editor, layout, canvaskit,
profiler, text, lint, rpc, figma-api, constants

* refactor(vue): decompose canvas input, surface lifecycle, and controls

Canvas surface: gl-surface, kit-loader, render-loop, resize-observer

Canvas input handlers:
- move: drop-target, move-snap, duplicate-drag
- select: select-move, select-hover, select-hit
- resize: resize-rect, resize-vector, resize-start
- transform: rotation, marquee, pan, text-selection
- text-edit: navigation, clipboard, textarea lifecycle
- Shared: click-count, space-key, pan, pan-zoom, draw, raf-scheduler

Editor composition:
- commands split: actions, context, metadata, edit, selection, view
- menu-model split: command-groups, builders, types
- Gradient stop composable reuse in primitive root

Controls: fill, layout, typography, appearance, effects, stroke,
okhcl, prop-scrub, node-props, undo-batch, color-variable-binding
Variables/i18n/document/export helpers

Organize canvas, primitives, controls, editor, and variables into
cohesive module directories with package-local import aliases

Expose MenuActionNode/MenuSeparatorNode from public API

* refactor(app): split document IO, editor session, and automation bridge

Document IO: source-state, naming, writer, reload-source, reload-state,
imported-document, watch-targets, save-targets

Editor session: create, modules, types, accessors, computed, refs
Editor canvas: loader-overlay, collaboration-awareness,
context-selection, menu-actions, menu-model

Automation bridge: eval, tools, exports, files, selection, RPC fallback

AI/ACP: transport, map-update, permission, debug, chat effects/storage
Collab: awareness, graph-bindings, yjs-sync, follow, session, types
Shell keyboard: actions, bindings, clipboard, focus, nudging,
raw-events, registry, reserved, shortcuts, space-tool
Shell menu: app-menu, document-name, entry, files

Demo: colors, effects, helpers, section builders (components,
app-preview, effects, standalone, variables) — document.ts reduced
from 981 to 32 lines as pure orchestrator

Move app modules under src/app/ with organized domain structure:
editor, document, ai, collab, shell, automation, demo, tabs

* refactor(app): decompose UI components with provide/inject context

Split monolithic components using Reka UI-inspired namespace folders
with scoped provide/inject context — no prop drilling:

- CollabPanel/ — context, avatars, share, connected, join
- ColorPickerPanel/ — context, area, format, field groups, sliders
- MobileHud/ — context, action toast, tool badge, file menu, presence
- ProviderSettings/ — context, API key/type, endpoint, tokens, photos
- Toolbar/ — actions, types, desktop, mobile, tool button, flyout
- LayoutSection/ — types, auto-layout, flex, grid, padding, size, clip

Properties helpers: fill-okhcl adapter, fill-label, color-style-row
Menu: entry helpers, document-name rename, stale type removal

* refactor(mcp): split server into focused modules

- browser-rpc — WebSocket client management
- mcp-sessions — session lifecycle
- tool-output — response formatting
- tool-schema — Zod schema generation from ToolDefs
- jsx-preprocess — JSX source transformation
- result — result helpers
- tool-registration — MCP tool wiring
- auth — API key validation
- http-options — CORS/request handling
- stdio-bridge — stdio transport adapter

* refactor(cli): split analyze subcommands and shared helpers

- Analyze subcommands: clusters, colors, spacing, typography
- RPC data loading helper
- Migrate imports to targeted core subpath exports

* refactor(docs): split VitePress config and shared table component

Config helpers: sdk-sidebar, seo, labels, sidebars, locale-theme,
root-theme, locales

Shared SdkDataTable component replaces duplicated table markup in
SdkPropsTable, SdkEventsTable, and SdkSlotsTable

Update contributing and testing docs

* refactor(tauri): decompose desktop entrypoint

Split lib.rs into focused service modules:

- fig_container.rs — .fig archive/compression commands
- fonts.rs — font cache and system font enumeration
- menu.rs — native menu construction
- menu_events.rs — menu event dispatch and devtools toggle
- window.rs — main window show/focus lifecycle

* test: share domain test factories and migrate fixtures

New shared helpers:
- tests/helpers/scene.ts — makeSceneGraph factory
- tests/helpers/vector-network.ts — vertex/segment/network builders
- tests/helpers/fig-traversal.ts — all-node collection, type counts
- tests/helpers/undo.ts — undo test utilities
- tests/helpers/editor-history.ts — editor history test helpers

Migrate render, vector, fig-roundtrip, and undo tests to use shared
factories instead of inline fixture construction

* build: add structural lint rules, split vite config, update docs

Structural lint (oxlint.structure.json + lint/plugin.js):
- 20+ custom rules enforcing package boundaries, lifecycle patterns,
  naming conventions, and import discipline

Vite config split: raw-markdown, canvaskit-assets, pwa, server,
aliases, automation plugins

Remove legacy shims and utils superseded by SDK/core modules
Update AGENTS.md, CONTRIBUTING.md, eval-command docs, tsconfig

* fix(vue): normalize canvas directory casing and remove duplicate export

- Rename Canvas/ to canvas/ in git index to match #vue/canvas/* imports
  (PascalCase was correct for component primitives but canvas/ is a
  non-component domain directory)
- Remove duplicate ./random subpath export in core package.json

* fix: add #vue and #core Vite resolve aliases for dev server

* refactor(core): reduce remaining large modules

Split the remaining large core hotspots into cohesive domain modules while preserving public facades and behavior.

- Extract scene graph types, variables, node defaults, and vector-network helpers
- Decompose canvas renderer orchestration, state, paints, colors, lifecycle, labels, and delegated domain methods into renderer/ and labels/ subfolders
- Split Kiwi node-change, binary variable binding, layout, RPC, vector, JSX export, clipboard, design JSX, and Figma proxy helpers
- Replace collision-driven *Fn import aliases with namespace imports and enforce the pattern in lint

Validation:
- bun run check
- bun --filter @open-pencil/vue build
- bun run test:dupes

* fix(app): forward color input attrs

* fix(app): cover section drawing errors

* fix(editor): undo option-drag duplicates

* docs: document domain subfolder convention

* fix(app): handle undo redo on keydown

* refactor(app): dispatch shortcuts from keydown

* refactor: group prefixed domain modules

* refactor(app): use tinykeys for shortcuts

* refactor(core): group symbol override modules

* refactor(core): group fig kiwi container helper

* refactor(canvas): split overlay rendering modules

* refactor(vue): remove unused internal barrels

* fix(app): lay out demo components before instancing

* fix(app): restore demo badge spacing

* perf(canvas): split scene and overlay rendering

* refactor(vue): wrap wheel gesture lifecycle

* fix(canvas): wait for fonts before hiding loader

* docs: update unreleased changelog
2026-04-30 15:14:19 +03:00
Danila Poyarkov 862c0f28dc Unify UI styling to slot-based useXxxUI() pattern
- Convert button, input, icon-button, toast, surface from flat-string
  functions to slot-based { base: string } return pattern
- Convert section from bare TV exports to useSectionUI() with
  { wrapper, label } slots
- Remove standalone select helpers (selectTrigger, selectContent,
  selectItem) in favor of useSelectUI()
- Delete use-component-ui.ts — no longer needed
- Update all 20+ call sites across property panels and chat components
2026-04-06 22:58:34 +03:00
Danila Poyarkov fd0124b6f8 Add Arabic and RTL support
# Conflicts:
#	packages/core/src/renderer/text.ts
2026-03-29 21:06:29 +03:00
Mahmoud Almontasser 9ed102b706 feat: add Arabic and RTL support 2026-03-29 11:42:30 +02:00
Danila Poyarkov 4b9cd54983 Translate more editor UI surfaces 2026-03-25 18:47:23 +03:00
Danila Poyarkov 1da5483fec Format remaining SDK refactor files 2026-03-24 17:02:01 +03:00
Danila Poyarkov 74bb119579 Extract Vue SDK editor controls and pickers 2026-03-24 09:50:28 +03:00
Danila Poyarkov fb2bfb5827 SDK rewrite: delete 15 useless wrappers, replace with composables
Deleted 15 fake headless component folders (slot-prop wrappers with
no provide/inject, no DOM, no ARIA). Replaced with 9 composables:
- usePosition, useLayout, useAppearance, useTypography
- useExport, useVariables, useFillPicker, useGradientStops, usePageList

Kept 6 real headless component families with provide/inject + DOM:
- Canvas (WebGL surface lifecycle)
- ScrubInput (pointer capture + input + display)
- Toolbar (wraps Reka UI Toolbar)
- LayerTree (wraps Reka UI Tree + Atlaskit DnD)
- PropertyList (array CRUD via inject)
- GradientEditor (pointer capture for stop drag)

App components updated: use composables directly instead of wrapping
in Root components. -1047 net lines, cleaner separation.
2026-03-17 20:22:41 +03:00
Danila Poyarkov d3aa511056 Self-review fixes: VueUse adoption, tailwind-variants recipes
VueUse:
- useClipboard({ copiedDuring }) in CodePanel, CollabPanel \u2014 replaces
  manual copied ref + setTimeout pattern
- useDebounceFn in stores/editor.ts \u2014 replaces manual clearTimeout +
  setTimeout for autosave
- onScopeDispose in ExportSection \u2014 replaces onUnmounted for URL cleanup

Tailwind-variants:
- New iconButton() recipe (sm/md) \u2014 replaces 15+ repeated button class strings
- New sectionWrapper() + sectionLabel() \u2014 replaces 12 repeated section classes
- Applied across all 10 property section components
2026-03-17 16:48:12 +03:00
Danila Poyarkov 2a69ef956f SDK: AppearanceControls, TypographyControls, EffectsSection via PropertyListRoot
New SDK components:
- AppearanceControlsRoot — corner radius, visibility, opacity, blend mode
- TypographyControlsRoot — font family/weight/size, alignment, formatting
  toggles, missing font detection, font loading

EffectsSection rewritten to compose from PropertyListRoot \u2014 effect CRUD
via SDK, scrub tracking stays in app component.

All property sections now use SDK headless primitives:
- PositionSection → PositionControlsRoot
- LayoutSection → LayoutControlsRoot
- AppearanceSection → AppearanceControlsRoot
- TypographySection → TypographyControlsRoot
- FillSection → PropertyListRoot
- StrokeSection → PropertyListRoot
- EffectsSection → PropertyListRoot
- ExportSection → ExportControlsRoot
2026-03-17 15:59:43 +03:00
Danila Poyarkov 2757e9e6ec Fix PR review: critical bugs, package config, remaining title attrs
Critical fixes:
- useSceneComputed: watchEffect on state.sceneVersion as fallback
  when replaceGraph() swaps the emitter (bridge stayed on old graph)
- useLayerDrag: capture monitorForElements() cleanup, onScopeDispose
  (was leaking global listeners on tab switch)

Package fixes:
- packages/vue: sideEffects: false for tree-shaking
- Build: --noEmit → --emitDeclarationOnly (was skipping .d.ts)
- canvaskit-wasm peerDep: optional via peerDependenciesMeta
- use-canvas.ts: onUnmounted → onScopeDispose

Remaining title= attrs migrated to <Tip>:
- AppMenu toggle UI, TypographySection missing font, ProviderSettings
2026-03-17 15:30:28 +03:00
Danila Poyarkov 8ba8c48a4c Migrate all title= attributes to Reka UI Tooltip via <Tip>
Replace 30 title= attributes across 15 component files with <Tip>
wrappers for proper Reka UI Tooltip behavior (positioning, delay,
animation, accessibility).

Remaining 2: TypographySection missing-font icon (non-interactive),
AppMenu toggle-UI button (browser menu context).
2026-03-17 12:22:25 +03:00
Danila Poyarkov 513b16983f Clean remaining component leaks: requestRender, graph access, variable queries
Editor API additions:
- bindVariable/unbindVariable in nodes.ts (with undo + requestRender)
- setPageColor in pages.ts (state + requestRender)
- getVariablesByType, getVariable, getCollection, resolveColorVariable,
  getVariablesForCollection, getCollections, getCollectionCount,
  getVariableCount in variables.ts (read-through to graph)
- getNode, getChildren, getPages in create.ts (read-through to graph)

Removed:
- 5 redundant requestRender() after updateNodeWithUndo
- All store.graph.* access from components (was in 7 files, now 0)

Remaining requestRender (1 file): EffectsSection uses updateNode
(without undo) which correctly needs manual render trigger.
2026-03-17 11:38:32 +03:00
Danila Poyarkov 0fbaf1c24e Adopt Reka UI Tooltip/ToggleGroup, VueUse useClipboard
Reka UI:
- Tip.vue — reusable tooltip wrapping Reka UI Tooltip primitives
- TooltipProvider in EditorView.vue with 400ms delay
- PositionSection: 9 title= attrs → <Tip> wrappers
- TypographySection: alignment → ToggleGroup (single),
  formatting (bold/italic/underline/strikethrough) → ToggleGroup
  (multiple). Keyboard nav + ARIA for free.

VueUse:
- useClipboard replaces navigator.clipboard.writeText in
  CodePanel, CollabPanel, MobileHud, CanvasMenu
2026-03-17 11:30:25 +03:00
Danila Poyarkov 51bbf67f1f Remove re-export shims, import directly from @open-pencil/vue
Delete 9 shim files from src/composables/ that just re-exported
SDK composables. All 18 consumers now import from '@open-pencil/vue'.

Merge dual useNodeProps()/useMultiProps() calls into single
useNodeProps() in 5 property section components.

src/composables/ now has only 4 app-specific files:
use-chat, use-collab, use-keyboard, use-menu.
2026-03-17 08:10:05 +03:00
Danila Poyarkov 4b49d69601 Fix all 135 vue-tsc errors, promote to check gate
vue-tsc now runs as part of 'bun run check' — zero errors.

Fixes by category:
- TS18047 (73): null guards in LayoutSection, TypographySection
- TS18048 (17): collab injection guards in CollabPanel, MobileHud
- TS2339 (12): Uint8Array.fromBase64/toBase64 declarations (ES2024)
- TS2741 (10): AppMenu discriminated union for separators
- TS6133 (7): remove unused declarations
- TS2322 (6): FontPicker AcceptableValue, Toolbar motion-v variants
- TS2345 (3): FillSection updateArrayItem signature, ScrubInput guard
- TS2314 (2): ChatMessage UIMessagePart generic args
- Misc (5): PropertiesPanel dead comparison, VariablesDialog onSubmit,
  NodeContextMenuContent document access, LayerTree arg count
2026-03-16 22:21:21 +03:00
Danila Poyarkov f44f06f721 Refactor: extract business logic from components, clean composables
Tier 1 — Core API fixes:
- updateNodeWithUndo now calls requestRender internally, eliminating
  15+ redundant requestRender() calls across 6 property panel components
- New editor/variables.ts: 7 undo-able variable CRUD operations
  (renameCollection, addCollection, removeCollection, addVariable,
  removeVariable, renameVariable, updateVariableValue)
- New editor/alignment.ts: alignNodes, flipNodes, rotateNodes —
  moves bounding box geometry out of PositionSection.vue

Tier 2 — Composable cleanup:
- Merged use-node-props + use-multi-props into single useNodeProps()
  with MIXED sentinel, eliminating duplicate computed refs
- Added toolCursor() utility — replaces if/else chain in EditorCanvas
- Renamed use-toast.ts → toast.ts (singleton module, not a composable)

Tier 3 — use-canvas-input.ts split (1497 → 817 lines):
- input/types.ts (88) — DragState variants, HandlePosition, TOOL_TO_NODE
- input/geometry.ts (170) — hit testing, handle positions, rotation cursor
- input/pan-zoom.ts (266) — wheel, touch pinch, Safari gestures
- input/resize.ts (121) — constrainToAspectRatio, applyResize
- input/auto-layout.ts (103) — insert indicator computation

All 955 tests pass, 0 lint errors, 1.48% duplication.
2026-03-16 20:35:17 +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
Anton Soldatov 3ac016b806
Add data-test-id attributes to all Vue components (#41) 2026-03-04 17:18:26 +03:00
Danila Poyarkov a5fe5b0cf4 Optimize zoom/pan smoothness with shallowReactive, useRafFn, and input coalescing
- Switch editor state from reactive() to shallowReactive() to avoid deep
  Proxy wrapping of Set, arrays, and nested objects
- Replace Vue watch on renderVersion with useRafFn dirty-flag loop that
  polls for changes without going through Vue's watcher scheduler
- Coalesce wheel events: accumulate deltaX/deltaY/zoomDelta between
  frames and flush once per rAF instead of per event
- Coalesce Safari gesturechange events: buffer the latest scale/position
  and apply once per rAF
- Fix Set.delete() on shallowReactive state to use replace pattern
2026-03-02 23:43:39 +03:00
Danila Poyarkov 25233294a1 Replace native <select> with reka-ui AppSelect component
- New AppSelect.vue: generic wrapper around reka-ui Select primitives
- Replace all 4 native <select> elements (font weight, effect type,
  export scale, export format)
- Fix font picker dropdown truncating names (min-w-56)
- Show explanation when local fonts unavailable (Safari/Firefox)
2026-03-02 21:33:04 +03:00
Danila Poyarkov ef78ffd2e4 Render text from SkPicture cache when fonts are missing
On copy: serialize each text node's paragraph into an SkPicture and
embed the bytes (base64) in the OpenPencil clipboard payload.

On paste: if the font isn't available, render from the cached SkPicture
instead of the broken fallback. Text looks pixel-perfect without the
font installed — same behavior as Figma.

Missing font indicator:
- Orange ⚠ badge on canvas over text nodes with unavailable fonts
- Amber warning banner in the Typography panel listing missing families
- isFontLoaded() check in @open-pencil/core for querying font status
2026-03-02 19:35:04 +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 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 70c88dd72b Replace relative imports with @/ alias
Add @/ → src/ alias in vite.config.ts and tsconfig.json.
Rewrite all ../ imports across 32 files to use @/ paths.
Same-directory ./ imports kept as-is.
2026-02-28 23:08:45 +03:00
Danila Poyarkov 046f23b138 Format codebase with oxfmt 2026-02-28 22:41:34 +03:00
Danila Poyarkov ce46dea54d Sort nodes by position when wrapping in auto-layout, fix null node crash
Nodes are sorted top-to-bottom (then left-to-right) before
reparenting into auto-layout frame. Positions are snapshotted
as plain IDs before mutation to avoid reactive proxy issues.

Property panel sections guard against null node with v-if,
preventing crashes during selection transitions.
2026-02-28 10:36:43 +03:00
Danila Poyarkov de83a716cd Remove prop drilling from property sections
All sections now get selected node from useNodeProps() composable
instead of receiving it as a prop from PropertiesPanel.
2026-02-28 09:27:03 +03:00
Danila Poyarkov 7b0f6c10f7 Fix text alignment icons: use direct template tags for auto-import 2026-02-28 09:01:54 +03:00
Danila Poyarkov 7e9cd46e0b Split PropertiesPanel into sections, add Typography panel
- PropertiesPanel.vue: 513 → 67 lines (composition only)
- Extracted: PositionSection, LayoutSection, AppearanceSection,
  FillSection, StrokeSection, TypographySection
- Shared useNodeProps() composable for update/commit logic
- Typography panel: font family picker with search (queryLocalFonts),
  weight select, font size, line height, letter spacing, text alignment
- Font picker shows each family in its own typeface
- Loads selected font into both CanvasKit and browser on pick
2026-02-28 09:00:25 +03:00