Skip to content

Commit 9f79bfb

Browse files
authored
fix(dashboard): collapsed email editor height; sandbox email-preview iframes (#1406)
## 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 `ResizablePanel`s, 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. ```diff -<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).
1 parent 0ab2654 commit 9f79bfb

2 files changed

Lines changed: 9 additions & 2 deletions

File tree

apps/dashboard/src/components/email-preview.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ function EmailPreviewContent({
106106

107107
return (
108108
<iframe
109+
sandbox="allow-scripts"
109110
srcDoc={inertPreviewHtml}
110111
className="w-full h-full border-0"
111112
title="Email Preview"
@@ -692,6 +693,7 @@ function EmailPreviewEditableContent({
692693

693694
return (
694695
<iframe
696+
sandbox="allow-scripts"
695697
ref={iframeRef}
696698
srcDoc={editableHtml}
697699
className="w-full h-full border-0"

apps/dashboard/src/components/vibe-coding/vibe-code-layout.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ export default function VibeCodeLayout({
132132
return (
133133
<TooltipProvider delayDuration={300}>
134134
{/* Mobile Layout - visible on small screens, hidden on md+ */}
135-
<div className="flex flex-col h-full w-full overflow-hidden md:hidden">
135+
{/* h-[calc(100dvh-3.5rem)] anchors to viewport (minus sticky header) so the
136+
editor doesn't depend on an unbroken h-full chain through the dashboard
137+
shell, which collapses because <main> doesn't stretch (items-start). */}
138+
<div className="flex flex-col h-[calc(100dvh-3.5rem)] w-full overflow-hidden md:hidden">
136139
{/* Mobile Header - Compact */}
137140
<div className="px-3 pt-3 pb-2 shrink-0">
138141
<div className="flex flex-col gap-2">
@@ -354,7 +357,9 @@ export default function VibeCodeLayout({
354357
</div>
355358

356359
{/* Desktop/Tablet Layout - hidden on small screens, visible on md+ */}
357-
<div className="hidden md:flex flex-col h-full w-full overflow-hidden">
360+
{/* See note on the mobile wrapper above; the dark-mode header is taller
361+
(floating card with mt-3/mb-3 padding), matching sidebar-layout.tsx. */}
362+
<div className="hidden md:flex flex-col h-[calc(100vh-3.5rem)] dark:h-[calc(100vh-6rem)] w-full overflow-hidden">
358363
<div className="flex-1 overflow-hidden flex flex-col">
359364
{/* Top Header / Toolbar - with consistent inset spacing */}
360365
<div className="px-6 pt-4 pb-3">

0 commit comments

Comments
 (0)