Skip to main content

Detailed Package Specifications

Comprehensive technical specifications for reptidex’s 2 shared packages, including internal modules, APIs, and implementation details.

@reptidex/ui Package

Design System Module

Purpose: Design tokens, themes, and foundational design system elements
Module: @reptidex/ui/design-system Size: ~20KB gzipped Dependencies: Tailwind CSS, Class Variance Authority Exports: Forest & Stone tokens, themes, utilities
Design Tokens:
export const forestStoneTokens = {
  colors: {
    deepPine: {
      50: '#f7f8f6',
      100: '#e8ebe4',
      500: '#3B4031',  // Deep Pine - primary text/headings
      600: '#2d3125',
      700: '#1f221a',
      900: '#0f100e'
    },
    mossGreen: {
      50: '#f4f6f1',
      100: '#e3e8dc',
      500: '#8A9A5B',  // Moss Green - primary buttons/links
      600: '#6f7d4a',
      700: '#555f39',
      900: '#2a2f1c'
    },
    warmSand: {
      50: '#fdfcfa',
      100: '#f7f4ee',
      500: '#D9CBA3',  // Warm Sand - secondary surfaces
      600: '#c9b588',
      700: '#b39f6d',
      900: '#5a4f36'
    },
    softClay: {
      50: '#fdf8f5',
      100: '#f6ebe1',
      500: '#C68642',  // Soft Clay - accents/CTAs
      600: '#a66d35',
      700: '#865428',
      900: '#432a14'
    },
    offWhite: {
      50: '#ffffff',
      100: '#F6F3EE',  // Off-White - base background
      200: '#f0ebe3',
      500: '#e8e1d6'
    }
  },
  spacing: {
    xs: '0.25rem',   // 4px
    sm: '0.5rem',    // 8px  
    md: '1rem',      // 16px
    lg: '1.5rem',    // 24px
    xl: '2rem',      // 32px
    '2xl': '3rem',   // 48px
    '3xl': '4rem'    // 64px
  },
  typography: {
    fontFamily: {
      heading: ['Merriweather', 'serif'],
      body: ['Roboto', 'sans-serif'],
      accent: ['Playfair Display', 'serif'],
      mono: ['JetBrains Mono', 'Consolas', 'monospace']
    },
    fontSize: {
      xs: ['0.75rem', { lineHeight: '1rem' }],
      sm: ['0.875rem', { lineHeight: '1.25rem' }], 
      base: ['1rem', { lineHeight: '1.5rem' }],
      lg: ['1.125rem', { lineHeight: '1.75rem' }],
      xl: ['1.25rem', { lineHeight: '1.75rem' }],
      '2xl': ['1.5rem', { lineHeight: '2rem' }],
      '3xl': ['1.875rem', { lineHeight: '2.25rem' }]
    }
  },
  borderRadius: {
    none: '0',
    sm: '8px',     // Small elements
    md: '16px',    // Standard components
    lg: '24px',    // Large cards
    full: '50%'    // Circular elements
  },
  shadows: {
    sm: '0 2px 8px rgba(59, 64, 49, 0.06)',   // Subtle lift
    md: '0 6px 24px rgba(59, 64, 49, 0.08)',  // Standard cards
    lg: '0 12px 40px rgba(59, 64, 49, 0.12)', // Modals, overlays
    xl: '0 24px 64px rgba(59, 64, 49, 0.16)'  // Maximum elevation
  }
};
Theme Configuration:
import { forestStoneTokens } from './tokens';

export const forestStoneTheme = {
  colors: forestStoneTokens.colors,
  typography: forestStoneTokens.typography,
  spacing: forestStoneTokens.spacing,
  borderRadius: forestStoneTokens.borderRadius,
  shadows: forestStoneTokens.shadows
};

