Commit Graph
66 Commits
Author SHA1 Message Date
Sameh Khamis 8ff22f1a12 Merge main into feat/shipwrecks
Main added the News layer at the same places: both layers are kept in the
layer names, panel order (News stays last of Events), standalone sources,
voice manifest, credits, DATA_SOURCES and the CURRENT-STATE table. News took
share-link token 00 first, so Shipwrecks moves to 01 (ledger, registry,
CURRENT-STATE and the token tests). The catalog now has 40 layers and the
share-link registry 38. Shipwreck strings are added to the new Hebrew
catalog; the other catalogs keep both sides' strings.
2026-11-10 14:03:39 -08:00
Denis Groshev 0fdde159cb Merge branch 'main' into denis/gev-news-layer
# Conflicts:
#	DATA_SOURCES.md
#	docs/CURRENT-STATE.md
#	src/data/local_data/natural_earth/README.md
#	src/i18n/same-as-english.json
#	style.css
2026-11-09 15:35:56 +01:00
Sameh Khamis c84cfcadeb fix(satellites): class choices survive a reload and travel in share links
Only catalog and the tracked satellite were saved for the layer, so a class
chip lasted until the next reload and no share link carried it. The choice
is now the satellites option `classes`: one letter per class shown
(`sitpd` by default, `0` for none), with Starlink left to `catalog`, and
the layer takes the letters back through setParams. A partial request
(`{debris: false}`) hides only what it names.
2026-11-08 15:16:01 -08:00
Denis Groshev a3902590cd Merge branch 'main' into denis/gev-news-layer 2026-11-07 23:19:12 +01:00
Itallo BrandãoandClaude Opus 5.5 584138decf Merge main into feat/shipwrecks
Keeps main's reformatted layer table in docs/CURRENT-STATE.md and adds the Shipwrecks row to it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 18:37:28 -03:00
Sameh Khamis 9c8bb206ef feat(traffic): Lines and Dots toggles; hosts choose which road modes they offer
createTrafficSource({ roadModes }) lets a host offer a subset of the TomTom,
OSM and Hybrid road modes (all three by default). Without OSM the layer never
draws simulated roads, and a single mode shows no road chip.

While flow is live the Street Traffic row offers Lines (every road with flow
as a steady green/amber/red line, from the heat-line renderer) and Dots (the
moving dots), saved in layer state and share links. Defaults: Lines off,
Dots on.
2026-11-06 11:37:57 -08:00
Denis Groshev 2e87dd87eb Merge branch 'main' into denis/gev-news-layer
Wildlife Sightings (#1591) published share token 9, so News takes the
next free token, 00. The token tests read the ledger before both where
they were written against it, and the README counts both layers.
2026-11-06 12:33:13 +01:00
Itallo BrandãoandClaude Opus 5.5 096b3c85bf Merge main into feat/shipwrecks; move shipwrecks to token 00
Main's wildlife-sightings layer took share-link token 9, so shipwrecks
takes the next free token, 00. Locale, docs, format-scope and voice
manifest conflicts keep both sides. Ledger and registry tests now model
the ledger before shipwrecks took 00, and layer counts include both
new layers.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 02:52:49 -03:00
Denis Groshev 5bb489d1fb Put news on the globe, with a feed panel in the right rail
Events ▸ News draws the last 24 hours of reports from curated hotspots
(Ukraine–Russia, Israel–Gaza–Lebanon, Iran–Hormuz, Yemen–Red Sea, Sudan,
Sahel) and developing situations elsewhere, from any server that answers the
`/api/news` read routes.

- Globe: one marker per merged event, clamped to the ground and culled at the
  horizon; one beacon per hotspot when zoomed out and a pulsing beacon for a
  developing situation; hotspot outlines with disputed areas dotted and
  labelled by who administers and who claims them; picture cards for at most
  ten major or fast-growing events with two or more sources, never
  overlapping, switchable off.
- Panel: hotspot chips, a severity filter (significant and above by default),
  categories, minor news off by default, and a scrolling list of event cards
  with picture, headline, place, sources, age and the sides that reported it.
  "In view" lists what is near the map below 3,000 km once the camera
  settles. A card flies the globe to its event and opens it with a link to
  the publisher. Reduced motion stops the pulse and the flights.
- Core serves no news itself: the local server proxies GEV_NEWS_ORIGIN when it
  is set, and answers 503 unavailable otherwise, so the layer reads "not
  configured" once instead of retrying.
- Share token 9 (enabled-only); voice stays off for this layer.
2026-11-06 00:45:24 +01:00
Yehe LiuandClaude Opus 5.5 14f88b3e08 feat: Wildlife Sightings layer from iNaturalist and Acartia
Recent community wildlife observations, as a new Wildlife group in the
layer panel.

- Server: keyless iNaturalist proxy (v2 observations with only the
  projected fields; undocumented v1 UTFGrid density tiles, decoded on the
  server) sharing one paced queue (burst 4, ~1/s, 60/min, 8,000/day budget,
  up to four in flight), in-memory caches and upstream-only cooldowns;
  Acartia Salish Sea marine-mammal feed rounded to 0.01 degrees with
  comments dropped.
- Layer: grid counts drawn as a soft view-ranked density glow; real sampled
  sightings as taxon badges at their true positions with ambient photo
  thumbnails and labels; obscured records stay privacy cells; a clicked
  sighting stays pinned through camera moves and refreshes.
- Anchored photo card per selection with the single iNaturalist link;
  attribution in the layer row and the Data attribution popover.
- Filters (CONFIRMED, taxa presets) round-trip in share links with token 9.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 16:17:12 -05:00
Itallo BrandãoandClaude Opus 5.5 dcd0f2cccd feat(layers): add shipwrecks layer (EMODnet + Wikidata)
A new Events layer maps shipwrecks with a card per wreck:
- EMODnet Human Activities heritage shipwrecks (about 6,500 in European
  waters, CC BY 4.0), skipping rows taken from wrecksite.eu.
- Wikidata shipwrecks with a Wikipedia article plus sinking events
  (CC0); an event folds into its ship for the date and death toll.
- A same-origin /api/shipwrecks proxy refreshes once a day and serves
  the last snapshot as stale for up to a week.

Markers are a boat icon coloured by era and sized by deaths; four era
chips filter the layer. The card follows the air-accidents layout
(deaths first). Text that names people is dropped; victims appear as
a count only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-05 04:42:59 -03:00
German Khokhlov 908cce915a Merge remote-tracking branch 'origin/main' into claude/hal-676-gev-fire-intensity-742bd1
# Conflicts:
#	src/i18n/locales/es.json
#	src/i18n/locales/ru.json
#	src/i18n/locales/uk.json
#	src/i18n/locales/zh-CN.json
2026-11-04 22:45:08 +04:00
Matt Van Horn 08b8be26fe Merge remote-tracking branch 'upstream/main' into fix/drone-airspace
# Conflicts:
#	docs/CURRENT-STATE.md
#	src/app/constructCatalog.test.mjs
#	src/app/sourceComposition.test.mjs
#	src/data/layerState.test.mjs
#	src/data/layerStateTokenLedger.test.mjs
#	src/i18n/locales/ar.json
2026-11-03 11:55:42 -08:00
German Khokhlov dda9a48e5b Merge remote-tracking branch 'origin/main' into claude/hal-676-gev-fire-intensity-742bd1 2026-11-03 21:38:02 +04:00
German Khokhlov f01200b467 Cross-fade Active Fires markers and heatmap; fix the spot size
The far-zoom heatmap now uses the ½× grid (4096 × 2048, ≈10 km per
pixel, one-pixel blur), so a lone fire's spot matches its marker at the
3,000 km hand-off. The spot size slider and its share option are gone.

Markers and heatmap are both kept built at every zoom and cross-fade by
camera height between 2,400 and 3,600 km on every frame, so a fast zoom
never shows a beat with neither. The heatmap fades by imagery alpha:
hiding a Cesium imagery layer unmaps it from the globe tiles, and showing
it again re-mapped them late, which was the gap at the band edge. Under
the markers the heatmap is built 150 ms after the data or filter changes.
2026-11-03 20:41:50 +04:00
German Khokhlov 3139369673 Add a heatmap spot size slider to the Active Fires row
A ½×–4× slider (log2 axis, 5 % steps, default 1×) under the intensity
filter sets how large far-zoom heat spots are. It scales the grid, not
the blur: 2× is a 1024 × 512 grid with the same one-pixel blur, so each
spot grows exactly and weak lone fires don't fade as they widen. A change
rebuilds only the heatmap. Share links keep it as w.s (percent, 50–400).
The layer row now takes a list of sliders.
2026-11-03 14:56:40 +04:00
Said Turksever fc7ff1055c Split Open Map Overlays into Sea Marks and Ski Pistes layers
Each overlay is now its own Infrastructure row instead of a chip on one
shared row: sea-marks (token 6) and ski-pistes (token 7), both
enabled-only, built by createOsmOverlayLayers() from one descriptor
list. Drops the option group and its share-codec defaults, gives each
layer its own name, voice entry and CURRENT-STATE row, and shifts the
token-ledger fixtures to the next free digit 8.
2026-11-02 16:20:08 +00:00
Said Turksever ce1ad3b95a Merge main into prereq-10-osm-overlays and port Open Map Overlays
Main moved on since this PR opened; the overlay layer is ported to the
current contracts:

- Layer-state token 6 (j belongs to transit; new single-character
  tokens are digits) with a reservation-ledger row, and the option owner
  renamed to the layer id so the compact share codec can encode it.
- Constructed per catalog by createOsmOverlaysLayer() in
  constructCatalog.js; src/standalone/data.js no longer registers layers.
- Drapes through maps/imageryHost.js, so the overlays show on Google 3D
  (tileset imagery) as well as the globe stacks, and follow map-stack
  switches. Imagery layers are created on enable and destroyed on
  disable/destroy.
- Translated chip labels and layer name in every locale, LAYER_NAME_KEYS,
  Infrastructure panel group, voice manifest entry.
- Attribution: OpenSeaMap tiles CC BY-SA 2.0, OpenSnowMap credit as
  required, OSM ODbL credit for both; bulk-download note in DATA_SOURCES.
- CURRENT-STATE row, changelog fragment, share-codec absent defaults,
  updated token-ledger fixtures and voice schema pins.
2026-11-02 15:55:40 +00:00
Matt Van Horn 4c2159a70a Merge upstream/main into fix/drone-airspace
Move the share-link token to 6 (3 is gnss-integrity), build altitude
ranges from LOWER/UPPER VAL, UOM and CODE, mark card and legend strings
for translation, rename the layer to Prohibited & Restricted Airspace,
move the changelog entry to changelog.d, and keep the layer voice-off
while the Realtime prompt is at its budget.
2026-10-27 05:06:05 -07:00
German KhokhlovandClaude Fable 5.1 38d29f2beb feat(fires): FRP intensity filter, confidence switch and six-stop color ramp (HAL-676)
Color every FIRMS detection by fire radiative power on the Portolan
cold → hot ramp (blue · teal · lime · amber · orange · red at
0 · 5 · 20 · 50 · 100 · 300 MW), replacing the three warm stops that
made a 2 MW and a 40 MW fire look alike. Heat cells, glow sprites and
card accents all read the same ramp (src/data/firmsIntensity.js).

Add a minimum-FRP log slider whose track is painted with the ramp (so
it doubles as the legend), an All / Nominal+ / High confidence switch
and an "N of M shown" counter to the Active Fires row. The filter runs
once in ingestion.applyFilter(), so markers, cards, heat cells, the
context store, the top-fires query and the analyst records all see one
set. Default is 0 MW / All: many of the strongest fires are rated low.

Persist both options in the layer state (token w: minFrp `f`,
confidence `c`), including the short share codec, and give the agent's
get_active_fires tool matching min_frp_mw / confidence arguments.

Layer panel gains generic `slider` and `segmented` row descriptors
(src/ui/rowFilterControls.js). New strings in es / ru / zh-CN.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-10-27 13:09:32 +04:00
Sameh Khamis da02fd8645 feat(share): short /share/<code> links with a compact versioned view format (#1418)
* feat(share): add compact versioned share-view codec

Add a standalone codec for `#s=<base64url>` share links: a version byte
followed by protobuf-style tag-length-value fields. Settings equal to
their default are omitted and refilled from the same table on decode;
unknown fields are skipped by wire type, so new fields never break
older apps. Views are a place (camera) or a thing (an aircraft,
military aircraft, vessel or satellite to follow, with camera offset
and last-known fix); an unknown kind falls back to the camera.

The decoder bounds its input and rejects malformed payloads with a
reason instead of throwing. A converter maps the long form's parsed
state to and from the codec state; the long form is unchanged.

* fix(share): tighten codec decoding after review

Unknown detection mode and allocation names now fall back to their
defaults instead of reaching the lenient long-form migration. Future
panel values and style-parameter tokens are skipped rather than applied
or rejected, and layer options are accepted only under their owner
layer. Wrap longitudes before scaling, normalize negative zero, reuse
the layer-state tracking ID grammar, and align the format doc with the
decoder.

* feat(share): let a host share the link its own way

ShareLinkManager.buildLink() returns the current share link without
copying it, and copyLink() now copies exactly that. The share button
passes the link to an optional services.shareLink(url), which resolves
to 'shared', 'copied', 'cancelled' or false; 'cancelled' shows no toast
and 'shared' shows the new "Link shared" (es, ru, zh-CN). Without the
service the button copies as before.

* feat(share): open and make short /share/<code> links

The share button now makes `<app>/share/<code>` links with the compact
share-view code, and the app opens them at start-up through the same
restore pipeline as long links. Once the app next writes the address, the
share path is replaced (no new history entry) by the app's own address and
the live long-form hash, so a reload follows the live view. A host can pass
the code as `initialShareCode` instead. A code that fails to decode restores
nothing and shows "This share link can't be opened".

The code gains a trailing CRC-8 check byte, so any single mistyped
character is rejected instead of opening a different view. Thing views no
longer store a camera: it is derived from the entity's last-known fix and
the follow offset on a sphere, the entity kind is implied by the id's
field, and the followed entity's layer bit is implied; an aircraft followed
from the app's own camera is a 42-character code. The `#s=` hash form is
gone.

While the camera follows an aircraft, military aircraft or satellite, the
link is a thing view with the camera's offset and the entity's position.
Opening one turns the layer on and follows the entity through the existing
shared-follow restore, then swings the camera round to the shared offset; a
vessel view selects the vessel once the vessel feed has it. If the entity
is not reporting within its window, the view stays at its last-known fix
and says so. The panel and style-parameter tables now come from the codec,
and a host share hook that returns a plain value or throws still gets a
toast.

* fix(share): encode style settings against the style's own look

Settings a style applies when selected (bloom, sharpen, HUD, detection,
celestial ring and the style's shader parameters) now default, per field,
to what that style applies in a fresh session, read from the tables the
app applies (the first-load baseline, the style presets and the shader
defaults). The encoder compares against the view's style and the decoder
fills omitted settings from the decoded style, so a view that keeps its
style's look costs only its camera and style. The previous defaults did not
match a fresh session (sharpen, HUD and detection were off), so every link
paid for them, and a style's own shader parameters were always written.

A test pins the per-style defaults as frozen v1 meanings. A followed
aircraft in Noir is now 44 characters and the real long-link example 39.

* docs: changelog for #1418
2026-10-26 15:31:51 -07:00
Mazeyar Moeini Feizabadi 4da002bd42 Merge current main into S2 GhostMaps and preserve updated import and voice behavior 2026-10-26 16:25:49 +01:00
Sameh Khamis f60b9ef7c8 chore: remove unused imports and local helpers
Drop module-local helpers that nothing calls (linkAbort, summarize,
stringOption, summarizeEntity and the polygonCenter, propertyObject and
unwrapProperties helpers only it used, CALLOUT_DOT_SIZE_PX) and unused
import specifiers in runtime modules, tests, QA scripts and tools.

None of the removed bindings is exported. Re-exports such as the ALPR
policy constants and every package export are unchanged.
2026-10-22 18:09:37 -07:00
GPTmadeitandClaude Opus 5.5 1f77d2290c feat: add S2 GhostMaps live intel layer
Adds an "S2 GhostMaps" layer (Events group) that renders S2 Underground's
GhostMaps KMZ exports (Common Intelligence Picture, Kinetic 30d, Border
Crisis, Iran CIP/Kinetic, Yemen FLOT, latest incident drop).

- server/providers/ghostMaps.js: watcher polls the repo's git smart-HTTP
  ref advertisement (default every 15s, GHOSTMAPS_POLL_SECONDS), fetches
  the tree only on a new head, caches commit-pinned files by blob sha and
  pushes catalog updates to browsers over SSE (/api/ghostmaps/events).
- src/layers/ghostMaps: KML reader, ArcGIS popup-table parsing, per-product
  data sources, product chips, horizon-culled pins, report cards.
- Unit tests, QA gate (scripts/qa-ghostmaps.mjs), credits (CC BY-NC-SA 4.0),
  i18n, voice manifest, layer-state token 5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-20 16:24:53 -04:00
German KhokhlovandClaude Opus 5.5 20f4cf3763 i18n: fix the LGTM review findings and tidy the branch texts
- i18n scanner: lastError sink, no false sinks on defaults and ternaries
- shared src/i18n/marker.js for modules that only mark strings
- wind field messages move to wind/fields.js; CITY_POIS imports from cityPois.js
- drop review IDs, change history and stale references from comments and docs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 21:00:28 +04:00
Meir Michanie f3da0916d9 Merge upstream/main: air-quality layer (AQHI) meets i18n
Upstream's #568 adds the Air quality layer to the Weather group (ECCC
AQHI, Canada only) with a band legend, worst-first list and voice
support.

- package-boundaries: both sides' entries unioned.
- layerPanel/weatherPanel tests: both sides' new tests kept; the
  weatherPanel fixture follows upstream (legendCategorical: true, the
  'list' block id) while our localized-history (K-6) test stays.
- air-quality strings marked: band labels and the provider label as
  msg() data, the readings-list aria-label through t(); es/ru entries
  added (+6 keys each).
- The synthetic air-quality allocation-benchmark titles follow the
  file's own convention (// i18n-ignore) like TRK/VESSEL/CAMERA.

Also verified the auto-merged weatherPanel.js against upstream: ORDER
gains 'air-quality', OBSERVED stays slice(2, 5), and the data-driven
legendCategorical path is upstream's.

npm run i18n: complete (2559), no unmarked strings. npm test: only the
pre-existing environmental OPENAI_API_KEY leak fails. Build passes.
2026-10-15 23:16:24 +02:00
German KhokhlovandClaude Opus 5.5 7fd1dd867f Merge origin/main: Panoramax, bikeshare Norway/Detroit, cloud filter strength, Overpass cache
Conflicts:
- KartaView viewer moved into the shared photoViewer.js on main; its
  labels now take the provider name ("{name} street-level photo") and
  are translated there.
- Weather: main's cloud filter strength slider, with its labels in t().
- Street Level caption: translated creator line plus main's licence.

Panoramax status and error texts are marked like KartaView's, and
"Panoramax answered HTTP {status}" gets a template in the panel and the
layer row. Catalogs: es/ru 2553 messages each, complete.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-16 00:06:45 +04:00
Sameh Khamis 046126eb36 Merge branch 'main' into feat/aqhi
Port to current main: main reserved layer-state token 0 for street-level, so
air-quality takes the next free token, 4 (registry, ledger, and the allocation
tests shift by one). Main moved source contracts into sourceComposition.js, so
air-quality registers its getSnapshot contract there. Package boundaries keep
main's lists plus this branch's air-quality modules and packages.
2026-10-15 12:39:00 -07:00
NotProniss 86b7dc8d3c feat(weather): add satellite cloud filter strength slider
Blend the existing infrared brightness key from Full (0%) to Clouds only (100%), preserving Soft/Vivid as independent opacity presets. Commit filter changes on slider release and retain legacy share-link mode codes.
2026-10-15 20:52:22 +02:00
German KhokhlovandClaude Opus 5.5 8029747b7e Merge origin/main: GNSS integrity, geo file import and cockpit emergencies meet i18n
Conflicts resolved by keeping both sides: the Events group gains
gnss-integrity with a translated label, the cockpit aircraft line shows
main's transponder emergency through a localized formatter (cockpitMath.js
stays i18n-free, like the Context scope line), and the boundary lists keep
both the i18n modules and main's new data modules.

Main's new UI is translated into es and ru: the GNSS Integrity row, legend
and notes; geo file import (button, hints, list actions, every import
error, with numbers passed as placeholders); transponder status names.
Also formats kartaview/viewer.js so format:check passes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 16:55:45 +04:00
Mazeyar Moeini Feizabadi 01a8ad7aa7 fix: address GNSS integrity and aircraft emergency review findings 2026-10-14 17:43:13 +02:00
Mazeyar Moeini Feizabadi ec80c89c1e fix: integrate GNSS and emergency features with current main contracts 2026-10-13 17:42:30 +02:00
Leo PechnickiandClaude eefcb338b3 feat(layers): GNSS interference layer from ADS-B NIC/NACp integrity
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]>
2026-10-13 17:19:23 +02:00
Matt Van Horn ffb332472d feat(layers): add FAA drone restrictions airspace layer
Show US prohibited and restricted special-use airspace from the keyless
FAA SUA FeatureServer as an off-by-default Infrastructure layer, with
share-link token 3 and an informational-only disclaimer.
2026-10-12 19:35:29 +00:00
German KhokhlovandClaude Opus 5.5 3129e531e6 feat(i18n): translate the whole interface
Mark every UI string the unmarked-text check reported (534) plus text it
cannot see (helpers, joined labels, runtime-built panels), and translate
the result to Spanish and Russian: 1,679 messages each.

- Kept on purpose, with the reason where the text lives: provider and
  product names, place and camera names, codes and units, dB values, the HUD
  terminal readouts, demo shot titles that saved projects look up by title,
  and text for AI tools and the voice model.
- Modules shared with the server or AI tools stay free of the i18n import
  (local msg); the wind reading and voice session stay English and are
  translated where the page shows them.
- npm test now runs the unmarked-text check.
- Fixes on the way: a duplicated radio privacy paragraph, the contact panel
  toggle saying Expand while open, the CSS filter mistaking &amp; for CSS.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-12 19:24:42 +04:00
Sameh Khamis eacfc1b06a Persist explicit provider toggles through the layer state coordinator 2026-10-09 19:09:30 -07:00
Sameh Khamis 3ba8c6d61b Use a neutral compact switch codec and clarify provider size limits 2026-10-09 18:47:04 -07:00
Sameh Khamis 7b1b2df764 Preserve custom Street Level provider switches in shared state 2026-10-09 17:53:47 -07:00
Said TurkseverandClaude Opus 5.5 4ea6631425 docs(street-level): shorten docs and comments before merge
Trim the doc text this PR adds (README, CHANGELOG, CURRENT-STATE,
KNOWN-ISSUES, SECURITY, DATA_SOURCES, .env.example, the CI job comment),
drop code comments that restate the code, cut design-note JSDoc to a line
or two, and drop review tags from test names. Comments only; no code
changes.

Also: README key count (seven keys, two metered), CURRENT-STATE's gate
paragraph (CI runs a production build with a dummy token), the stacked
doc above meshBoxInRange and the orphaned inMeshRange line in
coverage.js, and a duplicate ALPR row in DATA_SOURCES that a merge had
brought back.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 21:59:12 +01:00
Said TurkseverandClaude Opus 5.5 3af0f80cdb test(street-level): close the mutation survivors; Esc in rich text
A mutation review left 18 earlier survivors; each now has a test that
fails with the mutant applied:

- the expanded viewer returns into the panel on shrink, close and destroy
  (the fake DOM had no parentNode, so reinsertion was never exercised); the
  fake DOM gains offsetParent and a MutationObserver that honours its
  options, and a <body> child added while expanded goes inert;
- each pose moves the marker, drives FOLLOW and selects a new sequence;
  close() retires an open still mounting or opening; a provider toggled
  while the layer is off activates nothing; the adapter drops late and
  superseded poses;
- share-link defaults: "any date" (0 days), the codec max pinned to the
  filter's MAX_SINCE_DAYS (layerState cannot import it across package
  boundaries, so a test ties the two), Mapillary on when a link omits it;
- Esc that something else handled, or typed in a field, keeps the
  selection; a key problem outranks LOADING on the pill;
- the marker's terrain height (mesh, DEM, clamp then re-place);
- proxy: LRU memory cache and its half-budget rule, Retry-After capped,
  a request after a cancelled fetch starts a fresh one;
- date-line tile ranking with and without focus; the horizon cull idles
  while the camera stands still; the old zoom goes as soon as the new one
  lands; a rate limit holds every refresh inside its wait.

One real bug found and fixed: Esc inside a contenteditable cleared the
selected sequence (selection.js now ignores isContentEditable targets).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-06 16:54:06 +01:00
Said TurkseverandClaude Opus 5.5 5a7ce5785e Merge upstream/main (v0.2.1) into pr/mapillary-layer
Brings in 123 commits from main, including the v0.2.0 and v0.2.1 releases
(MCP server and tool catalog, server security hardening, build hygiene).

Conflicts:
- package.json: kept main's script order and added this PR's
  qa:panel-resize and qa:street-level; exports keep both sides (main's
  tools/view entries and this PR's street-level and Mapillary entries).
- src/tooling/viteBuild.test.mjs: kept this PR's Mapillary-token define
  assertion and main's new wildcard-host allowedHosts assertions.
- CHANGELOG.md: main now has release sections; this PR's entries go under
  [Unreleased].

Checks after the merge: doctor, format:check, check:boundaries,
layer-token:check (28 published, 1 new: street-level 0), npm test (5726
pass), build, the Windows job's focused tests (70 pass), qa:panel-resize,
and the keyless street-level gate. The new CSP allows Mapillary (https:
connect/img, blob workers). The keyed gate stops at coverage locally
because the dev .env Mapillary token is invalid (upstream 403), not because
of the merge.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-03 13:29:32 +01:00
rileygramlich 8887ca47eb feat(air-quality): add the Air quality layer to the Weather group
Reworks #568 to the shape asked for in review.

Generalized from "AQHI" to a provider-backed Air quality layer, with ECCC's
AQHI as the first registered provider. Readings are carried as
{ provider, scale, value, band } rather than a bare number, because
air-quality indices are not interchangeable: AQHI runs 1-10+ on health risk,
the US AQI runs 0-500 on concentration breakpoints, and Europe's EAQI is a
five-step category. Naming the index a value belongs to is what lets another
network be added without inventing a shared numeric meaning. Coverage is
stated as "Canada only" on the provider, the layer row and the card.

Adds the WEATHER panel readout (readout: true) carrying the band legend and
the worst readings currently in view, with getRowControls/setRowControlsListener
following the wind and cyclone cards. An unresolvable view rectangle degrades
to the worst readings overall rather than to a blank card, and a rectangle
crossing the antimeridian is handled.

update() now takes the earthquakes layer's abort-and-guard shape: it aborts any
in-flight request, passes the signal through to the source, and refuses to
touch the scene when the request was superseded, aborted, or the layer was
disabled while it was outstanding. disable() and destroy() abort too. Tests
cover the late-response, superseded-refresh and disabled-update paths.

Rebased on main: the source is registered in src/sources/reference.js, and the
allocation profile was re-measured against the current worker — median 3,900
B/frame, max 4,114, 85.7 B/candidate, inside the shared 154 ceiling, budgeted
at 5,200 for 26% headroom. The earlier figures no longer held.

Freshness is now judged on the observation time rather than on when the last
response arrived. lastUpdate only says the feed answered; a station that keeps
returning a reading from four hours ago would otherwise be narrated as live by
the HUD and the voice provenance envelope. getStats() reports observedAt and
stale, so layerFeedState() calls the layer stale past two missed hourly
publications, and degraded when a refresh fails over readings that are still
good.

The share-link token is 0, which is what layer-token:next allocates. Taking it
moves the ledger fixtures that had hard-coded 0 as the free digit and 3 as the
one after; those shift to 3 and 4. The pinned equality between the reservation
ledger and LEGACY_LAYER_STATE_TOKENS also had to become "the closed legacy set
plus air-quality", since that set is documented as never growing. Happy to
re-pin this differently if you would rather the fixtures derive the provisional
digits from nextLayerStateToken() so the next layer does not move them again.

The layer and its source are registered as ./layers/air-quality and
./layers/air-quality/source, each with its own boundary group, following the
perimeters pair. check:boundaries requires the package exports and the group
exports to be the same set, so a layer that ships a source needs both.
2026-10-02 15:12:37 -06:00
Sameh Khamis 9de2705eaf refactor: move the layer state coordinator into its own module
layerState.js held both the share-link layer codec and the coordinator
that restores layer choices into the running app and persists them to
browser storage. Move the coordinator, unchanged, to
layerStateCoordinator.js so the codec stays a pure data module that code
outside the browser can use to write share links. The registry stays in
layerState.js, where the token tooling reads it, and the aircraft
barrel re-exports both.
2026-10-01 13:16:19 -07:00
Said Turksever c4514784fc Merge branch 'main' into pr/mapillary-layer 2026-09-29 15:44:25 +01:00
f4fcf267c6 fix(osm): stop using public Overpass by default (#648) (#742)
Traffic roads come from OpenFreeMap vector tiles, or TomTom flow tiles with a
key (Hybrid by default). Military areas use OpenFreeMap polygons with a bundled
worldwide name index; wide views show named points. ALPR reads an hourly
OpenStreetMap extract. Cockpit place context and country, state and US county
outlines use bundled Natural Earth and US Census data. Overpass is used only
when an operator configures their own endpoint.

Also: camera arrivals no longer land below ground, keyless terrain retries on
429, bounded relation geometry for configured Overpass, one shared
OpenStreetMap credit, and separate tile and API transports for embedders.

Co-authored-by: theGaryLarson <[email protected]>
Co-authored-by: Milan Khanal <[email protected]>
2026-09-28 19:07:57 -05:00
Said TurkseverandClaude Opus 5.5 ecb16b2fe2 Merge upstream/main into pr/mapillary-layer
Brings in #744 (share-link token ledger). package.json: keep the branch's
sorted scripts and add layer-token:next and layer-token:check.

Street Level's token moves from the provisional `3` to `0`: following
CONTRIBUTING, the provisional entry was removed and
`npm run layer-token:next -- street-level` assigned `0` (free digits in
order). It is added to the registry and to
src/data/layerStateTokenReservations.json, and
`npm run layer-token:check -- --base-ref upstream/main` passes (28
published, 1 new). No link with the provisional token was ever published.

Pinned token tests move with the ledger as CONTRIBUTING asks: 29
reservations, next free digit `3`, and the competing-PR and retired-digit
scenarios shift up one free digit (0->3, 3->4) so they still test the same
rules. The README share examples and the free-digit notes in
CONTRIBUTING and CURRENT-STATE now read `0`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 21:49:46 +01:00
Said TurkseverandClaude Fable 5.1 28e6a1faa7 feat(street-level): provider contract with Mapillary as the first provider
Street Level is now a provider-neutral layer modelled on the iD editor's
photo overlay. The core owns what every imagery provider shares: the
enable state and a switch per provider, the imagery filter (360°/flat and
captured-since in relative days), one click handler routed by pick prefix,
one viewer host that mounts whichever provider's viewer adapter owns the
open image, the position marker, camera follow, one on-globe credit per
active provider, and share-link parameters (one bit per provider plus the
filter). Providers implement the contract in registry.js: coverage,
sequences, nearest-image lookup and a viewer adapter emitting a common
pose. Mapillary is the only registered provider; Google Street View,
KartaView and Panoramax slot in beside it.

Panel: a PROVIDERS chip row (one chip per registered provider, keyless
chips read as errors), relative-day SINCE options, a provider-neutral
caption whose link label follows the provider, and no vendor mark in the
header. The panel inner is a rail scroller like CCTV's.

Tests cover the registry, params codec, filter, pick router, UI-state
composition, the provider definition and coverage-tile decoding; the QA
harness switches the provider off and on and checks the filter.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-25 18:34:09 +01:00
Said TurkseverandClaude Fable 5.1 2dbc684fb1 refactor(street-level): rename the Mapillary layer to street-level
Pure move: the layer id, panel id, CSS prefix, classes, exports and config
paths become provider-neutral (street-level, #street-level-panel, sl-);
the Mapillary-specific modules move under providers/mapillary/. Behaviour
is unchanged. The two sprite collections now register under distinct ids
instead of overwriting each other under 'mapillary'.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-25 18:13:53 +01:00
Said TurkseverandClaude Fable 5.1 9ba6f96d87 Merge upstream/main into pr/mapillary-layer
Upstream took share token '2' for fire perimeters, so Street Level moves to
'3'. mapillary-js joins upstream's optimizeDeps include list instead of
overriding it; catalog and registry tests count the new upstream layers.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-25 17:58:27 +01:00
manjunath22466 54735a9e6f fix: allocate share-link layer tokens without collisions 2026-09-24 16:25:55 +05:30