npm run dev # Start development server (http://localhost:3000)
npm run build # Production build
npm start # Start production server
npm run lint # Run ESLint
npm run aidev # Vercel AI SDK devtoolsNote: No test runner configured. Add test scripts to package.json if needed.
Required in .env.local:
NOVITA_API_KEY=your-novita-api-key
E2B_API_KEY=your-e2b-api-key- React/hooks imports
- Third-party packages
- Internal imports with
@/path alias - Type imports (if separate)
'use client'
import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import type { LLMModel } from '@/lib/models'- Client components: Add
'use client'at top - Components use
forwardRefpattern for Radix UI primitives - Use CVA (class-variance-authority) for variant styles
- Export types alongside components
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva("base-classes", {
variants: {
variant: { default: "...", outline: "..." },
size: { default: "...", sm: "..." }
},
defaultVariants: { variant: "default", size: "default" }
})
function Button({ className, variant, size, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants>) {
return <button className={cn(buttonVariants({ variant, size, className }))} {...props} />
}- Use
cn()from@/lib/utilsfor Tailwind class merging - Prefer functional patterns over class-based
- Strict mode enabled
- Use
interfacefor object shapes,typefor unions/primitives - Prefer explicit return types for exported functions
- Components: PascalCase (
FragmentWeb.tsx) - Utilities: camelCase (
cn.ts) - Types/interfaces: PascalCase (
LLMModel) - Files: kebab-case for folders, PascalCase for components
- Use
export const maxDuration = 60for Vercel timeouts - Type request/response interfaces locally
- Try/catch with proper error responses:
export async function POST(req: Request) {
try {
const body = await req.json()
return Response.json({ data })
} catch (error) {
console.error('Error:', error)
return Response.json(
{ error: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
)
}
}Use Zod for runtime validation:
import { z } from 'zod'
export const fragmentSchema = z.object({
title: z.string().describe('Short title'),
code: z.string().describe('Generated code'),
})
export type FragmentSchema = z.infer<typeof fragmentSchema>Run npm run lint before committing. ESLint config uses eslint-config-next with TypeScript and core-web-vitals presets.
All internal imports use @/ prefix:
import { something } from '@/lib/something'
import { Button } from '@/components/ui/button'- Next.js: 16.1.1 (App Router)
- React: 19.2.3
- Vercel AI SDK:
ai,@ai-sdk/openai,@ai-sdk/react - UI: Radix UI primitives + shadcn/ui patterns
- Styling: Tailwind CSS v4 +
tailwind-merge,clsx - Validation: Zod
- Sandbox:
novita-sandbox
/api/chat- LLM streaming with Vercel AI SDKstreamObject()/api/sandbox/create- Sandbox creation vianovita-sandbox
- React hooks (
useState,useEffect, custom hooks) @/hooks/for shared logic (e.g.,useSandboxAgent,useScreenRecorder)
Server-side:
import { streamObject } from 'ai'
const stream = await streamObject({ model, schema, messages })
return stream.toTextStreamResponse()Client-side:
import { experimental_useObject as useObject } from '@ai-sdk/react'
const { object, submit, isLoading } = useObject({ api: '/api/chat' })