A+ Quality Standards for Web Applications

Quality grading rubric and implementation checklist for PSI React applications. Established April 2026 through Customer module upgrade from D (66%) to A+ target (95%+).


Grading Rubric

Quality score calculated from 8 weighted categories:

CategoryWeightGrade Tiers
Test Coverage20%F: 0-20%, D: 21-40%, C: 41-60%, B: 61-80%, A: 81-95%, A+: 96-100%
Accessibility15%F: 0-20%, D: 21-40%, C: 41-60%, B: 61-80%, A: 81-95%, A+: 96-100%
Error Handling10%F: No retry, D: Basic try/catch, C: User messages, B: Retry logic, A: Full recovery, A+: Telemetry
Documentation10%F: None, D: README only, C: Some JSDoc, B: Most documented, A: All + examples, A+: Interactive
Performance10%F: <40%, D: 40-55%, C: 56-70%, B: 71-85%, A: 86-95%, A+: 96-100% (memoization, lazy loading)
Code Quality10%F: Many any, D: Some any, C: Type-safe, B: No magic numbers, A: Extracted utils, A+: Full DRY
UX Polish10%F: Basic, D: Loading text, C: Spinners, B: Skeletons, A: Optimistic updates, A+: Animations
Features15%F: Broken, D: Partial, C: Core works, B: Most features, A: All features, A+: Extra enhancements

Letter Grade Thresholds

  • A+: 95-100% (Production-ready, enterprise-grade)
  • A: 90-94% (Excellent, minor polish needed)
  • B: 80-89% (Good, needs improvement in 1-2 areas)
  • C: 70-79% (Acceptable, significant gaps)
  • D: 60-69% (Poor, multiple critical issues)
  • F: <60% (Failing, not production-ready)

A+ Implementation Checklist

1. Test Coverage (20% weight)

Target: 80%+ coverage with comprehensive test suites

  • Unit tests for all validation logic

    • Extract validation to src/customer/utils/validation.ts
    • Test all required fields, length constraints, format validation
    • Test business rules and edge cases
    • Example: 56 validation tests for Customer module
  • Context/State tests for all reducer actions

    • Test SET_CUSTOMER, UPDATE_CUSTOMER_DATA, SET_DIRTY
    • Verify state transitions and side effects
    • Test error states and loading states
  • Service tests for data transformation

    • Test parsing functions (parseCustomer, buildCREC)
    • Test multi-valued field handling (AM/VM delimiters)
    • Test date/number conversions (MD0/MD2 formats)
  • Component tests for user interactions

    • Test form validation UI
    • Test button clicks and dialog flows
    • Test autocomplete behavior
    • Use @testing-library/react patterns

Configuration:

// vitest.config.ts
test: {
  globals: true,
  environment: 'happy-dom', // Faster than jsdom, no ESM issues
  setupFiles: ['./src/shared/test-setup.ts'],
  include: ['src/**/*.test.{ts,tsx}'],
  coverage: {
    include: ['src/customer/**/*.ts', 'src/customer/**/*.tsx'],
    exclude: ['**/*.test.ts', '**/*.test.tsx'],
    thresholds: {
      statements: 80,
      branches: 75,
      functions: 80,
      lines: 80,
    },
  },
}

File structure:

src/customer/
├── utils/
│   ├── validation.ts           ← Pure validation functions
│   └── __tests__/
│       └── validation.test.ts  ← 56 tests
├── hooks/
│   ├── useCustomerOperations.ts
│   └── __tests__/
│       └── useCustomerOperations.test.ts
├── context/
│   ├── CustomerContext.tsx
│   └── __tests__/
│       └── CustomerContext.test.tsx
└── services/
    ├── customerService.ts
    └── __tests__/
        └── customerService.test.ts

2. Accessibility (15% weight)

Target: WCAG 2.1 AA compliance, 100% keyboard navigation

  • ARIA attributes on all interactive elements

    // Autocomplete
    <Autocomplete
      aria-label="Search customers"
      aria-describedby="search-help"
      aria-expanded={isOpen}
      aria-controls="customer-list"
    />
     
    // Dialogs
    <Dialog
      role="dialog"
      aria-modal="true"
      aria-labelledby="dialog-title"
      aria-describedby="dialog-description"
    />
     
    // Loading states
    <div role="status" aria-live="polite" aria-atomic="true">
      Loading customers...
    </div>
     
    // Error messages
    <div role="alert" aria-live="assertive">
      {error}
    </div>
  • Focus management

    • Trap focus in modals
    • Return focus to trigger on close
    • Skip-to-content links
    • Visible focus indicators (outline: 2px solid)
  • Keyboard shortcuts documented

    • Ctrl+S: Save
    • Ctrl+N: New
    • Ctrl+E: Edit
    • Escape: Close dialogs
    • Tab/Shift+Tab: Navigate
  • AG Grid accessibility

    • Enable enableRangeSelection for keyboard nav
    • Set rowSelection: 'single' with keyboard support
    • Add aria-label to grid container
  • Color contrast 4.5:1 minimum

    • Text on background
    • Button states
    • Icon visibility

