Commit graph

386 commits

Author SHA1 Message Date
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
Anton A S 824193ab7a Sync specs & docs: canvas-native text editing, font-kit, variables dialog
- Merge from master: 19 commits (text editing, system fonts, variables UI)
- Update specs: text-editing (canvas-native, TextEditor, phantom textarea,
  font picker, keyboard nav, mouse selection), canvas-rendering (selection
  rects, blinking caret), editor-ui (Variables dialog with TanStack Table,
  ColorInput, alpha checkerboard, demo collections), desktop-app (font-kit
  commands, CSS font faces, app identity), undo-redo (variable operations)
- Update docs: features (text editing, font picker, variables, undo),
  figma-comparison (notes for text/fonts/variables rows),
  roadmap (Phase 4 delivered items)
- Archive sync-text-editing-fonts-variables-ui change
2026-03-01 09:47:46 +03:00
Anton A S 384c66f5b2 Merge remote-tracking branch 'origin/master' into add-vitepress-docs 2026-03-01 09:35:02 +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
Danila Poyarkov 5ccae709a0 Add system font enumeration via font-kit
Rust: list_system_fonts and load_system_font Tauri commands using
font-kit crate for cross-platform system font access.

TS: font shim detects Tauri runtime and uses invoke() for font
listing/loading instead of queryLocalFonts (unavailable in WKWebView).
2026-03-01 02:56:12 +03:00
Danila Poyarkov f7d1d59dc1 Rename Cargo crate to open_pencil, binary to OpenPencil
Fixes macOS Dock showing 'open-pencil-app' instead of 'OpenPencil'.
2026-03-01 02:42:12 +03:00
Danila Poyarkov faf7ba6387 Fix app name in macOS Dock: open-pencil-app → OpenPencil 2026-03-01 02:39:32 +03:00
Danila Poyarkov 20544366c7 Add undo/redo for all variable operations
Push undo entries for: create/delete variable, create collection,
rename variable, rename collection, change color/value.
2026-03-01 02:38:31 +03:00
Danila Poyarkov 6d407820eb Fix variables table not updating on create/delete
The variables computed was missing the sceneVersion dependency,
so it never re-evaluated when addVariable bumped sceneVersion.
2026-03-01 02:33:42 +03:00
Danila Poyarkov f9d5c43586 Fix missing type icons in variables table
Import lucide icon components explicitly for TanStack Table h() cells.
The icon-[lucide--*] CSS class syntax doesn't work with unplugin-icons.
2026-03-01 02:21:40 +03:00
Danila Poyarkov 2695cffe6a Extract ColorInput component, fix alpha slider checkerboard
New ColorInput component (swatch + hex, optional editable mode)
replaces duplicated ColorPicker + hex input pattern in:
- PageSection, StrokeSection, EffectsSection
- VariablesDialog (color variable cells use ColorInput with picker)

Fix ColorPicker alpha slider: add checkerboard background behind
transparent-to-color gradient, matching FillPicker's implementation.
2026-03-01 02:18:23 +03:00
Anton A S adea7eb645 Sync specs & docs: variables, image export, CLI, core extraction
- Update specs: scene-graph (variables/collections/modes/bindings/.fig import),
  editor-ui (VariablesPanel, ExportSection, splash), canvas-rendering
  (variable resolution, image export, sceneVersion/renderVersion),
  desktop-app (monorepo), tooling (Bun workspace), testing (variable tests)
- Create cli spec: info, tree, find, export commands
- Update docs: features, figma-comparison (79/150), roadmap (Phase 4 ,
  Phase 5 🟡), keyboard-shortcuts (⇧⌘E), contributing (monorepo structure)
