diff --git a/backend/apps/agents/agent_manager.py b/backend/apps/agents/agent_manager.py
index 78a4a7e5..a8307969 100644
--- a/backend/apps/agents/agent_manager.py
+++ b/backend/apps/agents/agent_manager.py
@@ -711,6 +711,11 @@ class AgentManager:
global_settings = load_settings()
composed_prompt = self._compose_system_prompt(global_settings.default_system_prompt, mode_sys_prompt, session.system_prompt, connected_tools_ctx, outputs_ctx, browser_ctx)
+ if session.mode == "view-builder":
+ from backend.apps.outputs.view_builder_templates import VIEW_BUILDER_SKILL
+ skill_block = f"\n{VIEW_BUILDER_SKILL}\n"
+ composed_prompt = f"{composed_prompt}\n\n{skill_block}" if composed_prompt else skill_block
+
mcp_servers = await self._build_mcp_servers(session.allowed_tools)
_browser_delegation_tools = ["CreateBrowserAgent", "BrowserAgent", "BrowserAgents"]
diff --git a/backend/apps/modes/models.py b/backend/apps/modes/models.py
index 03c201fb..0afc742c 100644
--- a/backend/apps/modes/models.py
+++ b/backend/apps/modes/models.py
@@ -76,43 +76,28 @@ BUILTIN_MODES: list[Mode] = [
),
Mode(
id="view-builder",
- name="View Builder",
- description="Create and iterate on reusable View artifacts.",
+ name="App Builder",
+ description="Create and iterate on reusable App artifacts.",
system_prompt=(
- "You are helping the user build a reusable View — a self-contained "
- "web app rendered in an iframe.\n\n"
- "Your working directory is a dedicated workspace folder for this view. "
- "You can create any file structure you need using the Write tool.\n\n"
- "## Required files\n\n"
- "1. **index.html** — The entry point. A complete HTML document. "
- "React 18 is available via esm.sh CDN imports:\n"
- ' \n'
- " The structured input data is available at `window.OUTPUT_INPUT` (object) "
- "and any server-side result at `window.OUTPUT_BACKEND_RESULT`.\n\n"
- "2. **schema.json** — A JSON Schema object defining the structured input "
- "the view accepts. Example:\n"
- ' {"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}\n\n'
- "3. **meta.json** — Metadata for this view. Always write this file with "
- "a short name and one-sentence description. Example:\n"
- ' {"name":"Sales Dashboard","description":"Interactive dashboard showing sales metrics"}\n\n'
- "## Optional files\n\n"
- "- **backend.py** — Python code that receives `input_data` as "
- "a global dict and must assign its result to a global `result` dict.\n"
- "- **Any additional files** — You can create subdirectories and split code "
- "across multiple files. For example:\n"
- " - `components/Chart.js` — Reusable components\n"
- " - `utils/helpers.js` — Utility functions\n"
- " - `styles/main.css` — Stylesheets\n\n"
- "Files are served from the workspace, so relative imports work naturally:\n"
- ' ``\n'
- ' ``\n'
- " `import { helper } from './utils/helpers.js'` (in ES modules)\n\n"
- "## Guidelines\n\n"
- "Write files immediately when you have code ready. The user can see "
- "a live preview that auto-refreshes from these files. Always write the "
- "complete file content (do not use Edit for partial patches on first creation). "
- "For complex views, split code into separate files to keep things organized."
+ "You are an App Builder — an AI assistant that creates self-contained "
+ "web apps rendered in an iframe preview.\n\n"
+ "Your working directory is a dedicated workspace folder pre-seeded with "
+ "template files. Read the existing files before making changes.\n\n"
+ "## Critical rules\n\n"
+ "- The entry point MUST be named `index.html`. Never rename it or create "
+ "a different HTML file as the main entry point.\n"
+ "- Write files immediately when you have code ready — the user sees a "
+ "live preview that auto-refreshes from these files.\n"
+ "- Always write the complete file content on first creation (do not use "
+ "Edit for partial patches on new files).\n"
+ "- For complex apps, split code into separate files (JS, CSS, etc.) "
+ "and reference them from index.html with relative paths.\n"
+ "- Always update meta.json with a short name and one-sentence description.\n"
+ "- Build beautiful, polished UIs with modern design — dark themes, smooth "
+ "transitions, proper spacing, and responsive layouts.\n\n"
+ "Read the SKILL.md reference in your workspace for the full technical "
+ "specification of the App platform (available globals, file conventions, "
+ "schema format, backend.py usage, and examples)."
),
tools=None,
default_next_mode=None,
diff --git a/backend/apps/modes/modes.py b/backend/apps/modes/modes.py
index 6d393143..37aabd90 100644
--- a/backend/apps/modes/modes.py
+++ b/backend/apps/modes/modes.py
@@ -59,7 +59,8 @@ def load_mode(mode_id: str) -> Mode | None:
@modes.router.get("/list")
async def list_modes():
- return {"modes": [m.model_dump() for m in _load_all()]}
+ builtin_defaults = {m.id: m.model_dump() for m in BUILTIN_MODES}
+ return {"modes": [m.model_dump() for m in _load_all()], "builtin_defaults": builtin_defaults}
@modes.router.get("/{mode_id}")
@@ -93,6 +94,16 @@ async def update_mode(mode_id: str, body: ModeUpdate):
return {"ok": True, "mode": mode.model_dump()}
+@modes.router.post("/{mode_id}/reset")
+async def reset_mode(mode_id: str):
+ """Reset a built-in mode to its hardcoded defaults."""
+ builtin = next((m for m in BUILTIN_MODES if m.id == mode_id), None)
+ if not builtin:
+ raise HTTPException(status_code=400, detail="Only built-in modes can be reset")
+ _save(builtin)
+ return {"ok": True, "mode": builtin.model_dump()}
+
+
@modes.router.delete("/{mode_id}")
async def delete_mode(mode_id: str):
mode = _load(mode_id)
diff --git a/backend/apps/outputs/outputs.py b/backend/apps/outputs/outputs.py
index 206e29e8..22b3bdf7 100644
--- a/backend/apps/outputs/outputs.py
+++ b/backend/apps/outputs/outputs.py
@@ -15,6 +15,7 @@ from backend.apps.outputs.models import (
WorkspaceSeedRequest,
)
from backend.apps.outputs.executor import execute_backend_code
+from backend.apps.outputs.view_builder_templates import VIEW_BUILDER_SKILL, VIEW_TEMPLATE_FILES
from backend.apps.settings.settings import load_settings
logger = logging.getLogger(__name__)
@@ -235,6 +236,14 @@ async def seed_workspace(body: WorkspaceSeedRequest):
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, "w") as f:
f.write(content)
+ else:
+ for rel_path, content in VIEW_TEMPLATE_FILES.items():
+ full_path = os.path.join(folder, rel_path)
+ with open(full_path, "w") as f:
+ f.write(content)
+
+ with open(os.path.join(folder, "SKILL.md"), "w") as f:
+ f.write(VIEW_BUILDER_SKILL)
if body.meta:
with open(os.path.join(folder, "meta.json"), "w") as f:
diff --git a/backend/apps/outputs/view_builder_skill.md b/backend/apps/outputs/view_builder_skill.md
new file mode 100644
index 00000000..1e78377d
--- /dev/null
+++ b/backend/apps/outputs/view_builder_skill.md
@@ -0,0 +1,223 @@
+# App Builder — Platform Reference
+
+You are building an **App**: a self-contained web app served in an iframe.
+The workspace you're working in is the source of truth — every file you write
+here is served directly to the live preview.
+
+---
+
+## File conventions
+
+| File | Required | Purpose |
+|------|----------|---------|
+| `index.html` | **Yes** | Entry point. Must be a complete HTML document. This is the ONLY file the preview iframe loads — never rename it. |
+| `meta.json` | **Yes** | `{"name":"…","description":"…"}` — displayed in the UI header. Always write this. |
+| `schema.json` | Recommended | JSON Schema defining the input form (the "Test Input" tab). |
+| `backend.py` | Optional | Server-side Python executed before rendering. |
+| Everything else | Optional | JS, CSS, images, subdirectories — referenced from `index.html` via relative paths. |
+
+### ⚠️ Do NOT
+
+- Name the main HTML file anything other than `index.html` — the platform
+ will not find it and the preview will be blank.
+- Use `document.write()` — it breaks the injected data globals.
+- Assume any external server or API is available unless the user provides one.
+
+---
+
+## Injected globals
+
+Before `index.html` loads, the platform injects two globals:
+
+```javascript
+window.OUTPUT_INPUT // Object — structured input from the schema form
+window.OUTPUT_BACKEND_RESULT // Object | null — result from backend.py execution
+```
+
+These are available immediately in any `
+```
+
+ES module imports between JS files:
+
+```javascript
+// components/Chart.js
+import { formatNumber } from '../utils/helpers.js';
+```
+
+---
+
+## Using React
+
+React 18 is available via esm.sh CDN — no build step needed:
+
+```html
+
+
+
+```
+
+Other CDN libraries work too — use `https://esm.sh/` or `https://cdn.jsdelivr.net/npm/` for any npm package.
+
+---
+
+## Design guidelines
+
+- **Dark theme by default** — use dark backgrounds (#0f1117, #1a1d27) with
+ light text (#e2e8f0) unless the user requests otherwise.
+- **Modern aesthetics** — rounded corners (8-12px), subtle borders, box shadows,
+ smooth transitions (0.15-0.3s ease).
+- **Responsive** — use flexbox/grid, test at different sizes.
+- **Typography** — system font stack for UI, monospace for code/data.
+- **Color accents** — use a single accent color with variations for hover/active states.
+- **Spacing** — consistent padding (12-20px), adequate whitespace between sections.
+- **Interactivity** — hover effects, focus states, loading indicators where appropriate.
+
+---
+
+## Complete minimal example
+
+```html
+
+
+
+
+
+ My App
+
+
+
+