The problem nobody talked about
(but everyone had).
- Export gives 12 separate PDF files — one per frame.
- Drag files to a sketchy online merger tool or Adobe Acrobat.
- Manually reorder them, hoping it's correct.
- Start over completely if the client asks for one revision.
- Select frames and get one combined PDF instantly.
- Outcome-based presets (e.g. "Client Deck", "Email Ready").
- Export history with 1-click recovery without re-running export.
- Settings persist across sessions automatically.
- Freelancers: Needs professional presentation, small file size.
- In-House Designers: Needs exact pixel dimensions, lossless quality.
- Design Students: Needs beautiful A4 output for portfolios.
- Users don't think in settings. They think in outcomes.
- Replaced configuration hurdles with 1-tap presets.
- One tap for common cases, one more tap for everything else.
Getting the core right
before adding features.
The first version did one thing: combined frames into a single PDF. No presets, no cover page, no history. The goal was to validate the core pipeline worked so a non-technical designer could export in under 30 seconds.
code.js) and the UI (ui.html). They communicate exclusively via postMessage. Every frame image had to be exported as PNG bytes, serialized as a binary string, passed to the UI, and assembled into PDF pages by jsPDF.apply() calls in QuickJS.MSG constants object to prevent mystery messages. Preferences are saved via typed round-trip patterns to figma.clientStorage. Every export is logged with its PDF bytes stored separately so previous exports can be re-downloaded instantly without re-running the pipeline.Three ways to solve this.
Only one shipped.
postMessage, and assemble the PDF client-side with jsPDF. No server, no extra tools, no file ever leaves the user's machine. Harder to engineer around Figma's memory limits, but it's the only option that kept the plugin free, private, and offline-capable.What the first users taught me.
Redesigning for outcomes.
Feedback wasn't about bugs, it was about UX failures. "Why is the quality slider grayed out?" "How do I add a cover page?" The plugin had no opinion about the common case, settings were labeled like engineering docs, and features were hidden behind tabs.
figma.clientStorage. On return, the plugin opens exactly where the user left off.Three bugs that taught me
more than the features did.
After a refactor, the plugin failed to load. The console showed a syntax error on user?.name. Figma's sandbox runs QuickJS, which lacked optional chaining support at the time. My local environment and Figma's sandbox were not the same.
Replaced with standard ternary checks. Lesson: Explicit failure surfaces problems; silent failure buries them. Using typed constants ensured the plugin failed loudly.
Clicking the accordion toggle rotated the chevron, but no settings appeared. The parent panel was set to overflow: hidden, clipping the expanded content.
Allowed panel scrolling and defined minimum heights. Lesson: Panel layout must be tested at extremes—max content, min content, and all expandable sections open simultaneously.
Users reported missing files after seeing the "PDF saved" toast. They had clicked Cancel on the OS Save dialog. The plugin had no visibility outside its execution context.
Changed semantics to "PDF generated — find it anytime in History." Positioned the History tab as a recovery mechanism since bytes are stored before the download dialog opens.
Accessibility & Visual System
in a constrained space.
- Keyboard Nav: Changed divs to
<button role="checkbox">with ARIA states. - Focus Traps: Escape key cancellation and focus containment for modals.
- ARIA Regions: Added
aria-livefor frame selection counts and toast notifications.
- Brand Restraint: Brand color
#0014D1reserved strictly for action/selection. - Semantic Tokens: Built a two-tier token system supporting seamless Dark Mode.
- Typography: Inter with strict scale (min 10px labels) for high-density legibility.
Four versions in 28 days.
What I learned as a Product Engineer.
Shipped sequentially across 28 days based on real user signal, not staging assumptions.
Up from 8%. A 4x lift from a single layout change moving it into the primary user flow.
In the core PDF export pipeline across all four versions due to disciplined architectural constraints.