Skip to content

Conversation

@google-labs-jules
Copy link
Contributor

@google-labs-jules google-labs-jules bot commented Jan 13, 2026

User description

This change adds OpenStreetMap as a new mapping option in the settings, with full feature parity to the existing map providers. It includes drawing tools, measurement labels, and integration with the geospatial analysis tools.


PR created automatically by Jules for task 834065588649487967 started by @ngoiyaeric


PR Type

Enhancement


Description

  • Adds OpenStreetMap as new selectable map provider option

  • Implements OSMMap component with drawing and measurement tools

  • Integrates Leaflet and react-leaflet dependencies for map functionality

  • Enables Gemini geospatial tool compatibility with OSM provider

  • Updates settings UI and state management for provider selection


Diagram Walkthrough

flowchart LR
  A["Settings UI"] -->|"Add OSM option"| B["MapProvider Component"]
  B -->|"Route to OSM"| C["OSMMap Component"]
  C -->|"Uses"| D["Leaflet Libraries"]
  D -->|"Provides"| E["Drawing & Measurement"]
  F["Geospatial Tool"] -->|"Support OSM"| G["Gemini Integration"]
Loading

File Walkthrough

Relevant files
Enhancement
settings.ts
Add OSM to MapProvider type definition                                     

lib/store/settings.ts

  • Extended MapProvider type to include 'osm' option alongside 'mapbox'
    and 'google'
+1/-1     
osm-map.tsx
Implement OSMMap component with drawing tools                       

components/map/osm-map.tsx

  • Created new OSMMap component with MapContainer and TileLayer from
    react-leaflet
  • Implemented DrawControl with polygon and polyline drawing capabilities
  • Added measurement label generation using Turf.js for area and distance
    calculations
  • Integrated map state management for camera position and drawn features
  • Fixed Leaflet icon paths for Next.js compatibility
+187/-0 
osm-map.css
Add measurement label styling for OSM map                               

components/map/osm-map.css

  • Created stylesheet for measurement labels with semi-transparent
    background
  • Styled labels with padding, border-radius, and shadow for visibility
+11/-0   
map-provider.tsx
Integrate OSMMap into map provider routing                             

components/map/map-provider.tsx

  • Added dynamic import for OSMMap component with loading state
  • Updated conditional rendering to route 'osm' provider to OSMMap
    component
+7/-0     
settings.tsx
Add OpenStreetMap option to settings UI                                   

components/settings/components/settings.tsx

  • Added OpenStreetMap radio button option to map provider selection UI
  • Maintains consistency with existing Mapbox and Google Maps options
+4/-0     
geospatial.tsx
Enable Gemini geospatial tool for OSM provider                     

lib/agents/tools/geospatial.tsx

  • Extended Gemini geospatial tool condition to support OSM provider
  • Changed condition from 'google' only to 'google' or 'osm' for
    grounding
+1/-1     
Dependencies
package.json
Add Leaflet and react-leaflet dependencies                             

package.json

  • Added leaflet, leaflet-draw, and react-leaflet dependencies
  • Added type definitions for leaflet, leaflet-draw, and react-leaflet
+6/-0     

Summary by CodeRabbit

Release Notes

  • New Features
    • Added OpenStreetMap as a selectable map provider with integrated drawing tools for creating and editing polygons and polylines
    • Measurements automatically display for drawn shapes, showing area and length calculations
    • Enhanced visual styling for measurement labels with improved readability

✏️ Tip: You can customize this high-level summary in your review settings.

This commit introduces OpenStreetMap (OSM) as a new selectable map provider in the application settings.

Key changes include:
- Added `react-leaflet`, `leaflet`, and `leaflet-draw` dependencies to support the new map implementation.
- Created a new `OSMMap` component (`components/map/osm-map.tsx`) with feature parity to the existing maps, including drawing tools for polygons/lines and measurement labels.
- Updated the settings UI (`components/settings/components/settings.tsx`) and state management (`lib/store/settings.ts`) to include the OSM option.
- Modified the main map provider (`components/map/map-provider.tsx`) to conditionally render the `OSMMap` component.
- Integrated the `geospatialTool` (`lib/agents/tools/geospatial.tsx`) to be compatible with the OSM provider, using the same Gemini grounding mechanism as Google Maps.

Note: The end-to-end test suite is currently unstable and failing with timeouts. Frontend verification was also blocked by issues in the test environment. These issues are likely unrelated to the changes in this commit and require separate investigation.
@google-labs-jules
Copy link
Contributor Author

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link

vercel bot commented Jan 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
qcx Ready Ready Preview, Comment Jan 14, 2026 9:35am

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This PR adds OpenStreetMap support as a new map provider option. It introduces an OSMMap component using Leaflet and react-leaflet with integrated drawing tools, measurement labeling via Turf.js, and synchronization of camera state and drawn features with shared map data.

Changes

