Skip to content

feat(page-editor): Page Editor agent with design systems, live preview, and zero-build export#3370

Open
vibegui wants to merge 1 commit into
mainfrom
vibegui/page-editor-agent
Open

feat(page-editor): Page Editor agent with design systems, live preview, and zero-build export#3370
vibegui wants to merge 1 commit into
mainfrom
vibegui/page-editor-agent

Conversation

@vibegui
Copy link
Copy Markdown
Contributor

@vibegui vibegui commented May 15, 2026

Summary

Adds a local-first Page Editor agent that builds zero-build landing pages with Claude Code, plus a dedicated preview pane and a scaffolding pipeline that splits pages from design systems.

  • Two-concept storage: design-systems/<slug>/ (tokens.css, tokens.js, demo.html, meta.json) and pages/<slug>/ (index.html, app.js, sections.js, page.js, meta.json). Pages bind to a design system via meta.json.
  • New MCP tools: DESIGN_SYSTEM_CREATE / LIST / SET, PAGE_PREVIEW_PAGE_CREATE, alongside the existing PAGE_PREVIEW_STATUS / SET / REFRESH. Scaffolding is template-driven so the agent doesn't hand-roll boilerplate — stages 1 and 2 are single tool calls that switch the preview within ~50ms.
  • Preview pane: dual selector (page + design system), a welcome quiz on every fresh chat that composes a prompt and drops it into the chat input, an Export button, and an iframe that re-keys on file changes so the latest bytes always render.
  • Self-contained zip export: index.html inlines the bound design system's CSS as <style> and consolidates the local JS modules into one inline <script type="module">, so unzip-and-double-click works. Original multi-file source preserved under src/.
  • WCAG-based contrast enforcement runs on every DESIGN_SYSTEM_CREATE: fg ≥ 7:1, muted ≥ 5.5:1, border ≥ 1.5:1 against bg, with mixing toward fg to preserve hue. Fixes the recurring pastel-on-pastel illegibility from agent-generated palettes.
  • Robustness fixes hit along the way:
    • mcp-clients/client.ts: SELF (<orgId>_self) pseudo-connections route to an in-process MCP server over InMemoryTransport instead of an HTTP self-roundtrip. The HTTP path failed in conductor worktrees because Bun fetch on macOS can't resolve arbitrary *.localhost subdomains, so the virtual MCP's tool list never reached Claude Code.
    • lazy-client.ts: cache bypass for in-process MCP servers so newly-added management tools show up immediately.
    • templates.ts: tokens.js rendered via JSON.stringify so font stacks with embedded quotes can't produce SyntaxErrors; tokens.css font interpolation normalized via a small helper.
    • app.js template wraps each section in a Preact ErrorBoundary — a single broken section now shows a small inline error instead of blanking the page.

Test plan

  • bun run check — clean
  • bun test apps/mesh/src/page-preview/ — 24 pass / 0 fail (77 assertions)
  • Closed-loop integration script apps/mesh/scripts/test-page-preview-mcp.ts drives the live virtual-MCP endpoint end-to-end: initializetools/listDESIGN_SYSTEM_CREATEPAGE_PREVIEW_PAGE_CREATEPAGE_PREVIEW_REFRESHGET /export (validates zip magic bytes) → PAGE_PREVIEW_STATUS. PASS on a fresh server.
  • Manual: recruit Page Editor → quiz welcome → submit prompt → preview shows design-system demo → page shell appears → section edits trigger staggered fade-in reveal → export download produces a double-click-openable bundle.
  • Reviewer: try a deliberately bad palette (e.g. muted: "#E5DDF3" on bg: "#F3EBFF") and confirm the on-disk tokens.css ends up legible.

🤖 Generated with Claude Code

Demo readiness fixes (2026-05-15)

