) => {
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 && (
)}
```
**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