fix(bikeshare): scope city failures to range and report partial outages

A failure now ends when its city leaves range or the camera rises above the
activation gate, so an empty map no longer reads LOAD FAILED. Late activation
and status rejections are ignored like late successes. Partial failure reads
DEGRADED, every in-range city failing over cached points reads STALE, and
freshness advances only on a refresh that succeeded. Behavioural tests drive
the real proximity, update and enable paths. Docs say no licence is published
for Toronto and list Social Bicycles and PBSC. The merge also restored the #61
GBFS v3 changelog entry that an earlier merge on main dropped.
This commit is contained in:
Sameh Khamis committed 2026-10-15 13:08:45 -07:00
1 parent c9f43e8517
commit 51e32a30f2
9 files changed
+351 -24

No files matched your search

+14 -7
View File
@@ -56,7 +56,7 @@ export function createControls({ state: layerState, services, parts, source }) {
/**
* Return current layer statistics for the UI status display.
* @returns {{ count: number, lastUpdate: number|null, loading: boolean, loadingLabel?: string, error?: string }}
* @returns {{ count: number, lastUpdate: number|null, loading: boolean, loadingLabel?: string, error?: string, degraded?: boolean, stale?: boolean }}
*/
getStats() {
const stats = {
@@ -71,12 +71,19 @@ export function createControls({ state: layerState, services, parts, source }) {
: 'scanning nearby systems...';
}
// A failed city is only the layer's failure when it left nothing on the
// map. While any city still renders, the layer is working and says so.
if (
layerState._failedCityIds.size > 0 &&
layerState._stationRenderMap.size === 0
) {
stats.error = 'GBFS fetch error';
// map. While other cities still render, the partial failure reads
// DEGRADED; when every in-range city failed and only previously drawn
// points remain, they read STALE.
if (layerState._failedCityIds.size > 0) {
if (layerState._stationRenderMap.size === 0) {
stats.error = 'GBFS fetch error';
} else {
stats.degraded = true;
const allFailed = Array.from(layerState._activeCityIds).every(
(cityId) => layerState._failedCityIds.has(cityId),
);
if (allFailed) stats.stale = true;
}
}
return stats;
},