# RFCP — Iteration 3.5.2: Native Backend + GPU Fix + UI Polish
## Overview
Fix critical architecture issues: GPU indicator dropdown broken, GPU acceleration not working
(CuPy in wrong Python environment), and prepare path to remove WSL2 dependency for end users.
Plus UI polish items carried over from 3.5.1.
**Priority:** GPU fixes first, then UI polish, then native Windows exploration.
---
## CRITICAL CONTEXT
### Current Architecture Problem
```
RFCP.exe (Electron, Windows)
└── launches backend via WSL2:
python3 -m uvicorn app.main:app --host 0.0.0.0 --port 8090
└── /usr/bin/python3 (WSL2 system Python 3.12)
└── NO venv, NO CuPy installed
User installed CuPy in Windows Python → backend doesn't see it.
User installed CuPy in WSL system Python → needs --break-system-packages
```
### GPU Hardware (Confirmed Working)
```
nvidia-smi output (from WSL2):
NVIDIA GeForce RTX 4060 Laptop GPU
Driver: 581.42 (Windows) / 580.95.02 (WSL2)
CUDA: 13.0
VRAM: 8188 MiB
GPU passthrough: WORKING ✅
```
### Files to Reference
```
backend/app/services/gpu_backend.py — GPUManager class
backend/app/api/routes/gpu.py — GPU API endpoints
frontend/src/components/ui/GPUIndicator.tsx — GPU badge/dropdown
desktop/ — Electron app source
installer/ — Build scripts
```
---
## Task 1: Fix GPU Indicator Dropdown Z-Index (Priority 1 — 10 min)
### Problem
GPU dropdown WORKS (opens on click, shows diagnostics, install hints) but renders
BEHIND the right sidebar panel. The sidebar (Sites, Coverage Settings) has higher
z-index than the GPU dropdown, so the dropdown is invisible/hidden underneath.
See screenshots: dropdown is partially visible only when sidebar is made very narrow.
It shows: "COMPUTE DEVICES", "CPU (NumPy)", install hints, "Run Diagnostics",
and even diagnostics JSON — all working but hidden behind sidebar.
### Root Cause
GPUIndicator dropdown z-index is lower than the right sidebar panel z-index.
### Solution
In `GPUIndicator.tsx` — find the dropdown container div and set z-index
higher than the sidebar:
```tsx
{isOpen && (
...
)}
```
**Key requirements:**
1. `z-index: 9999` (or at minimum higher than sidebar)
2. Position: dropdown should open to the LEFT (toward center of screen)
to avoid being cut off by right edge
3. `right-0` on the absolute positioning (anchored to right edge of badge)
**Alternative approach** — use Tailwind z-index:
```tsx
className="absolute top-full right-0 mt-1 z-[9999] ..."
```
**Also check:** The parent container of GPUIndicator might need `position: relative`
for absolute positioning to work correctly against the right sidebar.
### Testing
- [ ] Click "CPU" badge → dropdown appears ABOVE the sidebar
- [ ] Full dropdown visible: devices, install hints, diagnostics
- [ ] Dropdown doesn't get cut off on right side
- [ ] Click outside → dropdown closes
- [ ] Dropdown works at any window width
---
## Task 2: Install CuPy in WSL Backend (Priority 1 — 10 min)
### Problem
CuPy installed in Windows Python, but backend runs in WSL2 system Python.
### Solution
Add a startup check in the backend that detects missing GPU packages
and provides clear instructions. Also, the Electron app should try to
install dependencies on first launch.
**Step 1: Backend startup GPU check**
In `backend/app/main.py`, add on startup:
```python
@app.on_event("startup")
async def check_gpu_availability():
"""Log GPU status on startup for debugging."""
import logging
logger = logging.getLogger("rfcp.gpu")
# Check CuPy
try:
import cupy as cp
device_count = cp.cuda.runtime.getDeviceCount()
if device_count > 0:
name = cp.cuda.Device(0).name
mem = cp.cuda.Device(0).mem_info[1] // 1024 // 1024
logger.info(f"✅ GPU detected: {name} ({mem} MB VRAM)")
logger.info(f" CuPy {cp.__version__}, CUDA devices: {device_count}")
else:
logger.warning("⚠️ CuPy installed but no CUDA devices found")
except ImportError:
logger.warning("⚠️ CuPy not installed — GPU acceleration disabled")
logger.warning(" Install: pip install cupy-cuda12x --break-system-packages")
except Exception as e:
logger.warning(f"⚠️ CuPy error: {e}")
# Check PyOpenCL
try:
import pyopencl as cl
platforms = cl.get_platforms()
for p in platforms:
for d in p.get_devices():
logger.info(f"✅ OpenCL device: {d.name.strip()}")
except ImportError:
logger.info("ℹ️ PyOpenCL not installed (optional)")
except Exception:
pass
```
**Step 2: GPU diagnostics endpoint enhancement**
Enhance `/api/gpu/diagnostics` to return install commands:
```python
@router.get("/diagnostics")
async def gpu_diagnostics():
import platform, sys
diagnostics = {
"python": sys.version,
"platform": platform.platform(),
"executable": sys.executable,
"is_wsl": "microsoft" in platform.release().lower(),
"cuda_available": False,
"opencl_available": False,
"install_hint": "",
"devices": []
}
# Check nvidia-smi
try:
import subprocess
result = subprocess.run(
["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
diagnostics["nvidia_smi"] = result.stdout.strip()
except:
diagnostics["nvidia_smi"] = "not found"
# Check CuPy
try:
import cupy
diagnostics["cupy_version"] = cupy.__version__
diagnostics["cuda_available"] = True
count = cupy.cuda.runtime.getDeviceCount()
for i in range(count):
d = cupy.cuda.Device(i)
diagnostics["devices"].append({
"id": i,
"name": d.name,
"memory_mb": d.mem_info[1] // 1024 // 1024,
"backend": "CUDA"
})
except ImportError:
if diagnostics.get("is_wsl"):
diagnostics["install_hint"] = "pip3 install cupy-cuda12x --break-system-packages"
else:
diagnostics["install_hint"] = "pip install cupy-cuda12x"
return diagnostics
```
**Step 3: Frontend shows diagnostics clearly**
In GPUIndicator dropdown, show:
```
⚠ No GPU detected
Your system: WSL2 + NVIDIA RTX 4060
To enable GPU acceleration:
┌─────────────────────────────────────────────┐
│ pip3 install cupy-cuda12x │
│ --break-system-packages │
└─────────────────────────────────────────────┘
Then restart RFCP.
[Copy Command] [Run Diagnostics]
```
### Testing
- [ ] Backend startup logs GPU status
- [ ] /api/gpu/diagnostics returns WSL detection + install hint
- [ ] Frontend shows clear install instructions
- [ ] After installing CuPy in WSL + restart → GPU appears in list
---
## Task 3: Terrain Profile Click Fix (Priority 2 — 5 min)
### Problem
Clicking "Terrain Profile" button in ruler measurement also adds a point on the map.
### Solution
In the Terrain Profile button handler:
```tsx
const handleTerrainProfile = (e: React.MouseEvent) => {
e.stopPropagation();
e.preventDefault();
// ... open terrain profile
};
```
Also check if the button is rendered inside a map click handler area —
may need `L.DomEvent.disableClickPropagation(container)` on the parent.
### Testing
- [ ] Click "Terrain Profile" → opens profile, NO new ruler point added
- [ ] Map click still works normally when not clicking the button
---
## Task 4: Coverage Boundary — Real Contour Shape (Priority 2 — 45 min)
### Problem
Current boundary is a rough circle/ellipse. Should follow actual coverage contour.
### Approaches
**Option A: Shapely Alpha Shape (recommended)**
```python
# backend/app/services/boundary_service.py
from shapely.geometry import MultiPoint
from shapely.ops import unary_union
import numpy as np
def calculate_coverage_boundary(points: list, threshold_dbm: float = -100) -> list:
"""Calculate concave hull of coverage area above threshold."""
# Filter points above threshold
valid = [(p['lon'], p['lat']) for p in points if p['rsrp'] >= threshold_dbm]
if len(valid) < 3:
return []
mp = MultiPoint(valid)
# Use convex hull first, then try concave
try:
# Shapely 2.0+ has concave_hull
from shapely import concave_hull
hull = concave_hull(mp, ratio=0.3)
except ImportError:
# Fallback to convex hull
hull = mp.convex_hull
# Simplify to reduce points (0.001 deg ≈ 100m)
simplified = hull.simplify(0.001, preserve_topology=True)
# Extract coordinates
if simplified.geom_type == 'Polygon':
coords = list(simplified.exterior.coords)
return [{'lat': c[1], 'lon': c[0]} for c in coords]
return []
```
**Option B: Grid-based contour (simpler)**
```python
def grid_contour_boundary(points: list, threshold_dbm: float, resolution: float):
"""Find boundary by detecting edge cells in grid."""
# Create binary grid: 1 = above threshold, 0 = below
# Find cells where 1 is adjacent to 0 = boundary
# Convert cell coords back to lat/lon
# Return ordered boundary points
```
### API Endpoint
```python
# Add to coverage calculation response
@router.post("/coverage/calculate")
async def calculate_coverage(...):
result = coverage_service.calculate(...)
# Calculate boundary
if result.points:
boundary = calculate_coverage_boundary(
result.points,
threshold_dbm=settings.min_signal
)
result.boundary = boundary
return result
```
### Frontend
```tsx
// CoverageBoundary.tsx — use returned boundary coords
// Instead of calculating alpha shape on frontend
const CoverageBoundary = ({ points, boundary }) => {
// If server returned boundary, use it
if (boundary && boundary.length > 0) {
return [p.lat, p.lon])} />;
}
// Fallback to current convex hull implementation
return ;
};
```
### Dependencies
Need `shapely` installed:
```
pip install shapely # or pip3 install shapely --break-system-packages
```
Check if already in requirements.txt.
### Testing
- [ ] 5km calculation → boundary follows actual coverage shape
- [ ] 10km calculation → boundary is irregular (terrain-dependent)
- [ ] Toggle boundary on/off works
- [ ] Boundary doesn't crash with < 3 points
---
## Task 5: Results Popup Enhancement (Priority 3 — 15 min)
### Problem
Calculation complete toast/popup doesn't show which models were used.
### Solution
Enhance the toast message after calculation:
```tsx
// Current:
toast.success(`Calculated ${points} points in ${time}s`);
// Enhanced:
const modelCount = result.modelsUsed?.length ?? 0;
const freq = sites[0]?.frequency ?? 0;
const presetName = settings.preset ?? 'custom';
toast.success(
`${points} pts • ${time}s • ${presetName} • ${freq} MHz • ${modelCount} models`,
{ duration: 5000 }
);
```
### Testing
- [ ] After calculation, toast shows: points, time, preset, frequency, model count
---
## Task 6: Native Windows Backend (Priority 3 — Research/Plan)
### Problem
Current setup REQUIRES WSL2. Users without WSL2 can't use RFCP at all.
### Current Flow
```
RFCP.exe (Electron)
→ detects WSL2
→ launches: wsl python3 -m uvicorn ...
→ backend runs in WSL2 Linux
```
### Target Flow
```
RFCP.exe (Electron)
→ Option A: embedded Python (Windows native)
→ Option B: detect system Python (Windows)
→ Option C: keep WSL2 but with fallback
```
### Research Tasks (don't implement yet, just investigate)
1. **Check how Electron currently launches backend:**
```bash
# Look at desktop/ directory
cat desktop/src/main.ts # or main.js
# Find where it spawns python/uvicorn
```
2. **Check if Windows Python works for backend:**
```powershell
# In Windows PowerShell:
cd D:\root\rfcp\backend
python -m uvicorn app.main:app --host 0.0.0.0 --port 8090
# Does it start? What errors?
```
3. **Evaluate embedded Python options:**
- python-embedded (official, ~30 MB)
- PyInstaller (bundle backend as .exe)
- cx_Freeze
- Nuitka (compile Python to C)
4. **Document findings** — create a brief report:
```
RFCP-Native-Backend-Research.md
- Current architecture (WSL2 dependency)
- Windows Python compatibility test results
- Recommended approach
- Migration steps
- Timeline estimate
```
### Goal
User downloads RFCP.exe → installs → clicks icon → everything works.
No WSL2. No manual pip install. GPU auto-detected.
---
## Implementation Order
### Priority 1 (30 min total)
1. **Task 1:** Fix GPU dropdown — make it clickable again
2. **Task 2:** GPU diagnostics + install instructions in UI
3. **Task 3:** Terrain Profile click propagation fix
### Priority 2 (1 hour)
4. **Task 4:** Coverage boundary real contour (shapely)
5. **Task 5:** Results popup enhancement
### Priority 3 (Research only)
6. **Task 6:** Investigate native Windows backend — report only, no implementation
---
## Build & Deploy
```bash
# After implementation:
cd /mnt/d/root/rfcp/frontend
npx tsc --noEmit # TypeScript check
npm run build # Production build
# Rebuild Electron:
cd /mnt/d/root/rfcp/installer
bash build-win.sh
# Test:
# Install new .exe and verify GPU indicator works
```
---
## Success Criteria
- [ ] GPU dropdown opens when clicking badge
- [ ] Dropdown shows device list or install instructions
- [ ] After `pip3 install cupy-cuda12x --break-system-packages` in WSL + restart → GPU visible
- [ ] Terrain Profile click doesn't add ruler points
- [ ] Coverage boundary follows actual signal contour
- [ ] Results toast shows model count and frequency
- [ ] Native Windows backend research document created