The existing "route that fits" case used 199 steps, which is how the
off-by-one went unnoticed. Folds the docs note into the existing cap sentence
and makes the changelog entry specific.
Number(null) is 0, so the cockpit cloud profile treated a missing weather code
as clear but available. The briefing's three checks now share one helper, and
the current-state note is its own bullet instead of folding into the route one.
Clears the timeout guards once each race settles, measures the 15,000-vehicle transit ingestion with main-thread CPU time instead of wall time, and times the region-timeout case from the boundary lookup with a 2 s ceiling, below the 3 s default budget, so an ignored budget still fails.
Co-authored-by: Sameh Khamis <[email protected]>
- Pin finiteCoordinate helper behavior across numeric, non-finite, and string inputs
- Verify recovery stage downrange distance, flight numbers, and coordinates fall back safely to null on non-finite data
- Pin recovery status transitions for LOST and EXPENDABLE stage categories
- Verify payload massKg and amount normalization edge cases for blank, negative, and non-numeric values
This branch moved twice at once: upstream merged KartaView (#1121, a
third keyless street-level provider sharing a new lineCoverage.js), and
the remote i18n-spanish gained the whole-interface translation work
(1993 catalog entries, translateKnown, data-i18n-runtime, the unmarked-
string detector in npm run i18n) built on the Street View merge.
Integration keeps the remote's mechanisms and re-adds KartaView on top:
- presentMeta: remote's translateHint for coverage hints, plus the
KartaView count line as its own literal tn pair (Russian plurals need
the noun inside the template; keyed off coverageLabel.noun with the
sequences pair as the default).
- uiState: upstream's highlightsSequence legend gate (KartaView's lines
do not highlight) with t('Selected').
- layer-panels.html: remote's data-i18n/data-i18n-runtime hooks plus the
sl-since-note wiring KartaView's upload-date SINCE note needs.
- kartaview viewer: the photo-navigation aria-label and img alt go
through t(), as the remote's unmarked-string detector requires.
- catalogs: remote's 1987-entry base (its refined keys supersede this
branch's earlier Street View wording) plus the KartaView entries:
coverage hints, the upload-date note, the streets count line (with
Russian one/few/many/other forms) and the viewer strings.
npm run i18n: complete, no unmarked strings. npm test: 6520/6522 (the
one failure is the pre-existing environmental OPENAI_API_KEY leak in
devFreshDotenv). Build passes.
Upstream's KartaView PR (#1121) extracted the Mapillary coverage engine
into a shared lineCoverage.js and made the panel wording
provider-specific (coverage noun/action, SINCE upload-date note,
highlightsSequence legend gate), again in the regions this branch
localizes.
- mapillary/coverage.js: taken as upstream's thin re-export of the new
lineCoverage.js; the generic too-high hint stays msg()-marked, now in
lineCoverage.js where the default lives.
- kartaview/policy.js: coverage hints (tooHigh, noCoverage) marked as
msg() data so they translate like every other coverage hint; thrown
error messages stay English, matching the Mapillary precedent.
- presentMeta: KartaView counts streets, and Russian plurals need the
noun inside the template, so the count line gets its own literal tn
pair keyed off coverageLabel.noun, falling back to the sequences pair;
upstream's noun/action data contract is untouched.
- uiState legend: upstream highlightsSequence gate with t('Selected');
sinceNote localized with a {names} placeholder.
- layer-panels.html: sl-since-note wiring kept, data-i18n hooks kept.
- CHANGELOG: both sides kept. es/ru: 4 new keys each.
npm run i18n complete (953 messages each); npm test 6504/6506 (the one
failure is the pre-existing environmental OPENAI_API_KEY leak in
devFreshDotenv, unrelated); build passes.
A final review of both PDFs found text the earlier checks missed:
- Map cards: fire confidence (high/nominal/low) and age (24h), vessel
types (TOWING, TANKER) and installation classes (BASE, AIRFIELD) showed
in English. They now go through the catalog; the data stays English.
- The wind readout kept "10-14 14:00 UTC" and "29.93°N 97.74°W"; it now
uses the locale's date, decimal separator and hemisphere letters.
- Catalog: one term per concept (label/подпись, basemap/подложка,
PROVIDER CONTROLS, HEARD, zoom in, REGIONAL NEWS), calques and broken
grammar in the scene editor, provider settings and Bhote Koshi notes,
and Spanish wording that read wrong ("restringidas por el proveedor",
"Incluye … no se incluyen", "justo ahora", "Sal").
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NYC DOT asks developers who use its camera feed to sign a data-sharing
agreement, and this project does not hold one that covers downstream
installs. The pack now loads only when CCTV_NYC_ENABLED=1; unset, empty
or any other value makes no request to webcams.nyctmc.org (tested).
The NYC credit carries NYC DOT's requested application disclaimer
verbatim, and DATA_SOURCES, README, CHANGELOG, CURRENT-STATE and
.env.example describe the prerequisite.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The layer recorded failure in a single `_error` string. Every city feed is
fetched independently, so one retired operator set that flag for the whole
layer — and the only code that cleared it ran on a successful *activation*,
which `runProximityCheck` skips for a city that already has rendered points.
A dead feed that re-entered camera range therefore set the flag on every
camera move and nothing ever cleared it: a permanent LOAD FAILED over a map
drawing every other city's stations.
Failure is now tracked per city in `_failedCityIds`, and `getStats()` reports
it only when the failures left nothing to draw. A city that recovers removes
its own entry rather than waiting for a neighbour to succeed.
This was reachable only because Buffalo sits 95 km from Toronto, inside the
100 km activation radius — the one dead city in the registry with a healthy
neighbour in range. The other four failed alone, where LOAD FAILED with an
empty map reads as correct, which is why the latch went unnoticed.
Probing all 33 registry systems found five dead. Three had moved and are
repointed: Reddy Bikeshare (Buffalo) to Social Bicycles, Tugo (Tucson) and
Trinity Metro (Fort Worth) to PBSC, all verified serving stations. Two are
dropped because the systems wound down rather than moved — Broward B-cycle
404s at every documented endpoint, and CoGo's replacement Lyft feed publishes
zero stations with status last reported in March 2025. `gbfs.cogobikeshare.com`
no longer resolves, so its allowlist entry goes too.
All 31 remaining systems were verified live, and every registry URL was
checked against the proxy host and path allowlists.
Toronto runs the same PBSC platform as the Austin, Honolulu and
Chattanooga systems already in the registry, and publishes the identical
GBFS v2 endpoint pair under `toronto.publicbikesystem.net`. The proxy
allowlist already admits the host through its `*.publicbikesystem.net`
wildcard and the paths end in the expected `station_information.json` /
`station_status.json`, so the system needs no new plumbing — only the
registry entry that names it.
Verified against the live feeds: 1,069 stations in both documents, all
1,069 joining on `station_id`. The layer reads `num_bikes_available`,
which Toronto populates normally, so availability renders without a
vendor-specific mapping. It does not populate the standard
`num_bikes_available_types`, reporting per-type counts under PBSC ids
(ICONIC/BOOST/EFIT/ASTRO) instead; nothing reads that field today, and
this entry deliberately does not translate it rather than adding a
mapping with no consumer.
The center is downtown at 43.6532, -79.3832 with the group's 90 km load
radius. The stations span roughly 37 km east-west, from Etobicoke to
Scarborough, so a tighter radius would drop the outer ends on a downtown
pan.
The new registry test is the reason this was cheap to be sure about: it
holds every city's feed URLs against the same host and path predicates
the proxy enforces, so a city the proxy would silently refuse now fails
the suite instead of rendering an empty layer.
Credited to Bike Share Toronto (Toronto Parking Authority). The City's
open-data package declares no licence and the feed publishes no
`license_url`, so it is treated as attribution-only, like the other GBFS
operators, rather than claiming a licence that was never granted.
The province's 511 pack covers Ontario's highways; this adds the municipal
network underneath it — roughly 336 cameras across downtown arterials plus
the Gardiner, DVP, Allen and Lake Shore, from the City's own open data.
The list is keyless and carries no frame URL, so frame URLs are built from
the convention the dataset documents (`CameraImages/loc####.jpg`) and a
camera number validated as plain digits. The frame proxy is therefore
pinned to the portal origin by construction rather than by trusting a
field. The list fetch refuses redirects, bounds the body it buffers, and
releases the transport on every rejection path.
The dataset publishes no camera facing. `D1`-`D4` look like a compass and
map trivially onto one, but they describe the *comparison* images — static
reference stills captured in 2016 and served from a different path — not
the live pan-tilt-zoom frame. A heading derived from them would be
confidently wrong for every camera in the city, so every camera takes the
shared id-hash fallback at low confidence, as headingless Austin, TfL,
Fintraffic and Calgary cameras do. A test pins that down so it is not
"fixed" later.
Names stay verbatim: the feed's labels are upper-case, and title-casing
would mangle MCCOWAN RD and ST HELEN'S AVE more often than it helped.
Carries the Open Government Licence – Toronto attribution the licence
requires. Disable with `CCTV_TORONTO_ENABLED=0`.
Main added the Google Street View street-level provider and key guidance
that offers another source. Its new text goes through t()/msg() with ru
and es entries; the key guidance the panel shows is built in the UI
language by src/ui/keyGuidance.js, while keySetupCore.mjs keeps the
English sentences the server and AI tools use.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Upstream's Street View rework (second imagery provider, one source at a
time, OPEN STREET VIEW button, key-alternative guidance) rewrote exactly
the street-level regions this branch localizes.
Resolved as upstream logic + re-applied localization:
- keySetupCore: new unlock copy kept as msg() data; added
keySetupAlternativeKeyNames() returning machine values (registry key
names) so key-setup core stays English while presentation localizes.
- New src/ui/keyGuidance.js: localized key-guidance composer shared by
layerPanel and streetLevelPresentation (requirement sentence plus the
"or light X to use your Y key" alternatives tail, placeholders only).
- streetLevelPresentation: keyGuidance()/presentNearest()/presentMeta
relocalized (t/tn + formatNumber instead of toLocaleString);
renderModes and month-precision dates kept from upstream.
- layer-panels.html: upstream row ids, sl-meta-row and the new button
kept, data-i18n hooks re-applied (incl. OPEN STREET VIEW fallback).
- es/ru catalogs: 13 new keys, stale unlock key replaced in place.
npm run i18n: complete for es and ru. npm test: 6345/6347; the one
failure (devFreshDotenv provenance) also fails on plain upstream/main
and on the pre-merge tip here - this machine leaks OPENAI_API_KEY into
spawned children with a replaced env, so it is environmental.
- Satellite class in analyst records and voice payloads stays English
("STATION", "COMMS"), so voice filters on the class work in every
language; cards and the legend still show the translated label.
- Layer source names (OSM routing, ALPR sources) stay English in layer
state; the layer row translates them.
- New translateKnown(): catalog text when there is an entry, the given text
otherwise, with no warning. The layer row uses it for loading labels and
errors, Recent Imagery for its notices.
- Mark the remaining Recent Imagery and wind messages.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review on #856 (kvnloo): the analyst projection flattened a 7700 squawk and
the readsb ADS-B emergency field into the same emergency/emergencyKind pair.
- mapAnalystRecord (flights, military) adds emergencySource ('squawk' |
'ads-b' | null, from aircraftEmergency().source), emergencyBroadcast (new
pure emergencyBroadcastPhrase: "broadcasting squawk 7700 (general
emergency)" vs "ADS-B reports general emergency") and observedAtMs (the
record's lastContactEpochMs: last transponder message, epoch ms).
- emergencySource is a queryable analyst text field; the voice payload passes
it and emergencyBroadcast through, and observedAtMs only for aircraft with
a status. Tool description asks the model to narrate with the phrase.
- Source-to-analyst fixture test: OpenSky 7700 vs adsb.lol emergency=general
on squawk 1200 differ in provenance and timestamp; filterable by source.
- Voice tool-payload pins re-derived for the additive description change.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- aircraftEmergency: `source` is 'squawk' only when the Mode A code itself
names the status; the Cockpit HUD no longer quotes an unrelated squawk
(e.g. SQUAWK 1200 on an ADS-B `downed` status).
- military: new billboards and the untrack restore use the fleet tint, so an
emergency stays red instead of snapping back to amber; tint helpers take the
record already in hand instead of a second lookup.
- cockpit/cyber CSS: emergency colour via a custom property / theme token,
meta line bolded so the status does not rely on colour alone in the HUD.
- tests: revert Prettier churn in unscoped test files; only semantic edits.
- docs/CURRENT-STATE.md: transponder emergency highlighting section.
- qa-emergency-squawk: (iv) RELEASE check; new files added to format scope.
Co-Authored-By: Claude <[email protected]>
Flights and Military Flights tint aircraft red while the latest poll
carries squawk 7500/7600/7700 or an adsb.lol ADS-B emergency status
(general/unlawful/nordo/downed). Minimum-fuel and lifeguard priorities
are reported but not tinted. The Cockpit HUD aircraft-meta line names
the broadcast code in red. The status is not sticky: a cleared code
stops highlighting on the next refresh.
The analyst/voice query engine gains squawk, emergency and
emergencyKind fields. No new source or request: OpenSky state vectors
already carry the squawk and adsb.lol/readsb carries both fields.
Built by the Hex project (Crew Leo Agile dev team).
Co-Authored-By: Claude <[email protected]>
The layer compared the server's fetchedAt with the browser's clock, so a
skewed client clock could shift or void the 30-minute window. The proxy now
returns ageMs (its own now minus fetchedAt, at serve time, on fresh, cached
and stale answers); the source refuses a snapshot without a finite,
non-negative ageMs, and the layer dates rows as its now() - ageMs. fetchedAt
stays in the payload for display.
Tests: proxy cached/stale answers report ageMs on the server clock; the
source rejects missing/invalid/negative ageMs; the layer replay test drives
ageMs. qa-gnss stubs send ageMs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Maintainer review on #855 (mazy1998, 2026-10-06):
- records: presence (seenAt) and degraded evidence (degradedAt) age
separately, so continuing healthy reports no longer keep an older
degraded flag alive past 30 minutes; an aircraft returning after the
window starts fresh. pruneGnssObservations ages the store with no new
rows.
- Rows are dated by the snapshot's fetchedAt, which the proxy keeps on
cached and stale answers: a replay never restarts the window, the
proxy stops serving a stale snapshot older than the window, the source
refuses an undated one, and cells expire during fetch failures and
while there is no view anchor.
- Normalization accepts ADS-B versions 1 and 2 only; a missing, invalid
or negative seen_pos is not a confirmed position from the last minute.
- qa-gnss: keyboard, 390x844 viewport (fresh page: isMobile reloads) and
replay-age sections; the live cell assertion bins the returned rows
with the layer's own rules instead of a raw row count.
Review on #855: the per-aircraft rule (NIC < 7 || NACp < 8, borrowed from
the US 14 CFR 91.227 ADS-B Out minima) is GEV-defined, not gpsjam's, and
gpsjam's map shows low navigation accuracy, not necessarily jamming.
- Primary cell state is now `navigation-integrity-anomaly` (medium/high) or
`nominal`; `suspected-gnss-interference` is a secondary `interpretation`
marked validated: false.
- GNSS_PROVENANCE / gnssProvenance({ windowMs }) records the classifier
(gev-nic-nacp-v1, GEV, unvalidated), the gpsjam aggregation (formula,
bands [0.02, 0.1], FAQ source) and the GEV 30-minute visited-view window,
built from the constants the code applies. Exposed as layer.provenance
and getStats().provenance; the proxy tags responses with `classifier`.
- Visible name "GNSS Integrity", legend "... low accuracy", blurb leads with
low navigation accuracy and states the threshold is a GEV choice.
- Layer id `gnss-interference` and share token `0` unchanged.
- Voice aliases add "gnss integrity", "navigation integrity",
"low navigation accuracy"; the interference aliases stay.
- Docs describe the method as GEV threshold + gpsjam aggregation.
- Tests: provenance object, renamed state/label, proxy classifier tag;
qa-gnss asserts provenance and wording in the real app.
Hex project - Crew Leo Agile dev team.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- Route the adsb.lol GNSS source through createReferenceSources and the
constructCatalog contract; app layer module is a thin pass-through.
- Describe the method honestly: gpsjam.org's published cell formula and
bands; per-aircraft thresholds are the 14 CFR 91.227(c) ADS-B Out minima
(NIC < 7, NACp < 8) plus readsb gpsOkBefore.
- Proxy: reuse the adsb.lol anchor parser, honour upstream Retry-After
(clamped 5-120 s), serve stale during cooldown, cap the body size.
- Layer: redraw only when a cell id or band changes, treat a settling
camera as a retry rather than a rejected enable, keep low cells visible,
skip rows without finite coordinates, state the 3-aircraft rule in the
legend blurb.
- Voice: gnss-interference in set_layer_visibility / show_data_layers_menu
plus common aliases; voice pins re-derived.
- qa-gnss: stub the route in-page (request interception stalls Cesium's
geometry workers), assert every cell is built, cover all bands over land
and sea, screenshot every state with the Data Layers row open.
Hex project - Crew Leo Agile dev team.
Co-Authored-By: Claude <[email protected]>
Bins airborne ADS-B v1/v2 aircraft that report degraded navigation
integrity (NIC < 7, NACp < 8, or readsb's GPS-loss flag) into 0.5 degree
cells over a rolling 30-minute window, coloured with the gpsjam.org
bands. Keyless: reads the capped adsb.lol point snapshot around the view
through /api/gnss-integrity, which returns integrity fields only, caches
60 s per whole-degree anchor, backs off on upstream 429 and serves the
last snapshot on failure. Adds scripts/qa-gnss.mjs.
Built by the Hex project (Crew Leo Agile dev team).
Co-Authored-By: Claude <[email protected]>
From the re-shot Russian screenshots:
- Translated: weather frame/imagery status errors, HUD summary provenance
("НЕДОСТУПНО: СУДА AIS ОНЛАЙН"), Bhote Koshi map labels, cockpit signal
distances (КМ). The HUD summary fields are separated with " · " in every
language, so the style name and view band no longer read as one phrase.
- Numbers follow the locale: cockpit altitude ("26 175"), precipitation.
- No breaks inside codes and dates: ADS-B and similar in layer rows,
Sentinel-2, cyclone issue dates (word joiner; the UI font has no U+2011),
no-break space after label colons in cyclone details.
- Layout: count lines under ALPR, short unavailable chip (context
"status chip"), roomier draw segments, tighter CCTV calibration chips,
two-line beat titles and media card titles, wrapping weather status.
- Wording: ru advisory is «сводка» (was «бюллетень»; "Advisory center" is
«Центр циклона по сводке»), Street View is «Просмотр улиц», OPACITY
«НЕПРОЗР.», synced «синхр.», Area «Зона»; the import dialog no longer says
"this scene" for several scenes. Glossary updated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- One provider is lit at a time: lighting a chip darkens the others, and a
chip click records every registered provider's switch in one update so
the share link keeps the swap (also off Google 3D, where Street View is
not listed). The layer starts with the first default-on provider
(Mapillary, free); params that light several keep the one already on,
so reapplying them never flips (litByParams). The now-unreachable
"several lit" chip paths are gone.
- Leaving Google 3D during a Street View lookup ends it without an error
or a stuck loading state (openImage's early return resets it; the
lookup loop rechecks availability).
- A source swap during a lookup no longer reports "no imagery".
- Street View lit off Google 3D says the panel needs the Google 3D map.
- Comments trimmed to the why; keySetupCore.test.mjs back to upstream's
formatting plus the one new test (was a 551-line reformat).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
fetchPinnedRadioResponse built `new Response(stream, { status })` inside
the raw https response callback, which runs outside the surrounding
promise. A Radio Browser mirror answering 204, 205 or 304 (null-body
statuses), or any status outside 200-599, made the Response constructor
throw. Nothing caught it, so the whole dev/preview server process exited
and every layer went down, not only radio. The catalog's mirror failover
never saw the error.
Mirror the guard local-receivers.js already uses: null-body statuses get
a null body, statuses outside 200-599 are rejected, and anything thrown
in the callback rejects the promise so the existing error handling and
mirror failover run.
Register the keyless webcams.nyctmc.org catalog as a live CCTV source,
synthesize frame URLs from validated camera UUIDs, and keep the nearest
cameras to the five borough cores under the existing catalog ceiling.
A review of the Russian UI screenshots found ~90 places where longer words
broke the layout or text was still English. Fixed so they hold for any
language, then shortened where a control is short by design.
- Layout: label columns that size to their text (weather and wind settings,
Recent Imagery slots and opacity, style sliders, display panel), layer
list names and icons, buttons that wrap instead of overflowing (CCTV,
scenes, Bhote Koshi), voice header that always shows the cost, styled
EN | ES | RU chips, voice error tray above the dock.
- t() context: t('CLEAR', { context: 'action' }) is «СБРОСИТЬ» while the sky
stays «ЯСНО»; also used for radio categories, voice states and directions.
- Units and dates follow the locale through src/ui/unitLabels.js
(formatQuantity: localized number, translated unit, non-breaking space):
wind, pressure, cyclones, directions, CCTV, SDR gain, scene timing, flight
tooltip, Recent Imagery dates and resolution.
- Newly translated: directions turn-by-turn steps (built from the maneuver;
English matches the server word for word), HUD mode and summary, voice
card states, radio categories on the globe, CCTV status, layer source
lines, scene dialogs (Cancel, file pickers), Bhote Koshi statuses, titles,
comparison dates and caveat, Recent Imagery notices. Stored data and text
for AI tools stay English.
- es and ru: 1,885 messages each.
Co-Authored-By: Claude Opus 5.5 <[email protected]>