# RFCP - Iteration 10: Final Frontend Audit & Polish **Goal:** Production-ready frontend - zero critical bugs, clean code, polished UX, comprehensive audit. **Status:** Ready for Implementation **Estimated Time:** 8-12 hours (can split into 2 sessions) --- ## 📋 Overview This is the **final frontend iteration** before moving to backend work. After this iteration, the frontend should be: - ✅ Bug-free and stable - ✅ Well-documented code - ✅ TypeScript strict mode compliant - ✅ Performance optimized - ✅ Production-ready UX --- ## 🎯 Four-Phase Approach ``` Phase 1: Critical Fixes (P1) ↓ Phase 2: Code Audit & Refactor (P1) ↓ Phase 3: UX Polish (P2) ↓ Phase 4: Final Audit & Testing (P1) ``` --- ## 📍 Phase 1: Critical Fixes **Priority:** P1 CRITICAL **Time:** ~2-3 hours ### 1.1 Stack Overflow at 50m Resolution **Problem:** RangeError: Maximum call stack size exceeded **Investigation:** ```bash # Check Web Worker for recursion cat public/workers/rf-worker.js # Look for patterns: # - Recursive path loss calculation # - Infinite loop in terrain processing # - Deep call stacks in Fresnel calculations ``` **Likely culprits:** 1. **Path Loss calculation** - recursive terrain checks 2. **Fresnel zone** - recursive clearance checks 3. **LOS calculation** - recursive elevation sampling **Solution pattern:** ```javascript // Add depth guard to any recursive function function calculatePathLoss(point, site, depth = 0, maxDepth = 20) { if (depth > maxDepth) { console.warn('Max recursion depth reached in pathLoss'); return DEFAULT_PATH_LOSS; } // ... calculation logic if (needsRecursion) { return calculatePathLoss(newPoint, site, depth + 1, maxDepth); } } ``` **Files to check:** - `public/workers/rf-worker.js` - `src/lib/calculator.ts` (if has additional logic) - `src/utils/pathLoss.ts` (if exists) **Testing:** ```bash # After fix, test all resolutions: 50m → ✅ No crash, calculates in <10s 100m → ✅ Works smoothly 200m → ✅ Works smoothly 500m → ✅ Works smoothly # Monitor console for recursion warnings ``` --- ### 1.2 Green Coverage Radius Circle **Problem:** Green/cyan circle visible, conflicts with RSRP gradient **Investigation:** ```bash # Find all Circle components grep -r "Circle" src/ --include="*.tsx" # Check for green/cyan colors grep -r "#00ff00\|#00bcd4\|rgb(0,255,0)" src/ # Check for dashed circles grep -r "dashArray" src/ ``` **Likely locations:** 1. `src/components/map/Map.tsx` 2. `src/components/map/SiteMarker.tsx` 3. `src/components/map/CoverageLayer.tsx` **Solution A: Remove completely** ```tsx // Find and DELETE: {showCalculationRadius && ( )} ``` **Solution B: Change to orange (recommended)** ```tsx ``` **Solution C: Make toggleable** ```tsx // In settings panel: ``` **Recommended:** Solution B (orange) or C (toggleable) --- ### 1.3 Any Other Critical Bugs **From user testing:** (TBD based on screenshots/reports) --- ## 🔍 Phase 2: Code Audit & Refactor **Priority:** P1 **Time:** ~3-4 hours ### 2.1 TypeScript Strict Mode **Enable strict type checking:** **File:** `tsconfig.json` ```json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true } } ``` **Fix all type errors:** ```bash npm run type-check # OR npx tsc --noEmit ``` **Common fixes needed:** 1. **Replace `any` with proper types:** ```typescript // ❌ Bad const data: any = response.data; // ✅ Good interface ResponseData { sites: Site[]; coverage: CoveragePoint[]; } const data: ResponseData = response.data; ``` 2. **Null checks:** ```typescript // ❌ Bad const site = sites.find(s => s.id === id); site.name = 'New Name'; // Might be undefined! // ✅ Good const site = sites.find(s => s.id === id); if (!site) return; site.name = 'New Name'; ``` 3. **Function signatures:** ```typescript // ❌ Bad function calculate(sites, settings) { ... } // ✅ Good function calculate( sites: Site[], settings: CoverageSettings ): CoverageResult { ... } ``` **Files to audit:** - `src/store/*.ts` - Zustand stores - `src/lib/*.ts` - Utilities - `src/components/**/*.tsx` - Components - `src/utils/*.ts` - Helpers --- ### 2.2 ESLint Configuration **Install and configure strict linting:** ```bash npm install --save-dev eslint-config-airbnb-typescript npm install --save-dev @typescript-eslint/eslint-plugin npm install --save-dev @typescript-eslint/parser ``` **File:** `eslint.config.js` ```javascript import js from '@eslint/js'; import typescript from '@typescript-eslint/eslint-plugin'; import tsParser from '@typescript-eslint/parser'; import react from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks'; export default [ js.configs.recommended, { files: ['**/*.{ts,tsx}'], languageOptions: { parser: tsParser, parserOptions: { project: './tsconfig.json', }, }, plugins: { '@typescript-eslint': typescript, 'react': react, 'react-hooks': reactHooks, }, rules: { // TypeScript '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/explicit-function-return-type': 'warn', '@typescript-eslint/no-unused-vars': 'error', // React 'react/prop-types': 'off', // Using TypeScript 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', // General 'no-console': ['warn', { allow: ['warn', 'error'] }], 'prefer-const': 'error', 'no-var': 'error', }, }, ]; ``` **Run lint:** ```bash npm run lint npm run lint -- --fix # Auto-fix where possible ``` --- ### 2.3 Code Organization & Cleanup **Remove dead code:** ```bash # Find unused exports npx ts-prune # Remove unused imports (auto) npm run lint -- --fix ``` **Consistent naming:** - Components: `PascalCase` (SiteForm, HeatmapLegend) - Files: Match component name (SiteForm.tsx) - Utilities: `camelCase` (calculateDistance, normalizeRSRP) - Constants: `UPPER_SNAKE_CASE` (MAX_SITES, DEFAULT_POWER) - Types/Interfaces: `PascalCase` (Site, CoverageSettings) **File structure review:** ``` src/ ├── components/ │ ├── map/ # Map-related components │ ├── panels/ # Side panels │ ├── ui/ # Reusable UI components │ └── common/ # Common components ├── store/ # Zustand stores ├── lib/ # Business logic ├── utils/ # Pure utility functions ├── types/ # TypeScript types └── hooks/ # Custom React hooks ``` **Move misplaced files if needed.** --- ### 2.4 Performance Audit **React.memo for expensive components:** ```typescript // Components that render frequently but props rarely change export default React.memo(HeatmapLegend); export default React.memo(SiteMarker); export default React.memo(CoverageStats); ``` **useMemo for expensive calculations:** ```typescript const filteredSites = useMemo( () => sites.filter(s => s.frequency === selectedFreq), [sites, selectedFreq] ); ``` **useCallback for stable callbacks:** ```typescript const handleSiteUpdate = useCallback( (id: string, updates: Partial) => { updateSite(id, updates); }, [updateSite] ); ``` **Web Worker efficiency:** - Verify chunking strategy (currently 4 workers max) - Check worker termination (no leaks) - Monitor calculation times **Tile cache optimization:** ```typescript // HeatmapTileRenderer.ts constructor(radiusMeters = 400, maxCacheSize = 150) { // Verify cache size is appropriate // Monitor cache hit rate in dev mode } ``` **Bundle size check:** ```bash npm run build npx vite-bundle-visualizer # Target: <500KB gzipped for main bundle ``` --- ### 2.5 Error Handling **Add try-catch blocks:** ```typescript // API calls async function fetchCoverageData() { try { const response = await fetch('/api/coverage'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return await response.json(); } catch (error) { console.error('Coverage fetch failed:', error); toast.error('Failed to load coverage data. Please try again.'); return null; } } // Worker communication worker.onerror = (error) => { console.error('Worker error:', error); toast.error('Coverage calculation failed. Please try again.'); terminateWorker(); }; // User actions const handleDeleteSite = async (id: string) => { try { await deleteSite(id); toast.success('Site deleted'); } catch (error) { console.error('Delete failed:', error); toast.error('Failed to delete site. Please try again.'); } }; ``` **Fallback states everywhere:** ```typescript // Loading {isLoading && } // Error {error && } // Empty {sites.length === 0 && } // Success {sites.length > 0 && } ``` --- ## 🎨 Phase 3: UX Polish **Priority:** P2 **Time:** ~2-3 hours ### 3.1 Loading States **Global loading indicator:** ```tsx // App.tsx {isCalculating && (

