feat(shell): export dialog + multi-format raster (PNG/JPEG/WEBP) + Property-panel preview
Phase 1 — codec plumbing:
- RasterFormat::{Png, Jpeg, Webp} enum.
- export_raster(doc, target, format, scale) with NaN-guarded scale
clamp; JPEG forces white background (no alpha); quality 100/92/92
matches TS canvas.toDataURL.
- File dialog filters expanded from {PNG, SVG} to {PNG, JPEG, WEBP, SVG}.
- 6 unit tests cover format dispatch + alpha matrix + byte
signatures + scale clamp.
Phase 2 — ExportDialog modal:
- widgets/export_dialog.rs (new, ~330 lines). 5 format pills + 3 scale
pills + Cancel / Export buttons. ExportFormat::is_implemented gates
PDF off until Phase 4 ships real emit (codex stop-gate concern:
PDF pill was selectable but always errored).
- File menu "Export Image…" opens the modal first; the dialog's
Export button queues new FileAction::ExportImageConfirm which uses
Document.ui.export_format + export_scale.
- Native widget host: scrim + paint after figma-import modal,
top-most-modal hit-test slot before file-menu / canvas, Escape
closes the dialog.
- persistence.rs branches Confirm path on ui.export_format: PNG/JPEG/
WEBP via export_raster, SVG via export_svg, PDF returns an
explicit "not yet implemented" error (unreachable from UI now).
- 8 unit tests (format coverage, hit-test per pill, button rects,
centred-on-viewport, in-dialog contains, scale round-trip).
Phase 3 — Property panel preview:
- paint_export_section shows live ui.export_format / export_scale
instead of hardcoded "1x" / "PNG" placeholders.
- PropertyPanel struct gains export_format / export_scale; populated
by build_from_snapshot.
- PropertyPanelAction::OpenExportDialog routes to FileAction::
ExportImage on both native + web dispatchers.
Codex review: 2 rounds. Round 1 (Phase 1): NaN guard + nicer error
strings landed in-flight. Round 2 (Phase 2): 3 CONCERNs (Escape
unwired, PDF papercut, file-menu/dialog z-order race) + 2 NITs
(rect_contains half-open bounds, file caps) all addressed.
Tests: 195 total (177 shell-core + 12 shell-native + 6 desktop). wasm32
build clean.
Follow-ups: PDF emit (Phase 4), Property-panel section hit-test for
single-click open, input.rs / main.rs over-cap split.