3. Documentation (10% weight)

Target: JSDoc on all public APIs, comprehensive README

  • Component documentation

    /**
     * Customer module main layout component.
     * 
     * Orchestrates customer data loading, editing, and dialog management.
     * 
     * @example
     * ```tsx
     * <CustomerProvider>
     *   <CustomerLayout />
     * </CustomerProvider>
     * ```
     * 
     * @remarks
     * - Requires CustomerProvider in ancestor tree
     * - Integrates with SharedContext for cross-module state
     * - Supports dirty tracking with confirmation dialogs
     */
    export function CustomerLayout() { ... }
  • Hook documentation

    /**
     * Customer CRUD operations hook.
     * 
     * @returns {Object} Customer operations
     * @returns {Function} returns.openCustomer - Load customer by number
     * @returns {Function} returns.save - Save current customer
     * @returns {Function} returns.deleteCustomer - Delete current customer
     * @returns {Function} returns.createNew - Create new customer
     * 
     * @throws {Error} If called outside CustomerProvider
     * 
     * @example
     * ```tsx
     * const { openCustomer, save } = useCustomerOperations();
     * await openCustomer('12345');
     * await save();
     * ```
     */
    export function useCustomerOperations() { ... }
  • Utility function documentation

    /**
     * Validates customer data against business rules.
     * 
     * @param customer - Customer record to validate
     * @param customerNo - Optional customer number to validate format
     * @returns null if valid, error message string if invalid
     * 
     * @remarks
     * Validation rules:
     * - Required: name1, customerType
     * - Length constraints: name1/name2 (30 max), address1/address2 (25 max)
     * - Format: ZIP (12345 or 12345-6789), State (2 uppercase letters)
     * - Business: Inactive status requires dateClosed, Address requires city
     * 
     * @example
     * ```ts
     * const error = validateCustomer(customer, '123456');
     * if (error) {
     *   showError(error);
     *   return;
     * }
     * ```
     */
    export function validateCustomer(customer: Customer, customerNo?: string): string | null
  • README.md in module root

    • Overview and purpose
    • Architecture diagram
    • File structure
    • Key patterns (dirty tracking, dialogs, validation)
    • Development guide (run tests, build, deploy)
    • Known issues and workarounds

4. Error Handling (10% weight)

Target: Retry logic, user-friendly messages, graceful degradation

  • Network retry utility

    // src/shared/utils/retry.ts
    export async function withRetry<T>(
      fn: () => Promise<T>,
      options: {
        maxAttempts?: number;
        delayMs?: number;
        backoff?: 'linear' | 'exponential';
        onRetry?: (attempt: number, error: Error) => void;
      } = {}
    ): Promise<T> {
      const { maxAttempts = 3, delayMs = 1000, backoff = 'exponential', onRetry } = options;
      
      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
          return await fn();
        } catch (error) {
          if (attempt === maxAttempts) throw error;
          
          const delay = backoff === 'exponential' 
            ? delayMs * Math.pow(2, attempt - 1)
            : delayMs * attempt;
          
          onRetry?.(attempt, error as Error);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
      throw new Error('Unreachable');
    }
  • Error boundaries at module level

    // Already implemented in Customer and SPN modules
    <ErrorBoundary fallback={<ErrorFallback />}>
      <CustomerLayout />
    </ErrorBoundary>
  • User-friendly error messages

    • Map HTTP status codes to messages
    • Avoid technical jargon (“Failed to fetch” → “Unable to load customer”)
    • Provide actionable guidance (“Check your network connection”)
  • Error recovery flows

    • Retry button in error state
    • “Dismiss” to return to safe state
    • Auto-retry for transient failures (network)
    • Manual retry for user errors (validation)

5. Performance (10% weight)

Target: Fast renders, optimized re-renders, lazy loading

  • useMemo for expensive computations

    const filteredCustomers = useMemo(() => {
      return customers.filter(c => 
        c.name1.toLowerCase().includes(searchText.toLowerCase())
      );
    }, [customers, searchText]);
  • React.memo for heavy components

    export const CustomerCard = React.memo(function CustomerCard({ customer }) {
      // Only re-renders when customer prop changes
    });
  • Debounced search inputs

    const debouncedSearch = useMemo(
      () => debounce((text: string) => setSearchText(text), 300),
      []
    );
  • Lazy loading for large grids

    • AG Grid virtual scrolling (built-in)
    • Pagination for 1000+ rows
    • Infinite scroll for sidebar lists
  • Code splitting (if applicable)

    const CustomerModule = lazy(() => import('./customer/CustomerApp'));

6. Code Quality (10% weight)

