Code Mirror

Other

Mirror frontend and backend code across the stack. Invoke when user wants to generate backend from frontend code, generate frontend from backend code, sync existing code across stack, or scaffold full-stack features from one side.

Install

openclaw skills install code-mirror

Code Mirror - Frontend-Backend Code Mirroring

Code Mirror analyzes existing frontend or backend code and generates the corresponding counterpart on the other side. It understands the structural semantics — API contracts, data models, routing, validation, state management — and produces idiomatic, production-ready code that perfectly mirrors the source.


When to Use This Skill

Activate Code Mirror when the user:

  • Provides frontend code and asks for the backend implementation
  • Provides backend code and asks for the frontend integration
  • Wants to scaffold a full-stack feature from one side of the stack
  • Asks to sync types or API calls between existing frontend and backend
  • Says "mirror", "bridge", "generate counterpart", "sync types", "scaffold API", "generate client"
  • Has a backend change and wants frontend types updated (or vice versa)

Core Workflow

Phase 1: Identify Source and Target

First, understand what the user has and what they want.

Source side detection (read the code the user provides):

CluesSide
useState, useEffect, useCallback, axios, fetch, onClick, ref, component, template, v-for, v-if, ngIf, ngFor, queryClient, useQuery, useMutation, .tsx, .jsx, .vue, .svelteFrontend
@app.route, router., req.body, res.json, @Controller, @GetMapping, @PostMapping, @RestController, Model.find, Schema, prisma, entity, repository, BaseModel, Depends, HTTPException, .go, .java, .py with fastapi/djangoBackend

If ambiguous, ask: "Is this frontend or backend code? What framework are you using?"

Target stack detection (infer from existing project or ask the user):

Check the user's project files in the workspace:

FileStack
package.jsonCheck dependencies for React, Vue, Angular, Express, NestJS
requirements.txt / pyproject.tomlPython with FastAPI, Django, Flask
go.modGo with Gin, Echo, net/http
pom.xml / build.gradleJava with Spring Boot
Cargo.tomlRust with Actix, Rocket
*.csprojC# with ASP.NET Core

If no existing project, ask: "What stack do you want me to generate for? (e.g., Express + TypeScript, FastAPI, Go Gin, Spring Boot)"

Phase 2: Analyze Source Code

Read and deeply understand the source code. Extract the following based on side:

When analyzing FRONTEND code, extract:

1. API Calls — all endpoints the frontend hits

// For each API call, record:
// - HTTP method (GET, POST, PUT, PATCH, DELETE)
// - URL path (/api/users, /api/users/:id)
// - Request body shape (from the data being sent)
// - Response shape (from how the response is used)
// - Query parameters (from URL params)
// - Path parameters (from template strings like `/users/${id}`)

axios.get('/api/users')                           // GET /api/users → User[]
axios.post('/api/users', { name, email })         // POST /api/users → User
axios.put(`/api/users/${id}`, { name })           // PUT /api/users/:id → User
axios.delete(`/api/users/${id}`)                  // DELETE /api/users/:id
fetch('/api/users?page=1&limit=20')               // GET with query params

2. Data Models — TypeScript interfaces, PropTypes, Zod schemas

// Extract: name, all fields, field types, optional/required markers, enums, default values

interface User {
  id: string;                    // required string
  name: string;                  // required string
  email: string;                 // required string
  role: 'admin' | 'user';        // enum
  age?: number;                  // optional number
  createdAt: Date;               // date
}

3. Form Validation Rules — Zod, Yup, or manual validation

// Extract: field name, validation type (required, email, min, max, pattern, custom)

const schema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().min(18).optional(),
  password: z.string().min(8),
});

4. State Management Patterns (optional, for complex apps)

// Extract: store structure, state shape, actions that modify state

const useUserStore = create((set) => ({
  users: [],
  loading: false,
  error: null,
  fetchUsers: async () => { ... },
  addUser: (user) => set((state) => ({ users: [...state.users, user] })),
}));

When analyzing BACKEND code, extract:

1. Route Definitions — every endpoint the backend exposes

# For each route, record:
# - Path (including path parameters like /users/{user_id})
# - HTTP method
# - Request body schema (from Pydantic model or DTO)
# - Query parameters (from function arguments)
# - Response model (from response_model or return type)
# - Status codes (especially 201, 204)

@router.get("/users")                              # GET /users → List[UserOut]
@router.get("/users/{user_id}")                    # GET /users/:id → UserOut
@router.post("/users")                             # POST /users → UserOut
@router.put("/users/{user_id}")                    # PUT /users/:id → UserOut
@router.delete("/users/{user_id}", status_code=204) # DELETE /users/:id → 204

2. Data Models / Schemas — database models and API schemas

# Extract: name, fields, types, required/optional, validators, relationships

class User(BaseModel):
    id: str
    name: str = Field(..., min_length=1, max_length=100)
    email: EmailStr
    role: Literal["admin", "user"] = "user"
    age: Optional[int] = Field(None, ge=0, le=120)
    created_at: datetime

3. Business Logic (for more complete generation)

# Extract: operations, side effects (email sending, logging), error handling patterns

def create_user(db: Session, user: UserCreate):
    # hash password
    # check if email exists
    # send welcome email
    # return created user
    pass

4. Authentication/Authorization

# Extract: auth method (JWT, session, API key), protected routes, role requirements

@router.get("/admin/users", dependencies=[Depends(require_admin)])
async def get_admin_users(...):
    pass

Phase 3: Generate Target Code

Generate code following the target stack's best practices. Always produce code that fits naturally into the existing project structure.

Frontend → Backend (generate in this order):

  1. Data models — ORM schema (Prisma, Mongoose, SQLAlchemy, TypeORM)
  2. Validation schemas — matching frontend form rules (Zod, Pydantic, Joi, class-validator)
  3. Route definitions — covering all endpoints frontend calls
  4. Controller/Handler functions — implementing each route
  5. Service layer — if the project uses services
  6. Middleware — auth, error handling, CORS, logging
  7. Database configuration — connection setup, migrations

Backend → Frontend (generate in this order):

  1. TypeScript types — from backend response/request models
  2. API client — typed fetch/axios functions for all endpoints
  3. React Query hooks or Vue composables or Angular services
  4. State management — stores/slices for complex data flows
  5. UI components — forms, lists, detail views
  6. Client-side routes — if generating full pages

Phase 4: Validate and Output

After generation, verify consistency and present the code clearly.

Consistency Checklist:

  • Every frontend API call has a corresponding backend route
  • Every backend route is called by the frontend (if generating from backend)
  • Request/response types match on both sides
  • Validation rules are consistent (same required fields, same formats)
  • Error responses have consistent shapes (status codes, error message fields)
  • Authentication headers/tokens are handled correctly
  • No placeholder comments like // TODO: implement

Output Format:

For each generated file, show:

  1. File path as a heading — so user knows exactly where to put it
  2. Brief explanation (optional, for complex files)
  3. Complete file content — no snippets, no placeholders

Example:

I'll generate the Express backend for your React todo form.

