From 5c4918affb792bbfe4ac863031b3dcd55734a30c Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Fri, 6 Nov 2026 20:20:11 +0400 Subject: [PATCH] Tighten coverage prompt comments and docs --- .../1576-transit-bikeshare-coverage-prompt.md | 2 +- docs/CURRENT-STATE.md | 22 +++++++++---------- src/data/lifecycle.js | 5 ++--- src/layers/bikeshare/controls.js | 13 +++++------ src/layers/bikeshare/coverageStatus.test.mjs | 7 ++---- src/layers/bikeshare/rowRepaint.test.mjs | 2 -- src/layers/bikeshare/viewport.js | 5 +---- src/layers/promptRepaint.test.mjs | 6 ++--- src/layers/transit/gatePrompt.test.mjs | 6 ++--- src/layers/transit/queries.js | 5 ++--- src/layers/transit/viewport.js | 6 ++--- src/ui/statusText.js | 6 ++--- 12 files changed, 31 insertions(+), 54 deletions(-) diff --git a/changelog.d/1576-transit-bikeshare-coverage-prompt.md b/changelog.d/1576-transit-bikeshare-coverage-prompt.md index c3dcbe82a..660c1f30b 100644 --- a/changelog.d/1576-transit-bikeshare-coverage-prompt.md +++ b/changelog.d/1576-transit-bikeshare-coverage-prompt.md @@ -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). diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 69d83d759..13cce0050 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -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 diff --git a/src/data/lifecycle.js b/src/data/lifecycle.js index c01926451..3ea02eca9 100644 --- a/src/data/lifecycle.js +++ b/src/data/lifecycle.js @@ -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; diff --git a/src/layers/bikeshare/controls.js b/src/layers/bikeshare/controls.js index 03bb544ab..d90a62b5b 100644 --- a/src/layers/bikeshare/controls.js +++ b/src/layers/bikeshare/controls.js @@ -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`; diff --git a/src/layers/bikeshare/coverageStatus.test.mjs b/src/layers/bikeshare/coverageStatus.test.mjs index a9f17e73e..004c75c94 100644 --- a/src/layers/bikeshare/coverageStatus.test.mjs +++ b/src/layers/bikeshare/coverageStatus.test.mjs @@ -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'; diff --git a/src/layers/bikeshare/rowRepaint.test.mjs b/src/layers/bikeshare/rowRepaint.test.mjs index 144ff5502..5f5538168 100644 --- a/src/layers/bikeshare/rowRepaint.test.mjs +++ b/src/layers/bikeshare/rowRepaint.test.mjs @@ -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 diff --git a/src/layers/bikeshare/viewport.js b/src/layers/bikeshare/viewport.js index 008c01333..d4cc44cb6 100644 --- a/src/layers/bikeshare/viewport.js +++ b/src/layers/bikeshare/viewport.js @@ -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'); } diff --git a/src/layers/promptRepaint.test.mjs b/src/layers/promptRepaint.test.mjs index 563aaeac7..38193ca72 100644 --- a/src/layers/promptRepaint.test.mjs +++ b/src/layers/promptRepaint.test.mjs @@ -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'; diff --git a/src/layers/transit/gatePrompt.test.mjs b/src/layers/transit/gatePrompt.test.mjs index 7f439fcdc..dc4c3e863 100644 --- a/src/layers/transit/gatePrompt.test.mjs +++ b/src/layers/transit/gatePrompt.test.mjs @@ -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'; diff --git a/src/layers/transit/queries.js b/src/layers/transit/queries.js index 2efe928d5..7a9f2e074 100644 --- a/src/layers/transit/queries.js +++ b/src/layers/transit/queries.js @@ -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`; diff --git a/src/layers/transit/viewport.js b/src/layers/transit/viewport.js index a62d49009..38315e4ae 100644 --- a/src/layers/transit/viewport.js +++ b/src/layers/transit/viewport.js @@ -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'); } diff --git a/src/ui/statusText.js b/src/ui/statusText.js index f4cbb59a0..d13faff7e 100644 --- a/src/ui/statusText.js +++ b/src/ui/statusText.js @@ -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$/,