Latest commit (aa7622ecd) ships a tight cluster of end-to-end fixes shaking out during demo prep:

  • Recruit modal: update-path was dropping PAGE_PREVIEW_PAGE_CREATE, PAGE_PREVIEW_PROGRESS, and the three DESIGN_SYSTEM_* tools from existing agents' selected_tools. Re-recruiting an existing Page Editor stripped its mandated first-call tools. Unified both branches behind a single PAGE_EDITOR_SELECTED_TOOLS constant.
  • Agent stalls after PAGE_PREVIEW_PAGE_CREATE: the agent reliably emitted a long prose plan and ended its turn without promoting the preview. Added a nextStep advisory to each chain-driving tool's response (DESIGN_SYSTEM_CREATE, PAGE_PREVIEW_PAGE_CREATE, PAGE_PREVIEW_SET, PAGE_PREVIEW_REFRESH) naming the exact next 1–3 tool calls and the live slug. Tool-response nudges are far stickier than top-of-prompt rules for stopping prose-planning regressions. System prompt also gets a new "THE ONE RULE" section.
  • Hero (and other sections) rendered template defaults ("Build a beautiful page.") because the nextStep was citing made-up prop names. Audited templates.ts:662–873 and rewrote nextStep + system prompt with the actual contracts for all ten library sections.
  • Wrong design-system colors flashed on screen: DESIGN_SYSTEM_CREATE's description claimed missing fields get "sensible defaults" — but defaultBrand() is dark-neon indigo on near-black, not a smart default for arbitrary briefs. The agent would call DS_CREATE sparse, see wrong colors, then re-call with the real palette. Tightened description + prompt to commit the full palette on the first call.
  • Outline stepper persisted past the build: gated on state.isRunning so it fades out the moment the agent's turn ends.
  • Session isolation: the activePage / showKind server-state fallbacks were firing whenever this chat had no session DS/page yet — including for brand-new chats where the agent had only called PAGE_PREVIEW_PROGRESS. state.json from a previous chat would pull the old page into the preview. Both fallbacks now also gate on !previewToolFiredEarly so they only fire for true cold loads.

Summary by cubic

