first commit

This commit is contained in:
Matt Kane
2026-04-01 10:44:22 +01:00
commit 43fcb9a131
1789 changed files with 395041 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
# Phase 1: Discovery & Reference Capture
Before writing any code, gather comprehensive reference materials from the demo site.
## 1.0 Create Discovery Folder
Create a `discovery/` folder in your theme directory to store all reference materials:
```
discovery/
├── screenshots/ # Reference screenshots from demo site
│ ├── homepage.png
│ ├── single-post.png
│ ├── archive.png
│ ├── category.png
│ ├── page.png
│ └── 404.png
├── images/ # Sample images downloaded for seed content
│ ├── featured-1.jpg
│ ├── featured-2.jpg
│ └── hero.jpg
└── notes.md # Design decisions and observations
```
The `notes.md` file should capture:
- Color values extracted from the demo
- Font families and sizes observed
- Layout patterns (header style, sidebar position, footer columns)
- Special components or interactions to recreate
- Anything that might be forgotten between sessions
## 1.1 Identify All Page Types
Identify the URL of the demo site for the WordPress theme you are converting. For wordpress.org themes, this is usually wp-themes.com/theme-name/. For other themes, use the "Live Preview" link. This may show it inside a frame; if so, ignore the frame and focus on the theme's actual content.
Use the agent-browser to explore the demo site to find every distinct page type:
- **Homepage** - Often has unique layout (hero, featured posts, etc.)
- **Blog/Archive** - Post listing page
- **Single Post** - Individual blog post with content
- **Page** - Static page (About, Contact, etc.)
- **Category/Tag Archive** - Taxonomy listing pages
- **Search Results** - If the theme has custom search styling
- **404 Page** - Error page styling
Use agent-browser to navigate the demo and discover pages:
```bash
agent-browser open https://demo-site.com
# Click around to find different page types
# Check the navigation menu for page links
# Look for "View all posts" or category links
```
## 1.2 Screenshot All Page Types
Capture full-page screenshots of each page type to `discovery/screenshots/`:
```bash
# Homepage
agent-browser open https://demo-site.com
agent-browser screenshot discovery/screenshots/homepage.png --full
# Single post (find a post with featured image and good content)
agent-browser open https://demo-site.com/sample-post/
agent-browser screenshot discovery/screenshots/single-post.png --full
# Blog archive
agent-browser open https://demo-site.com/blog/
agent-browser screenshot discovery/screenshots/archive.png --full
# Category page
agent-browser open https://demo-site.com/category/news/
agent-browser screenshot discovery/screenshots/category.png --full
# Static page
agent-browser open https://demo-site.com/about/
agent-browser screenshot discovery/screenshots/page.png --full
# 404 page
agent-browser open https://demo-site.com/nonexistent-page-xyz/
agent-browser screenshot discovery/screenshots/404.png --full
```
## 1.3 Download Sample Images
If the theme is open source (GPL), download sample images from the demo to `discovery/images/`. This ensures visual consistency when comparing.
```bash
# Find featured images in demo posts
agent-browser eval "Array.from(document.querySelectorAll('article img')).map(i => i.src)"
# Download images for seed content
curl -o discovery/images/featured-1.jpg "https://demo-site.com/wp-content/uploads/photo1.jpg"
curl -o discovery/images/featured-2.jpg "https://demo-site.com/wp-content/uploads/photo2.jpg"
```
For premium themes or when images aren't freely available, use Unsplash images that match the demo's visual style (same aspect ratios, similar subjects).
## 1.4 Document Page Structure
For each page type, document observations in `discovery/notes.md`:
- Header style (sticky? transparent? logo position?)
- Sidebar presence and position
- Footer layout (columns? widgets?)
- Special components (hero sections, CTAs, etc.)
- Color values (use browser DevTools color picker)
- Font families and sizes
- Spacing patterns
This inventory guides which templates and components you need to build, and preserves details that might be forgotten between sessions.
## Theme Source Discovery
### WordPress.org Themes
For themes on wordpress.org (e.g., `https://wordpress.org/themes/theme-name/`):
1. **Demo/Preview**: Click "Preview" button or visit `https://wp-themes.com/theme-name/`
2. **Source Download**: The "Download" button provides a ZIP, or use:
```bash
curl -O https://downloads.wordpress.org/theme/theme-name.zip
unzip theme-name.zip
```
3. **Theme Info**: The page includes author, version, tags, and description
### GitHub-Hosted Themes
1. **Source**: Clone or download the repository
2. **Demo**: Check README for demo URL, or look for `Demo:` in theme description
3. **Documentation**: Usually in README or `/docs` folder
### ThemeForest / Premium Themes
1. **Demo**: Use the "Live Preview" button on the product page
2. **Source**: Requires purchase - ask the user to provide the unzipped theme files
3. **Documentation**: Usually included in the download or linked from the product page
### Auto-Discovery
When given only a theme URL or name, derive URLs yourself:
1. Fetch the listing page to extract demo URL, download URL, and theme info
2. Download the source (if freely available)
3. Open the demo in agent-browser
Don't ask the user for URLs you can derive yourself.