// Tailwind CSS configuration
export const tailwindConfig = {
  theme: {
    extend: {
      colors: {
        'deep-pine': forestStoneTokens.colors.deepPine,
        'moss-green': forestStoneTokens.colors.mossGreen,
        'warm-sand': forestStoneTokens.colors.warmSand,
        'soft-clay': forestStoneTokens.colors.softClay,
        'off-white': forestStoneTokens.colors.offWhite
      },
      fontFamily: {
        heading: forestStoneTokens.typography.fontFamily.heading,
        body: forestStoneTokens.typography.fontFamily.body,
        accent: forestStoneTokens.typography.fontFamily.accent
      },
      borderRadius: forestStoneTokens.borderRadius,
      boxShadow: forestStoneTokens.shadows
    }
  }
};

export const darkTheme = createTheme({
  ...lightTheme,
  palette: {
    ...lightTheme.palette,
    mode: 'dark',
    background: {
      default: designTokens.colors.neutral[900],
      paper: designTokens.colors.neutral[800]
    }
  }
});

Component Library Module

Purpose: Core UI components for all reptidex applications
Module: @reptidex/ui/components Size: ~100KB gzipped Dependencies: Radix UI, Class Variance Authority, Tailwind CSS Components: 35+ Forest & Stone components
Component Categories:

Layout Components

  • Container, Grid, Stack, Flex
  • Section, Sidebar, Header
  • ResponsiveLayout, MobileDrawer

Data Display

  • AnimalCard, AnimalGrid, AnimalTable
  • PedigreeTree, GeneticChart
  • MorphBadge, StatusBadge

Forms & Inputs

  • FormField, FormSection, FormWizard
  • AnimalForm, BreedingForm
  • ImageUpload, DatePicker

Navigation

  • NavBar, Breadcrumbs, Tabs
  • Pagination, SortControls
  • SearchBox, FilterPanel
AnimalCard Component:
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../utils';
import { Animal, Morph } from '@reptidex/core';

interface AnimalCardProps {
  animal: Animal;
  variant?: 'compact' | 'detailed' | 'grid';
  showActions?: boolean;
  showPedigree?: boolean;
  onSelect?: (animal: Animal) => void;
  onEdit?: (animal: Animal) => void;
}

const animalCardVariants = cva(
  'relative overflow-hidden rounded-md border border-warm-sand-200 bg-white shadow-sm transition-all duration-200',
  {
    variants: {
      variant: {
        compact: 'w-full max-w-sm',
        detailed: 'w-full',
        grid: 'aspect-square w-full'
      },
      interactive: {
        true: 'cursor-pointer hover:-translate-y-1 hover:shadow-md',
        false: 'cursor-default'
      }
    },
    defaultVariants: {
      variant: 'detailed',
      interactive: false
    }
  }
);

