Files
site-builder/craft/scripts/e2e-sitesmith-cleaned-entry.ts
shadowdaoandClaude Opus 5.5 465cf17bb0 Sitesmith: self-review render via review_render_upload; one-line-per-node summaries; real-browser check of cleaned output
- The render is uploaded with the new review_render_upload action (backend kind
  review_render, the only image a review job accepts); mocks follow.
- summarizeCanvas() never lets a newline or a quote into a node line (the server
  validates review summaries line by line); test.
- test:browser:sitesmith-cleaned (Firefox + Chromium): a page published from a
  cleaned ordinary Sitesmith response requests nothing outside the allow-listed
  hosts (own assets, fonts, Font Awesome CDN, images.pexels.com, placehold.co)
  and has no inline handlers or javascript:/data: URL attributes; the same
  response uncleaned is the control (requests example.com). All requests are
  answered or aborted locally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:47:04 -08:00

32 lines
2.0 KiB
TypeScript

// Bundled (esbuild) by scripts/e2e-sitesmith-cleaned.mjs: turns one ordinary Sitesmith response into a
// published-style page, with or without the apply-time cleaner, through the real exporter.
import { cleanAiResponse } from '../src/sitesmith/clean-ai-response';
import { flattenTreeForCraft } from '../src/utils/craft-tree';
import { exportToHtml } from '../src/utils/html-export';
import { DEFAULT_SITE_DESIGN } from '../src/state/SiteDesignContext';
import type { SitesmithResponse } from '../src/types/sitesmith';
const n = (type: string, props: Record<string, unknown>, nodes?: any[]) => ({ type: { resolvedName: type }, props, ...(nodes ? { nodes } : {}) });
export const RESPONSE: SitesmithResponse = {
type: 'replace', scope: 'page', message: 'Built a page.',
pages: [{ name: 'Home', tree: n('Container', { node_id: 'ROOT', tag: 'div' }, [
n('Navbar', { node_id: 'nav', logoType: 'text', logoText: 'Brand', logoUrl: '/', links: [{ text: 'Home', href: '/' }, { text: 'Old link', href: 'javascript:void(0)' }] }),
n('Heading', { node_id: 'h', text: 'Fresh bread every morning', level: 'h1' }),
n('ImageBlock', { node_id: 'i1', src: 'assets/hero.png', alt: 'Our bakery' }),
n('ImageBlock', { node_id: 'i2', src: 'https://example.com/photo.png', alt: 'Off-list host' }),
n('ImageBlock', { node_id: 'i3', src: 'https://placehold.co/600x400?text=Team', alt: 'Placeholder' }),
n('Container', { node_id: 'c', tag: 'div', style: { backgroundImage: 'url(https://example.com/bg.png)', padding: '24px' } }, [
n('ButtonLink', { node_id: 'b', text: 'Book a table', href: 'https://example.com/book' }),
]),
n('VideoBlock', { node_id: 'v', videoUrl: 'https://example.com/clip.mp4' }),
]) }],
};
export function render(clean: boolean): string {
const resp = clean ? cleanAiResponse(RESPONSE) : RESPONSE;
if (resp.type !== 'replace') throw new Error('unexpected');
const flat = flattenTreeForCraft(resp.pages[0].tree);
return exportToHtml(JSON.stringify(flat.nodes), { title: 'Home', includeFonts: true, design: DEFAULT_SITE_DESIGN }).html;
}