Merge remote-tracking branch 'origin/main' into feat/viewport-context-decision-upstream

This commit is contained in:
Sameh Khamis committed 2026-11-10 15:44:17 -08:00
commit 100369f9af
189 files changed
+29069 -743

No files matched your search

+9
View File
@@ -1231,6 +1231,7 @@
"src/data/firmsIntensity.js",
"src/data/firmsIntensity.test.mjs",
"src/ui/layerPanel/RowSwitch.vue",
"src/ui/layerPanel/RowRange.vue",
"src/ui/layerPanel/RowSlider.vue",
"src/sources/phaseTiming.test.mjs",
"scripts/build-census-places.mjs",
@@ -1285,6 +1286,13 @@
"src/voice/panelLayout.js",
"src/voice/textChat.js",
"src/voice/textProtocol.js",
"src/layers/shipwrecks/records.test.mjs",
"src/layers/shipwrecks/cards.test.mjs",
"src/layers/shipwrecks/eras.test.mjs",
"src/layers/shipwrecks/period.test.mjs",
"src/layers/shipwrecks/index.test.mjs",
"src/layers/shipwrecks/source.test.mjs",
"src/data/shipwrecksProxy.test.mjs",
"src/app/sourceComposition.js",
"src/app/sourceComposition.test.mjs",
"src/data/dataCredits.js",
@@ -1295,6 +1303,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
+154 -1
View File
@@ -50,6 +50,7 @@
"src/ui/views/panels/CockpitSignalList.vue",
"src/ui/views/panels/CockpitUtilityControls.vue",
"src/ui/cockpitBriefPages.js",
"src/data/regionalNewsWindow.js",
"src/ui/cockpitContextState.js",
"src/ui/cockpitInstrumentState.js",
"src/ui/cockpitRollingValue.js",
@@ -73,6 +74,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -507,6 +509,7 @@
"server/providers/regional/weather-effects.js",
"server/providers/regional/weather.js",
"src/data/regionalModel.js",
"src/data/regionalNewsWindow.js",
"src/geoDistance.js",
"src/nominatimGeocode.js",
"src/sources/httpBody.js",
@@ -529,6 +532,32 @@
],
"external": []
},
"news-provider": {
"runtime": "node",
"exports": ["./server/providers/news"],
"modules": [
"server/providers/news.js",
"server/providers/news/keyed.js",
"server/providers/news/categorize.js",
"server/providers/news/areas.js",
"server/providers/news/gdeltEvents.js",
"server/providers/news/upstream.js",
"server/providers/common/http.js",
"server/providers/common/same-site.js",
"src/data/adminBoundaries.js",
"src/data/bundledJson.js",
"src/data/retryableLoad.js",
"src/data/local_data/natural_earth/countries.json",
"src/data/local_data/natural_earth/disputed_areas.json",
"src/layers/news/source.js",
"src/layers/news/records.js",
"src/localRequestGate.mjs",
"src/sources/httpBody.js",
"src/sources/capability.js",
"src/i18n/marker.js"
],
"external": []
},
"openai-provider": {
"runtime": "node",
"exports": ["./server/providers/openai"],
@@ -607,6 +636,7 @@
"src/search/nominatim.js",
"src/nominatimGeocode.js",
"src/data/regionalModel.js",
"src/data/regionalNewsWindow.js",
"src/geoDistance.js",
"src/data/naturalEarthRegions.js",
"src/data/bundledJson.js",
@@ -656,6 +686,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -704,6 +735,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -730,6 +762,7 @@
"src/ui/rail/focusSafeChildren.js",
"src/ui/layerPanel/RowText.vue",
"src/ui/layerPanel/RowSwitch.vue",
"src/ui/layerPanel/RowRange.vue",
"src/ui/layerPanel/RowSlider.vue",
"src/ui/railTimeline.js",
"src/ui/clearLayersControl.js",
@@ -745,6 +778,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -765,6 +799,11 @@
"src/ui/rail/RailTimeline.vue",
"src/ui/rail/rangeValue.js",
"src/ui/weather/WeatherReadout.vue",
"src/ui/newsPanel.js",
"src/ui/news/NewsFeed.vue",
"src/ui/news/NewsCard.vue",
"src/ui/kit/UiEmpty.vue",
"src/ui/kit/UiSelect.js",
"src/ui/kit/UiButton.vue",
"src/ui/kit/UiBadge.vue",
"src/ui/rail/fieldValue.js",
@@ -788,6 +827,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -815,6 +855,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -845,6 +886,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -882,6 +924,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -906,6 +949,7 @@
"src/ui/cockpit.js",
"src/ui/cockpitBriefing.js",
"src/ui/cockpitBriefPages.js",
"src/data/regionalNewsWindow.js",
"src/ui/cockpitContextState.js",
"src/ui/cockpitInstrumentState.js",
"src/ui/cockpitRollingValue.js",
@@ -951,6 +995,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -978,6 +1023,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1065,6 +1111,7 @@
"src/ui/cockpit.js",
"src/ui/cockpitBriefing.js",
"src/ui/cockpitBriefPages.js",
"src/data/regionalNewsWindow.js",
"src/ui/cockpitContextState.js",
"src/ui/cockpitInstrumentState.js",
"src/ui/cockpitRollingValue.js",
@@ -1116,6 +1163,7 @@
"src/ui/rail/focusSafeChildren.js",
"src/ui/layerPanel/RowText.vue",
"src/ui/layerPanel/RowSwitch.vue",
"src/ui/layerPanel/RowRange.vue",
"src/ui/layerPanel/RowSlider.vue",
"src/ui/railTimeline.js",
"src/ui/layers.js",
@@ -1180,6 +1228,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1213,6 +1262,11 @@
"src/ui/rail/RailTimeline.vue",
"src/ui/rail/rangeValue.js",
"src/ui/weather/WeatherReadout.vue",
"src/ui/newsPanel.js",
"src/ui/news/NewsFeed.vue",
"src/ui/news/NewsCard.vue",
"src/ui/kit/UiEmpty.vue",
"src/ui/kit/UiSelect.js",
"src/ui/stores/streetLevel.js",
"src/ui/stores/cctv.js",
"src/ui/stores/radio.js",
@@ -1283,6 +1337,7 @@
"src/ui/styles/first-run.css",
"src/ui/styles/provider-settings.css",
"src/ui/styles/weather.css",
"src/ui/styles/news.css",
"src/ui/styles/recent-imagery.css"
],
"external": []
@@ -1372,6 +1427,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1447,6 +1503,7 @@
"src/layers/flights/state.js",
"src/layers/flights/testing.js",
"src/layers/flights/tracking.js",
"src/data/trailContinuity.js",
"src/layers/flights/records.js",
"src/layers/flights/recordPolicy.js",
"src/layers/flights/snapshotRenderer.js",
@@ -1459,6 +1516,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1510,6 +1568,7 @@
"src/layers/military/state.js",
"src/layers/military/testing.js",
"src/layers/military/tracking.js",
"src/data/trailContinuity.js",
"src/layers/military/records.js",
"src/layers/military/recordPolicy.js",
"src/layers/military/snapshotRenderer.js",
@@ -1521,6 +1580,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1528,6 +1588,7 @@
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/ui/unitLabels.js",
"src/time/worldClock.js",
"src/time/julian.js"
],
@@ -1598,6 +1659,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1635,6 +1697,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1676,6 +1739,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1716,6 +1780,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1764,6 +1829,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1816,6 +1882,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1863,6 +1930,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1926,6 +1994,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -1963,6 +2032,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2020,6 +2090,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2091,6 +2162,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2115,6 +2187,7 @@
"src/layers/alpr/overlay.js",
"src/layers/alpr/source.js",
"src/data/inputOwnership.js",
"src/data/displayText.js",
"src/layers/alpr/records.js",
"src/data/tomtomTiles.js",
"src/data/tileBudget.js",
@@ -2131,6 +2204,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2181,6 +2255,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2211,6 +2286,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2275,6 +2351,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2353,6 +2430,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -2601,6 +2679,7 @@
"src/data/trafficPresetStyle.js",
"src/data/trafficQueue.js",
"src/data/trailRenderer.js",
"src/data/trailContinuity.js",
"src/data/vesselLabels.js",
"src/data/vesselTypes.js",
"src/firstRunExperience.js",
@@ -2920,6 +2999,7 @@
"src/ui/cockpit.js",
"src/ui/cockpitBriefing.js",
"src/ui/cockpitBriefPages.js",
"src/data/regionalNewsWindow.js",
"src/ui/cockpitContextState.js",
"src/ui/cockpitInstrumentState.js",
"src/ui/cockpitRollingValue.js",
@@ -2966,6 +3046,7 @@
"src/ui/rail/focusSafeChildren.js",
"src/ui/layerPanel/RowText.vue",
"src/ui/layerPanel/RowSwitch.vue",
"src/ui/layerPanel/RowRange.vue",
"src/ui/layerPanel/RowSlider.vue",
"src/ui/railTimeline.js",
"src/ui/layers.js",
@@ -3023,6 +3104,7 @@
"src/voice/chatPreferences.js",
"src/voice/panelLayout.js",
"src/voice/voiceMiniPreview.js",
"src/data/displayText.js",
"src/voice/gevActions.js",
"src/agent/worldPacket.js",
"src/voice/speech.js",
@@ -3094,11 +3176,25 @@
"src/layers/ghostMaps/records.js",
"src/layers/ghostMaps/kml.js",
"src/layers/ghostMaps/source.js",
"src/app/layers/shipwrecks.js",
"src/layers/shipwrecks/index.js",
"src/layers/shipwrecks/source.js",
"src/layers/shipwrecks/records.js",
"src/layers/shipwrecks/cards.js",
"src/layers/shipwrecks/eras.js",
"src/layers/shipwrecks/period.js",
"src/app/layers/droneAirspace.js",
"src/layers/droneAirspace/index.js",
"src/layers/droneAirspace/source.js",
"src/layers/droneAirspace/records.js",
"src/layers/droneAirspace/cards.js",
"src/app/layers/news.js",
"src/layers/news/index.js",
"src/layers/news/model.js",
"src/layers/news/records.js",
"src/layers/news/source.js",
"src/layers/news/sprites.js",
"src/layers/news/cards.js",
"src/layers/cctv/sourcePolicy.js",
"src/sources/reference.js",
"src/search/nominatim.js",
@@ -3302,6 +3398,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -3351,6 +3448,10 @@
"src/ui/rail/RailTimeline.vue",
"src/ui/rail/rangeValue.js",
"src/ui/weather/WeatherReadout.vue",
"src/ui/newsPanel.js",
"src/ui/news/NewsFeed.vue",
"src/ui/news/NewsCard.vue",
"src/ui/kit/UiSelect.js",
"src/ui/recentImagery/RecentImageryCard.vue",
"src/ui/recentImagery/RecentImageryReadout.vue",
"src/ui/stores/streetLevel.js",
@@ -3598,6 +3699,7 @@
"src/data/trafficPresetStyle.js",
"src/data/trafficQueue.js",
"src/data/trailRenderer.js",
"src/data/trailContinuity.js",
"src/data/vesselLabels.js",
"src/data/vesselTypes.js",
"src/keylessGeocoder.js",
@@ -3871,6 +3973,7 @@
"src/search/coordinateParser.js",
"src/search/presetGeocoder.js",
"src/app/localGeojsonServices.js",
"src/data/displayText.js",
"src/layers/alpr/records.js",
"src/layers/earthquakes/records.js",
"src/app/layers/perimeters.js",
@@ -3889,15 +3992,30 @@
"src/layers/ghostMaps/records.js",
"src/layers/ghostMaps/kml.js",
"src/layers/ghostMaps/source.js",
"src/app/layers/shipwrecks.js",
"src/layers/shipwrecks/index.js",
"src/layers/shipwrecks/source.js",
"src/layers/shipwrecks/records.js",
"src/layers/shipwrecks/cards.js",
"src/layers/shipwrecks/eras.js",
"src/layers/shipwrecks/period.js",
"src/app/layers/droneAirspace.js",
"src/layers/droneAirspace/index.js",
"src/layers/droneAirspace/source.js",
"src/layers/droneAirspace/records.js",
"src/layers/droneAirspace/cards.js",
"src/app/layers/news.js",
"src/layers/news/index.js",
"src/layers/news/model.js",
"src/layers/news/records.js",
"src/layers/news/source.js",
"src/layers/news/sprites.js",
"src/layers/news/cards.js",
"src/layers/cctv/sourcePolicy.js",
"src/search/nominatim.js",
"src/nominatimGeocode.js",
"src/data/regionalModel.js",
"src/data/regionalNewsWindow.js",
"src/sources/httpBody.js",
"src/i18n/marker.js",
"src/sources/nominatim.js",
@@ -4039,6 +4157,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4171,6 +4290,7 @@
"src/search/nominatim.js",
"src/nominatimGeocode.js",
"src/data/regionalModel.js",
"src/data/regionalNewsWindow.js",
"src/sources/httpBody.js",
"src/i18n/marker.js",
"src/sources/nominatim.js",
@@ -4223,6 +4343,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4256,6 +4377,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4310,6 +4432,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4348,6 +4471,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4454,6 +4578,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4477,7 +4602,11 @@
},
"regional-source": {
"exports": ["./sources/regional"],
"modules": ["src/data/regionalModel.js", "src/geoDistance.js"],
"modules": [
"src/data/regionalModel.js",
"src/data/regionalNewsWindow.js",
"src/geoDistance.js"
],
"external": []
},
"voice-action-schemas": {
@@ -4527,6 +4656,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4547,6 +4677,7 @@
"exports": ["./layers/alpr/source"],
"modules": [
"src/layers/alpr/policy.js",
"src/data/displayText.js",
"src/layers/alpr/records.js",
"src/layers/alpr/source.js",
"src/data/tomtomTiles.js",
@@ -4685,6 +4816,7 @@
"src/search/google.js",
"src/search/geospatial.js",
"src/data/regionalModel.js",
"src/data/regionalNewsWindow.js",
"src/geoDistance.js",
"src/sources/httpBody.js",
"src/i18n/marker.js",
@@ -4709,6 +4841,7 @@
"src/nominatimGeocode.js",
"src/search/geospatial.js",
"src/data/regionalModel.js",
"src/data/regionalNewsWindow.js",
"src/geoDistance.js",
"src/sources/httpBody.js",
"src/i18n/marker.js"
@@ -4818,6 +4951,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4873,6 +5007,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4936,6 +5071,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -4960,6 +5096,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -5030,6 +5167,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -5054,6 +5192,17 @@
],
"external": []
},
"news-source": {
"exports": ["./layers/news/source"],
"modules": [
"src/layers/news/source.js",
"src/layers/news/records.js",
"src/sources/httpBody.js",
"src/sources/capability.js",
"src/i18n/marker.js"
],
"external": []
},
"perimeters-source": {
"exports": ["./layers/perimeters/source"],
"modules": [
@@ -5079,6 +5228,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -5246,6 +5396,7 @@
"src/layers/submarineCables/bundledSource.js",
"src/layers/alpr/source.js",
"src/layers/alpr/policy.js",
"src/data/displayText.js",
"src/layers/alpr/records.js",
"src/layers/alpr/tileRecords.js",
"src/sources/infrastructureData.js",
@@ -5285,6 +5436,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
@@ -5331,6 +5483,7 @@
"src/i18n/locales/de.json",
"src/i18n/locales/es.json",
"src/i18n/locales/fr.json",
"src/i18n/locales/he.json",
"src/i18n/locales/it.json",
"src/i18n/locales/ja.json",
"src/i18n/locales/pt.json",
+241
View File
@@ -0,0 +1,241 @@
#!/usr/bin/env node
/**
* Trail freeze gate: freezing a trail chunk never shows a blank frame.
*
* A tracked aircraft's trail is cut into chunks; every chunk but the newest
* is frozen into static geometry, which Cesium builds asynchronously. Until
* that geometry is in the scene, the chunk keeps being drawn dynamically.
*
* This gate drives the real app and a real Cesium scene: it draws a trail of
* CHUNK_POINTS points (all dynamic), then adds one point so the first chunk
* freezes, and on every rendered frame after that it checks two things:
* 1. every frozen chunk is either still drawn by a dynamic polyline or its
* static geometry is in the scene (DataSourceDisplay reports DONE);
* 2. the number of trail-coloured pixels never drops below 90% of the
* count measured just before the freeze.
* It also checks that clearing the trail releases every chunk entity.
*
* The camera is held still while the gate runs.
*
* Needs a running dev server:
* node scripts/qa-trail-freeze.mjs [--url http://localhost:4173]
*/
import puppeteer from 'puppeteer';
import { chromeExecutablePath } from './qa-chrome.mjs';
const urlFlag = process.argv.indexOf('--url');
const APP_URL =
urlFlag > 0 ? process.argv[urlFlag + 1] : 'http://localhost:4173';
const browser = await puppeteer.launch({
headless: 'new',
protocolTimeout: 300_000,
executablePath: chromeExecutablePath(),
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--use-gl=angle',
'--use-angle=swiftshader',
'--disable-dev-shm-usage',
'--disable-background-timer-throttling',
'--disable-renderer-backgrounding',
'--window-size=960,640',
],
});
let failed = false;
const report = (name, ok, detail) => {
if (!ok) failed = true;
console.log(
` [${ok ? 'PASS' : 'FAIL'}] ${name}${detail ? ` — ${detail}` : ''}`,
);
};
try {
const page = await browser.newPage();
await page.setViewport({ width: 960, height: 640 });
await page.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 120_000 });
await page.waitForFunction(
() =>
window.__godsEyeView?.viewer &&
document.getElementById('loading-screen')?.classList.contains('hidden'),
{ timeout: 180_000 },
);
const result = await page.evaluate(async () => {
// The exact Cesium module the renderer uses, so classes are shared.
const source = await (await fetch('/src/data/trailRenderer.js')).text();
const cesiumUrl = source.match(/from\s+["']([^"']*cesium[^"']*)["']/)[1];
const Cesium = await import(cesiumUrl);
const { createTrail, CHUNK_POINTS } =
await import('/src/data/trailRenderer.js');
const viewer = window.__godsEyeView.viewer;
const scene = viewer.scene;
const nextFrame = () =>
new Promise((resolve) => {
const remove = scene.postRender.addEventListener(() => {
remove();
resolve();
});
scene.requestRender();
});
// A line across the middle of the current view, in mid-air in front of
// the camera, so terrain and buildings never hide it.
viewer.camera.cancelFlight?.();
const camera = viewer.camera;
const ahead = Cesium.Cartesian3.add(
camera.positionWC,
Cesium.Cartesian3.multiplyByScalar(
camera.directionWC,
3_000,
new Cesium.Cartesian3(),
),
new Cesium.Cartesian3(),
);
const points = Array.from({ length: CHUNK_POINTS + 1 }, (_, i) =>
Cesium.Cartesian3.add(
ahead,
Cesium.Cartesian3.multiplyByScalar(
camera.rightWC,
-1_200 + (2_400 * i) / CHUNK_POINTS,
new Cesium.Cartesian3(),
),
new Cesium.Cartesian3(),
),
);
const added = [];
const removed = [];
const add = viewer.entities.add.bind(viewer.entities);
const remove = viewer.entities.remove.bind(viewer.entities);
viewer.entities.add = (definition) => {
const entity = add(definition);
if (String(entity.id).startsWith('gev-trail:')) added.push(entity);
return entity;
};
viewer.entities.remove = (entity) => {
if (String(entity?.id).startsWith('gev-trail:')) removed.push(entity);
return remove(entity);
};
const gl = scene.context._gl;
const width = gl.drawingBufferWidth;
const height = gl.drawingBufferHeight;
const pixels = new Uint8Array(width * height * 4);
// Magenta trail: count pixels that are clearly magenta.
const countTrailPixels = () => {
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
let count = 0;
for (let p = 0; p < pixels.length; p += 4) {
const r = pixels[p];
const g = pixels[p + 1];
const b = pixels[p + 2];
if (r > 150 && b > 150 && g < 110) count++;
}
return count;
};
const trail = createTrail(viewer, { color: '#ff00ff', width: 8 });
trail.setPositions(points.slice(0, CHUNK_POINTS));
for (let i = 0; i < 20; i++) await nextFrame();
const before = trail._chunksForTest();
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
let lit = 0;
for (let p = 0; p < pixels.length; p += 4)
if (pixels[p] || pixels[p + 1] || pixels[p + 2]) lit++;
const cam = viewer.camera.positionCartographic;
const diagnostics = {
lit,
size: [width, height],
camera: [
Cesium.Math.toDegrees(cam.longitude),
Cesium.Math.toDegrees(cam.latitude),
cam.height,
],
preserveDrawingBuffer: gl.getContextAttributes().preserveDrawingBuffer,
};
let baseline = 0;
for (let i = 0; i < 5; i++) {
await nextFrame();
baseline = Math.max(baseline, countTrailPixels());
}
trail.setPositions(points);
const frames = [];
const sphere = new Cesium.BoundingSphere();
let resolvedAt = null;
for (let i = 0; i < 400; i++) {
await nextFrame();
const state = trail._chunksForTest();
const covered = state.frozen.every(
(chunk) =>
chunk.standIn ||
viewer.dataSourceDisplay.getBoundingSphere(
chunk.entity,
false,
sphere,
) === Cesium.BoundingSphereState.DONE,
);
frames.push({ covered, pixels: countTrailPixels() });
if (resolvedAt == null && state.frozen.every((c) => !c.standIn))
resolvedAt = i;
if (resolvedAt != null && i > resolvedAt + 30) break;
}
trail.clear();
for (let i = 0; i < 3; i++) await nextFrame();
const frozenAfterClear = trail._chunksForTest().frozen.length;
trail.destroy();
const leaked = added.filter((entity) => !removed.includes(entity)).length;
viewer.entities.add = add;
viewer.entities.remove = remove;
return {
chunkPoints: CHUNK_POINTS,
diagnostics,
frozenBefore: before.frozen.length,
baseline,
frames,
resolvedAt,
frozenAfterClear,
entities: added.length,
leaked,
};
});
if (process.env.GEV_TEST_VERBOSE)
console.log(JSON.stringify(result.diagnostics));
const minPixels = Math.min(...result.frames.map((frame) => frame.pixels));
report(
'the trail draws before the freeze',
result.baseline > 200 && result.frozenBefore === 0,
`baseline=${result.baseline} trail pixels, frozen chunks=${result.frozenBefore}`,
);
report(
'the frozen chunk reaches the scene as static geometry',
result.resolvedAt != null,
`stand-in dropped after ${result.resolvedAt} frames`,
);
report(
'every frame after the freeze draws the frozen chunk (dynamic copy or static geometry)',
result.frames.every((frame) => frame.covered),
`${result.frames.length} frames checked`,
);
report(
'no blank frame: trail pixels never drop below 90% of the pre-freeze count',
minPixels >= 0.9 * result.baseline,
`min=${minPixels} baseline=${result.baseline}`,
);
report(
'clearing and destroying release every trail entity',
result.frozenAfterClear === 0 && result.leaked === 0,
`entities created=${result.entities} leaked=${result.leaked}`,
);
} catch (error) {
report('gate ran', false, error.message);
} finally {
await browser.close();
}
console.log(failed ? 'RESULT: FAIL' : 'RESULT: PASS');
process.exit(failed ? 1 : 0);
+1 -1
View File
@@ -801,7 +801,7 @@ try {
);
check(
`${preset.name}: the icon raster cache stays bounded`,
styling.iconCacheSize <= 6 * 2 * 3,
styling.iconCacheSize <= 6 * 2 * 3 * 2,
`${styling.iconCacheSize} rasters`,
);
}
+153 -21
View File
@@ -42,8 +42,9 @@
* and a real mouse click on the tracked plane is a
* NO-OP (no deselect, no camera spike).
* M3. AGE-OUT RELEASE — when the tracked plane's fixes stop arriving
* (3 missed polls via the shim), tracking clears and
* the camera is RELEASED IN PLACE: viewer.trackedEntity
* it stays tracked (STALE, then SIGNAL LOST) until
* 10 min after its newest fix; then tracking clears
* and the camera is RELEASED IN PLACE: viewer.trackedEntity
* undefined, NO jump (owner decision 2026-07-02 —
* the old ~80 km overview flyTo is gone).
*
@@ -362,7 +363,12 @@ async function main() {
'Synthetica', // 2 origin_country
Math.floor(Date.now() / 1000), // 3 time_position
Math.floor(Date.now() / 1000), // 4 last_contact
f.lon, // 5 longitude
// A plane given `moveFromMs` flies east at its reported speed
// (the trail-head check needs a moving icon); others hold still.
Number.isFinite(f.moveFromMs)
? f.lon + (f.vel * (Date.now() - f.moveFromMs)) / 1000
/ (111320 * Math.cos((f.lat * Math.PI) / 180))
: f.lon, // 5 longitude
f.lat, // 6 latitude
f.alt, // 7 baro_altitude (m)
f.onGround === true, // 8 on_ground (ground-traffic phase flips this live)
@@ -1498,6 +1504,110 @@ async function main() {
`max=${maxDelta.toFixed(3)} m over ${samples.length} frames (tol ${JITTER_TOL_M} m, mode=${modelUp ? 'model-matrix' : 'display-contract'})`);
}
// ============================================================
// TRAIL HEAD ENDS AT THE DRAWN ICON, every rendered frame.
// The head's last vertex is where the tracked plane is drawn (the
// display position, or the model's tail anchor while the model is the
// visual). getTrackedInfo reports the dead-reckoned position without
// the brief post-report easing; its gap is logged, not asserted.
// ============================================================
console.log('\nTrail head — ends at the drawn icon on every frame');
// The fixture planes repeat one position, so the icon holds and draws no
// head. Fly the tracked one east for this check; the display runs 30 s
// behind, so give it time to reach the moving reports.
await evalPage((icao) => {
const f = window.__SYNTH.flights.find((p) => p.icao === icao);
f.moveFromMs = Date.now();
}, trackedIcao);
await sleep(55_000);
const headSamples = await sampleFrames(page, 40, (icao) => {
const viewer = window.__godsEyeView.viewer;
const fl = window.__godsEyeView.dataManager.layers.get('flights').module;
const ent = viewer.trackedEntity;
const time = viewer.clock.currentTime;
const head = viewer.entities.values.find((e) => String(e.id).startsWith('gev-trail:fl-head-'));
const line = head?.polyline?.positions?.getValue(time);
const drawn = ent?.position?.getValue(time);
if (!line?.length || !drawn) return null;
const end = line[line.length - 1];
const dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
const ti = fl.getTrackedInfo();
let infoGap = null;
if (ti && ti.icao24 === icao) {
// WGS84 lat/lon → ECEF on the ellipsoid; the angle at the centre to
// the drawn position, times the mean radius, is the ground gap.
const a = 6378137, e2 = 6.69437999014e-3;
const lat = (ti.latitude * Math.PI) / 180, lon = (ti.longitude * Math.PI) / 180;
const n = a / Math.sqrt(1 - e2 * Math.sin(lat) ** 2);
const p = {
x: n * Math.cos(lat) * Math.cos(lon),
y: n * Math.cos(lat) * Math.sin(lon),
z: n * (1 - e2) * Math.sin(lat),
};
const dot = p.x * drawn.x + p.y * drawn.y + p.z * drawn.z;
const cos = dot / (Math.hypot(p.x, p.y, p.z) * Math.hypot(drawn.x, drawn.y, drawn.z));
infoGap = 6371008.8 * Math.acos(Math.min(1, cos));
}
// While the 3D model is the visual the head ends at the model's tail
// anchor, which sits on the drawn (pixel-size-enlarged) model.
const model = window.__findTrackedModel ? window.__findTrackedModel() : null;
const sphere = model && model.ready && model.show ? model.boundingSphere : null;
// boundingSphere leaves out the minimum-pixel-size enlargement that
// computedScale carries; the drawn model is that much larger.
const enlarge = sphere && Number.isFinite(model.computedScale) && model.scale > 0
? Math.max(1, model.computedScale / model.scale)
: 1;
const radius = sphere ? sphere.radius * enlarge : null;
return {
end: dist(end, drawn),
onModel: sphere ? dist(end, sphere.center) <= radius + 50 : false,
modelRadius: radius,
infoGap,
vertices: line.length,
};
}, trackedIcao);
// The same reading between frames, the way a page script polls it: the
// head holds the last rendered frame while getTrackedInfo dead-reckons
// the current instant. Logged only, to tell the two apart.
const betweenFrames = [];
for (let i = 0; i < 8; i++) {
await sleep(600);
betweenFrames.push(await evalPage((icao) => {
const viewer = window.__godsEyeView.viewer;
const head = viewer.entities.values.find((e) => String(e.id).startsWith('gev-trail:fl-head-'));
const line = head?.polyline?.positions?.getValue(viewer.clock.currentTime);
const drawn = viewer.trackedEntity?.position?.getValue(viewer.clock.currentTime);
if (!line?.length || !drawn) return null;
const end = line[line.length - 1];
return Math.hypot(end.x - drawn.x, end.y - drawn.y, end.z - drawn.z);
}, trackedIcao));
}
console.log(` (between frames: head end vs entity position, m: ${
betweenFrames.map((v) => (v == null ? '-' : v.toFixed(1))).join(' ')})`);
// Freeze the plane where it got to, so later checks see a still contact.
await evalPage((icao) => {
const f = window.__SYNTH.flights.find((p) => p.icao === icao);
f.lon += (f.vel * (Date.now() - f.moveFromMs)) / 1000
/ (111320 * Math.cos((f.lat * Math.PI) / 180));
delete f.moveFromMs;
}, trackedIcao);
const headUsable = headSamples.values.filter((v) => v != null);
if (headUsable.length < 10) {
skip('trail head: ends at the drawn icon on every frame',
`only ${headUsable.length} frames with a drawn head (timedOut=${headSamples.timedOut})`);
} else {
const worst = Math.max(...headUsable.map((v) => v.end));
const off = headUsable.filter((v) => !(v.end <= 50 || v.onModel));
const onModel = headUsable.filter((v) => v.end > 50 && v.onModel).length;
const radii = headUsable.map((v) => v.modelRadius).filter((v) => v != null);
const gaps = headUsable.map((v) => v.infoGap).filter((v) => v != null);
record('trail head: last vertex on the drawn icon (within 50 m, or on the drawn 3D model) on every frame',
off.length === 0,
`${off.length} of ${headUsable.length} frames off; max end-to-centre=${worst.toFixed(1)} m, ${onModel} frames ending on the model's tail (model radius ${
radii.length ? Math.max(...radii).toFixed(0) : 'n/a'} m); getTrackedInfo vs drawn icon max=${
gaps.length ? Math.max(...gaps).toFixed(1) : 'n/a'} m (logged only)`);
}
// ============================================================
// INVARIANT 2: NO PULL-OUT on a track switch
// tracking B while tracking A must not fly to ~80 km overview.
@@ -1744,7 +1854,8 @@ async function main() {
// ============================================================
// REGRESSION M3 (rewritten 2026-07-02 — deliberate behavior change):
// when the TRACKED plane ages out (3 missed polls), tracking must clear
// when the TRACKED plane ages out (10 min after its newest fix, past the
// 3 missed polls that remove an untracked plane), tracking must clear
// and the camera must be RELEASED IN PLACE — viewer.trackedEntity
// undefined and NO jump (the old contract flew an ~80 km overview; the
// owner field-ruled that wrong: "it randomly zooms way up and loses my
@@ -1765,27 +1876,48 @@ async function main() {
const beforeTracking = !!fl.getTrackedInfo();
// Simulate the age-out via the shim: the plane stops arriving.
window.__SYNTH.flights = window.__SYNTH.flights.filter((f) => f.icao !== icao);
// MISSING_POLL_LIMIT = 3 consecutive absent polls → removal + clear.
// Capture the camera pose right BEFORE the removing poll — the follow
// camera drifts with the coasting plane until the instant of release,
// so this is the "where the user was looking" reference.
// The tracked plane outlives MISSING_POLL_LIMIT (3 absent polls): it is
// kept until 10 min after its newest fix, so the camera keeps its target.
await fl.update(v);
await fl.update(v);
const c0 = v.camera.positionCartographic;
const atAgeOut = {
lonDeg: (c0.longitude * 180) / Math.PI,
latDeg: (c0.latitude * 180) / Math.PI,
height: c0.height,
};
await fl.update(v); // third miss → removal + clear + in-place release
return {
beforeTracking,
atAgeOut,
stillTrackedAfter: !!fl.getTrackedInfo(),
viewerStillTracking: !!v.trackedEntity,
};
await fl.update(v);
const trackedThroughMisses = !!fl.getTrackedInfo();
// Jump the page clock past the 10 min retention instead of waiting.
const realNow = Date.now;
Date.now = () => realNow() + 11 * 60 * 1000;
try {
// Let the follow camera settle on the stopped plane, then capture the
// pose right BEFORE the removing poll: the "where the user was
// looking" reference.
await new Promise((res) => {
const stop = v.scene.postRender.addEventListener(() => {
stop();
res();
});
v.scene.requestRender();
});
const c0 = v.camera.positionCartographic;
const atAgeOut = {
lonDeg: (c0.longitude * 180) / Math.PI,
latDeg: (c0.latitude * 180) / Math.PI,
height: c0.height,
};
await fl.update(v); // past retention → removal + clear + in-place release
return {
beforeTracking,
trackedThroughMisses,
atAgeOut,
stillTrackedAfter: !!fl.getTrackedInfo(),
viewerStillTracking: !!v.trackedEntity,
};
} finally {
Date.now = realNow;
}
}, m3Icao);
record('M3: the tracked plane stays tracked through 3 missed polls (kept 10 min after its newest fix)',
m3.trackedThroughMisses,
`trackedThroughMisses=${m3.trackedThroughMisses}`);
record('M3: tracking cleared after the tracked plane ages out (getTrackedInfo null)',
m3.beforeTracking && !m3.stillTrackedAfter,
`before=${m3.beforeTracking} trackedAfter=${m3.stillTrackedAfter}`);