View File

@@ -0,0 +1,122 @@
# Phase 2: Design Extraction
Extract design tokens from the WordPress theme source and live demo.
## 2.1 Analyze the Live Site
Use `agent-browser` to extract computed styles:
```bash
agent-browser eval "(() => {
const body = getComputedStyle(document.body);
const header = document.querySelector('header, .site-header');
return JSON.stringify({
body: {
fontFamily: body.fontFamily,
fontSize: body.fontSize,
color: body.color,
background: body.backgroundColor,
},
header: header ? {
background: getComputedStyle(header).backgroundColor,
height: getComputedStyle(header).height,
} : null,
}, null, 2);
})()"
```
## 2.2 Extract Design Tokens
Read the theme's CSS files. Look for:
```
style.css # Main stylesheet (has theme header)
assets/css/ # Additional stylesheets
theme.json # Block themes (WP 5.9+) - structured design tokens
```
### CSS Variable Mapping
| WP Pattern | EmDash Variable |
| ---------------- | ------------------ |
| Body font family | `--font-body` |
| Heading font | `--font-heading` |
| Primary color | `--color-primary` |
| Background | `--color-base` |
| Text color | `--color-contrast` |
| Content width | `--content-width` |
### Block Theme (theme.json)
Block themes store design tokens in `theme.json`:
```json
{
"settings": {
"color": {
"palette": [{ "slug": "primary", "color": "#0073aa", "name": "Primary" }]
},
"typography": {
"fontFamilies": [{ "fontFamily": "'Open Sans', sans-serif", "slug": "body" }]
},
"layout": {
"contentSize": "650px",
"wideSize": "1200px"
}
}
}
```
## 2.3 Create Base Layout
Create `src/layouts/Base.astro` with:
- Extracted CSS variables in `:root`
- Header/footer structure matching WP theme
- Font loading (Google Fonts or local)
- Responsive breakpoints
### CSS Variables Template
```css
:root {
/* Colors */
--color-base: #ffffff;
--color-contrast: #1a1a1a;
--color-primary: #0073aa;
--color-accent: #ff6b35;
--color-muted: #6b7280;
--color-border: #e5e7eb;
/* Typography */
--font-body: system-ui, sans-serif;
--font-heading: Georgia, serif;
/* Font sizes */
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.25rem;
--text-5xl: clamp(2.5rem, 5vw, 3rem);
/* Spacing */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--space-16: 4rem;
--space-24: 6rem;
/* Layout */
--content-width: 720px;
--wide-width: 1200px;
--header-height: 80px;
}
```
See `references/design-extraction.md` for detailed extraction techniques.

View File

