Skip to content

Revert "Reset branch to 76ace98 and synchronize with main"#489

Merged
ngoiyaeric merged 1 commit into
feature/resolution-search-enhancement-img-3118023839244746163from
revert-485-feature/resolution-search-enhancement-img-3118023839244746163-2394226024207327659
Feb 4, 2026
Merged

Revert "Reset branch to 76ace98 and synchronize with main"#489
ngoiyaeric merged 1 commit into
feature/resolution-search-enhancement-img-3118023839244746163from
revert-485-feature/resolution-search-enhancement-img-3118023839244746163-2394226024207327659

Conversation

@ngoiyaeric
Copy link
Copy Markdown
Collaborator

@ngoiyaeric ngoiyaeric commented Feb 4, 2026

User description

Reverts #485


PR Type

Enhancement, Bug fix


Description

  • Update AI model selection to use vision-capable models when images present

  • Support dual map provider imagery (Mapbox and Google) in resolution search

  • Remove unused UI components (usage view, history sidebar context)

  • Simplify provider context hierarchy and remove MCP-related code

  • Fix map navigation controls and drawing mode interactions


Diagram Walkthrough

flowchart LR
  A["Map Capture"] -->|Mapbox + Google| B["Dual Image Support"]
  B -->|Vision Detection| C["Model Selection"]
  C -->|Vision Model| D["Resolution Search"]
  E["Remove Contexts"] -->|Usage/History| F["Simplified Layout"]
  F -->|Direct Components| G["Cleaner UI"]
  H["MCP Cleanup"] -->|Remove References| I["Streamlined Code"]
Loading

File Walkthrough

Relevant files
Enhancement
21 files
index.ts
Update model selection for vision capability                         
+4/-4     
actions.tsx
Support dual map imagery and refactor resolution search   
+95/-34 
layout.tsx
Remove unused context providers from layout                           
+22/-30 
chat-panel.tsx
Remove MCP instance and simplify form handling                     
+4/-4     
chat.tsx
Remove usage view and simplify suggestions rendering         
+55/-51 
conditional-lottie.tsx
Remove usage toggle from visibility logic                               
+1/-3     
followup-panel.tsx
Remove MCP and drawn features from form data                         
+6/-5     
header-search-button.tsx
Capture both Mapbox and Google map previews                           
+36/-26 
header.tsx
Remove purchase popup and usage toggle functionality         
+17/-38 
history.tsx
Refactor history to use Sheet component directly                 
+38/-14 
mobile-icons-bar.tsx
Update Stripe purchase link                                                           
+1/-1     
profile-toggle-context.tsx
Remove closeProfileView function from context                       
+1/-6     
profile-toggle.tsx
Remove usage toggle interaction logic                                       
+6/-12   
resolution-image.tsx
Support dual image display for map comparison                       
+67/-21 
search-related.tsx
Convert to form-based submission and remove MCP                   
+25/-10 
chat-history-client.tsx
Remove credits preview and history toggle dependency         
+2/-37   
inquire.tsx
Add vision model detection for image content                         
+7/-1     
query-suggestor.tsx
Add vision model detection for image content                         
+7/-1     
researcher.tsx
Remove drawn features from system prompt                                 
+3/-9     
resolution-search.tsx
Update system prompt for dual image analysis                         
+9/-5     
task-manager.tsx
Add vision model detection for image content                         
+6/-1     
Tests
1 files
map.spec.ts
Remove drawing mode zoom control test                                       
+0/-5     
Miscellaneous
6 files
CC BY-NC 4.0.docx
Delete license document file                                                         
[link]   
history-sidebar.tsx
Delete history sidebar component file                                       
+0/-35   
history-toggle-context.tsx
Delete history toggle context provider                                     
+0/-30   
purchase-credits-popup.tsx
Delete purchase credits popup component                                   
+0/-73   
usage-toggle-context.tsx
Delete usage toggle context provider                                         
+0/-30   
usage-view.tsx
Delete usage view component file                                                 
+0/-96   
Bug fix
1 files
mapbox-map.tsx
Simplify navigation control and drawing mode logic             
+7/-31   