export const AnimalCard: React.FC<AnimalCardProps> = ({
  animal,
  variant = 'detailed',
  showActions = true,
  showPedigree = false,
  onSelect,
  onEdit
}) => {
  const handleCardClick = () => {
    if (onSelect) onSelect(animal);
  };

  return (
    <div
      className={cn(animalCardVariants({
        variant,
        interactive: !!onSelect
      }))}
      onClick={handleCardClick}
    >
      {/* Animal Image */}
      <Box sx={{ position: 'relative', height: variant === 'compact' ? 120 : 200 }}>
        <Image
          src={animal.primaryImage?.url || '/placeholder-snake.jpg'}
          alt={`${animal.name} - ${animal.species.commonName}`}
          fill
          style={{ objectFit: 'cover' }}
        />
        {animal.status !== 'available' && (
          <Chip
            label={animal.status.toUpperCase()}
            size="small"
            color="warning"
            sx={{ position: 'absolute', top: 8, right: 8 }}
          />
        )}
      </Box>

      <CardContent>
        <Typography variant="h6" component="h3" gutterBottom>
          {animal.name}
        </Typography>
        
        <Typography variant="body2" color="text.secondary" gutterBottom>
          {animal.species.commonName} • {animal.sex} • {animal.age} old
        </Typography>

        {/* Morph Badges */}
        <Box sx={{ mt: 1, mb: 1 }}>
          {animal.morphs.slice(0, variant === 'compact' ? 2 : 4).map((morph) => (
            <MorphBadge 
              key={morph.id} 
              morph={morph} 
              size="small" 
              sx={{ mr: 0.5, mb: 0.5 }}
            />
          ))}
          {animal.morphs.length > (variant === 'compact' ? 2 : 4) && (
            <Chip 
              label={`+${animal.morphs.length - (variant === 'compact' ? 2 : 4)} more`}
              size="small"
              variant="outlined"
            />
          )}
        </Box>

        {variant === 'detailed' && (
          <>
            <Typography variant="body2" gutterBottom>
              <strong>Lineage:</strong> {animal.lineage?.completeness || 0}% complete
            </Typography>
            
            {animal.price && (
              <Typography variant="h6" color="primary" sx={{ mt: 1 }}>
                ${animal.price.toLocaleString()}
              </Typography>
            )}
          </>
        )}

        {showPedigree && animal.pedigree && (
          <Box sx={{ mt: 2 }}>
            <Typography variant="subtitle2" gutterBottom>
              Pedigree Preview:
            </Typography>
            <PedigreePreview pedigree={animal.pedigree} maxGenerations={2} />
          </Box>
        )}
      </CardContent>

      {showActions && (
        <CardActions>
          <Button size="small" onClick={() => onSelect?.(animal)}>
            View Details
          </Button>
          {onEdit && (
            <Button size="small" onClick={() => onEdit(animal)}>
              Edit
            </Button>
          )}
        </CardActions>
      )}
    </Card>
  );
};
MorphBadge Component:
interface MorphBadgeProps {
  morph: Morph;
  size?: 'small' | 'medium';
  showInheritance?: boolean;
}

const morphBadgeVariants = cva(
  'inline-flex items-center rounded-sm font-medium text-white',
  {
    variants: {
      size: {
        small: 'px-2 py-1 text-xs',
        medium: 'px-3 py-1.5 text-sm'
      },
      inheritance: {
        recessive: 'bg-blue-600',
        codominant: 'bg-amber-600',
        dominant: 'bg-moss-green-600',
        'incomplete-dominant': 'bg-purple-600',
        unknown: 'bg-gray-500'
      }
    },
    defaultVariants: {
      size: 'medium',
      inheritance: 'unknown'
    }
  }
);

export const MorphBadge: React.FC<MorphBadgeProps> = ({
  morph,
  size = 'medium',
  showInheritance = false
}) => {
  return (
    <span
      className={cn(morphBadgeVariants({
        size,
        inheritance: morph.inheritance as any
      }))}
    >
      {showInheritance ? `${morph.name} (${morph.inheritance})` : morph.name}
    </span>
  );
};

Charts & Visualization Module

Purpose: Data visualization components for analytics and reporting
Module: @reptidex/ui/charts Size: ~40KB gzipped Dependencies: Recharts, Tailwind CSS Chart Types: Line, Bar, Pie, Scatter, Heatmap with Forest & Stone styling
BreedingAnalyticsChart Component:
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { BreedingRecord } from '@reptidex/core';

interface BreedingAnalyticsProps {
  data: BreedingRecord[];
  dateRange: { start: Date; end: Date };
  groupBy: 'month' | 'quarter' | 'year';
  metrics: ('success_rate' | 'hatch_rate' | 'fertility_rate')[];
}

