-
-
Notifications
You must be signed in to change notification settings - Fork 237
0.30.7 #1600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add Playwright tests for map functionality.
Fix maps layers
Fix/live map memory bloat
WalkthroughThis update refactors the map's live mode, marker creation, and photo/visits layer management for improved modularity, performance, and memory efficiency. It introduces new JavaScript modules for live map handling, marker factory, and photo display, with corresponding updates to controllers and helpers. Comprehensive end-to-end Playwright test suites and configuration are added. Minor Ruby test and documentation updates are included. Changes
Sequence Diagram(s)Live Map Point Streaming and Update FlowsequenceDiagram
participant User
participant UI (Map Page)
participant MapsController
participant LiveMapHandler
participant LeafletMap
User->>UI (Map Page): Enables Live Mode
UI (Map Page)->>MapsController: Initialize LiveMapHandler
MapsController->>LiveMapHandler: enable()
loop For each incoming GPS point
MapsController->>LiveMapHandler: appendPoint(point)
LiveMapHandler->>LeafletMap: Add marker, update heatmap, polylines, fog
end
User->>UI (Map Page): Disables Live Mode
MapsController->>LiveMapHandler: disable()
LiveMapHandler->>LeafletMap: Cleanup last marker
Photo Layer Fetch and DisplaysequenceDiagram
participant User
participant MapsController
participant fetchAndDisplayPhotos
participant LeafletMap
User->>MapsController: Enables Photos Layer
MapsController->>fetchAndDisplayPhotos: fetchAndDisplayPhotos(params)
fetchAndDisplayPhotos->>LeafletMap: Show loading spinner
fetchAndDisplayPhotos->>fetchAndDisplayPhotos: Fetch photo metadata
fetchAndDisplayPhotos->>LeafletMap: Add photo markers with popups
fetchAndDisplayPhotos->>LeafletMap: Remove loading spinner
Estimated code review effort🎯 5 (Critical) | ⏱️ ~80 minutes Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🔇 Additional comments (1)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Bugbot found 1 bugTo see it, activate your membership in the Cursor dashboard. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 20
♻️ Duplicate comments (1)
e2e/marker-factory.spec.js (1)
32-32: Same hard-coded date issue as other test files.Consider using dynamic dates for better test reliability across different environments and time periods.
🧹 Nitpick comments (21)
app/javascript/controllers/trips_controller.js (1)
10-11: Import path update LGTM, but keep quote style consistentThe new imports are correct and necessary for the refactor.
Minor style nit: surrounding imports in this file use double-quotes, while the two new lines use single-quotes. Aligning on one quote style keeps the file tidy.-import { showFlashMessage } from '../maps/helpers'; -import { fetchAndDisplayPhotos } from '../maps/photos'; +import { showFlashMessage } from "../maps/helpers"; +import { fetchAndDisplayPhotos } from "../maps/photos";e2e/live-map-handler.spec.js (1)
31-31: Consider using dynamic date ranges for better test reliability.The hard-coded date range
2025-06-04may become stale over time and could cause tests to fail when no data exists for that specific date.Consider using dynamic date ranges based on the current date:
- await page.goto('/map?start_at=2025-06-04T00:00&end_at=2025-06-04T23:59'); + const today = new Date(); + const startDate = today.toISOString().split('T')[0]; + await page.goto(`/map?start_at=${startDate}T00:00&end_at=${startDate}T23:59`);e2e/memory-leak-fix.spec.js (1)
33-33: Use dynamic date ranges for test reliability.Same issue as the previous test file - hard-coded dates may cause test failures when no data exists for that specific date.
Apply the same dynamic date solution:
- await page.goto('/map?start_at=2025-06-04T00:00&end_at=2025-06-04T23:59'); + const today = new Date(); + const startDate = today.toISOString().split('T')[0]; + await page.goto(`/map?start_at=${startDate}T00:00&end_at=${startDate}T23:59`);app/javascript/maps/markers.js (1)
32-36: Distance calculation is acceptable but could be improved.The simplified distance calculation using Euclidean distance is a reasonable performance optimization for marker simplification. However, the TODO comment suggests haversine distance was considered.
The current implementation is adequate for marker filtering, but if more accurate distance calculation is needed in the future, consider implementing a simple haversine approximation:
- // Note: haversineDistance function would need to be imported or implemented - // For now, using simple distance calculation - const latDiff = currLat - prevLat; - const lngDiff = currLon - prevLon; - const distance = Math.sqrt(latDiff * latDiff + lngDiff * lngDiff) * 111000; // Rough conversion to meters + // Simple haversine approximation for better accuracy + const toRad = (deg) => deg * (Math.PI / 180); + const dLat = toRad(currLat - prevLat); + const dLng = toRad(currLon - prevLon); + const a = Math.sin(dLat/2) * Math.sin(dLat/2) + + Math.cos(toRad(prevLat)) * Math.cos(toRad(currLat)) * + Math.sin(dLng/2) * Math.sin(dLng/2); + const distance = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) * 6371000; // Earth radius in metersapp/javascript/maps/live_map_handler.js (3)
36-36: Fix typo in property nameThe property name
fogLinethresholdappears to be a typo.- this.fogLinethreshold = options.fogLinethreshold || 10; + this.fogLineThreshold = options.fogLineThreshold || 10;
206-210: Make polyline styling configurableThe polyline color and weight are hardcoded, limiting customization options.
Add configuration options and use them:
+ this.polylineColor = options.polylineColor || '#3388ff'; + this.polylineWeight = options.polylineWeight || 3; const newSegment = L.polyline([ [prevPoint[0], prevPoint[1]], [newPoint[0], newPoint[1]] ], { - color: this.routeOpacity > 0 ? '#3388ff' : 'transparent', - weight: 3, + color: this.routeOpacity > 0 ? this.polylineColor : 'transparent', + weight: this.polylineWeight, opacity: this.routeOpacity });
221-227: Implement fog of war update logicThe fog of war update is currently just a placeholder with no actual implementation.
The method needs to implement the actual fog clearing logic. Would you like me to help implement this feature or create an issue to track it?
_updateFogOfWar() { if (this.map.hasLayer(this.fogOverlay)) { - // This would need to be implemented based on the existing fog logic - // For now, we'll just log that it needs updating - console.log('LiveMapHandler: Fog of war update needed'); + // TODO: Implement fog of war clearing logic + // - Clear fog around the new point using this.clearFogRadius + // - Update fog overlay visualization + console.warn('LiveMapHandler: Fog of war update not yet implemented'); } }app/javascript/maps/visits.js (3)
494-499: Improve user messaging and remove debug loggingThe placeholder message could be more helpful, and debug console.log should be removed.
- console.log('Drawer opened - showing placeholder message'); // Just show a placeholder message in the drawer, don't fetch visits const container = document.getElementById('visits-list'); if (container) { - container.innerHTML = '<p class="text-gray-500">Enable "Suggested Visits" or "Confirmed Visits" layers to see visits data</p>'; + container.innerHTML = ` + <div class="text-gray-500 text-center p-4"> + <p class="mb-2">No visits data loaded</p> + <p class="text-sm">Enable "Suggested Visits" or "Confirmed Visits" layers from the map controls to view visits.</p> + </div> + `; }
540-541: Remove debug logging statementsMultiple console.log statements should be removed before merging to production.
Remove all the console.log statements or replace with a proper logging solution that can be toggled via configuration.
Also applies to: 556-557, 574-575, 578-581, 597-597
613-614: Remove excessive debug loggingThe createMapCircles method has too many console.log statements that should be removed.
Remove all console.log statements or wrap them in a development-only check:
- console.log('No visits to create circles for'); + if (window.DEBUG) console.debug('No visits to create circles for');Also applies to: 618-619, 622-624, 633-641, 658-659, 662-663, 668-668, 672-673
app/javascript/maps/marker_factory.js (2)
20-27: Add parameter validation for robustnessThe function should validate inputs to prevent invalid CSS or negative sizes.
export function createStandardIcon(color = 'blue', size = 8) { + // Validate size + if (size <= 0 || !Number.isFinite(size)) { + console.warn('Invalid marker size, using default'); + size = 8; + } + return L.divIcon({ className: 'custom-div-icon', html: `<div style='background-color: ${color}; width: ${size}px; height: ${size}px; border-radius: 50%;'></div>`, iconSize: [size, size], iconAnchor: [size / 2, size / 2] }); }
231-240: Consider using constants for array indicesThe marker data array uses hardcoded indices which reduces maintainability.
Define constants at the module level:
// At the top of the file const MARKER_DATA_INDICES = { LATITUDE: 0, LONGITUDE: 1, BATTERY: 2, ALTITUDE: 3, TIMESTAMP: 4, VELOCITY: 5, ID: 6, COUNTRY: 7 }; // Then use them in the code: const updatedMarkerData = [ parseFloat(data.latitude), parseFloat(data.longitude), originalMarkerData[MARKER_DATA_INDICES.BATTERY], originalMarkerData[MARKER_DATA_INDICES.ALTITUDE], // etc... ];e2e/map.spec.js (5)
191-191: Remove console.log statements from testsConsole logs should be avoided in test files. Use Playwright's test reporter features instead if debugging information is needed.
- console.log(`Stats control displays: ${distance} ${unit} | ${points} points`);
591-591: Remove debugging console.log statementsMultiple console.log statements are present throughout the settings panel tests. These should be removed for production test suites.
Remove all console.log statements:
- console.log(`Settings panel closed after submission: ${isPanelClosed}`); - console.log(`Fog settings panel closed after submission: ${isPanelClosed}`); - console.log('Panel stayed open after submission - testing persistence directly'); - console.log('Fog panel stayed open after submission - testing persistence directly'); - console.log(`Points rendering panel closed after submission: ${isPanelClosed}`); - console.log('Points panel stayed open after submission - testing persistence directly');Also applies to: 675-675, 679-679, 865-865, 869-869
1432-1439: Performance test might be too lenientA 15-second timeout for page load might be too generous and could hide performance regressions.
Consider:
- Setting a more aggressive target (e.g., 5 seconds)
- Adding performance metrics collection
- Testing under different network conditions
test('should load within reasonable time', async () => { const startTime = Date.now(); + const metrics = []; await page.goto('/map'); await page.waitForSelector('.leaflet-container', { timeout: 15000 }); const loadTime = Date.now() - startTime; - expect(loadTime).toBeLessThan(15000); // Should load within 15 seconds + expect(loadTime).toBeLessThan(5000); // Should load within 5 seconds + + // Collect additional metrics + const performanceMetrics = await page.evaluate(() => + JSON.stringify(performance.getEntriesByType('navigation')[0]) + ); + console.info('Performance metrics:', performanceMetrics); });
1667-1667: Remove console.log from error handling test- console.log(`Console errors detected: ${consoleErrors.length}`); + // Verify errors were detected and handled + expect(consoleErrors.length).toBeGreaterThan(0);
1-1671: Consider splitting this large test file for better maintainabilityThis test file is 1671 lines long, which makes it difficult to maintain and navigate. Consider splitting it into smaller, focused test files.
Split the test file by functionality:
e2e/map/core-display.spec.js- Core map display testse2e/map/navigation.spec.js- Date and time navigation testse2e/map/layer-controls.spec.js- Layer control testse2e/map/settings.spec.js- Settings panel testse2e/map/calendar.spec.js- Calendar panel testse2e/map/interactive-elements.spec.js- Interactive map elementse2e/map/performance.spec.js- Performance and loading testse2e/map/accessibility.spec.js- Accessibility testse2e/map/error-handling.spec.js- Error handling testsEach file can share common setup through a base configuration or page object model pattern.
app/javascript/controllers/maps_controller.js (2)
122-124: Remove commented-out dead codeThese commented lines are duplicates of the active code above and serve no purpose. Dead code should be removed to improve maintainability.
let distance = parseInt(this.element.dataset.distance) || 0; const pointsNumber = this.element.dataset.points_number || '0'; - // Original stats data loading disabled: - // let distance = parseInt(this.element.dataset.distance) || 0; - // const pointsNumber = this.element.dataset.points_number || '0';
422-476: Consider refactoring to reduce code duplicationThe world borders fetching logic (lines 445-458) duplicates the same logic from
setupScratchLayer(lines 372-385). Consider extracting this into a separate method to follow DRY principles.+ async fetchWorldBordersData() { + if (this.worldBordersData) { + return this.worldBordersData; + } + + const response = await fetch('/api/v1/countries/borders.json', { + headers: { + 'Accept': 'application/geo+json,application/json' + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + this.worldBordersData = await response.json(); + return this.worldBordersData; + } + async refreshScratchLayer() { console.log('Refreshing scratch layer with current data'); if (!this.scratchLayer) { console.log('Scratch layer not initialized, setting up'); await this.setupScratchLayer(this.countryCodesMap); return; } try { // Clear existing data this.scratchLayer.clearLayers(); // Get current visited countries based on current markers const visitedCountries = this.getVisitedCountries(this.countryCodesMap); console.log('Current visited countries:', visitedCountries); if (visitedCountries.length === 0) { console.log('No visited countries found'); return; } - // Fetch country borders data (reuse if already loaded) - if (!this.worldBordersData) { - console.log('Loading world borders data'); - const response = await fetch('/api/v1/countries/borders.json', { - headers: { - 'Accept': 'application/geo+json,application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - this.worldBordersData = await response.json(); - } + // Fetch country borders data + console.log('Loading world borders data'); + const worldData = await this.fetchWorldBordersData(); // Filter for visited countries - const filteredFeatures = this.worldBordersData.features.filter(feature => + const filteredFeatures = worldData.features.filter(feature => visitedCountries.includes(feature.properties["ISO3166-1-Alpha-2"]) );Then update
setupScratchLayerto use the same method.e2e/live-mode.spec.js (2)
407-415: Document network simulation requirementsThe network simulation might fail in certain test environments. Consider adding a comment about the requirements or making this test conditional.
// Test connection resilience by simulating various network conditions + // Note: Network simulation requires Playwright to be running with appropriate permissions + // and may not work in all CI environments (e.g., Docker containers without network capabilities) try { // Simulate brief network interruption await page.context().setOffline(true);
36-37: Consider parameterizing test datesThe tests use hardcoded future dates (2025-06-04) which will eventually become past dates. Consider using relative dates or parameterizing these values.
+ // Use a date far enough in the future to avoid conflicts with real data + const TEST_DATE = new Date(); + TEST_DATE.setFullYear(TEST_DATE.getFullYear() + 10); + const TEST_DATE_STRING = TEST_DATE.toISOString().split('T')[0]; + test.beforeEach(async () => { - // Navigate to June 4, 2025 where we have test data - await page.goto('/map?start_at=2025-06-04T00:00&end_at=2025-06-04T23:59'); + // Navigate to test date where we have test data + await page.goto(`/map?start_at=${TEST_DATE_STRING}T00:00&end_at=${TEST_DATE_STRING}T23:59`);Also applies to: 226-226, 472-476
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
.app_version(1 hunks)CHANGELOG.md(1 hunks)app/javascript/controllers/maps_controller.js(22 hunks)app/javascript/controllers/trips_controller.js(1 hunks)app/javascript/maps/fog_of_war.js(1 hunks)app/javascript/maps/helpers.js(1 hunks)app/javascript/maps/live_map_handler.js(1 hunks)app/javascript/maps/marker_factory.js(1 hunks)app/javascript/maps/markers.js(3 hunks)app/javascript/maps/photos.js(1 hunks)app/javascript/maps/visits.js(8 hunks)e2e/live-map-handler.spec.js(1 hunks)e2e/live-mode.spec.js(1 hunks)e2e/map.spec.js(1 hunks)e2e/marker-factory.spec.js(1 hunks)e2e/memory-leak-fix.spec.js(1 hunks)playwright.config.js(1 hunks)spec/services/tracks/track_builder_spec.rb(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
app/javascript/maps/fog_of_war.js (1)
app/javascript/controllers/maps_controller.js (4)
mapElement(1092-1092)controller(1169-1169)controller(1206-1206)fog(760-760)
e2e/marker-factory.spec.js (1)
app/javascript/maps/marker_factory.js (2)
marker(71-81)marker(105-109)
app/javascript/maps/markers.js (1)
app/javascript/maps/marker_factory.js (4)
marker(71-81)marker(105-109)createInteractiveMarker(65-90)createSimplifiedMarker(100-128)
🪛 Biome (2.1.2)
app/javascript/maps/photos.js
[error] 105-105: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 108-108: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 111-114: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
[error] 115-115: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (23)
playwright.config.js (1)
1-51: Well-structured Playwright configuration with good CI/local environment handling.The configuration follows Playwright best practices with proper test isolation, artifact collection, and Rails server integration. The CI-specific settings (single worker, retries, forbidOnly) are appropriate for stable test execution.
app/javascript/maps/helpers.js (1)
202-202: Good modularization by moving photo functionality to dedicated module.The removal of photo-specific functions from generic helpers and their relocation to
app/javascript/maps/photos.jsimproves code organization and maintainability. The remaining utility functions are appropriately scoped for general use.app/javascript/maps/fog_of_war.js (4)
107-125: Excellent refactoring with improved state management and immediate initialization.The transition from arrow functions to traditional function expressions enables proper
thiscontext binding for storing map and controller references. The immediate fog drawing when markers exist is a good UX improvement.
128-142: Proper event handler management with named function reference.Storing the resize handler as
this._onResizeenables controlled cleanup and prevents memory leaks. The conditional redraw logic is appropriate for maintaining fog state across resize events.
145-155: Thorough cleanup in onRemove method.The explicit removal of the resize event listener prevents memory leaks, which aligns with the PR's goal of eliminating memory leaks in live map functionality.
157-162: Well-designed public API for external fog updates.The new
updateFogmethod provides a clean interface for external components to trigger fog redraws, supporting the refactored layer management approach mentioned in the AI summary..app_version (1)
1-1: Appropriate version increment for this release.The version bump to 0.30.7 properly reflects the scope of changes including map refactoring, memory leak fixes, and comprehensive E2E test additions.
spec/services/tracks/track_builder_spec.rb (1)
326-326: Good improvement to test reliability with appropriate tolerance.Changing from exact equality to a 1-second tolerance for duration checks is a sensible improvement for integration tests, accommodating minor timing variations that can occur during test execution without compromising test validity.
CHANGELOG.md (1)
7-22: Changelog entry looks fineEntry provides clear, granular notes for 0.30.7; no issues spotted.
app/javascript/maps/markers.js (2)
1-1: Good refactoring to use marker factory pattern.The import and delegation to
marker_factoryfunctions centralizes marker creation logic and improves maintainability.
46-48: Excellent use of factory delegation.The delegation to
createSimplifiedMarkerwithuserSettingsparameter maintains consistency with the factory pattern and ensures proper marker configuration.e2e/marker-factory.spec.js (3)
80-85: Excellent marker consistency validation logic.The CSS property validation ensures markers have consistent styling (border radius, colors, dimensions) which is crucial for UI consistency.
113-129: Good use of performance.memory API for memory monitoring.The memory monitoring using
window.performance.memoryprovides valuable insights into memory usage patterns, which is important for the memory leak fixes being tested.
150-179: Outstanding documentation of refactoring benefits.This test case serves as excellent living documentation of the marker factory refactoring benefits, including code reuse, memory safety, maintenance, and performance improvements.
app/javascript/maps/photos.js (4)
5-7: Excellent retry mechanism implementation.The retry logic with exponential backoff and maximum retry attempts provides robust error handling for network requests.
48-66: Well-structured asynchronous photo loading.The Promise-based approach for loading photo thumbnails with proper error handling ensures that failed photo loads don't block the entire operation.
20-29: Clean loading control implementation.The custom Leaflet control for showing loading state provides good user feedback during photo fetching operations.
170-185: Rich popup content with good user experience.The popup content includes comprehensive photo metadata, hover effects, and proper external links. The styling and interaction design enhances usability.
app/javascript/maps/live_map_handler.js (1)
160-170: Well-implemented memory managementThe sliding window approach correctly limits memory usage by removing the oldest points and their corresponding markers.
app/javascript/maps/visits.js (2)
236-238: Good architectural improvementDecoupling visits data fetching from drawer UI state improves separation of concerns and makes the layer control the single source of truth for visibility.
675-681: Good separation of concernsSplitting display logic into
createMapCirclesfor map rendering anddisplayVisitsfor UI updates improves modularity and testability.app/javascript/maps/marker_factory.js (2)
37-52: Well-designed for live streaming efficiencyThe function correctly creates lightweight markers without heavy features, perfect for high-frequency updates in live mode.
65-90: Excellent implementation of interactive markersThe function properly creates feature-rich markers with all necessary metadata and delegates complex drag handling to a dedicated function, following good separation of concerns.
| constructor(map, layers, options = {}) { | ||
| this.map = map; | ||
| this.markersLayer = layers.markersLayer; | ||
| this.polylinesLayer = layers.polylinesLayer; | ||
| this.heatmapLayer = layers.heatmapLayer; | ||
| this.fogOverlay = layers.fogOverlay; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add parameter validation for required arguments
The constructor should validate that required parameters are provided to fail fast with clear error messages.
Add validation at the beginning of the constructor:
constructor(map, layers, options = {}) {
+ if (!map) {
+ throw new Error('LiveMapHandler: map parameter is required');
+ }
+ if (!layers || typeof layers !== 'object') {
+ throw new Error('LiveMapHandler: layers parameter must be an object');
+ }
+
this.map = map;
this.markersLayer = layers.markersLayer;
this.polylinesLayer = layers.polylinesLayer;
this.heatmapLayer = layers.heatmapLayer;
this.fogOverlay = layers.fogOverlay;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| constructor(map, layers, options = {}) { | |
| this.map = map; | |
| this.markersLayer = layers.markersLayer; | |
| this.polylinesLayer = layers.polylinesLayer; | |
| this.heatmapLayer = layers.heatmapLayer; | |
| this.fogOverlay = layers.fogOverlay; | |
| constructor(map, layers, options = {}) { | |
| if (!map) { | |
| throw new Error('LiveMapHandler: map parameter is required'); | |
| } | |
| if (!layers || typeof layers !== 'object') { | |
| throw new Error('LiveMapHandler: layers parameter must be an object'); | |
| } | |
| this.map = map; | |
| this.markersLayer = layers.markersLayer; | |
| this.polylinesLayer = layers.polylinesLayer; | |
| this.heatmapLayer = layers.heatmapLayer; | |
| this.fogOverlay = layers.fogOverlay; |
🤖 Prompt for AI Agents
In app/javascript/maps/live_map_handler.js around lines 17 to 23, the
constructor lacks validation for required parameters. Add checks at the start of
the constructor to verify that 'map' and 'layers' are provided and not null or
undefined. If any required parameter is missing, throw an error with a clear
message indicating which parameter is missing to ensure the code fails fast and
aids debugging.
| // Parse the received point data | ||
| const newPoint = data; | ||
|
|
||
| // Add the new point to the markers array | ||
| this.markers.push(newPoint); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate and clone point data to prevent mutations
The point data is assigned directly without validation or cloning, which could lead to unexpected mutations.
// Parse the received point data
- const newPoint = data;
+ if (!Array.isArray(data) || data.length < 2) {
+ console.error('LiveMapHandler: Invalid point data format');
+ return;
+ }
+ // Clone the array to prevent external mutations
+ const newPoint = [...data];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Parse the received point data | |
| const newPoint = data; | |
| // Add the new point to the markers array | |
| this.markers.push(newPoint); | |
| // Parse the received point data | |
| if (!Array.isArray(data) || data.length < 2) { | |
| console.error('LiveMapHandler: Invalid point data format'); | |
| return; | |
| } | |
| // Clone the array to prevent external mutations | |
| const newPoint = [...data]; | |
| // Add the new point to the markers array | |
| this.markers.push(newPoint); |
🤖 Prompt for AI Agents
In app/javascript/maps/live_map_handler.js around lines 83 to 87, the point data
is assigned directly from the input without validation or cloning, risking
unintended mutations. To fix this, validate the structure and content of the
incoming point data to ensure it meets expected criteria, then create a deep
clone of the point object before adding it to the markers array to prevent
external mutations from affecting internal state.
| this._updatePolylines(newPoint); | ||
|
|
||
| // Pan map to new location | ||
| this.map.setView([newPoint[0], newPoint[1]], 16); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make map zoom level configurable
The zoom level is hardcoded to 16, which should be configurable for different use cases.
+ // Configuration options
+ this.maxPoints = options.maxPoints || 1000;
+ this.routeOpacity = options.routeOpacity || 1;
+ this.timezone = options.timezone || 'UTC';
+ this.distanceUnit = options.distanceUnit || 'km';
+ this.userSettings = options.userSettings || {};
+ this.clearFogRadius = options.clearFogRadius || 100;
+ this.fogLineThreshold = options.fogLineThreshold || 10;
+ this.liveViewZoom = options.liveViewZoom || 16;
// Pan map to new location
- this.map.setView([newPoint[0], newPoint[1]], 16);
+ this.map.setView([newPoint[0], newPoint[1]], this.liveViewZoom);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.map.setView([newPoint[0], newPoint[1]], 16); | |
| // Configuration options | |
| this.maxPoints = options.maxPoints || 1000; | |
| this.routeOpacity = options.routeOpacity || 1; | |
| this.timezone = options.timezone || 'UTC'; | |
| this.distanceUnit = options.distanceUnit || 'km'; | |
| this.userSettings = options.userSettings || {}; | |
| this.clearFogRadius = options.clearFogRadius || 100; | |
| this.fogLineThreshold = options.fogLineThreshold || 10; | |
| this.liveViewZoom = options.liveViewZoom || 16; | |
| // Pan map to new location | |
| this.map.setView([newPoint[0], newPoint[1]], this.liveViewZoom); |
🤖 Prompt for AI Agents
In app/javascript/maps/live_map_handler.js at line 104, the map zoom level is
hardcoded to 16. Modify the code to accept the zoom level as a configurable
parameter or option, allowing different zoom levels to be set dynamically
instead of using the fixed value 16.
| _updateLastMarker() { | ||
| // Remove previous last marker | ||
| if (this.lastMarkerRef) { | ||
| this.map.removeLayer(this.lastMarkerRef); | ||
| } | ||
|
|
||
| // Add new last marker and store reference | ||
| if (this.markers.length > 0) { | ||
| const lastPoint = this.markers[this.markers.length - 1]; | ||
| const lastMarker = L.marker([lastPoint[0], lastPoint[1]]); | ||
| this.lastMarkerRef = lastMarker.addTo(this.map); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use marker factory and add distinctive styling for last marker
The last marker should use the marker factory for consistency and have distinctive styling to indicate it's the current position.
_updateLastMarker() {
// Remove previous last marker
if (this.lastMarkerRef) {
this.map.removeLayer(this.lastMarkerRef);
}
// Add new last marker and store reference
- if (this.markers.length > 0) {
+ if (this.markers && this.markers.length > 0) {
const lastPoint = this.markers[this.markers.length - 1];
- const lastMarker = L.marker([lastPoint[0], lastPoint[1]]);
+ // Use marker factory with special styling for last marker
+ const lastMarker = createLiveMarker(lastPoint, {
+ size: 12, // Larger size for visibility
+ className: 'last-marker-icon'
+ });
+ // Add pulsing animation or special styling
+ lastMarker.options.icon.options.className += ' pulse-animation';
this.lastMarkerRef = lastMarker.addTo(this.map);
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| _updateLastMarker() { | |
| // Remove previous last marker | |
| if (this.lastMarkerRef) { | |
| this.map.removeLayer(this.lastMarkerRef); | |
| } | |
| // Add new last marker and store reference | |
| if (this.markers.length > 0) { | |
| const lastPoint = this.markers[this.markers.length - 1]; | |
| const lastMarker = L.marker([lastPoint[0], lastPoint[1]]); | |
| this.lastMarkerRef = lastMarker.addTo(this.map); | |
| } | |
| } | |
| _updateLastMarker() { | |
| // Remove previous last marker | |
| if (this.lastMarkerRef) { | |
| this.map.removeLayer(this.lastMarkerRef); | |
| } | |
| // Add new last marker and store reference | |
| if (this.markers && this.markers.length > 0) { | |
| const lastPoint = this.markers[this.markers.length - 1]; | |
| // Use marker factory with special styling for last marker | |
| const lastMarker = createLiveMarker(lastPoint, { | |
| size: 12, // Larger size for visibility | |
| className: 'last-marker-icon' | |
| }); | |
| // Add pulsing animation or special styling | |
| lastMarker.options.icon.options.className += ' pulse-animation'; | |
| this.lastMarkerRef = lastMarker.addTo(this.map); | |
| } | |
| } |
🤖 Prompt for AI Agents
In app/javascript/maps/live_map_handler.js around lines 233 to 245, the
_updateLastMarker method creates the last marker directly instead of using the
marker factory, and it lacks distinctive styling. Refactor the code to create
the last marker using the existing marker factory method and apply a unique
style or icon to visually distinguish it as the current position marker.
| marker.on('drag', function(e) { | ||
| const newLatLng = e.target.getLatLng(); | ||
| const map = e.target._map; | ||
| const pointIndex = e.target.options.pointIndex; | ||
| const originalLat = e.target.options.originalLat; | ||
| const originalLng = e.target.options.originalLng; | ||
|
|
||
| // Find polylines by iterating through all map layers | ||
| map.eachLayer((layer) => { | ||
| // Check if this is a LayerGroup containing polylines | ||
| if (layer instanceof L.LayerGroup) { | ||
| layer.eachLayer((featureGroup) => { | ||
| if (featureGroup instanceof L.FeatureGroup) { | ||
| featureGroup.eachLayer((segment) => { | ||
| if (segment instanceof L.Polyline) { | ||
| const coords = segment.getLatLngs(); | ||
| const tolerance = 0.0000001; | ||
| let updated = false; | ||
|
|
||
| // Check and update start point | ||
| if (Math.abs(coords[0].lat - originalLat) < tolerance && | ||
| Math.abs(coords[0].lng - originalLng) < tolerance) { | ||
| coords[0] = newLatLng; | ||
| updated = true; | ||
| } | ||
|
|
||
| // Check and update end point | ||
| if (Math.abs(coords[1].lat - originalLat) < tolerance && | ||
| Math.abs(coords[1].lng - originalLng) < tolerance) { | ||
| coords[1] = newLatLng; | ||
| updated = true; | ||
| } | ||
|
|
||
| // Only update if we found a matching endpoint | ||
| if (updated) { | ||
| segment.setLatLngs(coords); | ||
| segment.redraw(); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| // Update the marker's original position for the next drag event | ||
| e.target.options.originalLat = newLatLng.lat; | ||
| e.target.options.originalLng = newLatLng.lng; | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize polyline update performance and improve tolerance handling
The nested iteration through all map layers could impact performance with many layers, and the tolerance value might be too restrictive.
marker.on('drag', function(e) {
const newLatLng = e.target.getLatLng();
const map = e.target._map;
const pointIndex = e.target.options.pointIndex;
const originalLat = e.target.options.originalLat;
const originalLng = e.target.options.originalLng;
+
+ // Use a more reasonable tolerance for GPS coordinates
+ const tolerance = 0.000001; // ~0.1 meter precision
+
+ // Cache polyline layers for better performance
+ if (!map._polylineLayers) {
+ map._polylineLayers = [];
+ map.eachLayer((layer) => {
+ if (layer instanceof L.LayerGroup) {
+ layer.eachLayer((subLayer) => {
+ if (subLayer instanceof L.Polyline ||
+ (subLayer instanceof L.FeatureGroup && subLayer.getLayers().some(l => l instanceof L.Polyline))) {
+ map._polylineLayers.push(subLayer);
+ }
+ });
+ }
+ });
+ }
- // Find polylines by iterating through all map layers
- map.eachLayer((layer) => {
- // Check if this is a LayerGroup containing polylines
- if (layer instanceof L.LayerGroup) {
+ // Update polylines using cached layers
+ map._polylineLayers.forEach((layer) => {
layer.eachLayer((featureGroup) => {
if (featureGroup instanceof L.FeatureGroup) {
featureGroup.eachLayer((segment) => {
if (segment instanceof L.Polyline) {
const coords = segment.getLatLngs();
- const tolerance = 0.0000001;
+ if (!coords || coords.length < 2) return;
+
let updated = false;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/javascript/maps/marker_factory.js between lines 144 and 192, the current
code iterates deeply through all map layers and feature groups to find and
update polylines, which can degrade performance with many layers. To fix this,
optimize by maintaining a direct reference or index to relevant polylines
instead of iterating all layers on each drag event. Also, increase the tolerance
value to a more reasonable threshold to better handle floating-point precision
issues when comparing coordinates.
| const url = page.url(); | ||
| // Allow for timezone differences by checking for current date or next day | ||
| const today = new Date().toISOString().split('T')[0]; | ||
| const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0]; | ||
| expect(url.includes(today) || url.includes(tomorrow)).toBe(true); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider timezone-aware date comparisons
The date comparison logic might fail in different timezones. Consider using a more robust approach.
- // Allow for timezone differences by checking for current date or next day
- const today = new Date().toISOString().split('T')[0];
- const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0];
- expect(url.includes(today) || url.includes(tomorrow)).toBe(true);
+ // Use URL parsing for more reliable date extraction
+ const urlParams = new URLSearchParams(new URL(url).search);
+ const startDate = urlParams.get('start_at');
+
+ // Verify it's a date from today (considering timezone)
+ const startDateTime = new Date(startDate);
+ const now = new Date();
+ const diffHours = Math.abs(startDateTime - now) / (1000 * 60 * 60);
+
+ // Should be within 24 hours of current time
+ expect(diffHours).toBeLessThan(24);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const url = page.url(); | |
| // Allow for timezone differences by checking for current date or next day | |
| const today = new Date().toISOString().split('T')[0]; | |
| const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0]; | |
| expect(url.includes(today) || url.includes(tomorrow)).toBe(true); | |
| }); | |
| const url = page.url(); | |
| // Use URL parsing for more reliable date extraction | |
| const urlParams = new URLSearchParams(new URL(url).search); | |
| const startDate = urlParams.get('start_at'); | |
| // Verify it's a date from today (considering timezone) | |
| const startDateTime = new Date(startDate); | |
| const now = new Date(); | |
| const diffHours = Math.abs(startDateTime - now) / (1000 * 60 * 60); | |
| // Should be within 24 hours of current time | |
| expect(diffHours).toBeLessThan(24); | |
| }); |
🤖 Prompt for AI Agents
In e2e/map.spec.js around lines 315 to 320, the current date comparison uses
local time and may fail across different timezones. To fix this, use a
timezone-aware date library or convert dates to UTC consistently before
comparison. Replace the current logic with one that explicitly handles timezone
differences to ensure the test passes reliably regardless of the environment
timezone.
| // Due to double-event issue causing toggling, force panel to be visible via JavaScript | ||
| await page.evaluate(() => { | ||
| const panel = document.querySelector('.leaflet-right-panel'); | ||
| if (panel) { | ||
| panel.style.display = 'block'; | ||
| localStorage.setItem('mapPanelOpen', 'true'); | ||
| console.log('Forced panel to be visible via JavaScript'); | ||
| } | ||
| }); | ||
|
|
||
| // After forcing visibility, panel should be visible | ||
| await expect(panel).toBeVisible(); | ||
|
|
||
| // Verify panel contains dynamically loaded content | ||
| await expect(panel.locator('#year-select')).toBeVisible(); | ||
| await expect(panel.locator('#months-grid')).toBeVisible(); | ||
|
|
||
| // Test closing functionality - force panel to be hidden due to double-event issue | ||
| await page.evaluate(() => { | ||
| const panel = document.querySelector('.leaflet-right-panel'); | ||
| if (panel) { | ||
| panel.style.display = 'none'; | ||
| localStorage.setItem('mapPanelOpen', 'false'); | ||
| console.log('Forced panel to be hidden via JavaScript'); | ||
| } | ||
| }); | ||
|
|
||
| // Panel should be hidden (but may still exist in DOM for performance) | ||
| const finalVisible = await panel.isVisible(); | ||
| expect(finalVisible).toBe(false); | ||
|
|
||
| // Test toggle functionality works both ways - force panel to be visible again | ||
| await page.evaluate(() => { | ||
| const panel = document.querySelector('.leaflet-right-panel'); | ||
| if (panel) { | ||
| panel.style.display = 'block'; | ||
| localStorage.setItem('mapPanelOpen', 'true'); | ||
| console.log('Forced panel to be visible again via JavaScript'); | ||
| } | ||
| }); | ||
| await expect(panel).toBeVisible(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
Consider investigating the root cause of double-event issues
The tests contain multiple workarounds for "double-event issues" that force panel visibility through JavaScript. This approach might mask actual bugs in the application.
Instead of working around the issue, consider:
- Investigating why double events are occurring
- Fixing the root cause in the application code
- Testing the actual user experience without workarounds
If workarounds are necessary for now, add a TODO comment:
+ // TODO: Remove this workaround once double-event issue is fixed in the application
// Due to double-event issue causing toggling, force panel to be visible via JavaScriptInvestigate and remove forced visibility workarounds for the calendar panel
The tests currently force the panel’s display state via JavaScript to work around a “double-event” toggle issue. This approach can mask underlying bugs in the application.
Please address the root cause instead of relying on these workarounds:
- Track down why two toggle events are firing and fix the event handler logic in the app code.
- Update your tests to drive and assert the panel’s behavior based on real user interactions (clicks) rather than manually mutating styles.
- Once the fix is in place, remove the JS‐injected visibility code from the tests.
If you need a temporary note for this workaround, add a TODO above the forced evaluation:
+ // TODO: Remove this workaround once the double-event toggle bug is fixed in the app
// Due to double-event issue causing toggling, force panel to be visible via JavaScript
await page.evaluate(() => {
const panel = document.querySelector('.leaflet-right-panel');
if (panel) {
panel.style.display = 'block';
localStorage.setItem('mapPanelOpen', 'true');
console.log('Forced panel to be visible via JavaScript');
}
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Due to double-event issue causing toggling, force panel to be visible via JavaScript | |
| await page.evaluate(() => { | |
| const panel = document.querySelector('.leaflet-right-panel'); | |
| if (panel) { | |
| panel.style.display = 'block'; | |
| localStorage.setItem('mapPanelOpen', 'true'); | |
| console.log('Forced panel to be visible via JavaScript'); | |
| } | |
| }); | |
| // After forcing visibility, panel should be visible | |
| await expect(panel).toBeVisible(); | |
| // Verify panel contains dynamically loaded content | |
| await expect(panel.locator('#year-select')).toBeVisible(); | |
| await expect(panel.locator('#months-grid')).toBeVisible(); | |
| // Test closing functionality - force panel to be hidden due to double-event issue | |
| await page.evaluate(() => { | |
| const panel = document.querySelector('.leaflet-right-panel'); | |
| if (panel) { | |
| panel.style.display = 'none'; | |
| localStorage.setItem('mapPanelOpen', 'false'); | |
| console.log('Forced panel to be hidden via JavaScript'); | |
| } | |
| }); | |
| // Panel should be hidden (but may still exist in DOM for performance) | |
| const finalVisible = await panel.isVisible(); | |
| expect(finalVisible).toBe(false); | |
| // Test toggle functionality works both ways - force panel to be visible again | |
| await page.evaluate(() => { | |
| const panel = document.querySelector('.leaflet-right-panel'); | |
| if (panel) { | |
| panel.style.display = 'block'; | |
| localStorage.setItem('mapPanelOpen', 'true'); | |
| console.log('Forced panel to be visible again via JavaScript'); | |
| } | |
| }); | |
| await expect(panel).toBeVisible(); | |
| // TODO: Remove this workaround once the double-event toggle bug is fixed in the app | |
| // Due to double-event issue causing toggling, force panel to be visible via JavaScript | |
| await page.evaluate(() => { | |
| const panel = document.querySelector('.leaflet-right-panel'); | |
| if (panel) { | |
| panel.style.display = 'block'; | |
| localStorage.setItem('mapPanelOpen', 'true'); | |
| console.log('Forced panel to be visible via JavaScript'); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In e2e/map.spec.js around lines 962 to 1002, the test forcibly sets the calendar
panel's visibility via JavaScript to bypass a double-event toggle issue, which
hides the real problem. Investigate and fix the root cause in the application
code by correcting the event handler logic to prevent duplicate toggle events.
Then, update the tests to simulate actual user interactions like clicks to
toggle the panel and assert its visibility accordingly. After confirming the
fix, remove all forced style manipulations and related JavaScript injections
from the test code. Optionally, add a TODO comment above any temporary forced
visibility code if you keep it during the fix.
| await layerControl.click(); | ||
|
|
||
| // Find and enable Areas layer | ||
| const areasCheckbox = page.locator('.leaflet-control-layers-overlays').locator('input').filter({ hasText: /Areas/ }).first(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Fix deprecated Playwright API usage
The filter method with hasText option is not the recommended approach for text-based filtering in recent Playwright versions.
- const areasCheckbox = page.locator('.leaflet-control-layers-overlays').locator('input').filter({ hasText: /Areas/ }).first();
+ const areasCheckbox = page.locator('.leaflet-control-layers-overlays label:has-text("Areas") input').first();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const areasCheckbox = page.locator('.leaflet-control-layers-overlays').locator('input').filter({ hasText: /Areas/ }).first(); | |
| const areasCheckbox = page.locator('.leaflet-control-layers-overlays label:has-text("Areas") input').first(); |
🤖 Prompt for AI Agents
In e2e/map.spec.js at line 1416, the use of the deprecated filter method with
hasText option should be replaced. Instead of using filter({ hasText: /Areas/
}), use the locator method with a text selector or the getByText method to find
the input element containing the text "Areas". Update the code to use the
current recommended Playwright API for text-based filtering.
| expect(codeAnalysis.hasOldClearLayersPattern).toBe(false); // Old inefficient pattern should be gone | ||
| expect(codeAnalysis.hasIncrementalMarkerAdd).toBe(true); // New efficient pattern should exist | ||
| expect(codeAnalysis.hasBoundedData).toBe(true); // Should have bounded data structures |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Strengthen memory leak verification assertions.
The conditional logic is good, but the assertions could be more comprehensive to catch regressions.
Consider adding more specific memory leak pattern checks:
// If controller is found, verify the fix
expect(codeAnalysis.hasOldClearLayersPattern).toBe(false); // Old inefficient pattern should be gone
expect(codeAnalysis.hasIncrementalMarkerAdd).toBe(true); // New efficient pattern should exist
expect(codeAnalysis.hasBoundedData).toBe(true); // Should have bounded data structures
+ // Additional checks for memory safety
+ expect(codeAnalysis.methodLength).toBeLessThan(2000); // Method should be reasonably sized
+ expect(codeAnalysis.hasLastMarkerTracking).toBe(true); // Should track markers efficiently📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| expect(codeAnalysis.hasOldClearLayersPattern).toBe(false); // Old inefficient pattern should be gone | |
| expect(codeAnalysis.hasIncrementalMarkerAdd).toBe(true); // New efficient pattern should exist | |
| expect(codeAnalysis.hasBoundedData).toBe(true); // Should have bounded data structures | |
| // If controller is found, verify the fix | |
| expect(codeAnalysis.hasOldClearLayersPattern).toBe(false); // Old inefficient pattern should be gone | |
| expect(codeAnalysis.hasIncrementalMarkerAdd).toBe(true); // New efficient pattern should exist | |
| expect(codeAnalysis.hasBoundedData).toBe(true); // Should have bounded data structures | |
| // Additional checks for memory safety | |
| expect(codeAnalysis.methodLength).toBeLessThan(2000); // Method should be reasonably sized | |
| expect(codeAnalysis.hasLastMarkerTracking).toBe(true); // Should track markers efficiently |
🤖 Prompt for AI Agents
In e2e/memory-leak-fix.spec.js around lines 74 to 76, the current assertions
check for some memory leak patterns but are not comprehensive enough to catch
all regressions. Enhance the test by adding more specific assertions that verify
the absence of other known inefficient memory usage patterns and confirm the
presence of all expected efficient patterns. This will strengthen the memory
leak verification and improve test coverage against regressions.
| console.log('- Incremental polyline updates'); | ||
|
|
||
| // Test passes regardless as we've verified the fix is in the source code | ||
| expect(true).toBe(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Test always passes without meaningful verification.
Similar to the previous file, this test always passes regardless of the actual verification outcome, which diminishes its value.
Implement conditional assertions based on the verification results:
- // Test passes regardless as we've verified the fix is in the source code
- expect(true).toBe(true);
+ // Verify that either the efficient patterns are found or old patterns are absent
+ const hasImprovements = hasEfficientImplementation.hasIncrementalAdd ||
+ hasEfficientImplementation.hasBoundedArrays ||
+ hasEfficientImplementation.hasEfficientTracking;
+ const lacksOldProblems = !hasEfficientImplementation.hasOldPattern;
+ expect(hasImprovements || lacksOldProblems).toBe(true);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| expect(true).toBe(true); | |
| // Verify that either the efficient patterns are found or old patterns are absent | |
| const hasImprovements = hasEfficientImplementation.hasIncrementalAdd || | |
| hasEfficientImplementation.hasBoundedArrays || | |
| hasEfficientImplementation.hasEfficientTracking; | |
| const lacksOldProblems = !hasEfficientImplementation.hasOldPattern; | |
| expect(hasImprovements || lacksOldProblems).toBe(true); |
🤖 Prompt for AI Agents
In e2e/memory-leak-fix.spec.js at line 138, the test currently uses a trivial
assertion that always passes, providing no meaningful verification. Replace the
placeholder expect(true).toBe(true) with assertions that check actual conditions
or results relevant to the test scenario, ensuring the test outcome reflects the
correctness of the code under test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (7)
app/javascript/maps/visits.js (1)
585-600: Same layer control state checking issue remains unaddressed.This implementation still accesses the private
_layersproperty and uses fragile string comparisons, which was flagged in previous reviews. The suggested solution was to usemap.hasLayer(this.visitCircles)andmap.hasLayer(this.confirmedVisitCircles)instead of iterating through internal layer properties.app/javascript/maps/live_map_handler.js (4)
17-23: Parameter validation still missing.The constructor lacks validation for required parameters that was suggested in previous reviews. The recommended validation should check that
mapandlayersparameters are provided and valid.
83-84: Point data validation and cloning still missing.The point data is still assigned directly without validation or cloning, which was flagged in previous reviews. The recommended approach was to validate the data structure and clone the array to prevent mutations.
104-104: Zoom level still hardcoded.The map zoom level remains hardcoded at 16, which was flagged in previous reviews. The suggested solution was to make this configurable via options.
233-245: Last marker should use factory and distinctive styling.The
_updateLastMarkermethod still creates markers directly instead of using the marker factory, and lacks distinctive styling to indicate it's the current position, both of which were flagged in previous reviews.app/javascript/maps/marker_factory.js (2)
162-198: Polyline update performance issue remains unaddressed.The nested iteration through all map layers during drag events still impacts performance, which was flagged in previous reviews. The suggested optimization was to cache polyline layers and use a more reasonable tolerance value.
267-270: Alert-based error notification still needs improvement.The use of
alert()for error notification still blocks the UI thread, which was flagged in previous reviews. The recommended approach was to use a non-blocking notification system.
🧹 Nitpick comments (1)
app/javascript/maps/visits.js (1)
614-676: Good separation of concerns, but optimize logging and performance.The extraction of map circle creation is excellent for maintainability. However, consider these improvements:
- Conditional logging: Wrap console.log statements in development checks to avoid performance impact in production
- Incremental updates: Consider whether clearing and recreating all circles is necessary, or if incremental updates would be more efficient for large datasets
- console.log('fetchAndDisplayVisits called'); + if (window.DEBUG || process.env.NODE_ENV === 'development') { + console.log('fetchAndDisplayVisits called'); + }Apply similar changes to other console.log statements throughout the method.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
app/javascript/controllers/maps_controller.js(22 hunks)app/javascript/controllers/trips_controller.js(1 hunks)app/javascript/maps/fog_of_war.js(4 hunks)app/javascript/maps/live_map_handler.js(1 hunks)app/javascript/maps/marker_factory.js(1 hunks)app/javascript/maps/markers.js(3 hunks)app/javascript/maps/visits.js(8 hunks)
✅ Files skipped from review due to trivial changes (1)
- app/javascript/controllers/trips_controller.js
🚧 Files skipped from review as they are similar to previous changes (2)
- app/javascript/maps/markers.js
- app/javascript/maps/fog_of_war.js
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/javascript/maps/marker_factory.js (4)
app/javascript/controllers/maps_controller.js (2)
marker(750-750)data(1700-1700)e2e/live-mode.spec.js (1)
apiKey(1164-1167)app/javascript/maps/markers.js (1)
renderer(6-6)app/javascript/maps/popups.js (1)
createPopupContent(3-34)
🔇 Additional comments (28)
app/javascript/maps/visits.js (3)
236-238: LGTM! Good separation of concerns.This change properly decouples visit data refresh from drawer state, ensuring visits are always updated when selection is cleared. The comment clearly explains the new responsibility division.
492-504: Excellent separation of UI and data concerns.The drawer now focuses solely on displaying content rather than managing data fetching. The placeholder message effectively guides users to the proper controls for enabling visit layers.
682-801: Excellent refactoring with clear separation of responsibilities.The split between map circle creation and drawer UI updates is well-executed. The defensive programming with early return when the container doesn't exist prevents potential errors. This design makes the code more maintainable and testable.
app/javascript/maps/live_map_handler.js (1)
156-194: Excellent memory management design.The bounded data structures and incremental update approach effectively address the memory leak issues. The
_enforcePointLimits()method properly maintains array bounds, and the heatmap/polyline updates are efficiently implemented to avoid recreation of entire layers.app/javascript/maps/marker_factory.js (2)
3-63: Excellent constant definitions and factory design.The
MARKER_DATA_INDICESconstants greatly improve code readability and maintainability. The factory functions are well-designed with clear purposes:createLiveMarkeroptimized for performance, and proper parameterization throughout. The velocity-based color coding is a nice touch.
76-101: Well-architected interactive marker creation.The function properly separates concerns by delegating popup creation and drag handling. Storing metadata in marker options provides the necessary context for drag operations while maintaining clean separation between creation and behavior.
app/javascript/controllers/maps_controller.js (22)
7-7: LGTM: New LiveMapHandler import supports modularization.The import of
LiveMapHandleraligns with the refactoring to delegate live map functionality to a dedicated class for better memory management and modularity.
34-35: LGTM: Import refactoring improves modularity.The reduction of helper imports to just
showFlashMessageand the addition offetchAndDisplayPhotosfrom a dedicated photos module reflects good separation of concerns.
84-84: LGTM: Fog line threshold configuration added.Adding
fogLineThresholdconfiguration improves customizability of the fog of war feature.
158-158: LGTM: Simplified fog overlay creation.The fog overlay creation has been streamlined to create a single persistent instance, which aligns with the unified fog overlay management mentioned in the refactoring goals.
238-240: LGTM: Clear documentation for LiveMapHandler initialization.The comment clearly explains the purpose of the new initialization method.
312-343: LGTM: Comprehensive LiveMapHandler initialization.The initialization method properly configures the LiveMapHandler with all necessary layers and options. The approach of passing existing data to the handler enables continuity between static and live modes.
345-354: LGTM: Proper delegation to LiveMapHandler with appropriate fallback.The appendPoint method correctly delegates to the LiveMapHandler when live mode is enabled and provides a helpful warning when the handler is not available or live mode is disabled.
380-381: LGTM: World borders data caching improves performance.Caching the world borders data prevents unnecessary re-fetching of static geographic data.
419-473: LGTM: Dynamic scratch layer refresh with proper caching.The new
refreshScratchLayer()method provides dynamic updates while efficiently reusing cached world borders data. The error handling and logging are appropriate for debugging.
570-603: LGTM: Enhanced layer event handlers support modular functionality.The new event handlers properly integrate with the refactored photo, visits, and scratch map modules. The date parameter extraction and delegation to appropriate managers follows good separation of concerns.
622-632: LGTM: Proper cleanup when visits layer is removed.The handler correctly clears visit circles when the Suggested Visits layer is disabled, ensuring proper memory management and state consistency.
705-705: LGTM: Consistent fog overlay references throughout controller.The consistent use of
this.fogOverlaythroughout the controller ensures proper integration with the unified fog overlay management approach.Also applies to: 1117-1117, 1927-1927
750-754: LGTM: Improved addLastMarker method with proper return handling.The method now returns the marker reference for tracking and properly handles the null case when no markers exist.
756-761: LGTM: Performance improvement with requestAnimationFrame.Using
requestAnimationFramefor the fog canvas drawing operation ensures optimal timing with the browser's render cycle, improving performance.
1027-1034: LGTM: Proper LiveMapHandler integration with settings.The settings update correctly enables or disables the LiveMapHandler based on the
live_map_enabledsetting, ensuring the handler state stays synchronized with user preferences.
1086-1086: LGTM: Local property synchronization with settings.Updating the local
liveMapEnabledproperty ensures consistency between the controller state and user settings.
1165-1190: LGTM: Fixed scope issue in toggle panel button.Storing the controller reference and using it in the event handler properly fixes the JavaScript scope issue where
thiswould not refer to the controller instance inside the Leaflet control.
1370-1384: LGTM: Consistent photo initialization based on user settings.The automatic photo fetching when
photos_enabledis true in user settings ensures a consistent experience and matches the pattern used in layer event handlers.
1388-1388: LGTM: Updated fog initialization with new method signature.The fog initialization correctly uses the updated
updateFogmethod signature with thefogLineThresholdparameter.
1392-1403: LGTM: Enhanced visits initialization with improved logic.The visits initialization properly checks for enabled confirmed visits layers and includes helpful logging for debugging the initialization process.
1502-1504: LGTM: Improved panel state management.Setting the panel display to 'block' and updating localStorage ensures proper visibility and remembers user preferences across sessions.
718-718: LGTM: Consistent fog update method usage.The
updateFogcall in thedeletePointmethod correctly uses the updated method signature with thefogLineThresholdparameter.
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores
Style