`src/models/Todo.ts`
```typescript
// full file content

src/validators/todoValidator.ts

// full file content

src/routes/todos.ts

// full file content

The routes match the frontend calls: POST /api/todos expects { title, description } and returns a todo with an id. All validation rules from your frontend form are mirrored in the backend Zod schema.

Stack-Specific Generation Templates

For Express + TypeScript Backend

Model (Mongoose):

import mongoose, { Schema, Document } from 'mongoose';

export interface I{{Resource}} extends Document {
  {{#each fields}}
  {{name}}: {{mongooseType}};
  {{/each}}
  createdAt: Date;
  updatedAt: Date;
}

const {{resource}}Schema = new Schema<I{{Resource}}>(
  {
    {{#each fields}}
    {{name}}: { 
      type: {{mongooseType}}, 
      required: {{required}}{{#if unique}}, unique: true{{/if}}
    },
    {{/each}}
  },
  { timestamps: true }
);

export const {{Resource}}Model = mongoose.model<I{{Resource}}>('{{Resource}}', {{resource}}Schema);

Validator (Zod):

import { z } from 'zod';

export const create{{Resource}}Schema = z.object({
  {{#each fields}}
  {{name}}: z.{{zodType}}(){{#if required}}{{else}}.optional(){{/if}}{{#if validation}}.{{validation}}{{/if}},
  {{/each}}
});

export const update{{Resource}}Schema = create{{Resource}}Schema.partial();

export type Create{{Resource}}Input = z.infer<typeof create{{Resource}}Schema>;
export type Update{{Resource}}Input = z.infer<typeof update{{Resource}}Schema>;

Route:

import { Router } from 'express';
import { {{Resource}}Model } from '../models/{{Resource}}';
import { validate } from '../middleware/validate';
import { create{{Resource}}Schema, update{{Resource}}Schema } from '../validators/{{resource}}Validator';
import { authMiddleware } from '../middleware/auth';

const router = Router();

// Apply auth middleware to all routes (if needed)
router.use(authMiddleware);

router.get('/', async (req, res) => {
  const items = await {{Resource}}Model.find();
  res.json(items);
});

router.get('/:id', async (req, res) => {
  const item = await {{Resource}}Model.findById(req.params.id);
  if (!item) return res.status(404).json({ error: '{{Resource}} not found' });
  res.json(item);
});

router.post('/', validate(create{{Resource}}Schema), async (req, res) => {
  const item = await {{Resource}}Model.create(req.body);
  res.status(201).json(item);
});

router.put('/:id', validate(update{{Resource}}Schema), async (req, res) => {
  const item = await {{Resource}}Model.findByIdAndUpdate(req.params.id, req.body, { new: true });
  if (!item) return res.status(404).json({ error: '{{Resource}} not found' });
  res.json(item);
});

router.delete('/:id', async (req, res) => {
  const item = await {{Resource}}Model.findByIdAndDelete(req.params.id);
  if (!item) return res.status(404).json({ error: '{{Resource}} not found' });
  res.status(204).end();
});

export default router;

For FastAPI Backend (Python)

Schema (Pydantic):

from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
from enum import Enum

class {{Resource}}Role(str, Enum):
    {{#each enums}}
    {{value}} = "{{value}}"
    {{/each}}

class {{Resource}}Base(BaseModel):
    {{#each fields}}
    {{name}}: {{pythonType}} = Field(
        {{#if required}}...{{else}}None{{/if}},
        {{#if validation}}...{{/if}}
    )
    {{/each}}

class {{Resource}}Create({{Resource}}Base):
    {{#each requiredFields}}
    {{name}}: {{pythonType}}
    {{/each}}

class {{Resource}}Update(BaseModel):
    {{#each fields}}
    {{name}}: Optional[{{pythonType}}] = None
    {{/each}}

class {{Resource}}Out({{Resource}}Base):
    id: str
    created_at: datetime
    updated_at: Optional[datetime] = None

    class Config:
        from_attributes = True

Router:

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List

from app.database import get_db
from app.schemas.{{resource}} import {{Resource}}Create, {{Resource}}Update, {{Resource}}Out
from app import crud

router = APIRouter(prefix="/{{resource}}s", tags=["{{resource}}s"])

@router.get("", response_model=List[{{Resource}}Out])
async def list_{{resource}}s(
    skip: int = Query(0, ge=0),
    limit: int = Query(100, ge=1, le=100),
    db: Session = Depends(get_db),
):
    return crud.{{resource}}.get_multi(db, skip=skip, limit=limit)

@router.get("/{{item_id}}", response_model={{Resource}}Out)
async def get_{{resource}}({{item_id}}: str, db: Session = Depends(get_db)):
    item = crud.{{resource}}.get(db, id={{item_id}})
    if not item:
        raise HTTPException(status_code=404, detail="{{Resource}} not found")
    return item

@router.post("", response_model={{Resource}}Out, status_code=201)
async def create_{{resource}}(
    item: {{Resource}}Create,
    db: Session = Depends(get_db),
):
    return crud.{{resource}}.create(db, obj_in=item)

@router.put("/{{item_id}}", response_model={{Resource}}Out)
async def update_{{resource}}(
    {{item_id}}: str,
    item: {{Resource}}Update,
    db: Session = Depends(get_db),
):
    db_item = crud.{{resource}}.get(db, id={{item_id}})
    if not db_item:
        raise HTTPException(status_code=404, detail="{{Resource}} not found")
    return crud.{{resource}}.update(db, db_obj=db_item, obj_in=item)

@router.delete("/{{item_id}}", status_code=204)
async def delete_{{resource}}({{item_id}}: str, db: Session = Depends(get_db)):
    item = crud.{{resource}}.get(db, id={{item_id}})
    if not item:
        raise HTTPException(status_code=404, detail="{{Resource}} not found")
    crud.{{resource}}.remove(db, id={{item_id}})

For React + TypeScript Frontend

Types:

// types/{{resource}}.ts
export interface {{Resource}} {
  id: string;
  {{#each fields}}
  {{name}}: {{tsType}};
  {{/each}}
  createdAt: string;
  updatedAt: string;
}

export interface Create{{Resource}}Input {
  {{#each requiredFields}}
  {{name}}: {{tsType}};
  {{/each}}
  {{#each optionalFields}}
  {{name}}?: {{tsType}};
  {{/each}}
}

export interface Update{{Resource}}Input {
  {{#each fields}}
  {{name}}?: {{tsType}};
  {{/each}}
}

API Client:

// api/{{resource}}.ts
import axios from 'axios';
import type { {{Resource}}, Create{{Resource}}Input, Update{{Resource}}Input } from '../types/{{resource}}';

const BASE_URL = '/api/{{resource}}s';

export const {{resource}}Api = {
  list: () => axios.get<{{Resource}}[]>(BASE_URL),
  
  getById: (id: string) => axios.get<{{Resource}}>(`${BASE_URL}/${id}`),
  
  create: (data: Create{{Resource}}Input) => axios.post<{{Resource}}>(BASE_URL, data),
  
  update: (id: string, data: Update{{Resource}}Input) => 
    axios.put<{{Resource}}>(`${BASE_URL}/${id}`, data),
  
  delete: (id: string) => axios.delete(`${BASE_URL}/${id}`),
};

React Query Hooks:

// hooks/use{{Resource}}s.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { {{resource}}Api } from '../api/{{resource}}';
import type { Create{{Resource}}Input, Update{{Resource}}Input } from '../types/{{resource}}';

export function use{{Resource}}s() {
  return useQuery({
    queryKey: ['{{resource}}s'],
    queryFn: async () => {
      const { data } = await {{resource}}Api.list();
      return data;
    },
  });
}

export function use{{Resource}}(id: string) {
  return useQuery({
    queryKey: ['{{resource}}s', id],
    queryFn: async () => {
      const { data } = await {{resource}}Api.getById(id);
      return data;
    },
    enabled: !!id,
  });
}

export function useCreate{{Resource}}() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (input: Create{{Resource}}Input) => {{resource}}Api.create(input),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['{{resource}}s'] });
    },
  });
}

export function useUpdate{{Resource}}() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({ id, ...input }: Update{{Resource}}Input & { id: string }) => 
      {{resource}}Api.update(id, input),
    onSuccess: (_, { id }) => {
      queryClient.invalidateQueries({ queryKey: ['{{resource}}s'] });
      queryClient.invalidateQueries({ queryKey: ['{{resource}}s', id] });
    },
  });
}

export function useDelete{{Resource}}() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (id: string) => {{resource}}Api.delete(id),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['{{resource}}s'] });
    },
  });
}

For Vue 3 + TypeScript Frontend

Composable:

// composables/use{{Resource}}s.ts
import { ref, readonly } from 'vue';
import axios from 'axios';
import type { {{Resource}}, Create{{Resource}}Input, Update{{Resource}}Input } from '../types/{{resource}}';

export function use{{Resource}}s() {
  const items = ref<{{Resource}}[]>([]);
  const loading = ref(false);
  const error = ref<string | null>(null);
  const currentItem = ref<{{Resource}} | null>(null);

  const fetchAll = async () => {
    loading.value = true;
    error.value = null;
    try {
      const { data } = await axios.get<{{Resource}}[]>('/api/{{resource}}s');
      items.value = data;
    } catch (e) {
      error.value = (e as Error).message;
    } finally {
      loading.value = false;
    }
  };

  const fetchOne = async (id: string) => {
    loading.value = true;
    error.value = null;
    try {
      const { data } = await axios.get<{{Resource}}>(`/api/{{resource}}s/${id}`);
      currentItem.value = data;
      return data;
    } catch (e) {
      error.value = (e as Error).message;
      throw e;
    } finally {
      loading.value = false;
    }
  };

  const create = async (input: Create{{Resource}}Input) => {
    const { data } = await axios.post<{{Resource}}>('/api/{{resource}}s', input);
    items.value.push(data);
    return data;
  };

  const update = async (id: string, input: Update{{Resource}}Input) => {
    const { data } = await axios.put<{{Resource}}>(`/api/{{resource}}s/${id}`, input);
    const index = items.value.findIndex(i => i.id === id);
    if (index !== -1) items.value[index] = data;
    if (currentItem.value?.id === id) currentItem.value = data;
    return data;
  };

  const remove = async (id: string) => {
    await axios.delete(`/api/{{resource}}s/${id}`);
    items.value = items.value.filter(i => i.id !== id);
    if (currentItem.value?.id === id) currentItem.value = null;
  };

  return {
    items: readonly(items),
    currentItem: readonly(currentItem),
    loading: readonly(loading),
    error: readonly(error),
    fetchAll,
    fetchOne,
    create,
    update,
    remove,
  };
}

Special Scenarios

Scenario 1: Sync Existing Code (Diff Mode)

User says: "My FastAPI backend added a phone field to User. Update my React frontend."

Process:

  1. Read backend model to find the new phone field
  2. Read frontend types to see current state
  3. Identify the diff: what's missing in frontend
  4. Generate only the changes needed:
    • Add phone: string to User interface
    • Add phone to CreateUserInput/UpdateUserInput
    • Update API client if needed (no change for GET, need to include in POST/PUT)
    • Update form component to include phone field
  5. Output each changed file with the specific changes, not full files (unless user asks for full)

Output format for sync:

I found the new phone field in your backend User model. Here are the frontend updates needed:

src/types/user.ts — add phone field:

  export interface User {
    id: string;
    name: string;
    email: string;
+   phone: string;
    role: 'admin' | 'user';
    createdAt: string;
  }

src/api/users.ts — no change needed (API calls remain the same)

src/components/UserForm.tsx — add phone input:

  const schema = z.object({
    name: z.string().min(1),
    email: z.string().email(),
+   phone: z.string().regex(/^\+?[1-9]\d{1,14}$/),
  });

Scenario 2: One Side Only, Generate Full Counterpart

User says: "Here's my React form for creating a product. Generate the Express backend."

Process:

  1. Extract from React:
    • API endpoint: POST /api/products
    • Request body fields: name, price, description
    • Validation rules: name required, price positive number
  2. Generate complete backend:
    • Product model with extracted fields
    • Zod validator with same rules
    • POST route handler
    • (Optional) GET list route for completeness
  3. Output complete, runnable files

Scenario 3: Full-Stack Feature from Description

User says: "I need a todo app with users. Generate React frontend and Express backend."

Process:

  1. Design schema from description:
    • User: id, name, email, password (hashed)
    • Todo: id, title, completed, userId, createdAt
  2. Generate backend in order:
    • Models (User, Todo)
    • Validators (create user, update todo, etc.)
    • Routes (auth, todos CRUD)
    • Auth middleware (JWT)
  3. Generate frontend in order:
    • Types (User, Todo)
    • API client (auth calls, todo calls)
    • Auth context/hooks
    • React Query hooks
    • Components (Login, Register, TodoList, TodoForm)
    • Routes (protected routes)
  4. Ensure all endpoints match between frontend API calls and backend routes

Type Mapping Reference

When converting between languages, use these mappings:

TypeScript ↔ Python (FastAPI)

TypeScriptPython (Pydantic)
stringstr
numberint | float
booleanbool
Date / string (ISO)datetime
Array<T> | T[]List[T]
Record<string, T>Dict[str, T]
T | null | T | undefinedOptional[T]
interfaceBaseModel
enumEnum or Literal
'a' | 'b'Literal['a', 'b']
Partial<T>Separate Update model (all optional)
Omit<T, 'id'>Separate Create model without id

TypeScript ↔ Go

TypeScriptGo
stringstring
numberint | int64 | float64
booleanbool
Datetime.Time
Array<T> | T[][]T
Record<string, T>map[string]T
T | null*T (pointer)
interfacestruct with JSON tags
enumconst with iota or string constants

TypeScript ↔ Java (Spring Boot)

TypeScriptJava
stringString
numberint | Long | Double
booleanboolean | Boolean
DateLocalDateTime | Instant
Array<T> | T[]List<T>
Record<string, T>Map<String, T>
T | nullOptional<T> or @Nullable
interfaceDTO class with getters/setters
enumenum

TypeScript ↔ C# (ASP.NET Core)

TypeScriptC#
stringstring
numberint | long | double | decimal
booleanbool
DateDateTime | DateTimeOffset
Array<T> | T[]List<T> | T[]
Record<string, T>Dictionary<string, T>
T | nullT? (nullable reference types)
interfaceclass with properties

Example Usage

Example 1: Frontend → Backend

User:

Here's my React component for user management. Generate the Express backend for it.

interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

function UserList() {
  const [users, setUsers] = useState<User[]>([]);
  
  useEffect(() => {
    axios.get('/api/users').then(res => setUsers(res.data));
  }, []);
  
  const addUser = async (name: string, email: string, role: string) => {
    const res = await axios.post('/api/users', { name, email, role });
    setUsers([...users, res.data]);
  };
  
  const deleteUser = async (id: number) => {
    await axios.delete(`/api/users/${id}`);
    setUsers(users.filter(u => u.id !== id));
  };
  
  // ... render
}

Code Mirror response:

I'll generate the Express backend that mirrors your React frontend.

From your frontend, I identified:

  • GET /api/users → returns User[]
  • POST /api/users → accepts { name, email, role } → returns User
  • DELETE /api/users/:id

src/models/User.ts

import mongoose, { Schema, Document } from 'mongoose';

export interface IUser extends Document {
  name: string;
  email: string;
  role: 'admin' | 'user';
  createdAt: Date;
  updatedAt: Date;
}

const UserSchema = new Schema<IUser>(
  {
    name: { type: String, required: true },
    email: { type: String, required: true, unique: true },
    role: { type: String, enum: ['admin', 'user'], default: 'user' },
  },
  { timestamps: true }
);

export const UserModel = mongoose.model<IUser>('User', UserSchema);

src/validators/userValidator.ts

import { z } from 'zod';

export const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  role: z.enum(['admin', 'user']).default('user'),
});

export const updateUserSchema = createUserSchema.partial();

src/routes/users.ts

import { Router } from 'express';
import { UserModel } from '../models/User';
import { validate } from '../middleware/validate';
import { createUserSchema, updateUserSchema } from '../validators/userValidator';

const router = Router();

router.get('/', async (req, res) => {
  const users = await UserModel.find();
  res.json(users);
});

router.post('/', validate(createUserSchema), async (req, res) => {
  const user = await UserModel.create(req.body);
  res.status(201).json(user);
});

router.delete('/:id', async (req, res) => {
  const user = await UserModel.findByIdAndDelete(req.params.id);
  if (!user) return res.status(404).json({ error: 'User not found' });
  res.status(204).end();
});

export default router;

The routes exactly match your frontend calls. The POST validator enforces the same required fields your frontend sends.

### Example 2: Backend → Frontend (Sync Mode)

**User:**

My FastAPI backend User model added a phone field. Update my React frontend types and form.

Backend model:

class User(BaseModel):
    id: str
    name: str
    email: str
    phone: str = Field(..., pattern=r'^\+?[1-9]\d{1,14}$')
    role: Literal['admin', 'user'] = 'user'
    created_at: datetime
**Code Mirror response:**

I found the new phone field. Here are the frontend updates:

src/types/user.ts — add phone field:

  export interface User {
    id: string;
    name: string;
    email: string;
+   phone: string;
    role: 'admin' | 'user';
    createdAt: string;
  }

  export interface CreateUserInput {
    name: string;
    email: string;
+   phone: string;
    role?: 'admin' | 'user';
  }

src/components/UserForm.tsx — add phone field with validation:

  const schema = z.object({
    name: z.string().min(1),
    email: z.string().email(),
+   phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number'),
    role: z.enum(['admin', 'user']).optional(),
  });

src/api/users.ts — no change needed (API calls remain the same, phone will be included automatically in POST/PUT)

The validation pattern matches your backend's Field(pattern=...) constraint.

Quality Checklist

Before finalizing generated code, verify:

  • Endpoint coverage — Every endpoint in source has counterpart in target
  • Type alignment — All fields, types, optional/required markers match
  • Validation consistency — Same rules exist on both sides (required fields, email format, min/max, patterns)
  • Error handling — Error responses have consistent shapes and status codes (400 for validation, 401 for auth, 404 for not found, 500 for server errors)
  • Authentication — Token storage/sending in frontend matches backend middleware expectations
  • Code style — Generated code matches existing project patterns (import style, naming, file organization)
  • Completeness — No placeholder comments like // TODO: implement this
  • Runnable — Code would run as-is (assuming correct dependencies are installed)
  • No hallucinations — Generated types, validators, and routes are directly derived from source, not invented

Error Handling in Generated Code

Always include proper error handling appropriate to the stack:

Backend error handling patterns:

// Express - 404
if (!item) return res.status(404).json({ error: 'Resource not found' });

// Express - validation
if (!result.success) return res.status(400).json({ errors: result.error.flatten() });

// Express - duplicate
try {
  await Model.create(data);
} catch (err) {
  if (err.code === 11000) return res.status(409).json({ error: 'Duplicate key' });
  throw err;
}
# FastAPI - 404
if not item:
    raise HTTPException(status_code=404, detail="Resource not found")

# FastAPI - validation (handled automatically by Pydantic)
# FastAPI - duplicate
if existing:
    raise HTTPException(status_code=409, detail="Email already registered")

Frontend error handling patterns:

// React Query - errors are automatically available in query result
const { error } = useQuery(...);

// Axios interceptor for global error handling
axios.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // redirect to login
    }
    return Promise.reject(error);
  }
);

Notes for the AI

When using this skill:

  1. Always read the source code thoroughly before generating. Don't assume or invent fields.
  2. Detect the stack from existing project files if available. Match the existing code style.
  3. Generate complete files — no snippets, no placeholders. Users should be able to copy-paste and run.
  4. Show file paths as headings so users know where files go.
  5. For sync operations, show diffs (using ```diff format) unless user asks for full files.
  6. Preserve all business logic from source — don't drop validation rules or computed fields.
  7. If anything is ambiguous, ask clarifying questions before generating.
  8. After generation, run through the Quality Checklist mentally to ensure nothing was missed.