Skip to content

fix(dashboard): collapsed email editor height; sandbox email-preview iframes#1406

Merged
BilalG1 merged 2 commits intodevfrom
fix/email-editor-display-and-iframe-sandbox
May 4, 2026
Merged

fix(dashboard): collapsed email editor height; sandbox email-preview iframes#1406
BilalG1 merged 2 commits intodevfrom
fix/email-editor-display-and-iframe-sandbox

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented May 4, 2026

Summary

Two small dashboard fixes bundled together.

1. Email editor renders with zero height

The email template/theme/draft pages render VibeCodeLayout, whose mobile and desktop root wrappers used h-full. The dashboard shell's <main> (sidebar-layout.tsx:750) has no explicit height — its flex parent uses items-start, so <main> shrinks to its content rather than stretching. With no definite height up the chain, every h-full along the way (sidebar-layout's inner div, the data-full-bleed wrapper, VibeCodeLayout's own root) resolves to auto, and since the editor's content lives inside absolutely-positioned ResizablePanels, the wrapper collapses to ~0.

Fix: anchor VibeCodeLayout's root wrappers to viewport-minus-header instead of h-full. The values match what sidebar-layout.tsx:738 already uses for the sticky sidebar (3.5rem light / 6rem dark for the floating header card). With a definite height at the top, the existing flex-1 chains inside VibeCodeLayout resolve correctly without any layout/architecture refactor in the surrounding dashboard shell.

-<div className="flex flex-col h-full w-full overflow-hidden md:hidden">
+<div className="flex flex-col h-[calc(100dvh-3.5rem)] w-full overflow-hidden md:hidden">

-<div className="hidden md:flex flex-col h-full w-full overflow-hidden">
+<div className="hidden md:flex flex-col h-[calc(100vh-3.5rem)] dark:h-[calc(100vh-6rem)] w-full overflow-hidden">

Trade-off: the editor knows the dashboard header is 3.5rem (6rem dark). The same numbers are already hardcoded in sidebar-layout.tsx, so this isn't a new coupling.

2. Sandbox the email-preview iframes

EmailPreviewContent and EmailPreviewEditableContent rendered user-authored template HTML in iframes with no sandbox at all. With srcDoc-rendered iframes treated as same-origin by default, that meant any <script> (or onerror=, javascript: URL, etc.) inside a template could read the dashboard's cookies/localStorage and call the API as the viewing admin.

