Skip to content

Figma offline importer (--fig): fidelity and robustness bugs found in a real org import #155

Open

Description

@iagh66

Context

Bulk-importing a real Figma org (35 .fig / .jam / .deck files exported with Save local copy) through the offline door maude design import-figma --fig surfaced a set of importer bugs. All of them were confirmed against real files and worked around with an in-memory Bun preload patch (no changes on disk); this issue collects them so they can be fixed properly in apps/studio/figma/* and apps/studio/bin/_import-figma.mjs.

1. Offline board import calls the REST API → Figma has no such file or node

importBoard() resolves image fills with resolveAssets(target.fileKey, …) (REST) even when local is set. The key is content-derived (fig<hash>), so every .jam with an image fails on the real write (dry-run passes). Frames already use resolveArchiveAssets(local, …); boards should do the same (pass imageRef through from pendingImages).

2. Hidden groups resurface

flattenWrappers() hoists the children of a styleless GROUP even when the group is visible:false, so its (visible) children render. Example: a hidden "glass card" group appeared as opaque white boxes on every OG frame. Skip hidden wrappers before flattening.

3. Font family is dropped → browser serif fallback

mapTypeStyle() deliberately never carries fontFamily, and imported artboards don't inherit the DS font, so all text renders in Times. Proposal: map the Figma family through an allowlist onto self-hosted DS stacks (sans / serif / mono) — the canvas CSP is font-src 'self', so the raw family can't be loaded anyway.

4. Paints and effects are mostly lost

mapNodeStyle():

  • ignores paint opacity (a 10 % white fill becomes solid white),
  • uses only the first visible fill (no stacking),
  • supports only linear gradients (no RADIAL / ANGULAR / DIAMOND),
  • maps only DROP/INNER shadows — LAYER/FOREGROUND blur (filter: blur), BACKGROUND_BLUR (backdrop-filter) and blendMode (mix-blend-mode) are dropped.

5. Forced contrast floor flips text colour

ensureContrast(ink, ctx.ground) measures against the frame's flat fill. When the real background is a child (gradient, image, blurred blobs) white text is "corrected" to black. Make it opt-in or compute it against the actual ground.

6. Component instances render empty

rebuildTree() (fig-decode.ts) gives INSTANCE nodes no children. They can be expanded from symbolData.symbolID → the SYMBOL's children (needs a cycle guard and a node budget — files with many instances otherwise exceed the 20 000-node cap).

7. Multi-page files

  • Frames: selectFrames() takes only the first CANVAS, so multi-page design files lose every other page (one file: 1 imported frame instead of 115).
  • Boards: all pages are translated into one world, stacked on the same coordinates, so content overlaps badly. Lay pages out side by side (or one board per page).

8. Files with no top-level FRAME are rejected

no FRAME or COMPONENT found for local files whose frames sit inside SECTIONs, or that only have top-level GROUPs. Descend into SECTION/GROUP and fall back to top-level groups.

9. Figma Slides (.deck) unsupported

The prelude fig-deck is refused. The container is the same format (spoofing the prelude to fig-kiwi decodes fine); slides live under Presentation > Row as UNKNOWN nodes and translate as frames.

10. Smaller items

  • buildClusterSvg ignores gradient fills (gradient blobs collapse to one solid colour).
  • A non-finite transform component (m00) aborts the whole decode; one bad node shouldn't fail the file (optionally: identity + report).
  • Some .jam exports fail with could not decode canvas.fig: malformed varuint64: more than 10 bytes (zip is valid).
  • SVG asset promotion launches a browser per asset (~8 assets/min); a 648-vector file takes ~80 min.
  • Each import emits one canvas per frame flat in ui/; a "one canvas per file, one artboard per frame" option (+ target folder) would avoid hundreds of canvases and later moves.

Repro

Any multi-page FigJam board, a design file with sections, an OG-image file with blurred instance backgrounds and a Slides deck reproduce 1–9.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions