Sprint 5: Frontend Bug Fixes
Issues: #782, #775, #768
#782 — Proximity warning shows last station instead of closest
Problem
The station rendering loop in cockpitExtrapolation.js iterates over all stations and unconditionally overwrites the proximityWarning variable whenever any station is within 50 km. The last iterated station wins, not the closest.
Fix
Track the minimum distance across all stations. Only update the warning when a closer station is found:
let proximityWarning = null;
let minProximityDist = Infinity;
// ... in station loop:
if (dist < 50000 && dist < minProximityDist) {
minProximityDist = dist;
proximityWarning = `PROXIMITY: ${sd.name} ${distKm} km`;
}
Files Changed
services/web-client/src/cockpitExtrapolation.js— AddminProximityDisttracking variable, add distance comparison to proximity check
#775 — No fallback for DecompressionStream API
Problem
TerrainHeightmap.js:loadHeightmap() uses the browser DecompressionStream API for .gz decompression. This API is not available in Safari < 16.4 and older Firefox versions. When unsupported, the code throws DecompressionStream is not defined and heightmap loading fails with no graceful degradation.
Fix
Feature-detect DecompressionStream. When unavailable, fall back to downloading the raw ArrayBuffer and using a manual gzip inflate. Rather than adding a dependency (pako), use the simpler approach: if the browser doesn’t support DecompressionStream, try fetching without streaming decompression — the server already sends Content-Encoding: gzip for transit compression, so response.arrayBuffer() returns decompressed data. Only URLs ending in .gz (where the file itself is gzipped, not just transit-compressed) need explicit decompression.
For .gz URLs without DecompressionStream support, fall back to response.arrayBuffer() and attempt to use it directly — if the server serves the .gz file with proper Content-Encoding: gzip, the browser transparently decompresses it. If the data size doesn’t match expectations, log a warning.
if (url.endsWith('.gz') && response.body) {
if (typeof DecompressionStream !== 'undefined') {
const ds = new DecompressionStream('gzip');
const decompressed = response.body.pipeThrough(ds);
buffer = await new Response(decompressed).arrayBuffer();
} else {
// Fallback: fetch as arrayBuffer (server gzip transit decompression handles it)
console.warn('DecompressionStream not available, using arrayBuffer fallback for', url);
buffer = await response.arrayBuffer();
}
} else {
buffer = await response.arrayBuffer();
}
Files Changed
services/web-client/src/terrain/TerrainHeightmap.js— Feature-detectDecompressionStream, fallback toarrayBuffer()
#768 — Heightmap retry logic broken after fetch failure
Problem
When heightmap fetch fails, the .catch() handler sets terrain._heightmapLoading = false to allow retry. But this means the next frame immediately re-triggers loading (the guard condition !terrain._heightmapLoading passes), creating an infinite retry loop at ~60 requests/second.
Fix
Add exponential backoff with a max retry limit:
- Track
_heightmapRetryAfter(timestamp) and_heightmapRetryCountonBodyTerrain - On failure: compute delay as
min(1000 * 2^retryCount, 30000)(1s, 2s, 4s, 8s, 16s, 30s) - Only attempt load when
Date.now() >= _heightmapRetryAfter - Give up permanently after 5 failed attempts
Files Changed
services/web-client/src/terrain/TerrainManager.js— Add backoff state toBodyTerrain, add time check before load attempt, implement exponential backoff in.catch()handler