Standards & Reference

Code Quality

This document defines the code quality standards, practices, and tools for the Flowstate application.

Table of Contents

  1. Overview
  2. TypeScript Standards
  3. Functional Programming Principles
  4. Component Quality Standards
  5. Linting and Formatting
  6. Code Review Standards
  7. Performance Standards
  8. Maintainability Standards
  9. Quality Metrics
  10. Enforcement

Overview

Code quality is a fundamental pillar of the FlowState Standard. High-quality code reduces bugs, improves maintainability, and enhances developer productivity.

Quality Objectives

ObjectiveDescription
CorrectnessCode behaves as specified
ReliabilityCode handles edge cases gracefully
MaintainabilityCode is easy to understand and modify
PerformanceCode meets performance requirements
SecurityCode follows security best practices
TestabilityCode is designed for easy testing

Quality Pillars

┌─────────────────────────────────────────────────────────────┐
│                    Code Quality                              │
├─────────────┬─────────────┬─────────────┬──────────────────┤
│  Type Safe  │  Functional │   Tested    │   Documented     │
│  TypeScript │  Principles │   Coverage  │   Contracts      │
└─────────────┴─────────────┴─────────────┴──────────────────┘

TypeScript Standards

Strict Type Checking

Enable all strict TypeScript options:

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "useUnknownInCatchVariables": true,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true
  }
}

Type Definition Standards

// ✅ Good: Explicit, descriptive interfaces
interface EventDetails {
  id: string;
  name: string;
  status: EventStatus;
  startDate: Date;
  endDate: Date;
  deliverables: Deliverable[];
}

// ❌ Bad: Implicit any, loose typing
interface EventDetails {
  id: any;
  name: string;
  status: string;  // Should be union type
  startDate: any;  // Should be Date
  deliverables: any[];
}

Union Types over Enums

Prefer union types for type safety with better tree-shaking:

// ✅ Preferred: Union types
type EventStatus = 'on-track' | 'at-risk' | 'delayed';
type DeliverableState = 'draft' | 'in-review' | 'approved' | 'rejected';

// Configuration objects for display
const EVENT_STATUS_CONFIG: Record<EventStatus, { label: string; color: string }> = {
  'on-track': { label: 'On Track', color: 'green' },
  'at-risk': { label: 'At Risk', color: 'yellow' },
  'delayed': { label: 'Delayed', color: 'red' },
};

// ⚠️ Avoid: Enums (unless necessary for reverse mapping)
enum EventStatusEnum {
  OnTrack = 'on-track',
  AtRisk = 'at-risk',
  Delayed = 'delayed',
}

Utility Types

Use TypeScript utility types effectively:

// Partial for optional updates
type EventUpdate = Partial<Event>;

// Pick for selecting properties
type EventSummary = Pick<Event, 'id' | 'name' | 'status'>;

// Omit for excluding properties
type NewEvent = Omit<Event, 'id' | 'createdAt' | 'updatedAt'>;

// Record for object types
type StatusColorMap = Record<EventStatus, string>;

// Required for making properties required
type RequiredEvent = Required<Event>;

// Readonly for immutable data
type ImmutableEvent = Readonly<Event>;

Generics

Use generics for reusable, type-safe code:

// Generic API response wrapper
interface ApiResponse<T> {
  data: T;
  error: string | null;
  status: number;
}

// Generic list component props
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string;
}

// Generic service pattern
class DataService<T extends { id: string }> {
  async get(id: string): Promise<T | null> { /* ... */ }
  async list(): Promise<T[]> { /* ... */ }
  async create(data: Omit<T, 'id'>): Promise<T> { /* ... */ }
  async update(id: string, data: Partial<T>): Promise<T> { /* ... */ }
  async delete(id: string): Promise<void> { /* ... */ }
}

Discriminated Unions

Use discriminated unions for type-safe state handling:

// Result type for operations that can fail
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

// Async state with discriminated union
type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

// Usage with exhaustive checking
function handleAsyncState<T>(state: AsyncState<T>): string {
  switch (state.status) {
    case 'idle':
      return 'Not started';
    case 'loading':
      return 'Loading...';
    case 'success':
      return `Success: ${JSON.stringify(state.data)}`;
    case 'error':
      return `Error: ${state.error.message}`;
    default:
      // Exhaustive check - TypeScript error if a case is missing
      const _exhaustive: never = state;
      return _exhaustive;
  }
}

Functional Programming Principles

Core Principles

These principles are foundational to code quality and directly enhance testability:

PrincipleDescriptionBenefit
ImmutabilityData cannot be changed after creationEliminates side effects
Pure FunctionsSame inputs always produce same outputsPredictable, testable
Referential TransparencyExpressions can be replaced by valuesSimplified debugging
First-Class FunctionsFunctions as valuesComposition, modularity
Disciplined StateControlled, explicit state changesEasier reasoning

Immutability

// ✅ Good: Immutable operations
const updateEvent = (event: Event, updates: Partial<Event>): Event => ({
  ...event,
  ...updates,
  updatedAt: new Date(),
});

const addDeliverable = (event: Event, deliverable: Deliverable): Event => ({
  ...event,
  deliverables: [...event.deliverables, deliverable],
});

// ❌ Bad: Mutating data
function updateEventBad(event: Event, updates: Partial<Event>): Event {
  Object.assign(event, updates);  // Mutation!
  event.updatedAt = new Date();   // Mutation!
  return event;
}

Pure Functions

// ✅ Good: Pure function
const calculateProgress = (deliverables: Deliverable[]): number => {
  if (deliverables.length === 0) return 0;
  const approved = deliverables.filter(d => d.status === 'approved').length;
  return Math.round((approved / deliverables.length) * 100);
};

// ✅ Good: Pure function with date injection
const isOverdue = (dueDate: Date, currentDate: Date = new Date()): boolean => {
  return dueDate < currentDate;
};

// ❌ Bad: Impure - depends on external state
let totalCalculations = 0;
function calculateProgressBad(deliverables: Deliverable[]): number {
  totalCalculations++;  // Side effect!
  console.log('Calculating...');  // Side effect!
  return /* calculation */;
}

Function Composition

// Compose utility
const compose = <T>(...fns: Array<(arg: T) => T>) =>
  (initial: T): T => fns.reduceRight((acc, fn) => fn(acc), initial);

// Pipe utility (left-to-right composition)
const pipe = <T>(...fns: Array<(arg: T) => T>) =>
  (initial: T): T => fns.reduce((acc, fn) => fn(acc), initial);

// Composable transformation functions
const sortByDate = (events: Event[]): Event[] =>
  [...events].sort((a, b) => a.startDate.getTime() - b.startDate.getTime());

const filterByStatus = (status: EventStatus) =>
  (events: Event[]): Event[] => events.filter(e => e.status === status);

const limitTo = (count: number) =>
  <T>(items: T[]): T[] => items.slice(0, count);

// Compose transformations
const getUpcomingDelayedEvents = pipe(
  filterByStatus('delayed'),
  sortByDate,
  limitTo(5)
);

const result = getUpcomingDelayedEvents(allEvents);

Separation of Concerns

// ✅ Good: Separate pure logic from effects
// Pure business logic
const validateEvent = (event: Partial<Event>): ValidationResult => {
  const errors: string[] = [];
  if (!event.name?.trim()) errors.push('Name is required');
  if (!event.startDate) errors.push('Start date is required');
  if (event.endDate && event.startDate && event.endDate < event.startDate) {
    errors.push('End date must be after start date');
  }
  return { valid: errors.length === 0, errors };
};

// Side effects at boundary
const saveEvent = async (event: Event): Promise<Result<Event>> => {
  const validation = validateEvent(event);
  if (!validation.valid) {
    return { success: false, error: new ValidationError(validation.errors) };
  }

  try {
    const saved = await api.events.save(event);
    return { success: true, data: saved };
  } catch (error) {
    return { success: false, error: error as Error };
  }
};

Component Quality Standards

Component Structure

// Standard component structure
import React, { useState, useCallback, useMemo } from 'react';
import { cn } from '@/lib/utils';

// 1. Type definitions at top
interface EventCardProps {
  event: Event;
  onSelect?: (event: Event) => void;
  className?: string;
}

// 2. Constants and helper functions
const STATUS_STYLES = {
  'on-track': 'bg-green-100 text-green-800',
  'at-risk': 'bg-yellow-100 text-yellow-800',
  'delayed': 'bg-red-100 text-red-800',
} as const;

// 3. Component definition
const EventCard: React.FC<EventCardProps> = ({
  event,
  onSelect,
  className,
}) => {
  // 4. Hooks at top of component
  const [isHovered, setIsHovered] = useState(false);

  // 5. Memoized values
  const statusStyle = useMemo(
    () => STATUS_STYLES[event.status],
    [event.status]
  );

  // 6. Callbacks
  const handleClick = useCallback(() => {
    onSelect?.(event);
  }, [event, onSelect]);

  // 7. Render
  return (
    <div
      className={cn('rounded-lg p-4', statusStyle, className)}
      onClick={handleClick}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      {/* Content */}
    </div>
  );
};

