diff --git a/web/src/components/app/EmailEditor.tsx b/web/src/components/app/EmailEditor.tsx
index 7a4b8cee..1efd403f 100644
--- a/web/src/components/app/EmailEditor.tsx
+++ b/web/src/components/app/EmailEditor.tsx
@@ -9,6 +9,10 @@
// document, an event handler) forces the source view. The visual surface
// works by assigning innerHTML, and a signature is org data a teammate
// wrote, so an
there would run in someone else's dashboard.
+// Inline styles are allowed through, because almost every real signature
+// has them, so the surface is paint-contained instead: that makes it the
+// containing block for a positioned descendant, and a signature saying
+// position:fixed can no longer lay itself over a teammate's dashboard.
// - the preview renders in the inbox's sandboxed frame, never in the
// dashboard DOM, for the same reason.
@@ -359,7 +363,7 @@ export default function EmailEditor({
commitHtml(adopted);
setCode(true);
}}
- className="min-h-[120px] px-3 py-2.5 text-[13px] text-slate-800 outline-none prose prose-sm max-w-none"
+ className="email-signature-surface min-h-[120px] px-3 py-2.5 text-[13px] text-slate-800 outline-none prose prose-sm max-w-none"
/>
)}
diff --git a/web/src/global.css b/web/src/global.css
index aab72ffc..844e0dce 100644
--- a/web/src/global.css
+++ b/web/src/global.css
@@ -459,7 +459,12 @@ svg.loading circle {
leave the dashboard, and a mail client would ignore them anyway. A cell with
its own inline border or background wins, so a real design still previews as
itself while it is being edited. */
-.tiptap-body table { border-collapse: collapse; margin: 0.4em 0; width: 100%; table-layout: fixed; }
+.tiptap-body table { border-collapse: collapse; margin: 0.4em 0; max-width: 100%; }
+/* Only a table that sizes itself nowhere else. A presentational width= loses to
+ any stylesheet rule, so forcing one here edited a 600px design at full
+ container width and table-layout:fixed threw away its column widths. An
+ inline style beats this rule on its own. */
+.tiptap-body table:not([width]) { width: 100%; }
.tiptap-body td, .tiptap-body th { border: 1px dashed #e2e8f0; padding: 0.4em 0.55em; vertical-align: top; position: relative; }
.tiptap-body th { background: #f8fafc; font-weight: 600; text-align: left; }
.tiptap-body td > p:last-child, .tiptap-body th > p:last-child { margin-bottom: 0; }
@@ -468,6 +473,14 @@ svg.loading circle {
background: rgba(2, 132, 199, 0.08); /* sky-600 */
}
+/* The signature is other people's markup mounted in the dashboard's own DOM,
+ because a contentEditable is how the visual editor works. Paint containment
+ makes this box the containing block for anything positioned inside it and
+ clips to it, so a signature carrying position:fixed covers itself rather
+ than a teammate's screen. Markup that could execute never reaches here at
+ all; EmailEditor forces the source view for that. */
+.email-signature-surface { contain: layout paint; }
+
/* Merge-tag chips shown inside the composer editor. Only the node-view button
carries .tpl-var; the resolved `` the preview renders is left
unstyled on purpose so it reads as the plain value it will send as. */