Cohort / File(s) Summary
Map Provider Core
lib/store/settings.ts
Extends MapProvider type to include 'osm' option alongside existing 'mapbox' and 'google' providers
Map Provider Routing
components/map/map-provider.tsx
Adds conditional branch to dynamically import and render OSMMap component when mapProvider === 'osm'
OSM Map Component
components/map/osm-map.tsx
New React-Leaflet component implementing OSM tiles, DrawControl for user-drawn polygons/polylines, Turf-based measurement display, map event handlers for camera state sync, and Leaflet icon initialization for Next.js
OSM Map Styling
components/map/osm-map.css
Introduces CSS rule for .leaflet-measurement-label with semi-transparent background, padding, rounded corners, bold font, drop shadow, and text truncation
Settings UI
components/settings/components/settings.tsx
Adds OSM radio option to Map Provider selection group in Settings UI
Geospatial Tools
lib/agents/tools/geospatial.tsx
Expands Gemini model trigger condition to activate for both 'google' and 'osm' map providers
Dependencies
package.json
Adds runtime dependencies (leaflet, leaflet-draw, react-leaflet) and TypeScript definitions (@types/leaflet, @types/leaflet-draw, @types/react-leaflet)

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant OSMMap
    participant DrawControl as DrawControl<br/>(Leaflet)
    participant Turf
    participant MapData as Shared<br/>MapData
    participant MapEvents

    User->>OSMMap: Mount component
    OSMMap->>DrawControl: Initialize FeatureGroup & attach draw control
    
    User->>DrawControl: Draw polygon/polyline
    DrawControl->>OSMMap: Trigger draw event
    OSMMap->>Turf: Calculate area/length
    Turf-->>OSMMap: Measurement values
    OSMMap->>OSMMap: Render measurement label
    OSMMap->>MapData: Update drawn features (GeoJSON)
    
    User->>OSMMap: Pan/Zoom map
    OSMMap->>MapEvents: Detect move/zoom end
    MapEvents->>MapData: Update cameraState (center, zoom)
    
    User->>DrawControl: Edit/delete feature
    DrawControl->>OSMMap: Trigger edit/delete event
    OSMMap->>Turf: Recalculate measurement
    OSMMap->>MapData: Sync updated features
    OSMMap->>OSMMap: Update labels
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🗺️ A leafy hop through OpenStreet,
Where drawn polygons and measurements meet,
The rabbit hops, the map stays true,
Camera state syncs, features too!
Three providers now, oh what a sight—
OSM's the newest, shining bright! 🐰

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add OpenStreetMap as a Map Provider' directly and clearly summarizes the main objective of the pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

This commit introduces OpenStreetMap (OSM) as a new selectable map provider in the application settings.

Key changes include:
- Added `react-leaflet`, `leaflet`, and `leaflet-draw` dependencies to support the new map implementation.
- Created a new `OSMMap` component (`components/map/osm-map.tsx`) with feature parity to the existing maps, including drawing tools for polygons/lines and measurement labels.
- Updated the settings UI (`components/settings/components/settings.tsx`) and state management (`lib/store/settings.ts`) to include the OSM option.
- Modified the main map provider (`components/map/map-provider.tsx`) to conditionally render the `OSMMap` component.
- Integrated the `geospatialTool` (`lib/agents/tools/geospatial.tsx`) to be compatible with the OSM provider, using the same Gemini grounding mechanism as Google Maps.
- Fixed TypeScript errors in the `OSMMap` component to ensure a successful build.

Note: The end-to-end test suite is currently unstable and failing with timeouts. Frontend verification was also blocked by issues in the test environment. These issues are likely unrelated to the changes in this commit and require separate investigation.
@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 14, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive data logging

Description: The new/retained server-side console.log prints full geospatial tool params (likely
including user-entered location queries/addresses) which can leak sensitive user data into
application logs and any downstream log aggregation systems.
geospatial.tsx [236-237]

Referred Code
console.log('[GeospatialTool] Execute called with:', params, 'and map provider:', mapProvider);
Third-party data exposure

