Skip to content

Preserve foreignObject content and hidden UI state in SVG exports#630

Merged
JackWilb merged 7 commits intomainfrom
copilot/fix-svg-export-foreignobjects
Apr 21, 2026
Merged

Preserve foreignObject content and hidden UI state in SVG exports#630
JackWilb merged 7 commits intomainfrom
copilot/fix-svg-export-foreignobjects

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

SVG downloads were being generated from raw outerHTML, which dropped foreignObject content in the exported file and let hover-only UI such as bookmark outlines show up incorrectly. This updates export serialization so the downloaded SVG matches the rendered plot more closely.

  • Export serialization

    • clone #upset-svg before export instead of dumping the live node with outerHTML
    • serialize with XMLSerializer and explicit SVG/XLink namespaces
    • preserve XHTML namespace on HTML nodes inside foreignObject
  • Computed style capture

    • inline computed styles onto the cloned tree so standalone SVGs keep the same visibility and appearance as the in-app render
    • this keeps CSS-hidden elements hidden in the exported file, including hover-only bookmark outlines
  • Regression coverage

    • add a focused Playwright check that captures the generated SVG blob and asserts:
      • foreignObject nodes are present
      • embedded XHTML content is preserved
      • hidden MUI bookmark icons remain hidden in the exported markup
export const serializeSVGForDownload = (svg: SVGSVGElement) => {
  const clone = svg.cloneNode(true) as SVGSVGElement;

  clone.setAttribute('xmlns', SVG_NAMESPACE);
  clone.setAttribute('xmlns:xlink', XLINK_NAMESPACE);
  inlineStyles(svg, clone);

  return new XMLSerializer().serializeToString(clone);
};

Reference screenshot from the issue:
https://github.com/user-attachments/assets/bde6da00-8274-4c9c-9cdf-58d2ec529e89

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • www.googletagmanager.com
    • Triggering command: /home/REDACTED/.cache/ms-playwright/chromium_headless_shell-1217/chrome-headless-shell-linux64/chrome-headless-shell /home/REDACTED/.cache/ms-playwright/chromium_headless_shell-1217/chrome-headless-shell-linux64/chrome-headless-shell --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl (dns block)
    • Triggering command: /home/REDACTED/.cache/ms-playwright/chromium_headless_shell-1217/chrome-headless-shell-linux64/chrome-headless-shell /home/REDACTED/.cache/ms-playwright/chromium_headless_shell-1217/chrome-headless-shell-linux64/chrome-headless-shell --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl t (dns block)
    • Triggering command: /home/REDACTED/.cache/ms-playwright/chromium_headless_shell-1217/chrome-headless-shell-linux64/chrome-headless-shell /home/REDACTED/.cache/ms-playwright/chromium_headless_shell-1217/chrome-headless-shell-linux64/chrome-headless-shell --disable-field-trial-config --disable-REDACTED-networking --disable-REDACTED-timer-throttling --disable-REDACTEDing-occluded-windows --disable-back-forward-cache --disable-breakpad --disable-client-side-phishing-detection --disable-component-extensions-with-REDACTED-pages --disable-component-update --no-default-browser-check --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=AvoidUnnecessaryBeforeUnloadCheckSync,BoundaryEventDispatchTracksNodeRemoval,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Transl = get && echo "****** (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 14, 2026

Deploy Preview for upset2 ready!

Name Link
🔨 Latest commit 5206427
🔍 Latest deploy log https://app.netlify.com/projects/upset2/deploys/69e79b0ae668040008c81374
😎 Deploy Preview https://deploy-preview-630--upset2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI and others added 3 commits April 14, 2026 16:13
Agent-Logs-Url: https://github.com/visdesignlab/upset2/sessions/f23f66dd-5e23-4ba8-9a19-59ec54600334

Co-authored-by: JackWilb <36867477+JackWilb@users.noreply.github.com>
Agent-Logs-Url: https://github.com/visdesignlab/upset2/sessions/f23f66dd-5e23-4ba8-9a19-59ec54600334

Co-authored-by: JackWilb <36867477+JackWilb@users.noreply.github.com>
Agent-Logs-Url: https://github.com/visdesignlab/upset2/sessions/f23f66dd-5e23-4ba8-9a19-59ec54600334

Co-authored-by: JackWilb <36867477+JackWilb@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix SVG download to include foreignObjects and hide hover elements Preserve foreignObject content and hidden UI state in SVG exports Apr 14, 2026
Copilot AI requested a review from JackWilb April 14, 2026 16:23
@JackWilb JackWilb marked this pull request as ready for review April 21, 2026 15:42
@JackWilb JackWilb merged commit 67962de into main Apr 21, 2026
5 of 8 checks passed
@JackWilb JackWilb deleted the copilot/fix-svg-export-foreignobjects branch April 21, 2026 15:49
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.

Download SVG doesn't pull in foreignObjects and some elements are always present

2 participants