From 54e58b1a49950fe094b99ad5b8dae98e13944dcb Mon Sep 17 00:00:00 2001 From: Y1fe1Zh0u Date: Tue, 12 May 2026 19:09:04 +0800 Subject: [PATCH] Stop live HTML drafts from rebuilding preview iframes Streaming workspace HTML drafts were being treated as complete preview documents, so every token burst updated iframe srcdoc and forced a child document rebuild. Keep the live state as a source preview while drafting, preserve the final file preview path, and coalesce chat autoscroll work onto a single animation frame. Constraint: HTML generation streams partial tool arguments before the final workspace file exists Constraint: Generated AI HTML should not execute inside the app preview sandbox while still drafting Rejected: Increase the srcdoc debounce interval | still rebuilds iframes repeatedly during longer generations Rejected: Disable workspace live draft events globally | would remove useful progress for non-HTML files Confidence: high Scope-risk: narrow Directive: Do not reintroduce iframe srcDoc rendering for live HTML drafts without measuring iframe rebuild count and sandbox warnings Tested: npm run build Tested: Local frontend at http://127.0.0.1:3008 and browser probe confirmed source-draft updates create zero iframe loads Not-tested: Full backend authenticated HTML generation flow because this worktree has no initialized backend database --- .../components/WorkspaceOperationPanel.tsx | 31 ++++++++++++++++--- frontend/src/index.css | 20 ++++++++++++ frontend/src/pages/AgentDetail.tsx | 28 ++++++----------- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/WorkspaceOperationPanel.tsx b/frontend/src/components/WorkspaceOperationPanel.tsx index 32b9c2da3..2acc93d58 100644 --- a/frontend/src/components/WorkspaceOperationPanel.tsx +++ b/frontend/src/components/WorkspaceOperationPanel.tsx @@ -72,6 +72,7 @@ const DEFAULT_TREE_WIDTH = 240; const DEFAULT_HISTORY_WIDTH = 320; const MIN_SIDE_WIDTH = 220; const MAX_SIDE_WIDTH = 520; +const HTML_DRAFT_PREVIEW_LIMIT = 20000; function extOf(path: string): string { const idx = path.lastIndexOf('.'); @@ -257,6 +258,18 @@ function buildRevisionDiff(revision: any): string { return chunks.join('\n'); } +function HtmlDraftPreview({ content }: { content: string }) { + const shownContent = content.length > HTML_DRAFT_PREVIEW_LIMIT + ? `${content.slice(0, HTML_DRAFT_PREVIEW_LIMIT)}\n\n... truncated while drafting ...` + : content; + + return ( +
+
{shownContent}
+
+ ); +} + function HtmlPreviewFrame({ content, title, @@ -276,7 +289,12 @@ function HtmlPreviewFrame({ const fitFixedWidthContent = () => { const frame = frameRef.current; - const doc = frame?.contentDocument; + let doc: Document | null | undefined; + try { + doc = frame?.contentDocument; + } catch { + return; + } const root = doc?.documentElement; const body = doc?.body; if (!frame || !doc || !root || !body) return; @@ -304,7 +322,12 @@ function HtmlPreviewFrame({ const bindFrameFitObservers = () => { const frame = frameRef.current; - const doc = frame?.contentDocument; + let doc: Document | null | undefined; + try { + doc = frame?.contentDocument; + } catch { + return; + } if (!frame || !doc?.documentElement || !doc.body) return; observersRef.current?.frame?.disconnect(); @@ -371,7 +394,7 @@ function HtmlPreviewFrame({