Calculating coverage...

)} ``` **Component-level loading:** ```tsx // SiteList.tsx {isLoading ? ( ) : sites.length === 0 ? ( ) : ( )} ``` **Button loading states:** ```tsx ``` --- ### 3.2 Empty States **No sites:** ```tsx

No Sites Yet

Add your first site to start RF coverage planning

``` **No coverage:** ```tsx

No Coverage Calculated

Click "Calculate Coverage" to see RF heatmap

``` **Search no results:** ```tsx

No sites found matching "{searchQuery}"

``` --- ### 3.3 Error States **Generic error:** ```tsx

Something Went Wrong

{error.message}

``` **Network error:** ```tsx

Connection Lost

Check your internet connection and try again

``` **Validation error:** ```tsx
Power must be between 1-100W
``` --- ### 3.4 Keyboard Shortcuts Help Modal **Create component:** **File:** `src/components/common/KeyboardShortcutsHelp.tsx` ```tsx import { useEffect, useState } from 'react'; const SHORTCUTS = [ { key: 'N', description: 'New Site' }, { key: 'S', description: 'Save Project' }, { key: 'C', description: 'Clone Selected Sector' }, { key: 'Delete', description: 'Delete Selected Site' }, { key: 'Ctrl+Z', description: 'Undo' }, { key: 'Ctrl+Shift+Z', description: 'Redo' }, { key: 'Ctrl+F', description: 'Search Sites' }, { key: 'Esc', description: 'Close Dialogs' }, { key: '?', description: 'Show This Help' }, ]; export default function KeyboardShortcutsHelp() { const [visible, setVisible] = useState(false); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === '?' && !e.ctrlKey && !e.metaKey) { e.preventDefault(); setVisible(v => !v); } if (e.key === 'Escape') { setVisible(false); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, []); if (!visible) return null; return (
setVisible(false)}>
e.stopPropagation()}>

