@seliseblocks/mailcraft 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,135 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@seliseblocks/mailcraft` are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.2.10] — 2026-09-01
8
+
9
+ ### Fixed
10
+ - **A per-block font survives a save.** Heading, text, list, button, menu and table blocks read their `font-family` back on import, so a font chosen in the inspector is still there after export → reload. A text run claims its family at block level only when every piece of the run shares it — a mixed-typography run keeps its inline declarations instead — and a claimed family is removed from the run's own HTML, so the same declaration never ships twice and a second save produces the same bytes as the first.
11
+ - The heading block's **Condensed** stack folds back into the Font style toggle it came from, instead of importing as an opaque per-block font.
12
+ - The document font is read from the `<body>` first. The element scan that stood in for it crowned the *first block's* font, so one custom-font heading at the top of an email flipped the whole theme on reload; the scan stays as the fallback for emails that declare nothing on the body.
13
+ - **An imported font stack is now selectable in the Font controls.** The select ships ten email-safe stacks and an imported email's stack matches none of them — a `<select>` whose value is in no option shows nothing selected, so the panel read "Inherit — theme font" about a heading really set in Georgia, and the only way to learn what font it was was to overwrite it. The document's own stack keeps a seat of its own, labelled by its first family.
14
+ - **Values that only restate the theme are inherit again.** The exporter writes every inherited value as a concrete declaration, so a round trip used to come back with the theme font, text ink and content background stamped onto every block and row as an explicit override — visually identical, but a later theme edit no longer reached anything. Anything genuinely different from the theme is a real override and stays.
15
+ - Styling that quietly reset on every save/reload is read back: a **list**'s font, size, ink, line spacing, item gap and spacing; a **table**'s font, size, width, cell padding, alignment and header tint; a **menu**'s item spacing; an **image** block's own padding; a **divider**'s spacing (a declared zero included, so a tightened divider stops springing back to 14px); a **column**'s border.
16
+ - Zebra striping is claimed only from the renderer's own stripe tint, and only where there are enough body rows to tell — imported tables were striped by default before, whether the source was or not.
17
+ - A section's **background overlay** survives. The tint ships as the exporter's own `linear-gradient(rgba(20,22,24,α)…)` over the photo; unread, the percentage vanished on every save while the image stayed. Only that exact signature is folded back — a foreign gradient says nothing about MailCraft's tint and stays out of the model.
18
+ - **The save → reload round trip now preserves every remaining inspector control** (a full fidelity pass against the exporter's own markup — export → import → export is byte-identical from the first reload on):
19
+ - A **linked image**'s width — it lives on the wrapping anchor, so every linked logo reloaded at full width.
20
+ - A **heading**'s line spacing and its own padding, an explicit zero included.
21
+ - **Social strips**: Outline and Bare no longer reload as filled Square badges (`background:transparent` is the absence of a fill, not a fill); a badge strip keeps its fill color instead of the black/white contrast ink painted over it; per-network colors fold back into the **Brand** palette; icon spacing reads off the anchors instead of the row gutter; and a strip with **Show network names** on reloads as the social block it is — it used to come back as a menu of links.
22
+ - A text block's **Show on** (device visibility) — recognized blocks kept it, text runs dropped it.
23
+ - Adjacent zero-padded **text blocks stay separate blocks**: buffered together, the second lost its size, weight — everything — on every save. A text block's declared-zero spacing sticks, too.
24
+ - **Rows**: vertical align; the single-column gutter (content crept 10px wider per side on every reload — a social strip's or data table's row included); the outside-margin sliders' values; and the explicit mobile modes (two-up, reverse) read back off their own classes.
25
+ - **Columns**: background, radius, inner padding and border now survive `loadTemplate`/`loadDoc` at all — `normalizeDoc` rebuilt every column without its styling, so a card column lost its paint before the export ever ran.
26
+ - The **Inline SVG block no longer vanishes** on reload (previously the sanitizer dropped the drawing and the whole row with it), and the export now ships the same width-carrying span the canvas draws, so the Width slider reaches sent mail and reads back.
27
+ - **Line spacing stops drifting**: the exporter ships line-height in px (Outlook needs a length), and dividing back rarely landed on the slider's value — 1.6 at 16px reloaded as 1.625, moving again on every save. Recovered ratios snap to the nearest slider step that reproduces the same pixels.
28
+ - Colors reload as the **hex** the picker understands, not CSSOM's `rgb(…)` serialization (button, divider, menu, social) — also what lets the theme-equality folds actually match.
29
+ - **`theme.link` is no longer hijacked on import** by menu items, whose navigation color outvoted the document's real links.
30
+ - Dynamic-content **marker rows stop picking up wrapper backgrounds** — a page background stamped onto one painted a colored band in the canvas that no sent mail would show.
31
+ - HTML import: a nested layout table's header-cell veto now applies only to the candidate's own row, so a **data table inside a column** keeps the gutter around it.
32
+
33
+ ### Added
34
+ - **Fidelity markers.** A handful of blocks render into markup that cannot be read back into the block it came from — a countdown bakes its digits into text, a video is just a linked image, a section box and a code sample are styled divs like any other, a raw-CSS block is a bare `<style>`, and a flex/grid row is a div no table walker can re-shape. The export now stamps a compact attribute layer (`data-mc`, `data-mcp`, `data-mcr`, plus the inert `mc-keep` class) that the importer trusts when present and ignores otherwise; mail clients ignore unknown attributes wholesale. The payoff on reload: a **countdown is live again** instead of stale baked digits, video / section box / code / raw CSS keep their identity and every setting, **flex and grid rows stop collapsing to one column**, and "On mobile: Keep columns" survives. Content halves are still read from the rendered markup and sanitized like any import — a marker is data about a block, never markup to inject. Hosts that want pristine HTML can pass `exportHtml({ markers: false })` and accept the lossy (content-preserving) reload.
35
+ - **The theme's Link color now does something.** It was a dead control end-to-end: the canvas painted content links in the *host's* brand accent, the export shipped no color (recipients saw their client's default blue), and the importer re-guessed the value from a vote that menu items always won. Now the canvas paints sheet links from the theme, the export stamps the color inline on every anchor that has none of its own (mail clients have no stylesheet to inherit from — a hand-colored link keeps its color), the import folds restated stamps back to inherit so a later Link color edit still reaches every link, and menu/social anchors are barred from the recovery vote.
36
+ - **Row outside margins render in sent mail, and Max width ships at all.** Margins were exported on a `<td>`, which every major client ignores; Max width existed only on the canvas. Both now ride a wrapper `<div>` inside the cell — margins outside the painted box, exactly as the canvas draws it, auto-centered when capped — and read back on reload. Rows using neither keep the old markup byte for byte.
37
+ - The **raw-CSS block round-trips as itself**: it reloaded as an opaque HTML block while its rules were *also* folded inline onto every matched element, doubling the styling on each save. Marked, it comes back as the css block (note included) and the fold skips it; foreign `<style>` tags keep today's fold-inline behaviour.
38
+ - `test/roundtrip.test.mjs` — the round-trip fidelity suite: one assertion per inspector control (blocks, rows, columns, theme), block-identity coverage for every marked type, a byte-convergence check, and the `markers: false` degradation floor asserting no content is ever silently dropped. Runs in `npm test`.
39
+
40
+ ### Removed
41
+ - The **Embed block**. It exported an `<iframe>`, and mail clients strip iframes wholesale — Gmail, every Outlook, Yahoo and the rest drop the tag or render a blank gap — so the block only worked in browser contexts and shipped dead weight in every real send. A document saved with one loses nothing: it reloads as a raw-HTML block holding the same iframe markup it always exported.
42
+
43
+ ## [0.2.9] — 2026-09-01
44
+
45
+ ### Fixed
46
+ - HTML import keeps a section that paints one background image as **one row**: builder sections (Beefree, Stripo — `table.row[background-image]` holding a stack of per-block tables) previously shredded into one row per block, each stamped with its own copy of the background image, so the export re-drew the image's top slice once per block. Blocks keep their per-block padding through the merge; rows with their own background, frame or image are real band boundaries and are never merged.
47
+ - HTML import reads the section cell's own background color ahead of its wrapper's — a dark hero cell inside a white content table kept `#ffffff` before, so white text over a background photo sat on a white band whenever the image hadn't loaded. Legacy `bgcolor` on a `<tr>` is now read too.
48
+ - Imported headings inherit `color` and `text-align` from the section cell the way text runs always did — a white `<h1>` whose color lived on the `<td>` imported in the default dark ink and vanished into the hero image. An own value on the heading still wins.
49
+ - A bare text run with no element of its own (`<td style="font-size:30px;font-weight:800;color:#0065b2">{{Code}}</td>`) now reads its typography off the surrounding cell — verification codes and similar merge-tag cells imported at the theme default before, dropping size, weight, color and line-height on every save.
50
+ - A card drawn across several stacked content tables (top piece `border-radius:16px 16px 0 0; border-bottom:none`, side-borders-only middles, bottom piece `0 0 16px 16px`) no longer doubles its frame: the fragments are consumed into the one canvas frame the theme claims, while horizontal edges between sections survive as separators.
51
+ - Beefree-style dividers — a content-free cell whose only drawing is a `border-top`, usually in a `width="20%"` inner table — import as the divider block they draw instead of a junk text row holding a hair space.
52
+ - Empty `<p></p>` elements no longer import as phantom padded rows.
53
+ - The inspector's color swatch normalizes `rgb()`, `rgba()` and 3-digit hex before feeding the native picker, which only accepts `#rrggbb` — imported non-hex colors showed a black swatch and opened the picker at black.
54
+ - Text size, Text color, Line spacing, Text weight and Align now reach imported (and AI-drafted) text and list blocks whose content carries its own inline styles. Text size scales the inline hierarchy proportionally (a 15/26/15px block moved to a 45px base becomes 45/78/45); the other controls take ownership of their property, the way the Font control already did. The rewrite happens only when the control is moved, lands in a single undo step, and untouched documents are never rewritten.
55
+ - The rich-text toolbar's Smaller/Larger text buttons clamp to the same range as the inspector (8–96 for text, 12–120 for headings). Previously they clamped to a private 10–64, so one "Larger text" click on a 96px block shrank it to 64.
56
+ - The Smaller/Larger text buttons no longer appear on Section box and Raw HTML blocks, where no size is rendered — clicking them only wrote an inert `size` value into the saved document.
57
+ - Imported list items now pass through the same sanitizer as every other import path: event handlers, classes and `mso-*` noise are stripped while whitelisted inline typography survives.
58
+ - The docs site's Quick-start hero follows the site's own theme toggle instead of the OS scheme, so the colour logo shows on the light page (the near-white dark-mode wordmark no longer paints on white).
59
+
60
+ ### Added
61
+ - Copy button in the code view's toolbar — puts the source pane on the clipboard, unsaved edits included, with a "Copied" confirmation. Localized in all 31 locales from existing strings.
62
+
63
+ ## [0.2.8] — 2026-09-01
64
+
65
+ ### Added
66
+ - Mobile layout modes and per-device visibility handling.
67
+ - Open Graph card generation tooling.
68
+
69
+ ### Fixed
70
+ - HTML import preserves the row → column → blocks structure.
71
+ - Docs site renders the Quick start page (`/README.html` no longer 404s).
72
+
73
+ ## [0.2.7] — 2026-09-01
74
+
75
+ ### Added
76
+ - Brand artwork SVGs and theme-aware logo rendering.
77
+
78
+ ### Fixed
79
+ - Drag and drop: dropped sections land where the indicator line shows.
80
+
81
+ ## [0.2.6] — 2026-08-31
82
+
83
+ ### Fixed
84
+ - Round-trip: saving never mutates the design.
85
+
86
+ ## [0.2.5] — 2026-08-31
87
+
88
+ ### Fixed
89
+ - Visible elevation on the settings panel.
90
+
91
+ ## [0.2.4] — 2026-08-31
92
+
93
+ ### Added
94
+ - Marketing homepage on the docs site.
95
+
96
+ ### Fixed
97
+ - Slider behaviour under RTL locales.
98
+
99
+ ## [0.2.3] — 2026-08-31
100
+
101
+ ### Added
102
+ - Full content-area border as a theme option.
103
+
104
+ ## [0.2.2] — 2026-08-31
105
+
106
+ ### Changed
107
+ - Locales load lazily, one file per language, instead of shipping all translations up front.
108
+ - Preview polish.
109
+
110
+ ## [0.2.1] — 2026-08-31
111
+
112
+ ### Changed
113
+ - Canvas and chrome polish.
114
+
115
+ ## [0.2.0] — 2026-08-31
116
+
117
+ ### Added
118
+ - TypeScript declarations ship with the package.
119
+ - Condition and Loop dynamic-content marker blocks, exported as literal `{{#if}}` / `{{#each}}` template tags.
120
+ - Expanded translations and improved AI draft functionality.
121
+
122
+ ### Removed
123
+ - Seeded example assets in the library; local library handling improved.
124
+
125
+ ## [0.1.1] — 2026-08-30
126
+
127
+ ### Added
128
+ - Documentation site; the full configuration surface documented; restyled demo.
129
+
130
+ ### Fixed
131
+ - Contributing link 404; dark scrollbar on a light page.
132
+
133
+ ## [0.1.0] — 2026-08-30
134
+
135
+ Initial release: a drag-and-drop HTML email editor packaged as a zero-dependency Web Component — HTML-only host contract, configurable top bar, `createEditor` mount API, minified single-file build, and a test suite covering the core, render layer and keyboard shortcuts.
package/DOCS.md CHANGED
@@ -8,18 +8,18 @@
8
8
  - [Why it is built this way](#why-it-is-built-this-way)
9
9
  - [How to use it](#how-to-use-it)
10
10
  - [Install](#install)
11
- - [Two ways to mount](#two-ways-to-mount)
12
- - [Framework wiring](#framework-wiring)
13
- - [Saving and restoring](#saving-and-restoring)
14
- - [Templates](#templates)
15
- - [Merge variables](#merge-variables)
16
- - [Image uploads](#image-uploads)
17
- - [Choosing what the top bar shows](#choosing-what-the-top-bar-shows)
18
- - [Keyboard shortcuts](#keyboard-shortcuts)
19
- - [The footer strip](#the-footer-strip)
20
- - [Language, direction and theme](#language-direction-and-theme)
21
- - [Matching your app's typography](#matching-your-apps-typography)
22
- - [Matching your brand color](#matching-your-brand-color)
11
+ - [Mount the editor](#mount-the-editor)
12
+ - [Wire it into a framework](#wire-it-into-a-framework)
13
+ - [Save and restore](#save-and-restore)
14
+ - [Load templates](#load-templates)
15
+ - [Add merge variables](#add-merge-variables)
16
+ - [Accept image uploads](#accept-image-uploads)
17
+ - [Choose what the top bar shows](#choose-what-the-top-bar-shows)
18
+ - [Use the keyboard shortcuts](#use-the-keyboard-shortcuts)
19
+ - [Configure the footer strip](#configure-the-footer-strip)
20
+ - [Set language, direction and theme](#set-language-direction-and-theme)
21
+ - [Match your app's typography](#match-your-apps-typography)
22
+ - [Match your brand color](#match-your-brand-color)
23
23
  - [API reference](#api-reference)
24
24
  - [How it works inside](#how-it-works-inside)
25
25
 
@@ -45,9 +45,9 @@ A user drags rows and blocks onto a canvas, edits text in place, styles it in an
45
45
  | Runtime dependencies | none |
46
46
  | Framework | none — a custom element works in React, Angular, Vue, Svelte and plain HTML |
47
47
  | Isolation | Shadow DOM; the editor's CSS and the host's cannot reach each other |
48
- | Size | ~639 KB minified, ~172 KB gzipped, one file |
48
+ | Size | ~683 KB minified, ~189 KB gzipped, one file |
49
49
  | Languages | 31, RTL automatic |
50
- | Content blocks | 20 — text, heading, image, button, divider, spacer, social, video, countdown, menu, list, table, box, html, css, code, svg, embed, condition, loop |
50
+ | Content blocks | 19 — text, heading, image, button, divider, spacer, social, video, countdown, menu, list, table, box, html, css, code, svg, condition, loop |
51
51
 
52
52
  ---
53
53
 
@@ -55,7 +55,7 @@ A user drags rows and blocks onto a canvas, edits text in place, styles it in an
55
55
 
56
56
  Four decisions shape the whole API. Each is a constraint the host would otherwise inherit.
57
57
 
58
- ### 1. HTML in, HTML out — no document format
58
+ ### 1. HTML in, HTML out — there is no document format
59
59
 
60
60
  The obvious design is to expose the editor's internal document as JSON and let hosts store that. It is also the one that ages worst: the moment a host persists that JSON, its shape becomes a public contract. Every new block type, every renamed prop, every changed default becomes a migration the host has to run against rows in its own database.
61
61
 
@@ -101,7 +101,7 @@ Validation runs *before* the provider is called, so a rejected file never reache
101
101
 
102
102
  An editor embedded in a product that already has a header, a breadcrumb and a Save button ends up with two bars and two logos stacked on each other. So the top bar is switchable down to the individual control, and can be removed entirely.
103
103
 
104
- Most of what the bar does survives without it. Undo, redo and export are element methods; the screenshot methods never needed the bar at all; and the keyboard shortcuts are bound at the window level, not on the bar, so `Ctrl/Cmd+Z`, `Shift+Ctrl/Cmd+Z` and `Ctrl/Cmd+E` keep working either way. Three panels are opened only from the bar, though — the live **preview** overlay, the **Code** modal and the **AI draft** panel — so dropping those parts does take them out of reach. The table under [Choosing what the top bar shows](#choosing-what-the-top-bar-shows) says which is which.
104
+ Most of what the bar does survives without it. Undo, redo and export are element methods; the screenshot methods never needed the bar at all; and the keyboard shortcuts are bound at the window level, not on the bar, so `Ctrl/Cmd+Z`, `Shift+Ctrl/Cmd+Z` and `Ctrl/Cmd+E` keep working either way. Three panels are opened only from the bar, though — the live **preview** overlay, the **Code** modal and the **AI draft** panel — so dropping those parts does take them out of reach. The table under [Choose what the top bar shows](#choose-what-the-top-bar-shows) says which is which.
105
105
 
106
106
  ---
107
107
 
@@ -133,7 +133,7 @@ Without a bundler:
133
133
  <script src="https://unpkg.com/@seliseblocks/mailcraft/dist/mailcraft-editor.bundle.js"></script>
134
134
  ```
135
135
 
136
- ## Two ways to mount
136
+ ## Mount the editor
137
137
 
138
138
  **As a tag**, when the container is part of your markup:
139
139
 
@@ -183,7 +183,7 @@ It is a wrapper, not a second implementation — it creates the same element and
183
183
 
184
184
  By default the editor is appended, so existing content in the container survives; pass `{ replace: true }` to empty it first.
185
185
 
186
- ## Framework wiring
186
+ ## Wire it into a framework
187
187
 
188
188
  The element is framework-agnostic; only the plumbing differs.
189
189
 
@@ -217,7 +217,7 @@ export function EmailEditor({ html, onSave }) {
217
217
 
218
218
  **Vue / Svelte** — both set DOM properties for non-string bindings automatically, so `<mailcraft-editor :toolbar="cfg">` works as written.
219
219
 
220
- ## Saving and restoring
220
+ ## Save and restore
221
221
 
222
222
  ```js
223
223
  // save
@@ -231,7 +231,7 @@ Editing continues where it left off. Anything the importer cannot classify into
231
231
 
232
232
  The editor also autosaves to `localStorage`, scoped **per browser tab**, so two tabs are two independent documents. That is a convenience for reload, not your persistence layer.
233
233
 
234
- ## Templates
234
+ ## Load templates
235
235
 
236
236
  Templates are host content **and** host UI. The editor ships no catalogue and has no Templates tab — you render your own picker and push the choice in:
237
237
 
@@ -241,7 +241,7 @@ editor.loadTemplate({ name: 'Welcome', html });
241
241
 
242
242
  Applying one is a normal undoable edit with a toast, and your string is never mutated. Ready-made examples ship in the package under `examples/templates/`, with a working picker in [`examples/vanilla.html`](examples/vanilla.html) — both host-app content, shipped in the package for you to lift but not part of its API.
243
243
 
244
- ## Merge variables
244
+ ## Add merge variables
245
245
 
246
246
  ```html
247
247
  <mailcraft-editor variables="first_name,company,unsubscribe_url"></mailcraft-editor>
@@ -267,7 +267,7 @@ The exporter balances the document as a whole: a stray end marker emits nothing,
267
267
 
268
268
  A start marker's expression field suggests the host's `variables` in a dropdown but stays free text, since conditions and loops routinely reference names that aren't inline tokens (`order.items`, a boolean flag, whatever your engine understands).
269
269
 
270
- ## Image uploads
270
+ ## Accept image uploads
271
271
 
272
272
  A provider and a `maxBytes` ceiling are both required. The package ships no files of its own: with no provider the library opens empty and holds only what is dropped into it — data URIs, kept in the local draft, which no email client renders. With a provider but no `maxBytes` every upload is refused.
273
273
 
@@ -322,7 +322,7 @@ Setting `editor.storageProvider = null` drops back to that empty local library.
322
322
 
323
323
  `examples/vanilla.html` wires a working provider over IndexedDB — latency, cursor paging, server-side folders and search, uploads that survive a reload, deletes that stay deleted. It is the shape of a real integration with `fetch` swapped out, and it is where every file in the live demo's Assets modal comes from.
324
324
 
325
- ## Choosing what the top bar shows
325
+ ## Choose what the top bar shows
326
326
 
327
327
  ```html
328
328
  <mailcraft-editor toolbar="none"></mailcraft-editor> <!-- no bar -->
@@ -340,7 +340,7 @@ The **attribute names what to keep**; the **property names what to drop**. Marku
340
340
 
341
341
  `none` is the documented spelling for "no bar"; `hidden`, `off` and `false` are accepted as the same thing, and `toolbar="all"` is the explicit form of the default. The property takes `false` for no bar, and an object where only the keys set to `false` do anything — unlisted parts stay on.
342
342
 
343
- ### What a hidden part costs you
343
+ ### The cost of a hidden part
344
344
 
345
345
  | part | with the bar gone |
346
346
  |---|---|
@@ -356,7 +356,7 @@ Screenshots are unaffected by any of this: `screenshotPng()`, `previewScreenshot
356
356
 
357
357
  `editor.core` reaches the rest (`core.openCode()`, `core.setState({ aiOpen: true })`, `core.setState({ previewOpen: true })`), but `EditorCore` is internal — its shape is free to change between versions. Keep `code`, `ai` or `preview` in the bar if you need them.
358
358
 
359
- ## Keyboard shortcuts
359
+ ## Use the keyboard shortcuts
360
360
 
361
361
  Bound at the window level while the editor is connected, so they work with any bar configuration, including none.
362
362
 
@@ -374,7 +374,7 @@ Typing is never hijacked: inside a form field, `Ctrl/Cmd+Z` is the browser's own
374
374
 
375
375
  One caveat if your app binds the same keys: the handler asks whether the event came from a text field, not whether it came from inside the editor. `Ctrl/Cmd+E` anywhere on the page opens the export dialog, and `Backspace` on a non-field element elsewhere in your UI deletes the editor's selected block. An editor kept on a route of its own never notices; one sitting beside your own keyboard-driven UI might, and the fix is to `stopPropagation()` on the keydowns you own before they reach `window`.
376
376
 
377
- ## The footer strip
377
+ ## Configure the footer strip
378
378
 
379
379
  The editor carries a one-line attribution along the bottom of the shell:
380
380
 
@@ -411,7 +411,7 @@ editor.messages = { 'footer.poweredBy': 'Powered by Acme' };
411
411
 
412
412
  Hiding the strip collapses its row — the canvas keeps every pixel it had.
413
413
 
414
- ## Language, direction and theme
414
+ ## Set language, direction and theme
415
415
 
416
416
  ```html
417
417
  <mailcraft-editor locale="de" theme="dark"></mailcraft-editor>
@@ -429,7 +429,7 @@ editor.messages = { 'action.export': 'Send to campaign' };
429
429
 
430
430
  Every key, with its English default, is listed in [Every message key](#every-message-key). English is always the floor: a key your table leaves out shows the built-in English rather than a gap, and `missingKeys(yourTable)` tells you what a full replacement still lacks.
431
431
 
432
- ## Matching your app's typography
432
+ ## Match your app's typography
433
433
 
434
434
  ```html
435
435
  <mailcraft-editor ui-font="inherit"></mailcraft-editor>
@@ -438,7 +438,7 @@ Every key, with its English default, is listed in [Every message key](#every-mes
438
438
 
439
439
  Editor chrome only — never the fonts inside the email being edited.
440
440
 
441
- ## Matching your brand color
441
+ ## Match your brand color
442
442
 
443
443
  ```html
444
444
  <mailcraft-editor accent="#e11d48"></mailcraft-editor>
@@ -538,7 +538,7 @@ template, not to your app.
538
538
 
539
539
  | method | returns |
540
540
  |---|---|
541
- | `exportHtml()` | send-ready email HTML |
541
+ | `exportHtml(options?)` | send-ready email HTML. `{ markers: false }` omits the `data-mc*` fidelity markers for pristine output — countdown, video, section box, code and raw-CSS blocks, flex/grid rows and “Keep columns” then reload lossily (content always survives) |
542
542
  | `importHtml(html)` | number of rows produced |
543
543
  | `loadTemplate({ name, html })` | — |
544
544
  | `undo()` / `redo()` | — |
@@ -771,14 +771,14 @@ Useful when debugging an integration, or before changing the source.
771
771
 
772
772
  **Import.** Real-world email HTML — inline and class styles, builder scaffolding, per-side borders, card columns, social strips — becomes native blocks wherever the shape is recognizable. Nested grids and `rowspan`/`colspan` survive as raw-HTML blocks: rendered and exported, not block-editable.
773
773
 
774
- **Export** reads back the rendered DOM, so what the user sees is what ships. Import and export are meant to stay round-trip compatible.
774
+ **Export** reads back the rendered DOM, so what the user sees is what ships. Import and export stay round-trip compatible: re-importing an export restores every inspector setting, and exporting again reproduces the same bytes. A few blocks render into markup that cannot be read back (a countdown bakes its digits, a video is a linked image), so the export stamps a compact `data-mc*` attribute layer that the importer trusts and mail clients ignore — `exportHtml({ markers: false })` omits it for hosts that want pristine HTML, trading a lossy (but content-preserving) reload.
775
775
 
776
776
  **Build.** `build.js` is a zero-dependency bundler that turns the ESM sources into one plain `<script>`, then minifies through esbuild with a sourcemap alongside. esbuild is a dev dependency only — consumers install nothing transitive, and a clone with no `node_modules` still produces a working bundle.
777
777
 
778
778
  ```sh
779
779
  npm install # devDependencies only: esbuild (minify), jsdom (DOM tests), c8 (coverage)
780
780
  node build.js # rebuild dist/ after any change under src/
781
- npm test # 13 suites — core logic runs DOM-free, the editor/importer suites run on jsdom
781
+ npm test # 14 suites — core logic runs DOM-free; the editor, importer and round-trip suites run on jsdom
782
782
  ```
783
783
 
784
784
  Deeper notes for coding agents and anyone changing the source live in `AGENTS.md` in the repository.
package/README.md CHANGED
@@ -59,7 +59,7 @@ Exported HTML is valid input to `loadTemplate`, so saving the export *is* saving
59
59
 
60
60
  Every attribute is also a property. TypeScript declarations ship with the package. Full reference and integration recipes: **[DOCS.md](DOCS.md)**.
61
61
 
62
- ## For AI agents
62
+ ## Paste into your AI agent
63
63
 
64
64
  Everything an assistant needs to wire this up correctly:
65
65
 
@@ -84,6 +84,9 @@ DATA HTML in, HTML out. There is no JSON document format in the public API.
84
84
  editor.importHtml(html) apply HTML (same importer)
85
85
  editor.exportHtml() -> string send-ready email HTML
86
86
  Persist by storing exportHtml() and passing it back to loadTemplate().
87
+ exportHtml({ markers: false }) omits the data-mc* fidelity markers
88
+ (pristine HTML; countdown/video/box/code/CSS blocks and flex/grid
89
+ rows then reload lossily).
87
90
  Unclassifiable markup survives as a raw-HTML block. Nothing is dropped.
88
91
  Condition/Loop blocks export literal {{#if expr}}/{{#each expr}} tags
89
92
  (always balanced) for the host's templating engine; never evaluated
package/README.md.txt CHANGED
@@ -59,7 +59,7 @@ Exported HTML is valid input to `loadTemplate`, so saving the export *is* saving
59
59
 
60
60
  Every attribute is also a property. TypeScript declarations ship with the package. Full reference and integration recipes: **[DOCS.md](DOCS.md)**.
61
61
 
62
- ## For AI agents
62
+ ## Paste into your AI agent
63
63
 
64
64
  Everything an assistant needs to wire this up correctly:
65
65
 
@@ -84,6 +84,9 @@ DATA HTML in, HTML out. There is no JSON document format in the public API.
84
84
  editor.importHtml(html) apply HTML (same importer)
85
85
  editor.exportHtml() -> string send-ready email HTML
86
86
  Persist by storing exportHtml() and passing it back to loadTemplate().
87
+ exportHtml({ markers: false }) omits the data-mc* fidelity markers
88
+ (pristine HTML; countdown/video/box/code/CSS blocks and flex/grid
89
+ rows then reload lossily).
87
90
  Unclassifiable markup survives as a raw-HTML block. Nothing is dropped.
88
91
  Condition/Loop blocks export literal {{#if expr}}/{{#each expr}} tags
89
92
  (always balanced) for the host's templating engine; never evaluated