Description: Loading map tiles from the third-party OpenStreetMap tile server
(https://{s}.tile.openstreetmap.org/...) can expose users' IP addresses and map
view/interaction-derived location data to an external service, which may violate
privacy/compliance expectations if not disclosed/consented and not routed via a controlled
proxy.
osm-map.tsx [176-179]

Referred Code
<TileLayer
  attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing failure handling: The new OSMMap does not show any explicit handling for common external dependency failures
(e.g., tile load/network errors or Leaflet draw/control initialization issues), so it is
unclear how the UI degrades when OSM tiles or map controls fail.

Referred Code
return (
  <>
    <MapContainer
      center={initialCenter}
      zoom={initialZoom}
      scrollWheelZoom={true}
      className="h-full w-full"
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <FeatureGroup>
        <DrawControl />
      </FeatureGroup>
      <MapEvents onMoveEnd={handleMapMoveEnd} />
    </MapContainer>

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 14, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Use existing map library for OSM

Instead of adding Leaflet as a new dependency to support OpenStreetMap, adapt
the existing Mapbox component to render OSM tiles. This avoids increasing
application complexity and bundle size.

Examples:

components/map/osm-map.tsx [1-187]
'use client'

import { useEffect, useRef, useCallback } from 'react'
import { MapContainer, TileLayer, FeatureGroup, useMap, useMapEvents } from 'react-leaflet'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet-draw/dist/leaflet.draw.css'
import * as turf from '@turf/turf'
import { useMapData } from './map-data-context'
import { useMapLoading } from '../map-loading-context'

 ... (clipped 177 lines)
package.json [51-91]
    "@types/leaflet": "^1.9.21",
    "@types/leaflet-draw": "^1.0.13",
    "@types/mapbox__mapbox-gl-draw": "^1.4.8",
    "@types/pg": "^8.15.4",
    "@types/react-leaflet": "^3.0.0",
    "@upstash/redis": "^1.35.0",
    "@vercel/analytics": "^1.5.0",
    "@vercel/speed-insights": "^1.2.0",
    "@vis.gl/react-google-maps": "^1.7.1",
    "ai": "^4.3.19",

 ... (clipped 31 lines)

Solution Walkthrough:

Before:

// file: components/map/map-provider.tsx
const Mapbox = dynamic(() => import('./mapbox-map'));
const GoogleMapComponent = dynamic(() => import('./google-map'));
const OSMMap = dynamic(() => import('./osm-map')); // New dependency stack

export function MapProvider(...) {
  const { mapProvider } = useSettingsStore();

  if (mapProvider === 'google') {
    return <GoogleMapComponent />;
  } else if (mapProvider === 'osm') {
    return <OSMMap />; // Renders new Leaflet-based map
  } else {
    return <Mapbox />;
  }
}

// file: components/map/osm-map.tsx
// A completely new component using react-leaflet and leaflet-draw
// with its own implementation for drawing, labels, and state management.

After:

// file: components/map/map-provider.tsx
const Mapbox = dynamic(() => import('./mapbox-map'));
const GoogleMapComponent = dynamic(() => import('./google-map'));
// No OSMMap import needed

export function MapProvider(...) {
  const { mapProvider } = useSettingsStore();

  if (mapProvider === 'google') {
    return <GoogleMapComponent />;
  } else {
    // Re-use Mapbox component for both 'mapbox' and 'osm'
    return <Mapbox provider={mapProvider} ... />;
  }
}

// file: components/map/mapbox-map.tsx (conceptual change)
export function Mapbox({ provider, ... }) {
  // Logic to switch tile source based on provider prop
  const tileSourceUrl = provider === 'osm' ? 'https://...osm-url...' : 'mapbox://...';
  // ... existing Mapbox GL implementation uses the selected tile source
}
Suggestion importance[1-10]: 9

__

Why: This is a significant architectural suggestion that correctly identifies the addition of a redundant mapping library (Leaflet), proposing a much cleaner solution that reduces bundle size, complexity, and future maintenance effort.

High
Possible issue
Fix memory leak by removing labels

In the DrawControl component's useEffect cleanup function, add logic to remove
all measurement label markers from the map to prevent memory leaks.

components/map/osm-map.tsx [119-127]

 return () => {
   map.off(L.Draw.Event.CREATED, onDrawCreated)
   map.off(L.Draw.Event.EDITED, onDrawEdited)
   map.off(L.Draw.Event.DELETED, onDrawDeleted)
+
+  // Clean up labels
+  Object.values(labelsRef.current).forEach(marker => marker.remove())
+  labelsRef.current = {}
+
   if (map.hasLayer(featureGroup)) {
     map.removeLayer(featureGroup)
   }
   map.removeControl(drawControl)
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a memory leak where measurement labels are not cleaned up on component unmount and provides a correct fix, improving the component's stability.

Medium
Prevent runtime error on map move

In handleMapMoveEnd, add a fallback to an empty object when spreading
prev.cameraState to prevent a runtime error if it is null or undefined.

components/map/osm-map.tsx [155-166]

 const handleMapMoveEnd = useCallback((map: L.Map) => {
   const center = map.getCenter()
   const zoom = map.getZoom()
   setMapData(prev => ({
     ...prev,
     cameraState: {
-      ...prev.cameraState,
+      ...(prev.cameraState || {}),
       center: { lat: center.lat, lng: center.lng },
       zoom,
     },
   }))
 }, [setMapData])

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential runtime error if cameraState is null or undefined and provides a simple, effective fix to improve code robustness.

Medium
General
Separate OSM geospatial handling

Separate the logic for the 'osm' map provider in the geospatial tool, as the
current implementation for Gemini is only compatible with Google Maps.

lib/agents/tools/geospatial.tsx [243]

-if (selectedModel?.includes('gemini') && (mapProvider === 'google' || mapProvider === 'osm')) {
+if (selectedModel?.includes('gemini') && mapProvider === 'google') {
+  // existing Gemini + Google logic
+} else if (selectedModel?.includes('gemini') && mapProvider === 'osm') {
+  // TODO: implement or fallback for OSM-based geospatial queries
+}

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the existing Gemini logic is incompatible with the newly added 'osm' provider, preventing a potential runtime error or incorrect behavior.

Medium
Improve midpoint calculation for labels

Improve the polyline label positioning by using turf.along to calculate the true
geometric midpoint, rather than just selecting the middle vertex.

components/map/osm-map.tsx [63-65]

-const line = geojson.geometry.coordinates
-const midpoint = line[Math.floor(line.length / 2)]
+const midpointFeature = turf.along(geojson, length / 2)
+const midpoint = midpointFeature.geometry.coordinates
 labelPos = [midpoint[1], midpoint[0]]
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion provides a more accurate geometric calculation for placing labels on polylines, which improves the user experience by ensuring labels are visually centered.

Low
Use static imports for Leaflet icons

Replace require() calls with ES module import statements for Leaflet marker icon
images to align with modern JavaScript practices.

components/map/osm-map.tsx [15-20]

+import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png'
+import markerIcon from 'leaflet/dist/images/marker-icon.png'
+import markerShadow from 'leaflet/dist/images/marker-shadow.png'
+
 delete (L.Icon.Default.prototype as any)._getIconUrl
 L.Icon.Default.mergeOptions({
-  iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png').default,
-  iconUrl: require('leaflet/dist/images/marker-icon.png').default,
-  shadowUrl: require('leaflet/dist/images/marker-shadow.png').default,
+  iconRetinaUrl: markerIcon2x,
+  iconUrl: markerIcon,
+  shadowUrl: markerShadow,
 })
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion improves code style by replacing require with modern ES module import statements, which can lead to better static analysis and bundling.

Low
  • Update

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OSM integration is broadly on the right track, but a few changes are needed to avoid inconsistent behavior and future brittleness. The biggest concerns are: (1) position is ignored for OSM (and Google), creating cross-provider inconsistency; (2) global Leaflet icon patching at module scope with any/require is brittle; and (3) measurement labeling is both inefficient and incorrect for lines (vertex midpoint vs distance midpoint). Additionally, setIsMapLoaded(true) should reflect real map readiness rather than mount time, and the geospatial Gemini gate for osm should be backed by explicit capability/adapter logic.

Additional notes (3)
  • Maintainability | components/map/map-provider.tsx:16-16
    OSMMap accepts no props, but MapProvider receives an optional position (lat/lng) and forwards it only to Mapbox. This means switching to OSM ignores a caller-provided position and also behaves differently from the Google branch (which also ignores position). If position is intended as a cross-provider feature (e.g., jump-to / initial center), OSM should honor it to avoid confusing and inconsistent behavior.

Right now OSM initializes from mapData.cameraState or a hard-coded London fallback, which may not match the rest of the app’s expectations when a position is supplied.

  • Performance | components/map/osm-map.tsx:133-166

MapEvents triggers state writes on every moveend/zoomend without change detection

handleMapMoveEnd unconditionally writes a new cameraState object to the store after every move/zoom end. If consumers subscribe to cameraState, this can create avoidable re-renders even when values are effectively unchanged (and it will always be “changed” by reference).

This is especially relevant for maps where move/zoom events can be frequent and UI is heavy.

  • Maintainability | lib/agents/tools/geospatial.tsx:243-246
    The geospatial tool now treats osm like google for the Gemini branch. However, the comment/context suggests Gemini “grounding mechanism as Google Maps”. If that branch relies on Google-specific assumptions (e.g., interpreting coordinates against Google map viewport semantics or expecting Google UI hooks), osm may behave incorrectly.

In this diff, only the provider gate changed; there is no visible provider-specific adaptation inside the Gemini path.

Summary of changes

Summary

This PR adds OpenStreetMap (OSM) as a selectable map provider with a new Leaflet-based implementation.

Key changes

  • Map provider selection

    • Extends MapProvider union to include osm (lib/store/settings.ts).
    • Adds OpenStreetMap option to Settings UI radio group (components/settings/components/settings.tsx).
    • Updates map routing to dynamically render the new OSMMap component (components/map/map-provider.tsx).
  • New OSM map implementation

    • Adds components/map/osm-map.tsx using react-leaflet + leaflet-draw and @turf/turf for measurements.
    • Adds components/map/osm-map.css for measurement label styling.
  • Tooling integration

    • Updates geospatial tool to treat osm similarly to google for Gemini processing (lib/agents/tools/geospatial.tsx).
  • Dependencies

    • Adds Leaflet/React-Leaflet and type packages in package.json.

Comment on lines +13 to +20
// Leaflet's default icon path is not set up correctly in Next.js by default.
// This fix ensures that the marker icons are loaded correctly.
delete (L.Icon.Default.prototype as any)._getIconUrl
L.Icon.Default.mergeOptions({
iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png').default,
iconUrl: require('leaflet/dist/images/marker-icon.png').default,
shadowUrl: require('leaflet/dist/images/marker-shadow.png').default,
})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file mutates global Leaflet defaults at module scope:

  • delete (L.Icon.Default.prototype as any)._getIconUrl
  • L.Icon.Default.mergeOptions(...)

Because osm-map.tsx is dynamically imported, this will run each time the chunk is evaluated; in dev/HMR it may execute multiple times. It also uses as any and require(...).default, which is brittle and makes the behavior harder to reason about.

This should be guarded to run only once and avoid the any escape hatch if possible.

Suggestion

Wrap the Leaflet icon patch in a one-time guard and avoid any by narrowing with an interface for the private field.

Example:

// osm-map.tsx (module scope)
let leafletIconPatched = false;

function patchLeafletDefaultIconOnce() {
  if (leafletIconPatched) return;
  leafletIconPatched = true;

  type IconDefaultWithPrivate = L.Icon.Default & { _getIconUrl?: unknown };
  const proto = L.Icon.Default.prototype as IconDefaultWithPrivate;
  delete proto._getIconUrl;

  L.Icon.Default.mergeOptions({
    iconRetinaUrl: new URL('leaflet/dist/images/marker-icon-2x.png', import.meta.url).toString(),
    iconUrl: new URL('leaflet/dist/images/marker-icon.png', import.meta.url).toString(),
    shadowUrl: new URL('leaflet/dist/images/marker-shadow.png', import.meta.url).toString(),
  });
}

patchLeafletDefaultIconOnce();

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines +41 to +87
const updateMeasurementLabels = useCallback(() => {
const layers = featureGroupRef.current.getLayers() as (L.Polygon | L.Polyline)[]
const currentDrawnFeatures: any[] = []

// Clear existing labels
Object.values(labelsRef.current).forEach(marker => marker.remove())
labelsRef.current = {}

layers.forEach(layer => {
const id = L.Util.stamp(layer)
const geojson = layer.toGeoJSON()
let measurement = ''
let labelPos: L.LatLngExpression | undefined

if (geojson.geometry.type === 'Polygon') {
const area = turf.area(geojson)
measurement = formatMeasurement(area, true)
const center = turf.centroid(geojson)
labelPos = [center.geometry.coordinates[1], center.geometry.coordinates[0]]
} else if (geojson.geometry.type === 'LineString') {
const length = turf.length(geojson, { units: 'meters' })
measurement = formatMeasurement(length, false)
const line = geojson.geometry.coordinates
const midpoint = line[Math.floor(line.length / 2)]
labelPos = [midpoint[1], midpoint[0]]
}

if (measurement && labelPos) {
const label = L.marker(labelPos, {
icon: L.divIcon({
className: 'leaflet-measurement-label',
html: `<span>${measurement}</span>`,
}),
}).addTo(map)
labelsRef.current[id] = label
}

currentDrawnFeatures.push({
id: id.toString(),
type: geojson.geometry.type,
measurement,
geometry: geojson.geometry,
});
})

setMapData(prev => ({ ...prev, drawnFeatures: currentDrawnFeatures }))
}, [map, setMapData])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateMeasurementLabels rebuilds all measurement markers on every create/edit/delete by removing all labels and re-adding them. For a small number of shapes this is fine, but it does not scale and will feel laggy with many features.

Also, the midpoint calculation for LineString uses the middle vertex (coordinates[Math.floor(n/2)]) rather than the midpoint along the line length, which produces incorrect labels for uneven segment lengths.

Suggestion

Improve both correctness and performance:

  1. Compute a true mid-point along the line using Turf:
const lengthKm = turf.length(geojson, { units: 'kilometers' });
const mid = turf.along(geojson, lengthKm / 2, { units: 'kilometers' });
labelPos = [mid.geometry.coordinates[1], mid.geometry.coordinates[0]];
  1. Update labels incrementally by layer id (add/update/remove) instead of clearing all markers each time. A simple first step is to only clear/recreate the label for the edited/created layer by using the event payload (e.layers, e.layer) from Leaflet Draw.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines +42 to +110
const layers = featureGroupRef.current.getLayers() as (L.Polygon | L.Polyline)[]
const currentDrawnFeatures: any[] = []

// Clear existing labels
Object.values(labelsRef.current).forEach(marker => marker.remove())
labelsRef.current = {}

layers.forEach(layer => {
const id = L.Util.stamp(layer)
const geojson = layer.toGeoJSON()
let measurement = ''
let labelPos: L.LatLngExpression | undefined

if (geojson.geometry.type === 'Polygon') {
const area = turf.area(geojson)
measurement = formatMeasurement(area, true)
const center = turf.centroid(geojson)
labelPos = [center.geometry.coordinates[1], center.geometry.coordinates[0]]
} else if (geojson.geometry.type === 'LineString') {
const length = turf.length(geojson, { units: 'meters' })
measurement = formatMeasurement(length, false)
const line = geojson.geometry.coordinates
const midpoint = line[Math.floor(line.length / 2)]
labelPos = [midpoint[1], midpoint[0]]
}

if (measurement && labelPos) {
const label = L.marker(labelPos, {
icon: L.divIcon({
className: 'leaflet-measurement-label',
html: `<span>${measurement}</span>`,
}),
}).addTo(map)
labelsRef.current[id] = label
}

currentDrawnFeatures.push({
id: id.toString(),
type: geojson.geometry.type,
measurement,
geometry: geojson.geometry,
});
})

setMapData(prev => ({ ...prev, drawnFeatures: currentDrawnFeatures }))
}, [map, setMapData])

useEffect(() => {
const featureGroup = featureGroupRef.current
map.addLayer(featureGroup)

const drawControl = new L.Control.Draw({
edit: { featureGroup },
draw: {
polygon: {},
polyline: {},
rectangle: false,
circle: false,
marker: false,
circlemarker: false,
},
})
map.addControl(drawControl)

const onDrawCreated = (e: any) => {
const layer = e.layer
featureGroup.addLayer(layer)
updateMeasurementLabels()
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currentDrawnFeatures is typed as any[] and the draw event is typed as any. This is type-valid but reduces maintainability and increases the chance of runtime mistakes (e.g., assuming e.layer exists for all events).

Since this is core state (drawnFeatures) used elsewhere, it’s worth giving it an explicit shape and narrowing the event types.

Suggestion

Introduce a small local type for the drawn feature payload and minimally type the draw event(s) you use.

Example:

type DrawnFeature = {
  id: string;
  type: 'Polygon' | 'LineString';
  measurement: string;
  geometry: GeoJSON.Polygon | GeoJSON.LineString;
};

const currentDrawnFeatures: DrawnFeature[] = [];

const onDrawCreated = (e: L.LeafletEvent & { layer: L.Layer }) => {
  const layer = e.layer as L.Polygon | L.Polyline;
  featureGroup.addLayer(layer);
  updateMeasurementLabels();
};

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines +150 to +154
useEffect(() => {
setIsMapLoaded(true)
return () => setIsMapLoaded(false)
}, [setIsMapLoaded])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setIsMapLoaded(true) is called immediately on mount, not when Leaflet tiles/layers have actually loaded. That can cause downstream UI (or tools) to assume the map is ready while tiles are still blank or the map size hasn’t stabilized.

Leaflet exposes map events like load and tile-layer events like load/tileload that are more accurate signals.

Suggestion

Tie setIsMapLoaded(true) to a real Leaflet readiness event.

Example using whenReady:

<MapContainer
  ...
  whenReady={() => setIsMapLoaded(true)}
>

And keep the cleanup to set it false on unmount.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Comment on lines +170 to +183
<MapContainer
center={initialCenter}
zoom={initialZoom}
scrollWheelZoom={true}
className="h-full w-full"
>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<FeatureGroup>
<DrawControl />
</FeatureGroup>
<MapEvents onMoveEnd={handleMapMoveEnd} />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary nested FeatureGroup and potential layer ownership confusion

You render a React <FeatureGroup> but the draw logic uses an independent featureGroupRef (new L.FeatureGroup()) that you manually add to the map. The React <FeatureGroup> wrapper is unused for controlling the layers and may confuse future maintainers (it implies children will be in that group, but DrawControl adds layers to the manually-managed group).

This can also complicate cleanup and make it harder to extend (e.g., if someone expects drawn layers to be in the React-managed group).

Suggestion

Either (a) remove the React <FeatureGroup> wrapper entirely, or (b) wire leaflet-draw to the React FeatureGroup instance instead of creating your own.

Simplest option is removing the wrapper:

<MapContainer ...>
  <TileLayer ... />
  <DrawControl />
  <MapEvents onMoveEnd={handleMapMoveEnd} />
</MapContainer>

Reply with "@CharlieHelps yes please" if you'd like me to add a commit applying this cleanup.

@charliecreates charliecreates bot removed the request for review from CharlieHelps January 14, 2026 07:29
@ngoiyaeric
Copy link
Collaborator

@coderabbitai do a thorough review of the pull request and ensure the logic is solid and that the feature is working to perfection.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

@ngoiyaeric: I'll perform a thorough review of this pull request to ensure the OpenStreetMap integration is implemented correctly with solid logic and proper functionality. I'll examine the OSMMap component implementation, dependency integration, state management, and the Gemini geospatial tool compatibility.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

This commit fixes several TypeScript errors in the `OSMMap` component that were causing the production build to fail.

- Corrected the `leaflet-draw` options to use empty objects `{}` instead of booleans to enable drawing tools, satisfying the type definitions.
- Refactored the map event handling to use the `useMapEvents` hook, which is the correct approach for `react-leaflet`. This resolves the type error related to event handlers on the `MapContainer` component.

With these changes, the project now builds successfully.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/settings/components/settings.tsx (1)

203-220: Consider persisting the map provider selection.

The map provider choice is stored only in the zustand store (in-memory) and will reset on page refresh. If persistence is desired, consider adding it to the form schema and saving it alongside other settings.

This is pre-existing behavior for all map providers and not a blocker for this PR.

🤖 Fix all issues with AI agents
In `@components/map/osm-map.tsx`:
- Around line 119-128: The cleanup currently removes draw listeners, the
featureGroup and drawControl but forgets to remove measurement label markers
stored in labelsRef.current; iterate over labelsRef.current (if any), remove
each marker from the map (e.g., marker.remove() or map.removeLayer(marker)),
then clear labelsRef.current (set to empty array or null) before returning so no
orphaned label markers remain after unmount; apply this change in the same
effect cleanup where map.off(...) and map.removeControl(drawControl) are called.

In `@package.json`:
- Around line 51-55: The package.json lists a conflicting types package: remove
the "@types/react-leaflet" dependency entry so it no longer coexists with
react-leaflet v5's bundled types; edit package.json to delete the line
containing "@types/react-leaflet": "^3.0.0" and run npm/yarn install to update
lockfile and node_modules, keeping "@types/leaflet" intact.
♻️ Duplicate comments (5)
components/map/osm-map.tsx (5)

13-20: Module-scope icon patch should be guarded for idempotency.

This mutation runs on every module load/HMR cycle. Wrap in a one-time guard as previously suggested.


41-66: Midpoint calculation and incremental update issues previously flagged.

The vertex-based midpoint (line 64) and full rebuild on every change (lines 45-47) were already identified. Consider using turf.along() for true midpoint and incremental updates.


43-43: Weak typing with any reduces type safety.

currentDrawnFeatures: any[] and (e: any) were previously flagged. Consider introducing explicit types for drawn features and Leaflet Draw events.

Also applies to: 106-106


150-154: setIsMapLoaded(true) called on mount, not on actual map readiness.

Previously flagged: this signals readiness immediately rather than when tiles/layers have loaded. Use MapContainer's whenReady prop for accurate signaling.


180-182: Redundant React <FeatureGroup> wrapper.

Previously flagged: DrawControl uses its own featureGroupRef added directly to the map, making the React <FeatureGroup> wrapper unnecessary and potentially confusing.

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 196583e and 5669156.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • components/map/map-provider.tsx
  • components/map/osm-map.css
  • components/map/osm-map.tsx
  • components/settings/components/settings.tsx
  • lib/agents/tools/geospatial.tsx
  • lib/store/settings.ts
  • package.json
🧰 Additional context used
🧬 Code graph analysis (3)
lib/store/settings.ts (1)
components/map/map-provider.tsx (1)
  • MapProvider (21-35)
components/map/osm-map.tsx (3)
components/map/map-context.tsx (1)
  • useMap (24-30)
components/map/map-data-context.tsx (1)
  • useMapData (50-56)
components/map-loading-context.tsx (1)
  • useMapLoading (20-26)
components/map/map-provider.tsx (2)
components/map/osm-map.tsx (1)
  • OSMMap (141-187)
lib/store/settings.ts (2)
  • MapProvider (3-3)
  • useSettingsStore (10-13)
🔇 Additional comments (11)
lib/agents/tools/geospatial.tsx (1)

243-243: LGTM! Sensible extension for OSM support.

The change correctly extends Gemini-based geolocation to also work when the OSM map provider is selected. Since the Gemini grounding returns location coordinates (latitude/longitude) which are map-provider agnostic, this approach is sound.

lib/store/settings.ts (1)

3-3: LGTM!

The type expansion is clean and minimal. The existing default value ('mapbox') is preserved, ensuring backward compatibility.

components/settings/components/settings.tsx (1)

216-219: LGTM!

The OpenStreetMap option follows the same pattern as existing providers with proper accessibility linking between RadioGroupItem and Label.

package.json (1)

75-76: LGTM on runtime dependencies.

The Leaflet ecosystem packages (leaflet, leaflet-draw, react-leaflet) are correctly added to support the OSM map component.

Also applies to: 91-91

components/map/map-provider.tsx (2)

16-19: LGTM!

The dynamic import follows the established pattern with ssr: false (required for Leaflet's browser-only APIs) and a consistent loading placeholder.


28-29: Consider prop interface consistency with other map components.

OSMMap is rendered without the position prop that Mapbox receives. According to the snippet from osm-map.tsx, OSMMap derives its initial position from mapData.cameraState instead.

This difference in initialization patterns could lead to inconsistent behavior when switching providers. Verify this is intentional or consider aligning the interfaces.

components/map/osm-map.css (1)

1-11: LGTM!

The measurement label styling is appropriate with good visual presentation (semi-transparent background, subtle shadow) and correct behavior (pointer-events: none prevents interaction interference, white-space: nowrap keeps labels compact).

components/map/osm-map.tsx (4)

1-12: LGTM!

Imports are well-organized. Using useMap from react-leaflet is correct here for accessing the Leaflet map instance within the MapContainer context (distinct from the app-level useMap in map-context.tsx).


22-33: LGTM!

The measurement formatting logic correctly handles unit conversion with appropriate thresholds and decimal precision.


133-139: LGTM!

Clean event subscription pattern using useMapEvents. Both moveend and zoomend appropriately trigger camera state synchronization.


155-166: LGTM!

Camera state synchronization is well-implemented with useCallback for stable reference. Spreading prev.cameraState handles undefined gracefully.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +119 to +128
return () => {
map.off(L.Draw.Event.CREATED, onDrawCreated)
map.off(L.Draw.Event.EDITED, onDrawEdited)
map.off(L.Draw.Event.DELETED, onDrawDeleted)
if (map.hasLayer(featureGroup)) {
map.removeLayer(featureGroup)
}
map.removeControl(drawControl)
}
}, [map, updateMeasurementLabels])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Memory leak: measurement labels not cleaned up on unmount.

The cleanup function removes the feature group and draw control, but measurement label markers stored in labelsRef.current are not removed. When DrawControl unmounts, these markers remain orphaned on the map.

Proposed fix
     return () => {
+      // Clean up measurement labels
+      Object.values(labelsRef.current).forEach(marker => marker.remove())
+      labelsRef.current = {}
+
       map.off(L.Draw.Event.CREATED, onDrawCreated)
       map.off(L.Draw.Event.EDITED, onDrawEdited)
       map.off(L.Draw.Event.DELETED, onDrawDeleted)
       if (map.hasLayer(featureGroup)) {
         map.removeLayer(featureGroup)
       }
       map.removeControl(drawControl)
     }
🤖 Prompt for AI Agents
In `@components/map/osm-map.tsx` around lines 119 - 128, The cleanup currently
removes draw listeners, the featureGroup and drawControl but forgets to remove
measurement label markers stored in labelsRef.current; iterate over
labelsRef.current (if any), remove each marker from the map (e.g.,
marker.remove() or map.removeLayer(marker)), then clear labelsRef.current (set
to empty array or null) before returning so no orphaned label markers remain
after unmount; apply this change in the same effect cleanup where map.off(...)
and map.removeControl(drawControl) are called.

Comment on lines +51 to +55
"@types/leaflet": "^1.9.21",
"@types/leaflet-draw": "^1.0.13",
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
"@types/pg": "^8.15.4",
"@types/react-leaflet": "^3.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

react-leaflet v5 TypeScript definitions bundled included

💡 Result:

Yes — react-leaflet v5 includes/bundles its own TypeScript definitions. You still need to install Leaflet's types (e.g. @types/leaflet) as a dev dependency. [1][2]

Sources:

  • React Leaflet docs (installation / TypeScript support). [1]
  • React Leaflet v5 core API (exports types/interfaces). [2]

🌐 Web query:

@types/react-leaflet v3 react-leaflet v5 type conflicts compatibility

💡 Result:

Short answer: don't install @types/react-leaflet with react-leaflet v5 — it's a deprecated stub for older releases and will conflict with the built-in typings in react-leaflet v5. Remove @types/react-leaflet and keep only @types/leaflet (and a modern TS version). [1][2][3][4]

Why / action items:

  • react-leaflet v5 ships its own TypeScript definitions; the docs instruct using those and installing @types/leaflet only. [1][3]
  • @types/react-leaflet on npm is a deprecated stub (for older v3-era definitions) and can cause duplicate/type conflicts. Remove it. [2]
  • Also ensure your TypeScript version supports newer syntax (e.g. "export type" etc. — TS ≥3.8/4.x depending on your toolchain) to avoid related compiler errors. [4]

If you want, tell me your package.json (react-leaflet, @types/react-leaflet, @types/leaflet, typescript versions) and I’ll give exact commands to fix it.


Remove @types/react-leaflet from package.json — it conflicts with react-leaflet v5's bundled types.

react-leaflet v5.0.0 includes its own TypeScript definitions and should not be paired with @types/react-leaflet. The @types/react-leaflet package is a deprecated stub for older releases and will cause type conflicts. Remove line 55 ("@types/react-leaflet": "^3.0.0"); keep only @types/leaflet.

🤖 Prompt for AI Agents
In `@package.json` around lines 51 - 55, The package.json lists a conflicting
types package: remove the "@types/react-leaflet" dependency entry so it no
longer coexists with react-leaflet v5's bundled types; edit package.json to
delete the line containing "@types/react-leaflet": "^3.0.0" and run npm/yarn
install to update lockfile and node_modules, keeping "@types/leaflet" intact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants