Skip to content

DX follow-ups: renderToJson wrapper parity + Menu text inputs#31

Merged
ivoIturrieta merged 5 commits into
mainfrom
fix/dx-followup-renderjson-menu
Jun 28, 2026
Merged

DX follow-ups: renderToJson wrapper parity + Menu text inputs#31
ivoIturrieta merged 5 commits into
mainfrom
fix/dx-followup-renderjson-menu

Conversation

@ivoIturrieta

@ivoIturrieta ivoIturrieta commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Follow-ups from testing fresh Opus agents generating designs on 0.1.11. None blocked them, but these were the recurring sharp edges.

1. renderToJson accepts a wrapper component (parity with renderToHtml)

renderToHtml(<MyEmail/>) renders a custom wrapper through React, but renderToJson(<MyEmail/>) walked the element tree and rejected any root that wasn't <Body>/<Email>/<Page>/<Document> — so the two renderers disagreed. 3 of 4 agents hit this. Now renderToJson unwraps a plain function-component root to its returned element (bounded loop; class/forwardRef/memo still get the clear root-type error). Tests added for the wrapper case and the still-invalid case.

2. Menu text inputs relaxed to match the other text components

Menu's fontFamily/fontWeight/fontSize/letterSpacing kept the canonical strict types, so a string fontFamily or a number/em size that compiles on <Heading> failed on <Menu>. Relaxed to the shared agent-friendly inputs (Menu has no color/lineHeight field, so only these four). Type-only — values normalize at render time the same way as the other text components, and the result renders faithfully (font-family, font-size px, letter-spacing em). Guarded in the typecheck contract.

Verification

build ✅ · typecheck ✅ · 331 tests ✅ (+2). Verified against a packed build: renderToJson(<MyEmail/>) returns valid JSON, and Menu's arial/14px/0.08em all reach the output with no [object Object]/undefined.

Backward-compatible, type-/parity-only — fine as a patch.

Not included (separate, needs investigation): image-in-narrow-column sizing — the column renderer doesn't thread column width to the image exporter, so a documented maxWidth:"100%" thumbnail resolves to the full content-width cap. Tracking separately.


📖 Storybook Preview: https://unlayer.github.io/elements/pr/31/

ivoIturrieta and others added 2 commits June 27, 2026 19:00
…oHtml

renderToHtml renders a custom wrapper component through React, but renderToJson
walked the element tree and rejected anything whose root wasn't <Body>/<Email>/
<Page>/<Document> — so renderToJson(<MyEmail/>) threw while renderToHtml(<MyEmail/>)
worked. Unwrap a plain function-component root to its returned element (bounded
loop; class/forwardRef/memo still hit the clear root-type error). Adds tests for
the wrapper case and the still-invalid case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Menu's fontFamily/fontWeight/fontSize/letterSpacing kept the canonical strict
types, so a string fontFamily or a number/em size that compiles on Heading
failed on Menu. Relax them to the shared agent-friendly inputs (Menu has no
color/lineHeight field, so only these four). Type-only — values are normalized
at render time the same way as the other text components. Guarded in the tsc
contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 27, 2026 17:00

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses two recurring developer-experience friction points: (1) aligning renderToJson behavior with renderToHtml by supporting a wrapper root component, and (2) relaxing Menu text-style prop input types to match other text components.

Changes:

  • Added bounded unwrapping for plain function-component wrapper roots in renderToJson to reach <Body>/<Email>/<Page>/<Document>.
  • Added tests covering the wrapper success case and a wrapper that still resolves to an invalid root.
  • Relaxed Menu’s fontFamily/fontWeight/fontSize/letterSpacing prop types to use the shared agent-friendly text input types, with a typecheck contract guard.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
packages/react/src/utils/render-to-json.ts Adds wrapper-root unwrapping and centralizes valid root checks for renderToJson.
packages/react/src/utils/render-to-json.test.tsx Adds regression tests for wrapper-root parity and invalid-wrapper behavior.
packages/react/src/dx-types.test-d.tsx Extends DX type contract to assert relaxed Menu text input types compile.
packages/react/src/components/Menu.tsx Updates Menu semantic prop typing to accept shared text-style inputs (excluding unsupported fields).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/react/src/utils/render-to-json.ts
ivoIturrieta and others added 2 commits June 27, 2026 19:25
…cern

Public-repo hygiene — the comment now describes only this file's local head
type, not anything about how the rendering dependency is structured.
Invoking a wrapper component that uses React hooks throws a bare "Invalid hook
call" that masked the intended guidance. Catch the invocation and rethrow an
actionable error: a wrapper must synchronously return a root (Email/Page/
Document/Body) and use no hooks — pass the root element or call the component.
Adds a test for the throwing-wrapper path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comment thread packages/react/src/utils/render-to-json.ts
The error fires only when invoking the wrapper itself threw, so calling it
manually (renderToJson(MyEmail())) would fail identically — suggesting it was
misleading. Point only to passing the root element directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ivoIturrieta ivoIturrieta merged commit bc868dd into main Jun 28, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants