Skip to content

feat: support full preview in local-editor#1224

Merged
benlife5 merged 3 commits into
2026-custom-components-templatesfrom
local-editor-preview
Jun 2, 2026
Merged

feat: support full preview in local-editor#1224
benlife5 merged 3 commits into
2026-custom-components-templatesfrom
local-editor-preview

Conversation

@benlife5
Copy link
Copy Markdown
Contributor

@benlife5 benlife5 commented Jun 1, 2026

Opens a new tab with the contents of the editor run through <Render> to mimic the live page for better QA and the potential to use an accessibility testing browser extension

Screen.Recording.2026-06-01.at.3.19.36.PM.mov

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 495593b7-ecd6-4edd-84b7-7ed2102ff760

📥 Commits

Reviewing files that changed from the base of the PR and between 822f5c0 and f64dec3.

📒 Files selected for processing (4)
  • packages/visual-editor/src/index.ts
  • packages/visual-editor/src/local-editor/LocalEditorShell.tsx
  • packages/visual-editor/src/local-editor/selection.ts
  • packages/visual-editor/src/vite-plugin/templates/edit.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/visual-editor/src/local-editor/LocalEditorShell.tsx
  • packages/visual-editor/src/local-editor/selection.ts
  • packages/visual-editor/src/vite-plugin/templates/edit.tsx

Walkthrough

This PR adds client-side preview functionality to the local editor. It introduces utilities in selection.ts for generating preview URLs and reconstructing layout state from compressed localStorage history, adds an "Open Preview" button to the editor shell that opens previews in a new tab, and implements a Preview component in the page template that fetches, migrates, and renders preview data.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant LocalEditorShell
  participant buildLocalEditorPreviewUrl
  participant Browser
  participant PreviewComponent
  User->>LocalEditorShell: Click "Open Preview"
  LocalEditorShell->>buildLocalEditorPreviewUrl: Build URL (origin, templateId, entityId, locale)
  buildLocalEditorPreviewUrl-->>LocalEditorShell: Preview URL
  LocalEditorShell->>Browser: window.open(preview URL, noopener,noreferrer)
  Browser->>PreviewComponent: Request /edit/<templateId>?preview=1&previewStorageKey=...
  PreviewComponent->>PreviewComponent: readLocalEditorPreviewLayoutData -> migrate -> resolveAllData
  PreviewComponent-->>Browser: Render preview
Loading

Possibly related PRs

  • yext/visual-editor#1143: Extends the same local-editor foundation; related changes to LocalEditorShell and selection.ts for preview behavior.
  • yext/visual-editor#1115: Related edits to edit template routing and injected editPath/editTemplateName used by preview URLs.

Suggested labels

create-dev-release

Suggested reviewers

  • mkilpatrick
  • jwartofsky-yext
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: support full preview in local-editor' directly describes the main change: adding preview functionality to the local editor.
Description check ✅ Passed The description explains the purpose of the new preview feature—opening a tab with rendered editor contents for QA and accessibility testing—which aligns with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch local-editor-preview

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/visual-editor/src/vite-plugin/templates/edit.tsx (1)

196-233: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Rules of Hooks violation: conditional hook calls after early returns will crash Edit.

usePlatformBridgeDocument() and usePlatformBridgeEntityFields() are called after the if (!isClientReady) return null; and if (isPreviewMode) return <Preview />; early returns. The number of hooks invoked therefore changes across renders (first render: only useState/useEffect; later renders: also the bridge hooks; preview mode: never), which React rejects with "Rendered more hooks than during the previous render." Additionally, Edit is typed () => JSX.Element but return null is not assignable to JSX.Element.

Extract the non-preview branch into its own component so each component calls a stable set of hooks, and widen the return type.

🐛 Proposed fix
-const Edit: () => JSX.Element = () => {
+const EditorView = () => {
+  const entityDocument = usePlatformBridgeDocument();
+  const entityFields = usePlatformBridgeEntityFields();
+
+  return (
+    <VisualEditorProvider
+      templateProps={{
+        document: entityDocument,
+      }}
+      entityFields={entityFields}
+      tailwindConfig={tailwindConfig}
+    >
+      <Editor
+        document={entityDocument}
+        componentRegistry={componentRegistry}
+        themeConfig={defaultThemeConfig}
+      />
+    </VisualEditorProvider>
+  );
+};
+
+const Edit: () => JSX.Element | null = () => {
   // Wait for URLSearchParams to be available
   const [isClientReady, setIsClientReady] = React.useState(false);
 
   React.useEffect(() => {
     setIsClientReady(true);
   }, []);
 
   if (!isClientReady) {
     return null;
   }
 
   const searchParams = new URLSearchParams(window.location.search);
   const isPreviewMode = searchParams.get(previewModeParam) === "1";
 
   if (isPreviewMode) {
     return <Preview />;
   }
 
-  const entityDocument = usePlatformBridgeDocument();
-  const entityFields = usePlatformBridgeEntityFields();
-
-  return (
-    <VisualEditorProvider
-      templateProps={{
-        document: entityDocument,
-      }}
-      entityFields={entityFields}
-      tailwindConfig={tailwindConfig}
-    >
-      <Editor
-        document={entityDocument}
-        componentRegistry={componentRegistry}
-        themeConfig={defaultThemeConfig}
-      />
-    </VisualEditorProvider>
-  );
+  return <EditorView />;
 };
Based on learnings: "Do not use React hooks inside arbitrary callback functions. Hooks must be called only at the top level of React function components or custom hooks."
🤖 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/visual-editor/src/vite-plugin/templates/edit.tsx` around lines 196 -
233, The Edit component violates Rules of Hooks by calling
usePlatformBridgeDocument() and usePlatformBridgeEntityFields() conditionally;
extract the non-preview rendering into a new component (e.g., EditInner or
VisualEditorInner) that contains the hooks and returns the VisualEditorProvider
+ Editor tree, and have Edit only handle client-ready and preview branching to
return either <Preview /> or <EditInner />; also widen Edit's return type to
allow null (e.g., JSX.Element | null) so the initial client-ready null return is
valid. Ensure the new component owns the hooks (usePlatformBridgeDocument,
usePlatformBridgeEntityFields) and the original names VisualEditorProvider and
Editor are used unchanged when rendering.
🧹 Nitpick comments (1)
packages/visual-editor/src/vite-plugin/templates/edit.tsx (1)

237-320: ⚖️ Poor tradeoff

Avoid duplicating preview layout helpers in the autogenerated template

packages/visual-editor/src/vite-plugin/templates/edit.tsx inlines buildLocalEditorDocumentRequestPath / readLocalEditorPreviewLayoutData / readDocumentLayoutData, duplicating logic from packages/visual-editor/src/local-editor/selection.ts. However, these aren’t byte-for-byte copies (e.g., readDocumentLayoutData uses createEmptyLayoutData() in selection.ts, while the template returns the object inline), and only the first two helpers are exported from selection.tsreadDocumentLayoutData is private.

Because src/index.ts doesn’t re-export these helpers, the template can’t simply import them from @yext/visual-editor; consider re-exporting the needed helpers (or refactoring template generation to share the implementation) so the storage-key/decompression contract can’t drift.

🤖 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/visual-editor/src/vite-plugin/templates/edit.tsx` around lines 237 -
320, The template duplicates and drifts from the shared helpers:
buildLocalEditorDocumentRequestPath, readLocalEditorPreviewLayoutData and
readDocumentLayoutData are re-implemented in edit.tsx instead of reusing the
versions in local-editor/selection.ts (which uses createEmptyLayoutData and has
the authoritative storage-key/decompression contract); fix by refactoring so the
template imports the canonical implementations from `@yext/visual-editor` (or
change template generation to reference the shared module), add the missing
export(s) in src/index.ts for any helpers needed by the template (export
readLocalEditorPreviewLayoutData and/or readDocumentLayoutData or a single
wrapper that returns createEmptyLayoutData-consistent defaults), and ensure the
template uses those imports so behavior (storage key, decompression, and
empty-layout defaults) stays identical to selection.ts.
🤖 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/visual-editor/src/local-editor/selection.ts`:
- Line 165: The preview URL currently hardcodes `/edit/${templateId}` which
breaks when the router registers only `edit` (no param) when "main" is present;
update the previewUrl construction in selection.ts (the const previewUrl
variable referencing templateId) to use the routed editor path `/edit` and pass
templateId via a query parameter (e.g.,
previewUrl.searchParams.set('templateId', templateId)) or another non-path
mechanism consistent with editorRoute.ts returning `"edit"` when "main" exists,
so the URL matches the registered route instead of 404ing.

---

Outside diff comments:
In `@packages/visual-editor/src/vite-plugin/templates/edit.tsx`:
- Around line 196-233: The Edit component violates Rules of Hooks by calling
usePlatformBridgeDocument() and usePlatformBridgeEntityFields() conditionally;
extract the non-preview rendering into a new component (e.g., EditInner or
VisualEditorInner) that contains the hooks and returns the VisualEditorProvider
+ Editor tree, and have Edit only handle client-ready and preview branching to
return either <Preview /> or <EditInner />; also widen Edit's return type to
allow null (e.g., JSX.Element | null) so the initial client-ready null return is
valid. Ensure the new component owns the hooks (usePlatformBridgeDocument,
usePlatformBridgeEntityFields) and the original names VisualEditorProvider and
Editor are used unchanged when rendering.

---

Nitpick comments:
In `@packages/visual-editor/src/vite-plugin/templates/edit.tsx`:
- Around line 237-320: The template duplicates and drifts from the shared
helpers: buildLocalEditorDocumentRequestPath, readLocalEditorPreviewLayoutData
and readDocumentLayoutData are re-implemented in edit.tsx instead of reusing the
versions in local-editor/selection.ts (which uses createEmptyLayoutData and has
the authoritative storage-key/decompression contract); fix by refactoring so the
template imports the canonical implementations from `@yext/visual-editor` (or
change template generation to reference the shared module), add the missing
export(s) in src/index.ts for any helpers needed by the template (export
readLocalEditorPreviewLayoutData and/or readDocumentLayoutData or a single
wrapper that returns createEmptyLayoutData-consistent defaults), and ensure the
template uses those imports so behavior (storage key, decompression, and
empty-layout defaults) stays identical to selection.ts.
🪄 Autofix (Beta)

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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bbef837b-f599-4c36-bfb2-aeabcede91d8

📥 Commits

Reviewing files that changed from the base of the PR and between 21e4a8f and 822f5c0.

📒 Files selected for processing (3)
  • packages/visual-editor/src/local-editor/LocalEditorShell.tsx
  • packages/visual-editor/src/local-editor/selection.ts
  • packages/visual-editor/src/vite-plugin/templates/edit.tsx

Comment thread packages/visual-editor/src/local-editor/selection.ts
Comment thread packages/visual-editor/src/local-editor/LocalEditorShell.tsx Outdated
Comment thread packages/visual-editor/src/local-editor/selection.ts Outdated
Comment thread packages/visual-editor/src/vite-plugin/templates/edit.tsx Outdated
Comment thread packages/visual-editor/src/vite-plugin/templates/edit.tsx Outdated
Comment thread packages/visual-editor/src/vite-plugin/templates/edit.tsx Outdated
benlife5 and others added 2 commits June 1, 2026 16:49
Copy link
Copy Markdown
Collaborator

@mkilpatrick mkilpatrick left a comment

Choose a reason for hiding this comment

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

Nice

Copy link
Copy Markdown
Contributor

@briantstephan briantstephan left a comment

Choose a reason for hiding this comment

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

🔥

@benlife5 benlife5 requested a review from asanehisa June 2, 2026 13:21
@benlife5 benlife5 merged commit 1ad5f0e into 2026-custom-components-templates Jun 2, 2026
17 checks passed
@benlife5 benlife5 deleted the local-editor-preview branch June 2, 2026 13:40
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.

4 participants