Critical:
- Remove :protected from WeatherCache ETS (conflicted with :public)
- Register Aprsme.ReplayRegistry in supervision tree
- Route :continue_replay dead message to :start_replay handler
- Add 5000ms timeout to unbounded :rpc.call calls
- Fix falsy lat/lng check rejecting valid 0 coordinates
Medium:
- Fix live_reload pattern (temp_web -> aprsme_web)
- Remove duplicate ecto_repos config
- Make DB SSL configurable via DB_SSL env var
- Track sizeCheckTimeout on self for cleanup in destroyed()
- Wrap pushEvent calls in try/catch
- Remove unused size variable in marker cluster icon
- Capture touch coords at touchstart instead of stale TouchEvent
- Demote console.log to console.debug in production code
- Add catch-all to normalize_bounds preventing GenServer crash
Style:
- Add missing @impl true annotations in is.ex
- Improve migration failure error message
- Use textContent instead of innerHTML for error messages
- Use proper type for longPressTimer instead of any
Add a client-side userOpenedPopupMarkerId lock that's set instantly when
a user manually opens a popup (via click, OMS spiderfy click, or long-press)
and cleared when the popup closes. All programmatic openPopup paths
(highlight_packet, addMarker openPopup flag) now respect the lock.
Closes the network round-trip race where new packets arriving between
the user's click and the server's marker_clicked ack could steal the popup.
The coord-validation and antimeridian-unwrap logic added in the last
pass had been duplicated across map.ts, trail_manager.ts, and
info_map.ts. Move both into map_helpers.ts and import from there so
there is one authoritative implementation.
- Heatmap: filter points with invalid coords or out-of-range intensity
so malformed rows can't distort the layer.
- InfoMap: validate coords for range (not just NaN) and use an epsilon
when comparing positions, so float noise doesn't cause needless
re-renders or accept bogus data.
- TrailManager: getTrailCenter returns null for empty input instead of
the ambiguous (0, 0) null-island; callers guard accordingly.
- Normalize longitude delta in trail_manager Haversine so wraps across
180/-180 no longer compute a near-global distance and spuriously
split the trail into short-segment-filtered pieces.
- Unwrap longitudes before building segments and drawing polylines in
both the per-station TrailManager and the show_trail_line handler so
a date-line crossing renders as a short hop instead of a line that
wraps around the world.
- Raise maxHopDistance (10 -> 100 km) and drop minSegmentPoints (3 -> 2)
so legitimate fast movers (aircraft, balloons) and short valid hops
are drawn instead of being dropped.
- Unbind mousemove/mouseout handlers on a trail before removing it so
queued events can't fire against discarded state.
- Fix self.isValidCoordinate TypeError in updateMarker (function is
module-level, not on the hook) and add coord validation to
show_trail_line.
- Null boundsTimer after clearTimeout for cleanup consistency.
- RegexCache: change ETS table from :public to :protected for consistency
with other tables, preventing uncontrolled writes bypassing GenServer
- MobileChannel: ensure_float now returns nil instead of the original
unparsed string on parse failure, so validate_bounds properly rejects it
- map.ts: remove dead commented-out localStorage code
- map.ts: use instanceof HTMLAnchorElement instead of unsafe type cast
in popup navigation handler
- TrailManager: add destroyed flag to prevent stale RAF and debounce
callbacks from firing after destroy, and clean up hover timer on destroy
https://claude.ai/code/session_01Ps7Zq3wiBur1RtRKN7JM6X
Increase long-poll fallback timeout to 5s for mobile networks, add
visibility change listener for proper reconnection on app foreground,
and remove backdrop blur that stalls network operations on iOS Safari.
Only the 7 SVGs actually used are vendored into priv/heroicons/.
Also fixes the icon component path resolution (was missing the 24/
size directory) and corrects hero-x-mark/hero-map-pin icon names
that included an erroneous hero- prefix.
- Add trailLine state to MapHook
- Implement show_trail_line event handler with Leaflet polyline
- Implement clear_trail_line event handler
- Clear trail line when switching to heat map or markers
- Style trail line with blue color, 3px weight, 0.8 opacity
Convert app.js, info_map.js, error_boundary.js, and time_ago_hook.js
to TypeScript with proper typing for Phoenix hooks, Leaflet, DOM
interactions, and timer management. Update esbuild hex package to
~> 0.10 and binary to 0.25.4. Entry point changed to app.ts.
- Remove CSS `bottom: 50px !important` on popups that pushed them too
high above markers
- Hide trails when markers are clustered (zoom < 11) so trails don't
point to invisible markers inside cluster bubbles
- Fix locate button overlap with zoom controls and keep it always
light mode
- Remove duplicate slideover/toggle CSS from layout files (single
source of truth in component)
- Remove dead CSS classes and standardize breakpoints to 1024px
- Add isDestroyed guards on async callbacks to prevent race conditions
- Escape callsigns in marker HTML to prevent XSS
- Replace Leaflet _container private API with getContainer()
- Clean up about page placeholder text
Replace trail color palette with bold, high-saturation colors that avoid
yellows, oranges, and grays (common road colors on map tiles). Interleave
warm/cool tones so consecutive color assignments are maximally distinct.
Change color assignment to prefer globally unique colors first, falling
back to proximity-based deconfliction only when all 15 colors are in use.
- Replace N+1 get_latest_packet_for_callsign calls in RF path loading
with single get_latest_packets_for_callsigns batch query (DISTINCT ON)
- Change batch packet processing to collect marker removal IDs and push
one remove_markers_batch event instead of individual remove_marker calls
- Add callsignIndex reverse map (callsign_group → Set<markerID>) for O(1)
lookup in new_packet/new_packets handlers instead of scanning all markers
- Server tracks callsign replacements in prepare_packet_state, sends
convert_to_historical list so JS skips full markerStates scan
- Historical packets get red dot HTML server-side in build_minimal_packet_data
instead of JS createMarkerIcon generating it
- Add callsign_group field to all packet data output; server pre-sorts
by group with chronological order so JS iterates directly
- Normalize heat map intensity in Clustering.cluster_packets (min/50, cap 1.0)
instead of JS doing it per-point
- Remove buildPopupContent JS fallback and unused escapeHtml import;
server already provides popup in all data paths
- Batch real-time packet push_events: collect marker data across the
packet batch and send a single "new_packets" WebSocket message instead
of one per packet. JS handler processes all live→historical conversions
before adding new markers to avoid intra-batch conflicts.
- Batch RF path station queries: replace N+1 get_latest_packet_for_callsign
calls with a single DISTINCT ON query selecting only callsign + lat/lng.
- Add read_concurrency: true to streaming_packets_subscribers and :aprsme
ETS tables (read-heavy, rarely written).
- Remove dead GC code in PacketConsumer (threshold > 1000 unreachable with
max batch_size of 100).
Historical loading overrides all markers to historical:true, including
the most-recent marker. When a new live packet arrives, the conversion
check only looked for non-historical markers, missing the existing
"current" marker. Broaden the check to also convert markers with
is_most_recent_for_callsign set.
Use mousemove instead of mouseover so the RF path updates as the
cursor moves along the trail. Send the mouse position rather than
the nearest-position's coordinates so the path line anchors where
the user is actually hovering. Debounce and deduplicate events to
avoid flooding the server.
The slideover toggle button now uses a fixed light theme (white bg,
dark arrow) so it's always visible on the map regardless of OS dark
mode. Removed prefers-color-scheme overrides since the button overlays
the always-light map tiles.
Fixed map not resizing when closing the sidebar — the #aprs-map div
has phx-update="ignore" so server-rendered classes never updated.
Now uses LiveView JS commands (JS.toggle_class + JS.dispatch) to
toggle classes client-side and trigger map.invalidateSize(), replacing
the custom push_event/handleEvent JS handler.
parse_rf_path was filtering out all paths containing qA* constructs,
which is virtually every APRS-IS packet. The qA* element delimits the
RF path from the APRS-IS metadata — it should be split on, not
discarded. Now properly extracts digipeaters (before qA*) and the
igate (after qA*), while filtering APRS aliases like WIDE/RELAY/TRACE.
Also adds hover handlers to trail polylines so hovering anywhere on a
call's track shows the RF path, not just on the dot markers.
- Switch PacketProducer buffer from list to :queue for O(1) overflow handling
- Add cached leadership check via :persistent_term to eliminate GenServer.call
bottleneck in packet distribution hot path
- Simplify packets_live coordinate extraction from 70 lines of nested
conditionals to helper functions (extract_coordinate/2, format_coordinate/1)
- Remove debug logging from hot paths (packets.ex queries, PacketDistributor,
app.js console.logs)
- Remove sobelow dependency (false positives blocking commits)
- Add 25 new tests for PacketProducer, coordinate helpers, and cached leadership
Replace all DaisyUI component classes with pure Tailwind CSS utility
classes following TailwindUI conventions. Use dark: variants for dark
mode instead of DaisyUI's data-theme semantic color system.
- Remove DaisyUI plugin and theme vendor files
- Configure Tailwind v4 custom dark variant for data-theme attribute
- Migrate all components: cards, tables, badges, buttons, forms,
navigation, dropdowns, modals, alerts, star ratings, spinners
- Update all auth pages (login, register, settings, password reset,
confirmation) to TailwindUI card layout
- Update content pages (about, API docs, status, packets, bad packets,
info, weather) to TailwindUI patterns
- Replace opacity-based text styling with semantic gray colors
- Update test assertions to match new class names
- Remove BodyClassHook: use CSS :has() selectors on data-map-page attribute
instead of JS toggling body.map-page class
- Remove ResponsiveSlideoverHook: pass viewport_width via LiveSocket connect
params and determine initial slideover state server-side
- Convert theme selector from data-set-theme attributes with DOMContentLoaded
listeners to JS.dispatch("phx:set-theme") with a single event handler
- Fix buggy reRenderAllCharts that incorrectly used `new MapAPRSMap()` as a
Map constructor; charts now re-render via themeChanged event directly
Widen nearbyDistance, spiderfyDistanceMultiplier, and foot separation
to accommodate 120x32px marker labels that were overlapping when
spidered at close zoom levels.
- Add minimum movement threshold (100m) to skip stationary stations
- Add max hop distance (10km) to break trails at igate-hopping artifacts
- Require 3+ points per segment to filter noise from 2-point jumps
Add isNaN() checks in addition to isFinite() to catch NaN values before
they reach Leaflet's polyline renderer. This prevents 'can't access
property x, h is undefined' errors when invalid coordinates slip through.
Generated with Claude Code https://claude.com/claude-code
- Add extractCoordinate helper to recursively extract coordinates
- Handle coordinates that come as nested arrays like [[lat]]
- Log raw coordinate values when validation fails for debugging
- Prevents Invalid coordinates errors in console
This handles edge cases where coordinates are wrapped in arrays
before being passed to the marker creation code.
- Check if data object exists before accessing properties
- Validate lat/lng are numbers, not just truthy values
- Prevents TypeError when clustering tries to access undefined coordinates
- Fixes: Error adding historical packet: TypeError: can't access property x of undefined
- Set websocket timeout to 60000ms in endpoint configuration
- Add timeout option to LiveSocket client configuration
- Fixes timeout errors during initial map load with large packet count
- Remove duplicate time formatting function in components.ex
- Use existing time_ago_in_words from TimeHelpers instead of custom format_time_ago
- Remove unnecessary Float multiplication in format_coordinates
- Changed Float.round(lat * 1.0, 4) to Float.round(lat, 4)
- Fix TypeScript type safety issues
- Add pendingMarkers to LiveViewHookContext interface
- Add initializationTimeout property for proper cleanup
- Add cleanup for map initialization retry timeout
- Prevent memory leak by clearing timeout in destroyed() method
- Format code with mix format
All changes maintain backward compatibility while improving code quality.
- Check if markerClusterGroup is ready before removing markers
- Verify _map and _topClusterLevel exist in cluster group
- Add try-catch blocks to handle removal errors gracefully
- Clean up tracking maps even if layer removal fails
- Prevents 'can't access property "_childClusters"' errors
- Add marker queuing system for markers that arrive before map is ready
- Check map._container and getZoom to ensure internal structures exist
- Process pending markers after map.whenReady() callback
- Fix typo: bindTooltip -> bindTooltip
The errorTileUrl was set to a base64 data URL of a transparent 1x1 PNG,
which caused browsers to fail when trying to use it as a tile replacement.
Removing this option allows Leaflet to handle tile errors with its
default behavior, which is more robust and prevents console errors.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Combine Chart.js and date adapter into single bundle to ensure proper loading order
- Add validation to skip chart rendering when insufficient weather data (< 2 points)
- Update vendor loader to not load date adapter separately
- Prevents date adapter errors when weather stations have limited data