Skip to content

Fix: Mapbox preview not updating on search#223

Closed
ngoiyaeric wants to merge 3 commits intomainfrom
fix-mapbox-preview
Closed

Fix: Mapbox preview not updating on search#223
ngoiyaeric wants to merge 3 commits intomainfrom
fix-mapbox-preview

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Aug 15, 2025

User description

The Mapbox preview was not updating when a user searched for a location. This was because the flyTo animation was being interrupted and not correctly restarting.

This commit fixes the issue by:

  • Stopping any existing rotation before starting a new flyTo animation.
  • Ensuring the map rotation restarts after the flyTo animation is complete.
  • Using a finally block to ensure the isUpdatingPositionRef is always reset.
  • Fixing a few linting warnings.

PR Type

Bug fix


Description

  • Fix Mapbox preview not updating on search

  • Stop existing rotation before flyTo animation

  • Ensure rotation restarts after animation completes

  • Add proper error handling with finally block


Diagram Walkthrough

flowchart LR
  A["Search triggered"] --> B["Stop rotation"]
  B --> C["Start flyTo animation"]
  C --> D["Animation completes"]
  D --> E["Restart rotation"]
  E --> F["Reset position flag"]
Loading

File Walkthrough

Relevant files
Bug fix
mapbox-map.tsx
Fix map animation and rotation handling                                   

components/map/mapbox-map.tsx

  • Stop rotation before starting flyTo animation
  • Remove mapType condition for rotation restart
  • Add finally block for proper cleanup
  • Update dependency arrays for useCallback and useEffect
+5/-5     

Summary by CodeRabbit

  • Bug Fixes

    • Map rotation now reliably resumes after moves and is paused before navigating to a new target.
    • Improved stability when updating map position and during map unload to avoid stuck states and resource leaks.
    • Long-press drawing disabled in Real‑Time mode to prevent unintended inputs.
  • New Features

    • Drawings from the chat/map UI now synchronize to the server for persistence.

The Mapbox preview was not updating when a user searched for a location. This was because the `flyTo` animation was being interrupted and not correctly restarting.

This commit fixes the issue by:
- Stopping any existing rotation before starting a new `flyTo` animation.
- Ensuring the map rotation restarts after the `flyTo` animation is complete.
- Using a `finally` block to ensure the `isUpdatingPositionRef` is always reset.
- Fixing a few linting warnings.
@vercel
Copy link
Contributor

vercel bot commented Aug 15, 2025

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

Project Deployment Preview Comments Updated (UTC)
qcx Ready Preview Comment Aug 15, 2025 9:38am

@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 Aug 15, 2025

Walkthrough

Rotation now resumes unconditionally after moves and is explicitly stopped before programmatic target moves; MapData-driven position updates validate coordinates and halt rotation; long-press drawing disabled in RealTimeMode; provider composition changed (MapDataProvider now wraps AI); Chat reads map data via hook and syncs drawnFeatures to server; extensive debug logging added across map-query, geospatial tool, and actions.

Changes

Cohort / File(s) Summary
Map rotation & movement
components/map/mapbox-map.tsx
startRotation() is called unconditionally after position moves; stopRotation() is invoked before MapData-driven moves; updateMapPosition deps reduced to [startRotation, stopRotation]; rotation cleared before target moves.
State safety & cleanup
components/map/mapbox-map.tsx
Added finally to always reset isUpdatingPositionRef; long-press timer cleanup on unmount; preserved cleanup of draw controls, labels, rotation, geolocation watcher; removed unused useState import; map init effect now depends on setIsMapLoaded.
Target-position handling & validation
components/map/mapbox-map.tsx, components/map/map-query-handler.tsx
MapQueryHandler builds and logs a newMapData object with targetPosition ([lng, lat]) and mapFeature; Mapbox effect stops rotation, validates numeric lng/lat, and calls updateMapPosition(lat, lng) (rotation halted beforehand).
Interaction behavior
components/map/mapbox-map.tsx
Long-press handler now early-returns when in RealTimeMode (disables drawing there).
Provider composition
app/search/[id]/page.tsx
Reordered providers so MapDataProvider wraps AI (MapDataProvider outer, AI inner); adjusted closing tags/indentation.
Chat data access & server sync
components/chat.tsx
Removed MapDataProvider wrapper usage; switched to useMapData() (const { mapData } = useMapData()); added effect to call updateDrawingContext(id, mapData.drawnFeatures) when drawnFeatures exist.
Logging / observability
lib/agents/tools/geospatial.tsx, app/actions.tsx, components/map/map-query-handler.tsx
Added extensive JSON-formatted debug logs and stronger error handling in geospatial tool; added debug logs in actions when MAP_QUERY_TRIGGER is handled; MapQueryHandler logs toolOutput and newMapData before setMapData.
Public API
components/map/mapbox-map.tsx, app/search/[id]/page.tsx, components/chat.tsx, lib/agents/tools/geospatial.tsx
No exported/public function signatures were changed.

