# Schema and Seed Files

The seed file (`seed/seed.json`) defines the site's entire schema and optional demo content. It's applied on first run or via `npx emdash seed seed/seed.json`.

## Seed File Structure

```json
{
	"$schema": "https://emdashcms.com/seed.schema.json",
	"version": "1",
	"meta": {
		"name": "My Site",
		"description": "A description of this site",
		"author": "Author Name"
	},
	"settings": { ... },
	"collections": [ ... ],
	"taxonomies": [ ... ],
	"menus": [ ... ],
	"widgetAreas": [ ... ],
	"sections": [ ... ],
	"bylines": [ ... ],
	"content": { ... }
}
```

## Collections

Collections define content types. Each collection becomes a database table (`ec_{slug}`).

```json
{
	"slug": "posts",
	"label": "Posts",
	"labelSingular": "Post",
	"supports": ["drafts", "revisions", "search", "seo"],
	"commentsEnabled": true,
	"fields": [ ... ]
}
```

### Collection Supports

| Support     | Description               |
| ----------- | ------------------------- |
| `drafts`    | Draft/published workflow  |
| `revisions` | Revision history          |
| `search`    | Full-text search indexing |
| `seo`       | SEO meta fields in admin  |

### Slug Rules

- Lowercase alphanumeric + underscores: `/^[a-z][a-z0-9_]*$/`
- Max 63 characters
- Cannot conflict with reserved slugs

## Field Types

| Type           | Column type | Runtime shape                         | Notes                        |
| -------------- | ----------- | ------------------------------------- | ---------------------------- |
| `string`       | TEXT        | `string`                              | Single line text             |
| `text`         | TEXT        | `string`                              | Multi-line text (textarea)   |
| `number`       | REAL        | `number`                              | Floating point               |
| `integer`      | INTEGER     | `number`                              | Whole numbers                |
| `boolean`      | INTEGER     | `boolean`                             | Stored as 0/1                |
| `datetime`     | TEXT        | `Date`                                | ISO 8601 string in DB        |
| `image`        | TEXT        | `{ id, src?, alt?, width?, height? }` | **Object, not a string**     |
| `reference`    | TEXT        | `string` (ID)                         | Reference to another entry   |
| `portableText` | JSON        | `PortableTextBlock[]`                 | Rich text as structured JSON |
| `json`         | JSON        | `any`                                 | Arbitrary JSON data          |

### Field Definition

```json
{
	"slug": "title",
	"label": "Title",
	"type": "string",
	"required": true,
	"searchable": true
}
```

Fields can have:

- `slug` (required) -- field identifier
- `label` (required) -- display label in admin
- `type` (required) -- one of the types above
- `required` -- validation
- `searchable` -- include in full-text search index

### Common Field Patterns

**Blog post:**

```json
"fields": [
	{ "slug": "title", "label": "Title", "type": "string", "required": true, "searchable": true },
	{ "slug": "featured_image", "label": "Featured Image", "type": "image" },
	{ "slug": "content", "label": "Content", "type": "portableText", "searchable": true },
	{ "slug": "excerpt", "label": "Excerpt", "type": "text" }
]
```

**Portfolio project:**

```json
"fields": [
	{ "slug": "title", "label": "Title", "type": "string", "required": true, "searchable": true },
	{ "slug": "featured_image", "label": "Featured Image", "type": "image", "required": true },
	{ "slug": "client", "label": "Client", "type": "string" },
	{ "slug": "year", "label": "Year", "type": "string" },
	{ "slug": "summary", "label": "Summary", "type": "text", "searchable": true },
	{ "slug": "content", "label": "Content", "type": "portableText", "searchable": true },
	{ "slug": "gallery", "label": "Gallery", "type": "json" },
	{ "slug": "url", "label": "Project URL", "type": "string" }
]
```

**Page (minimal):**

```json
"fields": [
	{ "slug": "title", "label": "Title", "type": "string", "required": true, "searchable": true },
	{ "slug": "content", "label": "Content", "type": "portableText", "searchable": true }
]
```

## Taxonomies

Taxonomies are tag/category systems attached to collections.

```json
{
	"name": "category",
	"label": "Categories",
	"labelSingular": "Category",
	"hierarchical": true,
	"collections": ["posts"],
	"terms": [
		{ "slug": "development", "label": "Development" },
		{ "slug": "design", "label": "Design" }
	]
}
```

- `hierarchical: true` -- tree structure (like WordPress categories)
- `hierarchical: false` -- flat list (like WordPress tags)
- `collections` -- which collections this taxonomy applies to
- `terms` -- pre-defined terms to create

## Menus

Navigation menus, managed from the admin UI.

```json
{
	"name": "primary",
	"label": "Primary Navigation",
	"items": [
		{ "type": "custom", "label": "Home", "url": "/" },
		{ "type": "custom", "label": "About", "url": "/pages/about" },
		{ "type": "custom", "label": "Posts", "url": "/posts" }
	]
}
```

Menu item types:

- `custom` -- arbitrary URL
- Content references are resolved at render time

## Widget Areas

Named regions where editors can add configurable widgets.

```json
{
	"name": "sidebar",
	"label": "Sidebar",
	"description": "Widget area displayed on single post pages",
	"widgets": [
		{
			"type": "component",
			"componentId": "core:search",
			"title": "Search"
		},
		{
			"type": "component",
			"componentId": "core:categories",
			"title": "Categories"
		},
		{
			"type": "component",
			"componentId": "core:tags",
			"title": "Tags"
		},
		{
			"type": "component",
			"componentId": "core:recent-posts",
			"title": "Recent Posts",
			"settings": { "count": 5, "showDate": true }
		},
		{
			"type": "component",
			"componentId": "core:archives",
			"title": "Archives",
			"settings": { "type": "monthly", "limit": 6 }
		},
		{
			"type": "content",
			"title": "About",
			"content": [
				{
					"_type": "block",
					"style": "normal",
					"children": [{ "_type": "span", "text": "Some rich text content." }]
				}
			]
		}
	]
}
```