export const BreedingAnalyticsChart: React.FC<BreedingAnalyticsProps> = ({
  data,
  dateRange,
  groupBy,
  metrics
}) => {
  const chartData = useMemo(() => {
    return aggregateBreedingData(data, groupBy, dateRange);
  }, [data, groupBy, dateRange]);

  const metricConfig = {
    success_rate: { color: '#2e7d32', name: 'Success Rate' },
    hatch_rate: { color: '#1976d2', name: 'Hatch Rate' },  
    fertility_rate: { color: '#ed6c02', name: 'Fertility Rate' }
  };

  return (
    <Box sx={{ width: '100%', height: 400 }}>
      <ResponsiveContainer>
        <LineChart data={chartData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis 
            dataKey="period" 
            tickFormatter={(value) => formatPeriod(value, groupBy)}
          />
          <YAxis 
            tickFormatter={(value) => `${value}%`}
            domain={[0, 100]}
          />
          <Tooltip content={<CustomTooltip />} />
          
          {metrics.map((metric) => (
            <Line
              key={metric}
              type="monotone"
              dataKey={metric}
              stroke={metricConfig[metric].color}
              strokeWidth={2}
              name={metricConfig[metric].name}
              dot={{ fill: metricConfig[metric].color }}
            />
          ))}
        </LineChart>
      </ResponsiveContainer>
    </Box>
  );
};

const CustomTooltip = ({ active, payload, label }) => {
  if (!active || !payload?.length) return null;

  return (
    <Paper sx={{ p: 2, border: '1px solid', borderColor: 'divider' }}>
      <Typography variant="subtitle2" gutterBottom>
        {label}
      </Typography>
      {payload.map((entry, index) => (
        <Typography key={index} variant="body2" sx={{ color: entry.color }}>
          {entry.name}: {entry.value}%
        </Typography>
      ))}
    </Paper>
  );
};

@reptidex/core Package

API Client Module

Purpose: Type-safe API clients for all reptidex services
Module: @reptidex/core/api
Size: ~35KB gzipped
Dependencies: Axios, React Query, Zod
Services: 6 backend service clients
Base API Client:
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
import { authStore } from '../stores/auth';

export class BaseAPIClient {
  protected client: AxiosInstance;
  
  constructor(config: APIClientConfig) {
    this.client = axios.create({
      baseURL: config.baseURL,
      timeout: config.timeout || 10000,
      headers: {
        'Content-Type': 'application/json'
      }
    });

    this.setupInterceptors();
  }

  private setupInterceptors(): void {
    // Request interceptor for authentication
    this.client.interceptors.request.use(async (config) => {
      const token = authStore.getAccessToken();
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    });

    // Response interceptor for error handling
    this.client.interceptors.response.use(
      (response) => response,
      async (error) => {
        if (error.response?.status === 401) {
          const refreshed = await authStore.refreshToken();
          if (refreshed) {
            return this.client.request(error.config);
          }
          authStore.logout();
        }
        return Promise.reject(error);
      }
    );
  }

  protected async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.get(url, config);
    return response.data;
  }

  protected async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.post(url, data, config);
    return response.data;
  }

  protected async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.put(url, data, config);
    return response.data;
  }

  protected async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.delete(url, config);
    return response.data;
  }
}
Animals API Client:
import { BaseAPIClient } from './base';
import { Animal, CreateAnimalRequest, UpdateAnimalRequest, SearchFilters } from '../types';

export class AnimalsAPIClient extends BaseAPIClient {
  constructor() {
    super({ 
      baseURL: process.env.NEXT_PUBLIC_REPTI_ANIMAL_API_URL || 'http://localhost:8002'
    });
  }

  async getAnimals(filters: SearchFilters = {}): Promise<PaginatedResponse<Animal>> {
    const params = new URLSearchParams();
    
    if (filters.species) params.append('species', filters.species);
    if (filters.morphs?.length) params.append('morphs', filters.morphs.join(','));
    if (filters.sex) params.append('sex', filters.sex);
    if (filters.priceRange) {
      params.append('price_min', filters.priceRange[0].toString());
      params.append('price_max', filters.priceRange[1].toString());
    }
    if (filters.page) params.append('page', filters.page.toString());
    if (filters.limit) params.append('limit', filters.limit.toString());

    return this.get<PaginatedResponse<Animal>>(`/animals?${params.toString()}`);
  }

