Phone can finish a polygon without double-click. List is mute/go/rename/delete.
Inbox seeds from /api/geofence-alerts and appends WS hits. Watch all fence
ids on /ws/live. Selected-fence DVR uses GET /api/geofences/{id}/at.
Off-viewport clients that send {type:watch_geofences,ids} on /ws/live
still receive geofence_alert; AIS/ADS-B stay viewport-only.
GET /api/geofence-alerts accepts geofence_id/since/until/source_kind.
GET /api/geofences/{id}/at returns CAGG+FIRMS inside the fence at T
(404 if missing, empty lists if DB down). DELETE missing fences 404s.
Reject private/loopback/link-local hosts on POST /api/ingest/rss and
URL-shaped GDELT queries via camera_scraper.is_public_url (HTTP 400).
PATCH /api/sources/{id} only accepts name, url, config, enabled (422 else).
Pin titiler to the Pi-running digest, make workers=1 explicit for
in-memory WS/layer caches, and add PostGIS GIST indexes for events/fires
bbox pans without dropping the existing btree indexes.
GET /api/satellites?groups=stations,weather returns last-known satellite
positions propagated from CelesTrak GP JSON (OMM mean elements, not TLE)
via the real sgp4 library (TEME->geodetic), not two-body Kepler.
- Fetch gp.php?GROUP=...&FORMAT=JSON at most once per 2h per group,
cached with a last-good blob fallback for 403 / stale responses.
- SatNOGS TLE fallback only when the CelesTrak cache is empty.
- bbox viewport culling; unknown group -> 400.
- overlay_catalog() gains id=satellites (kind=points, /api/satellites).
- OMM path handles NORAD cat numbers >= 100000 (no TLE round-trip).
Poll GET /api/stats ~30s for rail integers (404 falls back to overlay
array lengths). Keyboard: ? cheat sheet, L layers, R reset, Esc closes
panels; F/S left unbound. Cheap sun-position day/night overlay default
off. Camera/HLS thumbs only at zoom >= 12. NOAA SWPC Kp chip in the
status strip (browser-direct; hide on failure).
Default-off LAYERS row paints L.circleMarker theatres colored by
severity (war/high/elevated). Popup shows label, description, and
eventCount. Catalog is fetched once (not on moveend). A 404 hides
the toggle. Null lat/lon rows are skipped so they never pin at 0,0.
Add parse_udot_ibi_page + scrape_udot_ibi for the UDOT IBI 511 traffic
camera feed (prod-ut.ibi511.com, no key). DataTables endpoint is POST
form-encoded and caps at 100 rows/page regardless of `length`; page walk
uses recordsTotal with a UDOT_IBI_MAX_PAGES (default 40) runaway cap.
Skip images[0].blocked/disabled, parse WKT POINT(lng lat) from
latLng.geography.wellKnownText, drop outside the Utah bbox
(lat 36.9-42.1, lon -114.2--108.9). discovery_source=udot, stable
source_url == snapshot_url == /map/Cctv/{id} (never scrape frames),
url_hash dedupe, OSINT_USER_AGENT + X-Requested-With header.
Unit tests: WKT lng/lat order, blocked/disabled skip, bbox drop,
malformed payload, missing WKT.
Right-click (long-press on touch) opens a What's here? HUD panel: reverse
geocode via GET /api/place (honest UA, 1 req/s, 60s/500-key cache) and lists
already-loaded cameras, aircraft, vessels, fires, and alerts within 5 km.
Show callsign/hex/registration/type/alt/gs/squawk in ADS-B popups, keep
Planespotters photos, badge and color squawk 7700/7600/7500 plus emergency
flags, and expose a MIL filter only after /api/aircraft sends extra.role.
Add parse_mdot_json for the MDOT MiDrive camera/list JSON where
coordinates/id live in the county field's map link (lat=/lon=/id=) and
the JPEG still lives in the image field's <img src>. Michigan bbox
filter, missing-coords drop, RTSP reject. discovery_source=mdot,
stable source_url keyed on camera id, url_hash dedupe. Wired into
scrape_source dispatch + CAMERA_SOURCE_URLS defaults.
Unit tests: HTML field extract, bbox drop, missing-coords/image drop,
malformed payload.
Curated static catalogue of 13 conflict theatres (Ukraine, Gaza, Sudan,
Myanmar, DRC, Yemen, Syria, Lebanon, Sahel, Somalia, Red Sea, Taiwan
Strait, Korean DMZ) with war|high|elevated severity and short factual
descriptions. GET /api/conflicts returns { zones: [{id,label,severity,
lat,lon,description,eventCount,lastUpdated}], timestamp } where
eventCount rolls up pre-existing geocoded news/GDELT//api/news/map rows
inside each zone bbox (0 when the tables are empty). Adds the conflicts
entry to overlay_catalog(). No upstream scraping, no jittered coords.