### Widget types

| Type        | Description               | Key fields                |
| ----------- | ------------------------- | ------------------------- |
| `content`   | Rich text (Portable Text) | `content`                 |
| `menu`      | Navigation menu           | `menuName`                |
| `component` | Core or custom component  | `componentId`, `settings` |

### Core widget components

- `core:search` -- search form
- `core:categories` -- category list with counts
- `core:tags` -- tag cloud
- `core:recent-posts` -- latest posts list
- `core:archives` -- monthly archive links

## Sections (Reusable Blocks)

Reusable content blocks that editors can insert via `/section` slash command in the editor.

```json
{
	"slug": "newsletter-signup",
	"title": "Newsletter Signup",
	"description": "A call-to-action block for newsletter subscriptions",
	"keywords": ["newsletter", "subscribe", "email", "cta"],
	"source": "theme",
	"content": [
		{
			"_type": "block",
			"style": "h3",
			"children": [{ "_type": "span", "text": "Stay in the loop" }]
		},
		{
			"_type": "block",
			"style": "normal",
			"children": [{ "_type": "span", "text": "Get notified when new posts are published." }]
		}
	]
}
```

## Bylines

Named author profiles, independent of user accounts.

```json
{
	"id": "byline-editorial",
	"slug": "emdash-editorial",
	"displayName": "EmDash Editorial"
}
```

Guest bylines:

```json
{
	"id": "byline-guest",
	"slug": "guest-contributor",
	"displayName": "Guest Contributor",
	"isGuest": true
}
```

## Settings

Site-wide settings:

```json
"settings": {
	"title": "My Blog",
	"tagline": "Thoughts on building for the web"
}
```

Available keys: `title`, `tagline`, `logo`, `favicon`, `social`, `timezone`, `dateFormat`.

## Content

Sample content organized by collection slug:

```json
"content": {
	"posts": [
		{
			"id": "post-1",
			"slug": "hello-world",
			"status": "published",
			"data": {
				"title": "Hello World",
				"excerpt": "My first post.",
				"featured_image": {
					"$media": {
						"url": "https://images.unsplash.com/photo-xxx?w=1200&h=800&fit=crop",
						"alt": "Description of image",
						"filename": "hello-world.jpg"
					}
				},
				"content": [
					{
						"_type": "block",
						"style": "normal",
						"children": [{ "_type": "span", "text": "This is the body text." }]
					}
				]
			},
			"bylines": [
				{ "byline": "byline-editorial" }
			],
			"taxonomies": {
				"category": ["development"],
				"tag": ["webdev", "opinion"]
			}
		}
	],
	"pages": [
		{
			"id": "about",
			"slug": "about",
			"status": "published",
			"data": {
				"title": "About",
				"content": [
					{
						"_type": "block",
						"style": "normal",
						"children": [{ "_type": "span", "text": "About this site." }]
					}
				]
			}
		}
	]
}
```

### Media references in seed content

Use `$media` for image fields -- EmDash downloads and stores the image:

```json
"featured_image": {
	"$media": {
		"url": "https://images.unsplash.com/photo-xxx?w=1200&h=800&fit=crop",
		"alt": "Description",
		"filename": "my-image.jpg"
	}
}
```

For external images without downloading:

```json
"featured_image": "https://images.unsplash.com/photo-xxx?w=1200"
```

### Reference fields in seed content

Use `$ref:id` format to reference other entries:

```json
"author": "$ref:byline-editorial"
```

### Portable Text in seed content

Content fields of type `portableText` are arrays of blocks:

```json
[
	{
		"_type": "block",
		"style": "normal",
		"children": [{ "_type": "span", "text": "A paragraph." }]
	},
	{
		"_type": "block",
		"style": "h2",
		"children": [{ "_type": "span", "text": "A heading" }]
	},
	{
		"_type": "block",
		"style": "blockquote",
		"children": [{ "_type": "span", "text": "A quote." }]
	}
]
```

Inline marks (bold, italic, links):

```json
{
	"_type": "block",
	"style": "normal",
	"children": [
		{ "_type": "span", "text": "This is " },
		{ "_type": "span", "text": "bold", "marks": ["strong"] },
		{ "_type": "span", "text": " and " },
		{ "_type": "span", "text": "italic", "marks": ["em"] }
	]
}
```

Block styles: `normal`, `h1`-`h6`, `blockquote`.

### Draft content

Set `"status": "draft"` to create unpublished content:

```json
{
	"id": "post-draft",
	"slug": "work-in-progress",
	"status": "draft",
	"data": { ... }
}
```

## Validation

```bash
npx emdash seed seed/seed.json --validate
```

Catches:

- Image fields with raw URLs (should use `$media`)
- Reference fields with raw IDs (should use `$ref:id`)
- PortableText not an array or missing `_type`
- Type mismatches (string vs number, etc.)

## Applying Seeds

```bash
npx emdash seed seed/seed.json              # Apply with content
npx emdash seed seed/seed.json --no-content  # Schema only (no sample content)
```

## Exporting Seeds

```bash
npx emdash export-seed                      # Schema only
npx emdash export-seed --with-content       # Schema + all content
npx emdash export-seed --with-content=posts,pages  # Specific collections
```
