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:
| Category | Weight | Grade Tiers |
|---|---|---|
| Test Coverage | 20% | F: 0-20%, D: 21-40%, C: 41-60%, B: 61-80%, A: 81-95%, A+: 96-100% |
| Accessibility | 15% | F: 0-20%, D: 21-40%, C: 41-60%, B: 61-80%, A: 81-95%, A+: 96-100% |
| Error Handling | 10% | F: No retry, D: Basic try/catch, C: User messages, B: Retry logic, A: Full recovery, A+: Telemetry |
| Documentation | 10% | F: None, D: README only, C: Some JSDoc, B: Most documented, A: All + examples, A+: Interactive |
| Performance | 10% | F: <40%, D: 40-55%, C: 56-70%, B: 71-85%, A: 86-95%, A+: 96-100% (memoization, lazy loading) |
| Code Quality | 10% | F: Many any, D: Some any, C: Type-safe, B: No magic numbers, A: Extracted utils, A+: Full DRY |
| UX Polish | 10% | F: Basic, D: Loading text, C: Spinners, B: Skeletons, A: Optimistic updates, A+: Animations |
| Features | 15% | 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
- Extract validation to
-
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
enableRangeSelectionfor keyboard nav - Set
rowSelection: 'single'with keyboard support - Add aria-label to grid container
- Enable
-
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
- Validation →
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
- Replace all
-
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
anytypes - ⚠️ 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)
-
Tests (highest ROI, unblocks everything)
- Validation tests → Context tests → Service tests → Component tests
- Target: 80% coverage
-
Accessibility (legal requirement, user-facing)
- ARIA attributes → Focus management → Keyboard nav docs
-
Documentation (maintainability)
- JSDoc components → JSDoc hooks → JSDoc utils → README
-
Error Handling (reliability)
- Retry utility → Error boundaries → User messages
-
UX Polish (delight factor)
- Skeleton loaders → Toast notifications → Optimistic updates
-
Performance (already good, just optimize hot paths)
- useMemo sidebar filter → React.memo heavy components
-
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:
- WCAG 2.1 Quick Reference
- ARIA Authoring Practices
- axe DevTools browser extension
Documentation:
- TSDoc syntax: typedoc.org
- JSDoc @param, @returns, @throws, @example, @remarks
Performance:
- React DevTools Profiler
- Chrome DevTools Performance tab
- web.dev performance guides
Related Documentation
- webapp-testing - Testing infrastructure and patterns
- webapp-compliance-standard - Security and compliance requirements
- index - All PSI web applications
Established: April 2026
Reference Implementation: Customer Module (Quoting v4)