Tighten coverage prompt comments and docs

This commit is contained in:
German Khokhlov committed 2026-11-06 20:20:11 +04:00
1 parent 3a78f40024
commit 5c4918affb
12 files changed
+31 -54

No files matched your search

@@ -1,6 +1,6 @@
- Say why Transit and Bike Share are empty outside coverage. Over a city with
no feed or system (London, Tokyo) their Layers rows read
`No feed here yet · 16 regions available` and
`No feed here yet · 17 regions available` and
`No bike share here yet · 61 systems available`, or how low to fly, instead
of `never`; leaving coverage clears the last-update time (German, @gekh,
#1576).
+10 -12
View File
@@ -1975,18 +1975,16 @@ Mapped-installation zoom guidance appears in the layer row without counting as
a global loading failure. Guidance statuses do not suppress independent refresh
errors.
Transit and Bike Share report the same kind of guidance when no feed or system
covers the view, instead of an undated `never`. Above their altitude gates the
rows read `GTFS-RT · Fly below 2,850 km to a covered region` and
`GBFS · Fly below 48 km to a covered city`; lower down over a city neither
covers (London, Tokyo) they read `GTFS-RT · No feed here yet · N regions
available` and `GBFS · No bike share here yet · N systems available`, N being
the enabled feeds and the catalogued systems. The
chip stays ON. A Bike Share city that is loading or failed keeps its own
loading or error text. Leaving every covered feed or system clears the layer's
last-update time, so the row never dates data it no longer draws. The same
English notes are the voice analyst's empty-result notes; both surfaces show
them translated through `translateStatusMessage` (`src/ui/statusText.js`).
Transit and Bike Share say the same when no feed or system covers the view,
instead of an undated `never`. Above their altitude gates the rows read
`GTFS-RT · Fly below 2,850 km to a covered region` and
`GBFS · Fly below 48 km to a covered city`; lower, over a city neither covers
(London, Tokyo), `GTFS-RT · No feed here yet · N regions available` and
`GBFS · No bike share here yet · N systems available` (covered regions,
catalogued systems). The chip stays ON. A loading or failed Bike Share city
shows its own text. Leaving coverage clears the last-update time. The voice
analyst gets the same English notes; both translate them through
`translateStatusMessage` (`src/ui/statusText.js`).
## Places and CCTV request bounds
+2 -3
View File
@@ -2393,9 +2393,8 @@ export class LayerLifecycle {
}
/**
* A tick's failure belongs to the feed it polled. Once the layer's own stats
* stop reporting one (the camera left that feed), the row stops showing it
* instead of waiting for the next tick.
* Drop a tick failure the layer's stats no longer report (the camera left
* that feed) instead of showing it until the next tick.
*/
_dropRecoveredRefreshError(layerId) {
const entry = layerId ? this.layers.get(layerId) : null;
+5 -8
View File
@@ -15,8 +15,7 @@ export function createControls({ state: layerState, services, parts, source }) {
updateInterval: STATUS_POLL_MS,
/**
* Keep the manager so camera-driven coverage changes can repaint the row
* before the next status poll.
* Kept so camera moves can repaint the row before the next status poll.
* @param {object} dataManager DataLayerManager instance.
*/
attachDataManager(dataManager) {
@@ -84,18 +83,16 @@ export function createControls({ state: layerState, services, parts, source }) {
lastUpdate: layerState._lastUpdate,
loading,
};
// No system in range is guidance, not an empty feed: the camera is too
// high, or no catalogued system is near the view. A city that failed
// leaves range empty too, and reports its failure below instead.
// No system in range: the camera is too high or no system is near. A
// failed city also leaves range empty and reports its failure below.
if (
layerState._enabled &&
!loading &&
layerState._activeCityIds.size === 0 &&
layerState._failedCityIds.size === 0
) {
// English data (the analyst's empty note to the model, and the Layers
// row's prompt); both show it through translateStatusMessage
// (ui/statusText.js).
// English: the analyst's empty note and the row's prompt, both shown
// through translateStatusMessage (ui/statusText.js).
const note = layerState._altitudeGateEnabled
? `No bike share here yet · ${GBFS_CITY_REGISTRY.length} systems available`
: `Fly below ${Math.round(ACTIVATION_ENTER_ALTITUDE_M / 1000).toLocaleString('en-US')} km to a covered city`;
+2 -5
View File
@@ -1,10 +1,7 @@
// BIKESHARE COVERAGE STATUS — a view with no system says so.
//
// Over a city with no catalogued system (London, Tokyo) the layer drew nothing
// and reported nothing, so its row read "ON · — · GBFS · never": the same as a
// feed that had never answered. These cases pin the rule that replaced it:
// with no system in range the layer reports a guidance status that says why,
// and a view it left keeps no date for data it no longer draws.
// With no system in range (London, Tokyo) the layer says why instead of
// "never", and keeps no date for data it no longer draws.
//
// Run with: npm test
import { test } from 'node:test';
-2
View File
@@ -1,5 +1,3 @@
// Bike Share row repaints: entering a city, leaving it or nudging the camera
// mid-load, and leaving a failed city.
// Bike Share: after the camera settles, the last row the panel painted must
// match what the layer reports now. Each case drives the real viewport,
// ingestion, rendering and controls; the manager is a spy that records the
+1 -4
View File
@@ -116,10 +116,7 @@ export function createViewport({ state: layerState, services, parts, source }) {
parts.rendering.removeCityPoints(cityId);
}
/**
* Repaint the layer's row now. Its text follows the camera (coverage
* prompt, station count), and the manager tick is 60 s.
*/
/** Repaint the row now: it follows the camera, the manager tick is 60 s. */
function refreshRow() {
layerState._dataManager?.refreshLayerStats?.('bikeshare');
}
+2 -4
View File
@@ -1,7 +1,5 @@
// When the camera changes which coverage prompt a Transit or
// Bike Share row shows, the layer asks the panel to repaint at once, instead
// of leaving the old prompt up until the manager's next tick (Transit 15 s,
// Bike Share 60 s).
// A camera move that changes a Transit or Bike Share coverage prompt repaints
// the row at once, not at the next manager tick (15 s, 60 s).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createState as createBikeState } from './bikeshare/state.js';
+2 -4
View File
@@ -1,7 +1,5 @@
// The Transit coverage prompt names the altitude the closed gate
// actually opens at. Descending over Boston to 2,900 km (below 3,000 km, above
// the 2,850 km enter threshold) loads nothing, so a prompt "Fly below 3,000 km"
// asks for something the camera has already done.
// The Transit prompt names the altitude the gate opens at: at 2,900 km over
// Boston (under 3,000 km, above the 2,850 km enter threshold) nothing loads.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createState } from './state.js';
+2 -3
View File
@@ -245,9 +245,8 @@ export function createQueries({ state, parts }) {
const health = aggregateTransitFeedHealth(statuses, now);
const count = state._vehicles.size;
if (state._enabled && active.length === 0) {
// English data (the analyst's empty note to the model, and the Layers
// row's prompt); both show it through translateStatusMessage
// (ui/statusText.js).
// English: the analyst's empty note and the row's prompt, both shown
// through translateStatusMessage (ui/statusText.js).
const note = state._altitudeGateOpen
? `No feed here yet · ${TRANSIT_REGION_COUNT} regions available`
: `Fly below ${Math.round(ACTIVATION_ENTER_ALTITUDE_M / 1000).toLocaleString('en-US')} km to a covered region`;
+2 -4
View File
@@ -111,8 +111,7 @@ export function createViewport({ state, services, parts }) {
changed = true;
}
}
// Leaving coverage leaves nothing on the map to date: a row that read
// "26s ago" over an empty view would claim a feed that is no longer here.
// Nothing is left on the map to date.
if (desired.size === 0) state._lastUpdate = null;
for (const [feedId, feed] of desired) {
if (!state._activeFeeds.has(feedId)) {
@@ -124,8 +123,7 @@ export function createViewport({ state, services, parts }) {
parts.ingestion.sweepAgedVehicles(Date.now());
parts.rendering.syncRenderHold();
governorRequestRender('transit-proximity');
// The row's text follows the camera (coverage prompt, feed list), so it
// repaints now instead of at the next 15 s manager tick.
// Repaint now: the row follows the camera, the manager tick is 15 s.
if (changed) state._dataManager?.refreshLayerStats?.('transit');
}
+2 -4
View File
@@ -269,10 +269,8 @@ export function translateStatusLine(text) {
/** Thousands separators out of an English number ("3,000" → 3000). */
const englishNumber = (text) => Number(text.replace(/,/g, ''));
// Coverage notes of a layer whose view has no feed, whole lines with a number
// in them: transit (transit/queries.js) and bikeshare (bikeshare/controls.js).
// Their " · " is part of one sentence, so they are matched before the line is
// split into pieces. Keep these in step with the producers.
// Coverage notes (transit/queries.js, bikeshare/controls.js). Their " · " is
// inside one sentence, so they match whole before the line is split.
const COVERAGE_NOTE_PATTERNS = [
[
/^Fly below ([\d,]+) km to a covered region$/,