6.5 KiB
RFCP - Iteration 8.1: Clone Fix + Coverage Gaps
Issue 1: Clone Creates New Site (Critical!)
Problem: Clone button creates new site instead of adding sector to existing site.
Root Cause: cloneSector function in sites store creates new site object.
Solution
File: frontend/src/store/sites.ts
Current (WRONG):
const cloneSector = (siteId: string) => {
const site = sites.find(s => s.id === siteId);
const clone = {
...site,
id: uuid(),
name: `${site.name}-clone`
};
setSites([...sites, clone]); // Creates NEW site ❌
};
Fixed (CORRECT):
const cloneSector = (siteId: string) => {
const site = sites.find(s => s.id === siteId);
if (!site) return;
// Get last sector as template
const lastSector = site.sectors[site.sectors.length - 1];
// Create new sector (NOT new site!)
const newSector: Sector = {
...lastSector,
id: `sector-${Date.now()}`,
azimuth: (lastSector.azimuth + 120) % 360, // 120° offset for tri-sector
};
// Add sector to EXISTING site ✅
updateSite(siteId, {
sectors: [...site.sectors, newSector]
});
// Clear coverage to force recalculation
useCoverageStore.getState().clearCoverage();
};
Update Button Label
File: frontend/src/components/panels/SiteList.tsx
<button onClick={() => cloneSector(site.id)}>
+ Add Sector {/* was: "Clone" */}
</button>
Issue 2: Coverage Gaps at 800m Wide
Problem: At 800m heatmap radius with 300m resolution, coverage points don't overlap enough → visible dots.
Why it happens:
- Resolution: 300m (distance between coverage points)
- Heatmap radius: 800m
- At high zoom, 800m radius in pixels is HUGE
- But point spacing (300m) stays same
- Result: Gaps between points visible
Solution A: Warn User (Quick)
File: frontend/src/components/panels/CoverageSettings.tsx
<select
value={heatmapRadius}
onChange={(e) => setHeatmapRadius(Number(e.target.value))}
>
<option value={200}>200m — Fast</option>
<option value={400}>400m — Balanced</option>
<option value={600}>600m — Smooth</option>
<option value={800}>800m — Wide</option>
</select>
{/* Warning for 800m */}
{heatmapRadius === 800 && resolution > 200 && (
<div className="warning">
⚠️ Wide radius works best with fine resolution (≤200m).
Current: {resolution}m. Consider reducing for smoother coverage.
</div>
)}
Solution B: Auto-adjust Resolution (Better)
File: frontend/src/store/coverage.ts
const calculateCoverage = async () => {
// Auto-adjust resolution based on heatmap radius
// Rule: resolution should be ≤ radius/2 for smooth coverage
const recommendedResolution = Math.min(
resolution,
Math.floor(heatmapRadius / 2)
);
if (recommendedResolution < resolution) {
console.log(`Auto-adjusting resolution: ${resolution}m → ${recommendedResolution}m for ${heatmapRadius}m radius`);
}
const effectiveResolution = recommendedResolution;
// Calculate with adjusted resolution
await worker.calculateCoverage({
sites,
radius,
resolution: effectiveResolution,
rsrpThreshold
});
};
Solution C: Dynamic Point Sampling (Advanced)
File: frontend/src/components/map/HeatmapTileRenderer.ts
Add adaptive point sampling in renderer:
private drawPoint(
intensityMap: Float32Array,
point: CoveragePoint,
centerX: number,
centerY: number,
radiusPixels: number
): void {
// ... existing code
// ADAPTIVE: If radius is very large, increase sampling
const sampleFactor = radiusPixels > 100 ? 2 : 1;
for (let y = minY; y < maxY; y += sampleFactor) {
for (let x = minX; x < maxX; x += sampleFactor) {
// ... draw with interpolation
}
}
}
Solution D: Clamp Max Radius (Safest)
File: frontend/src/components/panels/CoverageSettings.tsx
// Limit radius based on resolution
const maxAllowedRadius = resolution * 3; // 3x resolution max
<select
value={heatmapRadius}
onChange={(e) => {
const newRadius = Number(e.target.value);
if (newRadius > maxAllowedRadius) {
toast.warning(`Radius ${newRadius}m too large for ${resolution}m resolution. Max: ${maxAllowedRadius}m`);
return;
}
setHeatmapRadius(newRadius);
}}
>
<option value={200} disabled={resolution > 100}>200m — Fast</option>
<option value={400} disabled={resolution > 200}>400m — Balanced</option>
<option value={600} disabled={resolution > 300}>600m — Smooth</option>
<option value={800} disabled={resolution > 400}>800m — Wide</option>
</select>
Issue 3: Coverage Not Cleared on Sector Delete
File: frontend/src/store/sites.ts
const removeSector = (siteId: string, sectorId: string) => {
const site = sites.find(s => s.id === siteId);
if (!site || site.sectors.length <= 1) {
toast.error('Cannot remove last sector');
return;
}
const updatedSectors = site.sectors.filter(s => s.id !== sectorId);
updateSite(siteId, { sectors: updatedSectors });
// CRITICAL: Clear coverage!
useCoverageStore.getState().clearCoverage();
toast.success('Sector removed. Recalculate coverage to update.');
};
Recommended Fix Priority
Priority 1 (Critical):
- Fix cloneSector to add sector, not create site
- Update button label to "+ Add Sector"
- Clear coverage on sector delete
Priority 2 (Important):
- Add warning for 800m + 300m combo
- OR auto-adjust resolution based on radius
Priority 3 (Nice to have):
- Clamp max radius based on resolution
- Dynamic point sampling
Testing
Clone Fix:
- Create site
- Click "+ Add Sector"
- Should show "Sites (1)" with 2 sectors ✅
- NOT "Sites (2)" ❌
Coverage Gaps:
- Set resolution 300m
- Set radius 800m
- Calculate coverage
- At high zoom (16+), check for dots
- If dots visible → show warning OR auto-adjust
Build & Deploy
cd /opt/rfcp/frontend
npm run build
sudo systemctl reload caddy
Commit Message
fix(sites): clone adds sector to existing site, not new site
- Fixed cloneSector to add sector to same site
- Changed button label to "+ Add Sector"
- Added coverage cache clear on sector delete
- Sites count now accurate (counts sites, not sectors)
fix(coverage): prevent gaps with 800m radius
- Added warning for wide radius + coarse resolution
- Auto-adjust resolution to radius/2 for smooth coverage
- Clear coverage cache on sector changes
🚀 Ready for 8.1!