Sequence Diagram(s)

sequenceDiagram
  participant MapDataContext
  participant MapboxComponent
  participant Mapbox
  participant Rotation

  MapDataContext-->>MapboxComponent: emits targetPosition [lng, lat]
  MapboxComponent->>Rotation: stopRotation()
  MapboxComponent->>MapboxComponent: validate lng/lat (numbers)
  alt valid
    MapboxComponent->>Mapbox: updateMapPosition(lat,lng)
    Mapbox-->>MapboxComponent: move complete
    MapboxComponent->>Rotation: startRotation() (unconditional)
  else invalid
    MapboxComponent->>MapboxComponent: clear targetPosition/mapFeature
  end
Loading
sequenceDiagram
  participant App
  participant MapDataProvider
  participant AI
  participant Chat

  App->>MapDataProvider: mount (outer)
  MapDataProvider->>AI: provide AI (inner)
  AI->>Chat: render Chat
  Chat->>MapDataProvider: useMapData() (direct hook access)
  Chat->>Server: updateDrawingContext(id, drawnFeatures) [when present]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Review effort 3/5

Poem

I hop where markers pivot, then spin,
I stop the turn for each new begin.
Providers shifted, Chat listens from the hook,
Drawings sent upstream — a quick, merry look.
I twitch my whiskers and log every spin. 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-mapbox-preview

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@codiumai-pr-agent-free
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Timing Issue

The code sets isUpdatingPositionRef.current = false in both the setTimeout and finally block. This could lead to race conditions if the setTimeout executes after the finally block, potentially resetting flags in the wrong order.

  setTimeout(() => {
    startRotation()
    isUpdatingPositionRef.current = false
  }, 500)
} catch (error) {
  console.error('Error updating map position:', error)
} finally {
  isUpdatingPositionRef.current = false

@qodo-code-review
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Double Cleanup

isUpdatingPositionRef is set to false both inside the setTimeout callback and again in the finally block. This may be redundant and could hide timing issues or race conditions. Consider consolidating the state reset to a single, deterministic place.

  setTimeout(() => {
    startRotation()
    isUpdatingPositionRef.current = false
  }, 500)
} catch (error) {
  console.error('Error updating map position:', error)
} finally {
  isUpdatingPositionRef.current = false
}
Rotation Restart Timing

startRotation is called after a fixed 500ms timeout, regardless of whether moveend has fired or the map interaction is still in progress. Validate that this timeout reliably corresponds to the end of flyTo; otherwise, consider invoking rotation within the moveend handler to avoid premature or late restarts.

  setTimeout(() => {
    startRotation()
    isUpdatingPositionRef.current = false
  }, 500)
} catch (error) {
Dependency Changes

The updateMapPosition callback dependencies were changed to [startRotation, stopRotation]. Ensure any other variables referenced inside the callback (e.g., map, isUpdatingPositionRef) are stable or included to prevent stale closures. Also verify removing mapType from deps is intentional given previous conditional logic.

}, [startRotation, stopRotation])

@codiumai-pr-agent-free
Copy link
Contributor

codiumai-pr-agent-free bot commented Aug 15, 2025

PR Code Suggestions ✨

No code suggestions found for the PR.

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Aug 15, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Conditionally restart rotation