Set sandbox="allow-scripts" on both iframes:

  • Iframe is forced into a unique opaque origin → no access to parent cookies, localStorage, sessionStorage, or DOM.
  • No allow-same-origin, so credentialed fetches to the dashboard API don't carry the user's session (cookies aren't sent to a third-party opaque origin under default SameSite=Lax; cross-origin responses also unreadable due to CORS).
  • No allow-top-navigation / allow-forms / allow-popups → template can't redirect the parent tab, submit forms, or open windows.
  • allow-scripts is required so the inline scripts we inject (link-click prevention; the WYSIWYG editor that drives the postMessage flow at email-preview.tsx:413-435 and :625-672) can actually run. Without it, the editor itself was broken and links navigated freely.

Note: allow-scripts allow-same-origin together would be equivalent to no sandbox at all (the iframe could rewrite its own sandbox attribute and escape), so we deliberately omit allow-same-origin.

Residual risk (not addressed in this PR): a malicious template script can still postMessage a fake stack_edit_commit to the parent — the parent's e.source === iframeWindow check passes because the script is running in that iframe. The viewing admin would silently apply attacker-chosen source-code edits on save. That's a cross-admin UI-redress concern, not token exfiltration, and is best fixed with a CSP nonce on the injected script (so user template <script> tags can't run at all). Tracking as a follow-up.

Test plan

  • Open an email template editor — verify the preview, code panel, and chat panel are all visible at full height (light + dark mode).
  • Same for an email theme editor and an email draft editor in the draft stage.
  • Resize the window vertically — editor should fill the viewport below the header without overflowing past the bottom.
  • Click a link inside the rendered preview — should not navigate (link-click prevention script works under allow-scripts).
  • In edit mode, hover an editable text region, click to edit, type a change, hit ✓ — change should round-trip through postMessage and update the source.
  • Sanity check: paste <script>document.title='pwned'</script> (or <img onerror=...>) into a template, render preview — parent tab title/cookies/etc. should be untouched (script runs in opaque origin, can't reach parent).

…l-preview iframes

The email template/theme/draft editors render inside VibeCodeLayout, whose
mobile and desktop wrappers used `h-full`. The dashboard shell's `<main>` has
no explicit height (its flex parent uses `items-start`, so it doesn't stretch),
so percentage heights collapsed and the editor rendered with zero height.

Anchor the wrappers to viewport-minus-header instead, matching the values
already used by `sidebar-layout.tsx` (3.5rem light, 6rem dark for the floating
header). This decouples the editor from any ancestor height chain.

Also lock down the email preview iframes with `sandbox=""` so untrusted
template HTML can't run scripts, navigate the parent, submit forms, etc.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 4, 2026 9:47pm
stack-backend Ready Ready Preview, Comment May 4, 2026 9:47pm
stack-dashboard Ready Ready Preview, Comment May 4, 2026 9:47pm
stack-demo Ready Ready Preview, Comment May 4, 2026 9:47pm
stack-docs Ready Ready Preview, Comment May 4, 2026 9:47pm
stack-preview-backend Ready Ready Preview, Comment May 4, 2026 9:47pm
stack-preview-dashboard Ready Ready Preview, Comment May 4, 2026 9:47pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

Added sandbox="allow-scripts" to both email-preview iframes, and replaced h-full with explicit viewport-calc height classes for mobile and desktop in the vibe-coding layout, including a dark-mode desktop height override to align with the dashboard header.

Changes

Email Preview Iframe Sandboxing

Layer / File(s) Summary
Attribute change
apps/dashboard/src/components/email-preview.tsx
Both preview iframes now include sandbox="allow-scripts" on the <iframe> elements (normal and edit-mode).

Vibe Coding Layout Heights

Layer / File(s) Summary
Layout classes (mobile)
apps/dashboard/src/components/vibe-coding/vibe-code-layout.tsx
Replaced mobile wrapper h-full with h-[calc(100dvh-3.5rem)] to anchor to dynamic viewport height.
Layout classes (desktop/tablet)
apps/dashboard/src/components/vibe-coding/vibe-code-layout.tsx
Replaced desktop/tablet h-full with h-[calc(100vh-3.5rem)] and added dark:h-[calc(100vh-6rem)] to account for header height in dark mode.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I nibble on code with careful paws,
Sandboxed frames obey the laws.
Heights stretch true beneath the sky,
Dashboard rhythms sync nearby.
A tiny rabbit cheers — 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes both main changes: collapsed email editor height fix and iframe sandboxing, accurately reflecting 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.
Description check ✅ Passed The pull request description is comprehensive and well-structured, providing detailed explanations of both fixes, their rationale, trade-offs, residual risks, and a thorough test plan.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/email-editor-display-and-iframe-sandbox

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/components/email-preview.tsx (1)

694-702: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

sandbox="" breaks the WYSIWYG editor functionality.

The empty sandbox attribute blocks all JavaScript execution, but EmailPreviewEditableContent injects the WYSIWYG_EDITOR_SCRIPT (lines 125-573) which must run for edit mode to work. This script handles editable region parsing, overlays, hover/click events, contentEditable, and postMessage communication with the parent.

With sandbox="", none of this executes—edit mode is completely non-functional.

Use sandbox="allow-scripts" to permit the injected editor script while still blocking navigation, forms, popups, and same-origin access. The postMessage calls using '*' as targetOrigin will continue to work cross-origin.

Proposed fix
   return (
     <iframe
-      sandbox=""
+      sandbox="allow-scripts"
       ref={iframeRef}
       srcDoc={editableHtml}
       className="w-full h-full border-0"
       title="Email Preview (Edit Mode)"
     />
   );
🤖 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 `@apps/dashboard/src/components/email-preview.tsx` around lines 694 - 702, The
iframe's empty sandbox attribute prevents the injected editor script from
running, breaking edit mode; update the iframe in EmailPreviewEditableContent to
use sandbox="allow-scripts" (instead of sandbox="") so the WYSIWYG_EDITOR_SCRIPT
can execute while still restricting navigation/forms/popups/same-origin access;
ensure the change is applied where the iframe with ref={iframeRef} and
srcDoc={editableHtml} is returned so postMessage-based communication and
contentEditable behaviors work.
🤖 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.

Outside diff comments:
In `@apps/dashboard/src/components/email-preview.tsx`:
- Around line 694-702: The iframe's empty sandbox attribute prevents the
injected editor script from running, breaking edit mode; update the iframe in
EmailPreviewEditableContent to use sandbox="allow-scripts" (instead of
sandbox="") so the WYSIWYG_EDITOR_SCRIPT can execute while still restricting
navigation/forms/popups/same-origin access; ensure the change is applied where
the iframe with ref={iframeRef} and srcDoc={editableHtml} is returned so
postMessage-based communication and contentEditable behaviors work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c829e2f-2a2e-4d1e-940c-82c04d18f638

📥 Commits

Reviewing files that changed from the base of the PR and between 9ec32ce and ca21867.

📒 Files selected for processing (2)
  • apps/dashboard/src/components/email-preview.tsx
  • apps/dashboard/src/components/vibe-coding/vibe-code-layout.tsx

@BilalG1 BilalG1 requested a review from N2D4 May 4, 2026 21:13
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 May 4, 2026
Comment thread apps/dashboard/src/components/email-preview.tsx Outdated
Empty sandbox blocked the inline scripts that prevent link clicks and
drive the WYSIWYG editor's postMessage flow, breaking the editor.

`allow-scripts` (without `allow-same-origin`) lets those scripts run
while keeping the iframe in an opaque origin: malicious template HTML
still can't read parent cookies/localStorage/DOM, can't make credentialed
requests to the dashboard API, and can't navigate or submit forms in the
parent. Token exfiltration via XSS in template HTML remains blocked.

Residual risk: a script in the iframe can post a fake stack_edit_commit
to the parent (the e.source check passes since the script runs in the
iframe). That's a cross-admin UI-redress concern, not token theft, and
should be addressed separately (e.g. CSP nonce on the injected script).
@github-actions github-actions Bot assigned BilalG1 and unassigned N2D4 May 4, 2026
@BilalG1 BilalG1 merged commit 9f79bfb into dev May 4, 2026
37 checks passed
@BilalG1 BilalG1 deleted the fix/email-editor-display-and-iframe-sandbox branch May 4, 2026 22:37
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