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).
- 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
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.
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.
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.
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
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.
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().
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
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.
- 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