Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 43 additions & 21 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,34 @@ Quick reference for AI assistants working with DevMotion, an animation editor bu
## Core Architecture Patterns

### 1. **Svelte 5 Runes** - Use exclusively, no legacy stores

```typescript
let count = $state(0); // Reactive state
let doubled = $derived(count * 2); // Computed values
$effect(() => { /* side effects */ });
let count = $state(0); // Reactive state
let doubled = $derived(count * 2); // Computed values
$effect(() => {
/* side effects */
});
```

### 2. **Zod-First Types** - Single source of truth

```typescript
// Define schema first, infer types
export const LayerSchema = z.object({ /* ... */ });
export const LayerSchema = z.object({
/* ... */
});
export type Layer = z.infer<typeof LayerSchema>;
```

### 3. **Shared Mutations** - Web app and MCP server share logic

```typescript
// src/lib/ai/mutations.ts - pure functions that modify Project objects
export function mutateCreateLayer(ctx: MutationContext, input) { }
export function mutateCreateLayer(ctx: MutationContext, input) {}
```

### 4. **Layer Registry** - Dynamic layer type system

```typescript
// Register layer components with validation schemas
registerLayer('my-layer', Component, PropsSchema);
Expand All @@ -36,34 +44,37 @@ registerLayer('my-layer', Component, PropsSchema);

## Key File Locations

| What | Where |
|------|-------|
| **Global state** | `src/lib/stores/project.svelte.ts` (ProjectStore class) |
| **Type definitions** | `src/lib/schemas/animation.ts` (Zod schemas) |
| **Shared mutations** | `src/lib/ai/mutations.ts` (web + MCP) |
| **Animation engine** | `src/lib/engine/interpolation.ts` |
| **Layer components** | `src/lib/layers/components/` |
| **MCP server** | `src/routes/mcp/+server.ts` |
| **Database schemas** | `src/lib/server/db/schema/` |
| What | Where |
| -------------------- | ------------------------------------------------------- |
| **Global state** | `src/lib/stores/project.svelte.ts` (ProjectStore class) |
| **Type definitions** | `src/lib/schemas/animation.ts` (Zod schemas) |
| **Shared mutations** | `src/lib/ai/mutations.ts` (web + MCP) |
| **Animation engine** | `src/lib/engine/interpolation.ts` |
| **Layer components** | `src/lib/layers/components/` |
| **MCP server** | `src/routes/mcp/+server.ts` |
| **Database schemas** | `src/lib/server/db/schema/` |

---

## Essential Conventions

### Code Style

- **Package manager**: `pnpm` only (not npm/yarn)
- **Naming**: `kebab-case.ts`, `PascalCase.svelte`, `camelCase` functions
- **Imports**: External → SvelteKit → Internal → Relative
- **Store files**: `name.svelte.ts` suffix for rune-based stores
- **Prettier**: 2 spaces, single quotes, no trailing commas, 100 line width

### TypeScript

- Strict mode always on
- Prefer type inference over explicit types
- No `any` - use `unknown` or proper types
- Prefix unused vars with `_`

### Project Structure

```
src/lib/
├── stores/ # Global state (Svelte 5 runes)
Expand Down Expand Up @@ -94,6 +105,7 @@ src/lib/
```

### Animatable Properties

- Built-in: `position.x/y/z`, `scale.x/y/z`, `rotation.x/y/z`, `opacity`, `color`
- Custom: `props.fontSize`, `props.fill`, etc.

Expand All @@ -102,22 +114,25 @@ src/lib/
## Common Tasks

### Adding a Layer Type

1. Create component in `src/lib/layers/components/`
2. Export props schema: `export const MyLayerPropsSchema = z.object({ ... })`
3. Call `registerLayer('my-layer', MyComponent, MyLayerPropsSchema)`

### Modifying Project Store

```typescript
// src/lib/stores/project.svelte.ts
class ProjectStore {
myMethod() {
this.project = { ...this.project, /* changes */ };
this.project = { ...this.project /* changes */ };
// Auto-saves to localStorage (debounced)
}
}
```

### Database Changes

```bash
# 1. Define schema in src/lib/server/db/schema/
# 2. Export from schema/index.ts
Expand All @@ -126,14 +141,19 @@ pnpm db:push # Apply to database
```

### Adding MCP Tools

```typescript
// src/routes/mcp/+server.ts
server.registerTool('tool_name', {
description: '...',
inputSchema: z.object({ projectId: z.string(), /* ... */ })
}, async ({ projectId, ...params }) => {
// Use shared mutations from src/lib/ai/mutations.ts
});
server.registerTool(
'tool_name',
{
description: '...',
inputSchema: z.object({ projectId: z.string() /* ... */ })
},
async ({ projectId, ...params }) => {
// Use shared mutations from src/lib/ai/mutations.ts
}
);
```

---
Expand All @@ -160,6 +180,7 @@ pnpm db:studio # Database GUI
## Critical Patterns to Follow

### ✅ DO

- Use Svelte 5 runes (`$state`, `$derived`, `$effect`)
- Define Zod schemas first, infer TypeScript types
- Use `projectStore` for global animation state
Expand All @@ -168,6 +189,7 @@ pnpm db:studio # Database GUI
- Use `pnpm` exclusively

### ❌ DON'T

- Use legacy Svelte stores (`writable`, `derived`)
- Duplicate type definitions (Zod + TypeScript)
- Mutate state directly without reactivity
Expand Down
28 changes: 28 additions & 0 deletions Prompt demo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Prompt demo:
generate a demo video for DevMotion.
Here some info

## ✨ Features

### Animation Studio

- **Timeline Editor** – Full keyframe control with smooth interpolation and easing curves
- **Layer Management** – Text, shapes, and images with complete customization
- **Interactive Canvas** – Zoom, pan, grid controls, and real-time preview
- **Export** – High-quality MP4 videos with no watermarks or file limits

### AI-Powered Workflow

- **Intelligent Suggestions** – Get smart animation and layer recommendations
- **Auto-Generation** – Create motion sequences automatically
- **MCP Integration** – Use DevMotion tools directly in Claude via Model Context Protocol

### Project Management

- **Save & Load** – Store projects in JSON format for future editing
- **Database Storage** – Persistent project storage with PostgreSQL
- **Authentication** – Secure user accounts with Better Auth

Logo info
Family name: Montserrat Thin
PostScript name: Montserrat-SemiBold
37 changes: 22 additions & 15 deletions src/lib/ai/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ function resolveLayerId(
return null;
}

/**
* Build an error string listing available layers so the AI can self-correct.
*/
function layerNotFoundError(project: ProjectData, ref: string): string {
if (project.layers.length === 0) {
return `Layer "${ref}" not found. The project has no layers — create one first.`;
}
const available = project.layers.map((l) => `"${l.name}" (id: ${l.id})`).join(', ');
return (
`Layer "${ref}" not found. ` +
`Use layer_N for layers you just created in this conversation, ` +
`or reference existing layers by id/name. ` +
`Available layers: ${available}`
);
}

// ============================================
// Mutations
// ============================================
Expand Down Expand Up @@ -155,11 +171,8 @@ export function mutateAnimateLayer(
const resolvedId = resolveLayerId(ctx.project, input.layerId, ctx.layerIdMap);

if (!resolvedId) {
return {
success: false,
message: `Layer not found: ${input.layerId}`,
error: 'Use layer_0, layer_1, etc. for recently created layers, or an existing layer ID/name.'
};
const errMsg = layerNotFoundError(ctx.project, input.layerId);
return { success: false, message: errMsg, error: errMsg };
}

const layer = ctx.project.layers.find((l) => l.id === resolvedId);
Expand Down Expand Up @@ -224,11 +237,8 @@ export function mutateAnimateLayer(
export function mutateEditLayer(ctx: MutationContext, input: EditLayerInput): EditLayerOutput {
const resolvedId = resolveLayerId(ctx.project, input.layerId, ctx.layerIdMap);
if (!resolvedId) {
return {
success: false,
message: `Layer not found: ${input.layerId}`,
error: 'Invalid layer reference'
};
const errMsg = layerNotFoundError(ctx.project, input.layerId);
return { success: false, message: errMsg, error: errMsg };
}

const layerIndex = ctx.project.layers.findIndex((l) => l.id === resolvedId);
Expand Down Expand Up @@ -289,11 +299,8 @@ export function mutateRemoveLayer(
): RemoveLayerOutput {
const resolvedId = resolveLayerId(ctx.project, input.layerId, ctx.layerIdMap);
if (!resolvedId) {
return {
success: false,
message: `Layer not found: ${input.layerId}`,
error: 'Invalid layer reference'
};
const errMsg = layerNotFoundError(ctx.project, input.layerId);
return { success: false, message: errMsg, error: errMsg };
}

const index = ctx.project.layers.findIndex((l) => l.id === resolvedId);
Expand Down
Loading