feat(ui): add props editor to the preview toolbar#3613
Conversation
🦋 Changeset detectedLatest commit: 1cbfe65 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@melv-n is attempting to deploy a commit to the resend Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
2 issues found across 8 files
Confidence score: 3/5
- In
packages/ui/src/components/toolbar.tsx,activeTabis taken from the URL without checking whether that tab is actually available whenisRawHtmlEmail/isBuildinghide the Props trigger, so the UI can land on an invalid tab state and show missing/incorrect content to users — validate or coerceactiveTabto a visible tab before renderingTabs.Contentand add a quick navigation test for hidden-tab scenarios. - In
packages/ui/src/hooks/use-email-rendering-result.ts, theisFirstOverrideRunguard not resetting on the early unmount path can trigger an extra server action during React Strict Mode double-mount, which adds noise and avoidable backend work in development — reset the guard consistently in cleanup/early-return paths before merging.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/ui/src/components/toolbar.tsx">
<violation number="1" location="packages/ui/src/components/toolbar.tsx:328">
P2: The Props tab trigger is conditionally hidden when `isRawHtmlEmail` or `isBuilding` is true, but `activeTab` comes directly from the URL query param without validating against available tabs, and `Tabs.Content value="props">` unconditionally renders `<PreviewPropsEditor />`. When a user navigates to `?toolbar-panel=props` in a raw HTML preview or static build, the editor renders and mutating the JSON triggers `useEmailRenderingResult` to call `renderEmailByPath` — a server action that is unavailable in static builds, causing a runtime failure. The content should be guarded the same way as the trigger, or `activeTab` should be validated against the supported tabs for the current mode.</violation>
</file>
<file name="packages/ui/src/hooks/use-email-rendering-result.ts">
<violation number="1" location="packages/ui/src/hooks/use-email-rendering-result.ts:26">
P3: The `isFirstOverrideRun` guard in the effect doesn't reset on unmount in its early-return path, which causes an unnecessary server action call in React Strict Mode's double-mount behavior. When navigating to a new email or during HMR in development, the ref persists across mounts, so the component re-enters the effect with `isFirstOverrideRun` already `false` and issues a redundant `renderEmailByPath(emailPath, false, undefined)` call that returns the same data already in initial state.
Returning a cleanup that resets the ref from the early-return path would prevent this.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
c22ed98 to
1cbfe65
Compare
Thanks for the heads up @felipefreitag! Rebased onto canary and reconciled with #3633. The interesting overlap was the render cache. My branch made it props-aware (so editing props in the toolbar renders without clobbering the default), and #3633 changed hot reload to re-render only the open preview and just drop the cache for the other affected templates. I wired the two together:
So the OOM fix stays intact and the props editor keeps working across saves. I extended the tests to cover it, and kept your new |
Adds the preview props editing that was originally floated in #371 (that issue was closed once
PreviewPropsdefaults landed, so this PR doesn't close anything; it builds the UI-editor half that was discussed there): a Props tab in the preview toolbar (next to Linter/Compatibility/Spam) that lets you live-edit the props a template is rendered with, to try out long values, different alphabets, edge cases, and so on, without touching thePreviewPropsin the template file.What it does
PreviewPropsby default).renderEmailByPathserver action (debounced 400ms). Invalid JSON shows the parse error inline and leaves the preview untouched.PreviewProps..htmltemplates (no props) and in static builds (isBuilding), where the server action does not exist.Implementation notes
renderEmailByPathgains an optionalpreviewPropsOverrideparameter used instead ofEmail.PreviewProps, and the render cache now keys on path + override so default and overridden renders don't clobber each other (invalidatingCacheclears every entry for the path). The returned metadata includespreviewProps, the JSON-safe props the render used, which is what seeds the editor.PreviewProviderand flows throughuseEmailRenderingResult, which re-renders on override changes and passes the current override to the hot-reload re-render (via a ref, so the socket listener isn't re-registered).previewPropsfalls back to{}for those templates and everything else keeps working.How to test
pnpm --filter=react-email deva project (or the demo app), open a template previewUnit coverage added in
render-email-by-path.spec.tsfor the override parameter, the exposedpreviewProps, and cache isolation between default and overridden renders. Full@react-email/uisuite passes (99 tests).Screenshot
Related to #371.