Add iframe auto-resize via postMessage

Embeds now report content height to the parent frame using a
ResizeObserver on document.body. The EmbedDialog snippet includes
a listener script that adjusts the iframe height on each resize
event. Documented the spicebook-resize protocol in llms.txt.
This commit is contained in:
Ryan Malloy 2026-03-07 23:45:10 -07:00
parent bfab38e954
commit 00fa420743
3 changed files with 54 additions and 3 deletions

View File

@ -598,6 +598,26 @@ iframe.contentWindow.postMessage(
Accepted values for `theme`: `"dark"`, `"light"`. Other messages are ignored.
### Auto-resize via postMessage
The embed reports its content height to the parent page whenever the layout changes (initial render, simulation results, expanded cells). The parent listens for `spicebook-resize` messages and updates the iframe height:
```javascript
window.addEventListener('message', function(e) {
if (e.data && e.data.type === 'spicebook-resize') {
document.getElementById('spicebook-{notebook_id}').style.height = e.data.height + 'px';
}
});
```
The message payload:
```json
{ "type": "spicebook-resize", "height": 1842 }
```
`height` is `document.documentElement.scrollHeight` in pixels. The embed sends this message on every `ResizeObserver` callback, deduplicated to only fire when height actually changes. The **Embed** button's generated snippet includes this listener automatically.
### Discovering the embed snippet
In the notebook editor UI, the **Embed** button in the toolbar opens a popover with a ready-to-copy iframe snippet and a theme toggle.
In the notebook editor UI, the **Embed** button in the toolbar opens a popover with a ready-to-copy iframe snippet (including auto-resize listener) and a theme toggle.

View File

@ -38,6 +38,26 @@ export default function EmbedViewer({ notebookId, initialTheme }: EmbedViewerPro
return () => window.removeEventListener('message', handleMessage);
}, []);
// Report content height to parent frame for auto-resize
useEffect(() => {
if (typeof window === 'undefined' || window.parent === window) return;
let lastHeight = 0;
function reportHeight() {
const height = document.documentElement.scrollHeight;
if (height !== lastHeight) {
lastHeight = height;
window.parent.postMessage({ type: 'spicebook-resize', height }, '*');
}
}
const observer = new ResizeObserver(reportHeight);
observer.observe(document.body);
reportHeight();
return () => observer.disconnect();
}, []);
// Keep a ref to notebook for use inside handleRun to avoid stale closures
const notebookRef = useRef(notebook);
notebookRef.current = notebook;

View File

@ -14,12 +14,23 @@ export function EmbedDialog() {
const origin = typeof window !== 'undefined' ? window.location.origin : '';
const iframeId = `spicebook-${notebookId}`;
const snippet = `<iframe
id="${iframeId}"
src="${origin}/embed/${notebookId}?theme=${theme}"
width="100%" height="600"
style="border: 1px solid #334155; border-radius: 8px;"
allow="clipboard-write"
></iframe>`;
></iframe>
<script>
window.addEventListener('message', function(e) {
if (e.data && e.data.type === 'spicebook-resize') {
var f = document.getElementById('${iframeId}');
if (f) f.style.height = e.data.height + 'px';
}
});
</script>`;
function handleCopy() {
navigator.clipboard.writeText(snippet).then(() => {
@ -72,7 +83,7 @@ export function EmbedDialog() {
<textarea
readOnly
value={snippet}
rows={5}
rows={12}
className="w-full resize-none rounded-md border border-slate-600 bg-slate-800 p-2 font-mono text-[11px] text-slate-300 focus:outline-none focus:border-blue-500"
/>