When WFIGS was unreachable the proxy served its last snapshot, marked stale
(X-Data-Stale, body.stale), with no age limit, and the client source kept
only the rows. The layer then read healthy with a fresh update time and
get_fire_perimeters reported the perimeters as current; three days into an
outage it still said "1 mapped wildfire perimeter in the requested box."
The source now offers getSnapshotDetail ({rows, stale, fetchedAt}); the
layer reports stale with the snapshot's fetch time, the tool adds stale,
retrieved_at and "(data may be stale)", and the proxy serves a stale
snapshot for at most 24 hours, as the GNSS proxy caps its own.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
get_active_fires joined FIRMS acq_date and the unpadded acq_time, so a
detection at 00:45 UTC came back as "2026-01-01 45". The row now uses
acquisitionMsUtc, which pads the time, and reports an ISO UTC timestamp
like the other tool times.
Report stale traffic tiles and their fetch time, and release pending freshness metadata when tile requests fail or are cancelled.
Contribution by @quanticsoul4772.
The standalone military source no longer has getTrack, so the MCP
get_aircraft_track tool threw a TypeError and MCP advised a retry. It
now returns an `unsupported` ToolError when the selected feed has no
getTrack, while custom sources that supply history still work. Tests
cover the default composition through the catalog and MCP.
Also drop the unused epoch import and update the CURRENT-STATE track
sections to describe local trails and 503 history_unavailable.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Knip, configured with the application, package exports, scripts, QA
harnesses, workers, tools and tests as entry points, reports exports
that no module imports. Each was checked by hand against string-based
and namespace imports.
- Symbols that are still used inside their own module lose only the
`export` keyword (176 constants and helpers).
- Unused re-exports are removed from barrel modules (flight, military,
vessel, CCTV, directions, transit history, local ADS-B and street-level
policy modules).
- Declarations that nothing uses at all are deleted, among them
AIS_WATCHDOG_STATUSES, applyFilter, isRadioCountryCode, compactText,
DENSITY_STOPS, the unused CCTV endpoint constants, and the SDR band
constants.
- Default exports that nothing imports are removed. The ones in
src/data/earthquakes.js, src/data/telegeographySubmarineCables.js,
src/data/wind.js and src/data/bhoteKoshiLocator.js each constructed a
layer instance at import time that nothing used; those instances are
no longer created.
Every export of a package.json entry stays, as does TOMTOM_CREDIT,
which the traffic layer reads from the credits namespace it is given.
When the model upstream fails, the wind proxy keeps serving its last
run, however old, with manifest.stale set. The wind layer shows that,
but get_wind ignored it and gave the old run's wind as current.
get_wind now returns stale and adds "(forecast may be stale)" to its
summary, the way get_weather and the space tools flag stale data.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
aircraft_in_area stats summarized altitude over airborne aircraft but
speed over every match, so parked aircraft pulled the mean speed toward
zero. Both figures now use the same airborne subset, and the on-ground
fixture is stationary so the test can tell the difference.
Reserved AIS ship-type codes 100 to 255 now read as unknown, as type 0
does, instead of showing up in the breakdown as their own type.
Adds tests for an area where every aircraft is on the ground and for
find_vessel type and destination normalization, and notes in the
changelog fragment that every vessel answer is now normalized.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Reject non-numeric and out-of-range feed coordinates before they reach the
map or bike-share queries. Preserve numeric strings, aliases and zero.
Cover malformed records at the parser and public query boundaries.
Signed-off-by: Bortlesboat <[email protected]>
AIS sends ship types as ITU-R M.1371 codes, so vessels_in_area grouped
"70", "74" and "79" as three types, and type: "tanker" matched nothing on
feeds that send codes. Codes now read as the family the vessel cards already
show (CARGO, TANKER, TUG), for rows, group_by and the type filter. The code
table moves to src/data/vesselTypes.js, which has no i18n import, so the
server-side tools can use it; vesselLabels re-exports it.
A destination with no letters ("0", "---") now counts as unknown.
Live, Singapore: "most by type: CARGO (3), TANKER (3), PASSENGER (1)"; "how
many tankers near Singapore" found 3.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ICAO flight-plan Item 7 caps aircraft identification at seven characters, so
the flight number after the designator is one to four characters, not five.
Philippine RP-C1234 and Cuban CU-T1240 registrations read like a designator
plus a flight number once the hyphen is dropped; they now count as unknown.
Cubana's CUB123 still counts as CUB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
OpenSky, the default civil feed, reports no operator, so group_by: operator
said "no operator reported". aircraft_in_area now also takes
group_by: 'airline', read from the ICAO designator in each callsign: three
letters followed by a flight number that starts with a digit (SWA1234,
UAL12A). Registrations used as callsigns (N123AB) count as unknown. Rows are
unchanged; the designator is derived for the breakdown only.
Live, Texas: "785 aircraft ...; most by airline: AAL (89), SWA (65), UAL (52);
283 unknown."
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Route tleCatalogNumber() and noradIdFromSatnum() through one decoder that
accepts only plain digits or a valid Alpha-5 field. parseInt and Number()
read '2554A', '255 4', '-1234', '0x1F0', '1.5' and blank as numbers; they
now return null/NaN. Digit satnums of any length still decode, so OMM
satrecs numbered past 99999 keep working.
Add a layer test that several Alpha-5 satellites survive ingestion dedupe,
the changelog fragment for #767, and note in CURRENT-STATE that this is
Alpha-5 TLE compatibility and malformed fields are rejected.
The CCTV frame proxy marks a Street View fallback with X-CCTV-Source, but the portable source dropped the header and get_cctv_snapshot always reported a current view credited to the camera's agency. The source now keeps the recognised provenance, and the tool labels Street View frames as historical Google imagery, refuses synthetic placeholders, and reports unknown provenance without claiming a current view.
aircraft_in_area compared a nullable altitude, so a contact with neither barometric nor geometric altitude matched max_altitude_m as though it were at zero. Require a reported finite altitude only when a band is requested; unfiltered queries keep the contact.
AIS type 0 means "not available" (ITU-R M.1371), but the source keeps it
as '0', so group_by: 'type' listed "0" as a type and understated
unknown_count. vessels_in_area rows now report it as null, and a
regression fixture mixes type 0 with Cargo and Tanker.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rebase #1142 onto current main, keeping both sides of the changelog and runtime documentation conflicts. Retain unwrapped containment and circular query coordinates.
Address review feedback on perimeterAnchorDegrees: use circular longitude for the selected outer ring, excluding its closing duplicate. Compare multipart areas along short longitude edges so a small dateline part cannot steal the card anchor from a larger polygon.
Three new card regressions fail before the fix and pass afterward. Coverage includes reversed/rotated rings, vertices at +/-180 and both multipart selection directions. Local validation: 43 focused tests; full suite 7,636 passed, one skipped, zero failures; production build, formatting, module boundaries and whitespace checks pass on Node 24.19.0. Browser tracking validation follows on the QA branch.
Catalog numbers above 99999 arrive in a TLE as Alpha-5: a letter A-Z,
skipping I and O, stands for 10-33 in the ten-thousands place, so 100000
is A0000. satellite.js keeps the field as the string satrec.satnum, and
the three catalog builders converted it with Number(), which gives NaN;
the Set and Map used to deduplicate and store treat NaN as equal to NaN,
so the first such satellite was stored under NaN and every later one was
dropped as a duplicate (#751). The satellite tools read the same field
with parseInt and reported no number, so such a satellite could not be
found by it.
The decoder lives in src/sources/tle.js, beside parseTleText, which the
layer and the tools both import already: noradIdFromSatnum() for the
three builders, and tleCatalogNumber() decodes through the same function.
Every other field reads as it did before: Number() in the layer,
parseInt or null in the tools.
CHANGELOG: keep both new [Unreleased] entries (Panoramax, and the
emergency-transponder highlight from #856).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
isoTime returned toISOString() for any finite number, which throws past ±8.64e15 ms, so one bad record (e.g. a bike-share last_reported) failed the whole tool answer. Such timestamps are now null. The voice analyst's result rows had the same conversion and get the same guard.
Co-authored-by: Sameh Khamis <[email protected]>
Keep this branch's CHANGELOG entry above upstream's; upstream main no longer lists the GBFS station-name entry an earlier merge carried here.
Co-Authored-By: Claude Opus 5.5 <[email protected]>