@@ -0,0 +1,114 @@
# Phase 3: Template Conversion
Convert WordPress PHP templates to Astro components.
## 3.1 Analyze Theme Structure
Read `functions.php` to identify:
- `register_nav_menu()` → EmDash menus
- `register_sidebar()` → EmDash widget areas
- `add_theme_support()` → Features (thumbnails, formats, etc.)
- `register_post_type()` → Collections
- `register_taxonomy()` → EmDash taxonomy defs
- `add_shortcode()` → Portable Text blocks
## 3.2 Template Mapping
| WP Template | Astro Route |
| -------------- | ----------------------------------- |
| `index.php` | `src/pages/index.astro` |
| `single.php` | `src/pages/posts/[slug].astro` |
| `page.php` | `src/pages/pages/[slug].astro` |
| `archive.php` | `src/pages/posts/index.astro` |
| `category.php` | `src/pages/categories/[slug].astro` |
| `tag.php` | `src/pages/tags/[slug].astro` |
| `search.php` | `src/pages/search.astro` |
| `404.php` | `src/pages/404.astro` |
| `header.php` | Component in layout |
| `footer.php` | Component in layout |
## 3.3 Convert Templates
### The Loop → getEmDashCollection
```php
// WordPress
<?php while (have_posts()) : the_post(); ?>
<h2><?php the_title(); ?></h2>
<?php endwhile; ?>
```
```astro
---
// Astro/EmDash
import { getEmDashCollection } from "emdash";
const { entries: posts } = await getEmDashCollection("posts");
---
{posts.map(post => <h2>{post.data.title}</h2>)}
```
### Single Post → getEmDashEntry
```php
// WordPress
<?php the_content(); ?>
```
```astro
---
// Astro/EmDash
import { getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";
const { entry: post } = await getEmDashEntry("posts", Astro.params.slug);
---
{post && <PortableText value={post.data.content} />}
```
## 3.4 Page Templates
WordPress themes often register page templates (Full Width, Sidebar, Landing Page, etc.). In EmDash, this is a `select` field on the pages collection:
1. Add a `template` select field to the pages collection with the theme's template names as options (e.g. "Default", "Full Width", "Landing Page")
2. Create an Astro layout component for each template in `src/layouts/`
3. Map the field value to a layout component in the page route:
```astro
---
// src/pages/pages/[slug].astro
import { getEmDashEntry } from "emdash";
import PageDefault from "../../layouts/PageDefault.astro";
import PageFullWidth from "../../layouts/PageFullWidth.astro";
const { slug } = Astro.params;
const { entry: page } = await getEmDashEntry("pages", slug!);
if (!page) return Astro.redirect("/404");
const layouts = {
"Default": PageDefault,
"Full Width": PageFullWidth,
};
const Layout = layouts[page.data.template as keyof typeof layouts] ?? PageDefault;
---
<Layout page={page} />
```
Use human-readable option names (matching what the WP theme displayed) since these appear in the admin dropdown.
## Important: Server-Rendered Pages
**Never use `getStaticPaths()` or `export const prerender = true` for EmDash content pages.** Content changes at runtime, so pages must be server-rendered.
```astro
---
// CORRECT - server-rendered
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug!);
if (!post) {
return Astro.redirect("/404");
}
---
```
See `references/template-patterns.md` for more conversion patterns.

View File

@@ -0,0 +1,147 @@
# Phase 4: Dynamic Features
Implement CMS-driven features: site settings, menus, taxonomies, and widgets.
## 4.1 Site Settings
Map WordPress customizer values to EmDash site settings:
| WP Customizer Setting | EmDash Site Setting |
| --------------------- | --------------------- |
| Site Title | `title` |
| Tagline | `tagline` |
| Site Icon | `favicon` |
| Custom Logo | `logo` |
| Posts per page | `postsPerPage` |
| Date format | `dateFormat` |
```astro
---
import { getSiteSettings } from "emdash";
const settings = await getSiteSettings();
---
<header>
{settings.logo ? (
<img src={settings.logo.url} alt={settings.title} />
) : (
<span class="site-title">{settings.title}</span>
)}
{settings.tagline && <p class="tagline">{settings.tagline}</p>}
</header>
```
## 4.2 Navigation Menus
Identify menus in `functions.php`:
```php
register_nav_menus([
'primary' => 'Primary Navigation',
'footer' => 'Footer Links',
]);
```
Use in templates:
```astro
---
import { getMenu } from "emdash";
const primaryNav = await getMenu("primary");
---
<nav class="primary-nav">
{primaryNav && (
<ul>
{primaryNav.items.map(item => (
<li>
<a href={item.url} aria-current={Astro.url.pathname === item.url ? 'page' : undefined}>
{item.label}
</a>
{item.children.length > 0 && (
<ul class="submenu">
{item.children.map(child => (
<li><a href={child.url}>{child.label}</a></li>
))}
</ul>
)}
</li>
))}
</ul>
)}
</nav>
```
## 4.3 Taxonomies
Identify taxonomies in theme:
```php
register_taxonomy('genre', 'book', [
'label' => 'Genres',
'hierarchical' => true,
]);
```
Use in templates:
```astro
---
import { getTaxonomyTerms, getEntryTerms, getEntriesByTerm } from "emdash";
// Get all terms
const genres = await getTaxonomyTerms("genre");
// Get terms for a specific entry
const bookGenres = await getEntryTerms("books", book.id, "genre");
// Get entries by term
const fictionBooks = await getEntriesByTerm("books", "genre", "fiction");
---
```
## 4.4 Widget Areas
Identify sidebars in theme:
```php
register_sidebar([
'name' => 'Main Sidebar',
'id' => 'sidebar-1',
]);
```
Use in templates:
```astro
---
import { getWidgetArea } from "emdash";
import { PortableText } from "emdash/ui";
const sidebar = await getWidgetArea("sidebar");
---
{sidebar && sidebar.widgets.length > 0 && (
<aside class="sidebar">
{sidebar.widgets.map(widget => (
<div class="widget">
{widget.title && <h3>{widget.title}</h3>}
{widget.type === "content" && <PortableText value={widget.content} />}
</div>
))}
</aside>
)}
```
## 4.5 Widget Components
Map WP widgets to Astro components:
| WP Widget | EmDash Component |
| ---------------- | ------------------- |
| Recent Posts | `core:recent-posts` |
| Categories | `core:categories` |
| Tag Cloud | `core:tags` |
| Search | `core:search` |
| Archives | `core:archives` |
| Text/Custom HTML | `type: 'content'` |
| Navigation Menu | `type: 'menu'` |
See `references/emdash-api.md` for full API reference.

View File

@@ -0,0 +1,206 @@
# Phase 5: Create Seed File
Combine all theme features into a seed file with sample content.
## 5.1 Image Strategy
**Use the same images you downloaded in Phase 1** for visual consistency.
1. **Open source themes (GPL)**: Use exact images from the demo
2. **Premium themes**: Use Unsplash images matching the demo's style
3. **Local images**: Reference with `file:./` prefix:
```json
"featured_image": {
"$media": {
"url": "file:./discovery/images/hero.jpg",
"alt": "Hero image"
}
}
```
## 5.2 Validate Before Applying
```bash
# Validate without applying
emdash seed --validate
```
The validator catches common mistakes:
| Check | Error |
| ---------------------------- | ------------------------- |
| Image using raw URL | "must use $media syntax" |
| Reference using raw ID | "must use $ref:id syntax" |
| PortableText not an array | "expected array" |
| PortableText missing `_type` | "missing required \_type" |
### Common Fixes
```json
// WRONG - raw URL
"featured_image": "https://example.com/photo.jpg"
// CORRECT - $media syntax
"featured_image": {
"$media": {
"url": "https://example.com/photo.jpg",
"alt": "Description"
}
}
// WRONG - unknown byline reference
"bylines": [{ "byline": "author-1" }]
// CORRECT - define root bylines[] and reference byline IDs
"bylines": [{ "byline": "byline-author-1" }]
```
## 5.3 Seed File Structure
```json
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {
"name": "Theme Name",
"description": "Ported from WordPress theme"
},
"settings": {
"title": "Site Title",
"tagline": "Site tagline"
},
"collections": [
{
"slug": "posts",
"label": "Posts",
"fields": [
{ "slug": "title", "type": "string", "required": true },
{ "slug": "content", "type": "portableText" },
{ "slug": "featured_image", "type": "image" }
]
}
],
"taxonomies": [
{
"name": "categories",
"label": "Categories",
"hierarchical": true,
"collections": ["posts"],
"terms": [{ "slug": "news", "label": "News" }]
}
],
"bylines": [
{
"id": "byline-author-1",
"slug": "theme-author",
"displayName": "Theme Author"
}
],
"menus": [
{
"name": "primary",
"label": "Primary Navigation",
"items": [
{ "type": "custom", "label": "Home", "url": "/" },
{ "type": "custom", "label": "Blog", "url": "/posts" }
]
}
],
"content": {
"posts": [
{
"id": "post-1",
"slug": "hello-world",
"status": "published",
"bylines": [{ "byline": "byline-author-1" }],
"data": {
"title": "Hello World",
"content": [{ "_type": "block", "children": [{ "text": "Welcome!" }] }],
"featured_image": {
"$media": {
"url": "file:./discovery/images/featured-1.jpg",
"alt": "Featured image"
}
}
}
}
]
}
}
```
## 5.4 Adding Sections (Reusable Blocks)
If the theme has reusable block patterns, add them as sections:
```json
{
"sections": [
{
"slug": "hero-centered",
"title": "Centered Hero",
"description": "Full-width hero with centered heading and CTA button",
"keywords": ["hero", "banner", "header", "landing"],
"content": [
{
"_type": "block",
"style": "h1",
"children": [{ "_type": "span", "text": "Welcome to Our Site" }]
},
{
"_type": "block",
"children": [{ "_type": "span", "text": "Your compelling tagline goes here." }]
}
]
},
{
"slug": "newsletter-cta",
"title": "Newsletter Signup",
"keywords": ["newsletter", "subscribe", "email", "signup"],
"content": [
{
"_type": "block",
"style": "h3",
"children": [{ "_type": "span", "text": "Subscribe to our newsletter" }]
},
{
"_type": "block",
"children": [
{ "_type": "span", "text": "Get the latest updates delivered to your inbox." }
]
}
]
}
]
}
```
Editors can insert these sections using the `/section` slash command in the rich text editor.
## 5.5 Add Redirects for Legacy WordPress URLs
Include redirects in the seed when the WordPress theme used different URL structures.
```json
{
"redirects": [
{ "source": "/?p=123", "destination": "/hello-world" },
{ "source": "/2024/01/hello-world", "destination": "/hello-world", "type": 301 },
{ "source": "/category/news", "destination": "/categories/news" }
]
}
```
Rules:
- `source` and `destination` must be local paths (start with `/`)
- Supported `type` values are `301`, `302`, `307`, `308`
- Redirects are idempotent during seeding (existing `source` entries are skipped)
See `references/emdash-api.md` for full seed file schema.