  async getAnimal(id: string): Promise<Animal> {
    return this.get<Animal>(`/animals/${id}`);
  }

  async createAnimal(animalData: CreateAnimalRequest): Promise<Animal> {
    return this.post<Animal>('/animals', animalData);
  }

  async updateAnimal(id: string, animalData: UpdateAnimalRequest): Promise<Animal> {
    return this.put<Animal>(`/animals/${id}`, animalData);
  }

  async deleteAnimal(id: string): Promise<void> {
    return this.delete(`/animals/${id}`);
  }

  async getPedigree(animalId: string, generations: number = 4): Promise<Pedigree> {
    return this.get<Pedigree>(`/animals/${animalId}/pedigree?generations=${generations}`);
  }

  async getOffspring(animalId: string): Promise<Animal[]> {
    return this.get<Animal[]>(`/animals/${animalId}/offspring`);
  }
}

export const animalsAPI = new AnimalsAPIClient();
React Query Hooks:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { animalsAPI } from '../api/animals';
import { SearchFilters, CreateAnimalRequest } from '../types';

export function useAnimals(filters: SearchFilters = {}) {
  return useQuery({
    queryKey: ['animals', filters],
    queryFn: () => animalsAPI.getAnimals(filters),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
    keepPreviousData: true
  });
}

export function useAnimal(id: string) {
  return useQuery({
    queryKey: ['animals', id],
    queryFn: () => animalsAPI.getAnimal(id),
    enabled: !!id,
    staleTime: 10 * 60 * 1000 // 10 minutes
  });
}

export function useCreateAnimal() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (animalData: CreateAnimalRequest) => animalsAPI.createAnimal(animalData),
    onSuccess: (newAnimal) => {
      // Invalidate and refetch animals list
      queryClient.invalidateQueries({ queryKey: ['animals'] });
      // Add to cache
      queryClient.setQueryData(['animals', newAnimal.id], newAnimal);
    }
  });
}

export function usePedigree(animalId: string, generations: number = 4) {
  return useQuery({
    queryKey: ['pedigree', animalId, generations],
    queryFn: () => animalsAPI.getPedigree(animalId, generations),
    enabled: !!animalId,
    staleTime: 30 * 60 * 1000 // 30 minutes - pedigrees don't change often
  });
}

Business Logic Module

Purpose: reptidex-specific business logic and calculations
Module: @reptidex/core/business
Size: ~25KB gzipped
Dependencies: date-fns, lodash
Features: Genetic calculations, breeding logic, pricing algorithms
Genetic Calculations:
interface GeneticTraits {
  [gene: string]: 'normal' | 'het' | 'homo' | 'super';
}

interface GeneticProbability {
  phenotype: string;
  genotype: GeneticTraits;
  probability: number;
}

export function calculateGeneticProbability(
  sire: GeneticTraits,
  dam: GeneticTraits,
  targetMorphs?: string[]
): GeneticProbability[] {
  const allGenes = new Set([...Object.keys(sire), ...Object.keys(dam)]);
  const results: GeneticProbability[] = [];

  // Generate all possible combinations
  const combinations = generateGeneticCombinations(sire, dam, Array.from(allGenes));
  
  // Calculate probabilities for each combination
  const probabilityMap = new Map<string, number>();
  
  combinations.forEach(combination => {
    const phenotype = determinePhenotype(combination.genotype);
    const key = JSON.stringify(combination.genotype);
    
    probabilityMap.set(key, (probabilityMap.get(key) || 0) + combination.probability);
  });

  // Convert to result format
  probabilityMap.forEach((probability, genotypeKey) => {
    const genotype = JSON.parse(genotypeKey) as GeneticTraits;
    const phenotype = determinePhenotype(genotype);
    
    results.push({
      phenotype,
      genotype,
      probability: Math.round(probability * 100)
    });
  });

  // Filter by target morphs if specified
  if (targetMorphs?.length) {
    return results.filter(result => 
      targetMorphs.some(morph => result.phenotype.includes(morph))
    );
  }

  return results.sort((a, b) => b.probability - a.probability);
}

