Skip to content

fix: preserve Figma frame dimensions for editable clipboard copy - #36

Merged
trmquang93 merged 3 commits into
mainfrom
fix/copy-for-figma-editable
Apr 17, 2026
Merged

fix: preserve Figma frame dimensions for editable clipboard copy#36
trmquang93 merged 3 commits into
mainfrom
fix/copy-for-figma-editable

Conversation

@trmquang93

@trmquang93 trmquang93 commented Apr 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Copy for Figma (editable) produced a tiny empty rectangle in Figma when Satori-generated HTML lacked inline width:...px/height:...px on the outer element. The root frame collapsed to default or zero dimensions and hid all children.
  • After fixing the size, a second bug surfaced: the pasted frame was correctly sized but text was invisible and inline SVG icons produced empty FRAME subtrees.
  • Persist frame dimensions as first-class fields (sourceWidth, sourceHeight) at Figma paste time, resolve them preferentially over regex-parsing the HTML, and clamp the DOM-traversal root frame to a 100x100 minimum so pasted content is always visible/selectable.
  • Sanitize text font-family so CSS system keywords (-apple-system, system-ui, ...) and generic families never reach Figma, falling back to Inter.
  • Collapse <svg> icons into a single colored RECTANGLE placeholder at the SVG's bounding box (color derived from stroke / fill / currentColor; circular corner radius when the SVG contains a <circle> at a square aspect).
  • Backfill sourceWidth / sourceHeight in importFlow.js for older .drawd files so existing documents continue to open cleanly.

Changes

  • src/hooks/useFigmaPaste.js – capture frame.width / frame.height from convertFigmaBuffer alongside sourceHtml when creating the screen.
  • src/hooks/useScreenManager.js – add sourceWidth / sourceHeight to the makeScreen factory defaults.
  • src/utils/copyToFigma.js – add resolveViewport(screen) that prefers stored dimensions and falls back to the existing parseHtmlViewport regex; replace all parseHtmlViewport call sites in the editable-copy path.
  • src/utils/importFlow.js – default sourceWidth / sourceHeight to null for pre-existing flows.
  • mcp-server/src/figma-export/dom-traversal.js – clamp root frame width / height to a 100x100 minimum; add sanitizeFontFamily helper and swap the three text-node call sites to use it; special-case <svg> to emit a visible colored placeholder instead of recursing.

Test plan

  • npm run lint clean
  • npm test – 648/648 passing
  • Paste a Figma frame into Drawd, run Copy for Figma (editable), paste in Figma: editable layers appear at the correct frame size
  • Icons appear as visible colored placeholders at their original positions
  • Text renders after Figma finishes loading Inter (note: first paste can render blank until Figma's font loader resolves Inter; reload the Figma file to force resolution — tracked as follow-up to announce fonts via fontRequirements)
  • Open a pre-fix .drawd file, run the same copy: regex fallback keeps working for screens without stored dimensions
  • Copy multiple screens simultaneously: each child frame uses its own captured dimensions

Follow-ups (out of scope)

  • Editable vector icons: translate SVG path data to Figma vectorNetwork (non-trivial reverse of vectorNetworkToSvgPath in figmaToHtml.js).
  • Announce required fonts in the NODE_CHANGES message so Figma pre-resolves them before render; eliminates the "blank text until reload" behavior.
  • Round-trip the original Figma font name through figmaToHtml.js's resolveFontFamily so designer-chosen families survive instead of always landing on Inter.

Copy for Figma (editable) produced a tiny empty rectangle in Figma when
the Satori HTML lacked inline width/height on the outer element. The
regex-based parseHtmlViewport fell back to defaults or zeros, collapsing
the root frame so no child nodes were visible.

- Persist frame width/height as first-class screen fields at paste time
- Resolve viewport from stored dimensions first, regex as fallback for
  older screens
- Clamp root frame size to a 100x100 minimum in the DOM traversal to
  keep pasted content selectable even when dimensions are missing
- Backfill sourceWidth/sourceHeight for older .drawd files in importFlow
After the dimension fix, the pasted frame was the correct size but most
content was invisible: text rendered as empty and inline SVG icons
produced empty FRAME subtrees.

Two blind spots in the DOM traversal:

- makeTextNode forwarded the first token of the CSS font-family stack
  (e.g. "-apple-system") straight into Figma. That is a CSS system
  keyword, not a registered Figma font, so Figma rendered the text
  blank. Added sanitizeFontFamily to skip system keywords and generic
  families, falling back to Inter.

- Inline <svg> icons paint via SVG fill/stroke attributes, which the
  container path ignores (it only reads CSS background/border). Every
  path/circle became an empty FRAME. <svg> is now collapsed into a
  single colored RECTANGLE placeholder derived from stroke/fill/
  currentColor, with a circular corner radius when the SVG contains a
  <circle> at a square aspect.
@trmquang93
trmquang93 merged commit 6397293 into main Apr 17, 2026
1 check passed
@github-actions
github-actions Bot deleted the fix/copy-for-figma-editable branch April 17, 2026 01:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant