{{ message }}
Tags: NASA-AMMOS/MMGIS
Tags
security: XSS prevention, CI hardening, geodataset validation, log sa… …nitization (#1006) * perf: re-enable Numba JIT after benchmarking (saves ~6s per frame) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * debug: re-add timing instrumentation to sightmap response Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * perf: compute grid convergence analytically, skip GDAL TransformPoints _compute_directions was calling _pixels_to_geo_batch on all 940K cells (969x969 grid) to get each cell's longitude for the convergence angle. This took ~1s (29% of total). Now computes convergence directly from projected coordinates using atan2(x - false_easting, sign*(y - false_northing)) — pure numpy math, no GDAL coordinate transform. Expected: ~0.01s. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * perf: skip kernel unloading + increase coarse subgrid step to 50 - Remove spiceypy.unload() calls — process exits immediately after response so OS cleanup is sufficient. Saves ~0.235s. - Increase COARSE_AZEL_STEP from 10 to 50 — reduces coarse subgrid points from ~9400 to ~400 for sun az/el interpolation. Sun position varies slowly across the DEM so fewer sample points suffice. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: remove timing debug instrumentation from sightmap.py Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: interpolate sun azimuth in sin/cos space to prevent wrap artifacts When coarse subgrid has azimuth values near the 360°/0° boundary (e.g. 355° and 5°), linear interpolation produces ~180° — completely wrong direction that flips shadows. Now interpolates sin(az) and cos(az) separately, then recovers the angle via atan2. This handles the circular wrap correctly. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: viewport-aware sightmap — clip DEM read to visible area at native resolution Frontend sends current map viewport bounds (projected coords) with each static sightmap request. Backend clips the DEM read to the viewport intersection, reading at native resolution (capped at maxOutputDim). When zoomed in, this means the sightmap covers just the visible area but at much higher resolution than the full-DEM downsampled version. When zoomed out, viewport encompasses the full DEM and behavior is unchanged. For a 30993x30993 DEM zoomed to 1/10th coverage, instead of reading the full raster decimated to 1600x1600, we read only ~3000x3000 native pixels for the visible window — higher res and faster. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: compute viewport projected bounds from container corners, not lat/lng bbox In polar stereographic CRS, the lat/lng bounding box from getBounds() maps to a distorted region in projected space. Now samples all 4 container pixel corners, projects each through the CRS, and takes the envelope for correct projected bounds. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: position sightmap overlay using projected NW/SE corners directly In polar/rotated CRS, L.latLngBounds normalises by min/max lat/lng, which shuffles corners — getNorthWest() and getSouthEast() return points that don't correspond to the projected rectangle's NW and SE. The overlay is then mispositioned and stretched. New _projImageOverlay() helper overrides the overlay's _reset method to compute pixel position from the projected NW (xmin, ymax) and SE (xmax, ymin) corners via latLngToLayerPoint, bypassing the normalisation entirely. Applied to all three overlay creation paths: static sightmap, heatmap, and sweep frame. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: override _animateZoom on projected overlay to prevent zoom jump The default _animateZoom reads the normalised L.latLngBounds which has wrong corners in polar CRS, causing the overlay to briefly jump to the top-left during zoom transitions. Now _animateZoom uses the projected NW corner via _latLngToNewLayerPoint for correct animation. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: wire viewport bounds through batch/sweep mode Frontend sweep call now sends viewportBounds. Backend compute_sightmap_batch accepts and passes viewport_bounds to open_dem so playback also clips to the visible DEM region at native resolution. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: equalize sweep/static resolution + show 'Sweeping' on progress button - _resolutionToMaxDim now returns 800 for both modes (was 400 for sweep) - ProgressButton label shows children text alongside percentage - SightlineElement shows 'Sweeping'/'Generating' while loading Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: actually commit _resolutionToMaxDim change to equalize sweep/static res Was missed from the previous commit — sweep was still getting 400 instead of 800. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: re-fetch horizon profile after pan so charts stay in sync When the user panned, invalidateHorizonCache set _horizonCache to null but never triggered a re-fetch. Subsequent scrub or playback frame changes found the cache empty and either skipped the horizon redraw or drew the visibility timeline with no profile (marking everything not visible). - _onPanEnd now calls invalidateAndRefetch() which re-fetches the horizon profile if the graph panel is open. - _scrubToFrame and updatePlaybackFrame fall back to fetchAndDrawHorizon when the cache is null instead of drawing with missing data. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: correct horizon profile azimuth for projected CRS (polar stereo) HorizonProfile.py was marching along pixel/raster-space directions, treating pixel-up as north. In polar stereographic the grid north axis is rotated from true north by the grid convergence angle, so the profile azimuths were offset and the terrain silhouette appeared rotated in the chart. Added _grid_convergence() which computes the convergence at the observer's projected coordinates via atan2(x - FE, -(y - FN)). Each geographic azimuth is now rotated by the convergence before marching in pixel space, making the profile azimuths true geographic azimuths. No change for geographic (unprojected) CRS. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: correct horizon profile convergence formula for polar stereo Two bugs in the previous convergence fix: 1. _grid_convergence used atan2(x, -y) unconditionally, but for south-pole stereo the sign should be +1 (north is away from pole = positive y). Now uses north_sign = -1 for north-pole, +1 for south-pole, matching sightmap.py exactly. 2. The convergence was subtracted (geo_az - convergence) when it should be added (geo_az + convergence), matching sightmap.py's convention where convergence rotates from grid north to true north. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor: extract rate limiters into shared scripts/rateLimiters.js module - Create scripts/rateLimiters.js exporting apilimiter, authLimiter, computeLimiter - Remove inline rateLimit() definitions from scripts/server.js - Remove authLimiter/computeLimiter from the 's' setup object - Update Users/routes/users.js: import authLimiter directly, replace late-binding wrapper - Update Users/setup.js: remove router._authLimiter assignment - Update Utils/routes/utils.js: import computeLimiter directly, replace all 8 late-binding wrappers - Update Utils/setup.js: remove router._computeLimiter assignment - Update unit tests to import from the shared module Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: bump version to 5.0.26-20260609 [version bump] * fix: use geodesic destination for azimuth lines instead of angle rotation Replace the _localNorthAngle + screen-space rotation approach with a direct forward geodesic method: compute a destination lat/lng 1° along the desired azimuth, project it through Leaflet, and draw the line. This avoids potential compound angle errors and works correctly for any CRS because it uses Leaflet's own projection pipeline end-to-end rather than computing a screen-space north-offset angle and rotating. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: convert reference mission DEMs to Cloud Optimized GeoTIFF (COG) Both the Earth (USGS SF Hill) and Lunar South Pole (LRO LOLA 4000m) DEMs are now tiled COGs with deflate compression and overviews. This ensures consistent behavior with the sightmap COG requirement and enables fast overview-based reads at any resolution. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: add lunar LSMT to chronice, fix TimeUI indicator cleanup and observer time sync - chronice.py: add lunar LSMT support using SPICE et2lst with observer longitude; format: LDAY-NNNNNLHH:MM:SS; reverse conversion via iterative refinement - utils.js: pass optional lng param through to chronice.py - SightlineTool.js: remove TimeUI indicator on mode switch, cancel sweep, resweep start, and pan-end; pass lng from observer point for LSMT observers - SightlineElement.jsx: update global TimeControl when observer time inputs are changed (blur/Enter), fixing Mars SOL time not updating the TimeUI - Lunar ref mission config: add Moon (LSMT) observer with type=lsmt Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: LSMT lng fallback to map center, hide empty DEM dropdown, Enter key on observer time - _getObserverLng: fall back to map center when indicatorLastDragPoint is null - SightlineElement: hide DEM dropdown when no data options configured - SightlineElement: add onKeyDown Enter handler on observer time inputs Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * Improve Mars Reference Mission * chore: bump version to 5.0.28-20260610 [version bump] * fix: sightmap overlay CRS mismatch for non-custom projections, restore config descriptions - Only use _projImageOverlay and viewport clipping when the mission uses a custom projected CRS (projection.custom=true). For standard longlat/ Mercator missions (like Mars), the DEM's projected bounds are in a different CRS than the map, causing misplaced overlays. - Restore Layer-specific DEMs config row and improve field descriptions in sightline tool config.json (lost during ShadeTool→SightlineTool rename). - Add sweepColorRamps and observer type examples to descriptionFull. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: restore sightline config descriptions from ShadeTool, remove data row, clear default name - Remove Layer-specific DEMs config row (previously asked to remove) - Restore detailed descriptions from old ShadeTool config: - Sources: documents name/value properties, dropdown usage, kernel path - Observers: documents name/value/frame/body, chronos setup path - Default Height: full description of height parameter behavior - Observer Time Placeholder: documents format string usage - Frame/Body fields: proper SPICE reference descriptions - Remove 'Sightline N' default element name (now empty) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: observer time input 7hr drift — chronice result parsed as local instead of UTC Root cause: chronice lmst→utc returns '2026-05-30T21:36:57.975' (no Z suffix). The old ShadeTool correctly did: result.replace(' ', 'T') + 'Z' The new code used a regex chain that failed when milliseconds were present without a trailing Z, leaving the string timezone-ambiguous. new Date() then parsed it as local time (UTC-7), adding ~7 hours. Fix: strip milliseconds then unconditionally append Z, matching the old ShadeTool approach. The /ZZ$/ → Z guard prevents double-Z if chronice ever returns a Z-suffixed result in the future. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: sightmap sun direction for cylindrical projections, 1s time drift, tab-switch regen, add editable time field 1. Sightmap sun direction: _compute_directions now only applies convergence rotation for azimuthal projections (stereo/gnomonic). For cylindrical projections (Equidistant Cylindrical, Mercator), grid north = geographic north so convergence = 0. Previously applied polar-stereo formula to all projected CRS, giving ~90deg rotation on Mars DEM. 2. Observer time 1-second drift: restored _lastConvertedMs pattern from old ShadeTool. Saves sub-second precision from observer->UTC conversion and re-attaches it in UTC->observer reverse conversion for exact round-trips. 3. Tab-switch regeneration: _onTimeChange now tracks _lastGeneratedTime and skips if unchanged, preventing redundant sightmap computation when TimeControl re-broadcasts the same time on tab refocus. 4. Editable time field (vstOptionTime): restored from old ShadeTool. Shows current end time in configured format (DOY, etc), editable on blur/Enter. Parses via utcTimeFormat if configured, else appends Z directly. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: match old ShadeTool vstOptionTime styling and DOY format - CSS matches old ShadeTool exactly: full-width centered input, bold 14px, color-p0 bg, color-a1-5 text, transparent border that shows color-c on focus - Clock icon positioned absolute right (pointer-events: none) as in original - Structure uses flexbetween wrapper matching old jQuery markup - Mars reference mission utcTimeFormat changed to DOY: '%Y-%j %H:%M:%S' giving output like '2026-150 21:36:57' instead of ISO format Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * ui: hide observer start time in static mode, show only 'Time' field Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: horizon profile rotation for cylindrical CRS + visibility chart text non-selectable 1. HorizonProfile.py _grid_convergence: same fix as sightmap.py — only apply convergence for azimuthal projections (stereo/gnomonic). For cylindrical projections (Mars Equidist. Cylindrical), convergence = 0, so horizon terrain profile azimuths are now correct. 2. Visibility chart (.sightlineVisWrap): added user-select: none so dragging the timeline scrubber doesn't accidentally highlight text. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: horizon profile aspect ratio for geographic CRS + crosshair styling/lag - HorizonProfile.py: compute per-axis pixel scales (px_scale_x, px_scale_y) so the march direction accounts for longitude compression at observer latitude. For geographic CRS at 38°N, 1° lon ≈ 0.79 × 1° lat in meters; without this the march traces wrong physical angles, distorting azimuths. Also computes correct per-step physical distance instead of using the averaged pixel_scale. - Crosshair restyled: smaller (8px circle, 5px arms), lime green with black borders (box-shadow outline). - Crosshair converted from raw DOM element to Leaflet DivIcon marker. Leaflet handles positioning in its own transform pipeline, eliminating the lag that occurred when updating CSS left/top on the move event. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: add lime center dot at visible map center while sightline tool is open Small 6px lime green dot with 1px black border, always at 50%/50% of the map container (CSS-only positioning, no event tracking needed). Added on make(), removed on destroy(). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: update crosshair position immediately when sweepCenter is set Previously the crosshair only corrected its position on the next pan event. Now _updateCrosshairPosition() is called right after sweepCenter is stored for both static sightmap and batch/sweep completion. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: sightmap route security + batch limits + test fix (Devin Review) - Add SAFE_NAME_RE validation on target, obsRefFrame, obsBody to prevent directory traversal via SPICE kernel paths (matches /ll2aerll_bulk). - Add MAX_TIMES=200 cap on sightmap batch to prevent resource exhaustion. - Fix E2E test: batch response is a raw JSON array, not { results: [...] }. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: add error handler + headersSent guards on sightmap spawn Match ll2aerll_bulk pattern: handle child.on('error') and child.stdin.on('error') to prevent hung responses if Python fails to start. Add !res.headersSent guards on all response paths in the close handler. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: get_pixel_scale uses actual array rows instead of ds.RasterYSize After open_dem decimates a large DEM, gt[5] is scaled but ds still has the original RasterYSize. Using ds.RasterYSize with the decimated gt produces a wrong mid_lat for geographic CRS pixel scale. Now accepts dem_rows directly from dem.shape. Also: encodeURIComponent the chronice lng argument to match the other CLI args (consistency with unquote() on the Python side). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: correct East vector cross product order in sun_azel_at_cell cross(normal, north) yields West, not East. Changed to cross(north, normal) to match the batch version _sun_azel_batch. Currently unused at runtime but prevents future bugs. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor: remove dead code from sightmap.py Removed unused scalar functions that were superseded by vectorized equivalents: sun_azel_at_cell (replaced by _sun_azel_batch), is_nodata (replaced by _vectorized_is_nodata), geo_to_pixel (never called). Also removed the unused ds parameter from open_dem return value and _compute_bounds signature — ds was only kept alive for get_pixel_scale which no longer needs it after the dem_rows fix. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor: remove dead code from SightlineTool frontend SightlineTool.js: removed showSightlinemapLayers, showSweepLayers, refreshAllHeatmaps, _nextPow2 — all defined but never called. SightlineTool_Algorithm.js: removed the entire old client-side sightline algorithm (sightline, processUp/Down, mask, curveData, isNoData, compositeResults, calcHeight*, initializeGrids, perOctant) and their unused imports (jquery, F_, L_, G_). Only cumulativeVisibility is called externally; all other methods were from the pre-backend era and superseded by sightmap.py. SightlineTool_Graphs.js: removed _localNorthAngle, replaced by the geodesic _destinationPoint + _azimuthEndpoint approach. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: color picker/ramp dropdown clipping + reorder default colors Remove overflow:hidden from vstSightlineItem, vstSweepCard, and vstSweepCardsSection so absolutely-positioned color picker palettes and color ramp dropdowns are no longer clipped by their parent containers. Add border-radius to headers directly to preserve rounded corners. Bump vstColorPalette z-index from 100 to 10000 to match the ColorRampPicker popup z-index. Reorder MULTI_SOURCE_COLORS: yellow -> blue -> red -> green (swapped blue and red positions). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: improve LSMT inverse conversion precision from ~30s to <1s et2lst returns integer (hr, mn, sc) so one lunar second spans ~29 ET seconds. The old iterative loop converged to ±1 lunar second, giving ~30s UTC precision. Now uses binary search after the coarse loop to find the exact ET boundary where the second ticks over, narrowing to <0.5 ET seconds. Result is placed at the midpoint of the lunar second window for minimal round-trip error. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: color picker palette uses fixed positioning to escape overflow The Collapsible panel has overflow:hidden for its open/close animation, which clips the color picker dropdown. Changed the palette to position:fixed, computed from the swatch's bounding rect on click, so it escapes all overflow containers. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: revert fixed positioning, use overflow:visible on open panels Reverts position:fixed approach. Instead overrides overflow to visible on open Collapsible panels inside sightlineTool via [data-open] selector, so the color palette can extend past the panel boundary while keeping overflow:hidden during animations. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): implement 6 improvements to Sightline tool 1. Combine Playback and Composite Sweeps: always build both heatmap and atlas after sweep; mode switching is instantaneous without re-running the sweep. 2. Min/Max Distance options: add UI inputs and pass minDistance/ maxDistance through to sightmap.py ray-march kernel and HorizonProfile.py. Includes curvature-based early termination. 3. Better Color Ramps + Fix Transparency Bug: expose sweepColorRamps in admin config, reorder defaults, fix evalColor/evalColorWithStops discrete bin index calculation (Math.floor -> Math.round). 4. Draggable Horizon Profile Point: crosshair marker is now interactive and draggable; on dragend updates indicatorLastDragPoint and refetches horizon profile at the new location. 5. Vis Chart - Remove Gradients: remove gradient transition logic from drawVisibilityTimeline; uncertain regions shown as occluded. 6. Document Algorithms: add detailed algorithm documentation to sightmap.py and HorizonProfile.py headers. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: bump version to 5.1.1-20260611 [version bump] * fix: resolve merge conflict in configure/package.json Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): set mode before sweepShowAllFrames, add distance to horizon cache key - switchElementMode now sets sightlineMode='playback' before calling sweepShowAllFrames, which filters by sightlineMode. - Horizon profile cache now includes maxDist/minDist so changing distance parameters invalidates the cache and triggers a re-fetch. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): address 4 review issues 1. Default color ramps: add inferno + viridis to front of defaults; remove sweepColorRamps from all reference mission configs so they use the built-in defaults. 2. Mode switching: keep Results section open when switching between composite/playback (don't collapse if sweep data exists); call sweepShowFrame directly for the element when switching to playback. 3. Crosshair dragging: use Leaflet.Editable (enableEdit/disableEdit) instead of L.marker draggable option; listen on 'editable:dragend'. Map click handler now only triggers static sightline when element is in static mode - prevents unwanted resweep in playback mode. 4. maxDistance fix: backend route (utils.js) was not passing minDistance/maxDistance through to the Python sightmap.py stdin payload. Added both fields to payloadObj. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): hardcode 6 color ramps, revert crosshair to non-draggable 1. Color ramps: remove custom/configurable sweepColorRamps entirely. Hardcode exactly 6 ramps: - [transparent, color] - [transparent, color, transparent] - Inferno - Viridis - Red → Green (RdYlGn) - Black → White (Greys) 2. Crosshair: revert to original non-draggable behavior (interactive: false). Remove indicatorLastDragPoint from store and all references. Horizon profile and visibility chart now always use the current map center — they auto-update on pan end via the existing invalidateAndRefetch() call in _onPanEnd. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): reverse B&W ramp to White→Black, single color stop Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): anchor horizon profile to sweep center when available Revert the horizon profile to use sweepCenter when a sweep exists, falling back to the current map center when no sweep has been run. This keeps the horizon chart, entity arcs, and visibility timeline all consistent with the sweep observer location. Skip horizon invalidation on pan when anchored to sweep center to avoid unnecessary backend calls. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * perf(sightline): add timing instrumentation to sightmap pipeline Python backend (sightmap.py): - Timing for: kernel loading, SPICE az/el, DEM open, grid precompute, per-frame sun grid + march + tolist, json.dumps, total - Batch response now returns {results, _timing} with per-frame arrays - DEM/output dimensions logged for context Node.js route (utils.js): - Log total spawn-to-close time, JSON parse time, stdout size Frontend (SightlineTool.js): - Log API round-trip, grid parsing, heatmap compute, atlas build, total frontend processing - Console output tagged [Sightmap Timing] / [Sightmap Sweep] Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * TEMP: strip grid data from sightmap response for timing debug Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * perf(sightmap): cache frame-invariant data in batch mode Pre-compute and cache across all frames: - Coarse grid lat/lng (coordinate transform done once, not per-frame) - Bilinear interpolation weights (indices + weight arrays) - Ellipsoid geometry for az/el (cell positions, normals, north/east vectors) - Grid convergence angle for azimuthal projections Per-frame now only computes: - Source direction vector subtraction on ~119 coarse points - 3x bilinear weight-apply (fast matmul, no index recomputation) - sin/cos for direction on full grid Expected speedup: source_grid from ~116ms/frame to ~15-25ms/frame (~5-8x faster for 145 frames: ~17s → ~2-4s) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * perf(sightmap): eliminate full-grid trig via angle addition formula Instead of: bilinear interp sin/cos → arctan2 → +convergence → radians → sin/cos Now: bilinear interp sin/cos → multiply by cached sin/cos(convergence) Removes 6 numpy operations on the full 240K-cell grid per frame (arctan2, where, add, radians, sin, cos) and replaces them with 4 multiply + 2 add operations (cheaper element-wise math). Also computes coarse az as normalized sin/cos components directly from the dot products, skipping degrees/radians conversions on the coarse grid too. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): add resolution dropdown + restore grid data Add viewport-relative resolution control (1×, 0.5×, 0.25×, 0.125×) to the Display section. Default is 0.25× (medium). The scale factor multiplies the viewport's longest pixel dimension to produce maxOutputDim sent to the backend. - 1× = native (no decimation beyond viewport crop) - 0.5× = half viewport dims - 0.25× = quarter (default, balanced speed/quality) - 0.125× = eighth (fastest, coarsest) Server-side clamp raised from 800 to 4096 to support 1× on large viewports. Also restores grid data in sightmap responses (reverts the TEMP debug strip). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightmap): scale frame limit by resolution 1× (maxDim≥800): 256 frames 0.5× (maxDim≥400): 512 frames 0.25× (maxDim≥200): 1024 frames 0.125× (maxDim<200): 2048 frames Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor(sightmap): replace times array with startTime/endTime/stepSeconds Send 3 scalar values instead of potentially 2000+ timestamp strings. Backend generates timestamps internally from the range. - Frontend sends ISO timestamps (e.g. '2026-06-11T19:53:00Z') and stepSeconds (e.g. 60) - Node.js route validates the range and computes frame count for limit checking - Python parses ISO start/end, generates time strings with timedelta - Frame limit raised to 2048 max (at 0.125× resolution) - Shrinks request payload from ~100KB to ~500B for large sweeps Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): range circles, max distance infinity toggle, viewport padding - Draw dashed min/max distance circles on map when values are non-zero (orange for min, blue for max). Circles update on value change and center on sweep observer position when available. - Add ∞ toggle button on Max Dist row. When active, sends maxDistance=-1 to backend which skips viewport cropping and reads the full DEM (at the appropriate overview level for the resolution setting). - Add Tooltip on Max Dist label explaining viewport-only vs full DEM behavior. - Backend: when maxDistance > 0, pad viewport bounds by that distance in all directions so shadow-casting terrain beyond the visible extent is included. When maxDistance=-1 (infinity), viewport_bounds is set to None so the full DEM is read. - Store: add maxDistInfinity per-element field (default false). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: range circles use projected polygon, infinity mode preserves viewport resolution Range circles: - Replace L.circle (broken on custom planetary CRS) with L.polygon built from 64 vertices computed in projected coordinates via crs.project/unproject. Works correctly on polar stereo and any custom L.Proj.CRS. Infinity mode resolution fix: - Separate DEM read extent from output grid extent. When infinity mode is active (maxDistance=-1), the full DEM is loaded at overview resolution for shadow tracing, but the output grid is sized to the viewport subset only — preserving the same cell density as normal mode. - When maxDistance > 0 (finite padding), DEM read bounds are padded by maxDistance in all directions. Output grid still viewport-only. - Both compute_sightmap and compute_sightmap_batch support output_offset_row/col parameters that offset output grid cells within the loaded DEM. Coarse subgrid, precompute, and batch context all respect the offset so rays trace through the full loaded DEM while only evaluating viewport cells. - Add _compute_bounds_from_proj() to compute geographic bounds from projected viewport coords for correct response bounds. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor: replace min/max distance fields with DEM Extent dropdown Remove minDistance, maxDistance, maxDistInfinity fields and range circles. Replace with a simple 'DEM Extent' dropdown (Viewport / Full DEM) in the Display section, defaulting to Viewport. - Viewport: uses only the DEM visible on screen (fast) - Full DEM: reads the entire raster for distant shadow casting (slower, sends maxDistance=-1 to backend) Removes ~120 lines of range circle rendering code that is no longer needed. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * Revert "refactor: replace min/max distance fields with DEM Extent dropdown" This reverts commit b3d6cc503a170a522f48bdd7f5b21d723b9612e7. * Revert "fix: range circles use projected polygon, infinity mode preserves viewport resolution" This reverts commit 6b265aa823e9634e13d5857119826b174360d384. * Revert "feat(sightline): range circles, max distance infinity toggle, viewport padding" This reverts commit fd0c5c2262d9c2bccffa69102709be759333c1ad. * feat(sightline): add Shadow Reach LOD field for distant shadow casting Add a 'Shadow Reach' input (km) to the Display section that extends terrain loaded for shadow computation beyond the visible viewport. When set, the backend reads the viewport DEM at full resolution and a padded border region at a coarser COG overview level, composites them into a single array, and marches rays through the full composite while outputting only the viewport portion. - New open_dem_composite() reads viewport + low-res border - Output grid offset support in _precompute_grid_arrays, _compute_sun_grid, _precompute_batch_grid_context - Replaces minDistance/maxDistance with single shadowReach parameter - Tooltip explains the viewport-extension concept - Default 0 = viewport only (no performance impact) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): prevent OOM on large shadow reach, clamp to curvature, blur-only regen - Redesigned open_dem_composite(): single DEM read of the padded region at a managed resolution (scales working_dim by viewport/pad ratio, capped at 4× max_working_dim) instead of building a massive array at viewport pixel scale then upsampling. - Shadow reach clamped to planetary curvature limit: sqrt(2*R*h_max) where h_max=10km. For Moon (R=1737km) this caps at ~186km. - Shadow Reach input only triggers sightmap regen on blur (unfocus), not on every keystroke. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): Enter key triggers regen on Shadow Reach input Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): respect drag order for sightmap z-index, fix drop indicator position - After rendering a sightmap overlay, re-apply z-order based on the current element order in the store so the panel ordering is respected regardless of which sightmap finishes loading first. - Fix drop indicator: changed border-bottom to border-top so the line appears above the target element (matching where the drop will place the dragged item). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): position-aware drag-and-drop indicator (above/below) The drop indicator now shows border-top when hovering the upper half of a target element (insert above) and border-bottom when hovering the lower half (insert below). The drop logic uses the cursor's Y position relative to the element midpoint to determine placement. Applied to both SightlinePanel element cards and SweepSection cards. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): visibility timeline uses sightmap grid pixels, not horizon profile - Visibility chart now checks the observer's pixel in each frame's sightmap grid (lit=1/2 → visible, shadow=0 → occluded) instead of comparing source az/el against the horizon profile. - Observer pixel computed from projected coordinates (using CRS projection) when available, falling back to geographic bounds. - Replaces horizon profile interpolation in drawVisibilityTimeline with direct grid pixel lookup via centerVisible. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore(sightline): remove timing logs and fix graph slider performance - Remove all timing instrumentation from sightmap.py (timing dict, perf_counter calls, stderr serialization log, import time) - Remove _timing from sightmap API responses (single + batch) - Remove all console.log timing calls from SightlineTool.js - Fix graph time slider performance: _scrubToFrame was redrawing horizon + visibility canvases synchronously AND triggering the same redraws again via the scrub callback (sweepShowAllFrames → updatePlaybackFrame → requestAnimationFrame). Now _scrubToFrame just sets the index and calls the callback, letting updatePlaybackFrame handle all redraws in a single rAF. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): add distance-based fog shading to horizon profile Backend (HorizonProfile.py): - Track distance (meters) to the horizon point at each azimuth - Output now includes [az, el, dist_m] per sample Frontend (_drawHorizonCanvas): - Parse distance from profile data (backward-compatible if missing) - Replace uniform terrain fill with per-strip vertical fills - Each strip's opacity mapped via log scale from distance: close horizon = opaque, far horizon = transparent - Falls back to uniform fill if no distance data available Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): increase horizon profile fog opacity variation Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): horizon profile now refreshes after new sweep completes updatePlaybackFrame was using _horizonCache directly without validating it against the current sweep center. After a new sweep set a different sweepCenter, the stale cached profile was still drawn. Now routes through fetchAndDrawHorizon which validates cache keys (lat/lng/height) and refetches when the center has changed. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): add hover tooltip to horizon profile showing azimuth, elevation, and distance Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): increase horizon profile max radius from 5km to 50km 5km was too short to reach far crater rims on coarser DEMs, causing the horizon profile to report deeply negative elevation angles where the ray never found the actual skyline. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): increase horizon profile max radius to 250km Also raised the backend cap from 100km to 500km to accommodate. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * perf(sightline): add log stepping + early termination to horizon profile Ray march now uses logarithmic step size (1px near, ~11px at 2500px) so distant terrain is sampled more coarsely. Early termination stops a ray once the maximum plausible terrain peak (10km, minus curvature) at the current distance can't beat the already-found max elevation angle. Together these reduce samples from ~2500/ray to ~200-600/ray for 250km radius at 100m/pixel. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): add header with dual-handle log-scale horizon distance slider Adds a 'Sightline Graphs' title and a dual-handle range slider to the bottom panel header. The slider controls min/max horizon lookup distance on a log scale (1m–250km). Dragging either handle invalidates the cache and refetches the horizon profile with the new range. Default: 100m–250km. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): default horizon range to 1m–250km and add tippy tooltip Changed min distance default from 100m to 1m. Added tippy tooltip on the 'Horizon:' label explaining the dual-handle slider controls. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): increase crosshair circle and center dot radius by 1px Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): deduplicate visibility chart time ticks and add user-select:none When numTicks > frame count, multiple tick positions could round to the same frame index, producing duplicate labels (e.g. two 'Jan 14 12:00'). Now skips any tick whose frameIdx matches the previous one. Also added user-select: none to the time labels row. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): align visibility ticks with red slider position - Cap numTicks to frame count (no more ticks than frames) - Position ticks using frameIdx/(frameCount-1) — same formula as the red time slider — so they always line up exactly Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): set vstTimeStep width to 76px Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor(sightline): extract Sightline into dedicated backend module Move sightmap and horizon profile endpoints from API/Backend/Utils/ into a new API/Backend/Sightline/ module with its own setup.js, routes, and scripts directory. - POST /api/utils/sightmap → POST /api/sightline/sightmap - POST /api/utils/gethorizonprofile → POST /api/sightline/horizonprofile - private/api/sightmap.py → API/Backend/Sightline/scripts/sightmap.py - private/api/HorizonProfile.py → API/Backend/Sightline/scripts/HorizonProfile.py - Extract validateMissionsPath into shared API/validateMissionsPath.js - Update frontend calls.js and E2E tests to use new paths Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): update SPICE kernel relative path after script relocation The script moved from private/api/ to API/Backend/Sightline/scripts/, so the relative path to spice/kernels/ needs 4 parent traversals instead of 2. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * docs(sightline): update help with detailed algorithm descriptions - Document sightmap viewshed algorithm: source position via SPICE, DEM composite, tangent-plane projection, ray-march viewshed, output grid - Document horizon profile algorithm: per-azimuth ray march, elevation angle tracking, curvature correction, distance recording - Add parameter tables for both endpoints - Document performance methods: log stepping, early termination, managed resolution, batch streaming, frame limits - Update SPICE paths to reflect new spice/ directory - Update visibility timeline description (now pixel-based) - Add fog shading, hover tooltip, and range slider to horizon profile docs Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor(sightline): extract modules for Indicators, Export, Horizon, Visibility - Extract SightlineTool_Indicators.js (~689 lines): Az/El canvas gauges, sky dome, mini RAE - Extract SightlineTool_Export.js (~488 lines): PNG, GIF, CSV, Grid export functions - Extract SightlineTool_Horizon.js (~574 lines): horizon profile canvas, fog shading, tooltip - Extract SightlineTool_Visibility.js (~278 lines): visibility timeline bar chart - Extract RangeSlider reusable component to src/design-system/components/RangeSlider/ - SightlineTool.js reduced from 3082 to 1919 lines (thin delegates to new modules) - SightlineTool_Graphs.js reduced from 1532 to 981 lines (delegates to Horizon/Visibility) - Fix shadowReach isFinite validation bug (Devin Review feedback) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): invert color ramp to black→white, add horizon polygon overlay - Invert WhiteBlack color ramp to BlackWhite (black→white gradient) - Add faint horizon polygon on 2D map showing the horizon profile outline - Updates whenever horizon profile is fetched/redrawn - Removed when sightline graphs panel is closed - Very faint styling: white outline 0.35 opacity, fill 0.06 opacity Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): add horizon polygon toggle checkbox, increase polygon opacity - Add 'Polygon:' checkbox in graphs header bar (right of horizon range slider) - Default off; toggling on shows the horizon polygon overlay on the 2D map - Polygon is more visible: outline 0.6 opacity, fill 0.12 opacity, weight 1.5 - Checkbox state resets to off when graph panel closes - Uses existing mmgis-checkbox component pattern Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): thicker polygon border (weight 3), add tippy on Polygon label Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): composite hover uses bounds-based lookup instead of tile projection The _onCompositeHover function was using Globe_.litho.projection tile coordinates (topLeftTile, tileResolution) which don't exist in the sightmap data model. Replaced with bounds-based row/col computation using data._bounds [west, south, east, north] which is what the sightmap API actually returns. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): update azimuth lines on map pan/zoom Azimuth SVG overlay uses pixel coordinates from latLngToContainerPoint. When the map pans, these coordinates become stale but the overlay wasn't being redrawn. Now listens for 'move' events on the map while the graph panel is open and redraws the source azimuth lines on every move. Listener is removed on panel close. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): composite hover uses projBounds for projected CRS maps When the map uses a custom projected CRS, Leaflet e.latlng coordinates are in projected meters, not geographic degrees. The hover function now uses data._projBounds (projected) when in a projected CRS and data._bounds (geographic) when in standard geographic CRS. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): update polygon tippy text, add user-select:none to labels Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): project mouse coords before comparing to projBounds Leaflet's e.latlng gives geographic lat/lng even in projected CRS maps. For projected CRS (lunar south pole stereographic), we need to convert these to projected coordinates via crs.project() before comparing against data._projBounds. This was causing the hover to always produce out-of-range row/col on projected CRS maps. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * perf(sightline): progressive log2 stepping in sightmap ray march Replace fixed-step march with distance-based progressive stepping: step = base * max(1, log2(r+1)), combined with margin-based acceleration. Near the observer every pixel is sampled. At r=1000px the step grows to ~10x base; at r=25000px to ~15x base. This dramatically reduces iterations for high-resolution DEMs (e.g. 10m USGS) where rays can span 25,000+ pixels. Expected ~4-8x fewer samples per ray at distance with negligible accuracy loss (distant terrain must be very tall to affect the elevation angle). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * Revert "perf(sightline): progressive log2 stepping in sightmap ray march" This reverts commit 97d29eecbbf9c8e7ede869fa5e8bfa4745871ed0. * Revert "Revert "perf(sightline): progressive log2 stepping in sightmap ray march"" This reverts commit 975c772101aa0ebb4d161b82156100d77596e348. * perf(sightline): add in-march early termination + reduce working DEM size Two optimizations on top of the progressive log2 stepping: 1. In-march early termination (#4): at each sample, checks if the best-case terrain angle (MAX_TERRAIN_H minus curvature drop) at the current distance is below the source elevation. If so, no further terrain can block the source and the ray stops immediately. Most effective for high-elevation sources. 2. Match working DEM to output resolution (#5): reduced working_dim from 2x output to 1x output. Rays march through a ~400px array instead of ~800px, halving samples per ray and DEM I/O. Shadow accuracy is preserved since the output grid resolution is unchanged. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): add 2-minute timeout to sightmap.py Uses signal.SIGALRM on Unix and a threading.Timer fallback on Windows. On timeout, raises TimeoutError which is caught by the existing error handler and returns a JSON error response. Timeout is cancelled on successful completion. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): prevent server crash on oversized sightmap output Add a 256 MB stdout buffer cap with try-catch around string concatenation. If the Python process output exceeds the limit, the child process is killed and a 413 error is returned instead of crashing the Node.js server with RangeError: Invalid string length. This can happen at native resolution on large DEMs (e.g. 30993x30993) where the output grid JSON exceeds Node's string size limit. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): binary grid encoding, NDJSON streaming, delta compression, gzip Implement 7 output size reduction optimizations: 1. Binary encoding: uint8 grid → zlib compress → base64 (gridB64z field) 2. RLE via delta: batch frames encoded as XOR diffs (deltaB64z field) 3. Frontend decode: DecompressionStream → Uint8Array → 2D grid 4. Delta reconstruction: XOR previous flat with decoded delta 5. Precision: uint8 (0/1/9) instead of JSON number arrays 6. gzip: Node route compresses single-frame JSON; batch streams through zlib.createGzip() 7. NDJSON streaming: batch mode streams one JSON line per frame via chunked transfer Backend (sightmap.py): - compute_sightmap() returns gridB64z instead of grid array - compute_sightmap_batch() streams NDJSON lines to stdout First frame: full gridB64z, subsequent: deltaB64z (XOR vs prev) - Helper functions _encode_grid() and _encode_grid_delta() Node route (sightmap.js): - Batch: pipes child stdout through optional gzip to response stream - Single: buffers stdout, optionally gzip-compresses before sending - Content-Type: application/x-ndjson for batch, application/json for single Frontend (SightlineTool.js): - _decodeGridB64z(): atob → DecompressionStream('deflate') → Uint8Array → 2D grid - _applyDelta(): XOR reconstruct from previous frame flat array - Batch: uses fetch() with ReadableStream to parse NDJSON line-by-line - Progressive progress updates as NDJSON frames arrive during streaming Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): remove Python-side timeout, Node manages 3min single / scaled batch Python no longer has an internal 120s timeout that would kill long batch jobs. Node.js is the sole timeout authority: - Single-frame: 3 minutes (180s) - Batch: max(3min, frameCount × 30s), capped at 30 minutes Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): cap batch timeout at 5 minutes Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * docs: fix stale comment — batch timeout cap is 5 min, not 30 Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(sightline): replace CSV export with GeoTIFF for all modes - Static: single-band uint8 GeoTIFF (0=shadow, 1=lit, 9=nodata) - Playback: multi-band uint8 GeoTIFF (one band per frame) - Composite: single-band float32 GeoTIFF (0.0-1.0 visibility fraction) Uses the existing geotiff.js writeArrayBuffer with WGS 84 geographic CRS, ModelTiepoint and ModelPixelScale from the sightmap bounds. Output is a proper georeferenced TIFF openable in QGIS, ArcGIS, etc. Removes the old CSV export which caused RangeError on large grids (400x400 x 24 frames = 3.84M rows). A 24-frame multi-band GeoTIFF is ~4MB vs ~190MB CSV. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix(sightline): fix GeoTIFF export — use correct writeArrayBuffer input format Single-band (static/composite): pass flat TypedArray directly (not wrapped in array) so geotiff.js uses the flat code path with height/width metadata. Multi-band (playback): pass native 2D arrays [band][row][col] so geotiff.js can read dimensions from array structure. Verified all three modes produce valid GeoTIFFs via gdalinfo. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * revert(sightline): remove GeoTIFF exports, drop playback CSV export GeoTIFF exports had issues (geotiff.js bugs with float32/custom CRS). Reverts to original CSV exports for static and composite modes. Removes the playback CSV export entirely (caused RangeError on large grids). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: bump version to 5.1.2-20260615 [version bump] * feat: Phase 1 plugin restructure — unified /plugins/ directory - Move 16 tools from src/essence/Tools/ → plugins/core/tools/ - Move 13 backends from API/Backend/ → plugins/core/backend/ - Move 1 component from src/essence/Components/ → plugins/core/components/ - Add plugins/core/plugin.json manifest - Implement discoverPluginsUnified() three-level hierarchy scanner - Update updateTools.js, setups.js, resolve-plugin-deps.js to use unified scan - Update tool/component config.json paths for new locations - Add plugins/ to webpack babel-loader include - Replace 6 gitignore patterns with /plugins/* and !/plugins/core/ - Update AGENTS.md, CONTRIBUTING.md for new plugin structure - Update test helpers and specs for new layout Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: correct relative require paths in moved backends Backend files moved from API/Backend/ to plugins/core/backend/ need updated relative paths to reach API/ modules (logger, connection, etc). Also fix JSDoc comment containing '*/' in glob pattern that broke parser. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: bump version to 5.1.3-20260615 [version bump] * fix: escape glob pattern in JSDoc comment that broke parser The pattern 'plugins/*/tools/' in a block comment contains '*/' which prematurely terminates the /* */ comment block. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: correct rootDir path depth and plugin.json version - utils.js and sightmap.js rootDir needs 5 levels of ../ (not 4) to reach repo root from plugins/core/backend/X/routes/ - Sync plugin.json version with package.json (5.1.3-20260615) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: bump version to 5.1.4-20260616 [version bump] * fix: update relative import paths for plugin directory restructure All tools and components moved from src/essence/Tools/ and src/essence/Components/ to plugins/core/tools/ and plugins/core/components/ in the Phase 1 restructure. This commit updates ~250 relative import paths in those files so they resolve correctly from the new directory depth. Also fixes: - mmgisAPI LegendTool import (now points to plugins/core/tools/Legend/) - missionTemplates test require path (now plugins/core/backend/Utils/) - setups.js and resolve-plugin-deps.js reverted to unified scan only Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: correct SPICE kernel path depth in sightmap.py and address review findings - sightmap.py PATH_TO_KERNELS: needs 5 '../' levels from new location (plugins/core/backend/Sightline/scripts/ is 5 deep, was 4) - plugin.json version: sync to 5.1.4-20260616 matching package.json - New Tool Template: move to plugins/core/tools/ with updated imports Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat(Phase 2): Standardize plugin manifests, split backend lifecycle, co-locate tests - Rename config.json → plugin.json for all tool/component plugins - Split backend setup.js into plugin.json (metadata) + plugin.js (lifecycle) - Add Phase 2 manifest fields: uuid, id, version, type, tier, overridable, aliases, engines, peerDependencies - Update pluginValidation.js with new field schema and type checks - Enforce overridable:false in updateTools.js and setups.js - Add engines.mmgis compatibility check using semver - Implement semver-aware dependency conflict resolution (semver.intersects) - Add peerDependencies validation via checkPeerDependencies() - Simplify resolve-plugin-deps.js backend dep reading - Move tool E2E tests into plugins/core/tools/X/tests/ - Move backend API tests into plugins/core/backend/X/tests/ - Update Playwright config to scan both tests/ and plugins/**/tests/ - Update CONTRIBUTING.md with Phase 2 manifest documentation - Update test fixtures from config.json to plugin.json - Add unit tests for Phase 2 validation, semver resolution, and peerDeps Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * chore: remove /notes directory Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: overridable check inspects registered plugin, not incoming; fix config.json doc reference - setups.js: Store setupManifests map to track already-registered plugin's manifest; check that when an override is attempted (matches updateTools.js pattern where registry[name].overridable is checked) - CONTRIBUTING.md: Fix remaining config.json reference to plugin.json Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: Phase 3 — Plugin CLI and git-based registry system - plugins/plugin-cli.js: CLI tool with list, install, remove, enable, disable, update, validate, deps, info, registry commands - plugins/plugin-registries.json: git-based registry configuration - plugins/plugin-state.json: enable/disable state (gitignored) - Integrate plugin-state.json into discoverPluginsUnified() to skip disabled non-core plugins during discovery - Core plugins are protected from removal and disabling - npm run plugins maps to the CLI - plugins/README.md: full user-facing documentation - plugins/AGENTS.md: AI agent context for the plugin system - CONTRIBUTING.md: added Plugin CLI section - 17 unit tests for CLI commands and state integration Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: address Devin Review findings for Phase 3 - discoverPluginsUnified: validate plugin-state.json has 'plugins' key to prevent TypeError when state file is valid JSON but lacks the key - plugins/README.md: fix tier values (core/community/private, not extended) - plugins/README.md: display_name is not required for tools - plugins/AGENTS.md: fix tools require name+paths, not display_name - Add test for state file without plugins key Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: CONTRIBUTING.md template walkthrough references setup.js instead of plugin.js Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: CLI deps command uses claim.entry for pip/conda conflicts, not claim.version Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: add description to backend KNOWN_FIELDS for consistency Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: mergeNpm picks highest-lower-bound range; docs mark type/version as Recommended - mergeNpm now sorts compatible ranges by semver.minVersion() descending to pick the most restrictive range, not insertion-order last-seen - AGENTS.md and README.md updated to mark type and version as Recommended to match actual validation behavior (optional-but-typed) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: support "version": "core" — auto-resolves to MMGIS version Core plugins now use "version": "core" in their plugin.json manifests. The CLI resolves this to the current MMGIS version from package.json when displaying plugin info (e.g. "5.1.4-20260616 (core)"). This eliminates the need to manually sync plugin versions with MMGIS releases — core plugin versions are always the MMGIS version by definition. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: add author, license, repository, keywords fields to plugin manifest - Added to COMMON_FIELDS in pluginValidation.js (no warning for these) - All 31 core plugin.json files: author=NASA-AMMOS/MMGIS, license=Apache-2.0, repository=https://github.com/NASA-AMMOS/MMGIS - CLI info command displays author, license, repository, keywords - README.md manifest table updated with new fields Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: CLI visual overhaul + fix Devin Review bugs CLI visual improvements (zero dependencies): - ANSI colors throughout (cyan plugin names, yellow versions, green/red status, dim metadata, bold headers) - Box-drawing layout for 'info' command - Column-aligned output for 'list' command with summary bar - Step indicators [1/3] for install/remove/update - Colored help with version display - --no-color flag (also respects NO_COLOR env per no-color.org) - --json flag for machine-readable output (list, info) Bug fixes (from Devin Review): - checkPeerDependencies now resolves "version": "core" to the actual MMGIS version before semver checks (was silently skipped) - cmdDeps now includes conda dependencies alongside pip Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: revert info to flat layout, add type headers to list - info command: back to simple labeled rows with colors (no box-drawing) - list command: group plugins under type sub-headers (Tools, Backend, Components) with per-type colors (cyan, magenta, blue) - Update test assertions for new output format Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: use high-contrast colors for dark/light terminal support Replace blue (34) and magenta (35) which are hard to read on dark backgrounds. New palette uses only high-contrast ANSI colors: - Tools: cyan, Backend: yellow, Components: green - Repository URLs: cyan instead of blue - Registry URLs: white instead of blue Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: add webpack aliases for plugin-friendly imports Add 5 webpack resolve aliases so tools/components can import shared modules without fragile relative paths: @basics → src/essence/Basics/ @essence → src/essence/ @design → src/design-system/ @pre → src/pre/ @external → src/external/ Before: import L_ from '../../../../src/essence/Basics/Layers_/Layers_' After: import L_ from '@basics/Layers_/Layers_' Also adds jsconfig.json for IDE go-to-definition support. Refactors all 233 deep relative imports across 49 tool/component files. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: local install copies by default, --link for symlink Local path installs now copy the directory instead of symlinking. This avoids Windows EPERM errors with symlinks. npm run plugins -- install /path/to/plugins # copies npm run plugins -- install --link /path/to/plugins # symlink With --link, if symlink fails (EPERM on Windows), falls back to junction automatically. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor: consolidate plugin docs, remove legacy templates Merge plugins/AGENTS.md into plugins/README.md with full plugin templates (tool, backend, component). Remove: - API/Backend/setupTemplate.js (template now in README) - plugins/AGENTS.md (merged into README) - plugins/core/tools/New Tool Template.js (template now in README) Add deprecation warnings in discoverPluginsUnified() when plugins have legacy config.json or setup.js instead of plugin.json/plugin.js. Update CONTRIBUTING.md, docs, and .knowledge to reference new locations. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor: rename discoverPluginsUnified to discoverPlugins, improve warnings Remove legacy two-level discoverPlugins() (no callers remain). Rename discoverPluginsUnified() to discoverPlugins() across all callers, tests, and docs. Add deprecation warnings to CLI discoverAll() for config.json/setup.js. Add flat-repo structure warning during install when no tools/backend/ components subdirectory is found. Fix stale references: updateTools.js Kinds error message (config.js -> plugin.json), Development.md paths (API/Backend -> plugins/core/backend, setup.js -> plugin.js). Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: add activate command, auto-activate on plugin changes, fix backend engines check Add 'activate' CLI command that regenerates src/pre/tools.js and src/pre/components.js without a full webpack build. Auto-called after install, remove, enable, disable, and update commands. Add engines.mmgis compatibility check to API/setups.js for backend plugins, matching the existing check in updateTools.js for tools and components. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * refactor: activate prints diff only, suppresses verbose logger output Instead of dumping every loaded tool/component, activate() now compares src/pre/tools.js and src/pre/components.js before and after regeneration, printing only added/removed entries. Logger console output is suppressed during the regeneration step. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: color-code 'no changes' in activate based on context Yellow when changes were expected (install/remove/enable/disable/update), dim gray for standalone activate command. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: red 'Discovered 0 plugin(s)' on install, green otherwise Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: restore console output if activate throws (try/finally) Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: add 'create' command to scaffold new plugins Usage: npm run plugins -- create <tool|backend|component> <Name> --container <name> Scaffolds plugin.json (minimal required fields), entry point, CSS, and test spec. Auto-activates frontend plugins after creation. Supports core and external containers. Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * feat: --json flag for all CLI commands + create scaffold command - --json now works on all commands (install, remove, enable, disable, update, activate, validate, deps, create) — not just list/info - create command scaffolds tool/backend/component with plugin.json, entry point, CSS, and test spec; requires --container flag - activate() returns diff data and accepts silent mode for JSON consumers Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * docs: update README with create command, --json for all, --container flag Co-Authored-By: tariq.k.soliman <tariqksoliman@gmail.com> * fix: puzzle-outline fallback icon, tool header scaffold, Configure page CLI pointers - Default icon: puzzle-outline fallback in Tools.js, Components.js, ToolModal.js - Scaffold: tool template includes mmgisToolHeader with close button - Scaffold: plugin.json includes defaultIcon: puzzle-outline - Configure page: replaced outdated 'Custom Tools/Components' cards with Plugin CLI usage pointers - Fix: typeof null guard in pluginDiscovery.js (Devin Review) - Docs: path convention comment in updateTools.js (Devin Review) - Recreated plugins/AGENTS.md with current state Co-Authored-By: tariq.k.soliman <tariqksolim…
v2.9.0 (#424) * Make sure to remove time tab in time disabled * IFrame examples * #279 Time Improvements (#280) * Configure global start end time, Partial local vector time filter * #279 Time Improvements * InfoTool checks for links in metadata and renders anchor tags * Fix environment variable in sample.env for enabling websockets (#281) * Fix environment variable in sample.env for enabling websockets * Update docs * Bug Fix: Don't drawing uncertainty ellipse if either axis is 0 * ts-282 Composite Time Tiles, Time Tiles with range (#283) * Update Time Tile path structure * #285 init-db (#286) * #290 Upgrade config page jquery, fix i closing tag (#291) * #292 Controlled Time Layers only make initial query, refactored to remove a conflicting setTimeout (#293) * Fix crash when Time is completely disabled * #296 Fix WMS url query (#297) * #298 TimeUI Pan and Zoom and Improvements (#299) * #298 Early TimeUI Pan Zoom * #298 Pan, zoom, steps, modes, mostly done * TimeUI Improvements and verifications * #298 Fix Point Play bug * #298 Final Touchups * Make marker bearings projection-aware * Update Docs, Add redundant stopGuests check * Fix Invalid Date when using deep linked timestamps, improve mmgisAPI docs * Fix typo * #294 SameSite None env and login improvements (#302) * #294 SameSite First pass * #294 Fix session and use postgres as session store * Use a default session db port * #294 Regenerate session on login failure too * Add feature to reconnect WebSocket from client to server (#295) * Add materialize-css package * Update WebSocket on client side * Double reconnect attempt interval every time websocket reconnection attempt fails * Update package-lock.json * Move toast to be ~5 pixels from the right side * Only show 'Successfully Connected' toast if websocket connection has previously failed * Fix toast display time * #304 Fix bug in latest too * Layer UUIDs (#308) * #306 UUIDs part 1 * #306 Layer UUIDs UI touchups, still missing Config API * #306 Layer UUIDs for Config API * #309 Controlled Raster for initial query only (#311) * Fix function call order bug in addNewLayer function (#310) * Account for non-uuid deep link names * Fix toggleLayer bug (#260) * Fix Node 18+ build (#312) * #317 Fix time vectors not applying correctly (#319) * Fix bug where initially on annotations features have no click events * Add function to overwrite elements displayed in LegendTool (#320) * Add function to overwrite elements displayed in LegendTool * Add more checks to overwriteLegends function * Add overwriteLegends to mmgisAPI * #322 Add ROOT_PATH, Deprecate PUBLIC_URL (#323) * #322 ROOT_PATH, doesn't work fully for /configure yet * #322 ROOT_PATH for runtime subpaths * #322 Remove PUBLIC_URL and fix ROOT_PATH css * Update README.md * Update README.md * #324 Configurable Context Menu Actions (#325) * #324 Coordinates Config Tab Raw Variables * #324 Configurable context menu actions * Add docker build workflow * Make sure user input UUIDs through REST API are unique (#326) * Make sure user input UUIDs through REST API are unique * Fix overwriting existing UUID issue * #327 Assorted UI Improvements (#328) * #313 Expose as much as possible to the mmgisAPI (#330) * #313 Add Formulae_ * #313 More F_ docs * #313 Finish up F_ docs * #313 mmgisAPI addLayer and removeLayer * #313 Add logged_in endpoint and early Backend API docs * #331 Websocket aware configure page (#332) * #331 Websocket aware configure * Remove console.log * Update addLayer and removeLayer endpoints in REST API (#337) * Working on updating REST API * Do not update config if there are duplicate or bad UUIDs * Update docs * #303 Photosphere Targets and Target Pairing (#340) * #303 Pairings attachments, photosphere targets not working yet * #303 AzEl computed markers in Photosphere, deleted old docs * Add back in swagger docs * #303 Use originOFfset * Refactor with the uuid upgrade, Update docs * #341 Export working config, disable override (#342) * Add xml2js and proj4 packages. (#343) * add xml2js * Fix DrawTool Upload Loading style * Draw Edit properties * #335 Feature Request: GetMissions (#345) * #335 /configure/mission full param * #335 touchup docs * Fix typo * #346 Deep Link additional encodeURI * Fix missing uuids from configure * WMS TILESIZE param, features without uncertainty properties won't draw in Map, github link of docs page * Fix clearOnMapClick function for layers with no fill (#348) * Fix angleUnit on image attachment * Optional Info button for projects that need a link to project info #350 * Support existing az el properties for pairing attachments * Fix bug in Photosphere where points with matching names get stuck * Add dontFloor to mod function * Updates to info and help buttons - Hide info and help icons if a url is not configured - Default the info button to be off in the configuration - Change to info icon with a circle * #352 [New Feature]: DrawTool - Feature Property Templates (#353) * #352 Templating 1 * #352 Templating 2 * #352 Templating 3 * #352 Update Modal for templating * #352 Migrate DrawTool Templating * #352 Templating touchups * #354 Add MAIN_MISSION ENV (#355) * Do not append time parameters to wmts tile layers * Fix configure layer cloning * Remove typo ; in MAIN_MISSION env * Remove outdated firefox-specific css fix * Add blob to default-src csp * #356 DrawTool Templating - Incrementer field (#357) * #358 - DrawTool - Grouping editing should only update changed template fields (#359) * #360 Expose all endpoints to longtermtokens (#362) * #363 Support file_owner in webhook body. Include body for onFileDelete (#364) * Fix layer uuids showing up in bottom-right coords * Expose ToolController_ * #366 Don't copy uuid on layer clone (#367) * #369 Refresh vector layers, also some WEBSOCKET_ROOT_PATH (#370) * #368 Fix Websocket usage with ROOT_PATH (#373) * #369 Refresh vector layers, also some WEBSOCKET_ROOT_PATH (#370) (#371) * #368 Websockets use ROOT_PATH * #368 Fix configure websocket * Fix bad Configure/websockets json writing * Fix time memory leak from improper cleanup of tooltips and slider * chore(config): Inline import from same namespace (#375) * #378 LegendTool as Popup (#379) * #378 Separate legend tool * #378 Legend formatting, reacts to layer toggles * #380 DrawTool - Fix error saving templateless features (#381) * Reset stroke styles too * fix(imports): destructure modules (#384) * chore(naming): use standardized name (#385) * Add event listener for toggleSeparatedTool in MMGIS API (#388) * Prioritize Map splitscreen size when resizing * #372 calls to updateVectorLayer and appendLineString cause selected feature to be deselected when using info tool (#389) * #372 Reselect feature on updateVectorLayer and appendLineString * #372 Non-async updateVectorLayer, fix Pairings update * Remove description transition * Persist active feature on appendLineString * #390 InfoTool - Clicks Intersect Polygons (#391) * #387 DrawTool - Group Editing (#392) * #387 DrawTool - Group edit files Part 1 * #387 DrawTool - Group edit files Part 2 * DrawTool / Group Editing - Let other users save changes to features * Added an option to the LegendTool to automatically display on start * LegendTool needs to have hasVars set to true for the last commit * Added max-width to legend to prevent long text from filling screen * Added a .nvmrc file with node version * Added ellipsis and title for overflow legend text * #386 Local Login Issue (#396) * #386 Add setLoginToken to mmgisAPI * #386 Fix variable path * #386 SKIP_CLIENT_INITIAL_LOGIN env + mmgisAPI.initialLogin * #383 Support raster option on gdal2tiles_3.5.2/gdal2customtiles (#397) * #383 gdal2tiles_3.5.2_v2 for raster support * #383 Cleanup gdal2tiles scripts and improve documentation * #383 gdal2customtiles extentworld working, overview tiles not working * #383 Fix gdal2customtiles rasters with different pixel scales * gdal2customtiles - fix width calc, still slight offset * gdal2customtiles raster - fix rounding issues * Fix mmgisAPI featuresContained when a failed to load layer = null * #399 Crashes if Layers are valid JSON but not valid GeoJSON (#400) * #399 Add geojson validater to map * #399 allow overloaded coordinates in geojson for extended geojson * #401 Extend CSSO timeout check to main client (#402) * #401 Main site timeout notification part 1 * #401 Main site timeout notification part 2 * #401 Main site timeout notification - warn at 20 min * #403 rightClickMenuActions - Polygons and WKT (#404) * #403 - Bugfixes and rightclick finds features * #403 ContextMenu, Actions on features, WKT link populate * #405 DrawTool - Template - Incrementer - Move to backend (#406) * Mini fix to allow [] as empty geojson layer data * Fix DrawTool Incrementer self collision bug * Convert layer data [<FeatureCollection>] to geojson * #407 Click intersects on Query and Draw layers (#408) * #407 Include Query layers in click intersections * #407 Support intersects on DrawTool layers too * Allow the legend tool to be right justified on the screen * #409 Added example config in description for LegendTool * #410 Draw Tool - Time Integration (#411) * ts-410 DrawTool - Temporal part 1 * #410 DrawlTool - Temporal Integration * #410 Clearer DrawTool Template Date Start/End Icons * #412 Add Configuration Option to set TimeUI Extent (#413) * #414 IdentifierTool works again with human layer names (#415) * #422 Removed event blocking popup functions (#423) * Bump to 2.9.0 --------- Co-authored-by: ac-61 <ac-61@users.noreply.github.com> Co-authored-by: Joe Roberts <joe.t.roberts@jpl.nasa.gov> Co-authored-by: Even Stensberg <evenstensberg@gmail.com>
PreviousNext