function generateGeneticCombinations(
  sire: GeneticTraits,
  dam: GeneticTraits,
  genes: string[]
): { genotype: GeneticTraits; probability: number }[] {
  // Implementation of Punnett square logic for multiple genes
  const combinations: { genotype: GeneticTraits; probability: number }[] = [];
  
  // For each gene, calculate possible allele combinations
  genes.forEach(gene => {
    const sireAlleles = getAlleles(sire[gene] || 'normal');
    const damAlleles = getAlleles(dam[gene] || 'normal');
    
    // Generate all possible offspring genotypes for this gene
    sireAlleles.forEach(sireAllele => {
      damAlleles.forEach(damAllele => {
        const offspringGenotype = combineAlleles(sireAllele, damAllele);
        // Add to combinations with appropriate probability
      });
    });
  });

  return combinations;
}

function determinePhenotype(genotype: GeneticTraits): string {
  const expressedTraits: string[] = [];
  
  Object.entries(genotype).forEach(([gene, expression]) => {
    switch (expression) {
      case 'homo':
      case 'super':
        expressedTraits.push(gene);
        break;
      case 'het':
        // Some genes express in heterozygous form (codominant)
        if (isCodeminant(gene)) {
          expressedTraits.push(`${gene} het`);
        }
        break;
    }
  });

  return expressedTraits.length > 0 ? expressedTraits.join(' ') : 'normal';
}
Breeding Program Validation:
interface BreedingPair {
  sire: Animal;
  dam: Animal;
  expectedPairingDate: Date;
  notes?: string;
}

interface ValidationResult {
  valid: boolean;
  errors: string[];
  warnings: string[];
}

export function validateBreedingPair(pair: BreedingPair): ValidationResult {
  const errors: string[] = [];
  const warnings: string[] = [];

  // Age validation
  if (pair.sire.age < 12) { // months
    errors.push('Male must be at least 12 months old');
  }
  if (pair.dam.age < 18) { // months  
    errors.push('Female must be at least 18 months old');
  }

  // Weight validation (species-specific)
  const minWeights = getMinBreedingWeights(pair.sire.species.id);
  if (pair.sire.weight < minWeights.male) {
    errors.push(`Male weight (${pair.sire.weight}g) below minimum (${minWeights.male}g)`);
  }
  if (pair.dam.weight < minWeights.female) {
    errors.push(`Female weight (${pair.dam.weight}g) below minimum (${minWeights.female}g)`);
  }

  // Health status validation
  if (pair.sire.healthStatus !== 'healthy') {
    errors.push('Male must be in healthy condition');
  }
  if (pair.dam.healthStatus !== 'healthy') {
    errors.push('Female must be in healthy condition');
  }

  // Genetic compatibility warnings
  const geneticAnalysis = analyzeGeneticCompatibility(pair.sire, pair.dam);
  if (geneticAnalysis.inbreedingCoefficient > 0.125) {
    warnings.push('High inbreeding coefficient detected');
  }
  if (geneticAnalysis.lethalCombinations.length > 0) {
    errors.push(`Potentially lethal combinations: ${geneticAnalysis.lethalCombinations.join(', ')}`);
  }

  // Seasonal breeding considerations
  const breedingSeason = getOptimalBreedingSeason(pair.sire.species.id);
  const pairingMonth = pair.expectedPairingDate.getMonth();
  if (!breedingSeason.includes(pairingMonth)) {
    warnings.push('Pairing outside optimal breeding season');
  }

  return {
    valid: errors.length === 0,
    errors,
    warnings
  };
}

Authentication & Authorization Module

