feat: download and copy-as-code for generated components#56
feat: download and copy-as-code for generated components#56GeneralJerel wants to merge 3 commits intomainfrom
Conversation
Add the ability to download any generated visualization as a standalone HTML file (with animations preserved) and copy the source code to clipboard. This addresses the artifact export story needed for the upcoming component gallery. - New export-utils.ts with assembleStandaloneHtml (widgets), chartToStandaloneHtml (bar/pie charts), and triggerDownload - Download and copy buttons added to SaveTemplateOverlay, visible on all generated components (widgets, bar charts, pie charts) - Export SVG_CLASSES_CSS and FORM_STYLES_CSS from widget-renderer - Update demo-gallery plan with Variant-style layout and #55 context Closes #14 Closes #42
- Single three-dot (...) trigger button in top-right corner - Dropdown menu with: Copy to clipboard, Download file, Save as artifact - Overlay only visible on hover (hides when cursor leaves component) - Menu stays open while interacting, closes on outside click
- Change three-dot trigger from vertical to horizontal orientation - Remove "Save as artifact" menu item and all save-related state/UI - Menu now shows only: Copy to clipboard, Download file - Clean up unused imports (useAgent, SEED_TEMPLATES)
GeneralJerel
left a comment
There was a problem hiding this comment.
Review
Overall: Solid feature, clean implementation. The export-utils module is well-structured, the overlay refactor to a dropdown menu is a good UX improvement, and there are no security issues. A few findings below.
Issues
1. description prop declared in interface but unused — save-template-overlay.tsx:13
The description prop is still in SaveTemplateOverlayProps but is never destructured or used in the component body (line 21–28 omits it). All three callers (widget-renderer.tsx:694, bar-chart.tsx:43, pie-chart.tsx:52) still pass description=. Either remove the prop from the interface + callers, or add a prefixed _description to the destructure to be explicit.
2. Duplicate import line — export-utils.ts:1-2
import { THEME_CSS } from "./widget-renderer";
import { SVG_CLASSES_CSS, FORM_STYLES_CSS } from "./widget-renderer";Should be a single import:
import { THEME_CSS, SVG_CLASSES_CSS, FORM_STYLES_CSS } from "./widget-renderer";3. No error handling on navigator.clipboard.writeText — save-template-overlay.tsx:70
The .then() has no .catch(). If the clipboard API fails (e.g., browser denies permission, iframe context), this silently does nothing. Consider a minimal .catch() so the user isn't left confused.
4. Component still named SaveTemplateOverlay — The component no longer handles "Save as Template" at all — it's now a download/copy overlay. The filename and export name are misleading. Consider renaming to ExportOverlay or ComponentActionsOverlay. (Low priority if planned for a follow-up.)
5. tmpl-slideIn animation referenced but not defined — save-template-overlay.tsx:116
The dropdown references animation: "tmpl-slideIn 0.15s ease-out". This was presumably defined in a global stylesheet or the old version of this file. Verify the keyframe still exists somewhere — if it was only in the old overlay's inline styles (now removed), the dropdown won't animate.
Nit
.chalk/plans/demo-gallery.md(156 lines) is a planning doc that inflates the diff. If intentionally checked in for project context, fine — but it's not a code change.
What looks good
escapeHtml()correctly applied to all user-provided strings interpolated into HTML — no XSS risk- Import map in
assembleStandaloneHtmlmirrors widget-renderer's shell, so downloaded widgets work standalone triggerDownloadproperly revokes the object URL after use- Outside-click handler correctly cleans up on unmount
Summary
Changes
New:
apps/app/src/components/generative-ui/export-utils.tsassembleStandaloneHtml()— wraps widget HTML in a full document with theme CSS, import maps, and stubbed bridge functionschartToStandaloneHtml()— generates standalone Chart.js HTML from structured chart datatriggerDownload()— browser file download via Blob + object URLslugify()— filename helperModified:
apps/app/src/components/generative-ui/save-template-overlay.tsxModified:
apps/app/src/components/generative-ui/widget-renderer.tsxSVG_CLASSES_CSSandFORM_STYLES_CSSconstants (needed by export-utils)Updated:
.chalk/plans/demo-gallery.mdCloses
Test plan
🤖 Generated with Claude Code