Target: Zero any types, extracted constants, DRY principles

  • TypeScript strict mode

    // tsconfig.json
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  • Extract magic numbers to constants

    // src/customer/constants.ts
    export const VALIDATION = {
      NAME_MAX_LENGTH: 30,
      ADDRESS_MAX_LENGTH: 25,
      CITY_MAX_LENGTH: 20,
      ZIP_MAX_LENGTH: 10,
      PHONE_MAX_LENGTH: 15,
    } as const;
     
    export const DEBOUNCE_MS = 300;
    export const MAX_SEARCH_RESULTS = 15;
    export const AUTOCOMPLETE_LIMIT = 1000;
  • Consolidate delimiters

    // src/shared/types/unidata.ts
    export const AM = String.fromCharCode(254);
    export const VM = String.fromCharCode(253);
     
    // Import everywhere instead of redefining
    import { AM, VM } from '@/shared/types/unidata';
  • Extract utility functions

    • Validation → src/customer/utils/validation.ts
    • Formatters → src/shared/utils/formatters.ts
    • Date helpers → src/shared/utils/dates.ts

7. UX Polish (10% weight)

Target: Skeleton loaders, optimistic updates, smooth transitions

  • Loading skeletons (not just “Loading…” text)

    {isLoading ? (
      <div className="animate-pulse">
        <div className="h-8 bg-gray-200 rounded w-1/2 mb-4"></div>
        <div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
        <div className="h-4 bg-gray-200 rounded w-3/4"></div>
      </div>
    ) : (
      <CustomerDetails customer={customer} />
    )}
  • Optimistic updates

    const save = async () => {
      // Update UI immediately
      dispatch({ type: 'SET_DIRTY', isDirty: false });
      
      try {
        await saveCustomer(customer);
        showSuccess('Customer saved');
      } catch (err) {
        // Revert on failure
        dispatch({ type: 'SET_DIRTY', isDirty: true });
        showError('Failed to save');
      }
    };
  • Toast notifications (not alerts)

    // Use react-hot-toast or similar
    import toast from 'react-hot-toast';
     
    toast.success('Customer saved');
    toast.error('Failed to save customer');
    toast.loading('Saving...');
  • Progress indicators

    • Linear progress bar for multi-step operations
    • Determinate progress (50% complete) when possible
    • Indeterminate spinner for unknown duration
  • Smooth transitions

    • Dialog fade in/out (200ms)
    • Skeleton → content crossfade
    • List item hover states

8. Features (15% weight)

Target: All planned features working, edge cases handled

  • Core CRUD operations

    • Create new customer
    • Read/load customer by number
    • Update customer data
    • Delete customer (with confirmation)
  • Dirty tracking

    • Detect unsaved changes
    • Confirm on navigation away
    • Reset on save/cancel
  • Dialog management

    • Replace all window.confirm() with promise-based dialogs
    • Replace all window.alert() with toast notifications
    • Replace all window.prompt() with modal forms
  • Validation

    • Client-side validation before save
    • Server-side validation enforcement
    • Real-time validation feedback (on blur)
  • Search/autocomplete

    • Debounced search (300ms)
    • Keyboard navigation (arrow keys)
    • Clear/reset button
    • “No results” state

Module Audit Results

Customer Module (April 2026)

Initial Grade: D (66%)

  • ❌ Test Coverage: 0% (F)
  • ❌ Accessibility: 20% (F) - No ARIA
  • ⚠️ Documentation: 60% (D+) - No JSDoc
  • ⚠️ Error Handling: 70% (C+) - Basic try/catch
  • ✅ Performance: 85% (A-) - Good
  • ✅ Code Quality: 85% (B+) - Few any types
  • ⚠️ UX Polish: 75% (B) - “Loading…” text only
  • ✅ Features: 95% (A) - All core features work

After Phase 1 (Validation Tests): C+ (72%)

  • ✅ Test Coverage: 40% (D) - 56 validation tests passing
  • Remaining categories unchanged

Target: A+ (95%+)

  • Need 80%+ test coverage
  • Need 95%+ accessibility (ARIA everywhere)
  • Need 90%+ documentation (JSDoc all public APIs)
  • Need error retry logic
  • Need skeleton loaders and optimistic updates

Implementation Order (Priority)

  1. Tests (highest ROI, unblocks everything)

    • Validation tests → Context tests → Service tests → Component tests
    • Target: 80% coverage
  2. Accessibility (legal requirement, user-facing)

    • ARIA attributes → Focus management → Keyboard nav docs
  3. Documentation (maintainability)

    • JSDoc components → JSDoc hooks → JSDoc utils → README
  4. Error Handling (reliability)

    • Retry utility → Error boundaries → User messages
  5. UX Polish (delight factor)

    • Skeleton loaders → Toast notifications → Optimistic updates
  6. Performance (already good, just optimize hot paths)

    • useMemo sidebar filter → React.memo heavy components
  7. Code Quality (already good, consolidate constants)

    • Extract delimiters → Extract magic numbers → DRY utils

Tools & Resources

Testing:

  • Vitest 4.1.2 + @testing-library/react 16.3.2
  • happy-dom 15.11.7 (faster than jsdom, no ESM issues)
  • See webapp-testing for full setup guide

Accessibility:

Documentation:

  • TSDoc syntax: typedoc.org
  • JSDoc @param, @returns, @throws, @example, @remarks

Performance:

  • React DevTools Profiler
  • Chrome DevTools Performance tab
  • web.dev performance guides


Established: April 2026
Reference Implementation: Customer Module (Quoting v4)