Project

Roadmap & status

Phases A through H, strictly in order. A phase is finished only when its verification gate passes (from Phase A onward, a gate is a CI result), and the next one does not start on a red gate.

Why v6 rebuilds the core

The v5 core was sound: one rooted IR, registries, a generic compiler, and preview byte-identical to export. Its fixed four-level grammar could not express what the product needs: Figma-like layout control, inline variables, repeatable table rows. Nothing had been integrated externally, so v6 rebuilt the core properly instead of patching it, keeping every rule that worked.

Shipped under v5 (IR v1): history

These phases went green under v5 and were the base that Phases B–E rebuilt. Their code has since been replaced: IR v1 survives only as the input of migrate(), the v1 compiler is deleted, and apps/demo was retired in Phase E.

PhaseShippedGate evidence
0 · block-irRegistry-folded schema, grammar, ids and cloneSubtree, hydrate, tree ops that throw, legacy migration with a warning per loss, parity harness, no-switch scanc22f0dd, 89 tests
1 · compilerOne walker, HTML and MJML for 11 types, lint over caniemail, sanitizer, previewSrcDoc === exportHtml, snapshot corpus, MJML compiles with zero errors in Node9f2b8c8, 87 tests
2 · editor (apps/demo)Store over EmailDocument with zundo, canvasComponents, grammar-validated drag-and-drop, schema-derived inspector, iframe previewe074e68, d7c5b81
3 (partial) · variablesHandlebars, Jinja2 and Thymeleaf emitters, with repeat and when through the walker. The UI half was not built.a59254d

Phase 3 under v5 stops there. Its editor half is superseded by bindings v2 (Phase B) and the editor rebuild (Phase E). The old Phase 4 restructure is absorbed into Phase E, and the old Phases 5, 6 and 7 become G, H and D, with the MCP server moved from last to straight after the compiler.

The v6 phases

Phase A · Lock the decisions and the guardrails gate green

Docs, CI and no product code. Record decisions D1–D8, adopt the v6 plan, rewrite the locked sections of CLAUDE.md, update these pages so they describe what has shipped and mark v2 as planned, add a GitHub Actions workflow (install, Vitest, tsc, web build, web lint), and enforce module boundaries in CI: dependency-cruiser rules, public-interface snapshots, and a shared adapter source scan.

Gate: CI is green on the current code, boundary checks included · a deliberate upward import fails CI · a deliberate change to a public interface fails CI.

Phase B · IR v2 (@repo/block-ir) gate green

Registry entries for body, section, frame and the leaves, with structural limits as data. The Run schema and the leaf content schemas. Bindings v2. Safety tiers on every style key, plus styles.mobile, the dark keys and hideOn. A generated documentSchemaForAI with a parity test. repair(), the v1 → v2 migrate() chain, and the invariants rewritten.

Gate: Every invariant is green · the three legacy templates migrate to v2 with explicit warnings · a leaf under body is repaired · the AI schema passes Anthropic strict (no recursion) and OpenAI strict · the no-switch scan is green.

Phase C · Compiler v2 (@repo/email-compiler) gate green

A fluid-hybrid HTML skeleton and frame emitters, compiler-owned primitives (VML bulletproof button, MSO fixes, preheader, role="presentation"), dark mode from the dark keys, the MJML mapping with deep frames through mj-raw, the React Email emitter, runs in four dialects (Liquid added), lint v2, the annotate option, and a CI job that runs the real Jinja2, Thymeleaf and Liquid engines.

Gate: The snapshot corpus is regenerated and reviewed · MJML compiles with zero errors in Node · preview is byte-identical to export · inline variables, a line-items row repeat and when eq are correct in all four dialects · cross-engine equivalence is green · a <script> in a run never reaches the output.

Phase D · MCP server, local (heddlemail-mcp) gate green

A stdio server on the current MCP spec, adapter only. The local tools describe_blocks, check_ir, repair_ir, compile_ir and preview_ir, one issue shape, resources and a design_email prompt template. An eval harness over briefs × providers, and a heddlemail check CLI plus GitHub Action over the same functions.

Gate: npx from a packed tarball works · compile_ir is byte-identical to the package output · a leaf under body yields a grammar issue naming its path, and repair_ir fixes it · the eval baseline is recorded.

Phase E · Editor v2 (packages/editor, hosted in apps/web) gate pending

The canvas is an iframe of the annotated compile with an overlay for selection, padding and gap handles, resize and insertion lines. Inline run editing, a Figma-style inspector, the variables UI (chips, Repeat and When panels, sample data, Handlebars-executed preview), layers, undo/redo and copy/paste. E7 makes IR v2 and compiler v2 the main entry points and retires apps/demo and the canvasComponents registry.