Purpose: User authentication, session management, and access control
Module: @reptidex/core/auth
Size: ~15KB gzipped
Dependencies: JWT decode, Zustand
Features: JWT handling, RBAC, protected routes
Authentication Store:
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { jwtDecode } from 'jwt-decode';

interface User {
  id: string;
  email: string;
  firstName: string;
  lastName: string;
  roles: string[];
  organizationId?: string;
  subscription?: {
    tier: 'free' | 'breeder' | 'premium';
    status: 'active' | 'cancelled' | 'past_due';
  };
}

interface AuthState {
  user: User | null;
  accessToken: string | null;
  refreshToken: string | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  refreshTokens: () => Promise<boolean>;
  updateUser: (updates: Partial<User>) => void;
  hasRole: (role: string) => boolean;
  hasPermission: (permission: string) => boolean;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set, get) => ({
      user: null,
      accessToken: null,
      refreshToken: null,
      isAuthenticated: false,
      isLoading: false,

      login: async (email: string, password: string) => {
        set({ isLoading: true });
        
        try {
          const response = await fetch('/api/auth/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email, password })
          });

          if (!response.ok) {
            throw new Error('Login failed');
          }

          const { accessToken, refreshToken, user } = await response.json();
          
          set({
            user,
            accessToken,
            refreshToken, 
            isAuthenticated: true,
            isLoading: false
          });
        } catch (error) {
          set({ isLoading: false });
          throw error;
        }
      },

      logout: () => {
        set({
          user: null,
          accessToken: null,
          refreshToken: null,
          isAuthenticated: false
        });
      },

      refreshTokens: async () => {
        const { refreshToken } = get();
        if (!refreshToken) return false;

        try {
          const response = await fetch('/api/auth/refresh', {
            method: 'POST',
            headers: { 
              'Content-Type': 'application/json',
              'Authorization': `Bearer ${refreshToken}`
            }
          });

          if (!response.ok) {
            throw new Error('Token refresh failed');
          }

          const { accessToken, refreshToken: newRefreshToken } = await response.json();
          
          set({ 
            accessToken, 
            refreshToken: newRefreshToken 
          });

          return true;
        } catch (error) {
          get().logout();
          return false;
        }
      },

      updateUser: (updates: Partial<User>) => {
        set(state => ({
          user: state.user ? { ...state.user, ...updates } : null
        }));
      },

      hasRole: (role: string) => {
        const { user } = get();
        return user?.roles.includes(role) || false;
      },

      hasPermission: (permission: string) => {
        const { user } = get();
        if (!user) return false;

        // Role-based permissions mapping
        const rolePermissions: Record<string, string[]> = {
          admin: ['*'], // All permissions
          breeder: [
            'animals:read',
            'animals:write', 
            'breeding:read',
            'breeding:write',
            'marketplace:read',
            'marketplace:write'
          ],
          user: ['animals:read', 'marketplace:read']
        };

        return user.roles.some(role => 
          rolePermissions[role]?.includes('*') || 
          rolePermissions[role]?.includes(permission)
        );
      }
    }),
    {
      name: 'auth-storage',
      partialize: (state) => ({
        user: state.user,
        accessToken: state.accessToken,
        refreshToken: state.refreshToken,
        isAuthenticated: state.isAuthenticated
      })
    }
  )
);
Protected Route Component:
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuthStore } from '../stores/auth';

interface ProtectedRouteProps {
  children: React.ReactNode;
  requiredRoles?: string[];
  requiredPermissions?: string[];
  fallbackUrl?: string;
}

