Support field transforms within render - #1793
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughField transforms now apply during ChangesRender field transform support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Preview
participant InteractivePage
participant Render
participant SlotRender
participant DropZoneRender
Preview->>InteractivePage: render non-edit preview
InteractivePage->>Render: provide fieldTransforms
Render->>SlotRender: forward fieldTransforms
Render->>DropZoneRender: provide transform context
DropZoneRender->>SlotRender: forward fieldTransforms
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
packages/core/components/ServerRender/__tests__/field-transforms.spec.tsx (1)
266-292: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for the
ServerRenderpath with no root render.
Renderinpackages/core/components/ServerRender/index.tsxreturns from two branches: one whenconfig.root?.renderexists (line 195) and one fallback (line 209). Both branches passfieldTransformstoDropZoneRender, but only the first branch is exercised here. The client spec covers this case explicitly atpackages/core/components/Render/__tests__/field-transforms.spec.tsxline 105.Add an equivalent test with a config that omits
root.render.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/components/ServerRender/__tests__/field-transforms.spec.tsx` around lines 266 - 292, Add a ServerRender field-transform test covering the fallback branch of Render when config.root.render is omitted. Reuse the existing slot field configuration and custom slot transform, render with the same data, and assert the transformed slot output appears while the built-in rendered content does not, ensuring fieldTransforms reaches DropZoneRender in the no-root-render path.packages/core/components/Render/__tests__/field-transforms.spec.tsx (1)
1-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the
ResizeObserverstub to Jest setupThis test uses ESM, so static imports run before line 9. The reachable
ResizeObserverusage is insideuseEffect, but this placement does not protect module-evaluation code. Use JestsetupFilesif the stub must exist during imports.beforeAllis sufficient only for render-time usage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/components/Render/__tests__/field-transforms.spec.tsx` around lines 1 - 11, Move the global ResizeObserver stub from field-transforms.spec.tsx into the Jest setupFiles configuration so it is initialized before ESM imports and module evaluation; remove the local class and global assignment while preserving the existing test behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/docs/pages/docs/api-reference/components/render.mdx`:
- Around line 89-90: Update the global text transform in the render example to
wrap values with a phrasing element such as span instead of div, preserving
valid content when used inside HeadingBlock’s h1.
In `@apps/docs/pages/docs/extending-puck/field-transforms.mdx`:
- Line 28: Update the Example component’s Render invocation to provide the
required config and data props defined by Render, or explicitly mark
intentionally omitted props with a // ... placeholder so copied TypeScript
compiles without missing-property errors.
In `@packages/core/components/Render/__tests__/field-transforms.spec.tsx`:
- Around line 310-312: Strengthen the negative assertion in the array transform
test by checking that the escaped raw rich-text string is absent, using the same
unescaped sentinel approach as the object test’s "hidden" assertion. Keep the
existing rows:2 positive assertion unchanged.
---
Nitpick comments:
In `@packages/core/components/Render/__tests__/field-transforms.spec.tsx`:
- Around line 1-11: Move the global ResizeObserver stub from
field-transforms.spec.tsx into the Jest setupFiles configuration so it is
initialized before ESM imports and module evaluation; remove the local class and
global assignment while preserving the existing test behavior.
In `@packages/core/components/ServerRender/__tests__/field-transforms.spec.tsx`:
- Around line 266-292: Add a ServerRender field-transform test covering the
fallback branch of Render when config.root.render is omitted. Reuse the existing
slot field configuration and custom slot transform, render with the same data,
and assert the transformed slot output appears while the built-in rendered
content does not, ensuring fieldTransforms reaches DropZoneRender in the
no-root-render path.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 28fe61cc-0abe-4454-a325-a5da256be995
📒 Files selected for processing (15)
apps/docs/pages/docs/api-reference/components/render.mdxapps/docs/pages/docs/api-reference/field-transforms.mdxapps/docs/pages/docs/extending-puck/field-transforms.mdxpackages/core/components/DropZone/index.tsxpackages/core/components/Puck/__tests__/field-transforms.spec.tsxpackages/core/components/Puck/components/Preview/components/editor-page.tsxpackages/core/components/Puck/components/Preview/components/interactive-page.tsxpackages/core/components/Puck/components/Preview/index.tsxpackages/core/components/Render/__tests__/field-transforms.spec.tsxpackages/core/components/Render/index.tsxpackages/core/components/RichTextEditor/lib/use-richtext-props.tsxpackages/core/components/ServerRender/__tests__/field-transforms.spec.tsxpackages/core/components/ServerRender/index.tsxpackages/core/components/SlotRender/server.tsxpackages/core/lib/use-slots.tsx
There was a problem hiding this comment.
Pull request overview
Adds fieldTransforms support to the Render component (client) and the rsc/ServerRender entrypoint, so callers can transform stored field values at render time (including returning React elements) without mutating persisted page data. The change threads transforms through the existing slot-resolution walk and updates the richtext post-pass to avoid corrupting props when user transforms replace values.
Changes:
- Add optional
fieldTransformsprop toRenderandServerRender, threading it through slot rendering (useSlots,SlotRender,DropZone). - Update
useSlotsto merge built-in slot transforms with user transforms and force read-only behavior for render-time resolution. - Make
useRichtextPropsaware of transforms (skip types with user transforms) and add/adjust tests + documentation; editor preview paths updated so edit/interactive modes apply transforms consistently.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/lib/use-slots.tsx | Updates slot/field transform application for render paths and forces read-only transform context. |
| packages/core/components/SlotRender/server.tsx | Threads fieldTransforms through server slot rendering and richtext prop resolution. |
| packages/core/components/ServerRender/index.tsx | Adds fieldTransforms support to the rsc/ServerRender render and dropzone paths. |
| packages/core/components/ServerRender/tests/field-transforms.spec.tsx | Adds server render coverage for transform behavior across root/slots/zones and richtext interactions. |
| packages/core/components/RichTextEditor/lib/use-richtext-props.tsx | Skips richtext traversal for field types handled by user transforms to prevent corrupt props. |
| packages/core/components/Render/index.tsx | Adds fieldTransforms prop, stores it in context, and applies transforms to root/component props and slots. |
| packages/core/components/Render/tests/field-transforms.spec.tsx | Adds client Render coverage for field transforms, root-with-richtext regression, and nested replacement cases. |
| packages/core/components/Puck/components/Preview/index.tsx | Splits preview into edit vs interactive pages (interactive uses <Render> with transforms). |
| packages/core/components/Puck/components/Preview/components/interactive-page.tsx | New interactive preview path that passes fieldTransforms through <Render>. |
| packages/core/components/Puck/components/Preview/components/editor-page.tsx | Applies user transforms on the edit canvas root (consistent output vs interactive preview). |
| packages/core/components/Puck/tests/field-transforms.spec.tsx | Adds regression coverage ensuring edit-mode canvas applies root transforms. |
| packages/core/components/DropZone/index.tsx | Threads fieldTransforms from render context through dropzone item render + richtext props. |
| apps/docs/pages/docs/extending-puck/field-transforms.mdx | Updates docs to reflect transforms can be applied at render time via <Render>. |
| apps/docs/pages/docs/api-reference/field-transforms.mdx | Updates API reference to note support in both <Puck> and <Render> and isReadOnly behavior. |
| apps/docs/pages/docs/api-reference/components/render.mdx | Documents the new fieldTransforms prop on <Render>. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
0b72363 to
298514d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/lib/data/resolve-and-replace-data.ts`:
- Around line 35-42: Update the replaceRoot reducer handling in the replace-root
action to merge action.ui into state.ui before invoking walkAppState, matching
the existing replace behavior while preserving unchanged UI state when no
updates are provided. Add a regression test covering root resolution through the
replaceRoot path and asserting that the requested UI changes are retained.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ca91123a-3975-48e5-8de7-e883277701ae
📒 Files selected for processing (4)
packages/core/components/Puck/components/Preview/components/edit-page.tsxpackages/core/components/Puck/components/Preview/index.tsxpackages/core/lib/data/resolve-and-replace-data.tspackages/core/lib/get-selector-for-id.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/core/components/Puck/components/Preview/index.tsx
298514d to
1115986
Compare
chrisvxd
left a comment
There was a problem hiding this comment.
excellent work, only a few comments on language and phrasing
|
|
||
| ## Transforming at render time | ||
|
|
||
| Transforms can also be provided to [`<Render>`](/docs/api-reference/components/render) to apply them to a finished page. |
There was a problem hiding this comment.
to apply them to a finished page.
I find this phrasing a bit awkward
There was a problem hiding this comment.
Changed it to an active voice!
| @@ -296,7 +296,10 @@ describe("Render fieldTransforms", () => { | |||
| type: "List", | |||
| props: { | |||
There was a problem hiding this comment.
what is "scaped/unscaped HTML" in this commit message? Do you mean escaped?
| @@ -1,4 +1,4 @@ | |||
| import { memo, useMemo } from "react"; | |||
| import { useMemo } from "react"; | |||
There was a problem hiding this comment.
I think "fix: apply inline and plugin field transforms to root fields" should be simply "fix: apply field transforms to root fields"
e02a4c3 to
d56cfd2
Compare
|
Addressed your comments @chrisvxd! |
Co-authored-by: Chris Villa <chrisvxd@users.noreply.github.com>
… guide Co-authored-by: Chris Villa <chrisvxd@users.noreply.github.com>
d56cfd2 to
b7e6b3f
Compare
Closes #1780, Closes #1794
Description
This PR adds support for the
fieldTransformsAPI to theRendercomponent. Field transforms were previously only available insidePuck; now the same API can be applied at render time, after a page has been created.This makes it possible to transform stored field values into components or computed values on every render, without polluting the saved data. Unlike
resolveData, transforms can return React components (not just JSON) and re-run whenever the transforms or data change.Transforms run through the existing
useSlotswalk (the same one that resolves slots), so props are transformed in a single pass at every depth: root props, component props, and slot children. Both render paths are supported: the clientRender(which shares state through context) and the RSCRenderfrom therscbundle (which prop-drills, so nothing pulls the page to the client).Inside the editor, the interactive preview and the edit canvas run the same transforms, so the preview stays consistent with production output.
Changes made
Renderand the RSCRender(ServerRender) accept an optionalfieldTransformsprop and thread it throughuseSlots,SlotRender, and the dropzones, so it applies to root props, component props, and slot children at every depth.useSlotsnow merges the built-in slot transform with the caller's transforms ({ ...slotTransform, ...fieldTransforms }), so user transforms compose with slots and can override them.isReadOnly, since nothing is editable underRender.renderpath for transforming slots.useRichtextProps) is now transform-aware: it skips any field whose type has a user transform. Without this, the richtext pass would descend into a value a transform had already replaced, produce corrupt props, and crash React.Renderwith transforms) and an edit path.How to test
texttransform that converts every text value into a component with red text, and confirm it renders as that component instead of a raw string:Renderfrom therscbundle and render it inside a Next.js Server Component with the samefieldTransforms; confirm the transforms run on the server and nothing is pulled to the client.fieldTransformstoPuckand toggle between edit and interactive preview; confirm root and component props render identically in both modes.Summary by CodeRabbit
New Features
fieldTransformssupport toRender, including nested content, slots, drop zones, and rich-text fields.Bug Fixes
Documentation
Renderread-only behavior.