Guard startRotation() with a check to avoid restarting rotation during user
interactions or when rotation was intentionally stopped for non-real-time modes.
This prevents unintended rotation and race conditions after a flyTo finishes.
Use a ref/flag such as isUserInteracting or check the current mode before
restarting.

components/map/mapbox-map.tsx [221-224]

 setTimeout(() => {
-  startRotation()
+  // Only restart rotation if appropriate (e.g., in real-time mode and not interacting)
+  if (mapType === MapToggleEnum.RealTimeMode && !isUserInteractingRef.current) {
+    startRotation()
+  }
   isUpdatingPositionRef.current = false
 }, 500)
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that the PR's removal of the conditional check on mapType before calling startRotation() could lead to unintended map rotation in non-real-time modes, which is a valid functional issue.

Medium
General
Debounce targetPosition updates

Debounce or coalesce rapid targetPosition updates to avoid repeated
stopRotation() and overlapping flyTo calls that can flicker and stall rotation.
Introduce a short debounce before calling updateMapPosition.

components/map/mapbox-map.tsx [507-525]

 useEffect(() => {
-  if (mapData.targetPosition && map.current) {
-    stopRotation();
-    ...
-    updateMapPosition(lat, lng);
-    ...
+  if (!map.current || !mapData.targetPosition) return;
+  stopRotation();
+  const [lng, lat] = mapData.targetPosition as [number, number];
+  if (typeof lat === 'number' && typeof lng === 'number') {
+    const timer = setTimeout(() => updateMapPosition(lat, lng), 150);
+    return () => clearTimeout(timer);
   }
 }, [mapData.targetPosition, mapData.mapFeature, updateMapPosition, stopRotation]);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion to debounce targetPosition updates is a valid performance and UX improvement, as it can prevent flickering and janky animations from rapid, successive flyTo calls.

Low
  • More

This commit fixes an issue where the Mapbox preview would not update when a user searched for a location.

The root cause of the issue was the placement of the `MapDataProvider`. It was not correctly wrapping all the components that needed access to the map data. This meant that the `MapQueryHandler` and the `Mapbox` component were not sharing the same context, so the map would not update when the context was changed.

This commit fixes the issue by moving the `MapDataProvider` to wrap the `AI` provider in `app/search/[id]/page.tsx`. This ensures that all components have access to the same map data context.

This commit also includes a previous fix that makes the map update logic in `mapbox-map.tsx` more robust.
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: 0

🔭 Outside diff range comments (3)
components/map/mapbox-map.tsx (3)

500-504: Fix falsy checks for zero coordinates (0 latitude/longitude)

0 is a valid coordinate but falsy; the current check skips updates at the equator/prime meridian.

-    if (map.current && position?.latitude && position?.longitude && mapType === MapToggleEnum.RealTimeMode) {
-      updateMapPosition(position.latitude, position.longitude)
-    }
+    if (
+      map.current &&
+      position &&
+      Number.isFinite(position.latitude) &&
+      Number.isFinite(position.longitude) &&
+      mapType === MapToggleEnum.RealTimeMode
+    ) {
+      updateMapPosition(position.latitude, position.longitude)
+    }

487-497: Map initialization effect tears down/rebuilds on dependency changes

Because the effect returns a cleanup unconditionally, any dependency change triggers full teardown (map.remove()) and re-init on the next pass. This causes flicker, lost map state, and unnecessary work.

Make the initialization effect run once on mount/unmount.

-  }, [
-    handleUserInteraction, 
-    startRotation, 
-    stopRotation, 
-    mapType, 
-    updateMeasurementLabels, 
-    setupGeolocationWatcher, 
-    captureMapCenter, 
-    setupDrawingTools,
-    setIsMapLoaded // Added missing dependency
-  ])
+  }, [])

Note: map type changes and draw control setup are already handled in the dedicated mapType effect.


552-601: Cleanup-only effect currently tears down the map on any dependency change

This effect returns only a cleanup function yet depends on many values. On each dependency change, it removes the map, draw controls, labels, etc. This fights the rest of the component lifecycle.

