From f380ad62f4ff3950a296c07dce41728881d919a2 Mon Sep 17 00:00:00 2001 From: "Alexandro T. Netto" Date: Fri, 15 May 2026 17:12:00 -0700 Subject: [PATCH] =?UTF-8?q?fix(web):=20drop=20chrome=20bar=20=E2=80=94=20r?= =?UTF-8?q?ender=20HTML=20pages=20exactly=20like=20an=20.html=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shell was rendering a 33 px header above the iframe with "AI-generated content" + a Report mailto: link. Product call is now to render HTML pages with no chrome at all, so a Pagent URL feels identical to opening the .html file directly. Removes: - The REPORT_EMAIL constant + accompanying TODO - .html-chrome / .html-chrome-label / .html-chrome-report / .html-stack CSS - The
+ Report link from renderHtml() - The flex-column wrapper (iframe is now the only child) Iframe styling moves from `flex:1 1 auto;...;min-height:0` (designed for the flex parent) to `width:100%;height:100vh;border:0;display:block` so it pins to the viewport directly. Security boundary is unchanged. The three defense layers — server-side DOMPurify, meta-CSP in the srcdoc, iframe sandbox="" — all stay. The chrome bar was a visual disclosure, not a security control. --- apps/web/html-renderer.ts | 6 ++++- apps/web/main.ts | 54 ++++----------------------------------- 2 files changed, 10 insertions(+), 50 deletions(-) diff --git a/apps/web/html-renderer.ts b/apps/web/html-renderer.ts index d3674e5..1b51b42 100644 --- a/apps/web/html-renderer.ts +++ b/apps/web/html-renderer.ts @@ -51,6 +51,10 @@ export function createSandboxedIframe(sanitizedHtml: string): HTMLIFrameElement iframe.setAttribute('loading', 'lazy'); iframe.title = 'Agent-generated content'; iframe.srcdoc = buildScaffoldedHtml(sanitizedHtml); - iframe.style.cssText = 'flex:1 1 auto;width:100%;border:0;display:block;min-height:0'; + // Full viewport — no chrome wrapper. width:100% honors the parent + // (#app.is-html clears the 640 px cap; body has margin:0). height:100vh + // pins to the viewport so the iframe content scrolls inside the iframe, + // not the outer document. + iframe.style.cssText = 'width:100%;height:100vh;border:0;display:block'; return iframe; } diff --git a/apps/web/main.ts b/apps/web/main.ts index 68e9801..341e353 100644 --- a/apps/web/main.ts +++ b/apps/web/main.ts @@ -33,10 +33,6 @@ const POLL_MAX_MS = 30_000; const POLL_BACKOFF_FACTOR = 2; const POLL_TIMEOUT_MS = 60_000; -// TODO: make REPORT_EMAIL configurable via VITE_REPORT_EMAIL once self-hosters need it. -// Until then, abuse reports for pagent.link route to the project maintainer. -const REPORT_EMAIL = 'alex@blockful.io'; - class AgentUIApp extends SignalWatcher(LitElement) { static properties = { status: { state: true }, @@ -154,35 +150,6 @@ class AgentUIApp extends SignalWatcher(LitElement) { font-family: 'Material Symbols Outlined', sans-serif; font-variation-settings: 'FILL' 1; } - .html-chrome { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 14px; - background: light-dark(rgba(245, 245, 247, 0.95), rgba(20, 24, 32, 0.95)); - backdrop-filter: blur(8px); - border-bottom: 1px solid light-dark(rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.08)); - font-size: 13px; - color: var(--muted, #777); - } - .html-chrome-label::before { - content: '✦ '; - opacity: 0.6; - } - .html-chrome-report { - color: inherit; - text-decoration: underline; - font-size: 12px; - } - .html-chrome-report:hover { - color: var(--fg, #1b1b1b); - } - .html-stack { - display: flex; - flex-direction: column; - height: 100vh; - min-height: 0; - } `; declare status: 'connecting' | 'live' | 'closed' | 'error'; @@ -441,22 +408,11 @@ class AgentUIApp extends SignalWatcher(LitElement) {
Loading…
`; } - return html` -
- - ${this.htmlIframe()} -
- `; + // No chrome wrapper — the iframe itself is the page. Defense-in-depth + // (sandbox + meta-CSP + server-side sanitize) still applies; the + // chrome bar was only a visual disclosure and the product call is to + // render HTML pages exactly as if the user opened the .html file. + return this.htmlIframe(); } // The Lit literal cannot embed a raw iframe element easily because Lit owns