Commit graph

504 commits

Author SHA1 Message Date
Danila Poyarkov 5fa8895ef7 Update changelog with component properties and derivedSymbolData fixes 2026-03-04 18:14:23 +03:00
Danila Poyarkov 850490a224 Clean up instance-overrides: extract helpers, remove dead type
- Extract swapInstanceComponent() — reused by symbolOverrides and
  componentPropAssignments
- Extract findPropRefs() — componentId chain walk as standalone fn
- Flatten applyPropAssignments() into a recursive fn instead of
  nested closure inside a loop
- Remove unused ComponentPropDef interface
- Replace per-node Set allocation with depth-limited loop
- Remove unnecessary Record<string, unknown> cast
2026-03-04 18:12:30 +03:00
Danila Poyarkov 311fc76616 Apply derivedSymbolData sizes on import
Figma pre-computes child dimensions for each combination of
component property values and stores them in derivedSymbolData.
Apply these size overrides using the same guidPath resolution
as symbolOverrides so containers shrink correctly when boolean
properties hide children.
2026-03-04 18:05:24 +03:00
Danila Poyarkov a0a93ff500 Apply component property assignments on import
Evaluate componentPropAssignments from instances against
componentPropRefs on cloned children. Handles VISIBLE (boolean
toggle) and OVERRIDDEN_SYMBOL_ID (instance swap) property fields.
Walks componentId chain to find propRefs on source nodes.
2026-03-04 18:00:01 +03:00
Danila Poyarkov efdb09852f Fix override resolution for nested instance targets
When an override targets an instance node directly (not a descendant),
the node's componentId matches the remapped target. Check the current
node itself before searching descendants. Also prefer exact componentId
match over root-chain match for more precise resolution.
2026-03-04 17:04:49 +03:00
Danila Poyarkov 24207932f2 Fix stdin test: use pipe mode with explicit write/end
Buffer stdin may be consumed before the subprocess is ready on Linux.
Using pipe mode with explicit write/end ensures the data is available.
2026-03-04 16:56:58 +03:00
Danila Poyarkov c365f9f3b9 Fix flaky stdin test: use Buffer instead of ReadableStream 2026-03-04 16:41:26 +03:00
Danila Poyarkov b3b3e6256d Increase test timeout for CLI subprocess tests
CI runners are slower — 5s default timeout isn't enough for tests that
spawn bun subprocesses loading 55MB+ .fig fixtures.
2026-03-04 15:21:57 +03:00
Danila Poyarkov 26c14ac903 Unify instance override logic between .fig import and clipboard paste
Extract populateAndApplyOverrides() into instance-overrides.ts shared
by fig-import.ts and clipboard.ts. The clipboard path previously only
handled single-level guid paths and missed nested overrides, instance
swaps, and transitive propagation.
2026-03-04 15:15:19 +03:00
Danila Poyarkov 185e9e781b Fix CI: enable Git LFS checkout for test fixtures 2026-03-04 14:57:13 +03:00
Danila Poyarkov aa0a1b5def Fix workspace resolution: add bun export condition to core package
Bun picks the "node" condition pointing to dist/index.js which doesn't
exist in CI or fresh checkouts. Explicit "bun" condition ensures Bun
always resolves to source files.
2026-03-04 14:51:44 +03:00
Danila Poyarkov d087eb9b5d Update changelog with instance override fixes and profiler 2026-03-04 14:22:30 +03:00
Danila Poyarkov 74b09d4397 Merge branch 'renderer-profiler' into compatibility-fixes 2026-03-04 14:19:35 +03:00
Danila Poyarkov 713a0dc817 Fix instance overrides and layout scoping on .fig import
- Resolve symbolOverride guidPaths by overrideKey instead of guid
- Handle overriddenSymbolID for instance component swaps
- Propagate overrides transitively through the componentId clone chain
- Preserve internalOnly flag on canvas nodes during export
- Scope computeAllLayouts to current page for paste/undo/font-load
2026-03-04 14:18:37 +03:00
Danila Poyarkov 2a86ea5ce5 Add renderer profiler with HUD overlay, GPU timing, and phase instrumentation
Profiler modules in packages/core/src/profiler/:
- FrameStats: rolling 120-frame buffer tracking FPS, CPU/GPU times, node counts
- GpuTimer: EXT_disjoint_timer_query_webgl2 wrapper for GPU-side timing
- DrawCallCounter: WebGL context proxy counting draw calls per frame
- PhaseTimer: Chrome DevTools Performance panel custom tracks via User Timing API
- HudRenderer: in-canvas overlay with stats text and frame time bar graph
- CaptureStack: per-node profiling with speedscope JSON export
- RenderProfiler: orchestrates all layers with zero cost when disabled

