- Vision: [Your project's core vision - what problem are you solving and for whom?]
For complete specs and features, see [link to your detailed spec document].
- Your most important job is to manage your own context. Always read any relevant files BEFORE planning changes.
- When updating documentation, keep updates concise and on point to prevent bloat.
- Write code following KISS, YAGNI, and DRY principles - but prioritize shipping over perfection.
- When in doubt follow proven best practices for [your framework stack].
- Do not commit to git without user approval.
- Do not run any servers, rather tell the user to run servers for testing (npm run dev, expo start, etc.).
- Always consider industry standard libraries/frameworks first over custom implementations.
- Never use placeholders. Never omit code. Implement complete, production-ready solutions.
- Apply SOLID principles where relevant. Use modern framework features rather than reinventing solutions.
- Be brutally honest about whether an idea is good or bad for the [MVP/project timeline].
- Make side effects explicit and minimal.
- Design database schema to be evolution-friendly (avoid breaking changes).
- Let PostgreSQL/Supabase handle all time-based logic - never calculate time on the client.
- Default to creating multiple small, focused files rather than large monolithic ones
- Each file should have a single responsibility and clear purpose
- Keep files under 350 lines when possible - split larger files by extracting utilities, constants, types, or logical components into separate modules
- Separate concerns: utilities, constants, types, components, and business logic into different files
- Prefer composition over inheritance - use inheritance only for true 'is-a' relationships, favor composition for 'has-a' or behavior mixing
- Follow existing project structure and conventions - place files in appropriate directories. Create new directories and move files if deemed appropriate.
- Use well defined sub-directories to keep things organized and scalable
- Structure projects with clear folder hierarchies and consistent naming conventions
- Import/export properly - design for reusability and maintainability
Database Types Strategy:
- TypeScript interfaces are auto-generated by Supabase CLI - Never manually maintain database types
- Run
npx supabase gen types typescript --linked > shared/types/database.tsafter schema changes - These generated types are your source of truth for all database operations
What stays platform-specific (intentionally duplicated):
- ❌ Supabase client setup - Single client per platform (mobile uses AsyncStorage, admin uses SSR with cookies)
- ❌ All API calls - Platform-specific error handling and retry logic
- ❌ Utility functions - Even simple formatters stay separate unless truly universal
- ❌ Validation logic - Beyond basic constants
- ❌ State management - Completely platform-specific
- ❌ Business logic - Duplicate small functions rather than abstract prematurely
Rationale for minimal sharing:
- Different user types: Mobile ([your mobile users]) vs Admin ([your admin users]) typically have ~0% UI overlap
- AI-assisted development: Claude Code can maintain consistency across duplicated code easily
- MVP timeline: Architecture perfection is not the goal - working software is
- Future refactoring: With working software, real patterns for sharing will emerge naturally
Write for AI-assisted development:
- Early returns over nested ternaries - easier to read and modify
- Explicit types over implicit any - helps AI understand intent
- Clear variable names over clever abbreviations
- Simple patterns over clever one-liners
- Comments on "why" not "what" - explain business logic, not syntax
- Always use TypeScript for all code (
.tsand.tsxfiles) - TypeScript 5.3+ for both mobile and admin
- Define interfaces for all data structures
- Use type inference where obvious, explicit types where helpful
- Prefer interfaces over type aliases for object shapes
- Use enums for finite sets of values
- TypeScript is your primary safety net - no unit tests needed for MVP
// Good - Mobile/React Native example
interface [YourDataStructure] {
id: string;
[field]: [type];
// ... your fields
}
export async function [yourFunction](
request: [YourDataStructure]
): Promise<[ReturnType] | null> {
// Implementation
}- Components: PascalCase (e.g.,
UserCard,SettingsPanel) - Functions/Methods: camelCase (e.g.,
processData,calculateMetric) - Constants: UPPER_SNAKE_CASE (e.g.,
MAX_ITEMS,API_TIMEOUT) - Files: kebab-case for utilities (e.g.,
data-utils.ts), PascalCase for components (e.g.,UserCard.tsx) - Interfaces: PascalCase, optionally with
Iprefix (e.g.,IUser,IConfig) - Zustand stores: camelCase with
Storesuffix (e.g.,useAuthStore,useDataStore)
- Use JSDoc comments for functions and complex types
- Document component props with TypeScript interfaces
- Keep comments concise - let the code speak for itself
- Document "why" not "what" in inline comments
/**
* [Brief description of what this function does]
* @param [param] - [Description]
* @returns [Description of return value]
* @throws {[ErrorType]} [When this error occurs]
*/
export async function [yourFunction](
[param]: [type]
): Promise<[ReturnType]> {
// Implementation
}- Never trust external inputs - validate everything at the boundaries
- Keep Supabase keys in environment variables, never in code
- Use Supabase Row Level Security (RLS) to enforce strict user data isolation
- [Add your authentication strategy - OTP, OAuth, etc.]
- [Your Domain-Specific Privacy Concerns]:
- [List critical privacy requirements for your app]
- [Data retention policies]
- [What gets encrypted, what gets purged]
- Profile photos in secure Supabase Storage buckets with signed URLs
- Log security events but never log personal data
- Use UUIDs for all user-facing IDs
- Sanitize all user inputs before storing
- Use specific exceptions over generic ones
- Always log errors with context
- Provide helpful error messages
- Fail securely - errors shouldn't reveal system internals
- Every request needs a correlation ID for debugging
- Structure logs for machines, not humans - use JSON format with consistent fields (timestamp, level, correlation_id, event, context)
- Make debugging possible across service boundaries
Mobile App - Two-Tier State Architecture:
- Zustand for ephemeral UI state ([list your UI state - auth session, active screens, permissions, etc.])
- React Query for server state ([list your server state - profiles, data fetching, cached API responses])
- When to use which: Zustand for "what's happening now", React Query for "what's in the database"
React Query Usage Guidelines:
- ✅ Use for: [List use cases - profile fetching, data history, repeated API calls]
- ❌ Don't use for: One-time auth flows, [your exceptions]
- Query keys:
['entity', id]pattern for consistency - Configuration: 2-minute stale time, 5-minute cache (gcTime), 1 retry
- Mutations: Use optimistic updates for instant UI feedback with automatic rollback on errors
Admin Dashboard:
- Use SvelteKit's built-in stores for [your metrics and state management]
- [Describe what the admin dashboard does - monitoring, management, analytics, etc.]
General Principles:
- Let Supabase be the source of truth - client state is ephemeral
- Use optimistic updates for better UX but handle rollbacks properly
- Cache user preferences locally in AsyncStorage
- All APIs auto-generated by Supabase - no custom backend code for MVP
- Use Supabase JavaScript client for both mobile and admin
- RLS policies handle all authorization logic (strict user data isolation)
- Direct client-to-database communication is acceptable for MVP
- Use database functions for complex operations:
[your_function_1]()- [Description][your_function_2]()- [Description]- [List your key database functions]
- PostgreSQL triggers for:
- [Your trigger use cases]
- [Auto-cleanup tasks]
- [Data validation]
- Storage buckets for [your file types] with signed URLs
- PostgreSQL Type Casting: Always cast aggregates to match function return types (e.g.,
COUNT(*)::INTEGER) - Client-side UI State: Keep UI state like loading/error in React/Svelte state, not database
Common Supabase Patterns:
// Row Level Security ensures users only see their data
// But sometimes you need cross-user access (e.g., messaging, matching)
// Use SECURITY DEFINER functions for this (see section below)
// Type-safe database access with auto-generated types
import { Database } from '@/shared/types/database';
const { data, error } = await supabase
.from('users')
.select('*')
.eq('id', userId)
.single();Why needed: RLS policies correctly block cross-user data access, but [your use case] legitimately needs to read other users' data.
Solution: Use SECURITY DEFINER functions that bypass RLS with database owner permissions:
CREATE FUNCTION [your_function_name](
user_id UUID,
[params]
) RETURNS TABLE(...)
SECURITY DEFINER -- Bypasses RLS
AS $
-- Function validates its own authorization
-- Can read other users' data despite RLS blocking
-- Respects [your authorization rules]
$;When to use SECURITY DEFINER:
- [List specific use cases where cross-user access is legitimate]
- Admin dashboard reading aggregate metrics
- Any legitimate cross-user data access blocked by RLS
Security rule: SECURITY DEFINER functions MUST validate authorization internally since they bypass RLS.
Full Authority: You have complete access to manage all Supabase operations directly. Take ownership of database design, migrations, and configuration without asking permission for routine tasks.
CRITICAL EXCEPTION - TypeScript Type Generation:
npx supabase gen types typescript --linked > shared/types/database.ts
Proactive Responsibilities:
- Design and apply database schema migrations autonomously
- Run security advisors after any schema changes to catch issues early
- Generate TypeScript types via CLI (NOT MCP) after schema updates
- Create and manage development branches for testing risky changes
- Monitor logs when debugging issues
- Set up all RLS policies, indexes, functions, and triggers as needed
Act Autonomously For:
- All read operations (listing tables, checking extensions, viewing migrations)
- Creating/modifying schema via migrations (tables, indexes, functions, RLS policies)
- Enabling extensions as needed
- Running security and performance advisors
- Generating TypeScript types for the frontend
- Creating development branches for testing
- Debugging with logs and test queries
Inform User When:
- Merging branches to production
- Detecting critical security issues via advisors
- Making breaking schema changes that affect existing code
- Database operations fail unexpectedly
[PROJECT_NAME]-Specific Patterns:
- [List your specific Supabase patterns]
- Let PostgreSQL handle all time logic
- RLS policies on every table - users see ONLY their own data except via SECURITY DEFINER functions
- Always run security advisor after creating new tables
IMPORTANT: Always check Context7 FIRST before doing web searches for coding-related information. Context7 provides up-to-date library documentation and should be your primary source for technical references.
When to use:
- React Native/Expo documentation for mobile features
- SvelteKit documentation for admin dashboard
- Supabase client SDK documentation
- NativeWind/Tailwind CSS styling references
- [Your other key libraries]
Usage pattern:
// Get [library] documentation
mcp__context7__resolve_library_id(libraryName="[library-name]")
mcp__context7__get_library_docs(
context7CompatibleLibraryID="/[org]/[repo]",
topic="[topic]",
tokens=5000
)NativeWind v4 Styling:
// Use className prop directly on React Native components
import { View, Text, Pressable } from 'react-native';
export function [YourButton]() {
return (
<Pressable className="bg-blue-500 active:bg-blue-600 rounded-full p-4">
<Text className="text-white font-bold">[Action]</Text>
</Pressable>
);
}Navigation with Expo Router (File-Based):
// app/(tabs)/_layout.tsx - Defines bottom tab navigation
import { Tabs } from 'expo-router';
export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: '[Screen1]' }} />
<Tabs.Screen name="[screen2]" options={{ title: '[Screen2]' }} />
<Tabs.Screen name="[screen3]" options={{ title: '[Screen3]' }} />
</Tabs>
);
}
// File structure automatically creates routes:
// app/(tabs)/index.tsx → [Screen1]
// app/(tabs)/[screen2].tsx → [Screen2]
// app/(tabs)/[screen3].tsx → [Screen3]Platform-Specific Code (When Needed):
import { Platform } from 'react-native';
// Rare cases needing platform logic
if (Platform.OS === 'ios') {
// iOS-specific behavior
} else if (Platform.OS === 'android') {
// Android-specific behavior
}Do these NOW (easy now, painful to fix later):
- Environment variables for API URLs - Use
EXPO_PUBLIC_API_URLin.env - Touch-optimized interactions - Use
Pressablewith proper feedback - 44px minimum touch targets - All interactive elements must be thumb-friendly
- Test on Expo Go - Check on real devices early and often
- Proper keyboard types -
keyboardType="email-address",keyboardType="phone-pad" - Safe area handling - Use
react-native-safe-area-contextfor notches/home indicators
DON'T do these (they create issues):
- Web-specific APIs (localStorage, document, window) - use Expo equivalents
- Hover states (no cursor on mobile) - use active/pressed states instead
- Complex gesture libraries - use React Native's built-in Pressable first
- React Native's Image component - use expo-image instead
New Architecture Enabled:
- This project uses React Native's New Architecture (Fabric renderer + TurboModules)
- Required for Reanimated v4 and NativeWind v4
- SDK 55+ will make it mandatory
- Most libraries are compatible via interop layer
Important Expo Modules:
File System (Object-Oriented API):
import { File, Directory, Paths } from 'expo-file-system';
// Create files with object-oriented API (no manual URI construction)
const dataFile = new File(Paths.cache, '[category]', '[filename].json');
// Check if file exists
if (!dataFile.exists) {
dataFile.create();
}
// Read/write operations
await dataFile.write(data); // Write data
const content = await dataFile.text(); // Read as text
const bytes = await dataFile.bytes(); // Read as Uint8Array
const base64 = await dataFile.base64(); // Read as base64
// File operations
dataFile.copy(destination); // Copy file
dataFile.move(new Directory(Paths.document, 'saved')); // Move (URI auto-updates)
dataFile.delete(); // Delete file
// Directory operations
const dataDir = new Directory(Paths.cache, '[category]');
dataDir.create();
const files = dataDir.list(); // Returns (File | Directory)[]
// Sync methods also available for performance
const contentSync = dataFile.textSync();
// Legacy string-based API still available at 'expo-file-system/legacy' if needed-
NO
forwardRefin new components -refis now a regular prop// ❌ OLD (deprecated) const Input = forwardRef((props, ref) => <input ref={ref} {...props} />); // ✅ NEW function Input({ ref, ...props }) { return <input ref={ref} {...props} />; }
-
NO
Context.Provider- Use Context directly// ❌ OLD <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider> // ✅ NEW <ThemeContext value="dark">{children}</ThemeContext>
-
useRef()REQUIRES argument - TypeScript will error without it// ❌ BREAKS const ref = useRef(); // ✅ FIX const ref = useRef<[YourType]>(null);
-
Ref callbacks MUST NOT return implicitly - Use explicit blocks
// ❌ BREAKS in TypeScript <div ref={current => (instance = current)} /> // ✅ FIX <div ref={current => { instance = current }} />
✨ New Patterns to Adopt:
-
use()hook - Read promises/context in renderimport { use } from 'react'; const data = use(dataPromise); // Suspends until resolved const theme = use(ThemeContext); // Read context
-
Actions with
useActionState- Replace manual form state managementconst [error, submitAction, isPending] = useActionState( async (prevState, formData) => { const result = await updateData(formData.get("name")); if (result.error) return result.error; return null; }, null ); return ( <form action={submitAction}> <input name="name" /> <button disabled={isPending}>Submit</button> {error && <p>{error}</p>} </form> );
-
useOptimistic- Instant UI updates with automatic rollbackconst [optimisticName, setOptimisticName] = useOptimistic(currentName); const handleSubmit = async (formData) => { setOptimisticName(formData.get("name")); // Show immediately await updateName(formData.get("name")); // Reverts on error };
-
useFormStatus- Track form submission state in child componentsimport { useFormStatus } from 'react-dom'; function SubmitButton() { const { pending } = useFormStatus(); return <button disabled={pending}>Submit</button>; }
Migration Notes:
- Run
npx types-react-codemod@latest preset-19 ./[mobile-dir]to auto-fix TypeScript issues - Test in Strict Mode - React 19 has stricter enforcement
- Existing
forwardRefstill works but is deprecated (will be removed in future)
- [List your app-specific mobile features and patterns]
- [Navigation rules specific to your app's flow]
- [Edge cases unique to your domain]
- [Offline scenarios and how to handle them]
- [Any domain-specific Expo modules you're using]
- Use Svelte 5 runes syntax -
$state(),$derived(),$effect(),$props() - [Define admin capabilities - read-only monitoring, data modification, etc.]
- Server-side render for optimal performance with proper SSR authentication
- Use
@supabase/ssrfor cookie-based authentication management - [Describe what updates and how - polling, manual refresh, etc.]
- Responsive design - admins might check on mobile
- Performance patterns: Parallelize database queries with Promise.all()
- Key Metrics to track:
- [List your key metrics]
- [List your KPIs]
- [List your monitoring needs]
IMPORTANT: Use Svelte 5 runes, NOT the old Svelte 4 reactivity syntax
// Component with props - use $props() rune, NOT export let
<script lang="ts">
interface Props {
[yourProps]: [types];
}
let { [props] }: Props = $props();
// Reactive state - use $state() rune
let [stateVar] = $state<[type]>([initialValue]);
// Computed values - use $derived() rune
let [computed] = $derived([computation]);
// Side effects - use $effect() rune
$effect(() => {
// Side effects like fetching data, subscriptions, etc.
const timer = setInterval(() => {
// Periodic updates
}, 5000);
return () => clearInterval(timer);
});
</script>Key Svelte 5 patterns for [PROJECT_NAME]:
- Use
$state()for all reactive variables in components - Use
$derived()for computed [your metrics] - Use
$effect()for Supabase subscriptions and side effects - Use
$props()with TypeScript interfaces for type-safe props - Prefer runes over stores for component-local state
- Use SvelteKit's page/layout data for server state
- Use snippets for reusable template fragments
[Keep this section as-is - it's universal]
These patterns prevent common errors when using runes:
// ❌ WRONG - Becomes object when imported
export let count = $state(0);
// ✅ CORRECT - Export object and modify properties
export const counter = $state({ count: 0 });
export function increment() {
counter.count += 1;
}<!-- ❌ WRONG in runes mode -->
{#each items as item}
<input bind:value={item}>
{/each}
<!-- ✅ CORRECT - Use array indexing -->
{#each items as item, i}
<input bind:value={items[i]}>
{/each}// ❌ WRONG - Can't use runes in cleanup
$effect(() => {
return () => {
count = $state(0); // ERROR!
};
});
// ✅ CORRECT - $inspect.trace must be FIRST
$effect(() => {
$inspect.trace(); // Must be first!
doWork();
});- Store all timestamps in UTC
- Let database handle [your time-based operations] with triggers and pg_cron
- Soft delete for user accounts (GDPR compliance)
- Use UUID primary keys for all tables
- [Your partitioning strategy if applicable]
- [Your indexing strategy for performance]
- ❌ Unit tests - TypeScript is your safety net
- ❌ Integration tests - manual testing is faster for PoC
- ❌ E2E tests - too much setup overhead
- ❌ Performance testing - premature optimization
Keep it simple for PoC:
// Simple loading states - no complex state machines
type DataState<T> = {
data: T | null;
loading: boolean;
error: string | null;
};
// Basic error handling - user-friendly messages only
try {
// operation
} catch (error) {
console.error(error); // Full error for debugging
setError('Something went wrong. Please try again.'); // Generic for user
}Mobile (React Native):
- Controlled inputs for simple forms
- Basic validation on submit, not on every keystroke
- Show errors after blur or submit attempt
Web (SvelteKit):
- Use form actions with progressive enhancement
- Server-side validation is mandatory
- Client validation is nice-to-have for UX
// Simple validation pattern - both platforms
const validate[Field] = ([field]: string): boolean => {
return [validation logic];
};CRITICAL RULE: All user-facing text MUST use translation keys, never hardcoded strings.
Technology Stack:
- i18next + react-i18next - Translation framework with hook-based API
- expo-localization - Device locale detection
- AsyncStorage persistence - User language preference storage (works with Expo Go)
Development Workflow:
// ✅ CORRECT - Use translation keys
import { useTranslation } from 'react-i18next';
function MyComponent() {
const { t } = useTranslation();
return <Text>{t('common.welcome')}</Text>;
}
// ❌ WRONG - Never hardcode user-facing text
function MyComponent() {
return <Text>Welcome</Text>;
}Implementation Guidelines:
-
Only maintain English during development
- Add new keys to
[mobile|app]/lib/i18n/locales/en.jsonas needed - Other languages (Spanish, German, etc.) added when app is stable
- Translation key structure:
namespace.key(e.g.,common.cancel,errors.network)
- Add new keys to
-
When to add new keys
- Every button label, screen title, error message, placeholder text
- UI feedback messages (loading, success, error states)
- Navigation labels, form fields, tooltips
- Rule of thumb: If a user can see it, it must be translatable
- ❌ Custom UI components when library components work
- ❌ Complex state management - keep it flat and simple
- ❌ Offline-first architecture - assume connectivity (with graceful degradation)
- ❌ Micro-optimizations - 100ms vs 200ms doesn't matter for MVP
- ❌ Perfect responsive design - focus on primary screen sizes
- ❌ [Add your domain-specific anti-patterns]
- ❌ Error recovery flows - simple retry is enough
- ❌ Data migrations - okay to reset database during development
- ✅ Core user journey works end-to-end ([describe your critical path])
- ✅ TypeScript compiles without errors
- ✅ Basic error messages for common failures
- ✅ Manual testing of happy path
- ✅ [Your critical security/privacy features] are bulletproof
- ✅ [Your critical features] work reliably
- TypeScript:
npx tsc --noEmit- Must compile without errors [mobile/admin as applicable] - Manual Test: Does the feature work in Expo Go/simulator?
- Supabase: Are RLS policies working? Test cross-user data isolation
- [Your Critical Feature]: [Your verification step]
- [Your Other Critical Feature]: [Your verification step]
When adapting this template:
- Replace all [PLACEHOLDERS] with your project-specific information
- Vision statement - Write a clear, concise vision in section 1
- Remove sections that don't apply:
- SECURITY DEFINER if no cross-user data access needed
- Admin dashboard if building mobile-only
- Any domain-specific features you don't need
- Add sections for your unique requirements:
- Payment processing patterns
- AI/ML integrations
- Third-party API integrations
- Custom hardware interactions
- Set up i18n from day one:
- Create
lib/i18n/structure - Initialize
en.jsonwith your first translation keys - Configure TypeScript types for autocomplete
- Never hardcode user-facing text
- Create
- Configure Expo SDK 54 patterns:
- Set up file-based routing with Expo Router
- Configure NativeWind v4 for styling
- Set up expo-image, expo-secure-store, AsyncStorage
- Define domain-specific security requirements:
- Privacy concerns specific to your app
- Data retention policies
- What gets encrypted, what gets purged
- List your actual database functions instead of placeholders
- Define state management needs:
- What goes in Zustand vs React Query
- [Your data fetching patterns]
- Document your admin dashboard:
- Key metrics and KPIs to track
- Monitoring and management needs
- Who has access and why
- List your actual quick commands for development and deployment
- Test your instructions with Claude Code:
- Ask it to implement a simple feature
- Verify it follows your patterns
- Refine based on what works
Remember: This is a living document. Update it as you learn what works for your project and your AI assistant. The best instructions evolve with your codebase.