Skip to main content

Package Management

CRITICAL: ALWAYS use bun as the package manager. NEVER use npm, pnpm, or yarn for this project.
# Correct
bun install
bun add package-name
bun run dev

# WRONG - Do not use
npm install        # ❌
pnpm install       # ❌
yarn install       # ❌

TypeScript Standards

Strict Mode

ZapDev uses TypeScript strict mode with specific configurations:
  • Strict Mode: Enabled
  • No any: Warn only (avoid when possible)
  • Null Checks: Strict null checking enabled

Type Safety Rules

Always define explicit return types for functions
Use type inference where obvious
Prefer interface for object shapes
Prefer type for unions and intersections
// Good - Explicit return type
function calculateCredits(userId: string): number {
  return 5;
}

// Good - Type inference is clear
const credits = 5;

// Avoid - using 'any'
function processData(data: any) { }  // ❌

// Better - use proper types
function processData(data: ProjectData) { }  // ✅

Path Aliases

Use configured path aliases for clean imports:
  • @/*src/*
  • @/convex/*convex/*
// Good
import { api } from '@/convex/_generated/api';
import { Button } from '@/components/ui/button';
import { trpc } from '@/lib/trpc';

// Avoid relative paths
import { Button } from '../../../components/ui/button';  // ❌

Code Style

Naming Conventions

TypeConventionExample
ComponentsPascalCaseProjectCard, FileExplorer
FunctionscamelCasegetUserUsage, createProject
ConstantsSCREAMING_SNAKE_CASECREDIT_LIMITS, API_VERSION
Types/InterfacesPascalCaseProjectData, UserSession
Fileskebab-casecode-agent.ts, file-operations.ts
Folderskebab-caseapi-reference, sandbox-templates

File Organization

Feature-Based Modules: Each module has ui/ and server/ subdirectories:
src/modules/
├── projects/
│   ├── ui/              # UI components for projects
│   └── server/          # Server procedures for projects
├── messages/
│   ├── ui/
│   └── server/
└── usage/
    ├── ui/
    └── server/

React Conventions

Component Structure

// 1. Imports (grouped)
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { trpc } from '@/lib/trpc';
import type { ProjectData } from '@/types';

// 2. Types/Interfaces
interface ProjectCardProps {
  project: ProjectData;
  onSelect: (id: string) => void;
}

// 3. Component
export function ProjectCard({ project, onSelect }: ProjectCardProps) {
  // Hooks first
  const [isHovered, setIsHovered] = useState(false);
  const { data } = trpc.projects.getById.useQuery({ id: project.id });
  
  // Effects
  useEffect(() => {
    // Effect logic
  }, []);
  
  // Event handlers
  const handleClick = () => {
    onSelect(project.id);
  };
  
  // Render
  return (
    <div onClick={handleClick}>
      {/* JSX */}
    </div>
  );
}

Hooks Rules

Always call hooks at the top level
Use custom hooks for shared logic
Name custom hooks with use prefix
// Good - custom hook
function useProjectData(projectId: string) {
  const { data, isLoading } = trpc.projects.getById.useQuery({ id: projectId });
  return { project: data, isLoading };
}

// Usage
function ProjectView({ projectId }: { projectId: string }) {
  const { project, isLoading } = useProjectData(projectId);
  // ...
}

Convex Conventions

Authentication

Always use requireAuth(ctx) in Convex functions:
import { requireAuth } from '@/convex/lib/auth';
import { query, mutation } from './_generated/server';

export const getProjects = query(async (ctx) => {
  const userId = await requireAuth(ctx);
  
  return await ctx.db
    .query('projects')
    .withIndex('by_user', (q) => q.eq('userId', userId))
    .collect();
});

Database Queries

NEVER use .filter() in Convex queries. Always use indexes.
// Bad - Using filter
const projects = await ctx.db
  .query('projects')
  .filter((q) => q.eq(q.field('userId'), userId))  // ❌
  .collect();

// Good - Using index
const projects = await ctx.db
  .query('projects')
  .withIndex('by_user', (q) => q.eq('userId', userId))  // ✅
  .collect();

Anti-Patterns

Things to NEVER do in ZapDev:

Package Management

  • ❌ NEVER use npm, pnpm, or yarn
  • ✅ ALWAYS use bun

Database

  • ❌ NEVER use .filter() in Convex queries
  • ✅ ALWAYS use indexes

Security

  • ❌ NEVER expose Clerk user IDs in public APIs
  • ❌ NEVER use absolute paths in AI-generated code (e.g., /home/user/...)
  • ✅ ALWAYS validate and sanitize user inputs with Zod

E2B Sandboxes

  • ❌ NEVER start dev servers in E2B sandboxes
  • ✅ Pre-warm sandboxes with compile_page.sh

Documentation

  • ❌ NEVER create .md files in root directory
  • ✅ ALWAYS place documentation in explanations/

Styling

  • ❌ DO NOT load Tailwind as external stylesheet
  • ✅ Use Tailwind CSS v4 with PostCSS

TypeScript

  • ❌ DO NOT use as or any to bypass type errors
  • ✅ Fix the underlying type issue

Development

  • ❌ DO NOT run bun convex dev without user permission
  • ✅ Ask before starting background services

Security Best Practices

Input Validation

Always validate inputs using Zod:
import { z } from 'zod';

const createProjectSchema = z.object({
  name: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
  framework: z.enum(['nextjs', 'react', 'vue', 'angular', 'svelte'])
});

export const createProject = mutation({
  args: createProjectSchema,
  handler: async (ctx, args) => {
    const userId = await requireAuth(ctx);
    // Safe to use validated args
  }
});

File Path Sanitization

Always sanitize file paths:
import { sanitizePath } from '@/lib/sanitizers';

// Remove directory traversal attempts
const safePath = sanitizePath(userProvidedPath);

Environment Variables

Never commit secrets:
// Good - using environment variables
const apiKey = process.env.E2B_API_KEY;

// Bad - hardcoded secrets
const apiKey = 'sk_123456789';  // ❌

Documentation Standards

Code Comments

/**
 * Checks if user has available credits and consumes one.
 * Throws error if user has no credits remaining.
 * 
 * @param userId - The authenticated user's ID
 * @returns Promise that resolves when credit is consumed
 * @throws Error when user has no credits
 */
export async function consumeCredit(userId: string): Promise<void> {
  // Implementation
}

JSDoc for Public APIs

Use JSDoc for all exported functions and types:
/**
 * Configuration for framework-specific code generation
 */
export interface FrameworkConfig {
  /** Framework identifier */
  name: string;
  /** Default UI library for this framework */
  uiLibrary: string;
  /** Build command */
  buildCommand: string;
}

Git Workflow

Branch Naming

  • feature/ - New features
  • fix/ - Bug fixes
  • docs/ - Documentation updates
  • refactor/ - Code refactoring
  • test/ - Test additions/updates

Commit Messages

Follow conventional commits:
feat: add framework selection dialog
fix: resolve E2B sandbox timeout
docs: update API reference
refactor: extract credit logic to separate module
test: add tests for usage tracking
chore: update dependencies

Performance Guidelines

React Performance

// Use React.memo for expensive components
export const FileExplorer = React.memo(({ files }: FileExplorerProps) => {
  // Component logic
});

// Use useCallback for event handlers
const handleFileSelect = useCallback((fileId: string) => {
  onFileSelect(fileId);
}, [onFileSelect]);

// Use useMemo for expensive computations
const sortedFiles = useMemo(() => {
  return files.sort((a, b) => a.name.localeCompare(b.name));
}, [files]);

Database Performance

// Good - indexed query
const recentProjects = await ctx.db
  .query('projects')
  .withIndex('by_user_created', (q) => 
    q.eq('userId', userId).gt('createdAt', thirtyDaysAgo)
  )
  .take(10);

// Consider pagination for large datasets
const projects = await ctx.db
  .query('projects')
  .withIndex('by_user', (q) => q.eq('userId', userId))
  .paginate({ cursor, numItems: 20 });

Testing Conventions

See Testing Guide for comprehensive testing practices. Key points:
  • Use centralized mocks in tests/mocks/
  • Follow Arrange-Act-Assert pattern
  • Name tests descriptively: it('should return 5 credits for free tier users')
  • Mock external dependencies (E2B, Convex, Inngest)

UI/UX Guidelines

Shadcn/ui Components

ZapDev uses Shadcn/ui (copy/paste components, not a library):
// Import from components/ui
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardContent } from '@/components/ui/card';

// Use consistently
<Button variant="default" size="lg">
  Create Project
</Button>

Tailwind CSS

Use Tailwind v4 utility classes:
// Good - utility classes
<div className="flex items-center gap-4 p-6 rounded-lg bg-background">
  {/* content */}
</div>

// Avoid inline styles
<div style={{ display: 'flex', padding: '24px' }}>  // ❌

Framework-Specific Guidelines

Default Framework

Next.js 15 is the default framework unless user specifies otherwise:
  • Next.js 15 - Default for web apps
  • Angular 19 - Material Design, enterprise apps
  • React 18 - Vite + Chakra UI
  • Vue 3 - Vuetify
  • SvelteKit - DaisyUI

Framework Detection

See src/prompts/framework-selector.ts for AI framework selection logic.

Resources


Following these conventions ensures code quality, maintainability, and consistency across the ZapDev codebase.

Build docs developers (and LLMs) love