8000 Make zoom icons conditional on drawing mode by ngoiyaeric · Pull Request #455 · QueueLab/QCX · GitHub
[go: up one dir, main page]

Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator
@ngoiyaeric ngoiyaeric commented Jan 27, 2026

User description

The zoom icons (NavigationControl) in the Mapbox component are now conditionally rendered. They only appear when the user selects 'Draw & Measure' mode, which is when the drawing tools also appear on the right side of the map. This avoids UI clutter in other modes. E2E tests have been updated to reflect this new behavior.


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


PR Type

Enhancement


Description

  • Zoom controls now conditionally render only in Draw & Measure mode

  • Added navControlRef to manage NavigationControl lifecycle

  • Updated map initialization to check drawing mode before adding controls

  • Enhanced cleanup logic to safely remove controls on unmount and mode changes

  • Updated E2E tests to switch to drawing mode before verifying zoom controls


Diagram Walkthrough

flowchart LR
  A["Map Initialization"] --> B{"Drawing Mode?"}
  B -->|Yes| C["Add NavigationControl"]
  B -->|No| D["Skip NavigationControl"]
  E["Mode Change Handler"] --> F{"Switch to Drawing?"}
  F -->|Yes| C
  F -->|No| G["Remove NavigationControl"]
  C --> H["Zoom Controls Visible"]
  D --> I["Zoom Controls Hidden"]
  G --> I
Loading

File Walkthrough

Relevant files
Enhancement
mapbox-map.tsx
Conditional zoom controls based on drawing mode                   

components/map/mapbox-map.tsx

  • Added navControlRef useRef to track NavigationControl instance
  • Modified map initialization to conditionally add NavigationControl
    only in DrawingMode
  • Updated mapType change handler to add/remove controls based on mode
    transitions
  • Enhanced cleanup logic in unmount and mode change handlers to safely
    remove NavigationControl
  • Replaced responsive width check with drawing mode check for control
    visibility
+38/-2   
Tests
map.spec.ts
Update E2E tests for conditional zoom controls                     

tests/map.spec.ts

  • Added mode switching logic before zoom controls verification
  • Clicks map toggle and selects draw mode to make zoom controls visible
  • Ensures test runs in correct context where controls are expected
+4/-0     

Summary by CodeRabbit

  • New Features

    • Zoom/navigation controls are now added only when entering Drawing mode and removed when exiting, making them visible and usable only in Drawing mode.
  • Bug Fixes

    • Improved cleanup of map controls and drawing tools on mode switches and unmount to prevent duplicates, leaks, and leftover timers.
  • Tests

    • Updated tests to activate Drawing mode before interacting with zoom controls to reflect the mode-dependent UI.

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

This change ensures that the Mapbox navigation controls (zoom in, zoom out, and compass) are only visible when the map is in 'Draw & Measure' mode, aligning their visibility with the drawing tools.

- Added `navControlRef` to manage the `NavigationControl` instance.
- Modified map initialization and `mapType` change handler to add/remove the control based on the current mode.
- Updated E2E tests to switch to drawing mode before verifying zoom controls.
- Improved cleanup logic to ensure controls are removed on unmount.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 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 27, 2026

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

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

@charliecreates charliecreates bot requested a review from CharlieHelps January 27, 2026 20:04
@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 27, 2026

Walkthrough

The PR moves NavigationControl initialization/removal into DrawingMode lifecycle inside Mapbox, adds navControlRef and mapTypeRef to avoid stale closures, updates cleanup paths, and adjusts tests to activate drawing mode before asserting zoom control behavior.

Changes

Cohort / File(s) Summary
NavigationControl & Draw lifecycle
components/map/mapbox-map.tsx
Added navControlRef and mapTypeRef; initialize NavigationControl when entering DrawingMode and remove it when exiting or unmounting (with try-catch). Replaced width-based init with mode-based logic. Consolidated cleanup (draw controls, nav control, timers) and adjusted effect dependencies.
Test updates
tests/map.spec.ts
Modified two tests to activate drawing mode before further interactions by clicking data-testid="map-toggle" then data-testid="map-mode-draw", ensuring zoom/draw UI is present for assertions.

Sequence Diagram(s)

sequenceDiagram
  participant User as User/UI
  participant Mapbox as MapboxComponent
  participant Map as MapInstance
  participant Draw as DrawControl
  participant Nav as NavigationControl

  User->>Mapbox: click map toggle -> select DrawingMode
  Mapbox->>Mapbox: set mapTypeRef = DrawingMode
  Mapbox->>Draw: initialize & add to Map
  Mapbox->>Nav: create NavigationControl (store in navControlRef)
  Mapbox->>Map: addControl(Nav, "top-left")

  Note over Mapbox,Map: User finishes drawing / switches mode

  User->>Mapbox: switch away from DrawingMode
  Mapbox->>Draw: remove DrawControl (cleanup)
  Mapbox->>Nav: try remove NavigationControl (safe try-catch)
  Mapbox->>Mapbox: clear navControlRef, clear timers
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Review effort 3/5

Poem

🐰 I hopped in code where maps are drawn,
I placed a zoom where once was none,
I tidy brushes, timers, refs—
I nibble bugs and clear the clefs.
Hooray for modes, and maps that run! 🎨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Make zoom icons conditional on drawing mode' is specific and directly relates to the main change: the NavigationControl (zoom icons) is now only rendered when the user enters drawing mode.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

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

@qodo-code-review
Copy link
Contributor
qodo-code-review bot commented Jan 27, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
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: 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:
Swallowed exceptions: The new try/catch blocks catch control-removal failures and only console.log generic
messages (or log-and-ignore), which can silently hide real failures and lacks actionable
context/handling.

Referred Code
if (navControlRef.current) {
  try {
    map.current.removeControl(navControlRef
8000
.current);
    navControlRef.current = null;
  } catch (e) {
    console.log('Error removing navigation control:', e);
  }
}

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:
Console error leakage: The new console.log('Error removing navigation control:', e); may expose
internal error details/stack traces to end users via browser console rather than routing
details to secure internal logs.

Referred Code
} catch (e) {
  console.log('Error removing navigation control:', e);
}

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:
Unstructured console logs: Newly added console.log(...) statements are unstructured and include raw error objects
(e.g., e), which can leak sensitive details and are not suitable for secure/auditable
logging.

Referred Code
if (navControlRef.current) {
  try {
    map.current.removeControl(navControlRef.current);
    navControlRef.current = null;
  } catch (e) {
    console.log('Error removing navigation control:', e);
  }
}

// If switching from drawing mode, remove drawing tools but save features
if (drawRef.current) {
  // Save current drawings before removing control
  drawingFeatures.current = drawRef.current.getAll()

  try {
    map.current.off('draw.create', updateMeasurementLabels)
    map.current.off('draw.delete', updateMeasurementLabels)
    map.current.off('draw.update', updateMeasurementLabels)
    map.current.removeControl(drawRef.current)
    drawRef.current = null



 ... (clipped 131 lines)

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 27, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Simplify implementation using CSS for visibility

Instead of imperatively adding/removing the navigation control with JavaScript,
add it once and use CSS to toggle its visibility based on the drawing mode. This
simplifies the code by removing the need for a ref and complex lifecycle
management logic.

Examples:

components/map/mapbox-map.tsx [372-386]
        // Setup zoom controls if not already present
        if (!navControlRef.current) {
          navControlRef.current = new mapboxgl.NavigationControl();
          map.current.addControl(navControlRef.current, 'top-left');
        }
      } else {
        // Remove zoom controls if present
        if (navControlRef.current) {
          try {
            map.current.removeControl(navControlRef.current);

 ... (clipped 5 lines)
components/map/mapbox-map.tsx [456-459]
      if (mapType === MapToggleEnum.DrawingMode) {
        navControlRef.current = new mapboxgl.NavigationControl();
        map.current.addControl(navControlRef.current, 'top-left');
      }

Solution Walkthrough:

Before:

// components/map/mapbox-map.tsx
const navControlRef = useRef<mapboxgl.NavigationControl | null>(null);

// On map initialization
if (mapType === MapToggleEnum.DrawingMode) {
  navControlRef.current = new mapboxgl.NavigationControl();
  map.current.addControl(navControlRef.current);
}

// On mapType change
useEffect(() => {
  if (mapType === MapToggleEnum.DrawingMode) {
    // Add control if not present
  } else {
    // Remove control if present
    if (navControlRef.current) {
      map.current.removeControl(navControlRef.current);
      navControlRef.current = null;
    }
  }
}, [mapType]);

After:

// components/map/mapbox-map.tsx

// Add a class to the map container based on the mode
<div 
  ref={mapContainer} 
  className={mapType === MapToggleEnum.DrawingMode ? 'drawing-mode-active' : ''} 
/>

// On map initialization, add the control unconditionally
useEffect(() => {
  // ...
  map.current.addControl(new mapboxgl.NavigationControl(), 'top-left');
  // ...
}, []);
// No other refs or effects are needed to manage the control.

// In a CSS file:
.mapboxgl-ctrl-group { /* This is the class for the nav control */
  display: none;
}
.drawing-mode-active .mapboxgl-ctrl-group {
  display: block;
}
Suggestion importance[1-10]: 9

__

Why: The suggestion offers a significantly simpler, more declarative, and robust architectural alternative using CSS, which would remove complex imperative logic and state management for the control's lifecycle.

High
General
Wait for zoom controls to appear

In the test, add a waitForSelector call to ensure the zoom controls are visible
after switching to draw mode, making the test more reliable.

tests/map.spec.ts [39-40]

 await page.click('[data-testid="map-toggle"]');
 await page.click('[data-testid="map-mode-draw"]');
+await page.waitForSelector('.mapboxgl-ctrl-zoom-in', { state: 'visible' });
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This is a valuable suggestion for improving test stability by explicitly waiting for the UI to update after an action, which helps prevent flaky tests.

Medium
Refactor duplicated logic into one function

Refactor the duplicated navigation control removal logic into a single, reusable
removeNavControl function to improve code maintainability.

components/map/mapbox-map.tsx [524-531]

-if (navControlRef.current) {
-  try {
-    map.current.removeControl(navControlRef.current)
-    navControlRef.current = null
-  } catch (e) {
-    console.log('Navigation control already removed')
+const removeNavControl = useCallback(() => {
+  if (map.current && navControlRef.current) {
+    try {
+      map.current.removeControl(navControlRef.current);
+    } catch (e) {
+      console.log('Error removing navigation control:', e);
+    } finally {
+      navControlRef.current = null;
+    }
   }
-}
+}, []); // No dependencies needed as it operates on refs
 
+// ... then in your effects, you can replace the duplicated block with:
+removeNavControl();
+
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies duplicated code for removing navigation controls and proposes a good refactoring by creating a reusable removeNavControl function, which improves maintainability and reduces redundancy.

Low
Add null check for map reference

Use optional chaining (map.current?.removeControl(...)) to safely call
removeControl, preventing runtime errors if the map instance is not available.

components/map/mapbox-map.tsx [381]

-map.current.removeControl(navControlRef.current);
+map.current?.removeControl(navControlRef.current);
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: This suggestion correctly points out a potential null reference error on map.current and provides a concise fix using optional chaining, which improves the code's robustness.

Low
  • Update

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

Caution

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

⚠️ Outside diff range comments (2)
tests/map.spec.ts (1)

38-49: Consider adding a wait for the navigation control to appear after switching to drawing mode.

After switching to drawing mode (lines 39-40), the test immediately proceeds to check the map instance and interact with zoom controls. Since the NavigationControl is added asynchronously when mode changes, there could be a race condition where the control hasn't been added to the DOM yet.

♻️ Suggested fix
    // Switch to drawing mode to make zoom controls visible
    await page.click('[data-testid="map-toggle"]');
    await page.click('[data-testid="map-mode-draw"]');
+
+   // Wait for zoom controls to appear after mode switch
+   await page.waitForSelector('.mapboxgl-ctrl-zoom-in');

    const hasMap = await page.evaluate(() => Boolean((window as any).map));
components/map/mapbox-map.tsx (1)

609-668: Remove duplicate useEffect with empty body.

This useEffect has an empty body (line 611 only contains a placeholder comment) but includes a full cleanup function that duplicates the cleanup logic from the earlier useEffect at lines 509-548. Both effects have identical dependency arrays, which means:

  1. The effect body does nothing useful
  2. Cleanup runs twice on unmount, attempting to remove already-removed controls
  3. This appears to be a copy-paste artifact or incomplete refactoring

While the try-catch blocks prevent crashes, this is redundant code that should be removed entirely.

🔧 Proposed fix - remove the duplicate useEffect
-  // Cleanup for the main useEffect
-  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')
-          }
-        }
-
-        if (navControlRef.current) {
-          try {
-            map.current.removeControl(navControlRef.current)
-            navControlRef.current = null
-          } catch (e) {
-            console.log('Navigation control already removed')
-          }
-        }
-
-        Object.values(polygonLabelsRef.current).forEach(marker => marker.remove())
-        Object.values(lineLabelsRef.current).forEach(marker => marker.remove())
-
-        stopRotation()
-        setIsMapLoaded(false)
-        setMap(null)
-        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,
-    setMap
-  ]);

If longPressTimerRef cleanup is needed, add it to the existing cleanup function around line 537:

        stopRotation()
        setIsMapLoaded(false) // Reset map loaded state on cleanup
        setMap(null) // Clear map instance from context
        map.current.remove()
        map.current = null
      }