Either remove this effect (preferred) and rely on the initialization effect’s cleanup, or make it run only on unmount with an empty dependency array.

Option A (remove the effect entirely):

-  useEffect(() => {
-    // ... existing useEffect logic ...
-    return () => {
-      // ... existing cleanup logic ...
-      if (longPressTimerRef.current) { // Cleanup timer on component unmount
-        clearTimeout(longPressTimerRef.current);
-        longPressTimerRef.current = null;
-      }
-      // ... existing cleanup logic for map and geolocation ...
-      if (map.current) {
-        map.current.off('moveend', captureMapCenter)
-
-        if (drawRef.current) {
-          try {
-            map.current.off('draw.create', updateMeasurementLabels)
-            map.current.off('draw.delete', updateMeasurementLabels)
-            map.current.off('draw.update', updateMeasurementLabels)
-            map.current.removeControl(drawRef.current)
-          } catch (e) {
-            console.log('Draw control already removed')
-          }
-        }
-
-        Object.values(polygonLabelsRef.current).forEach(marker => marker.remove())
-        Object.values(lineLabelsRef.current).forEach(marker => marker.remove())
-
-        stopRotation()
-        setIsMapLoaded(false)
-        map.current.remove()
-        map.current = null
-      }
-
-      if (geolocationWatchIdRef.current !== null) {
-        navigator.geolocation.clearWatch(geolocationWatchIdRef.current)
-        geolocationWatchIdRef.current = null
-      }
-    };
-  }, [
-    handleUserInteraction,
-    startRotation,
-    stopRotation,
-    mapType, // mapType is already here, good.
-    updateMeasurementLabels,
-    setupGeolocationWatcher,
-    captureMapCenter,
-    setupDrawingTools,
-    setIsMapLoaded
-  ]);
+  // (Effect removed — cleanup is handled in the map initialization effect)

Option B (if you want to keep it): change the dependency array to [] so it only runs on unmount.

🧹 Nitpick comments (5)
components/map/mapbox-map.tsx (5)

221-224: Restart rotation only when appropriate; also avoid double-resetting the update flag

Starting rotation unconditionally after flyTo can re-enable rotation in RealTimeMode, which earlier logic avoided. Also, isUpdatingPositionRef.current is reset here and in the finally block—keep a single source of truth.

  • Gate rotation restart by mode (if intended).
  • Rely on the finally block to reset isUpdatingPositionRef.

If you want rotation to remain disabled in RealTimeMode after a search, apply:

-        setTimeout(() => {
-          startRotation()
-          isUpdatingPositionRef.current = false
-        }, 500)
+        setTimeout(() => {
+          if (mapType !== MapToggleEnum.RealTimeMode) {
+            startRotation()
+          }
+        }, 500)

And update the hook deps to include mapType:

-  }, [startRotation, stopRotation])
+  }, [startRotation, stopRotation, mapType])

227-229: Good: always reset isUpdatingPositionRef via finally

Solid reliability improvement. With this in place, you can remove the flag reset inside the setTimeout to avoid redundant writes.


209-220: Add a timeout fallback in case moveend doesn’t fire

If flyTo results in no effective movement (or gets interrupted), moveend might not fire, leaving the promise pending and the UI stuck. A small fallback avoids this.

-        await new Promise<void>((resolve) => {
-          map.current?.flyTo({
-            center: [longitude, latitude],
-            zoom: 12,
-            essential: true,
-            speed: 0.5,
-            curve: 1,
-          })
-          map.current?.once('moveend', () => {
-            resolve()
-          })
-        })
+        await new Promise<void>((resolve) => {
+          const timeoutId = setTimeout(() => resolve(), 3000) // Fallback if no 'moveend'
+          map.current?.once('moveend', () => {
+            clearTimeout(timeoutId)
+            resolve()
+          })
+          map.current?.flyTo({
+            center: [longitude, latitude],
+            zoom: 12,
+            essential: true,
+            speed: 0.5,
+            curve: 1,
+          })
+        })

231-231: Confirm removal of mapType from updateMapPosition deps

If you intend to restart rotation regardless of mode, this is fine. If you gate rotation by mapType (as suggested), add it back to the deps.