Integration:
- SkiaRenderer.render() instrumented with phase markers for scene, picture
  recording/replay, section titles, component labels, selection, rulers, flush
- Node counting and viewport culling tracking in renderNode()
- WebGL2 context passed through for GPU timer and draw call counter
- Shift+P keyboard shortcut to toggle HUD overlay
- toggleProfiler() added to editor store
2026-03-04 14:13:17 +03:00
Danila Poyarkov 72f140700d Guard null cached filters in renderer destroy 2026-03-04 12:30:35 +03:00
Danila Poyarkov 9d35b3a9b5 Apply symbol overrides to cloned instance children on .fig import
Figma .fig files store instance overrides (text, fills, visibility,
layout props, corner radii, etc.) in symbolData.symbolOverrides on
each INSTANCE node. Each override targets a descendant via guidPath
(chain of original Figma GUIDs).

Implementation:
- convertOverrideToProps() in kiwi-convert.ts maps raw kiwi override
  fields to SceneNode properties (reuses existing conversion helpers)
- resolveOverrideTarget() walks the cloned instance tree to find the
  target node by following componentId chains
- getComponentRoot() traverses componentId references to handle
  multi-level cloning (instance of instance of component)
- 49K+ overrides applied across 8.8K instances in Preline UI file
2026-03-04 11:11:52 +03:00
Danila Poyarkov 989378f444 Guard against null Yoga children in layout application
Instance population can create scene graph children that don't
have corresponding Yoga nodes (e.g. when the Yoga tree was built
before cloned children were fully resolved). Skip null yogaChild
instead of crashing.
2026-03-04 10:59:01 +03:00
Danila Poyarkov 362212b712 Populate instance children from components on .fig import
During .fig import, instance nodes have no children — Figma expects
them to be cloned from their component. The componentId references
used original Figma GUIDs that didn't match our generated node IDs.

Fix:
- Track original GUID → new node ID mapping during import
- Remap all instance componentId references after node creation
- Iteratively populate empty instances from their components
  (multiple passes needed since cloning creates new instances)
2026-03-04 10:55:29 +03:00
Danila Poyarkov 6f605562e5 Update changelog 2026-03-04 10:44:06 +03:00
Danila Poyarkov 942d3be059 Add auto-save toggle to File menu
Checkbox item in File > Auto-save to local file. Enabled by default.
When disabled, the 3s debounced auto-save to the opened .fig file
is skipped — user can still save manually with ⌘S.
2026-03-04 10:43:20 +03:00
Danila Poyarkov 3c0c4e530c Guard against double destroy and post-unmount rendering
- Add destroyed flag to SkiaRenderer.destroy() to prevent double-free
- Check destroyed flag in renderNow() to skip rendering after unmount
  (loadFonts promise could resolve after component unmounts)
2026-03-04 10:35:44 +03:00
Danila Poyarkov 4e966c4976 Use byte comparison for fractional index sorting
localeCompare is locale-dependent and produces different orderings
across runtimes (Bun vs browser, Safari vs Chrome). Figma's
fractional indexing uses base-95 ASCII which sorts correctly by
code point.
2026-03-04 10:27:22 +03:00
Danila Poyarkov 707313c3ff Update changelog 2026-03-04 10:17:18 +03:00
Danila Poyarkov 9e3a6c0e7f Extract collectFontKeys into core and add font tests
- Extract font key collection from editor store into
  collectFontKeys() in packages/core/src/fonts.ts
- 18 tests: styleToWeight, weightToStyle, markFontLoaded/isFontLoaded,
  collectFontKeys (nested nodes, style runs, deduplication, default
  font filtering, invalid IDs)
2026-03-04 10:16:26 +03:00
Danila Poyarkov ed85451579 Await font loading before hiding file-open loader
- Make loadFontsForNodes async and await it in openFigFile so the
  loading overlay stays visible until all Google Fonts are ready
- Load fonts when switching pages (previously only loaded for first page)
- Paste still fire-and-forgets font loading for instant feedback
2026-03-04 10:11:12 +03:00
Danila Poyarkov a92315149a Always show visibility toggle on fill, stroke, and effect rows 2026-03-04 09:56:16 +03:00
Danila Poyarkov 1ca96094ab Fix stroke color row overlap
- Remove opacity ScrubInput from color row (matches Figma layout)
- Constrain ColorInput with min-w-0 flex-1 so it doesn't push buttons off
- Add shrink-0 to eye/remove buttons to prevent squishing
2026-03-04 09:54:09 +03:00
Danila Poyarkov e7426f3c58 Fix stroke section layout to match Figma
- Move side selector from header to details row (right of weight input)
- Use 4-dot grid icon for the side selector button (matches Figma)
- Highlight button border with accent color when independent weights active
- Use 3-line stroke weight icon instead of filled rect
- Header now only has section label + add button
2026-03-04 09:51:39 +03:00
Danila Poyarkov 45cab3e5fb Merge branch 'individual-strokes' into compatibility-fixes 2026-03-04 09:49:23 +03:00
Danila Poyarkov c412b78ed8 Add stroke align, individual side strokes, and side selector UI
- Renderer: stroke align (INSIDE/CENTER/OUTSIDE) using clip-based
  approach for all shape types including sections