- Restore vitepress devDependency lost during merge
- Archive sync-variables-export-cli change
2026-03-01 02:17:48 +03:00
Danila Poyarkov 52a31cff2f Use TanStack Table for variables dialog with resizable columns
Replace hand-rolled flex layout with @tanstack/vue-table:
- Proper <table> with column resizing (drag header borders)
- Sticky header row
- FlexRender for cell content (reka-ui Editable inline editing)
- Use culori via parseColor instead of manual hex parsing
2026-03-01 02:12:19 +03:00
Danila Poyarkov e3f8ff623e Redesign variables dialog to match Figma's table layout
- Column per mode (Name | Mode 1 | Mode 2 | ...)
- Color swatch inside value cells, not next to name
- Search bar in top-right header area
- Single '+ Create variable' button at bottom
- Wider dialog (800px) for multi-mode tables
2026-03-01 02:08:13 +03:00
Danila Poyarkov 0ed1bfdf63 Add variable collections to demo: primitives, semantic tokens, spacing
Three collections with multi-mode support:
- Primitives (Light/Dark): 9 color variables with dark mode values
- Semantic (aliases): Background, Text, Border, Accent, Success, Error
- Spacing (Default/Compact): 8 number variables for space + radius tokens

Binds variables to dashboard sidebar, header, frame, and titles.
2026-03-01 02:04:41 +03:00
Danila Poyarkov 04b0efd020 Double-click to rename collection tabs in variables dialog 2026-03-01 02:03:24 +03:00
Danila Poyarkov ff48c03d5a Fix addCollection not triggering reactivity 2026-03-01 01:59:01 +03:00
Danila Poyarkov 5c4c6d7289 Move variables UI to dialog opened from page properties
Match Figma's pattern: Variables section in page-level properties
panel with settings icon that opens a full dialog (reka-ui Dialog +
Tabs + Editable). Remove sidebar panel approach.
2026-03-01 01:54:57 +03:00
Anton A S 79c2d823b5 Merge remote-tracking branch 'origin/master' into add-vitepress-docs
# Conflicts:
#	bun.lock
#	package.json
2026-03-01 01:53:19 +03:00
Danila Poyarkov e322d53ee3 Merge branch 'tauri-icon' 2026-03-01 01:52:14 +03:00
Danila Poyarkov d30ab4c5d5 Add variables support: collections, modes, bindings, UI panel
Core:
- Variable, VariableCollection types on SceneGraph
- resolveVariable with alias chain resolution + cycle detection
- Mode switching (activeMode per collection)
- Bind/unbind variables to node properties (fills/strokes colors)
- Renderer resolves variable bindings before painting

Import:
- Parse VARIABLE type NodeChanges from .fig files
- Extract paint variable bindings (fills/strokes)

UI:
- VariablesPanel with reka-ui Tabs (collections) + Editable (names/values)
- FillSection: variable picker (reka-ui Popover + Combobox)
- Bound fills show purple variable name badge with detach button
- Toggle variables panel from bottom of layers sidebar

7 new unit tests (83 total)
2026-03-01 01:47:52 +03:00
Danila Poyarkov 88765bd078 Use pencil loader icon as Tauri app icon 2026-03-01 01:37:01 +03:00
Danila Poyarkov aa083206d9 Add minimalist splash loader during WASM initialization 2026-03-01 01:30:01 +03:00
Danila Poyarkov 97428949fc Update README and AGENTS.md for monorepo structure and CLI 2026-03-01 01:28:24 +03:00
Danila Poyarkov eb6f8914a6 Point core exports to src/index.ts for Vite compatibility 2026-03-01 01:27:04 +03:00
Danila Poyarkov 22e9e7af87 Prepare packages for npm publishing
- Core: conditional exports (bun→src, import→dist), prepublishOnly builds
- CLI: publishConfig, files field
- bun publish resolves workspace:* → actual versions
2026-03-01 01:24:54 +03:00
Danila Poyarkov 33435cc1d1 Make CLI available as bun open-pencil in workspace 2026-03-01 01:21:29 +03:00
Danila Poyarkov 1975fae36e Add @open-pencil/cli with headless .fig file operations
Commands:
- open-pencil info <file> — document stats, node types, fonts
- open-pencil tree <file> — visual node tree with agentfmt
- open-pencil find <file> — search by name/type with rich output
- open-pencil export <file> — render to PNG/JPG/WEBP at any scale

Uses canvaskit-wasm/full for headless CPU rasterization.
All commands support --json for machine-readable output.
agentfmt provides consistent colored terminal formatting.
2026-03-01 01:19:24 +03:00
Danila Poyarkov 6d1cec1606 Extract @open-pencil/core package
Move engine (scene-graph, renderer, layout, codec, kiwi) into
packages/core/ as a Bun workspace package. The app's src/engine/
files become thin re-export shims so all existing imports still work.