export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
  children,
  requiredRoles = [],
  requiredPermissions = [],
  fallbackUrl = '/login'
}) => {
  const router = useRouter();
  const { isAuthenticated, hasRole, hasPermission, isLoading } = useAuthStore();

  useEffect(() => {
    if (isLoading) return;

    if (!isAuthenticated) {
      router.push(`${fallbackUrl}?redirect=${encodeURIComponent(router.asPath)}`);
      return;
    }

    // Check role requirements
    if (requiredRoles.length > 0 && !requiredRoles.some(role => hasRole(role))) {
      router.push('/unauthorized');
      return;
    }

    // Check permission requirements
    if (requiredPermissions.length > 0 && !requiredPermissions.some(permission => hasPermission(permission))) {
      router.push('/unauthorized');
      return;
    }
  }, [isAuthenticated, isLoading, hasRole, hasPermission, router, requiredRoles, requiredPermissions, fallbackUrl]);

  if (isLoading) {
    return <div>Loading...</div>;
  }

  if (!isAuthenticated) {
    return null;
  }

  return <>{children}</>;
};

// Hook for easier usage
export const useAuth = () => {
  const store = useAuthStore();
  
  return {
    user: store.user,
    isAuthenticated: store.isAuthenticated,
    isLoading: store.isLoading,
    login: store.login,
    logout: store.logout,
    hasRole: store.hasRole,
    hasPermission: store.hasPermission
  };
};

Testing & Quality Assurance

Unit Testing

// @reptidex/ui component testing
import { render, screen, fireEvent } from '@testing-library/react';
import { ThemeProvider } from '@mui/material/styles';
import { AnimalCard } from '../components/AnimalCard';
import { lightTheme } from '../design-system/themes';
import { mockAnimal } from '../../__mocks__/animal';

const renderWithTheme = (component: React.ReactElement) => {
  return render(
    <ThemeProvider theme={lightTheme}>
      {component}
    </ThemeProvider>
  );
};

describe('AnimalCard', () => {
  it('renders animal information correctly', () => {
    renderWithTheme(<AnimalCard animal={mockAnimal} />);
    
    expect(screen.getByText(mockAnimal.name)).toBeInTheDocument();
    expect(screen.getByText(mockAnimal.species.commonName)).toBeInTheDocument();
    expect(screen.getByRole('img')).toHaveAttribute('alt', expect.stringContaining(mockAnimal.name));
  });

  it('displays morph badges', () => {
    const animalWithMorphs = { 
      ...mockAnimal, 
      morphs: [
        { id: '1', name: 'Pied', inheritance: 'recessive' },
        { id: '2', name: 'Banana', inheritance: 'codominant' }
      ]
    };
    
    renderWithTheme(<AnimalCard animal={animalWithMorphs} />);
    
    expect(screen.getByText('Pied')).toBeInTheDocument();
    expect(screen.getByText('Banana')).toBeInTheDocument();
  });

  it('calls onSelect when card is clicked', () => {
    const onSelect = jest.fn();
    
    renderWithTheme(<AnimalCard animal={mockAnimal} onSelect={onSelect} />);
    
    fireEvent.click(screen.getByRole('article'));
    expect(onSelect).toHaveBeenCalledWith(mockAnimal);
  });
});
// @reptidex/core business logic testing
import { calculateGeneticProbability } from '../business/genetics';

describe('calculateGeneticProbability', () => {
  it('calculates simple recessive cross correctly', () => {
    const sire = { pied: 'het' };
    const dam = { pied: 'het' };
    
    const result = calculateGeneticProbability(sire, dam);
    
    expect(result).toEqual([
      { phenotype: 'normal', genotype: { pied: 'normal' }, probability: 75 },
      { phenotype: 'pied', genotype: { pied: 'homo' }, probability: 25 }
    ]);
  });

  it('calculates codominant cross correctly', () => {
    const sire = { banana: 'het' };  
    const dam = { banana: 'normal' };
    
    const result = calculateGeneticProbability(sire, dam);
    
    expect(result).toEqual([
      { phenotype: 'normal', genotype: { banana: 'normal' }, probability: 50 },
      { phenotype: 'banana het', genotype: { banana: 'het' }, probability: 50 }
    ]);
  });
});

This detailed specification provides comprehensive documentation for reptidex’s simplified 2-package architecture with Forest & Stone design system, covering all major modules, APIs, and implementation patterns needed for effective development.