You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Before making changes, read .instructions/general.instructions.md and any relevant instructions inside .instructions/apps or .instructions/packages for the files you plan to modify.
Keep TypeScript strictly typed. Do not use @ts-ignore or ESLint disable comments.
Always run pnpm typecheck to ensure your code is working. This also builds any needed deps. So do this anytime you cross a package boundary.
Use pnpm lint to check for linting errors and code style.
Use pnpm format -w to format all packages before committing.
The Github PR workflow will run pnpm typecheck, pnpm lint, and pnpm format to ensure code quality and will not allow merging if there are issues.
IF you're opening/reading files, read them in chunks of 1000 lines at a time. NEVER read less.
NEVER, EVER, EVER use @ts-ignore or eslint-disable comments. It breaks our linter and we cannot deploy the code. Instead report back IF you're stuck (we likely just have to rebook the TS/ESLint server)
Turbo Repo General Guidelines
Repository Structure
apps/ - Client applications
nextjs/ - Next.js 15 web application (app router)
expo/ - Expo SDK 53 / React Native mobile application
packages/ - Shared libraries
api/ - tRPC API with routers and services
auth/ - Authentication using Better Auth
db/ - Database schema and client using Drizzle
ui/ - Shared UI components for web and native
validators/ - Shared validation schemas
tooling/ - Development tooling
eslint/ - ESLint configurations
prettier/ - Prettier configurations
tailwind/ - Tailwind configurations
typescript/ - TypeScript configurations
Code Style
Use TypeScript for all code
Follow ESLint and Prettier configurations
Use absolute imports with appropriate path aliases:
// In Next.js app// For packagesimport{Button}from"@kidbook/ui";import{CamelCaseName}from"~/components/kebab-case-name";
Architecture Patterns
Full-stack TypeScript with end-to-end type safety
tRPC for type-safe API calls between client and server
Repository pattern for data access in API package
Drizzle ORM for database operations
Better Auth for authentication across platforms
Shared UI components with platform-specific implementations
Cross-Package Integration
Import from other packages using the package name:
// In UIimporttype{RouterOutput}from"@kidbook/api";// In APIexporttypeRouterOutput=inferRouterOutputs<AppRouter>;typeStory=RouterOutput["story"]["getById"];
Development Workflow
Use Turborepo for task orchestration. These tasks should be run from the root of the monorepo:
pnpm dev # Start development server for all apps
pnpm typecheck # Run type checking across the repo
pnpm lint # Run linting across the repo
Create new packages with consistent structure:
pnpm turbo gen package --name my-package
Create new apps with appropriate setup:
pnpm turbo gen app --name my-app
Environment Variables
Use per-package environment validation:
// In package/env.tsexportconstenv=createEnv({server: {DATABASE_URL: z.string().url(),},client: {NEXT_PUBLIC_BASE_URL: z.string().url(),},runtimeEnv: process.env,});
Store development variables in .env.local files (not committed)
Access environment variables through the package's env.ts:
import{useColorScheme}from"nativewind";functionComponent(){const{ colorScheme, toggleColorScheme }=useColorScheme();// Use colorScheme to conditionally apply styles}
Import shared styles:
import"../styles.css";
Component Patterns
Use React Native components with NativeWind styling:
Configure deep links for authentication and navigation in app.config.ts:
// app.config.tsexportdefaultdefineConfig({// ... other configscheme: "kidbook",// Define additional schemes for development});
Handle deep links for authentication:
// Register deep link handlerconstsubscription=registerAuthDeepLinks();// Clean up when component unmountsuseEffect(()=>{return()=>subscription.remove();},[]);
Export the router for inclusion in the root router:
// root.tsexportconstappRouter=createTRPCRouter({concept: conceptRouter,story: storyRouter,// other domain routers...});
Use proper procedure types:
publicProcedure - For endpoints that don't require authentication
protectedProcedure - For endpoints that require authenticated users
Follow this pattern for procedure definitions:
procedureName: publicProcedure.input(z.object({...}))// Input validation with Zod.output(outputSchema)// Output validation with Zod.query(async({ctx, input})=>{// Implementation using repository functions})
Repository Pattern
Create repository files for database access organized by entity:
Use artificial delay for development to simulate network conditions:
if(t._config.isDev){// artificial delay in dev 100-500msconstwaitMs=Math.floor(Math.random()*400)+100;awaitnewPromise((resolve)=>setTimeout(resolve,waitMs));}
Authentication & Authorization
Use the auth package for authentication
Implement proper authorization checks in procedures
Create protected procedures where necessary
Best Practices
Keep procedures focused on a single responsibility
const{ session, signIn, signOut }=useAuth();if(session?.user){// User is authenticated}
Environment Variables
Required environment variables:
AUTH_TWITTER_ID - Twitter OAuth client ID
AUTH_TWITTER_SECRET - Twitter OAuth client secret
Additional variables for other providers as needed
Client Usage
Web client functions:
// Sign in with email/passwordawaitauth.signIn.emailPassword({ email, password });// Sign in with OAuth providerawaitauth.signIn.oAuth({provider: "twitter"});// Sign outawaitauth.signOut();
Expo client functions:
// Sign in with OAuth provider in Expoawaitauth.signIn.oAuth({provider: "twitter"});// Handle deep links for OAuth callbackuseEffect(()=>{// Register deep link handlerconstsubscription=registerAuthDeepLinks();return()=>subscription.remove();},[]);
Protecting Routes
In Next.js, use middleware for route protection:
// middleware.tsimport{auth}from"@kidbook/auth/client";exportdefaultauth.middleware;// Specify which routes to protectexportconstconfig={matcher: ["/dashboard/:path*","/settings/:path*"],};
// Use reference type + reference id patternexportconstimages=pgTable("images",(t)=>({referenceType: imageReferenceTypeEnum("reference_type").notNull(),referenceId: t.uuid("reference_id").notNull(),// ...other fields}));// Then set up basic relationsexportconstimagesRelations=relations(images,({ one })=>({concept: one(concepts,{fields: [images.referenceId],references: [concepts.id],}),// ...other relations}));// Apply filters when querying// db.query.images.findMany({// where: eq(images.referenceType, "concept"),// with: { concept: true }// })
Timestamps with timezone: t.timestamp({ mode: "date", withTimezone: true })
Enums for constrained values: pgEnum() + column using that enum
Enforce constraints at database level:
Not null: .notNull()
Defaults: .default()
Foreign keys: Define in relations
Unique constraints: .unique()
Add indexes for frequently queried fields:
// Example: add index for a foreign key
conceptId: t.uuid("concept_id").references(()=>concepts.id),// Index definition{name: "idx_stories_concept_id",columns: [stories.conceptId]};
Schema Design
Use Drizzle ORM for database schema definition and operations
Define tables with proper relationships
Use appropriate data types for columns
Implement proper indexes for frequently queried fields
Queries
Write optimized queries to avoid performance issues
Use transactions for operations that need atomicity
Implement proper pagination for large result sets
Relationships
Define relationships using proper foreign key constraints
Use join operations efficiently
Consider denormalization for performance-critical queries
Type Safety
Leverage TypeScript and Drizzle's type inference
Ensure database types are properly mapped to application types
Use branded types for IDs where appropriate
Error Handling
Handle database errors gracefully
Implement retry logic for transient failures
Use custom error types for database-specific errors
Security
Never store sensitive data in plain text
Implement proper access controls
Use parameterized queries to prevent SQL injection
Use consistent naming for variants across components (primary, secondary, destructive, etc.)
Use consistent size names (sm, md, lg) across all components
Use the project's design system colors through CSS variables. You should use these instead of generic whites, greys, and blacks as they are theme aware.
bg-primarytext-primary-foreground/* Primary button */bg-secondarytext-secondary-foreground/* Secondary button */bg-destructivetext-destructive-foreground/* Destructive button */bg-mutedtext-muted-foreground/* Subtle UI elements */bg-accenttext-accent-foreground/* Highlighted UI elements */bg-cardtext-card-foreground/* Card component */bg-backgroundtext-foreground/* Base colors */bg-bordertext-border/* Form control colors */
... etc
Follow these opacity patterns for hover/focus states:
hover:bg-primary/90 /* 90% opacity for hover */
active:opacity-90 /* 90% opacity for active state */
Use the cn() utility from utils.ts to merge Tailwind classes
Use these utility classes for scrollbar styling:
.no-scrollbar/* Hides scrollbars across browsers */
Follow the pattern: entityNameSchema for main schemas
Use entityNameAction for operation-specific schemas:
exportconstuserSchema=z.object({...});// Complete user schemaexportconstuserCreateSchema=z.object({...});// Schema for user creationexportconstuserUpdateSchema=z.object({...});// Schema for user updates
Complex Validators
Create reusable validation components:
// Reusable password validatorexportconstpasswordValidator=z.string().min(8).regex(/[A-Z]/,"Password must contain at least one uppercase letter").regex(/[a-z]/,"Password must contain at least one lowercase letter").regex(/[0-9]/,"Password must contain at least one number");// Use in other schemasexportconstuserCreateSchema=z.object({email: z.string().email(),password: passwordValidator,});
Implement custom validators for complex rules:
exportconstdateRangeSchema=z.object({start: z.date(),end: z.date(),}).refine(({ start, end })=>end>start,{message: "End date must be after start date",});
API Integration
Import validators in API procedures:
// In API routerimport{userCreateSchema}from"@kidbook/validators";exportconstuserRouter=createTRPCRouter({create: publicProcedure.input(userCreateSchema).mutation(async({ ctx, input })=>{// Implementation using validated input}),});
Form Integration
Use validators with form libraries:
// In a React component with React Hook Formimport{zodResolver}from"@hookform/resolvers/zod";import{useForm}from"react-hook-form";import{userCreateSchema}from"@kidbook/validators";functionSignupForm(){constform=useForm({resolver: zodResolver(userCreateSchema),});// Form implementation}
When To Use This Package
For validation logic needed in multiple places
For complex validation rules beyond simple database schema validation
When the same validation needs to be performed on both client and server
For form validation in the UI that matches API expectations
Exports
Export both schemas and inferred types:
// Schema for validationexportconststorySchema=z.object({...});// Type for TypeScript usageexporttypeStory=z.infer<typeofstorySchema>;
Type Safety
Export inferred types from schemas using z.infer<typeof schemaName>
Use these types throughout the codebase for consistency
Maintain proper TypeScript integration
Validation Logic
Keep validation logic focused and specific
Implement custom validators for complex rules
Use appropriate error messages for validation failures
Error Handling
Provide clear, user-friendly error messages
Use consistent error format across all validators
Support internationalization for error messages when needed
Reusability
Create composable validation schemas
Avoid duplication by reusing schema components
Share common validation patterns across the application
Testing
Test validators with valid and invalid inputs
Verify error messages are correct and helpful
Ensure edge cases are properly handled
Documentation
Document complex validation rules
Include examples of valid and invalid values
Keep documentation updated when validation rules change