Repository navigation
Figma offline importer (--fig): fidelity and robustness bugs found in a real org import #155
Description
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) andblendMode(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
buildClusterSvgignores 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
.jamexports fail withcould 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