Gate: A two-card row with a nested icon+text row is built purely by direct manipulation · an illegal drop is refused with a reason · undo works across nested moves · canvas DOM minus annotations equals export DOM · the Handlebars preview shows a 3× repeat with sample data.

Phase F · Rendering verification planned

Playwright renders the corpus in Chromium, WebKit and Firefox at 600px and 375px, light and dark, diffed against baselines, labelled as an approximation, never ground truth for Outlook. An Outlook-engine adapter, exposed through the Phase D GitHub Action.

Gate: No unexpected diffs across the corpus · one Outlook-engine report per migrated template · the VML button renders in the Outlook report.

Phase G · Server planned

Templates CRUD, workspaces and sessions, the server-side MJML endpoint, and saved or synced modules. MCP remote tools: session_get, session_put, and send_test to a sandbox inbox. Sending is out of scope: templates are handed to the customer's own ESP or backend.

Gate: Templates round-trip through the API against documentSchema · an agent edits a session that then reopens in the editor.

Phase H · Importers, more dialects, then deferred AI planned

HTML and MJML import with a warning on every loss. Figma import, mapping auto-layout frames one to one and rejecting unmappable features with reasons. Django, Blade, Razor and Go dialects. Hosted AI generation only if positioning expands beyond developers.

Gate: Defined when the phase is opened.

Decisions that set the order

#DecisionResolution
D1Layout modelBounded frames: body → section → frame (≤3) → leaf, at most 4 children per row
D2Editor canvasCompiled HTML in an iframe, plus an overlay; canvasComponents is retired
D3SendingNot operated by us. Templates go to the customer's ESP or backend; send_test uses a sandbox only
D4Raw HTML blockNot in v1
D5Style tiersborderRadius progressive; letterSpacing and textTransform safe
D6MJML fidelityAlways valid MJML; deep frames go through mj-raw; HTML is the reference target
D7v1 dialectsHandlebars, Jinja2, Liquid, Thymeleaf
D8PositioningDeveloper-first and MCP-led

Current state of the repo

The code is IR v2. @repo/block-ir is the v2 contract and @repo/email-compiler the v2 compiler, each through its main entry point; v1 and legacy documents are upgraded by migrate(). The editor is @repo/editor, hosted at /editor in apps/web.

Phase A is done: CI and the boundary checks run on every push. Phase B is done: grammar, runs, bindings, safety tiers, the generated AI schema, repair(), migrate() and the nine invariants. Agent output goes through validation and repair by default rather than depending on any provider’s strict-decoding limits (decision D9). Phase C is done: fluid-hybrid HTML, MJML and React Email, runs in Handlebars, Jinja2, Liquid and Thymeleaf, lint v2 and the editor’s annotations, and CI runs the real Jinja2, Liquid and Thymeleaf engines against every dialect. Phase D is done: the MCP server with its five local tools, the heddlemail CLI and the GitHub Action are built and tested in CI, including an install from the packed tarball. The eval harness is built, but its baseline was deferred (decision D11). There are no live agent numbers yet, and they are required before the npm release.

Phase E is code-complete; its gate is pending in CI. E1 ships the canvas: an iframe of the annotated compile, recompiled in place on every edit, with click-to-select. E2 ships the overlay: padding, gap and resize handles, palette and move drags with insertion lines, refused drops shown with the IR’s reason, and repeat ghosts. E3 ships inline text editing that commits into runs. E4 ships the inspector, generated from each type’s schemas, with client badges by safety tier and mobile and dark tabs. E5 ships the variables UI: chips, Repeat and When panels, sample data derived from the document, and a Handlebars preview that renders repeats as real copies. E6 ships layers, undo/redo, shortcuts and copy/paste. E7 makes v2 the main entry points of both packages, deletes the v1 compiler, and retires apps/demo with its canvasComponents registry.

bash
pnpm install                       # workspace install
pnpm test                          # every suite
pnpm -F @repo/block-ir test        # invariants, schema, migration
pnpm -F @repo/email-compiler test  # walker, emitters, lint, snapshots
pnpm boundaries                    # dependency-cruiser layer check
pnpm -F heddlemail-mcp test        # MCP tools, CLI, packed-tarball install
pnpm -F heddlemail-mcp eval        # live eval (needs Claude API credentials)
pnpm -F web dev                    # this site, the docs, and the editor at /editor