@charliecreates charliecreates Bot requested a review from CharlieHelps February 4, 2026 09:39
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 4, 2026

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

Project Deployment Actions Updated (UTC)
qcx Building Building Preview, Comment Feb 4, 2026 9:39am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 4, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. 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.

  • 🔍 Trigger a full review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch revert-485-feature/resolution-search-enhancement-img-3118023839244746163-2394226024207327659

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.

@ngoiyaeric ngoiyaeric merged commit e2bd753 into feature/resolution-search-enhancement-img-3118023839244746163 Feb 4, 2026
4 of 5 checks passed
@qodo-code-review
Copy link
Copy Markdown
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Unbounded file upload

Description: The server action converts user-supplied uploaded files into base64 data: URLs via
arrayBuffer()/Buffer.from(...).toString('base64') without validating MIME type or
enforcing size limits, which can enable memory/CPU exhaustion (DoS) and cause
oversized/sensitive imagery to be persisted in AI state and logs.
actions.tsx [48-80]

Referred Code
const mapboxFile = formData?.get('mapboxFile') as File;
const googleFile = formData?.get('googleFile') as File;
const legacyFile = formData?.get('file') as File;
const timezone = (formData?.get('timezone') as string) || 'UTC';
const drawnFeaturesString = formData?.get('drawnFeatures') as string;
let drawnFeatures: DrawnFeature[] = [];
try {
  drawnFeatures = drawnFeaturesString ? JSON.parse(drawnFeaturesString) : [];
} catch (e) {
  console.error('Failed to parse drawnFeatures:', e);
}

let mapboxDataUrl = '';
let googleDataUrl = '';

