Commit graph

210 commits

Author SHA1 Message Date
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
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
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
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
Danila Poyarkov 7ff16f70e0 Fix app identifier to net.dannote.open-pencil, add CLI/headless plan to PLAN.md 2026-02-28 23:51:22 +03:00
Danila Poyarkov 89e48c553e Update README features, highlight ~5 MB install and offline 2026-02-28 23:38:46 +03:00
Danila Poyarkov 00a2923007 Add screenshot to README 2026-02-28 23:36:55 +03:00
Danila Poyarkov d1979473c3 Add figma-use reference to AGENTS.md 2026-02-28 23:27:36 +03:00
Danila Poyarkov 7a803772a5 Expand AGENTS.md with lessons from all project sessions
Added from ~/Development sessions: scene graph behaviors,
auto-layout gotchas, section/frame rendering rules, .fig
round-trip testing, Tauri permissions, icon conventions,
ScrubInput cursor handling, culori usage, vueuse hooks.
2026-02-28 23:23:46 +03:00
Danila Poyarkov 199520ae86 Expand AGENTS.md with lessons from all sessions 2026-02-28 23:22:07 +03:00
Danila Poyarkov fdb3fec236 Add AGENTS.md 2026-02-28 23:18:55 +03:00
Danila Poyarkov d33efa1228 Update PLAN.md with Yoga CSS Grid PR tracking details 2026-02-28 23:17:39 +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 6eca2f8432 Consolidate shared types and remove inline duplicates
- Move Vector, Matrix, Rect to src/types.ts as shared primitives
- GradientTransform is now a type alias for Matrix
- Consolidate Window API declarations (File System Access,
  queryLocalFonts) in src/global.d.ts
- Remove duplicate KiwiNodeChange from fig-export.ts and
  clipboard.ts, reuse NodeChange from kiwi/codec.ts
- Add missing fields to codec NodeChange: frameMaskDisabled,
  stackWrap, stackPositioning, stackChildPrimaryGrow, vectorData
- Widen codec Paint.type and Effect.type unions to match scene-graph
- Replace inline Rect definitions across 5 files with shared type
- fig-export.ts fillPaints now constructs typed Paint objects
2026-02-28 23:06:47 +03:00
Danila Poyarkov 18609e00c3 Fix all lint, typecheck, and formatting issues
- 0 lint warnings (was 66), 0 type errors (was 30+)
- Add typed interfaces for Paint.stops/transform, Effect.blendMode,
  NodeChange.strokeCap/strokeJoin/dashPattern in kiwi codec
- Add CompiledSchema.encodePaint/encodeNodeChange methods
- Remove all no-explicit-any: use Window declaration merging for
  queryLocalFonts, showOpenFilePicker, showSaveFilePicker
- Remove all non-null assertions: add guards, use ?. operator,
  ?? fallbacks throughout
- Add SceneGraph.copyProp typed helper for instance sync
- Suppress kiwi-schema lint warnings via oxlint override
- Remove unused imports, variables, parameters
- Use proper CanvasKit StrokeCap/StrokeJoin enums in renderer
- Run oxfmt with import sorting
2026-02-28 22:57:33 +03:00
Danila Poyarkov 046f23b138 Format codebase with oxfmt 2026-02-28 22:41:34 +03:00
Danila Poyarkov e4eebf2a87 Move number input spinner hiding to global CSS 2026-02-28 22:40:01 +03:00
Danila Poyarkov d43fac94ca Hide number input spinners in Firefox
Add [appearance:textfield] alongside the WebKit spinner hide rule.
Firefox ignores ::-webkit-inner-spin-button but respects
appearance: textfield to remove native number spinners.
2026-02-28 22:38:36 +03:00