- Renderer: individual side strokes when independentStrokeWeights is
  enabled, drawing per-side lines with correct align offsets
- StrokeSection: align dropdown, side selector dropdown (All/Top/Bottom/
  Left/Right/Custom), individual weight inputs with side-specific icons
- Kiwi serialize: export correct strokeAlign and border weight fields
- Scene graph: sync border weight props through instance sync
- 19 tests covering align, individual weights, instance sync, kiwi
  export, and FigmaAPI properties
2026-03-04 09:47:14 +03:00
Danila Poyarkov c3e0810620 Fix scrollbar: use utility class instead of global selector
Global * scrollbar styles caused scrollbars to appear on the
menu bar and other elements. Replace with scrollbar-thin utility
applied only to panel scroll containers.
2026-03-04 09:35:30 +03:00
Danila Poyarkov 969158a6c2 Move Google Fonts API key to constants 2026-03-04 09:33:42 +03:00
Danila Poyarkov 630277b896 Add Google Fonts fallback via Developer API
Fetch TTF files from Google Fonts when local and bundled fonts
are unavailable. Uses the Webfonts API for direct TTF URLs per
variant. Font file listings are cached per family; failed lookups
are recorded to avoid retries.
2026-03-04 09:32:45 +03:00
Danila Poyarkov 68cfba3b6d Fix text truncation: use textTruncation field instead of textAutoResize 2026-03-04 09:04:04 +03:00
Danila Poyarkov 8d22415ceb Update changelog with unreleased fixes 2026-03-04 02:33:30 +03:00
Danila Poyarkov 038c876bc6 Fix horizontal scrollbar on pages panel 2026-03-04 02:27:09 +03:00
Danila Poyarkov e17b3f3851 Enable watch feature for tauri-plugin-fs 2026-03-04 02:23:32 +03:00
Danila Poyarkov c40c6c78a8 Fix horizontal scrollbar on right panel, style scrollbars for Tauri 2026-03-04 02:21:06 +03:00
Danila Poyarkov b38d088383 Fix core tsconfig: add ESNext and DOM libs for npm publish 2026-03-04 01:58:37 +03:00
Danila Poyarkov 7ad4116860 Release v0.6.0 2026-03-04 01:47:37 +03:00
Danila Poyarkov 698ffe389a Remove Gatekeeper workaround — app is now code-signed 2026-03-04 01:45:29 +03:00
Danila Poyarkov 630e18a869 Merge branch 'fix-paste-layout' 2026-03-04 01:43:56 +03:00
Danila Poyarkov 6c87cf1a74 Update changelog with remaining paste fixes and infra changes 2026-03-04 01:43:52 +03:00
Danila Poyarkov 0b56cc1e70 Set up Apple code signing and notarization 2026-03-04 01:40:19 +03:00
Danila Poyarkov 101ce6a79e Fix broken MCP and eval-cli tests
MCP test: find_nodes searches currentPage only — switch to
FOUNDATIONS page where Button components live.
eval-cli test: material3.fig first page has 1 TEXT node, not 3 —
assert >0 instead of ==3.
2026-03-04 01:38:26 +03:00
Danila Poyarkov f23acb968f Use Cloudflare R2 for Git LFS storage 2026-03-04 01:25:33 +03:00
Danila Poyarkov 81ef1fbc45 Enable halfLeading for text rendering only, not measurement
Figma distributes leading equally above and below text. CanvasKit
defaults to ascent-biased leading. halfLeading: true fixes this but
must only apply to rendering (drawParagraph), not measureTextNode —
otherwise measured sizes change and break grid layouts.
2026-03-04 01:15:44 +03:00
Danila Poyarkov 8a13c5ffb6 Load all font weight/style variants needed by pasted text nodes
loadFontsForNodes only loaded the Regular style for each family.
Now collects fontWeight and italic from each text node and style
run, converts to style name via weightToStyle, and loads each
variant separately (e.g. Medium, Bold, Bold Italic).
2026-03-04 01:15:44 +03:00
Danila Poyarkov 26ed064b07 Fix InvalidCharacterError when copying nodes with non-ASCII text
btoa() only handles Latin-1. Use TextEncoder + binaryToBase64 for
encoding and TextDecoder + base64ToBinary for decoding the internal
OpenPencil clipboard format.
2026-03-04 01:15:44 +03:00