37-37: Prefer ReturnType<typeof setTimeout> for timer ref in browser context

NodeJS.Timeout can be incorrect under DOM typings. ReturnType<typeof setTimeout> is robust across environments.

-  const longPressTimerRef = useRef<NodeJS.Timeout | null>(null);
+  const longPressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 96c0b81 and 3757d7b.

📒 Files selected for processing (1)
  • components/map/mapbox-map.tsx (3 hunks)
🔇 Additional comments (1)
components/map/mapbox-map.tsx (1)

508-516: Stopping rotation before context-driven flyTo looks good

Halting rotation before a programmatic move removes the interference that was breaking the preview update. This aligns with the PR objective.

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: 1

🔭 Outside diff range comments (1)
components/chat.tsx (1)

66-72: Handle server action errors and support clearing on empty features; avoid noisy logs

The effect fires on every drawnFeatures change but:

  • It ignores the “clear” case (length === 0), leaving stale server state.
  • It doesn’t handle rejections, which can produce unhandled promise rejections.
  • It logs feature payloads to console on the client.

Apply:

-  useEffect(() => {
-    if (id && mapData.drawnFeatures && mapData.drawnFeatures.length > 0) {
-      console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures);
-      updateDrawingContext(id, mapData.drawnFeatures);
-    }
-  }, [id, mapData.drawnFeatures]);
+  useEffect(() => {
+    if (!id) return;
+    const features = mapData.drawnFeatures ?? [];
+    // Optional: enable for local debugging only
+    // if (process.env.NODE_ENV !== 'production') {
+    //   console.debug('drawnFeatures changed; updating drawing context', features);
+    // }
+    void updateDrawingContext(id, features).catch((err) => {
+      console.error('Failed to update drawing context', err);
+    });
+  }, [id, mapData.drawnFeatures]);

Optionally consider debouncing/batching if drawnFeatures changes rapidly during drawing interactions.

🧹 Nitpick comments (3)
app/search/[id]/page.tsx (2)

12-18: Type params per Next.js conventions; drop Promise/await for params

Next.js app router provides params synchronously. Typing params as Promise and awaiting it works at runtime but is misleading and can confuse tooling. Recommend using the standard shape.

Apply:

