Phase 2.5 — close the load/save loop so the editor isn't broken by a page
refresh.
Problem: easy-email doesn't ship an MJML→JSON parser, so loading an
existing MJML template into the editor canvas isn't possible. First-time
load = empty canvas. Without this fix, every page reload would also reset
to empty (even after saving), making the editor useless past one session.
Solution: persist easy-email's raw JSON tree (editor state) as a third
companion file alongside .mjml + .html. Editor reads .json on load when
present, falls back to empty otherwise.
Three files per template now:
gift-email-fr.mjml — MJML source (rendered by send-worker → already done)
gift-email-fr.html — compiled HTML (cached output, sent to recipients)
gift-email-fr.json — easy-email editor state (UI restoration only)
Backend (lib/campaigns.js):
- New templateJsonPath() helper + EDITABLE_TEMPLATES checks
- GET /campaigns/templates/:name returns { format, mjml, html, json }
when format=mjml (json null until first easy-email save)
- PUT /campaigns/templates/:name now accepts body.json alongside body.mjml
(writes both .mjml + .html [compiled] + .json [editor state])
- Backup hook extended to also backup .json before overwrite
Editor (EmailEditorApp.tsx):
- On load: prefer data.json → parse and seed initialValues. If json
missing but mjml present, show explanatory error banner + empty canvas
(user reconstructs once; that save fixes future loads).
- On save: send BOTH mjml (compiled via JsonToMjml) AND raw values
object as json. Hub persists all three artifacts.
First UX flow on next user visit:
1. Open editor → empty canvas + banner "MJML exists but no JSON
editor-state yet; reconstruct once to save a JSON snapshot"
2. User drag-drops blocks to rebuild the template visually
3. Click save → MJML + HTML + JSON all persist
4. Subsequent reloads load from JSON instantly with full editor state
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>