// 8. Export
export default EventCard;
export type { EventCardProps };

Component Composition

// Prefer composition over inheritance
// ✅ Good: Composable components
const Card: React.FC<CardProps> = ({ children, className }) => (
  <div className={cn('rounded-lg border bg-white shadow-sm', className)}>
    {children}
  </div>
);

const CardHeader: React.FC<CardHeaderProps> = ({ children, className }) => (
  <div className={cn('border-b p-4', className)}>{children}</div>
);

const CardContent: React.FC<CardContentProps> = ({ children, className }) => (
  <div className={cn('p-4', className)}>{children}</div>
);

// Usage
const EventCard: React.FC<EventCardProps> = ({ event }) => (
  <Card>
    <CardHeader>
      <h3>{event.name}</h3>
    </CardHeader>
    <CardContent>
      <EventDetails event={event} />
    </CardContent>
  </Card>
);

Props Guidelines

// ✅ Good: Clear, typed props
interface ButtonProps {
  /** Button content */
  children: React.ReactNode;
  /** Visual style variant */
  variant?: 'primary' | 'secondary' | 'danger';
  /** Button size */
  size?: 'sm' | 'md' | 'lg';
  /** Disabled state */
  disabled?: boolean;
  /** Loading state - shows spinner */
  loading?: boolean;
  /** Click handler */
  onClick?: () => void;
  /** Additional CSS classes */
  className?: string;
}

// ❌ Bad: Ambiguous, loosely typed props
interface ButtonProps {
  children: any;
  type?: string;  // Too vague
  size?: number;  // Should be predefined sizes
  style?: object; // Avoid inline styles
  click?: Function; // Should be specific function type
}

Hook Quality Standards

// Custom hook structure
import { useState, useEffect, useCallback, useMemo } from 'react';

interface UseEventsOptions {
  status?: EventStatus;
  autoRefresh?: boolean;
  refreshInterval?: number;
}

interface UseEventsReturn {
  events: Event[];
  loading: boolean;
  error: Error | null;
  refetch: () => Promise<void>;
  filter: (status: EventStatus) => void;
}

/**
 * Hook for managing event data with filtering and auto-refresh.
 *
 * @param options - Configuration options
 * @returns Event data, loading state, and control functions
 *
 * @example
 * ```tsx
 * const { events, loading, refetch } = useEvents({
 *   status: 'on-track',
 *   autoRefresh: true,
 * });
 * ```
 */
export function useEvents(options: UseEventsOptions = {}): UseEventsReturn {
  const { status, autoRefresh = false, refreshInterval = 30000 } = options;

  const [events, setEvents] = useState<Event[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);
  const [filterStatus, setFilterStatus] = useState<EventStatus | undefined>(status);

  const fetchEvents = useCallback(async () => {
    try {
      setLoading(true);
      setError(null);
      const data = await eventService.getEvents({ status: filterStatus });
      setEvents(data);
    } catch (err) {
      setError(err instanceof Error ? err : new Error('Failed to fetch events'));
    } finally {
      setLoading(false);
    }
  }, [filterStatus]);

  useEffect(() => {
    fetchEvents();
  }, [fetchEvents]);

  useEffect(() => {
    if (!autoRefresh) return;

    const interval = setInterval(fetchEvents, refreshInterval);
    return () => clearInterval(interval);
  }, [autoRefresh, refreshInterval, fetchEvents]);

  const filter = useCallback((newStatus: EventStatus) => {
    setFilterStatus(newStatus);
  }, []);

  return { events, loading, error, refetch: fetchEvents, filter };
}

Linting and Formatting

ESLint Configuration

// .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    es2022: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:jsx-a11y/recommended',
    'plugin:import/recommended',
    'plugin:import/typescript',
    'prettier',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    project: './tsconfig.json',
    ecmaFeatures: {
      jsx: true,
    },
  },
  plugins: ['@typescript-eslint', 'react', 'import'],
  rules: {
    // TypeScript
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    '@typescript-eslint/no-explicit-any': 'error',
    '@typescript-eslint/prefer-nullish-coalescing': 'error',
    '@typescript-eslint/prefer-optional-chain': 'error',
    '@typescript-eslint/strict-boolean-expressions': 'error',

    // React
    'react/react-in-jsx-scope': 'off',
    'react/prop-types': 'off',
    'react-hooks/rules-of-hooks': 'error',
    'react-hooks/exhaustive-deps': 'warn',

    // Import
    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
        'newlines-between': 'always',
        alphabetize: { order: 'asc' },
      },
    ],
    'import/no-duplicates': 'error',

    // General
    'no-console': ['warn', { allow: ['warn', 'error'] }],
    'prefer-const': 'error',
    'no-var': 'error',
    eqeqeq: ['error', 'always'],
  },
  settings: {
    react: { version: 'detect' },
    'import/resolver': {
      typescript: { project: './tsconfig.json' },
    },
  },
};