-export interface SearchPageProps {
-  params: Promise<{ id: string }>; // Keep as is for now
-}
+export interface SearchPageProps {
+  params: { id: string };
+}
 
 export async function generateMetadata({ params }: SearchPageProps) {
-  const { id } = await params; // Keep as is for now
+  const { id } = params;
   // TODO: Metadata generation might need authenticated user if chats are private
   // For now, assuming getChat can be called or it handles anon access for metadata appropriately
   const userId = await getCurrentUserIdOnServer(); // Attempt to get user for metadata
   const chat = await getChat(id, userId || 'anonymous'); // Pass userId or 'anonymous' if none
   return {
     title: chat?.title?.toString().slice(0, 50) || 'Search',
   };
 }
 
 export default async function SearchPage({ params }: SearchPageProps) {
-  const { id } = await params; // Keep as is for now
+  const { id } = params;
   const userId = await getCurrentUserIdOnServer();

Also applies to: 27-29


44-60: Avoid unsafe role cast; validate or narrow roles during mapping

Casting dbMsg.role to AIMessage['role'] can mask unexpected roles at runtime. Prefer narrowing with a type guard or fallback to a safe role to prevent corrupted state.

Example adjustment:

const allowedRoles = new Set<AIMessage['role']>(['system','user','assistant','tool']);
const role = allowedRoles.has(dbMsg.role as any)
  ? (dbMsg.role as AIMessage['role'])
  : 'assistant';

return {
  id: dbMsg.id,
  role,
  content: dbMsg.content,
  createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined,
};
components/chat.tsx (1)

111-116: Use Tailwind utilities instead of inline style; prefer top offset over margin on fixed element

For a fixed element, using top-[0.5in] is clearer than combining top-0 with mt-[0.5in]. Also replace inline zIndex with Tailwind’s z-10 for consistency.

-      <div
-        className="w-1/2 p-4 fixed h-[calc(100vh-0.5in)] top-0 right-0 mt-[0.5in]"
-        style={{ zIndex: 10 }} // Added z-index
-      >
+      <div
+        className="w-1/2 p-4 fixed h-[calc(100vh-0.5in)] right-0 top-[0.5in] z-10"
+      >
         {activeView ? <SettingsView /> : <Mapbox />}
       </div>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3757d7b and 07ba4f3.

📒 Files selected for processing (2)
  • app/search/[id]/page.tsx (1 hunks)
  • components/chat.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/search/[id]/page.tsx (6)
components/map/map-data-context.tsx (1)
  • MapDataProvider (26-34)
components/chat.tsx (1)
  • Chat (20-119)
lib/actions/chat-db.ts (1)
  • Chat (9-9)
lib/types/index.ts (1)
  • Chat (47-55)
app/page.tsx (1)
  • Page (9-18)
lib/actions/chat.ts (1)
  • msg (119-127)
🔇 Additional comments (2)
components/chat.tsx (2)

77-99: Mobile layout changes look good

Clean separation of mobile sections and consistent use of components. No issues spotted.


13-15: No action required — Chat is already wrapped by MapDataProvider

Verified Chat mounts are wrapped by MapDataProvider and components/chat.tsx correctly consumes useMapData, so no runtime context error expected.

  • app/page.tsx — MapDataProvider wraps (around lines 12–15).
  • app/search/[id]/page.tsx — MapDataProvider wraps (around lines 63–74).
  • components/chat.tsx — consumes useMapData(); the hook throws if used outside MapDataProvider (components/map/map-data-context.tsx).

Comment on lines +63 to 75
<MapDataProvider>
<AI
initialAIState={{
chatId: chat.id,
messages: initialMessages, // Use the transformed messages from the database
// isSharePage: true, // This was in PR#533, but share functionality is removed.
// If needed for styling or other logic, it can be set.
}}
>
<Chat id={id} />
</MapDataProvider>
</AI>
</AI>
</MapDataProvider>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify and align provider order across routes (MapDataProvider ↔ AI) to avoid inconsistent context lifecycles

Here, MapDataProvider wraps AI. In app/page.tsx (home), AI wraps MapDataProvider. Different provider hierarchies can change when mapData resets across page transitions (e.g., navigating between home and search), potentially leading to surprising state persistence/reset behavior. If this is intentional, ignore; otherwise, consider standardizing the order across routes.

Run this script to list occurrences and surrounding context so you can quickly verify composition order:


🏁 Script executed:

#!/bin/bash
# Show occurrences of AI, MapDataProvider, and Chat together to inspect hierarchy
rg -n -C3 -P '(?s)<AI\b|<MapDataProvider\b|<Chat\b' app/**/*.tsx

Length of output: 121


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Searching repository for AI, MapDataProvider, Chat usages..."
rg -n -S -C3 -P '(?s)<(AI|MapDataProvider|Chat)\b' || true
echo
echo "Listing any page.tsx files found..."
find . -type f -name 'page.tsx' -print || true

Length of output: 2064


Align provider order (MapDataProvider ↔ AI) across routes

Verified: app/page.tsx uses AI > MapDataProvider, while app/search/[id]/page.tsx uses MapDataProvider > AI. This mismatch can change context lifecycles and cause surprising state resets across navigation. Standardize the order.

Files to update:

  • app/page.tsx — AI wraps MapDataProvider (current; lines ~12–16)
  • app/search/[id]/page.tsx — MapDataProvider wraps AI (current; lines ~63–74) — change to match chosen order

Suggested change (make app/search/[id]/page.tsx match app/page.tsx by moving AI outermost):

Before
< MapDataProvider>



After
< AI initialAIState={...}>



🤖 Prompt for AI Agents
In app/search/[id]/page.tsx around lines 63 to 75, the provider order is
MapDataProvider wrapping AI which differs from app/page.tsx and can cause
context lifecycle mismatches; change the nesting so AI is the outer provider and
MapDataProvider is inside it, preserving the existing initialAIState prop on AI
and passing Chat id as before (i.e., wrap MapDataProvider and Chat with AI as
the parent) so the component tree matches app/page.tsx.

This commit adds extensive logging to the application to help diagnose an issue where the Mapbox preview is not updating when a user searches for a location.

Logging has been added to the following files:
- `lib/agents/tools/geospatial.tsx`
- `app/actions.tsx`
- `components/map/map-query-handler.tsx`
- `components/map/mapbox-map.tsx`

This logging will trace the data flow from the initial search query all the way to the map component, allowing us to pinpoint exactly where the process is failing.
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: 0

🧹 Nitpick comments (5)
lib/agents/tools/geospatial.tsx (2)

154-155: Gate verbose logs behind a debug flag to avoid noisy/PII-ish logs in production

These logs are great for diagnosing issues but can be noisy and potentially include sensitive inputs/results. Recommend guarding them by environment.

Apply this diff to gate the logs in development only:

@@
-    console.log('[GeospatialTool] Execute called with:', JSON.stringify(params, null, 2));
+    if (process.env.NODE_ENV !== 'production') {
+      console.log('[GeospatialTool] Execute called with:', JSON.stringify(params, null, 2));
+    }
@@
-      console.log('[GeospatialTool] Calling tool:', toolName, 'with args:', JSON.stringify(toolArgs, null, 2));
+      if (process.env.NODE_ENV !== 'production') {
+        console.log('[GeospatialTool] Calling tool:', toolName, 'with args:', JSON.stringify(toolArgs, null, 2));
+      }
@@
-      console.log('[GeospatialTool] Raw MCP Response:', JSON.stringify(toolCallResult, null, 2));
+      if (process.env.NODE_ENV !== 'production') {
+        console.log('[GeospatialTool] Raw MCP Response:', JSON.stringify(toolCallResult, null, 2));
+      }
@@
-        console.log('[GeospatialTool] Parsed MCP Response Content:', JSON.stringify(content, null, 2));
+        if (process.env.NODE_ENV !== 'production') {
+          console.log('[GeospatialTool] Parsed MCP Response Content:', JSON.stringify(content, null, 2));
+        }
-      } catch {
-        console.warn('[GeospatialTool] Content is not JSON, using as string:', content);
+      } catch {
+        if (process.env.NODE_ENV !== 'production') {
+          console.warn('[GeospatialTool] Content is not JSON, using as string:', content);
+        }
       }
