Compare commits

..

6 commits

Author SHA1 Message Date
38eb4fe4d6 Merge pull request 'feat(map): quiet HUD chrome — VIIRS default, collapsed layer rail' (#49) from feat/hud-chrome-quiet into master
All checks were successful
build-and-deploy / build-push-deploy (push) Successful in 27s
Reviewed-on: #49
2026-09-01 17:21:09 -04:00
Sirius DevOps
0fa49b8407 fix(map): NEWS ticker fills 32px news-only dock
.ticker stayed at height:50% after MKT was hidden, leaving an empty
half-bar. .dock.news-only .ticker is now 100% of the 32px dock.
2026-09-01 17:15:32 -04:00
Sirius DevOps
beb457c382 feat(map): quiet HUD chrome — VIIRS default, collapsed rail
Satellite-first chrome: daily VIIRS preferred, layer rail collapsed on
load, geofence after basemap, FIRMS-only default overlays, hide MKT
standby ticker, Strait select instead of buttons, IBM Plex instead of
Orbitron, news as 6px circleMarkers.
2026-09-01 17:15:24 -04:00
cb20473119 Merge pull request 'feat(map): geofence watch HUD — finish/cancel, inbox, fence DVR' (#48) from feat/geofence-watch-ui into master
All checks were successful
build-and-deploy / build-push-deploy (push) Successful in 21s
Reviewed-on: #48
2026-09-01 07:44:36 -04:00
Sirius DevOps
8c97ce50d2 feat(map): geofence watch HUD — finish/cancel, inbox, fence DVR
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.
2026-09-01 01:36:21 -04:00
75e065f8c2 Merge pull request 'feat(geofence): watch list, filtered hit log, fence snapshot' (#47) from feat/geofence-watch into master
All checks were successful
build-and-deploy / build-push-deploy (push) Successful in 15s
Reviewed-on: #47
2026-09-01 01:26:31 -04:00
3 changed files with 619 additions and 182 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
"""Geofence layer panel: draw + delete (DELETE /api/geofences/{id}).""" """Geofence layer panel: draw, watch, inbox, delete (HTML contract)."""
from __future__ import annotations from __future__ import annotations
@ -24,3 +24,33 @@ def test_load_geofences_renders_delete_controls():
assert "deleteGeofence" in js assert "deleteGeofence" in js
assert "onEachFeature" in js assert "onEachFeature" in js
assert "bindPopup" in js assert "bindPopup" in js
def test_finish_cancel_draw_controls():
assert 'id="gf-finish"' in HTML
assert 'id="gf-cancel"' in HTML
assert "function cancelGeofenceDraw" in HTML
assert "function onGfClose" in HTML
def test_watch_geofences_ws_payload():
assert "watch_geofences" in HTML
assert "function sendWatchGeofences" in HTML
def test_geofence_alert_inbox():
assert 'id="gf-inbox"' in HTML
assert "/api/geofence-alerts" in HTML
assert "function loadGfInbox" in HTML
assert "function pushGfInbox" in HTML
def test_delete_geofence_still_present():
assert "function deleteGeofence" in HTML
assert "method: 'DELETE'" in HTML or 'method: "DELETE"' in HTML
def test_fence_dvr_at_endpoint():
assert "/at?timestamp=" in HTML or "/at?timestamp=${" in HTML
assert "function dvrScrubFence" in HTML
assert "gfSelectedId" in HTML

113
tests/test_hud_chrome.py Normal file
View file

@ -0,0 +1,113 @@
"""Quiet HUD chrome: VIIRS default, collapsed rail, no Orbitron/MKT dashes."""
from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
HTML = (ROOT / "app/static/index.html").read_text()
def _attr(html: str, elem_id: str) -> str:
chunk = html.split(f'id="{elem_id}"', 1)[1].split(">", 1)[0]
return chunk
def test_initmap_prefers_viirs_true_color():
init = HTML.split("async function initMap", 1)[1].split("function readMapPrefs", 1)[0]
assert "VIIRS_SNPP_CorrectedReflectance_TrueColor" in init
assert init.index("VIIRS_SNPP_CorrectedReflectance_TrueColor") < init.index(
"MODIS_Terra_CorrectedReflectance_TrueColor"
)
assert init.index("MODIS_Terra_CorrectedReflectance_TrueColor") < init.index(
"BlueMarble_ShadedRelief_Bathymetry"
)
def test_orbitron_gone():
assert "Orbitron" not in HTML
assert "IBM Plex Sans" in HTML
assert "IBM Plex Mono" in HTML
def test_lp_note_stripped_from_layer_list():
assert 'class="lp-note"' not in HTML
body = HTML.split('class="lp-body"', 1)[1].split("lp-legend", 1)[0]
assert "lp-note" not in body
def test_default_overlays_basemap_and_firms_only():
fires = _attr(HTML, "lp-fires-on")
assert "checked" in fires
for eid in (
"lp-cams-on",
"lp-blips-on",
"lp-news-on",
"lp-radar-on",
"lp-alerts-on",
"lp-perim-on",
"lp-ac-on",
"lp-trains-on",
"lp-storms-on",
):
assert "checked" not in _attr(HTML, eid), eid
def test_geofence_markup_before_cameras():
assert 'id="gf-draw"' in HTML
assert HTML.index('id="gf-draw"') < HTML.index('id="lp-cams-on"')
assert HTML.index('id="lp-base-on"') < HTML.index('id="gf-draw"')
def test_parent_geofence_hud_survives():
assert "watch_geofences" in HTML
assert "function deleteGeofence" in HTML
assert 'id="gf-finish"' in HTML
assert 'id="gf-cancel"' in HTML
assert 'id="gf-inbox"' in HTML
def test_layer_rail_collapsed_on_load():
head = HTML.split('class="lp-head"', 1)[1].split("</div>", 1)[0]
assert 'aria-expanded="false"' in head
assert 'id="layer-panel" class="collapsed"' in HTML
def test_market_ticker_hidden_no_poll():
mkt = HTML.split('class="ticker market"', 1)[1].split(">", 1)[0]
assert "hidden" in mkt
assert "setInterval(probeMarket" not in HTML
assert "setInterval(loadMarket" not in HTML
init = HTML.split("function initMarketTicker", 1)[1].split("function ", 1)[0]
assert "/api/market" in init or "404-poll" in init
assert "setInterval" not in init
def test_news_ticker_fills_news_only_dock():
css = HTML.split("</style>", 1)[0]
compact = css.replace(" ", "").replace("\n", "")
assert ".dock.news-only{height:32px;}" in compact
assert ".dock.news-only.ticker{height:100%;}" in compact
assert ".ticker{display:flex;align-items:stretch;height:50%;" in compact
def test_news_pins_are_circle_markers():
js = HTML.split("async function loadNewsPins", 1)[1].split("function refreshLiveOverlays", 1)[0]
assert "L.circleMarker" in js
assert "fillOpacity: 0.7" in js or "fillOpacity:0.7" in js
assert "rotate(45deg)" not in js
assert "L.divIcon" not in js
def test_chokepoint_buttons_not_in_toolbar_flow():
assert 'id="chokepoint-select"' in HTML
css = HTML.split("</style>", 1)[0]
assert ".chokepoint-btns { display: none; }" in css or ".chokepoint-btns{display:none" in css.replace(
" ", ""
)
def test_brand_is_osint_slash():
assert "GLOBAL SITUATIONAL AWARENESS TERMINAL" not in HTML
assert "OSINT" in HTML
assert 'class="accent">//</span>' in HTML