Prettier Configuration

// .prettierrc
{
  "semi": false,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5",
  "printWidth": 100,
  "bracketSpacing": true,
  "arrowParens": "avoid",
  "endOfLine": "lf",
  "jsxSingleQuote": false,
  "plugins": ["prettier-plugin-tailwindcss"]
}

Import Order

// Correct import order

// 1. React and core libraries
import React, { useState, useEffect } from 'react';

// 2. Third-party libraries
import { useQuery } from '@tanstack/react-query';
import { format } from 'date-fns';
import { z } from 'zod';

// 3. Internal aliases (@/)
import { Button } from '@/components/ui/button';
import { useAuth } from '@/hooks/useAuth';
import { eventService } from '@/services/eventService';

// 4. Relative imports
import { EventCard } from './EventCard';
import { statusConfig } from './config';
import type { EventCardProps } from './types';

Code Review Standards

Review Checklist

Functionality

  • [ ] Code implements the requirements correctly
  • [ ] Edge cases are handled
  • [ ] Error handling is appropriate
  • [ ] No regressions introduced

Code Quality

  • [ ] TypeScript types are accurate and complete
  • [ ] No any types without justification
  • [ ] Functions are pure where possible
  • [ ] Code is DRY (Don't Repeat Yourself)
  • [ ] Complexity is appropriate

Testing

  • [ ] Unit tests cover new functionality
  • [ ] Edge cases have test coverage
  • [ ] Tests are readable and maintainable
  • [ ] No flaky tests introduced

Security

  • [ ] No sensitive data exposed
  • [ ] Input validation in place
  • [ ] No security vulnerabilities

Performance

  • [ ] No unnecessary re-renders
  • [ ] Expensive operations are memoized
  • [ ] No memory leaks

Accessibility

  • [ ] Keyboard navigation works
  • [ ] Screen reader compatible
  • [ ] Color contrast sufficient

Review Comments

// Good review comment examples:

// 🐛 Bug: This will fail when deliverables array is empty
const progress = approved / deliverables.length * 100;

// 💡 Suggestion: Consider using useMemo to avoid recalculating on every render
const sortedEvents = events.sort((a, b) => a.date - b.date);

// ❓ Question: Should we handle the case where the user has no permissions?
if (user.canEdit) { /* ... */ }

// ⚠️ Warning: This creates a new function on every render, may cause
// unnecessary re-renders in child components
onClick={() => handleClick(item.id)}

// ✅ Praise: Great use of discriminated unions for type safety!
type Result<T> = { success: true; data: T } | { success: false; error: Error };

Performance Standards

React Performance

// ✅ Good: Memoized expensive calculations
const expensiveValue = useMemo(() => {
  return events.reduce((acc, event) => {
    return acc + calculateComplexMetric(event);
  }, 0);
}, [events]);

// ✅ Good: Stable callback references
const handleClick = useCallback((id: string) => {
  setSelectedId(id);
}, []);

// ✅ Good: Memoized component
const EventCard = memo<EventCardProps>(({ event, onSelect }) => {
  return (/* ... */);
});

// ❌ Bad: Object/array in dependency array recreated every render
useEffect(() => {
  fetchData(filters);
}, [{ status: 'active' }]); // New object every render!

Bundle Size

CategoryTargetAction
Initial JS< 200KBCode splitting
Per-route< 50KBLazy loading
Images< 100KB eachOptimization
CSS< 50KBPurgeCSS
// Lazy loading routes
const Dashboard = lazy(() => import('./views/Dashboard'));
const Reports = lazy(() => import('./views/Reports'));
const Settings = lazy(() => import('./views/Settings'));

// Lazy loading components
const HeavyChart = lazy(() => import('./components/HeavyChart'));

Performance Budgets

// package.json
{
  "bundlesize": [
    {
      "path": "./dist/assets/index-*.js",
      "maxSize": "200 kB"
    },
    {
      "path": "./dist/assets/index-*.css",
      "maxSize": "50 kB"
    }
  ]
}

Maintainability Standards

File Organization

src/
├── components/           # Reusable UI components
│   ├── ui/              # Primitive components (Button, Card, etc.)
│   └── [Feature]/       # Feature-specific components
├── hooks/               # Custom React hooks
├── services/            # API and business logic
├── utils/               # Pure utility functions
├── types/               # Shared TypeScript types
├── lib/                 # Third-party integrations
├── views/               # Page components
└── constants/           # Application constants

Naming Conventions

TypeConventionExample
ComponentsPascalCaseEventCard.tsx
HookscamelCase with useuseEvents.ts
UtilitiescamelCaseformatDate.ts
TypesPascalCaseEventStatus
ConstantsUPPER_SNAKE_CASEMAX_RETRY_COUNT
Fileskebab-caseevent-service.ts

Documentation Requirements

/**
 * Calculates the overall progress percentage for an event.
 *
 * @param event - The event to calculate progress for
 * @returns Progress percentage from 0-100, or 0 if no deliverables
 *
 * @example
 * ```ts
 * const progress = calculateEventProgress(event);
 * // Returns: 75
 * ```
 *
 * @remarks
 * Progress is calculated as: (approved / total) * 100
 * Rounded to nearest integer.
 */
export function calculateEventProgress(event: Event): number {
  const { deliverables } = event;
  if (deliverables.length === 0) return 0;

  const approved = deliverables.filter(d => d.status === 'approved').length;
  return Math.round((approved / deliverables.length) * 100);
}

Quality Metrics

Code Quality Targets

MetricTargetTool
Test Coverage≥ 80%Vitest
Type Coverage≥ 95%TypeScript
Cyclomatic Complexity≤ 10ESLint
Function Length≤ 50 linesESLint
File Length≤ 300 linesESLint
Lint Errors0ESLint
Security Issues0 critical/highnpm audit

Automated Quality Checks

# .github/workflows/quality.yml
name: Quality Check

on: [push, pull_request]

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Type check
        run: npm run type-check

      - name: Lint
        run: npm run lint

      - name: Test with coverage
        run: npm run test:coverage

      - name: Check bundle size
        run: npm run build && npm run analyze

Quality Dashboard

Track these metrics over time:

  • Test coverage trends
  • Bundle size trends
  • TypeScript error counts
  • ESLint warning counts
  • Performance scores (Lighthouse)
  • Accessibility scores

Enforcement

Pre-commit Hooks

# .husky/pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
// package.json
{
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,md}": [
      "prettier --write"
    ]
  }
}

