type Section = 'full' | 'first_steps' | 'analysis' | 'structure' | 'seo' | 'manifest' | 'templates' | 'tokens' | 'forms' | 'assets' | 'checklist' | 'common_mistakes';
const SECTIONS: Record<Section, string> = {
first_steps: `# REQUIRED FIRST STEPS
## STOP. DO NOT BUILD ANYTHING YET.
Before writing ANY HTML, templates, or manifest.json, you MUST:
1. **Determine which project this is for** - Call \`list_projects\`
2. **Ask the user** - "Which project?" or "What should I name your new project?"
3. **Get a projectId** - Either from existing project or from \`create_site\`
4. **Get the schema** - Call \`get_tenant_schema\` to know what fields exist
**Skipping these steps WILL cause deployment failures.**
---
## Step 1: Check for Existing Projects
Call \`list_projects\` to see the user's existing Fast Mode projects.
## Step 2: Ask the User
Based on what \`list_projects\` returns:
**If NO projects exist:**
- This is a new user - ask: "What would you like to name your new project?"
- Proceed to Step 3b (New Project)
**If projects exist:**
- Present the list and ask: "Is this website for one of your existing projects, or should I create a new one?"
- Let the user choose which project, or confirm they want a new one
## Step 3a: For EXISTING Projects
1. User selects the project from the list
2. Call \`get_tenant_schema(projectId)\` to get the current collections and fields
3. **Use this schema to build templates with the correct field names**
4. When deploying, this will UPDATE the existing project
5. Proceed to Phase 1 (Website Analysis)
## Step 3b: For NEW Projects
1. Ask for the project name if you don't have it
2. Call \`create_site(name)\` to create the project
3. You'll receive a projectId to use for deployment
4. After building the package, call \`sync_schema\` to create collections/fields
5. **Ask the user:** "Would you like me to generate sample items so you can preview how the collections will look?"
6. If yes, call \`generate_sample_items(projectId)\` - it handles dependency ordering automatically
7. Proceed to Phase 1 (Website Analysis)
---
**WHY THIS MATTERS:**
- For existing projects: The schema determines which fields to use in templates
- For new projects: You need the projectId before you can deploy
- Always: The user must confirm which project to use - never assume
---
## CHECKPOINT: Before You Continue
**STOP.** Confirm you have completed these steps:
| Requirement | How to Get It |
|-------------|---------------|
| projectId | From \`list_projects\` (existing) or \`create_site\` (new) |
| Project name confirmed | Asked the user |
| Schema loaded | Called \`get_tenant_schema(projectId)\` |
**If you don't have a projectId, DO NOT PROCEED.** Go back to Step 1.
The projectId is REQUIRED for:
- \`get_tenant_schema\` - to get existing field names
- \`deploy_package\` - to upload the site
- \`sync_schema\` - to create new collections
`,
analysis: `# Phase 1: Website Analysis (MANDATORY)
Before writing ANY code, complete this analysis:
## 1.1 Map ALL URLs
Visit every page and document the exact URL structure:
\`\`\`
/ (Homepage)
/about or /about-us or /company
/services or /what-we-do
/contact or /get-in-touch
/blog or /news or /resources or /insights
/blog/post-slug
/team or /our-team
\`\`\`
## 1.2 Categorize Each Page
**Page Types:**
- **Static** - Fixed content (/, /about, /contact)
- **List** - Shows multiple items (/blog, /team)
- **Detail** - Single item from a list (/blog/my-post)
## 1.3 Identify Content Types
Identify repeating content that should be CMS collections:
- Time-stamped articles → **Blog/Posts collection**
- Staff/employee profiles → **Team collection**
- Downloadable files → **Downloads collection**
- Product listings → **Products collection**
- Testimonials → **Testimonials collection**
**Note:** Create custom collections in the CMS dashboard. You can use collection templates (Blog, Team, Downloads, etc.) to quickly set up common patterns.
## 1.4 Document Assets
List all asset locations:
\`\`\`
CSS: /css/*.css, /styles/*.css
JS: /js/*.js, /scripts/*.js
Images: /images/*, /assets/img/*
Fonts: Google Fonts links, /fonts/*
\`\`\`
## 1.5 Critical Rule
**PRESERVE ORIGINAL URLs**
If the site uses \`/resources\` for articles, keep \`/resources\`.
Do NOT change it to \`/blog\`.
Use the manifest's path configuration to maintain original URLs.`,
structure: `# Package Structure
Create a package with this structure (can be at repo root OR in a subfolder like cms_package/):
\`\`\`
website-package/ # Can be at root or in a subfolder
├── manifest.json # Required: Defines pages and CMS templates
├── public/ # Static assets (CSS, JS, images, fonts)
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── main.js
│ └── images/
│ └── logo.png
├── pages/ # HTML pages (static content)
│ ├── index.html
│ ├── about.html
│ └── contact.html
└── templates/ # CMS templates (dynamic content)
├── posts_index.html
├── posts_detail.html
├── team_index.html
└── services_index.html
\`\`\`
**SUPPORTED REPO STRUCTURES:**
\`\`\`
Option 1: CMS files at root
repo/
├── manifest.json
├── pages/
├── public/
└── templates/
Option 2: CMS files in subfolder (e.g. for Next.js projects)
repo/
├── app/ # Next.js app (ignored)
├── cms_package/ # CMS files here
│ ├── manifest.json
│ ├── pages/
│ ├── public/
│ └── templates/
└── package.json
\`\`\`
## Folder Rules
### public/
- ALL assets go here (CSS, JS, images, fonts)
- Maintains subfolder structure
- Referenced as \`/public/...\` in HTML
### pages/
- Static HTML pages
- One file per page
- Use data-edit-key for editable text
### templates/
- CMS-powered pages
- Use {{tokens}} for dynamic content
- Named by collection slug (e.g., posts_index.html, posts_detail.html)`,
seo: `# SEO Tags (CRITICAL - Do NOT Include)
Fast Mode automatically manages all SEO meta tags. Do NOT include these in your HTML templates:
## Tags to EXCLUDE
| Tag | Reason |
|-----|--------|
| \`<title>...\` | Managed via Settings → SEO & Design |
| \`<meta name="description">\` | Managed via Settings → SEO & Design |
| \`<meta name="keywords">\` | Managed via Settings → SEO & Design |
| \`<meta property="og:*">\` | Open Graph tags auto-generated |
| \`<meta name="twitter:*">\` | Twitter cards auto-generated |
| \`<link rel="icon">\` | Favicon managed in settings |
| \`<link rel="shortcut icon">\` | Favicon managed in settings |
| \`<link rel="apple-touch-icon">\` | Managed by Fast Mode |
| \`<meta name="google-site-verification">\` | Managed in settings |
## Correct \`<head>\` Structure
\`\`\`html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SEO managed by Fast Mode -->
<link rel="stylesheet" href="/public/css/style.css">
<!-- Font imports, external scripts, etc. are OK -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
</head>
\`\`\`
## How Fast Mode SEO Works
1. **Global SEO** (Settings → SEO & Design)
- Site-wide defaults: title, description, OG image, favicon
- Applied to all pages
2. **Page-Level SEO** (Editor → click page → SEO tab)
- Override specific pages with custom title/description
- Page-level settings take priority over global
3. **Automatic Injection**
- Fast Mode strips any hardcoded SEO tags from templates
- Injects correct SEO from settings at render time
- Guarantees single source of truth
## Why This Matters
- No duplicate meta tags (bad for SEO ranking)
- Content managers can update SEO without touching code
- Consistent SEO across all pages
- Works retroactively on existing templates`,
manifest: `# manifest.json Configuration
## Basic Structure
\`\`\`json
{
"pages": [
{
"path": "/",
"file": "pages/index.html",
"title": "Home",
"editable": true
}
],
"cmsTemplates": {
"postsIndex": "templates/posts_index.html",
"postsIndexPath": "/blog",
"postsDetail": "templates/posts_detail.html",
"postsDetailPath": "/blog",
"teamIndex": "templates/team_index.html",
"teamIndexPath": "/team"
},
"defaultHeadHtml": "<!-- fonts, meta tags -->"
}
\`\`\`
## CMS Template Configuration
All CMS collections use the **flat format** with four properties per collection:
\`\`\`json
{
"cmsTemplates": {
// Collection: posts
"postsIndex": "templates/posts_index.html",
"postsIndexPath": "/blog",
"postsDetail": "templates/posts_detail.html",
"postsDetailPath": "/blog",
// Collection: team (no detail pages)
"teamIndex": "templates/team_index.html",
"teamIndexPath": "/team",
// Collection: services
"servicesIndex": "pages/services.html",
"servicesIndexPath": "/services",
"servicesDetail": "templates/service-detail.html",
"servicesDetailPath": "/services"
}
}
\`\`\`
**Path Configuration Pattern:**
- \`{collectionSlug}Index\` - Template file for the index/listing page
- \`{collectionSlug}IndexPath\` - URL for the collection list page
- \`{collectionSlug}Detail\` - Template file for the detail page
- \`{collectionSlug}DetailPath\` - Base URL for detail pages (item slug appended)
**IMPORTANT:** The collection slug in the CMS is for the database. URL paths come EXCLUSIVELY from the manifest.
## Page Configuration
Each page needs:
- \`path\` - The URL (must start with /)
- \`file\` - Path to HTML file in pages/
- \`title\` - Page title for CMS
- \`editable\` - Enable inline editing (optional)
## Two Ways to Configure CMS Templates
### Option 1: In manifest.json (during package creation)
Configure \`cmsTemplates\` section as shown above when building the package.
### Option 2: Via Settings UI (after upload)
After uploading a package, go to **Dashboard → Settings → CMS Templates** to configure template mappings.`,
templates: `# Template Creation Guide
## Template Types
1. **Index Templates** - List pages (posts_index.html)
2. **Detail Templates** - Single item pages (posts_detail.html)
3. **Static Pages** - Fixed content with data-edit-key
## Static UI vs Dynamic Content
When converting templates, distinguish between:
### 1. Static UI Elements (Keep as-is)
Logos, icons, decorative backgrounds - these stay as \`/public/\` paths:
\`\`\`html
<!-- KEEP these static -->
<img src="/public/images/logo.png" alt="Company Logo">
<img src="/public/images/icons/arrow.svg" alt="">
\`\`\`
### 2. Dynamic Content (Replace with CMS tokens)
Blog posts, team members, products - replace example content with \`{{#each}}\` loops:
\`\`\`html
<!-- WRONG: Hardcoded example content -->
<article class="post-card">
<img src="/images/example-post.jpg" alt="Example Post">
<h2>My Example Post Title</h2>
<p>This is a sample description...</p>
</article>
<!-- CORRECT: CMS tokens with loop -->
{{#each posts sort="publishedAt" order="desc"}}
<article class="post-card">
{{#if image}}
<img src="{{image}}" alt="{{name}}">
{{/if}}
<h2><a href="{{url}}">{{name}}</a></h2>
<p>{{summary}}</p>
</article>
{{/each}}
\`\`\`
**Rule of thumb:** If it's site branding/design → keep static. If it's content that changes per item → use CMS tokens.
## Index Template Pattern
\`\`\`html
<main class="posts-page">
<h1 data-edit-key="posts-title">Blog</h1>
<div class="posts-grid">
{{#each posts limit=12 sort="publishedAt" order="desc"}}
<article class="post-card">
{{#if image}}
<img src="{{image}}" alt="{{name}}">
{{/if}}
<h2><a href="{{url}}">{{name}}</a></h2>
<p>{{summary}}</p>
{{#if author}}
<span>By {{author.name}}</span>
{{/if}}
</article>
{{/each}}
</div>
{{#unless posts}}
<p>No posts yet. Check back soon!</p>
{{/unless}}
</main>
\`\`\`
## Detail Template Pattern
\`\`\`html
<article class="post-detail">
{{#if image}}
<img src="{{image}}" alt="{{name}}">
{{/if}}
<h1>{{name}}</h1>
{{#if author}}
<div class="author">
By <a href="{{author.url}}">{{author.name}}</a>
</div>
{{/if}}
{{#if publishedAt}}
<time>{{publishedAt}}</time>
{{/if}}
<div class="content">
{{{body}}}
</div>
</article>
\`\`\`
## Team Template Pattern
\`\`\`html
<div class="team-grid">
{{#each team sort="order" order="asc"}}
<div class="team-member">
{{#if photo}}
<img src="{{photo}}" alt="{{name}}">
{{/if}}
<h3>{{name}}</h3>
{{#if role}}
<p class="role">{{role}}</p>
{{/if}}
{{#if bio}}
<div class="bio">{{{bio}}}</div>
{{/if}}
</div>
{{/each}}
</div>
\`\`\`
## Static Page Pattern
\`\`\`html
<main class="about-page">
<h1 data-edit-key="about-title">About Us</h1>
<p data-edit-key="about-intro">Introduction text...</p>
<section>
<h2 data-edit-key="about-mission-title">Our Mission</h2>
<p data-edit-key="about-mission-text">Mission statement...</p>
</section>
</main>
\`\`\`
## Template Must-Haves
- Include complete header (copy from source)
- Include complete footer (copy from source)
- All asset paths use /public/
- Rich text fields use {{{triple braces}}}`,
tokens: `# Token Reference
## Double Braces {{...}} - Escaped Output
Use for text, URLs, images:
\`\`\`html
{{name}}
{{image}}
{{url}}
{{author.name}}
\`\`\`
## Triple Braces {{{...}}} - Raw HTML
Use for rich text content:
\`\`\`html
{{{body}}}
{{{bio}}}
{{{description}}}
\`\`\`
**CRITICAL:** If a field contains HTML (richText type), you MUST use triple braces!
## Loop Syntax
\`\`\`html
{{#each collectionSlug}}
...content for each item...
{{/each}}
\`\`\`
**Modifiers:**
- \`limit=N\` - Maximum items
- \`featured=true\` - Only featured (if collection has boolean "featured" field)
- \`sort="field"\` - Sort by field
- \`order="asc|desc"\` - Sort direction
- \`where="field:value"\` - Filter by field value
## Nested Loops (Hierarchical Content)
For content hierarchies like categories with items, docs sections with pages, or any parent-child relationships.
**Works on ALL page types:** static pages, index pages, AND detail pages.
\`\`\`html
{{#each doc_categories}}
<div class="category-section">
<h3>{{name}}</h3>
<ul>
{{#each doc_pages where="category.slug:{{slug}}"}}
<li><a href="{{url}}">{{name}}</a></li>
{{/each}}
</ul>
</div>
{{/each}}
\`\`\`
**How it works:**
- The outer loop iterates through categories
- \`{{slug}}\` in the where clause references the current category's slug
- The inner loop filters doc_pages to only those matching that category
---
### Using @root. Prefix
Use \`@root.\` to access collections from the root context when you need to be explicit:
\`\`\`html
{{#each doc_categories}}
<div class="category-section">
<h3>{{name}}</h3>
<ul>
{{#each @root.doc_pages where="category.slug:{{slug}}"}}
<li><a href="{{url}}">{{name}}</a></li>
{{/each}}
</ul>
</div>
{{/each}}
\`\`\`
**When to use @root.:**
- When the inner collection name might be ambiguous
- When you want to be explicit about accessing root-level data
- Both \`{{#each collection}}\` and \`{{#each @root.collection}}\` work the same
---
### Common Patterns
\`\`\`html
<!-- Categories with posts -->
{{#each categories}}
<section>
<h2>{{name}}</h2>
{{#each posts where="category.slug:{{slug}}"}}
<article>{{name}}</article>
{{/each}}
</section>
{{/each}}
<!-- Authors with their articles -->
{{#each authors}}
<div class="author-section">
<h3>{{name}}</h3>
{{#each posts where="author.id:{{id}}"}}
<a href="{{url}}">{{name}}</a>
{{/each}}
</div>
{{/each}}
<!-- Documentation sidebar (on any page type) -->
{{#each doc_categories sort="order" order="asc"}}
<div class="sidebar-section">
<h4>{{name}}</h4>
{{#each @root.doc_pages where="category.slug:{{slug}}" sort="order" order="asc"}}
<a href="{{url}}">{{name}}</a>
{{/each}}
</div>
{{/each}}
\`\`\`
**Important:** The \`{{field}}\` in the where clause is resolved from the outer loop's current item.
## Conditionals
\`\`\`html
{{#if fieldName}}
...show if truthy...
{{else}}
...show if falsy...
{{/if}}
{{#unless fieldName}}
...show if falsy...
{{/unless}}
\`\`\`
## Equality Comparisons
\`\`\`html
<!-- Show when equal -->
{{#if (eq fieldA fieldB)}}...{{/if}}
<!-- Show when NOT equal (common for Related Posts) -->
{{#unless (eq slug ../slug)}}
<a href="{{url}}">{{name}}</a>
{{/unless}}
<!-- Compare to literal string -->
{{#eq status "published"}}...{{/eq}}
\`\`\`
## Comparison Helpers (Numeric Comparisons)
\`\`\`html
<!-- Less than: show first 4 items differently -->
{{#if (lt @index 4)}}
<div class="featured">{{name}}</div>
{{else}}
<div class="standard">{{name}}</div>
{{/if}}
<!-- Greater than: skip first item -->
{{#if (gt @index 0)}}
<article>{{name}}</article>
{{/if}}
<!-- Less than or equal -->
{{#if (lte @index 4)}}...{{/if}}
<!-- Greater than or equal -->
{{#if (gte @index 2)}}...{{/if}}
<!-- Not equal (for strings/numbers) -->
{{#if (ne status "draft")}}
<span>Published</span>
{{/if}}
\`\`\`
**Available comparison helpers:**
| Helper | Meaning | Example |
|--------|---------|---------|
| \`lt\` | Less than | \`{{#if (lt @index 4)}}\` |
| \`gt\` | Greater than | \`{{#if (gt @index 0)}}\` |
| \`lte\` | Less than or equal | \`{{#if (lte price 100)}}\` |
| \`gte\` | Greater than or equal | \`{{#if (gte stock 5)}}\` |
| \`ne\` | Not equal | \`{{#if (ne status "draft")}}\` |
| \`eq\` | Equal | \`{{#if (eq category.slug ../slug)}}\` |
**Works with:**
- Loop variables: \`@index\`, \`@length\`
- Numeric fields: \`price\`, \`stock\`, \`order\`
- String fields: \`status\`, \`type\`
- Literal values: \`4\`, \`"published"\`
## Loop Variables
Inside {{#each}} loops, these special variables are available:
**Standalone tokens:**
- \`{{@first}}\` - true for first item
- \`{{@last}}\` - true for last item
- \`{{@index}}\` - zero-based index (0, 1, 2...)
- \`{{@length}}\` - total number of items
**Conditional usage:**
\`\`\`html
{{#each posts}}
{{#if @first}}<div class="featured">{{/if}}
{{#unless @first}}<hr class="separator">{{/unless}}
<article>{{name}}</article>
{{#unless @last}},{{/unless}}
{{/each}}
\`\`\`
| Pattern | Use Case |
|---------|----------|
| \`{{#if @first}}\` | Style first item differently |
| \`{{#unless @first}}\` | Add separator before all but first |
| \`{{#if @last}}\` | Style last item differently |
| \`{{#unless @last}}\` | Add comma/separator after all but last |
**Important:** Loop variables only work inside \`{{#each}}\` blocks. Using them outside a loop will not work.
## Parent Context (\`../\`)
Inside loops, access the parent scope:
\`\`\`html
<!-- Filter posts by current author -->
{{#each posts}}
{{#if (eq author.name ../name)}}
<h3>{{name}}</h3>
{{/if}}
{{/each}}
<!-- Related Posts (exclude current) -->
{{#each posts limit=3}}
{{#unless (eq slug ../slug)}}
<a href="{{url}}">{{name}}</a>
{{/unless}}
{{/each}}
\`\`\`
- \`../name\` - Parent item's name field
- \`../slug\` - Parent item's slug
- \`../fieldName\` - Any field from parent scope
**Use cases:**
- Author pages: filter posts by current author
- Category pages: filter items by current category
- Related items: match based on current page
## Built-in Fields (Available on ALL items)
Every collection item automatically has:
- \`{{name}}\` - Item name/title (REQUIRED)
- \`{{slug}}\` - Item URL slug
- \`{{url}}\` - Full URL to detail page
- \`{{publishedAt}}\` - Publish date
- \`{{createdAt}}\` - Creation date
- \`{{updatedAt}}\` - Last modified date
## Custom Fields
Custom fields are defined when creating collections. The token matches the field slug:
\`\`\`html
{{#each services sort="publishedAt" order="desc"}}
<h2><a href="{{url}}">{{name}}</a></h2>
{{{description}}}
<img src="{{image}}" alt="{{name}}">
<span class="price">\${{price}}</span>
{{/each}}
\`\`\`
## Relation Fields
Access related item data with dot notation:
\`\`\`html
{{#each posts}}
{{#if category}}
<span class="category">{{category.name}}</span>
{{/if}}
{{/each}}
\`\`\`
**Use \`get_tenant_schema\` tool to see exact fields for a specific project.**`,
forms: `# Form Handling
Forms are automatically captured by the CMS.
## Basic Form Setup
\`\`\`html
<form data-form-name="contact" action="/_forms/contact" method="POST">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
\`\`\`
## Required Attributes
- \`data-form-name="xxx"\` on the form element
- \`action="/_forms/xxx"\` pointing to the form endpoint (MUST match data-form-name)
- \`method="POST"\` for form submission
- \`name="fieldName"\` on each input
## Form Handler Script
Add to your JavaScript (typically /public/js/main.js):
\`\`\`javascript
document.querySelectorAll('form[data-form-name]').forEach(form => {
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formName = form.dataset.formName || 'general';
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// IMPORTANT: Endpoint is /_forms/{formName}
const response = await fetch('/_forms/' + formName, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
form.reset();
alert(form.dataset.successMessage || 'Thank you!');
}
});
});
\`\`\`
**CRITICAL:** The form endpoint is \`/_forms/{formName}\` - NOT \`/api/forms/submit\`
---
## CRITICAL: Remove Original Form Handlers
**If the source site has JavaScript that handles form submissions, you MUST modify or remove it!**
The original site's JavaScript often does:
- \`e.preventDefault()\` - blocks the actual form submission
- Shows a "fake" success toast/message - but data goes nowhere
- Never actually submits to a server
### What to Look For in Original JS
\`\`\`javascript
// PROBLEM: This blocks real submissions!
form.addEventListener('submit', (e) => {
e.preventDefault();
// ... validation ...
showToast('Message sent!'); // FAKE! Data not saved!
});
\`\`\`
### What You Must Do
**Option A: Use Native Form Submission (Simplest)**
\`\`\`html
<form data-form-name="contact" action="/_forms/contact" method="POST">
<input type="text" name="name" required>
<input type="email" name="email" required>
<button type="submit">Send</button>
</form>
\`\`\`
Then **remove** the original JavaScript form handler entirely.
**Option B: Keep JS but POST to Fast Mode**
Replace the original handler with one that actually submits:
\`\`\`javascript
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formName = form.dataset.formName;
const response = await fetch('/_forms/' + formName, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(Object.fromEntries(new FormData(form)))
});
if (response.ok) {
showToast('Message sent!'); // NOW it's real!
form.reset();
}
});
\`\`\`
---
## Naming Conventions
- Contact form → \`contact\`
- Newsletter → \`newsletter\`
- Quote request → \`quote-request\`
- Application → \`job-application\`
## Important
**Tenant context is automatic** - the system handles associating forms with the correct site.`,
assets: `# Asset Path Rules
## Static Assets (CSS, JS, Fonts, Site Images)
**ALL static asset paths must use /public/ prefix**
### HTML Examples
\`\`\`html
<!-- CSS -->
<link rel="stylesheet" href="/public/css/style.css">
<!-- JavaScript -->
<script src="/public/js/main.js"></script>
<!-- Static Images (logos, icons, decorative) -->
<img src="/public/images/logo.png" alt="Logo">
<!-- Favicon -->
<link rel="icon" href="/public/images/favicon.ico">
\`\`\`
### CSS Examples
\`\`\`css
/* Correct */
background-image: url('/public/images/hero.jpg');
src: url('/public/fonts/custom.woff2');
/* Wrong */
background-image: url('../images/hero.jpg');
background-image: url('images/hero.jpg');
\`\`\`
### Conversion Table
| Original | Converted |
|----------|-----------|
| css/style.css | /public/css/style.css |
| ../css/style.css | /public/css/style.css |
| ./images/logo.png | /public/images/logo.png |
| /images/logo.png | /public/images/logo.png |
---
## CMS Content Images (CRITICAL)
**CMS-managed images are different from static assets. They are uploaded by users through the CMS dashboard and served from a CDN.**
### Two Types of Images
1. **Static assets** (\`/public/images/\`) - Site logos, icons, decorative elements bundled in the package
2. **CMS images** (\`{{fieldName}}\`) - User-uploaded content images managed through the dashboard
### CMS Image Handling
**ALWAYS use CMS tokens for content images - NEVER hardcode URLs**
\`\`\`html
<!-- CORRECT: CMS token for dynamic content -->
{{#if image}}
<img src="{{image}}" alt="{{name}}">
{{/if}}
<!-- WRONG: Hardcoded example content -->
<img src="/images/example-post.jpg" alt="Example Post">
\`\`\`
### Image Field Examples
\`\`\`html
<!-- Post images -->
<img src="{{image}}" alt="{{name}}">
<img src="{{thumbnail}}" alt="{{name}}">
<!-- Team member photos -->
<img src="{{photo}}" alt="{{name}}">
<!-- Custom collection images (field slug becomes token) -->
<img src="{{heroImage}}" alt="{{name}}">
<img src="{{productImage}}" alt="{{name}}">
\`\`\`
### Common Mistake: Mixing Static and CMS Images
\`\`\`html
<!-- WRONG: Don't mix hardcoded images with CMS content -->
{{#each products}}
<img src="/images/product-placeholder.jpg" alt="Product"> <!-- BAD -->
<h2>{{name}}</h2>
{{/each}}
<!-- CORRECT: All content comes from CMS -->
{{#each products}}
{{#if image}}
<img src="{{image}}" alt="{{name}}">
{{/if}}
<h2>{{name}}</h2>
{{/each}}
\`\`\`
---
## External Assets
Keep external URLs unchanged:
\`\`\`html
<!-- These stay the same -->
<link href="https://fonts.googleapis.com/..." rel="stylesheet">
<script src="https://cdn.example.com/lib.js"></script>
\`\`\``,
checklist: `# Pre-Submission Checklist
## Structure
- [ ] manifest.json at package root
- [ ] Static pages in pages/ folder
- [ ] Assets in public/ folder
- [ ] Templates in templates/ (or pages/)
## SEO (CRITICAL)
- [ ] NO \`<title>\` tags in HTML
- [ ] NO \`<meta name="description">\` tags
- [ ] NO \`<meta property="og:*">\` tags
- [ ] NO \`<meta name="twitter:*">\` tags
- [ ] NO \`<link rel="icon">\` tags
- [ ] Include \`<!-- SEO managed by Fast Mode -->\` comment in head
## manifest.json
- [ ] All static pages listed in pages array
- [ ] Each page has path, file, title
- [ ] CMS templates configured with {slug}Index, {slug}IndexPath, {slug}Detail, {slug}DetailPath
- [ ] Paths match original site URLs
## Assets
- [ ] ALL CSS uses /public/ paths
- [ ] ALL JS uses /public/ paths
- [ ] ALL images use /public/ paths
- [ ] CSS background-image urls updated
- [ ] Font paths in CSS updated
## Templates
- [ ] Templates include header/footer
- [ ] {{#each}} loops have {{/each}}
- [ ] {{#if}} conditions have {{/if}}
- [ ] {{#eq}} comparisons have {{/eq}}
- [ ] Rich text uses {{{triple braces}}}
- [ ] Parent refs (../) only inside loops
- [ ] Correct field names used
- [ ] **Static UI images** (logos, icons) use \`/public/\` paths
- [ ] **Content images** use CMS tokens
- [ ] **No hardcoded example content** - dynamic items use \`{{#each}}\` loops
- [ ] Index pages iterate over collections, not static example cards
## Static Pages
- [ ] data-edit-key on editable text
- [ ] Keys are unique and descriptive
- [ ] Forms have data-form-name
## Final Validation
- [ ] Run validate_manifest with manifest.json
- [ ] Run validate_template on each template
- [ ] Run validate_package for full check`,
common_mistakes: `# COMMON MISTAKES TO AVOID
**IMPORTANT:** AI agents frequently make these mistakes during website conversion. Read this section carefully before building a package.
---
## 1. WRONG Manifest Format
AIs often use a nested object format that Fast Mode does NOT support.
**WRONG (will NOT work):**
\`\`\`json
{
"collections": {
"posts": {
"indexPath": "/blog",
"indexFile": "collections/posts/index.html",
"detailPath": "/blog/:slug",
"detailFile": "collections/posts/detail.html"
}
}
}
\`\`\`
**CORRECT (use this):**
\`\`\`json
{
"cmsTemplates": {
"postsIndex": "templates/posts_index.html",
"postsDetail": "templates/posts_detail.html",
"postsIndexPath": "/blog",
"postsDetailPath": "/blog"
}
}
\`\`\`
Key differences:
- Use \`cmsTemplates\`, NOT \`collections\`
- Use FLAT keys: \`{slug}Index\`, \`{slug}Detail\`, \`{slug}IndexPath\`, \`{slug}DetailPath\`
- DO NOT nest objects inside collection names
---
## 2. WRONG Asset Folder
Fast Mode serves static assets from \`/public/\`, NOT \`/assets/\`.
**WRONG:**
\`\`\`
assets/
├── css/style.css ← Will return 404!
└── js/main.js ← Will return 404!
\`\`\`
**CORRECT:**
\`\`\`
public/
├── css/style.css ← Reference as /public/css/style.css in HTML
└── js/main.js ← Reference as /public/js/main.js in HTML
\`\`\`
If CSS/JS isn't loading, check that:
- Files are in \`public/\` folder (NOT \`assets/\`)
- HTML references use \`/public/css/style.css\` (WITH the /public/ prefix)
---
## 3. WRONG Template Folder
CMS templates go in \`templates/\`, NOT \`collections/\`.
**WRONG:**
\`\`\`
collections/
├── posts/
│ ├── index.html
│ └── detail.html
\`\`\`
**CORRECT:**
\`\`\`
templates/
├── posts_index.html
└── posts_detail.html
\`\`\`
Template naming: \`{collection}_index.html\` and \`{collection}_detail.html\`
---
## 4. MISSING Inline Editing
Without \`data-edit-key\`, users cannot edit text in the visual editor!
**WRONG (no inline editing):**
\`\`\`html
<h1>{{page_title}}</h1>
<p>{{intro_text}}</p>
\`\`\`
**CORRECT (enables inline editing):**
\`\`\`html
<h1 data-edit-key="page_title">{{page_title}}</h1>
<p data-edit-key="intro_text">{{intro_text}}</p>
\`\`\`
Every editable text element in static pages should have a unique \`data-edit-key\`.
---
## 5. Template URL Mismatches
Make sure links in templates match the manifest's path configuration.
**WRONG:**
\`\`\`json
// manifest.json
"postsDetailPath": "/blog"
\`\`\`
\`\`\`html
<!-- template -->
<a href="/posts/{{slug}}">Read more</a> ← Wrong path!
\`\`\`
**CORRECT:**
\`\`\`json
// manifest.json
"postsDetailPath": "/blog"
\`\`\`
\`\`\`html
<!-- template -->
<a href="{{url}}">Read more</a> ← Uses built-in {{url}} token
\`\`\`
**TIP:** Use \`{{url}}\` instead of hardcoding paths - it automatically uses the manifest config.
---
## 6. Field Naming Conventions
Use \`snake_case\` for field slugs, not camelCase.
**WRONG:**
\`\`\`
heroImage
authorBio
publishedDate
\`\`\`
**CORRECT:**
\`\`\`
hero_image
author_bio
published_date
\`\`\`
---
## 7. Inlining CSS/JS (Last Resort)
If external CSS/JS absolutely won't load, you can inline them in manifest.json:
\`\`\`json
{
"defaultHeadHtml": "<style>/* all CSS here */</style>",
"defaultBodyEndHtml": "<script>/* all JS here */</script>"
}
\`\`\`
**But try fixing the /public/ folder first** - inlining makes updates harder.
---
## 8. Keeping Original Form Handlers
The source site often has JavaScript that "handles" form submissions with fake success messages.
**WRONG (original site's JS):**
\`\`\`javascript
form.addEventListener('submit', (e) => {
e.preventDefault();
showToast('Message sent!'); // FAKE! Data goes nowhere!
});
\`\`\`
**CORRECT (submit to Fast Mode):**
\`\`\`html
<form data-form-name="contact" action="/_forms/contact" method="POST">
<!-- inputs -->
</form>
\`\`\`
Then REMOVE the original JavaScript handler, or replace it with one that actually POSTs to \`/_forms/{formName}\`.
**See \`get_conversion_guide(section: "forms")\` for detailed form handling instructions.**
---
## Validation Workflow
Before deploying, ALWAYS run:
1. \`validate_manifest\` - Catches format errors
2. \`validate_template\` - Catches token errors
3. \`validate_package\` - Full structure check
These tools will catch most of the above mistakes before deployment.`,
full: '', // Will be assembled from all sections
};
/**
* Returns the conversion guide, either full or a specific section
*/
export async function getConversionGuide(section: Section): Promise<string> {
if (section === 'full') {
return `# Complete Website Conversion Guide
---
## WORKFLOW OVERVIEW (Follow This Order)
| Step | Action | Tools to Use |
|------|--------|--------------|
| 1. SETUP | Call list_projects → Ask user for project → Get projectId | \`list_projects\`, \`create_site\` |
| 2. SCHEMA | Get existing schema OR plan new collections | \`get_tenant_schema\`, \`sync_schema\` |
| 3. ANALYZE | Map URLs, identify collections, document assets | (manual) |
| 4. BUILD | Create manifest.json, pages/, templates/, public/ | \`get_example\` |
| 5. VALIDATE | Check all files before deploying | \`validate_manifest\`, \`validate_template\`, \`validate_package\` |
| 6. DEPLOY | Upload package to project | \`deploy_package\` |
| 7. CONTENT | Optionally manage CMS content | \`create_cms_item\`, \`list_cms_items\`, \`update_cms_item\` |
**DO NOT skip steps. You MUST have a projectId before you can deploy.**
## CONTENT MANAGEMENT TOOLS
After deployment, you can manage CMS content directly:
| Tool | Description |
|------|-------------|
| \`create_cms_item\` | Create a new item (blog post, team member, etc.) |
| \`list_cms_items\` | List items in a collection with optional filters |
| \`get_cms_item\` | Get a single item by slug |
| \`update_cms_item\` | Update an existing item |
| \`delete_cms_item\` | Delete an item (**requires user confirmation**) |
**⚠️ DELETE SAFETY:** Never delete without asking the user first. The \`delete_cms_item\` tool requires \`confirmDelete: true\` which you should only set after explicit user permission.
---
${SECTIONS.first_steps}
---
${SECTIONS.common_mistakes}
---
${SECTIONS.analysis}
---
${SECTIONS.structure}
---
${SECTIONS.seo}
---
${SECTIONS.manifest}
---
${SECTIONS.templates}
---
${SECTIONS.tokens}
---
${SECTIONS.forms}
---
${SECTIONS.assets}
---
${SECTIONS.checklist}
---
## Need More Help?
Use these MCP tools during conversion:
- \`get_field_types\` - Get available field types for sync_schema
- \`get_tenant_schema\` - Get exact collections and fields for a specific project
- \`get_example\` - Get code examples for specific patterns
- \`validate_manifest\` - Check your manifest.json
- \`validate_template\` - Check template token usage
- \`validate_package\` - Full package validation
- \`generate_sample_items\` - Create placeholder items after creating new collections (handles relation dependencies automatically)
**Validate as you work** - don't wait until the end!`;
}
return SECTIONS[section] || `Section not found: ${section}`;
}