DX follow-ups: renderToJson wrapper parity + Menu text inputs#31
Merged
Conversation
…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>
There was a problem hiding this comment.
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
renderToJsonto reach<Body>/<Email>/<Page>/<Document>. - Added tests covering the wrapper success case and a wrapper that still resolves to an invalid root.
- Relaxed
Menu’sfontFamily/fontWeight/fontSize/letterSpacingprop 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.
…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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-ups from testing fresh Opus agents generating designs on 0.1.11. None blocked them, but these were the recurring sharp edges.
1.
renderToJsonaccepts a wrapper component (parity withrenderToHtml)renderToHtml(<MyEmail/>)renders a custom wrapper through React, butrenderToJson(<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. NowrenderToJsonunwraps 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.
Menutext inputs relaxed to match the other text componentsMenu'sfontFamily/fontWeight/fontSize/letterSpacingkept the canonical strict types, so a stringfontFamilyor a number/emsize that compiles on<Heading>failed on<Menu>. Relaxed to the shared agent-friendly inputs (Menu has nocolor/lineHeightfield, 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-sizepx,letter-spacingem). Guarded in thetypecheckcontract.Verification
build✅ ·typecheck✅ · 331 tests ✅ (+2). Verified against a packed build:renderToJson(<MyEmail/>)returns valid JSON, and Menu'sarial/14px/0.08emall reach the output with no[object Object]/undefined.Backward-compatible, type-/parity-only — fine as a patch.
📖 Storybook Preview: https://unlayer.github.io/elements/pr/31/