+
+     if (longPressTimerRef.current) {
+       clearTimeout(longPressTimerRef.current);
+       longPressTimerRef.current = null;
+     }
      
      if (geolocationWatchIdRef.current !== null) {
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73924c9 and 334e550.

📒 Files selected for processing (2)
  • components/map/mapbox-map.tsx
  • tests/map.spec.ts
🔇 Additional comments (4)
components/map/mapbox-map.tsx (4)

23-23: LGTM!

Good addition of navControlRef to track the NavigationControl instance. This enables proper lifecycle management for adding/removing the control based on map mode.


371-386: LGTM!

The mode-based lifecycle management for NavigationControl is well-implemented:

  • Guard condition prevents duplicate controls
  • Try-catch handles edge cases where control might already be removed
  • Ref is properly nulled after removal

456-459: LGTM!

Conditional initialization correctly adds NavigationControl only when the initial mode is DrawingMode, consistent with the mode-change handler.


523-531: LGTM!

Cleanup logic correctly removes the NavigationControl and nulls the ref with appropriate error handling.

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

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.

The navigation control lifecycle is now spread across init, map-type changes, and multiple cleanup blocks, with broad try/catch masking genuine ordering bugs; this should be consolidated and guarded on map.current. The removal/addition logic currently assumes the map instance is always present when toggling modes, which can break during initialization/teardown. There’s also a likely policy mismatch: removing the window.innerWidth > 768 condition may unintentionally enable zoom controls on mobile in drawing mode while the E2E suite still assumes mobile should never show them. The E2E test should explicitly wait for controls to render to reduce flakiness.

Additional notes (1)
  • Maintainability | tests/map.spec.ts:38-44
    The test switches modes and then immediately clicks .mapboxgl-ctrl-zoom-in. This may be flaky if the control is added asynchronously relative to the UI click handler / map style updates. Consider asserting the control is visible before interacting to reduce timing-related failures.
Summary of changes

What changed

components/map/mapbox-map.tsx

  • Introduced a persistent navControlRef (useRef<mapboxgl.NavigationControl | null>(null)) to manage Mapbox NavigationControl lifecycle.
  • Updated map mode switching logic:
    • When mapType === MapToggleEnum.DrawingMode, the map now adds NavigationControl (zoom/compass) in 'top-left'.
    • When leaving drawing mode, it removes the NavigationControl (wrapped in try/catch) and nulls the ref.
  • Updated initial map creation to add NavigationControl only in drawing mode (replacing the previous window.innerWidth > 768 condition).
  • Expanded cleanup/unmount logic to remove the NavigationControl if present.

tests/map.spec.ts

  • Updated the zoom controls E2E test to switch into “Draw & Measure” mode via [data-testid="map-toggle"][data-testid="map-mode-draw"] before interacting with .mapboxgl-ctrl-zoom-in.

Comment on lines 456 to 459
8000
if (mapType === MapToggleEnum.DrawingMode) {
navControlRef.current = new mapboxgl.NavigationControl();
map.current.addControl(navControlRef.current, 'top-left');
}

Choose a reason for hiding this comment

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

The initial condition for showing zoom controls changed from window.innerWidth > 768 (desktop-only) to “drawing mode only”. If drawing mode is available on mobile/tablet, this will now show zoom controls there too, which contradicts the E2E test’s assumption that zoom controls are not visible on mobile. Either the UI policy should remain “desktop + drawing mode”, or tests should be updated to reflect the new intended behavior across breakpoints.

Suggestion

If the intended product rule is both "desktop" and "drawing mode", restore the breakpoint check:

if (mapType === MapToggleEnum.DrawingMode && window.innerWidth > 768) {
  navControlRef.current = new mapboxgl.NavigationControl();
  map.current.addControl(navControlRef.current, 'top-left');
}

Alternatively, if drawing mode should show zoom controls even on mobile, adjust the E2E test skip logic accordingly. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

@charliecreates charliecreates bot removed the request for review from CharlieHelps January 27, 2026 20:09
@ngoiyaeric
Copy link
Collaborator Author

@coderabbitai the resolution search stopped working.

@coderabbitai
Copy link
Contributor
coderabbitai bot commented Jan 28, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@ngoiyaeric I'll investigate the "resolution search" issue for you. Let me search the codebase to understand this feature and see if there's any connection to the changes made in this PR.


🏁 Script executed:

#!/bin/bash
# Search for "resolution" related code in the codebase
echo "=== Searching for 'resolution' in the codebase ==="
rg -i "resolution" --type=ts --type=tsx -C3

echo -e "\n=== Searching for search-related components ==="
rg -i "search" --type=ts --type=tsx -g "!node_modules" -g "!*.spec.ts" -l | head -20

echo -e "\n=== Looking for the mapbox-map.tsx file to understand the full context ==="
cat components/map/mapbox-map.tsx

Length of output: 24003


🏁 Script executed:

#!/bin/bash
# Search more broadly for resolution and search related code
echo "=== Searching for files with 'search' in filename ==="
fd -e ts -e tsx -e js -e jsx search

echo -e "\n=== Searching for 'resolution' in TypeScript/JavaScript files ==="
rg -i "resolution" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx" -C3

echo -e "\n=== Searching for search-related patterns ==="
rg -i "geocod|search.*address|location.*search|place.*search" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx" -C2

echo -e "\n=== Looking for components in the map directory ==="
fd . components/map -e ts -e tsx

echo -e "\n=== Checking for any input or search UI components ==="
rg -i "input|searchbox|geocoder" -g "*.ts" -g "*.tsx" -g "*.js" -g "*.jsx" -l | head -20

Length of output: 19881


🏁 Script executed:

#!/bin/bash
# Read the header-search-button component to understand the resolution search implementation
echo "=== Reading header-search-button.tsx ==="
cat components/header-search-button.tsx

echo -e "\n=== Checking map-context.tsx to understand how map instance is provided ==="
cat components/map/map-context.tsx

echo -e "\n=== Check if there are any imports or dependencies that might have been affected ==="
rg "useMap|MapContext" -g "*.tsx" -g "*.ts" -C2 | head -100

Length of output: 11446


@ngoiyaeric I found the resolution search feature - it's the map analysis button in the header that captures and analyzes the current map view.

To help diagnose the issue, could you provide more details about what specifically isn't working:

  1. Is the search button disabled/grayed out? (This would indicate the map instance isn't being set in the context)

  2. Does clicking the button produce an error? (If so, what error message do you see?)

  3. Does it fail during the analysis process? (After clicking, does it show the spinning loader but then fail?)

  4. What map mode are you in when this happens? (Real-time mode or Draw & Measure mode?)

The PR changes only affect when the zoom controls (NavigationControl) are visible - they should now only appear in Draw & Measure mode. However, this shouldn't affect the map instance availability for the resolution search feature. Let me investigate potential timing issues:


🏁 Script executed:

#!/bin/bash
# Check git diff for the PR to see the exact changes made
echo "=== Checking the actual changes in the PR ==="
git diff HEAD~1 HEAD -- components/map/mapbox-map.tsx

echo -e "\n=== Check when setMap is called in the current code ==="
rg "setMap\(" components/map/mapbox-map.tsx -B5 -A5

Length of output: 4646


🏁 Script executed:

#!/bin/bash
# Check the header component to see if there's something about how resolution search button is rendered
echo "=== Checking header component ==="
cat components/header.tsx 2>/dev/null || cat components/header.tsx 2>/dev/null || echo "Header component not found at expected location"

echo -e "\n=== Find header component ==="
fd -e tsx -e ts header | grep -v node_modules | head -10

echo -e "\n=== Search for header-search-portal usage ==="
rg "header-search-portal" -g "*.tsx" -g "*.ts" -C3

echo -e "\n=== Check if there are any errors in browser console by searching for console.error in resolution-search ==="
cat lib/agents/resolution-search.tsx | grep -A5 -B5 "error\|Error"

Length of output: 3787


🏁 Script executed:

#!/bin/bash
# Check where HeaderSearchButton is imported and used
echo "=== Searching for HeaderSearchButton imports ==="
rg "HeaderSearchButton" -g "*.tsx" -g "*.ts" -C3

echo -e "\n=== Check mobile-header-search-portal div ==="
rg "mobile-header-search-portal" -g "*.tsx" -g "*.ts" -C5

echo -e "\n=== Check app layout or main app component ==="
cat app/layout.tsx | grep -A20 -B5 "Header"

echo -e "\n=== Check if there's a page component that renders HeaderSearchButton ==="
fd -e tsx "page" app/ | head -5 | xargs cat

Length of output: 8180


🏁 Script executed:

#!/bin/bash
# Search comprehensively for mobile-header-search-portal in all components
echo "=== Searching for mobile portal div in all files ==="
rg "mobile-header-search" -g "*.tsx" -g "*.jsx" -g "*.ts" -A2 -B2

echo -e "\n=== Check the full header component for mobile section ==="
cat components/header.tsx | tail -30

echo -e "\n=== Check if there's a mobile header or sidebar component ==="
fd mobile components/ -e tsx | xargs cat 2>/dev/null | head -100

echo -e "\n=== Check the sidebar component ==="
cat components/sidebar.tsx | head -50

Length of output: 4285


@ngoiyaeric I've identified the issue! The resolution search feature is broken because the mobile portal target is missing.

Root Cause

The HeaderSearchButton component tries to render buttons using React portals into two locations:

  1. Desktop: header-search-portal - exists in header.tsx
  2. Mobile: mobile-header-search-portal - does NOT exist anywhere

Without the portal target, the resolution search button cannot render on mobile devices.

Important Note

This issue is NOT caused by this PR's changes. The PR only modified when zoom controls appear (now only in Draw & Measure mode). This appears to be a pre-existing issue.

Questions to help resolve this:

  1. Are you accessing the app on mobile or desktop?
  2. Was this feature working before this PR? (If yes, we need to check what changed)
  3. Do you see the search button in the header at all?

Solution

To fix the mobile resolution search, add the missing portal div to the header's mobile section:

{/* Mobile menu buttons */}
<div className="flex md:hidden gap-2">
  <div id="mobile-header-search-portal" />  {/* ADD THIS LINE */}
  
  <a href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00" target="_blank" rel="noopener noreferrer">
    <Button variant="ghost" size="icon">
      <TentTree className="h-[1.2rem] w-[1.2rem]" />
    </Button>
  </a>
  <ProfileToggle/>
</div>

Would you like me to create an issue to track adding the mobile portal, or would you prefer to fix this in a follow-up PR? Also, please confirm whether the desktop version is working for you.

@google-labs-jules
Copy link
Contributor

@coderabbitai the resolution search stopped working.

I've fixed this regression. The issue was that my previous change caused the map instance to be destroyed and recreated every time the map mode was switched. This led to the loss of GeoJSON layers and other transient map state, which made the resolution search results disappear. I've now stabilized the map initialization so it only runs once on mount, and mode switches correctly update the controls without destroying the map.

This commit fixes a regression where the Mapbox instance was being destroyed and recreated on every mode switch. This was caused by including `mapType` in the dependency array of the initialization `useEffect`, whose cleanup function removed the map.

- Removed `mapType` from initialization `useEffect` dependencies.
- Added `mapTypeRef` to track the current mode without re-triggering map initialization.
- Stabilized callbacks used in the initialization effect.
- Removed redundant cleanup `useEffect` at the end of `mapbox-map.tsx`.
- Ensured resolution search results (GeoJSON layers) persist through mode switches.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
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

🤖 Fix all issues with AI agents
In `@components/map/mapbox-map.tsx`:
- Around line 558-569: The dependency array intentionally omits position,
mapData.cameraState, and mapType to avoid recreating the map; add an eslint
disable comment to document this and suppress react-hooks/exhaustive-deps
warnings (e.g., place a // eslint-disable-next-line react-hooks/exhaustive-deps
with a short rationale) immediately above the useEffect that depends on
captureMapCenter, handleUserInteraction, setIsMapLoaded, setMap,
setupDrawingTools, setupGeolocationWatcher, startRotation, stopRotation, and
updateMeasurementLabels so reviewers and linters understand the intentional
omission.
- Around line 493-500: Add a defensive null-check for map.current before calling
map.current.addControl inside the MapToggleEnum.DrawingMode branch: ensure when
mapTypeRef.current === MapToggleEnum.DrawingMode and after setupDrawingTools()
you only call map.current.addControl(...) if map.current is truthy (and
handle/log the unexpected null case); update the block that assigns
navControlRef.current and calls addControl to guard on map.current to match
other defensive patterns in this file.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 334e550 and ff33a46.

📒 Files selected for processing (1)
  • components/map/mapbox-map.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
components/map/mapbox-map.tsx (1)
components/map/map-3d.tsx (1)
  • props (18-94)
🔇 Additional comments (6)
components/map/mapbox-map.tsx (6)

23-23: LGTM!

Good use of a ref to track the NavigationControl instance, consistent with the existing drawRef pattern.


38-42: LGTM!

Good pattern to maintain a current reference to mapType for use in callbacks, avoiding stale closure issues while keeping callbacks stable.


230-240: LGTM!

Correctly using mapTypeRef.current to access the latest mode value without requiring mapType in the dependency array, keeping the callback stable.


304-321: LGTM!

Consistent application of the mapTypeRef pattern to keep this callback stable.


376-414: LGTM!

The NavigationControl lifecycle is well-managed:

  • Properly guards against duplicate controls with the null check
  • Defensive try-catch for removal
  • Correctly clears the ref after removal

514-540: LGTM!

Thorough cleanup implementation:

  • Timer cleanup prevents potential memory leaks
  • Both control refs are properly cleaned up with try-catch
  • Refs are explicitly nulled after removal

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

Comment on lines +493 to 500
// Initial setup for the current mode
if (mapTypeRef.current === MapToggleEnum.DrawingMode) {
setupDrawingTools()
if (!navControlRef.current) {
navControlRef.current = new mapboxgl.NavigationControl();
map.current.addControl(navControlRef.current, 'top-left');
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider adding a defensive null check for map.current.

While the early return at line 470 typically ensures map.current exists, adding a guard before addControl would be consistent with the defensive try-catch patterns used elsewhere in this file.

♻️ Suggested improvement
         // Initial setup for the current mode
         if (mapTypeRef.curr
4D1C
ent === MapToggleEnum.DrawingMode) {
           setupDrawingTools()
-          if (!navControlRef.current) {
+          if (!navControlRef.current && map.current) {
             navControlRef.current = new mapboxgl.NavigationControl();
             map.current.addControl(navControlRef.current, 'top-left');
           }
         }
📝 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.

Suggested change
// Initial setup for the current mode
if (mapTypeRef.current === MapToggleEnum.DrawingMode) {
setupDrawingTools()
if (!navControlRef.current) {
navControlRef.current = new mapboxgl.NavigationControl();
map.current.addControl(navControlRef.current, 'top-left');
}
}
// Initial setup for the current mode
if (mapTypeRef.current === MapToggleEnum.DrawingMode) {
setupDrawingTools()
if (!navControlRef.current && map.current) {
navControlRef.current = new mapboxgl.NavigationControl();
map.current.addControl(navControlRef.current, 'top-left');
}
}
🤖 Prompt for AI Agents
In `@components/map/mapbox-map.tsx` around lines 493 - 500, Add a defensive
null-check for map.current before calling map.current.addControl inside the
MapToggleEnum.DrawingMode branch: ensure when mapTypeRef.current ===
MapToggleEnum.DrawingMode and after setupDrawingTools() you only call
map.current.addControl(...) if map.current is truthy (and handle/log the
unexpected null case); update the block that assigns navControlRef.current and
calls addControl to guard on map.current to match other defensive patterns in
this file.

Comment on lines 558 to 569
BEA4
}, [
handleUserInteraction,
startRotation,
stopRotation,
mapType,
updateMeasurementLabels,
setupGeolocationWatcher,
captureMapCenter,
setupDrawingTools,
captureMapCenter,
handleUserInteraction,
setIsMapLoaded,
setMap
setMap,
setupDrawingTools,
setupGeolocationWatcher,
startRotation,
stopRotation,
updateMeasurementLabels
// Removed position, mapData.cameraState and mapType to avoid map recreation
])
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Intentional dependency omissions are documented - consider adding eslint disable comment.

The comment explains why position, mapData.cameraState, and mapType are omitted to prevent map recreation. This is a valid architectural decision for this component.

If the react-hooks/exhaustive-deps eslint rule flags this, consider adding an explicit disable comment with explanation:

♻️ Suggested improvement
   }, [
     captureMapCenter,
     handleUserInteraction,
     setIsMapLoaded,
     setMap,
     setupDrawingTools,
     setupGeolocationWatcher,
     startRotation,
     stopRotation,
     updateMeasurementLabels
-    // Removed position, mapData.cameraState and mapType to avoid map recreation
+    // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally omit position, mapData.cameraState, and mapType to prevent map recreation on mode switches
   ])
📝 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.

Suggested change
}, [
handleUserInteraction,
startRotation,
stopRotation,
mapType,
updateMeasurementLabels,
setupGeolocationWatcher,
captureMapCenter,
setupDrawingTools,
captureMapCenter,
handleUserInteraction,
setIsMapLoaded,
setMap
setMap,
setupDrawingTools,
setupGeolocationWatcher,
startRotation,
stopRotation,
updateMeasurementLabels
// Removed position, mapData.cameraState and mapType to avoid map recreation
])
}, [
captureMapCenter,
handleUserInteraction,
setIsMapLoaded,
setMap,
setupDrawingTools,
setupGeolocationWatcher,
startRotation,
stopRotation,
updateMeasurementLabels
// eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally omit position, mapData.cameraState, and mapType to prevent map recreation on mode switches
])
🤖 Prompt for AI Agents
In `@components/map/mapbox-map.tsx` around lines 558 - 569, The dependency array
intentionally omits position, mapData.cameraState, and mapType to avoid
recreating the map; add an eslint disable comment to document this and suppress
react-hooks/exhaustive-deps warnings (e.g., place a // eslint-disable-next-line
react-hooks/exhaustive-deps with a short rationale) immediately above the
useEffect that depends on captureMapCenter, handleUserInteraction,
setIsMapLoaded, setMap, setupDrawingTools, setupGeolocationWatcher,
startRotation, stopRotation, and updateMeasurementLabels so reviewers and
linters understand the intentional omission.

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

0