CI/CD Gates

GateRequirementAction on Failure
Lint0 errorsBlock merge
TypeScript0 errorsBlock merge
TestsAll passingBlock merge
Coverage≥ 80%Block merge
SecurityNo criticalBlock merge
Bundle SizeWithin budgetWarning

Quality Ownership

RoleResponsibility
DeveloperWrite quality code, tests, documentation
ReviewerEnforce standards during review
Tech LeadDefine and maintain standards
CI/CDAutomate enforcement

Debugging Patterns

Learnings from development sessions on effective debugging approaches.

Authentication Flow Debugging

When debugging authentication issues, trace the full flow systematically:

1. JWT Claims       → Check token has required claims (iss, sub, domainId)
2. Gateway Validation → Verify Kong validates and extracts claims
3. Header Injection  → Confirm x-domain-id header is set correctly
4. Service Handling  → Check downstream service reads and uses header

Component Debugging

// ✅ Good: Add diagnostic logging at component entry points first
export function MyComponent({ data }: Props) {
  // Temporary debug logging - remove before commit
  console.log('[MyComponent] Received data:', {
    hasData: !!data,
    dataLength: data?.length,
    firstItem: data?.[0]
  });

  // ... component logic
}

// Then drill down into specific areas based on log output

API/Network Debugging

StepActionTool
1Check request reaches serverBrowser DevTools Network tab
2Verify request headerscurl -v with same headers
3Check server logsdocker logs <container> or ./scripts/local-env.sh logs
4Verify response formatCompare expected vs actual response schema

Common Gotchas

IssueRoot CauseSolution
401 from KongMissing iss claim in JWTEnsure auth server sets issuer config
Schema migration failsMigration plugin not enabledAdd migration: true to RxDB plugins
Container has old codeBuilt from git clone without pushPush changes, then rebuild container
Embedding timeoutsDefault 30s timeout too shortUse AbortSignal.timeout(120000) minimum

Previous
Code Organization