if (mapboxFile) {
  const buffer = await mapboxFile.arrayBuffer();
  mapboxDataUrl = `data:${mapboxFile.type};base64,${Buffer.from(buffer).toString('base64')}`;
}
if (googleFile) {
  const buffer = await googleFile.arrayBuffer();


 ... (clipped 12 lines)
Exposed API key

Description: The client fetches Google Static Maps using NEXT_PUBLIC_GOOGLE_MAPS_API_KEY embedded in a
request URL, which exposes the key to end users and can be abused unless it is tightly
restricted by HTTP referrer/app restrictions and quota limits.
header-search-button.tsx [75-85]

Referred Code
const apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY;
if (apiKey) {
  let staticMapUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${center.lat},${center.lng}&zoom=${Math.round(zoom)}&size=640x480&scale=2&maptype=satellite&key=${apiKey}`;
  try {
    const response = await fetch(staticMapUrl);
    if (response.ok) {
      googleBlob = await response.blob();
    }
  } catch (e) {
    console.error('Failed to fetch google static map:', e);
  }
Sensitive data retention

Description: Base64-encoded map imagery is serialized into assistant message content
(analysisResult.image) and stored in chat/AI state, which increases the risk of sensitive
geospatial imagery being retained longer than intended and potentially exfiltrated via
downstream logging/analytics or later rendering.
actions.tsx [186-197]

Referred Code
  content: analysisResult.summary || 'Analysis complete.',
  type: 'response'
},
{
  id: groupeId,
  role: 'assistant',
  content: JSON.stringify({
    ...analysisResult,
    image: JSON.stringify({ mapbox: mapboxDataUrl, google: googleDataUrl })
  }),
  type: 'resolution_search_result'
},
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: Meaningful Naming and Self-Documenting Code

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

Status:
Unclear identifier name: The newly introduced variable groupeId is misspelled/unclear and does not self-document
its purpose, reducing readability.

Referred Code
const groupeId = nanoid();

async function processResolutionSearch() {

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:
Weak edge-case handling: The resolution-search path converts uploaded files to base64 data URLs without validating
file presence/type/size and logs JSON parse failures but continues with potentially
inconsistent drawnFeatures state.

Referred Code
const mapboxFile = formData?.get('mapboxFile') as File;
const googleFile = formData?.get('googleFile') as File;
const legacyFile = formData?.get('file') as File;
const timezone = (formData?.get('timezone') as string) || 'UTC';
const drawnFeaturesString = formData?.get('drawnFeatures') as string;
let drawnFeatures: DrawnFeature[] = [];
try {
  drawnFeatures = drawnFeaturesString ? JSON.parse(drawnFeaturesString) : [];
} catch (e) {
  console.error('Failed to parse drawnFeatures:', e);
}

let mapboxDataUrl = '';
let googleDataUrl = '';

if (mapboxFile) {
  const buffer = await mapboxFile.arrayBuffer();
  mapboxDataUrl = `data:${mapboxFile.type};base64,${Buffer.from(buffer).toString('base64')}`;
}
if (googleFile) {
  const buffer = await googleFile.arrayBuffer();


 ... (clipped 12 lines)

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:
Sensitive data in logs: Newly added console logging prints mapData.drawnFeatures (potentially sensitive
geospatial/user-derived information) directly to client logs.

Referred Code
    console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures);
    updateDrawingContext(id, {
      drawnFeatures: mapData.drawnFeatures,
      cameraState: mapData.cameraState,
    });
  }
}, [id, mapData.drawnFeatures, mapData.cameraState]);

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:
Missing input validation: External inputs from formData (e.g., mapboxFile, googleFile, mapProvider, and
drawnFeatures) are used without validation/sanitization, and binary uploads are converted
to large base64 strings without size/type enforcement.

Referred Code
const action = formData?.get('action') as string;
if (action === 'resolution_search') {
  const mapboxFile = formData?.get('mapboxFile') as File;
  const googleFile = formData?.get('googleFile') as File;
  const legacyFile = formData?.get('file') as File;
  const timezone = (formData?.get('timezone') as string) || 'UTC';
  const drawnFeaturesString = formData?.get('drawnFeatures') as string;
  let drawnFeatures: DrawnFeature[] = [];
  try {
    drawnFeatures = drawnFeaturesString ? JSON.parse(drawnFeaturesString) : [];
  } catch (e) {
    console.error('Failed to parse drawnFeatures:', e);
  }

  let mapboxDataUrl = '';
  let googleDataUrl = '';

  if (mapboxFile) {
    const buffer = await mapboxFile.arrayBuffer();
    mapboxDataUrl = `data:${mapboxFile.type};base64,${Buffer.from(buffer).toString('base64')}`;
  }


 ... (clipped 10 lines)

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

Generic: Comprehensive Audit Trails

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

Status:
Missing audit logging: The new resolution-search server action processes uploaded imagery and state changes
without emitting any structured audit log containing user identity, action, and outcome.

Referred Code
async function submit(formData?: FormData, skip?: boolean) {
  'use server'

  const aiState = getMutableAIState<typeof AI>()
  const uiStream = createStreamableUI()
  const isGenerating = createStreamableValue(true)
  const isCollapsed = createStreamableValue(false)

  const action = formData?.get('action') as string;
  if (action === 'resolution_search') {
    const mapboxFile = formData?.get('mapboxFile') as File;
    const googleFile = formData?.get('googleFile') as File;
    const legacyFile = formData?.get('file') as File;
    const timezone = (formData?.get('timezone') as string) || 'UTC';
    const drawnFeaturesString = formData?.get('drawnFeatures') as string;
    let drawnFeatures: DrawnFeature[] = [];
    try {
      drawnFeatures = drawnFeaturesString ? JSON.parse(drawnFeaturesString) : [];
    } catch (e) {
      console.error('Failed to parse drawnFeatures:', e);
    }


 ... (clipped 47 lines)

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:
Possible user error leak: The server action throws specific errors (e.g., missing files) and logs raw error objects,
which may surface internal details depending on the surrounding framework error
boundary/response behavior.

Referred Code
  drawnFeatures = drawnFeaturesString ? JSON.parse(drawnFeaturesString) : [];
} catch (e) {
  console.error('Failed to parse drawnFeatures:', e);
}

let mapboxDataUrl = '';
let googleDataUrl = '';

if (mapboxFile) {
  const buffer = await mapboxFile.arrayBuffer();
  mapboxDataUrl = `data:${mapboxFile.type};base64,${Buffer.from(buffer).toString('base64')}`;
}
if (googleFile) {
  const buffer = await googleFile.arrayBuffer();
  googleDataUrl = `data:${googleFile.type};base64,${Buffer.from(buffer).toString('base64')}`;
}

// Fallback if only 'file' was provided (backward compatibility)
if (!mapboxDataUrl && !googleDataUrl && legacyFile) {
  const buffer = await legacyFile.arrayBuffer();
  mapboxDataUrl = `data:${legacyFile.type};base64,${Buffer.from(buffer).toString('base64')}`;


 ... (clipped 5 lines)

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

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

@qodo-code-review
Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Send drawn features on form submission

In chat-panel.tsx, re-add the logic to append drawnFeatures to the form data on
submission to ensure the backend receives this context for all queries.

components/chat-panel.tsx [90-125]

 const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
   e.preventDefault()
   const formData = new FormData(e.currentTarget)
   formData.append('mapProvider', mapProvider)
   if (selectedFile) {
     formData.append('file', selectedFile)
   }
 
+  // Include drawn features in the form data
+  formData.append('drawnFeatures', JSON.stringify(mapData.drawnFeatures || []))
+
   setInput('')
   clearAttachment()
 
   const responseMessage = await submit(formData)
   setMessages(currentMessages => [...currentMessages, responseMessage as any])
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a significant functional regression where user-drawn map features are no longer sent with general queries, and it provides a direct fix at the source.

High
  • More

Copy link
Copy Markdown

@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.

Main issues are (1) an app/actions.tsx bug-risk from duplicated groupeId declarations, (2) loss of Mapbox NavigationControl lifecycle management which can lead to duplicated controls/leaks, and (3) multiple maintainability regressions (any for multimodal message content, hardcoded MIME types, Date.now() IDs). Additionally, the resolution-search result payload is double-encoded JSON (image as a JSON string), which increases parsing brittleness and migration cost.

Additional notes (2)
  • Maintainability | components/search-related.tsx:1-16

SearchRelated imports unused hooks/utilities after refactor

useEffect, useState, and readStreamableValue are imported but not used in the shown code. While ESLint may or may not catch this depending on config, keeping dead imports is a maintenance smell and makes the component harder to reason about.

  • Maintainability | components/chat-panel.tsx:117-122
    You append drawnFeatures in HeaderSearchButton, but ChatPanel and FollowupPanel no longer append it. Meanwhile submit() still reads drawnFeatures for resolution_search and (elsewhere) seems to have logic that used to pass drawn features to researcher but no longer does. Net effect: drawn features are inconsistently available depending on entry point, which is a behavioral regression for map-context queries.
Summary of changes

Summary

This PR is a revert of QCX#485 and rolls back several UI/UX features while also adjusting map-image resolution search to support dual previews.

Key changes

  • Resolution search input/output revamped

    • app/actions.tsx now accepts mapboxFile + googleFile (with a legacy file fallback), builds a multimodal content array with up to two images, and stores the result image payload as JSON.stringify({ mapbox, google }).
    • components/resolution-image.tsx updated to render a side-by-side Mapbox vs Google comparison (thumbnail + full dialog view).
    • components/header-search-button.tsx captures both a Mapbox canvas preview and a Google Static Maps preview, sending both to the server.
  • UI providers and panels removed

    • Removed UsageToggleProvider, HistoryToggleProvider, HistorySidebar, UsageView, and PurchaseCreditsPopup.
    • components/history.tsx now owns its own Sheet UI instead of relying on a global context.
  • Agent model selection now vision-aware

    • lib/utils/getModel(requireVision) now chooses vision vs non-vision models for xAI and uses gemini-1.5-pro for Google.
    • inquire, querySuggestor, taskManager, and researcher now detect image presence and call getModel(true) accordingly.
  • Mapbox navigation control behavior changed

    • components/map/mapbox-map.tsx adds a NavigationControl on desktop during map init and removes the prior explicit lifecycle management around drawing-mode transitions.
  • Misc UI changes

    • Header history toggle click removed; Stripe link replaces usage toggle button; z-index changed.
    • Suggestions overlay handling moved higher into components/chat.tsx.

Comment thread app/actions.tsx
Comment on lines +95 to +116
// Construct the multimodal content for the user message.
const contentParts: any[] = [
{ type: 'text', text: userInput }
]

if (mapboxDataUrl) {
contentParts.push({
type: 'image',
image: mapboxDataUrl,
mimeType: 'image/png'
})
}
if (googleDataUrl) {
contentParts.push({
type: 'image',
image: googleDataUrl,
mimeType: 'image/png'
})
}

const content = contentParts as any

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

any[] for multimodal content parts hides correctness issues

const contentParts: any[] and const content = contentParts as any bypass the CoreMessage['content'] contract. This is exactly the kind of place where subtle mistakes (wrong mimeType, wrong shape, missing fields) will silently ship and only fail at runtime in a model/tool adapter.

You already used CoreMessage['content'] elsewhere in this file; the resolution-search path should do the same.

Suggestion

Type the parts as CoreMessage['content'] (or the specific multimodal union type your ai SDK expects) and avoid any.

Example direction:

  • const content: CoreMessage['content'] = [{ type: 'text', text: userInput }, ...images]
  • Build images as a properly typed array of image parts.

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

Comment thread app/actions.tsx
Comment on lines +100 to +113
if (mapboxDataUrl) {
contentParts.push({
type: 'image',
image: mapboxDataUrl,
mimeType: 'image/png'
})
}
if (googleDataUrl) {
contentParts.push({
type: 'image',
image: googleDataUrl,
mimeType: 'image/png'
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hardcoded mimeType: 'image/png' ignores the actual file type

The Data URL is created using mapboxFile.type / googleFile.type, but the message parts always send mimeType: 'image/png'. If the browser provides e.g. image/jpeg or image/webp, downstream model/tooling could mis-handle or reject the payload.

This is a runtime correctness issue, not a typing issue.

Suggestion

Preserve the original MIME type alongside each data URL. For example, store mapboxMimeType / googleMimeType when building the data URLs and pass them through into the content parts.

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

Comment thread app/actions.tsx
Comment on lines 191 to 197
role: 'assistant',
content: JSON.stringify({
...analysisResult,
image: dataUrl
image: JSON.stringify({ mapbox: mapboxDataUrl, google: googleDataUrl })
}),
type: 'resolution_search_result'
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Resolution search stores image as a JSON string inside an already JSON-stringified message

You’re doing content: JSON.stringify({ ...analysisResult, image: JSON.stringify({ mapbox, google }) }). That means the image field is double-encoded, forcing consumers to JSON.parse twice and complicating backward compatibility.

This increases brittleness and makes migrations harder (you already had to add a try/catch parsing branch).

Suggestion

Store image as an object directly in the assistant message payload: image: { mapbox: mapboxDataUrl, google: googleDataUrl }. Then adjust the UI-state parser to accept either an object (new) or string (old).

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

Comment on lines +31 to 47
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const formData = new FormData(event.currentTarget as HTMLFormElement)

// // Get the submitter of the form
const submitter = (event.nativeEvent as SubmitEvent)
.submitter as HTMLInputElement
let query = ''
if (submitter) {
formData.append(submitter.name, submitter.value)
query = submitter.value
}

const userMessage = {
id: nanoid(),
id: Date.now(),
component: <UserMessage content={query} />
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SearchRelated switched IDs from nanoid() to Date.now() which can collide

Date.now() is not a safe unique identifier in fast, repeated interactions (multiple clicks within the same millisecond are possible, especially in tests or on fast devices). The rest of the codebase already used nanoid() for this.

Colliding IDs can break React list keys and message state updates.

Suggestion

Use nanoid() (or a deterministic stable key derived from content + index) instead of Date.now() for message IDs.

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 February 4, 2026 09:48
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.

1 participant