@@
-    console.log('[GeospatialTool] Returning result:', JSON.stringify(result, null, 2));
+    if (process.env.NODE_ENV !== 'production') {
+      console.log('[GeospatialTool] Returning result:', JSON.stringify(result, null, 2));
+    }

Also applies to: 208-208, 228-228, 240-245, 274-276


231-236: Content parsing fallback: consider a softer fallback before erroring on non-JSON content

Right now, if the content isn’t JSON (and can’t be parsed), we end up throwing “Unexpected response format.” If MCP tools sometimes return plain text (e.g., map links or coordinates in text), you might salvage minimal data (like a map URL) without failing.

If plain text payloads are expected from any tool, consider extracting lat/lng via a regex or isolating a URL before throwing, and include a bounded content snippet (e.g., first 200 chars) in the error for diagnostics.

Also applies to: 247-259

components/map/map-query-handler.tsx (2)

39-41: Validate coordinate ranges, not just types

Type checks pass for NaN/Infinity and out-of-bounds values. Add range/finite guards to prevent invalid targetPosition updates.

-      if (typeof latitude === 'number' && typeof longitude === 'number') {
+      if (
+        Number.isFinite(latitude) &&
+        Number.isFinite(longitude) &&
+        latitude >= -90 && latitude <= 90 &&
+        longitude >= -180 && longitude <= 180
+      ) {

34-35: Gate console logging in this effect under a debug flag

These logs are helpful during development, but should be quiet in production to avoid noise and leaking user queries/locations to logs.

-    console.log('[MapQueryHandler] useEffect triggered. toolOutput:', JSON.stringify(toolOutput, null, 2));
+    if (process.env.NODE_ENV !== 'production') {
+      console.log('[MapQueryHandler] useEffect triggered. toolOutput:', JSON.stringify(toolOutput, null, 2));
+    }
@@
-        console.log('[MapQueryHandler] Calling setMapData with:', JSON.stringify(newMapData, null, 2));
+        if (process.env.NODE_ENV !== 'production') {
+          console.log('[MapQueryHandler] Calling setMapData with:', JSON.stringify(newMapData, null, 2));
+        }
@@
-        console.warn('[MapQueryHandler] Invalid latitude/longitude in toolOutput.mcp_response:', toolOutput.mcp_response.location);
+        if (process.env.NODE_ENV !== 'production') {
+          console.warn('[MapQueryHandler] Invalid latitude/longitude in toolOutput.mcp_response:', toolOutput.mcp_response.location);
+        }
@@
-      if (toolOutput) {
-        console.warn('[MapQueryHandler] toolOutput provided, but mcp_response or location data is missing.', toolOutput);
-      } else {
-        console.log('[MapQueryHandler] toolOutput is null or undefined.');
-      }
+      if (process.env.NODE_ENV !== 'production') {
+        if (toolOutput) {
+          console.warn('[MapQueryHandler] toolOutput provided, but mcp_response or location data is missing.', toolOutput);
+        } else {
+          console.log('[MapQueryHandler] toolOutput is null or undefined.');
+        }
+      }

Also applies to: 47-47, 53-53, 61-65

components/map/mapbox-map.tsx (1)

221-229: Avoid double-resetting isUpdatingPositionRef; rely on finally for reset

You reset isUpdatingPositionRef both in the timeout and in finally, which is redundant. It also weakens the “no rotation during update” guard if reset happens before the 500ms delay.

-        setTimeout(() => {
-          startRotation()
-          isUpdatingPositionRef.current = false
-        }, 500)
+        setTimeout(() => {
+          startRotation()
+        }, 500)
       } catch (error) {
         console.error('Error updating map position:', error)
       } finally {
         isUpdatingPositionRef.current = false
       }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 07ba4f3 and 3e1129c.

📒 Files selected for processing (4)
  • app/actions.tsx (1 hunks)
  • components/map/map-query-handler.tsx (1 hunks)
  • components/map/mapbox-map.tsx (2 hunks)
  • lib/agents/tools/geospatial.tsx (7 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app/actions.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
lib/agents/tools/geospatial.tsx (3)
components/message.tsx (1)
  • BotMessage (11-29)
mapbox_mcp/index.ts (1)
  • testMCPConnection (14-89)
mapbox_mcp/hooks.ts (1)
  • query (91-156)
🔇 Additional comments (4)
lib/agents/tools/geospatial.tsx (1)

164-174: Good fail-fast path and user feedback when MCP is unavailable

Early return with a user-facing message, structured result, and logs is solid. This prevents downstream null dereferences and keeps UI responsive.

components/map/map-query-handler.tsx (1)

40-47: Correct ordering to [lng, lat] and mapFeature propagation looks good

Setting targetPosition as [longitude, latitude] matches Mapbox expectations and cleanly threads place_name/mapUrl. This aligns with the consumer in mapbox-map.tsx.

Also applies to: 48-51

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

221-229: Rotation resumes unconditionally after flyTo—confirm RealTimeMode expectations

This change restarts rotation even in RealTimeMode after programmatic moves (e.g., geolocation updates), which may be undesirable if RealTimeMode is meant to keep a steady tracked view.

If RealTimeMode should never rotate, consider passing a “resumeRotation: boolean” to updateMapPosition based on mapType, or check mapType inside. Happy to sketch that change if needed.


508-520: Stopping rotation before applying mapData targetPosition is the right fix

Halting rotation before flyTo and then resuming resolves the “preview not updating” issue due to interrupted animations.

@ngoiyaeric ngoiyaeric added Mapbox.tsx These are issues specifically in this file MCP Map labels Aug 24, 2025
@ngoiyaeric ngoiyaeric closed this Oct 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Map Mapbox.tsx These are issues specifically in this file MCP Review effort 2/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants