1fr in the grid row caused the panel to fill the entire remaining page
height on mobile (~1000px+), leaving large gaps around the centred content.
max-height: min(55vh, 520px) keeps it proportionate on any screen size.
Also switch to justify-content: flex-start so the canvas+strip pack at
the top rather than floating in the middle of a large void.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fills the empty space below the sky view circle with a compact
three-column AOS / TCA / LOS readout (time + azimuth/elevation)
and a duration + max elevation footer line.
Populated by drawPolarPlot() when a pass is selected; shows a
placeholder prompt otherwise.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Setting both width:100% and height:100% made CSS ignore aspect-ratio,
stretching the drawing buffer non-uniformly into the tall container.
Fixed by keeping only width:100% + max-height:100% so aspect-ratio:1/1
clamps the height and the element stays square.
Draw functions now use canvas.offsetWidth for the square buffer size.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- command-rail last row changed from minmax(260px, 340px) to 1fr so the
polar plot expands to fill whatever vertical space remains after the
Next Pass and Live Telemetry panels
- polar-container made flex-column so panel-content can grow with flex: 1
- #polarPlot width/height 100% with aspect-ratio 1/1 — canvas fills the
available square area and stays proportional
- Remove align-items: start from the 1320px breakpoint primary-layout so
the command-rail stretches to match map height in the two-column layout
- Fix matching 2-column command-rail rows at 1320px breakpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove align-self: start from .polar-container so the grid row's
minmax(260px, 340px) height is actually respected
- Switch #polarPlot to aspect-ratio: 1/1 so the canvas is always square
- Fix both draw functions to size canvas from getBoundingClientRect on
the canvas itself (not parent) using min(width, height) for a square plot
- Remove min-height from .dashboard to prevent empty space below content
on narrow/mobile screens where stacked panels are shorter than 720px
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move _passAbortController = null to after response.json() so the retry
scheduler cannot see a false idle state mid-parse, increment
_passRequestId, and discard the in-flight response — this was causing
non-ISS satellites to show no passes intermittently
- Add _computeSlantRange() helper using 3D ECEF geometry
- Update applyTelemetryPosition to compute slant range from SSE lat/lon/
altitude, giving distance updates at 1Hz instead of 5s HTTP poll rate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two root-cause bugs causing the reported issues:
1. Tracker never sent ISS positions: _start_satellite_tracker fell back
to sat_name.replace(' ', '-').upper() as the TLE cache key when the
DB entry had null TLE lines. For 'ISS (ZARYA)' this produced
'ISS-(ZARYA)' which has no matching entry in _tle_cache (keyed as
'ISS'). ISS was silently skipped every loop tick, so no SSE positions
were ever emitted and the map marker never moved.
Fix: try _BUILTIN_NORAD_TO_KEY.get(norad_int) first before the
name-derived fallback so the NORAD-to-key mapping is always used.
2. Stale TLE pass prediction results were cached: if startup TLEs were
too old for Skyfield to find events in the 48h window, the empty
passes list was cached for 300s. A page refresh within that window
re-served the empty result, showing 'NO PASSES FOUND' persistently.
Fix: only cache non-empty pass results so the next request
recomputes once the TLE auto-refresh has populated fresh data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
updateCountdown fell back to passes[0] even when it was in the past,
showing 00:00:00:00 with a stale satellite name indefinitely. Now
displays a clear 'NO UPCOMING PASSES' state with '--' for all fields
when no future pass exists in the current prediction window.
Ground track computation (90 Skyfield points per satellite) was blocking
the 1Hz tracker loop on every cache miss. On cold start with multiple
tracked satellites this could stall the SSE stream for several seconds.
Tracks are now computed in a 2-worker ThreadPoolExecutor. The tracker
loop emits position without groundTrack on cache miss; clients retain
the previous track via SSE merge until the new one is ready.
Consolidated to a single active-request guard with cleanup in finally.
The previous pattern had redundant null-checks across try and catch, and
an always-false check on a controller that was already null. Cancel-on-
new-request is now explicit before creating the new controller.
METEOR-M2 (NORAD 40069) is a weather satellite with LRPT downlink but
was missing from WEATHER_SAT_KEYS, so no capture button appeared in
the pass list. Adds it alongside M2-3 and M2-4.
Replace geocentric.distance().km - 6371 (fixed spherical radius) with
wgs84.subpoint(geocentric).elevation.km in the /position endpoint.
The SSE tracker was already fixed in the Task 1 commit.
Previously currentPos only had lat/lon, so the updateTelemetry fallback
(used before first live position arrives) always showed '---' for
altitude/elevation/azimuth/distance. currentPos now includes all fields
computed from the request observer location. updateTelemetry simplified
to delegate to applyTelemetryPosition.
TLE data was only refreshed once at startup. After each refresh, a new
24-hour timer is now scheduled in a finally block so it fires even on
refresh failure. threading moved to module-level import.
Adds a 'source' param to handleLivePositions. The SSE path ('sse') only
applies lat/lon/altitude/groundTrack since the server-side tracker has
no per-client location. The HTTP poll path ('poll') owns all observer-
relative data and the visible-count badge.
SSE runs server-wide with DEFAULT_LAT/LON defaults of 0,0. Emitting
elevation/azimuth/distance/visible from the tracker produced wrong
values (always visible:False) that overwrote correct data from the
per-client HTTP poll every second.
The HTTP poll (/satellite/position) owns all observer-relative data.
SSE now only emits lat/lon/altitude/groundTrack. Also removes the
unused DEFAULT_LATITUDE/DEFAULT_LONGITUDE import.