Adds a local‑first Page Editor agent with design systems, a stable host‑iframe live preview, a time‑travel stepper, and zero‑build export. Also adds a Page Preview tab and a faster browser‑as‑REPL build loop with paced section reveals.

  • New Features

    • Unified Design phase: split‑screen DS gallery + 24‑section library, a time‑travel stepper, and a 1.5s reveal queue. Honors reduced‑motion.
    • Preview pipeline: new PAGE_* and DESIGN_SYSTEM_* tools and org‑scoped /api/:org/page-preview/* routes with an in‑iframe Preact host (postMessage bridge, CSP SAMEORIGIN). Includes a Page Preview system tab and a recruit quiz.
    • Zero‑build export and readability: one‑file zip export with inlined CSS/JS via fflate, WCAG contrast enforcement (incl. onPrimary/onSecondary/onAccent), and curated default themes.
  • Bug Fixes

    • Footer settles at the end: no center‑scroll on the last section; bottom spacer collapses; brief hold, then scroll to top.
    • Stability: finished pages refresh read‑only; in‑process SELF/Dev‑assets MCP to avoid flaky HTTP; bypass cached tool lists for in‑process servers; hard‑disable shell/fs/search tools for this agent. Added a closed‑loop MCP script and contrast/service unit tests.

Written for commit 20a1187. Summary will update on new commits. Review in cubic

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Benchmark

Should we run the Virtual MCP strategy benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

Release Options

Suggested: Minor (2.346.0) — based on feat: prefix

React with an emoji to override the release type:

Reaction Type Next Version
👍 Prerelease 2.345.1-alpha.1
🎉 Patch 2.345.1
❤️ Minor 2.346.0
🚀 Major 3.0.0

Current version: 2.345.0

Note: If multiple reactions exist, the smallest bump wins. If no reactions, the suggested bump is used (default: patch).

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 issues found across 31 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/api/routes/proxy.ts">

<violation number="1" location="apps/mesh/src/api/routes/proxy.ts:98">
P2: Dev-assets support was added for `/:connectionId` but not for `/:connectionId/call-tool/:toolName`, so direct call-tool requests against `{org}_dev-assets` still return 404.</violation>
</file>

<file name="apps/mesh/src/web/components/home/page-editor-recruit-modal.tsx">

<violation number="1" location="apps/mesh/src/web/components/home/page-editor-recruit-modal.tsx:325">
P1: Updating an existing Page Editor agent drops required page-editor tools from `selected_tools`, so the updated agent can no longer execute its own build workflow.</violation>
</file>

<file name="apps/mesh/src/web/views/virtual-mcp/index.tsx">

<violation number="1" location="apps/mesh/src/web/views/virtual-mcp/index.tsx:839">
P2: `Page preview` becomes a one-way default: after switching away once, this option disappears and can’t be re-selected.</violation>
</file>

<file name="apps/mesh/src/api/routes/page-preview.ts">

<violation number="1" location="apps/mesh/src/api/routes/page-preview.ts:127">
P2: Do not return raw internal error messages from `/export`; this can leak server filesystem details. Return a sanitized message instead.</violation>
</file>

<file name="apps/mesh/src/mcp-clients/client.ts">

<violation number="1" location="apps/mesh/src/mcp-clients/client.ts:53">
P1: The new SELF detection is too broad: `endsWith("_self")` can misroute non-SELF user connections to the in-process management MCP.</violation>
</file>

<file name="apps/mesh/src/page-preview/service.ts">

<violation number="1" location="apps/mesh/src/page-preview/service.ts:1051">
P1: Escape `</script>` before embedding inline module code in exported HTML to prevent script-breakout injection.</violation>
</file>

<file name="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx">

<violation number="1" location="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx:131">
P2: Use an exact slug/path-segment check instead of substring matching when deciding whether PAGE_PREVIEW_SET activated the current session page.</violation>

<violation number="2" location="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx:715">
P1: The iframe sandbox currently allows both scripts and same-origin, which effectively defeats sandbox isolation for same-origin generated preview content.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Comment thread apps/mesh/src/web/components/home/page-editor-recruit-modal.tsx Outdated
Comment on lines +53 to +55
if (connection.id.endsWith("_self")) {
return connectInProcess(await managementMCP(ctx), "self-in-process");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The new SELF detection is too broad: endsWith("_self") can misroute non-SELF user connections to the in-process management MCP.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/mcp-clients/client.ts, line 53:

<comment>The new SELF detection is too broad: `endsWith("_self")` can misroute non-SELF user connections to the in-process management MCP.</comment>

<file context>
@@ -28,6 +50,9 @@ export async function clientFromConnection(
   ctx: MeshContext,
   superUser = false,
 ): Promise<Client> {
+  if (connection.id.endsWith("_self")) {
+    return connectInProcess(await managementMCP(ctx), "self-in-process");
+  }
</file context>
Suggested change
if (connection.id.endsWith("_self")) {
return connectInProcess(await managementMCP(ctx), "self-in-process");
}
const selfId = `${connection.organization_id}_self`;
if (connection.id === selfId) {
return connectInProcess(await managementMCP(ctx), "self-in-process");
}

`<style>\n${tokensCss}\n</style>`,
);
html = html.replace(
/<script[^>]*?src=["']\.\/app\.js["'][^>]*?>\s*<\/script>/g,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Escape </script> before embedding inline module code in exported HTML to prevent script-breakout injection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/page-preview/service.ts, line 1051:

<comment>Escape `</script>` before embedding inline module code in exported HTML to prevent script-breakout injection.</comment>

<file context>
@@ -0,0 +1,1165 @@
+    `<style>\n${tokensCss}\n</style>`,
+  );
+  html = html.replace(
+    /<script[^>]*?src=["']\.\/app\.js["'][^>]*?>\s*<\/script>/g,
+    `<script type="module">\n${inlineModule}\n</script>`,
+  );
</file context>

title="Page preview"
src={liveUrl}
className="absolute inset-0 w-full h-full border-0 bg-white"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The iframe sandbox currently allows both scripts and same-origin, which effectively defeats sandbox isolation for same-origin generated preview content.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx, line 715:

<comment>The iframe sandbox currently allows both scripts and same-origin, which effectively defeats sandbox isolation for same-origin generated preview content.</comment>

<file context>
@@ -0,0 +1,853 @@
+            title="Page preview"
+            src={liveUrl}
+            className="absolute inset-0 w-full h-full border-0 bg-white"
+            sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
+          />
+        )}
</file context>
Suggested change
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
sandbox="allow-scripts allow-forms allow-popups"

// unscoped /mcp/{connectionId}_dev-assets route registered in dev-only.ts
// so frontend code using the canonical /api/:org/mcp/<id> URL still
// reaches the dev-assets MCP server in dev mode.
if (connectionId.endsWith("_dev-assets")) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Dev-assets support was added for /:connectionId but not for /:connectionId/call-tool/:toolName, so direct call-tool requests against {org}_dev-assets still return 404.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/proxy.ts, line 98:

<comment>Dev-assets support was added for `/:connectionId` but not for `/:connectionId/call-tool/:toolName`, so direct call-tool requests against `{org}_dev-assets` still return 404.</comment>

<file context>
@@ -88,6 +90,27 @@ export const createProxyRoutes = () => {
+    // unscoped /mcp/{connectionId}_dev-assets route registered in dev-only.ts
+    // so frontend code using the canonical /api/:org/mcp/<id> URL still
+    // reaches the dev-assets MCP server in dev mode.
+    if (connectionId.endsWith("_dev-assets")) {
+      const devOrgId = connectionId.slice(0, -"_dev-assets".length);
+      if (!ctx.organization || ctx.organization.id !== devOrgId) {
</file context>

Comment thread apps/mesh/src/web/views/virtual-mcp/index.tsx
? await buildPageExportBundle({ orgId: org.id, slug })
: await buildDesignSystemExportBundle({ orgId: org.id, slug });
} catch (err) {
throw new HTTPException(404, { message: (err as Error).message });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Do not return raw internal error messages from /export; this can leak server filesystem details. Return a sanitized message instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/page-preview.ts, line 127:

<comment>Do not return raw internal error messages from `/export`; this can leak server filesystem details. Return a sanitized message instead.</comment>

<file context>
@@ -0,0 +1,153 @@
+          ? await buildPageExportBundle({ orgId: org.id, slug })
+          : await buildDesignSystemExportBundle({ orgId: org.id, slug });
+    } catch (err) {
+      throw new HTTPException(404, { message: (err as Error).message });
+    }
+    const { bundleName, files } = bundle;
</file context>

Comment thread apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/page-preview/host-html.ts">

<violation number="1" location="apps/mesh/src/page-preview/host-html.ts:684">
P2: Incremental refresh re-renders without remounting, so section error boundaries can stay stuck in error state after a fix.</violation>
</file>

<file name="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx">

<violation number="1" location="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx:668">
P2: The design-system sync effect only watches `designSystems.length`, so metadata changes (name/brand edits) are missed and the host grid can display stale data.</violation>

<violation number="2" location="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx:867">
P2: Re-keying the iframe by `refreshNonce` can break the host handshake lifecycle because readiness is not reset per iframe instance, so init intent messages may not be replayed to the new iframe.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Comment thread apps/mesh/src/page-preview/host-html.ts
);
// Send filesBase once we're ready so dynamic-import URLs resolve.
win.postMessage({ type: "host:hello", filesBase }, "*");
}, [hostReady, designSystems.length, filesBase]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The design-system sync effect only watches designSystems.length, so metadata changes (name/brand edits) are missed and the host grid can display stale data.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx, line 668:

<comment>The design-system sync effect only watches `designSystems.length`, so metadata changes (name/brand edits) are missed and the host grid can display stale data.</comment>

<file context>
@@ -496,28 +556,125 @@ export function PagePreviewTab() {
+    );
+    // Send filesBase once we're ready so dynamic-import URLs resolve.
+    win.postMessage({ type: "host:hello", filesBase }, "*");
+  }, [hostReady, designSystems.length, filesBase]);
 
   const handleExport = () => {
</file context>

Comment thread apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx Outdated
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/page-preview/host-html.ts">

<violation number="1" location="apps/mesh/src/page-preview/host-html.ts:381">
P2: Do not swallow all `tokens.js` import errors; only ignore true “missing module” cases and rethrow other failures so broken design-system code is visible.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

const tokensMod = await import(state.filesBase + '/files/design-systems/' + encodeURIComponent(dsSlug) + '/tokens.js?v=' + v);
brand = tokensMod.BRAND;
} catch (err) {
console.warn('[host] design system "' + dsSlug + '" not found — using current brand', err);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Do not swallow all tokens.js import errors; only ignore true “missing module” cases and rethrow other failures so broken design-system code is visible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/page-preview/host-html.ts, line 381:

<comment>Do not swallow all `tokens.js` import errors; only ignore true “missing module” cases and rethrow other failures so broken design-system code is visible.</comment>

<file context>
@@ -364,12 +364,23 @@ export const PAGE_PREVIEW_HOST_HTML = `<!doctype html>
+        const tokensMod = await import(state.filesBase + '/files/design-systems/' + encodeURIComponent(dsSlug) + '/tokens.js?v=' + v);
+        brand = tokensMod.BRAND;
+      } catch (err) {
+        console.warn('[host] design system "' + dsSlug + '" not found — using current brand', err);
+      }
+      return { brand, Sections: sectionsMod, blocks: pageMod.PAGE || [] };
</file context>

Tip: Review your code locally with the cubic CLI to iterate faster.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/mesh/src/api/routes/proxy.ts">

<violation number="1" location="apps/mesh/src/api/routes/proxy.ts:98">
P2: Dev-assets support was added for `/:connectionId` but not for `/:connectionId/call-tool/:toolName`, so direct call-tool requests against `{org}_dev-assets` still return 404.</violation>
</file>

<file name="apps/mesh/src/api/routes/page-preview.ts">

<violation number="1" location="apps/mesh/src/api/routes/page-preview.ts:127">
P2: Do not return raw internal error messages from `/export`; this can leak server filesystem details. Return a sanitized message instead.</violation>
</file>

<file name="apps/mesh/src/mcp-clients/client.ts">

<violation number="1" location="apps/mesh/src/mcp-clients/client.ts:53">
P1: The new SELF detection is too broad: `endsWith("_self")` can misroute non-SELF user connections to the in-process management MCP.</violation>
</file>

<file name="apps/mesh/src/page-preview/service.ts">

<violation number="1" location="apps/mesh/src/page-preview/service.ts:1051">
P1: Escape `</script>` before embedding inline module code in exported HTML to prevent script-breakout injection.</violation>
</file>

<file name="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx">

<violation number="1" location="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx:668">
P2: The design-system sync effect only watches `designSystems.length`, so metadata changes (name/brand edits) are missed and the host grid can display stale data.</violation>

<violation number="2" location="apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx:715">
P1: The iframe sandbox currently allows both scripts and same-origin, which effectively defeats sandbox isolation for same-origin generated preview content.</violation>
</file>

<file name="apps/mesh/src/page-preview/host-html.ts">

<violation number="1" location="apps/mesh/src/page-preview/host-html.ts:381">
P2: Do not swallow all `tokens.js` import errors; only ignore true “missing module” cases and rethrow other failures so broken design-system code is visible.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic

Comment thread apps/mesh/src/page-preview/host-html.ts Outdated
@vibegui vibegui force-pushed the vibegui/page-editor-agent branch from 6593dfb to 23c3395 Compare May 15, 2026 15:39
@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented May 15, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

@vibegui vibegui force-pushed the vibegui/page-editor-agent branch 2 times, most recently from 98826df to 1a388f4 Compare May 20, 2026 13:09
…s, choreographed build

Adds the Page Editor: a new builtin agent that builds landing pages
section-by-section in front of the user via a real-time preview pane.
The agent emits PAGE_* tool calls (PAGE_BOOTSTRAP, PAGE_RENDER_BLOCK,
PAGE_UPDATE_BLOCK, PAGE_REMOVE_BLOCK, PAGE_REVIEW_SUGGEST, and DS
management) which Studio observes from the chat stream and dispatches
straight into an iframe as host:* postMessages — a browser-as-REPL
pipeline that skips HTTP round-trips per block for ~10× faster builds.

Main pieces:
- apps/mesh/src/tools/page-preview/         — MCP tool surface
- apps/mesh/src/page-preview/service.ts     — server-side persistence
- apps/mesh/src/page-preview/templates.ts   — ~24 section library
- apps/mesh/src/page-preview/host-html.ts   — self-contained Preact
                                              runtime served into the
                                              preview iframe
- apps/mesh/src/web/layouts/main-panel-tabs/page-preview-tab.tsx
                                            — Studio-side host + bridge
- apps/mesh/src/web/components/home/page-editor-recruit-modal.tsx
                                            — seven-question welcome quiz

Build choreography (in host-html.ts):
- Phases: prelude → design → layout → building → done
- UnifiedDesignPhase: split-screen DS gallery + section library with
  the agent's outline highlighted (staggered pill animations)
- OutlineStepper: sticky stepper with click-to-time-travel preview
- queueReveal: paced reveal queue (MIN_REVEAL_INTERVAL_MS = 1500)
  so each new section gets reading room before the page scrolls
- Refresh of a done page short-circuits all choreography

Also adds:
- Contrast math (onPrimary/onSecondary/onAccent tokens) for readable
  buttons on brand backgrounds
- Auto-bubble of preview runtime errors back to the agent
- GEO/SEO baseline (JSON-LD, llms.txt, robots.txt)
- WELL_KNOWN_AGENT_TEMPLATES entry + home-screen tile

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vibegui vibegui force-pushed the vibegui/page-editor-agent branch from 0260fec to 20a1187 Compare May 25, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant