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
For LLM Reference: This directory contains comprehensive development best practices, coding standards, and workflow guidelines. Use these documents as authoritative references for all development decisions.
TypeScript: Primary language for fast development and team navigation
Monorepo: Turbo-based with apps/ and packages/ structure
React/Next.js: Frontend applications with feature-based organization
Hono: Backend services deployed to Cloud Run containers
Neon: PostgreSQL database with proper migrations
Code Quality Standards
Self-Documenting: Code should be readable without extensive comments
Functional Programming: Prefer declarative over imperative approaches
Type Safety: Use Zod schemas over TypeScript enums
Immutability: Use const by default, avoid var entirely
Pure Functions: Minimize side effects for better testing
Development Workflow
Branch Strategy: feature/, fix/, chore/ with Jira ticket references
Pull Requests: Comprehensive descriptions with Loom recordings for significant changes
Mobile Testing: Mandatory testing on mobile viewports (320px minimum)
Code Reviews: Required approval before merging to master
🔄 State Management Patterns
useReducer Hook Structure
/hooks/use-[feature-name]/
├── index.ts # Main hook implementation
├── actions.ts # Action creators and handlers
├── reducer.ts # Reducer function
├── schema.ts # Type definitions and validation schemas
└── /schema/ # Additional schema files (optional)
Key Requirements
Zod Schemas: Both validation and state schemas
Discriminated Unions: For all action types
Separated Handlers: No inline logic in reducer
Immutable Updates: All state changes return new objects
📝 Documentation Strategy
Primary Approach
Self-Documenting Code: Clear naming and structure
Strategic Video Documentation: Complex features and processes
API Documentation: Postman workspace with examples
Minimal Written Docs: Only when absolutely necessary
When to Document
✅ Complex business logic and algorithms
✅ External API integrations
✅ Architecture decisions and rationale
✅ Public API contracts
❌ Self-explanatory code
❌ Implementation details
❌ Redundant comments
🚀 Getting Started
For New Team Members
Read through all best practice documents
Set up development environment with required tools
Review existing codebase examples
Watch onboarding video documentation
Practice with a small feature following all guidelines
For Existing Projects
Audit current practices against these guidelines
Create migration plan for any deviations
Update CI/CD to enforce standards
Train team on new or updated practices
🔧 Tools and Setup
Required Development Tools
Prettier: Code formatting (mandatory)
ESLint: Code linting and quality
TypeScript: Type checking and IntelliSense
Git: Version control with proper branching
Recommended IDE Configuration
Auto-format on save with Prettier
Real-time ESLint feedback
TypeScript strict mode enabled
File organization following established patterns
📊 Compliance and Quality
Code Review Checklist
Follows naming conventions
Uses appropriate data structures (Zod schemas)
Implements proper error handling
Includes necessary tests
Mobile viewport tested
Self-documenting without excessive comments
Project Health Indicators
Clean Git history with meaningful commits
Consistent code style across the codebase
Up-to-date dependencies and tooling
Comprehensive API documentation in Postman
Regular video documentation updates
Note: These best practices are living documents. They should be updated as the team learns and technology evolves. All changes should be discussed and approved by the development team.
For LLM Reference: This document defines mandatory coding standards and style guidelines. All code must conform to these standards for consistency and maintainability.
Naming Conventions
Case Conventions
camelCase: Variables, functions, methods, and properties
PascalCase: Classes, interfaces, types, and React components
SCREAMING_SNAKE_CASE: Constants and environment variables
// ✅ Good - Clear intentconstuserAccountBalance=1250.0;consthasPermissionToDelete=true;constfilteredActiveUsers=users.filter((user)=>user.isActive);// ❌ Bad - Unclear intentconstbal=1250.0;constflag=true;constdata=users.filter((user)=>user.isActive);
Use Pronounceable Names
// ✅ Good - Easy to discussconstcreatedTimestamp=Date.now();constuserRegistrationDate=newDate();// ❌ Bad - Hard to pronounce/discussconstcrtdts=Date.now();constusrregdt=newDate();
Use Searchable Names
// ✅ Good - Easy to find in codebaseconstMAX_RETRY_ATTEMPTS=3;constDEFAULT_PAGE_SIZE=20;// ❌ Bad - Hard to search forconstretries=3;constsize=20;
Variable Declaration
Immutability Preference
// ✅ Preferred - Immutable by defaultconstuserProfile={name: "John",email: "john@example.com"};constuserList=["Alice","Bob","Charlie"];// ✅ Acceptable - When reassignment neededletcurrentIndex=0;letisLoading=false;// ❌ Never use - Outdated and error-pronevaruserName="john";
Rules for Variable Declarations
Always use const when the variable won't be reassigned
Use let only when reassignment is necessary
Never use var - it has confusing scoping rules
Declare variables close to their usage
Initialize variables when declaring them
Function Design Principles
Single Responsibility
// ✅ Good - Single responsibilityfunctioncalculateTax(amount: number,rate: number): number{returnamount*rate;}functionformatCurrency(amount: number): string{returnnewIntl.NumberFormat("en-US",{style: "currency",currency: "USD",}).format(amount);}// ❌ Bad - Multiple responsibilitiesfunctioncalculateAndFormatTax(amount: number,rate: number): string{consttax=amount*rate;returnnewIntl.NumberFormat("en-US",{style: "currency",currency: "USD",}).format(tax);}
Descriptive Function Names
// ✅ Good - Clear what the function doesfunctionvalidateEmailAddress(email: string): boolean{/* ... */}functioncalculateMonthlyPayment(principal: number,rate: number,years: number): number{/* ... */}// ❌ Bad - Unclear purposefunctioncheck(email: string): boolean{/* ... */}functioncalc(p: number,r: number,y: number): number{/* ... */}
Limited Function Arguments
// ✅ Good - Few parameters, clear purposefunctioncreateUser(name: string,email: string): User{/* ... */}// ✅ Good - Use object for many parametersinterfaceCreateUserOptions{name: string;email: string;age?: number;department?: string;role?: string;}functioncreateUserWithOptions(options: CreateUserOptions): User{/* ... */}// ❌ Bad - Too many parametersfunctioncreateUser(name: string,email: string,age: number,dept: string,role: string,active: boolean): User{/* ... */}
Pure Functions (Preferred)
// ✅ Excellent - Pure functionfunctionaddNumbers(a: number,b: number): number{returna+b;}// ✅ Good - Pure function with complex logicfunctioncalculateDiscount(price: number,discountPercent: number): number{if(discountPercent<0||discountPercent>100){thrownewError("Discount percent must be between 0 and 100");}returnprice*(discountPercent/100);}// ❌ Avoid - Side effects make testing difficultletglobalCounter=0;functionincrementAndReturn(): number{globalCounter++;console.log("Counter incremented");returnglobalCounter;}
/* ✅ Preferred - Container queries for component-based responsive design */
.card-container {
container-type: inline-size;
container-name: card;
}
@container card (min-width:300px) {
.card {
display: grid;
grid-template-columns:1fr2fr;
}
}
/* ❌ Less preferred - Media queries for global breakpoints */@media (min-width:768px) {
.card {
display: grid;
grid-template-columns:1fr2fr;
}
}
Conditional Logic
Conditional Encapsulation
// ✅ Good - Encapsulated conditionsconstcanDeletePost=(user: User,post: Post): boolean=>{returnuser.id===post.authorId||user.role==="admin";};if(canDeletePost(currentUser,selectedPost)){deletePost(selectedPost.id);}// ❌ Less clear - Inline conditionsif(currentUser.id===selectedPost.authorId||currentUser.role==="admin"){deletePost(selectedPost.id);}
Async Operations
Async/Await over Callbacks/Promises
// ✅ Preferred - Clean async/awaitasyncfunctionsaveUserData(userData: UserData): Promise<User>{try{constvalidatedData=awaitvalidateUserData(userData);constsavedUser=awaitdatabase.users.create(validatedData);awaitsendWelcomeEmail(savedUser.email);returnsavedUser;}catch(error){logger.error("Failed to save user data:",error);throwerror;}}// ❌ Avoid - Promise chainsfunctionsaveUserData(userData: UserData): Promise<User>{returnvalidateUserData(userData).then((validatedData)=>database.users.create(validatedData)).then((savedUser)=>{returnsendWelcomeEmail(savedUser.email).then(()=>savedUser);}).catch((error)=>{logger.error("Failed to save user data:",error);throwerror;});}
Code Organization
Group Related Code
// ✅ Good - Related functions grouped togetherclassUserService{// User creationasynccreateUser(userData: CreateUserData): Promise<User>{/* ... */}privatevalidateUserData(userData: CreateUserData): void{/* ... */}privatehashPassword(password: string): string{/* ... */}// User retrievalasyncgetUserById(id: string): Promise<User|null>{/* ... */}asyncgetUserByEmail(email: string): Promise<User|null>{/* ... */}// User updatesasyncupdateUser(id: string,updates: Partial<User>): Promise<User>{/* ... */}asyncupdateUserPassword(id: string,newPassword: string): Promise<void>{/* ... */}}
For LLM Reference: This document outlines our approach to documentation, emphasizing self-documenting code over extensive written documentation, with strategic use of video documentation and API documentation tools.
Core Philosophy
Self-Documenting Code First
Our primary documentation strategy is self-documenting code. Well-written code with clear naming, structure, and minimal complexity should tell its own story without requiring extensive external documentation.
Minimal Written Documentation
We intentionally minimize written documentation in favor of:
// ✅ Business rules are clear from code structureclassSubscriptionService{canUpgradeSubscription(user: User,targetPlan: SubscriptionPlan): boolean{consthasActiveSubscription=user.subscription?.status==="active";constisUpgrade=targetPlan.price>user.subscription?.plan.price;consthasValidPaymentMethod=user.paymentMethods.some((pm)=>pm.isValid);returnhasActiveSubscription&&isUpgrade&&hasValidPaymentMethod;}calculateProrationAmount(currentPlan: Plan,newPlan: Plan,daysRemaining: number): number{constdailyDifference=(newPlan.price-currentPlan.price)/30;returndailyDifference*daysRemaining;}}
When to Write Documentation
Required Documentation
Write documentation only when:
Complex Business Logic: Algorithms or business rules that aren't immediately obvious
External Dependencies: Third-party integrations with specific requirements
Architecture Decisions: High-level system design choices and rationale
API Contracts: Public API documentation for external consumers
Documentation That Should Be Avoided
Don't write documentation for:
Obvious Code: Self-explanatory functions and variables
Implementation Details: How the code works (should be clear from reading it)
Redundant Comments: Restating what the code already says
Outdated Information: Documentation that becomes stale quickly
// ❌ Unnecessary documentation - code is self-explanatory/** * Gets the user's full name by concatenating first and last name * @param firstName - The user's first name * @param lastName - The user's last name * @returns The user's full name */functiongetFullName(firstName: string,lastName: string): string{return`${firstName}${lastName}`;}// ✅ No documentation needed - code is clearfunctiongetFullName(firstName: string,lastName: string): string{return`${firstName}${lastName}`;}
Video Documentation Library
Strategic Video Documentation
Maintain a curated library of video documentation for:
Complex Features: New feature walkthroughs and explanations
System Architecture: High-level system design and interactions
Development Processes: Setup, deployment, and workflow explanations
Troubleshooting: Common issues and their solutions
Video Documentation Standards
Content Guidelines
Duration: 5-10 minutes maximum per video
Focus: One topic per video
Quality: Clear audio, readable screen capture
Structure: Introduction, main content, summary/next steps
Video Categories
/video-documentation/
├── features/ # Feature demonstrations
├── architecture/ # System design explanations
├── processes/ # Development workflow
├── troubleshooting/ # Problem resolution
└── onboarding/ # New team member resources
Maintenance
Regular Review: Quarterly review of video relevance
Update or Remove: Outdated videos should be updated or removed
Version Control: Tag videos with relevant software versions
Video Creation Workflow
Plan Content: Outline key points before recording
Record Demo: Use Loom or similar screen recording tool
Edit if Necessary: Basic editing for clarity
Catalog: Add to video documentation library with clear title and description
Share: Make accessible to relevant team members
API Documentation
Postman Workspace
Maintain comprehensive API documentation using Postman workspace:
For LLM Reference: This document provides prescriptive patterns for implementing forms and inputs in React applications. These patterns are extracted from the @/edit-menu feature.
Quick Reference
Required Patterns Summary
Use controlled inputs with onChange handlers for immediate state updates
Implement proper accessibility with labels, aria-describedby, and unique IDs
Apply debouncing for expensive operations (rich text editors, API calls)
Use Zod schemas for validation with safeParse pattern
Implement proper loading states and error handling
Follow component composition patterns for reusable form elements
Use TypeScript for type safety throughout form implementations
Basic Input Implementation
Standard Input Pattern
<divclassName='flex flex-col gap-y-2'><labelclassName='font-semibold'htmlFor={`input-${uniqueId}`}>
Field Label
</label><pclassName='text-xs text-neutral-700 mb-2'id={`aria-described-by-${uniqueId}`}>
Helpful description of what this field is for
</p><Inputid={`input-${uniqueId}`}name={`input-${uniqueId}`}aria-describedby={`aria-described-by-${uniqueId}`}defaultValue={initialValue}onChange={(e: React.ChangeEvent<HTMLInputElement>)=>updateState({id: itemId,data: {fieldName: e.target.value},})}/></div>
Input Rules
Always provide unique IDs using template literals with entity IDs
Always include labels with proper htmlFor attributes
Always include descriptive text with aria-describedby linkage
Use controlled components with onChange handlers
Pass typed event handlers with proper TypeScript annotations
"use server";import{z}from"zod";exportasyncfunctionupdateMenuName(args: {menuId: string;name: string}){try{// Always validate input with safeParseconstvalidatedFields=z.object({menuId: z.string(),name: z.string().min(1,"Name is required"),}).safeParse({menuId: args.menuId,name: args.name,});// Handle validation errorsif(!validatedFields.success){return{success: false,error: "Invalid menu data",};}// Use validated dataconstresult=awaitdb.update({id: validatedFields.data.menuId,name: validatedFields.data.name,});return{success: true,data: result,};}catch(error){return{success: false,error: "Sorry...we couldn't update your menu. Please try again.",};}}
Client-Side Schema
import{z}from"zod";exportconstitemSchema=z.object({name: z.string().min(1,"Name is required"),description: z.string().optional(),price: z.string().regex(/^\d+(\.\d{1,2})?$/,"Price must be a valid decimal with up to 2 decimal places").optional(),hidden: z.boolean().default(false),});exporttypeItem=z.infer<typeofitemSchema>;
Validation Rules
Always use safeParse instead of parse for user input
Define clear error messages in schema validation
Return consistent response format with success/error properties
Handle both validation and runtime errors separately
State Management with Local Storage
useLocalStorageReducer Hook
exportfunctionuseLocalStorageReducer<TState,TActionextends{type: string;payload?: any},>(reducer: Reducer<TState,TAction>,initialState: TState,key: string): [TState,React.Dispatch<TAction>,()=>void]{constgetInitialState=(): TState=>{try{if(typeofwindow==="undefined")returninitialState;constitem=window.localStorage.getItem(key);returnitem ? (JSON.parse(item)asTState) : initialState;}catch(error){console.error("Error reading from localStorage:",error);returninitialState;}};const[state,dispatch]=useReducer(reducer,getInitialState());useEffect(()=>{try{if(typeofwindow==="undefined")return;window.localStorage.setItem(key,JSON.stringify(state));}catch(error){console.error("Error writing to localStorage:",error);}},[state,key]);functionclearLocalStorage(){window.localStorage.removeItem(key);}return[state,dispatch,clearLocalStorage];}
// Always link labels to inputs<labelhtmlFor={`input-${uniqueId}`}>Field Label</label><Inputid={`input-${uniqueId}`}aria-describedby={`description-${uniqueId}`}/>// Provide helpful descriptions<pid={`description-${uniqueId}`}>
Describe what this field is for
</p>// Screen reader text for icons<buttonaria-label="Remove item"title="Remove item"><spanclassName="sr-only">Remove item</span><TrashIconclassName="w-4 h-4"/></button>// Proper ARIA roles for complex inputs<Buttonrole="combobox"aria-expanded={open}>Selectoption</Button>
Performance Optimization
Debouncing Patterns
// For expensive operations (API calls, complex calculations)const[debouncedValue]=useDebounce(inputValue,1000);// For rich text editorsconst[,cancel]=useDebounce(()=>{onChange(editorContent||"");},1000,[editorContent]);// For search inputsconst[debouncedSearchTerm]=useDebounce(searchTerm,300);
Performance Rules
Debounce expensive operations (1000ms for API calls, 300ms for search)
For LLM Reference: This document defines the mandatory Git workflow, branching strategy, and pull request process. All team members must follow these procedures for consistent collaboration.
Branching Model
Main Branch
Branch Name: master
Purpose: Production-ready code
Protection: All changes must go through pull requests
Direct Commits: Not allowed (except for emergency hotfixes)
Purpose: Maintenance tasks, refactoring, dependency updates
Lifetime: Created from master, merged back to masterCleanup: Delete after successful merge
Branch Creation Workflow
1. Start from Master
# Ensure you're on master and up-to-date
git checkout master
git pull origin master
2. Create Feature Branch
# Create and checkout new branch
git checkout -b feature/MEN-123-user-authentication
# Or create branch and push to remote
git checkout -b feature/MEN-123-user-authentication
git push -u origin feature/MEN-123-user-authentication
3. Regular Development
# Make commits with descriptive messages
git add .
git commit -m "Add user authentication middleware"# Push changes regularly
git push origin feature/MEN-123-user-authentication
Commit Message Standards
Format
[Type]: Brief description of changes
Optional longer description explaining what and why.
- Bullet points for multiple changes
- Reference Jira ticket: MEN-123
Commit Types
feat: New feature
fix: Bug fix
docs: Documentation changes
style: Code style changes (formatting, etc.)
refactor: Code refactoring
test: Adding or updating tests
chore: Maintenance tasks
Examples
git commit -m "feat: Add user authentication middlewareImplements JWT-based authentication for API endpoints.Includes middleware for token validation and user context.- Add JWT token validation- Create user context middleware- Update API route protectionCloses MEN-123"
Pull Request Process
Required Elements for Pull Requests
1. Descriptive Title
[MEN-123] Add user authentication system
2. Comprehensive Description
## Summary
Implements JWT-based authentication system for the application.
## Changes Made- Added authentication middleware
- Implemented token validation
- Created user context provider
- Updated API route protection
## Testing-[ ] Unit tests pass
-[ ] Integration tests pass
-[ ] Manual testing completed
-[ ] Mobile viewport tested
## Jira Ticket
MEN-123: Implement User Authentication
3. Loom Recording (Required for Substantial Changes)
When Required: Any PR with significant UI changes or complex functionality
Content: Screen recording demonstrating the changes
React useReducer State Management Hook Best Practices
For LLM Reference: This document provides prescriptive patterns for building state management hooks with useReducer. Follow these patterns exactly when implementing new hooks or refactoring existing ones.
Quick Reference
Required File Structure
/hooks/use-[feature-name]/
├── index.ts # Main hook implementation
├── actions.ts # Action creators and handlers
├── reducer.ts # Reducer function
├── schema.ts # Type definitions and validation schemas
└── /schema/ # Additional schema files (optional)
Implementation Checklist
Zod schemas for validation and runtime types
Discriminated union types for all actions
Separated action handler functions (not inline in reducer)
Immutable state updates in all handlers
TypeScript types exported from schema files
Hook returns both state and action creators
Tests for each action handler function
Default state defined and applied
Core Patterns
This guide outlines mandatory patterns for building robust, scalable state management hooks using React's useReducer. These patterns ensure consistency, type safety, and maintainability across all implementations.
File Structure & Organization
Mandatory Directory Structure
/hooks/use-[feature-name]/
├── index.ts # Main hook implementation
├── actions.ts # Action creators and handlers
├── reducer.ts # Reducer function
├── schema.ts # Type definitions and validation schemas
└── /schema/ # Additional schema files (if complex)
├── schema.ts
├── validation-schema.ts
└── shared.ts
Action types: Use kebab-case: 'add-item', 'update-filter'
Action interfaces: Use PascalCase + "Action": AddItemAction
Handler functions: Use camelCase matching entity: addItem, updateFilter
Always use payload property for action data
Handler functions must be pure (no side effects)
Always return new state objects (immutable updates)
Reducer Implementation
Template: Reducer Function (EXACT PATTERN)
// reducer.ts - FOLLOW THIS EXACT STRUCTUREimport{type[FeatureName]State}from'./schema';import{Actions,add[Entity],update[Entity],remove[Entity],// Import ALL action handlers}from'./actions';exportfunctionreducer(state: [FeatureName]State,action: Actions): [FeatureName]State{switch(action.type){case'add-[entity]':
returnadd[Entity](state,action);case'update-[entity]':
returnupdate[Entity](state,action);case'remove-[entity]':
returnremove[Entity](state,action);// Add case for EVERY action typedefault:
returnstate;}}
Reducer Rules (Non-Negotiable)
Import all action handlers - never define logic inline
Switch statement only - no other logic in reducer
Always include default case returning current state
One case per action type - no shared cases
Never mutate state - always return new objects
Import types from schema file - maintain separation
Main Hook Implementation
Template: Hook Structure (MANDATORY PATTERN)
// index.ts - FOLLOW THIS EXACT TEMPLATE'use client';import{useEffect,useReducer,useState,useMemo,useCallback}from'react';import{reducer}from'./reducer';import{type[FeatureName]State,validationSchema,stateSchema}from'./schema';import{formatZodErrors}from'@/lib/utils/zod/formatErrors';// Your error formattertypeUse[FeatureName]Props={initialState?: [FeatureName]State;onChange?: (state: [FeatureName]State)=>void;};exportfunctionuse[FeatureName]({ initialState, onChange }: Use[FeatureName]Props){// 1. ALWAYS define default stateconstdefaultState: [FeatureName]State={// Define your default state structure};// 2. ALWAYS process initial state with helper functionfunctionprocessInitialState(state?: [FeatureName]State): [FeatureName]State|null{if(!state)returnnull;// Add any ID generation or parsing logic herereturnstateSchema.parse(state);}// 3. ALWAYS use reducer with processed initial stateconst[state,dispatch]=useReducer(reducer,processInitialState(initialState)||defaultState);// 4. ALWAYS add validation stateconst[isStateValid,setIsStateValid]=useState<boolean|undefined>(undefined);const[validationErrors,setValidationErrors]=useState<string[]|undefined>(undefined);// 5. ALWAYS validate state on changesuseEffect(()=>{constresult=validationSchema.safeParse(state);if(result.error){setValidationErrors(formatZodErrors(result.error));}else{setValidationErrors(undefined);}setIsStateValid(result.success);},[state]);// 6. ALWAYS call onChange callbackuseEffect(()=>{onChange?.(state);},[state,onChange]);// 7. ALWAYS create action creators with useCallbackconstadd[Entity]=useCallback(([entity]: [EntityType])=>{dispatch({type: 'add-[entity]',payload: {[entity]}});},[]);constupdate[Entity]=useCallback((id: string,updates: Partial<[EntityType]>)=>{dispatch({type: 'update-[entity]',payload: { id, updates }});},[]);constremove[Entity]=useCallback((id: string)=>{dispatch({type: 'remove-[entity]',payload: { id }});},[]);// 8. ALWAYS add derived state with useMemoconstcan[Action]=useMemo(()=>{returnisStateValid&&/* your conditions */;},[isStateValid,/* dependencies */]);// 9. ALWAYS return consistent interfacereturn{
state,
isStateValid,
validationErrors,add[Entity],update[Entity],remove[Entity],can[Action],};}
Hook Implementation Rules (Must Follow)
Always use 'use client' directive at top
Always define defaultState constant
Always process initialState with helper function
Always include validation with useState + useEffect
Always call onChange in useEffect
Always use useCallback for action creators
Always use useMemo for derived state
Always return consistent interface with state + actions + validation
Import all types from schema - never define inline
Testing Reducer Functions
Why Test Reducers?
Reducer functions are pure functions that take state and actions as input and return new state. This makes them highly testable since they have no side effects and produce predictable outputs.
1. Testing Individual Action Handlers
Test each action handler function in isolation:
import{addItem,updateItem}from"./actions";import{State}from"./schema";describe("Action Handlers",()=>{constinitialState: State={items: [{id: "1",name: "Item 1",completed: false},{id: "2",name: "Item 2",completed: true},],};describe("addItem",()=>{it("should add a new item to the state",()=>{constnewItem={id: "3",name: "Item 3",completed: false};constaction={type: "add-item"asconst,payload: {item: newItem}};constresult=addItem(initialState,action);expect(result.items).toHaveLength(3);expect(result.items[2]).toEqual(newItem);expect(result).not.toBe(initialState);// Immutability check});it("should not modify the original state",()=>{constnewItem={id: "3",name: "Item 3",completed: false};constaction={type: "add-item"asconst,payload: {item: newItem}};constoriginalLength=initialState.items.length;addItem(initialState,action);expect(initialState.items).toHaveLength(originalLength);});});describe("updateItem",()=>{it("should update the correct item",()=>{constaction={type: "update-item"asconst,payload: {id: "1",updates: {name: "Updated Item 1"}},};constresult=updateItem(initialState,action);expect(result.items[0].name).toBe("Updated Item 1");expect(result.items[1]).toEqual(initialState.items[1]);// Other items unchanged});it("should not modify state if item not found",()=>{constaction={type: "update-item"asconst,payload: {id: "non-existent",updates: {name: "Updated"}},};constresult=updateItem(initialState,action);expect(result.items).toEqual(initialState.items);});});});
import{renderHook}from"@testing-library/react";import{useFeature}from"./index";describe("useFeature validation",()=>{it("should mark state as invalid when required fields are missing",()=>{const{ result }=renderHook(()=>useFeature({initialState: {items: []},// Missing required fields}));expect(result.current.isStateValid).toBe(false);expect(result.current.validationErrors).toBeDefined();});it("should mark state as valid when all required fields are present",()=>{constvalidState={items: [{id: "1",name: "Item 1",completed: false}],metadata: {version: "1.0"},};const{ result }=renderHook(()=>useFeature({initialState: validState,}));expect(result.current.isStateValid).toBe(true);expect(result.current.validationErrors).toBeUndefined();});});
Testing: Test each action handler function individually
Naming: Consistent conventions across all files
FORBIDDEN Patterns
❌ Logic inline in reducer switch cases
❌ Mutating state objects directly
❌ Missing default case in reducer
❌ Action creators without useCallback
❌ Missing validation setup
❌ Inconsistent naming conventions
❌ Side effects in action handlers
Quick Validation Checklist
All files follow exact template structure
Both validationSchema and stateSchema defined
All action types use discriminated unions
All action handlers are pure functions
Reducer only contains switch statement
Hook includes validation state management
All action creators use useCallback
Tests cover each action handler
LLM Implementation Instructions
When implementing a useReducer hook, follow these exact steps:
Step 1: Create schema.ts
// Copy this template and replace [FeatureName] and field definitionsimport{z}from'zod';exportconstvalidationSchema=z.object({// Define strict required schema here});exportconststateSchema=z.object({// Define permissive nullish schema here});exporttype[FeatureName]State=z.infer<typeofstateSchema>;
Step 2: Create actions.ts
// Follow the exact action pattern with discriminated unionsimport{[FeatureName]State}from'./schema';exporttype[Action]Action={type: '[action-name]';
payload: {/* action data */};};exporttypeActions=[Action1]Action|[Action2]Action;exportfunction[actionHandler](state: [FeatureName]State,action: [Action]Action): [FeatureName]State{// Return new state object with immutable updates}
Step 3: Create reducer.ts
// Switch statement only - import all handlersimport{[FeatureName]State}from'./schema';import{Actions,[handler1],[handler2]}from'./actions';exportfunctionreducer(state: [FeatureName]State,action: Actions): [FeatureName]State{switch(action.type){case'[action-type]':
return[handler](state,action);default:
returnstate;}}
Step 4: Create index.ts
// Follow the complete hook template with validation"use client";// Import all dependencies and follow the 9-step template
Step 5: Create tests
// Test each action handler function individually// Verify immutability and state correctness
const{ sendSuccess, sendError }=useNotifications();consthandleSave=()=>{constresult=validationSchema.safeParse(state);if(!result.success){sendError("Please fix validation errors before saving");return;}try{// Save logic heresendSuccess("Changes saved successfully");}catch(error){sendError("Failed to save changes");}};
Remember: Always follow the templates exactly. These patterns ensure consistency, type safety, and maintainability across all useReducer implementations.
For LLM Reference: This document defines the standard technology stack and tooling decisions for all projects. Follow these choices consistently unless there's a compelling reason to deviate.
Core Technologies
Primary Language
TypeScript is the primary language for all development.
Justification:
Fast Development: Rich IDE support, auto-completion, and refactoring tools
Large Community: Extensive ecosystem and community support
Team Navigation: Makes it easier for team members to understand and navigate codebases
Web Development Stack
Backend Services
Hono: Primary framework for backend services
Cloud Run: Preferred deployment target for containerized services
Avoid serverless for services (use containers instead)
Frontend Applications
React: Core UI library
Next.js: React framework for web applications
TailwindCSS: Utility-first CSS framework
Authentication
Clerk: Primary authentication provider
Database
Neon: PostgreSQL database provider
Cloud Platform
Google Cloud Platform (GCP): Primary cloud infrastructure