- Bun workspace with packages/core
- Zero DOM dependencies in core
- Headless-friendly CanvasKit init (locateFile option)
- Constants split: engine constants in core, UI constants in app
- All 76 unit tests pass, lint + typecheck clean, vite build works
2026-03-01 01:03:07 +03:00
Danila Poyarkov 80972c1479 Add loading placeholder while export preview renders 2026-03-01 00:53:21 +03:00
Danila Poyarkov 63cefe651c Add CSS containment to side panels to reduce repaints from canvas 2026-03-01 00:45:20 +03:00
Danila Poyarkov 8eff364dcd Use sceneVersion instead of renderVersion for UI panels
renderVersion bumps on every pan/zoom/hover — it's the canvas redraw signal.
sceneVersion only bumps when the scene graph actually changes.

Panels (properties, layers, context menu, pages, export preview) now
depend on sceneVersion, so they don't re-render during navigation.
2026-03-01 00:33:14 +03:00
Danila Poyarkov fee028b49f Fix export preview flicker: keep old image until new one is ready, avoid renderVersion deps 2026-03-01 00:32:17 +03:00
Danila Poyarkov ee16ce89de Split requestRender/requestRepaint to avoid export preview updates on pan/zoom 2026-03-01 00:29:42 +03:00
Danila Poyarkov cb6c042c07 Merge remote-tracking branch 'origin/export-image' 2026-03-01 00:20:00 +03:00
Danila Poyarkov 103c5617cf Re-encode JPG/WEBP via OffscreenCanvas instead of CanvasKit
The default CanvasKit WASM build lacks JPEG/WEBP encoders. Instead of
switching to the full build (+1MB), render PNG via CanvasKit then
re-encode to the target format using OffscreenCanvas.convertToBlob().
2026-03-01 00:11:48 +03:00
Danila Poyarkov 7d4b821e77 Use full CanvasKit build for JPEG/WEBP encoding support
The default canvaskit.wasm doesn't include JPEG/WEBP encoders
(encodeToBytes returns null). The full build is only ~1MB larger.
2026-03-01 00:09:35 +03:00
Danila Poyarkov 7a5a77c098 Render export preview at panel-appropriate resolution
Scale preview to fit 480px width (covers retina panels) instead of
fixed 0.5x which was far too blurry.
2026-03-01 00:05:19 +03:00
Danila Poyarkov 2fb22786c1 Add types/shared code and test strategy to CLI plan 2026-03-01 00:03:26 +03:00
Anton A S 82302de176 Sync specs and docs with master: AGENTS.md, CLI plan, app identifier, offline messaging
Sync 8 commits from master (70c88dd..7ff16f7):
- App identifier fixed to net.dannote.open-pencil
- AGENTS.md reference added to contributing docs
- CLI & Headless Mode added to Phase 5 roadmap
- Offline messaging added to Desktop App feature docs
- MCP server row updated with CLI/headless note
2026-03-01 00:02:05 +03:00
Danila Poyarkov e8223287df Validate all CLI risks: CanvasKit CPU, headless fonts, extraction scope 2026-03-01 00:00:31 +03:00
Danila Poyarkov 55b0b37789 Add image export (PNG/JPG/WEBP) with shared rendering
Extract renderThumbnail and renderNodesToImage into render-image.ts,
reusing the same offscreen CanvasKit surface approach that fig-export
used for thumbnails.

- ExportSection in properties panel with scale/format selectors,
  multi-export support, and live preview with checkerboard background
- Export via context menu and ⇧⌘E shortcut
- Tauri save dialog, File System Access API, and download fallback
- JPG renders with white background, PNG/WEBP with transparency
2026-02-28 23:59:39 +03:00
Danila Poyarkov 44e914d410 Detailed CLI & headless implementation plan 2026-02-28 23:58:47 +03:00
Danila Poyarkov f61d8c0d83 Add headless rendering plan (CanvasKit CPU rasterizer) 2026-02-28 23:54:10 +03:00
Anton A S c31b0aa239 Merge remote-tracking branch 'origin/master' into add-vitepress-docs 2026-02-28 23:52:49 +03:00