View File

@@ -0,0 +1,97 @@
# Phase 6: Verify & Iterate
Seed content, run the dev server, compare screenshots, and iterate until pages match.
## 6.1 Apply the Seed
```bash
# Validate first
emdash seed --validate
# Apply seed with content
emdash seed
```
## 6.2 Start Dev Server
Kill any existing server first:
```bash
lsof -ti:4321 | xargs kill -9 2>/dev/null || true
pnpm dev
```
## 6.3 Screenshot Each Page Type
Screenshot every page type you captured in Phase 1:
```bash
# Homepage
agent-browser open http://localhost:4321
agent-browser screenshot output/homepage.png --full
# Single post
agent-browser open http://localhost:4321/posts/hello-world
agent-browser screenshot output/single-post.png --full
# Blog archive
agent-browser open http://localhost:4321/posts
agent-browser screenshot output/archive.png --full
# Category page
agent-browser open http://localhost:4321/categories/news
agent-browser screenshot output/category.png --full
# Static page
agent-browser open http://localhost:4321/pages/about
agent-browser screenshot output/page.png --full
# 404 page
agent-browser open http://localhost:4321/nonexistent
agent-browser screenshot output/404.png --full
```
## 6.4 Compare & Iterate
Compare each screenshot pair:
| Page Type | Reference | Output |
| ----------- | --------------------------------------- | ------------------------ |
| Homepage | `discovery/screenshots/homepage.png` | `output/homepage.png` |
| Single Post | `discovery/screenshots/single-post.png` | `output/single-post.png` |
| Archive | `discovery/screenshots/archive.png` | `output/archive.png` |
| Category | `discovery/screenshots/category.png` | `output/category.png` |
| Page | `discovery/screenshots/page.png` | `output/page.png` |
| 404 | `discovery/screenshots/404.png` | `output/404.png` |
For each page, identify differences and fix:
1. **Layout** - CSS grid/flexbox, content width, spacing
2. **Typography** - Font family, sizes, line height
3. **Colors** - Background, text, links, borders
4. **Components** - Headers, footers, cards, buttons
5. **Responsive** - Check mobile viewport too
Re-screenshot after each round of fixes.
**Don't aim for pixel-perfect** - aim for "same design language."
## 6.5 Final Build Test
```bash
pnpm run build
```
## License Compliance
WordPress themes are GPL-licensed. Every ported theme needs:
1. **LICENSE** - GPL-2.0 text (download with curl, don't output directly):
```bash
curl -o LICENSE https://raw.githubusercontent.com/spdx/license-list-data/main/text/GPL-2.0-or-later.txt
```
2. **README.md** - Credits to original theme
3. **package.json** - `"license": "GPL-2.0-or-later"`