⌨️ Keyboard Shortcuts

{SHORTCUTS.map((shortcut) => (
{shortcut.key} {shortcut.description}
))}
); } ``` **CSS:** ```css .kbd { display: inline-block; padding: 3px 8px; font-family: monospace; font-size: 12px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,0.1); min-width: 80px; text-align: center; } .shortcuts-list { display: flex; flex-direction: column; gap: 12px; padding: 16px; } .shortcut-row { display: flex; gap: 16px; align-items: center; } ``` **Usage:** ```tsx // App.tsx import KeyboardShortcutsHelp from '@/components/common/KeyboardShortcutsHelp'; export default function App() { return ( <> {/* ... other components */} ); } ``` --- ### 3.5 Export/Import Project **Export all state to JSON:** **File:** `src/components/panels/ExportPanel.tsx` ```tsx import { useSitesStore } from '@/store/sites'; import { useCoverageStore } from '@/store/coverage'; export function exportProject() { const sites = useSitesStore.getState().sites; const settings = useCoverageStore.getState().settings; const project = { version: '1.0.0', exportDate: new Date().toISOString(), sites, settings, }; const blob = new Blob([JSON.stringify(project, null, 2)], { type: 'application/json', }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `rfcp-project-${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); toast.success('Project exported successfully'); } export function importProject(file: File) { const reader = new FileReader(); reader.onload = (e) => { try { const project = JSON.parse(e.target?.result as string); // Validate version if (project.version !== '1.0.0') { toast.error('Incompatible project version'); return; } // Import sites const { setSites } = useSitesStore.getState(); setSites(project.sites || []); // Import settings const { updateSettings } = useCoverageStore.getState(); updateSettings(project.settings || {}); toast.success('Project imported successfully'); } catch (error) { console.error('Import failed:', error); toast.error('Failed to import project'); } }; reader.readAsText(file); } ``` **UI:** ```tsx // In ProjectPanel or Settings
``` --- ### 3.6 Input Validation **Validate all numeric inputs:** **File:** `src/utils/validation.ts` ```typescript export interface ValidationRule { min?: number; max?: number; required?: boolean; message?: string; } export function validateNumber( value: number, rules: ValidationRule ): { valid: boolean; error?: string } { if (rules.required && (value === null || value === undefined)) { return { valid: false, error: rules.message || 'This field is required' }; } if (rules.min !== undefined && value < rules.min) { return { valid: false, error: `Minimum value is ${rules.min}` }; } if (rules.max !== undefined && value > rules.max) { return { valid: false, error: `Maximum value is ${rules.max}` }; } return { valid: true }; } // Validation rules export const VALIDATION_RULES = { power: { min: 1, max: 100, required: true, message: 'Power: 1-100W' }, gain: { min: 0, max: 30, required: true, message: 'Gain: 0-30 dBi' }, height: { min: 1, max: 500, required: true, message: 'Height: 1-500m' }, frequency: { min: 700, max: 3800, required: true, message: 'Freq: 700-3800 MHz' }, azimuth: { min: 0, max: 359, required: true, message: 'Azimuth: 0-359°' }, beamwidth: { min: 10, max: 360, required: true, message: 'Beamwidth: 10-360°' }, radius: { min: 1, max: 100, required: true, message: 'Radius: 1-100 km' }, resolution: { min: 50, max: 500, required: true, message: 'Resolution: 50-500m' }, }; ``` **Use in NumberInput component:** ```tsx // NumberInput.tsx import { validateNumber, VALIDATION_RULES } from '@/utils/validation'; export function NumberInput({ value, onChange, field, ...props }) { const rules = VALIDATION_RULES[field]; const validation = validateNumber(value, rules); return (
onChange(parseFloat(e.target.value))} className={validation.valid ? '' : 'error'} {...props} /> {!validation.valid && ( {validation.error} )}
); } ``` --- ### 3.7 Toast System Improvements **Enhanced toast with actions:** **File:** `src/components/common/Toast.tsx` ```tsx export interface ToastAction { label: string; onClick: () => void; } export interface ToastOptions { duration?: number; action?: ToastAction; } export function addToast( message: string, type: 'success' | 'error' | 'info' | 'warning', options?: ToastOptions ) { const id = crypto.randomUUID(); const duration = options?.duration || (type === 'error' ? 10000 : 3000); const toast: Toast = { id, message, type, action: options?.action, duration, }; // Add to store useToastStore.getState().addToast(toast); // Auto-remove after duration setTimeout(() => { useToastStore.getState().removeToast(id); }, duration); return id; } // Usage: toast.success('Site deleted', { duration: 10000, action: { label: 'Undo', onClick: () => restoreSite(deletedSite), }, }); ``` **Toast container improvements:** ```tsx // Support multiple toasts
{toasts.map((toast) => (
{toast.message} {toast.action && ( )}
))} {toasts.length > 1 && ( )}
``` --- ### 3.8 Console Cleanup **Remove dev logs in production:** ```typescript // utils/logger.ts const isDev = import.meta.env.DEV; export const logger = { log: (...args: unknown[]) => { if (isDev) console.log(...args); }, warn: (...args: unknown[]) => { if (isDev) console.warn(...args); }, error: (...args: unknown[]) => { // Always log errors console.error(...args); }, perf: (label: string, fn: () => void) => { if (!isDev) return fn(); const start = performance.now(); const result = fn(); const end = performance.now(); console.log(`⏱️ ${label}: ${(end - start).toFixed(2)}ms`); return result; }, }; // Replace all console.log with logger.log // Replace all console.warn with logger.warn // Keep console.error as-is (or use logger.error) ``` **Find and replace:** ```bash # Find all console.log/warn grep -r "console\\.log\\|console\\.warn" src/ # Replace with logger (manual or script) ``` --- ### 3.9 Tooltips for Complex Features **Add tooltips using native `title` or custom component:** ```tsx // Simple tooltips (native) // Or custom tooltip component ``` **Key features needing tooltips:** - Coverage Settings (what each setting does) - Antenna types (directional vs omnidirectional) - RSRP thresholds - Resolution vs calculation time tradeoff - Clone vs New Site --- ## ✅ Phase 4: Final Audit & Testing **Priority:** P1 **Time:** ~1-2 hours ### 4.1 Functionality Checklist **Site Management:** - [ ] Create new site → success - [ ] Edit site parameters → updates correctly - [ ] Delete site → confirmation dialog → deleted - [ ] Clone sector → creates new sector, not site - [ ] Batch operations work (select multiple, delete) **Coverage Calculation:** - [ ] Calculate coverage → completes successfully - [ ] 50m resolution → no crash - [ ] 500m resolution → fast calculation - [ ] Progress indicator shows - [ ] Results display correctly **Heatmap:** - [ ] Geographic scale preserved at all zoom levels - [ ] Colors match RSRP legend - [ ] Tile rendering <50ms average - [ ] Opacity control works - [ ] Toggle on/off works **UI/UX:** - [ ] Keyboard shortcuts work - [ ] Dark mode consistent - [ ] No console errors - [ ] Loading states show - [ ] Error states helpful - [ ] Empty states clear **Data Persistence:** - [ ] Sites saved to localStorage - [ ] Settings persist - [ ] Browser refresh preserves state - [ ] Export/import works --- ### 4.2 Performance Checklist **Coverage Calculation:** - [ ] <5s for typical scenario (10 sites, 200m resolution, 10km radius) - [ ] <30s for heavy scenario (50 sites, 100m resolution, 20km radius) - [ ] Web Workers terminate properly (no leaks) **Tile Rendering:** - [ ] Average tile render time <50ms - [ ] No stuttering during pan/zoom - [ ] Cache hit rate >50% after initial load **Memory:** - [ ] No memory leaks (check DevTools Memory tab) - [ ] Heap size stable after 5 minutes of use - [ ] Tile cache doesn't grow unbounded **Bundle Size:** - [ ] Main bundle <500KB gzipped - [ ] Initial load <2s on 3G - [ ] Code splitting working (if applicable) --- ### 4.3 Cross-Browser Testing **Browsers to test:** - [ ] Chrome (latest) - [ ] Firefox (latest) - [ ] Safari (if on Mac) - [ ] Edge (latest) **Mobile (basic):** - [ ] Responsive layout works - [ ] Touch gestures work - [ ] Mobile Chrome/Safari --- ### 4.4 Edge Cases **Extreme scenarios:** - [ ] 0 sites → proper empty state - [ ] 1 site → works correctly - [ ] 100+ sites → performance acceptable - [ ] Overlapping sites → coverage blending correct **Zoom levels:** - [ ] Zoom 1 (world) → works - [ ] Zoom 8 (city) → works - [ ] Zoom 18 (building) → works **Boundary conditions:** - [ ] Sites at map edge → no errors - [ ] Coverage extends beyond viewport → tiles load - [ ] Very large radius (100km) → calculates **Input validation:** - [ ] Invalid power (0W, 200W) → error shown - [ ] Invalid frequency (100 MHz) → error shown - [ ] Negative height → error shown - [ ] Empty required fields → error shown --- ### 4.5 Regression Testing **Features from previous iterations:** - [ ] Multi-sector sites work (Iteration 8) - [ ] Greek naming (Alpha, Beta, Gamma) correct - [ ] Number inputs with sliders work (Iteration 9) - [ ] Delete confirmation dialog works (Iteration 9.1) - [ ] Undo toast works (Iteration 9.1) - [ ] Keyboard shortcuts no conflicts (Iteration 9) - [ ] Coverage settings controls work (Iteration 9.1) --- ## 🚀 Deployment ### Build & Test Production Build ```bash # Clean build rm -rf dist/ npm run build # Test production build locally npm run preview # Verify: # - No console errors # - All features work # - Performance good # - Assets load correctly ``` ### Pre-Deploy Checklist - [ ] All tests pass - [ ] TypeScript strict mode clean - [ ] ESLint clean (no errors, minimal warnings) - [ ] Production build successful - [ ] Bundle size acceptable - [ ] No TODO/FIXME comments in critical code ### Deploy to VPS ```bash # On local machine cd /d/root/rfcp/frontend npm run build # Copy to VPS (adjust path/credentials) scp -r dist/* user@vps:/opt/rfcp/frontend/dist/ # On VPS sudo systemctl reload caddy ``` ### Post-Deploy Verification ```bash # Check site loads curl -I https://rfcp.eliah.one # Monitor logs sudo journalctl -u caddy -f # Test in browser # → https://rfcp.eliah.one # → All features work # → No console errors ``` --- ## 📝 Documentation Updates ### README.md ```markdown # RFCP - RF Coverage Planning Tool Production-ready tactical communications planning. ## Features - Multi-site RF coverage calculation - Geographic-scale heatmap visualization - Professional keyboard shortcuts - Real-time coverage analysis - Export/Import projects ## Tech Stack - React 18 + TypeScript - Leaflet + custom canvas renderer - Web Workers for parallel calculation - Zustand state management - Vite build system ## Quick Start \`\`\`bash npm install npm run dev \`\`\` ## Build \`\`\`bash npm run build npm run preview # Test production build \`\`\` ``` ### CHANGELOG.md ```markdown # Changelog ## Iteration 10 - Final Frontend Audit (2026-01-30) ### Fixed - Stack overflow at 50m resolution - Coverage radius circle color (green → orange) - TypeScript strict mode compliance - All ESLint warnings ### Added - Keyboard shortcuts help modal (press ?) - Export/Import project functionality - Input validation with error messages - Enhanced toast system with actions - Loading/error/empty states everywhere ### Improved - Code organization and cleanup - Performance optimization (React.memo, useMemo) - Error handling throughout - Console logging (dev/prod separation) - Bundle size optimization ### Performance - Coverage calculation: <5s typical, <30s heavy - Tile rendering: <50ms average - Bundle size: <500KB gzipped ``` --- ## 🎯 Success Criteria ### Must Have (P1): - ✅ Zero critical bugs - ✅ Zero console errors in production - ✅ All features functional - ✅ TypeScript strict mode passes - ✅ Performance targets met - ✅ Code audit complete ### Should Have (P2): - ✅ Keyboard shortcuts help - ✅ Export/Import project - ✅ Input validation - ✅ Polish all states (loading/error/empty) - ✅ Enhanced toast system ### Nice to Have (P3): - 📱 Mobile responsive improvements - 🎨 UI micro-animations - 📚 Comprehensive tooltips - 🔔 Advanced toast features --- ## 📊 Iteration Timeline **Session 1 (4-6 hours):** - Phase 1: Critical Fixes (2-3h) - Phase 2: Code Audit start (2-3h) **Session 2 (4-6 hours):** - Phase 2: Code Audit finish (1-2h) - Phase 3: UX Polish (2-3h) - Phase 4: Final Audit (1-2h) **Total: 8-12 hours** --- ## 🎬 After Iteration 10 ### Next Steps: 1. **Backend Audit** (Iteration 11) - API endpoints review - Database optimization - Error handling - Logging setup 2. **Documentation** (Iteration 12) - Architecture docs - Deployment guide - API documentation - User guide 3. **Production Deployment** - VPS hardening - SSL/HTTPS - Monitoring setup - Backup strategy --- ## 🎯 Ready for Implementation! **Priority Order:** 1. Phase 1 (Critical Fixes) → ASAP 2. Phase 2 (Code Audit) → Next 3. Phase 3 (UX Polish) → Then 4. Phase 4 (Final Audit) → Last **Let's make this frontend production-ready!** 🚀 --- **Status:** ✅ Specification Complete - Ready for Claude Code **Next:** Begin Phase 1 - Critical Fixes