Conversation
Restored the branch to the expected head at commit 488b47c. This recovers several missing features and architectural improvements: - Integrated HistoryToggleProvider and HistorySidebar. - Integrated UsageToggleProvider and the new UsageView component. - Removed legacy usage-sidebar.tsx. - Restored pricing and feature updates in PurchaseCreditsPopup. - Fixed layout structure in app/layout.tsx to support these new global state providers. Resolved previous merge conflicts and incorrect force-push state. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…c380 Restore Branch Head to Commit 488b47c
- Reset branch HEAD to historical recovery point 488b47c. - Applied latest code fixes from orphan commit 166aee9, including Mapbox rendering optimizations and GeoJSON streaming. - Improved type safety in app/actions.tsx by removing 'as any' casts and properly typing message arrays. - Refined getModel utility to support vision-aware model fallback. - Documented non-blocking background task pattern in server actions. - Updated feature/billing-integration branch with the restored state. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Reset branch HEAD to historical recovery point 488b47c. - Applied latest code fixes from orphan commit 166aee9. - Resolved TypeScript build error in app/actions.tsx by casting GeoJSON data to FeatureCollection. - Improved type safety for AIState message filtering and content handling. - Implemented background processing for resolution search with immediate UI streaming. - Optimized chat history fetching to trigger only when the sidebar is open. - Ensured Mapbox style loading is robust against re-renders. - Cleaned up dead code and improved documentation in server actions. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Reset branch HEAD to historical recovery point 488b47c. - Applied latest code fixes from orphan commit 166aee9. - Resolved TypeScript build error in app/actions.tsx by casting GeoJSON data to FeatureCollection. - Improved type safety for AIState message filtering and content handling. - Implemented background processing for resolution search with immediate UI streaming. - Re-enabled auto-opening of the pricing popup in components/header.tsx. - Optimized chat history fetching to trigger only when the sidebar is open. - Ensured Mapbox style loading is robust against re-renders. - Cleaned up dead code and improved documentation in server actions. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Restored historical state from 488b47c and merged latest changes. - Implemented streaming for resolution search in lib/agents/resolution-search.tsx and app/actions.tsx to improve response time. - Re-enabled auto-opening pricing popup in components/header.tsx. - Resolved all TypeScript build errors in app/actions.tsx. - Restored Grok model support for vision tasks in lib/utils/index.ts. - Optimized chat history loading in components/sidebar/chat-history-client.tsx. - Improved Mapbox style loading robustness in components/map/mapbox-map.tsx. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Synchronized branch with origin/main, prioritizing main's code structure and latest features. - Bridged the history with historical recovery point 488b47c. - Implemented streaming for resolution search in lib/agents/resolution-search.tsx and app/actions.tsx to resolve performance issues. - Restored the auto-opening Pricing Popup and Usage View in the Header component. - Integrated the Timezone Clock and time context into the restored resolution search logic. - Resolved TypeScript build errors with proper type casting and fixed a missing 'use client' directive in components/history.tsx. - Ensured all required providers (History, Usage, etc.) are correctly wrapped in the root layout. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Synchronized branch with origin/main, prioritizing main's code structure and latest features. - Bridged the history with historical recovery point 488b47c. - Integrated UsageView and billing UI into the Chat component, toggled by the tent tree icon. - Implemented streaming for resolution search in lib/agents/resolution-search.tsx and app/actions.tsx for better performance. - Restored the auto-opening Pricing Popup in the Header component. - Improved type safety across server actions and Mapbox components. - Ensured mutual exclusion between Settings, Usage, and Map views. - Fixed a missing 'use client' directive in components/history.tsx. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Integrated UsageView into the main Chat component for both mobile and desktop. - Ensured mutual exclusion between Settings, Usage, and Map views in the UI panel. - Updated ConditionalLottie to hide the loading animation when the Usage View is open. - Synchronized with origin/main while prioritizing its code structure. - Maintained historical recovery and performance optimizations for resolution search. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Synchronized with origin/main, prioritizing main branch code and features. - Fully restored historical context and missing changes from commit 488b47c. - Integrated Usage and Billing UI (UsageView) into the Chat component. - Implemented streaming for resolution search analysis to improve perceived performance. - Re-enabled auto-opening pricing popup in components/header.tsx. - Refined ConditionalLottie visibility to avoid overlaps with the Usage UI. - Ensured mutual exclusion between Settings, Usage, and Map views in the side panel. - Improved type safety across server actions and Mapbox components. - Resolved build failures related to missing client directives and type mismatches. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…ability - Recovered historical state from commit 488b47c and synchronized with main. - Optimized resolution search by refactoring to stream partial summaries. - Fixed Mapbox memory leaks by ensuring all event listeners are removed on cleanup. - Improved StreamableValue stability by initializing with default values. - Integrated Usage View with mutual exclusion logic and Lottie player visibility fixes. - Refined model selection for Grok vision and Gemini 1.5 Pro. - Integrated timezone-aware analysis using tz-lookup. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
- Fixed Type error in `app/actions.tsx` by casting `content` to `string` in `createStreamableValue`. - Addressed ESLint warnings in `components/map/mapbox-map.tsx` and `components/chat-panel.tsx` by adding missing dependencies to `useEffect` and `useCallback` hooks. - Ensured `relatedQueries` streamable value is initialized with an empty state for stability. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
Update LICENSE
- Reduced vertical padding in `.mobile-chat-input-area` in `globals.css`. - Removed redundant `.mobile-chat-input` class and associated styles from `globals.css`. - Adjusted `ChatPanel` to use more compact padding and a smaller minimum height on mobile. - Removed excessive left padding (`pl-14`) on the mobile chat input since the attachment button is moved to the icons bar. - Cleaned up unused mobile-specific CSS classes in `globals.css`. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…8048805505961867280 Reduce padding in mobile chat input area
Replaced all occurrences of the old Stripe checkout link with the new URL: https://buy.stripe.com/14A3cv7K72TR3go14Nasg02 Updated files: - components/mobile-icons-bar.tsx - components/header.tsx Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…683339330 Update Stripe checkout links
- Extend MapData context to support uploaded GeoJSON layers - Update ChatPanel to support GeoJSON file selection - Implement MapDataUpdater component for automatic context sync and map framing - Update Mapbox and Google Maps components to render uploaded GeoJSON - Enable AI tools to ingest GeoJSON into the map pipeline via MapQueryHandler - Ensure persistence of GeoJSON data across chat sessions via database sync - Add test IDs to key components for improved observability Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
👋 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 New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
|
WalkthroughDetects inline or uploaded GeoJSON on submit, emits a Changes
Sequence DiagramsequenceDiagram
actor User
participant ChatPanel as Chat Panel
participant Actions as app/actions
participant AIState as AI State
participant MapUpdater as MapDataUpdater
participant MapContext as MapData Context
participant MapRenderer as Map Components
User->>ChatPanel: paste or upload GeoJSON
ChatPanel->>Actions: submit(text/file)
Actions->>Actions: detect & parse GeoJSON
Actions->>AIState: push 'geojson_upload' message (id, filename, data)
Actions->>MapUpdater: stream MapDataUpdater component
MapUpdater->>MapContext: add uploadedGeoJson entry {id, filename, data, visible:true}
MapUpdater->>MapUpdater: compute bbox (turf.bbox)
MapUpdater->>MapRenderer: call fitBounds(bbox)
MapRenderer->>User: render GeoJsonLayer(s)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Important Action Needed: IP Allowlist UpdateIf your organization protects your Git platform with IP whitelisting, please add the new CodeRabbit IP address to your allowlist:
Failure to add the new IP will result in interrupted reviews. 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. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||||||||||
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/actions.tsx (1)
879-886:⚠️ Potential issue | 🟡 MinorAdd explicit return after the outer switch to handle unmapped type combinations.
The
case 'user'andcase 'assistant'branches contain inner switches without default cases. When a message type doesn't match any inner switch case, execution falls through the outer switch'sbreakstatement and returnsundefinedinstead of a value. The subsequent.filter(message => message !== null)will not remove theseundefinedentries.🛠️ Proposed fix
.map((message, index) => { const { role, content, id, type, name } = message ... switch (role) { ... } + return null }) - .filter(message => message !== null) as UIState + .filter((message): message is UIState[number] => message != null)
🤖 Fix all issues with AI agents
In `@app/search/`[id]/page.tsx:
- Around line 51-55: The mapping of DB records to AIMessage in page.tsx
currently casts nullable columns directly (dbMsg.type, dbMsg.toolName) which can
leave nulls where AIMessage expects undefined; update the mapping in the
function that constructs the AIMessage object (the block assigning
role/content/createdAt/type/name) to convert nullable values to undefined (e.g.,
use null-coalescing like dbMsg.type ?? undefined and dbMsg.toolName ?? undefined
or conditional expressions) so type and name are undefined when the DB returns
null.
In `@drizzle/migrations/0001_aromatic_ultimatum.sql`:
- Around line 1-12: This migration uses gen_random_uuid() in the calendar_notes
table but doesn't enable the pgcrypto extension; add a CREATE EXTENSION IF NOT
EXISTS "pgcrypto"; statement at the top of the first migration (before the
CREATE TABLE "calendar_notes") so gen_random_uuid() is available when the
migration runs.
In `@lib/actions/chat-db.ts`:
- Around line 123-136: The upsert currently uses
tx.insert(messages).values(messagesToInsert).onConflictDoUpdate targeting
messages.id which can overwrite rows across chats/users; modify the
onConflictDoUpdate call to include a setWhere clause that restricts updates to
rows where messages.chat_id = excluded.chat_id (or messages.chatId =
excluded.chatId) and messages.user_id = excluded.user_id (or messages.userId =
excluded.userId) so updates only apply when the incoming row matches the same
chatId and userId as the existing row; keep the same set mapping (content, role,
type, tool_name, tool_call_id, attachments) but add the setWhere guard to the
onConflictDoUpdate invocation on tx.insert/messages/messagesToInsert.
In `@lib/actions/chat.ts`:
- Around line 188-205: The current update branch in save/update logic uses
dbGetMessagesByChatId and existingMessages.find(...) which returns the first
(oldest) 'data' message and can leave the UI showing the last 'data' entry
out-of-sync with getUIStateFromAIState; change the selection to target the most
recent 'data' message (e.g., pick the last matching element by timestamp or id)
or iterate and update all messages with role === 'data' so the DB update against
messages (used in the db.update(...) block) updates the latest/each 'data'
record rather than the first one found; update references:
dbGetMessagesByChatId, existingMessages, dataMessage, and the db.update(...)
call accordingly.
In `@lib/db/schema.ts`:
- Around line 33-36: The attachments column is declared with
jsonb('attachments') which yields an untyped any; define and use a concrete type
for the JSON shape (e.g., Attachment or Attachment[] depending on expected
payload) and pass it into jsonb to lock down typings (e.g.,
jsonb<Attachment[]>('attachments')), or if null is not meaningful set a typed
default value instead; update the schema declaration that references attachments
and any related uses of toolName/toolCallId/type to use the new Attachment type
for downstream safety and remove unnecessary null checks.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
app/actions.tsxapp/search/[id]/page.tsxcomponents/chat.tsxcomponents/map/map-data-updater.tsxdrizzle/migrations/0001_aromatic_ultimatum.sqldrizzle/migrations/meta/0000_snapshot.jsondrizzle/migrations/meta/0001_snapshot.jsondrizzle/migrations/meta/_journal.jsonlib/actions/chat-db.tslib/actions/chat.tslib/db/schema.tspackage.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-13T13:26:30.086Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.086Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.
Applied to files:
app/actions.tsx
🧬 Code graph analysis (5)
lib/actions/chat-db.ts (1)
lib/db/schema.ts (1)
messages(26-37)
lib/db/schema.ts (1)
lib/types/index.ts (1)
Chat(49-57)
app/search/[id]/page.tsx (1)
lib/types/index.ts (1)
AIMessage(59-78)
components/chat.tsx (3)
components/map/map-data-context.tsx (2)
MapData(15-32)mapData(41-49)lib/actions/chat.ts (1)
updateDrawingContext(165-222)components/map/mapbox-map.tsx (3)
prevData(166-166)prevData(330-339)feature(78-164)
components/map/map-data-updater.tsx (2)
components/map/map-data-context.tsx (4)
useMapData(61-67)mapData(41-49)MapData(15-32)MapDataContextType(34-37)components/map/map-context.tsx (1)
useMap(24-30)
🪛 Biome (2.3.13)
app/actions.tsx
[error] 698-698: This callback passed to map() iterable method should always return a value.
Add missing return statements so that this callback returns a value on all execution paths.
(lint/suspicious/useIterableCallbackReturn)
🔇 Additional comments (10)
drizzle/migrations/meta/0000_snapshot.json (1)
1-178: Snapshot metadata update looks consistent.drizzle/migrations/meta/0001_snapshot.json (1)
1-305: Snapshot looks consistent with the updated schema.package.json (1)
65-65: No action required—drizzle-orm 0.45.1 has no breaking changes.drizzle-orm 0.45.1 (released December 10) contains only a single bug fix for pg-native Pool detection and has no breaking changes to migrations or query APIs. The current drizzle-kit 0.31.1 is compatible with this version; ideally, both should be upgraded together, but this is an optional alignment—not a blocker.
drizzle/migrations/meta/_journal.json (1)
11-18: Journal entry addition looks consistent.app/actions.tsx (3)
26-26: Import wiring is fine.
693-703: Filtering to the latest data message makes restoration deterministic.
859-878: Data-message GeoJSON reconstruction looks good.components/chat.tsx (3)
75-88: Refresh guard looks solid.The last-message ID guard should prevent repeated refresh cycles on the same response.
96-115: Snapshot-based map sync reduces redundant updates.
120-174: Layout-only wrapper changes.Also applies to: 180-238
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| role: dbMsg.role as AIMessage['role'], | ||
| content: dbMsg.content, | ||
| createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined, | ||
| // 'type' and 'name' are not in the basic Drizzle 'messages' schema. | ||
| // These would be undefined unless specific logic is added to derive them. | ||
| // For instance, if a message with role 'tool' should have a 'name', | ||
| // or if some messages have a specific 'type' based on content or other flags. | ||
| // This mapping assumes standard user/assistant messages primarily. | ||
| type: dbMsg.type as AIMessage['type'], | ||
| name: dbMsg.toolName as string, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd app/search && find . -type f -name "page.tsx" | head -5Repository: QueueLab/QCX
Length of output: 71
🏁 Script executed:
cd app && find . -type f -name "*.tsx" -o -name "*.ts" | grep -E "(page|types|message)" | head -20Repository: QueueLab/QCX
Length of output: 89
🏁 Script executed:
cat -n app/search/\[id\]/page.tsx | sed -n '40,65p'Repository: QueueLab/QCX
Length of output: 1170
🏁 Script executed:
# Find AIMessage type definition
rg -n "type AIMessage|interface AIMessage" --type=ts --type=tsxRepository: QueueLab/QCX
Length of output: 83
🏁 Script executed:
# Check dbMsg source and context
rg -n "dbMsg" app/search/\[id\]/page.tsx -B 5 -A 2Repository: QueueLab/QCX
Length of output: 607
🏁 Script executed:
rg -n "type AIMessage|interface AIMessage" -A 10Repository: QueueLab/QCX
Length of output: 1274
🏁 Script executed:
rg -n "type DrizzleMessage|interface DrizzleMessage" -A 10Repository: QueueLab/QCX
Length of output: 38
🏁 Script executed:
# Search for where these types are defined
fd -e ts -e tsx | xargs rg "export (type|interface) AIMessage"Repository: QueueLab/QCX
Length of output: 100
🏁 Script executed:
fd -e ts -e tsx | xargs rg "export (type|interface) DrizzleMessage"Repository: QueueLab/QCX
Length of output: 40
🏁 Script executed:
# Check for downstream usages of message.name and message.type
rg "\.name\s*\?" -t=ts -t=tsx | head -20Repository: QueueLab/QCX
Length of output: 83
🏁 Script executed:
# Look for optional chaining or null checks on message fields
rg "message\.(name|type|toolName)" | head -30Repository: QueueLab/QCX
Length of output: 501
🏁 Script executed:
# Find DrizzleMessage or Message type from Drizzle schema
rg -n "export type Message|export type DrizzleMessage" -A 15Repository: QueueLab/QCX
Length of output: 1189
🏁 Script executed:
# Check the database schema or Drizzle definitions
fd -e ts -e tsx | xargs rg "messages.*table\|export const messages"Repository: QueueLab/QCX
Length of output: 40
🏁 Script executed:
# Look at the Drizzle schema file
find . -name "*schema*" -o -name "*drizzle*" | head -10Repository: QueueLab/QCX
Length of output: 549
🏁 Script executed:
# Check db directory structure
ls -la db/ 2>/dev/null || ls -la lib/db/ 2>/dev/null || find . -type d -name "db" | head -3Repository: QueueLab/QCX
Length of output: 349
🏁 Script executed:
cat -n lib/db/schema.tsRepository: QueueLab/QCX
Length of output: 4470
🏁 Script executed:
# Check the actual schema definition for the messages table
cat -n lib/schema/messages.ts 2>/dev/null || cat -n lib/db/schema.ts | grep -A 30 "messages"Repository: QueueLab/QCX
Length of output: 3827
🏁 Script executed:
# Check the AIMessage type definition more completely
cat -n lib/types/index.ts | sed -n '59,75p'Repository: QueueLab/QCX
Length of output: 614
🏁 Script executed:
# Check if there are any null checks on message.type or message.name in the codebase
rg "message\.(type|name)\s*\?" -A 2 -B 2Repository: QueueLab/QCX
Length of output: 38
🏁 Script executed:
# Check how the initialMessages are used downstream
rg "initialMessages" app/search/\[id\]/page.tsx -A 5 -B 5Repository: QueueLab/QCX
Length of output: 836
🏁 Script executed:
# Check how the type and name fields are accessed in downstream code
rg "\.type\s*==|\.type\s*!=|\.type\s*\?|\.name\s*==|\.name\s*!=" app/ -A 2 -B 2Repository: QueueLab/QCX
Length of output: 3401
🏁 Script executed:
# Look at the AI component to see how it uses these fields
rg -n "initialAIState" app/ -A 10 | head -50Repository: QueueLab/QCX
Length of output: 1910
🏁 Script executed:
# Check if there are assertions or strict equality checks on type/name
rg "(type|name)\s*!==" app/ -B 2 -A 2Repository: QueueLab/QCX
Length of output: 981
Convert nullable database fields to undefined when mapping to AIMessage.
Lines 54–55 cast dbMsg.type and dbMsg.toolName without converting null values to undefined. Since the database schema defines these columns as nullable (no .notNull() constraint) and the AIMessage type expects undefined for optional fields, assign null-coalesced values to maintain type consistency.
Recommended fix
- type: dbMsg.type as AIMessage['type'],
- name: dbMsg.toolName as string,
+ type: dbMsg.type ?? undefined,
+ name: dbMsg.toolName ?? undefined,📝 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.
| role: dbMsg.role as AIMessage['role'], | |
| content: dbMsg.content, | |
| createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined, | |
| // 'type' and 'name' are not in the basic Drizzle 'messages' schema. | |
| // These would be undefined unless specific logic is added to derive them. | |
| // For instance, if a message with role 'tool' should have a 'name', | |
| // or if some messages have a specific 'type' based on content or other flags. | |
| // This mapping assumes standard user/assistant messages primarily. | |
| type: dbMsg.type as AIMessage['type'], | |
| name: dbMsg.toolName as string, | |
| role: dbMsg.role as AIMessage['role'], | |
| content: dbMsg.content, | |
| createdAt: dbMsg.createdAt ? new Date(dbMsg.createdAt) : undefined, | |
| type: dbMsg.type ?? undefined, | |
| name: dbMsg.toolName ?? undefined, |
🤖 Prompt for AI Agents
In `@app/search/`[id]/page.tsx around lines 51 - 55, The mapping of DB records to
AIMessage in page.tsx currently casts nullable columns directly (dbMsg.type,
dbMsg.toolName) which can leave nulls where AIMessage expects undefined; update
the mapping in the function that constructs the AIMessage object (the block
assigning role/content/createdAt/type/name) to convert nullable values to
undefined (e.g., use null-coalescing like dbMsg.type ?? undefined and
dbMsg.toolName ?? undefined or conditional expressions) so type and name are
undefined when the DB returns null.
| CREATE TABLE "calendar_notes" ( | ||
| "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | ||
| "user_id" uuid NOT NULL, | ||
| "chat_id" uuid, | ||
| "date" timestamp with time zone NOT NULL, | ||
| "content" text NOT NULL, | ||
| "location_tags" jsonb, | ||
| "user_tags" text[], | ||
| "map_feature_id" text, | ||
| "created_at" timestamp with time zone DEFAULT now() NOT NULL, | ||
| "updated_at" timestamp with time zone DEFAULT now() NOT NULL | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "CREATE EXTENSION.*pgcrypto|gen_random_uuid" drizzle/migrationsRepository: QueueLab/QCX
Length of output: 1209
🏁 Script executed:
rg -n "CREATE EXTENSION" drizzle/Repository: QueueLab/QCX
Length of output: 38
🏁 Script executed:
head -20 drizzle/migrations/0000_sweet_metal_master.sqlRepository: QueueLab/QCX
Length of output: 763
🏁 Script executed:
find . -maxdepth 2 -type f \( -name "README*" -o -name "*.md" -o -name "init*" -o -name "setup*" \) | head -20Repository: QueueLab/QCX
Length of output: 356
🏁 Script executed:
rg -i "pgcrypto|database.*init|extension" README.md ./docs/ ./CLOUD_BUILD_SETUP.md 2>/dev/null | head -20Repository: QueueLab/QCX
Length of output: 38
Ensure pgcrypto is enabled before using gen_random_uuid().
gen_random_uuid() requires the pgcrypto extension; it is not created in any migration file. This migration will fail unless pgcrypto is already enabled in the database. Add CREATE EXTENSION IF NOT EXISTS "pgcrypto"; at the top of the first migration, or confirm it exists in your database setup.
🛠️ Proposed fix
+CREATE EXTENSION IF NOT EXISTS "pgcrypto";
+
CREATE TABLE "calendar_notes" (📝 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.
| CREATE TABLE "calendar_notes" ( | |
| "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| "user_id" uuid NOT NULL, | |
| "chat_id" uuid, | |
| "date" timestamp with time zone NOT NULL, | |
| "content" text NOT NULL, | |
| "location_tags" jsonb, | |
| "user_tags" text[], | |
| "map_feature_id" text, | |
| "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| "updated_at" timestamp with time zone DEFAULT now() NOT NULL | |
| ); | |
| CREATE EXTENSION IF NOT EXISTS "pgcrypto"; | |
| CREATE TABLE "calendar_notes" ( | |
| "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, | |
| "user_id" uuid NOT NULL, | |
| "chat_id" uuid, | |
| "date" timestamp with time zone NOT NULL, | |
| "content" text NOT NULL, | |
| "location_tags" jsonb, | |
| "user_tags" text[], | |
| "map_feature_id" text, | |
| "created_at" timestamp with time zone DEFAULT now() NOT NULL, | |
| "updated_at" timestamp with time zone DEFAULT now() NOT NULL | |
| ); |
🤖 Prompt for AI Agents
In `@drizzle/migrations/0001_aromatic_ultimatum.sql` around lines 1 - 12, This
migration uses gen_random_uuid() in the calendar_notes table but doesn't enable
the pgcrypto extension; add a CREATE EXTENSION IF NOT EXISTS "pgcrypto";
statement at the top of the first migration (before the CREATE TABLE
"calendar_notes") so gen_random_uuid() is available when the migration runs.
| // Use upsert to avoid duplicate primary keys and prevent redundant appends | ||
| await tx.insert(messages) | ||
| .values(messagesToInsert) | ||
| .onConflictDoUpdate({ | ||
| target: messages.id, | ||
| set: { | ||
| content: sql`excluded.content`, | ||
| role: sql`excluded.role`, | ||
| type: sql`excluded.type`, | ||
| toolName: sql`excluded.tool_name`, | ||
| toolCallId: sql`excluded.tool_call_id`, | ||
| attachments: sql`excluded.attachments` | ||
| } | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and examine the chat-db.ts file
find . -name "chat-db.ts" -type fRepository: QueueLab/QCX
Length of output: 80
🏁 Script executed:
# Read the file to see the full context around lines 123-136
cat -n lib/actions/chat-db.ts | sed -n '100,150p'Repository: QueueLab/QCX
Length of output: 2263
🏁 Script executed:
# Check the messages table schema definition
fd -e ts -e js | xargs grep -l "messages.*Table\|export.*messages" | head -5Repository: QueueLab/QCX
Length of output: 95
🏁 Script executed:
# Examine the messages table schema
cat -n lib/db/schema.ts | head -150Repository: QueueLab/QCX
Length of output: 4470
🏁 Script executed:
# Search specifically for the messages table definition
grep -n "messages.*=" lib/db/schema.ts | head -10Repository: QueueLab/QCX
Length of output: 176
🏁 Script executed:
# Check if onConflictDoUpdate with where clause is used elsewhere in the codebase
rg "onConflictDoUpdate" -A 5 lib/Repository: QueueLab/QCX
Length of output: 391
🏁 Script executed:
# Check Drizzle ORM version in package.json to understand API capabilities
cat package.json | grep -A 2 -B 2 "drizzle"Repository: QueueLab/QCX
Length of output: 230
🌐 Web query:
Drizzle ORM onConflictDoUpdate where clause support version 0.45
💡 Result:
In Drizzle ORM v0.45.x, .onConflictDoUpdate() does support WHERE clauses, but not via a single where field. Instead you use:
targetWhere: addsWHERE ...to the conflict target (for partial indexes /ON CONFLICT (...) WHERE ...)setWhere: addsWHERE ...to the DO UPDATE part (... DO UPDATE SET ... WHERE ...)
Example:
await db.insert(employees).values({...}).onConflictDoUpdate({
target: employees.employeeId,
targetWhere: sql`name <> 'John Doe'`,
set: { name: sql`excluded.name` },
});
await db.insert(employees).values({...}).onConflictDoUpdate({
target: employees.employeeId,
set: { name: 'John Doe' },
setWhere: sql`name <> 'John Doe'`,
});This split (where → targetWhere + setWhere) was introduced earlier (v0.30.8 for Postgres; v0.30.9 for SQLite), so it’s available in v0.45.0 as well. [1] [2]
Notes:
- This applies to PostgreSQL and SQLite upserts (
ON CONFLICT ...). [1] - MySQL uses
.onDuplicateKeyUpdate(...)(and doesn’t have the sameON CONFLICT ... WHERE ...structure). [1]
Sources:
[1] Drizzle ORM Insert docs (where clauses, targetWhere/setWhere)
[2] Drizzle ORM v0.30.8 release note (split into setWhere and targetWhere)
🏁 Script executed:
# Check if there are any imports of `and`, `eq` functions from drizzle-orm in the file
grep -n "import.*from.*drizzle" lib/actions/chat-db.tsRepository: QueueLab/QCX
Length of output: 172
🌐 Web query:
Drizzle ORM setWhere syntax and condition with eq and chatId userId
💡 Result:
In Drizzle ORM:
1) Normal UPDATE / SELECT / DELETE: use .where(...) (not setWhere)
import { and, eq } from "drizzle-orm";
await db
.update(chatMembers)
.set({ lastReadAt: new Date() })
.where(and(
eq(chatMembers.chatId, chatId),
eq(chatMembers.userId, userId),
));(.where(...) takes a single condition; combine multiple with and(...).) (orm.drizzle.team)
2) INSERT ... onConflictDoUpdate(...): setWhere is for the DO UPDATE’s WHERE
import { and, eq } from "drizzle-orm";
await db.insert(chatMembers).values({ chatId, userId, ... })
.onConflictDoUpdate({
target: [chatMembers.chatId, chatMembers.userId],
set: { lastReadAt: new Date() },
setWhere: and(
eq(chatMembers.chatId, chatId),
eq(chatMembers.userId, userId),
),
});setWhere / targetWhere are specific to onConflictDoUpdate. (orm.drizzle.team)
Citations:
- 1: https://orm.drizzle.team/docs/update?utm_source=openai
- 2: https://orm.drizzle.team/docs/insert?utm_source=openai
Add setWhere guard to prevent cross-chat/user overwrites in upsert.
The upsert targets messages.id alone without guarding against conflicts. If an id collision occurs (or a malicious id is supplied), this can overwrite a message belonging to another chat or user. Add a setWhere clause that restricts updates to rows matching the same chatId and userId being inserted.
🔒 Guarded onConflict update
await tx.insert(messages)
.values(messagesToInsert)
.onConflictDoUpdate({
target: messages.id,
set: {
content: sql`excluded.content`,
role: sql`excluded.role`,
type: sql`excluded.type`,
toolName: sql`excluded.tool_name`,
toolCallId: sql`excluded.tool_call_id`,
attachments: sql`excluded.attachments`
- }
+ },
+ setWhere: and(
+ eq(messages.chatId, sql`excluded.chat_id`),
+ eq(messages.userId, sql`excluded.user_id`)
+ )
});🤖 Prompt for AI Agents
In `@lib/actions/chat-db.ts` around lines 123 - 136, The upsert currently uses
tx.insert(messages).values(messagesToInsert).onConflictDoUpdate targeting
messages.id which can overwrite rows across chats/users; modify the
onConflictDoUpdate call to include a setWhere clause that restricts updates to
rows where messages.chat_id = excluded.chat_id (or messages.chatId =
excluded.chatId) and messages.user_id = excluded.user_id (or messages.userId =
excluded.userId) so updates only apply when the incoming row matches the same
chatId and userId as the existing row; keep the same set mapping (content, role,
type, tool_name, tool_call_id, attachments) but add the setWhere guard to the
onConflictDoUpdate invocation on tx.insert/messages/messagesToInsert.
| try { | ||
| // We need to ensure the message is associated with the chat. | ||
| // dbCreateMessage requires chatId. | ||
| // Check if a 'data' message already exists to avoid duplication | ||
| const existingMessages = await dbGetMessagesByChatId(chatId); | ||
| const dataMessage = existingMessages.find(m => m.role === 'data'); | ||
|
|
||
| if (dataMessage) { | ||
| // Update existing message content using direct db call since chat-db.ts doesn't have an updateMessage | ||
| const { db } = await import('@/lib/db'); | ||
| const { messages } = await import('@/lib/db/schema'); | ||
| const { eq } = await import('drizzle-orm'); | ||
|
|
||
| await db.update(messages) | ||
| .set({ content: JSON.stringify(contextData) }) | ||
| .where(eq(messages.id, dataMessage.id)); | ||
|
|
||
| console.log('Drawing context updated for chat:', chatId, 'messageId:', dataMessage.id); | ||
| return { success: true, messageId: dataMessage.id }; | ||
| } |
There was a problem hiding this comment.
Update the most recent data message to stay in sync with UI restoration.
getUIStateFromAIState renders the last data message, but this update path uses find() which may pick the oldest entry if duplicates exist (legacy data). That can leave the rendered context stale after reloads. Prefer updating the latest data message (or updating all data messages for the chat) to match restoration behavior.
🐛 Proposed fix (pick latest data message)
- const dataMessage = existingMessages.find(m => m.role === 'data');
+ const dataMessage = [...existingMessages]
+ .filter(m => m.role === 'data')
+ .sort(
+ (a, b) =>
+ new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
+ )[0];📝 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.
| try { | |
| // We need to ensure the message is associated with the chat. | |
| // dbCreateMessage requires chatId. | |
| // Check if a 'data' message already exists to avoid duplication | |
| const existingMessages = await dbGetMessagesByChatId(chatId); | |
| const dataMessage = existingMessages.find(m => m.role === 'data'); | |
| if (dataMessage) { | |
| // Update existing message content using direct db call since chat-db.ts doesn't have an updateMessage | |
| const { db } = await import('@/lib/db'); | |
| const { messages } = await import('@/lib/db/schema'); | |
| const { eq } = await import('drizzle-orm'); | |
| await db.update(messages) | |
| .set({ content: JSON.stringify(contextData) }) | |
| .where(eq(messages.id, dataMessage.id)); | |
| console.log('Drawing context updated for chat:', chatId, 'messageId:', dataMessage.id); | |
| return { success: true, messageId: dataMessage.id }; | |
| } | |
| try { | |
| // Check if a 'data' message already exists to avoid duplication | |
| const existingMessages = await dbGetMessagesByChatId(chatId); | |
| const dataMessage = [...existingMessages] | |
| .filter(m => m.role === 'data') | |
| .sort( | |
| (a, b) => | |
| new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() | |
| )[0]; | |
| if (dataMessage) { | |
| // Update existing message content using direct db call since chat-db.ts doesn't have an updateMessage | |
| const { db } = await import('@/lib/db'); | |
| const { messages } = await import('@/lib/db/schema'); | |
| const { eq } = await import('drizzle-orm'); | |
| await db.update(messages) | |
| .set({ content: JSON.stringify(contextData) }) | |
| .where(eq(messages.id, dataMessage.id)); | |
| console.log('Drawing context updated for chat:', chatId, 'messageId:', dataMessage.id); | |
| return { success: true, messageId: dataMessage.id }; | |
| } |
🤖 Prompt for AI Agents
In `@lib/actions/chat.ts` around lines 188 - 205, The current update branch in
save/update logic uses dbGetMessagesByChatId and existingMessages.find(...)
which returns the first (oldest) 'data' message and can leave the UI showing the
last 'data' entry out-of-sync with getUIStateFromAIState; change the selection
to target the most recent 'data' message (e.g., pick the last matching element
by timestamp or id) or iterate and update all messages with role === 'data' so
the DB update against messages (used in the db.update(...) block) updates the
latest/each 'data' record rather than the first one found; update references:
dbGetMessagesByChatId, existingMessages, dataMessage, and the db.update(...)
call accordingly.
| attachments: jsonb('attachments'), | ||
| toolName: varchar('tool_name', { length: 100 }), | ||
| toolCallId: varchar('tool_call_id', {length: 100}), | ||
| type: varchar('type', { length: 50 }) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider typing attachments to avoid any propagation.
Line 33 uses jsonb('attachments') without a type, which makes downstream typings less safe. If null isn’t meaningful, a typed default also removes null checks.
♻️ Suggested typing (adjust shape as needed)
- attachments: jsonb('attachments'),
+ attachments: jsonb('attachments').$type<unknown[]>().default([]),📝 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.
| attachments: jsonb('attachments'), | |
| toolName: varchar('tool_name', { length: 100 }), | |
| toolCallId: varchar('tool_call_id', {length: 100}), | |
| type: varchar('type', { length: 50 }) | |
| attachments: jsonb('attachments').$type<unknown[]>().default([]), | |
| toolName: varchar('tool_name', { length: 100 }), | |
| toolCallId: varchar('tool_call_id', {length: 100}), | |
| type: varchar('type', { length: 50 }) |
🤖 Prompt for AI Agents
In `@lib/db/schema.ts` around lines 33 - 36, The attachments column is declared
with jsonb('attachments') which yields an untyped any; define and use a concrete
type for the JSON shape (e.g., Attachment or Attachment[] depending on expected
payload) and pass it into jsonb to lock down typings (e.g.,
jsonb<Attachment[]>('attachments')), or if null is not meaningful set a typed
default value instead; update the schema declaration that references attachments
and any related uses of toolName/toolCallId/type to use the new Attachment type
for downstream safety and remove unnecessary null checks.
…911856121500176 Ensure text wrapping for long sentences across UI components
- Optimized resolution search with streaming. - Improved timezone handling and AI context. - Fixed UI mutual exclusion and loading state logic. - Stabilized Mapbox cleanup and event listeners. - Resolved build errors and ESLint warnings. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…head features - Reverted branch to commit 6acfbe5. - Merged changes from main (e95a25b). - Preserved Tentree Icon (UsageView logic), History layout (HistoryContainer), and PurchaseCreditsPopup from 6acfbe5 as requested. - Updated Stripe checkout URL in PurchaseCreditsPopup to the official one (14A3cv7K72TR3go14Nasg02). - Re-applied bug fixes from 6acfbe5 (type casting in app/actions.tsx, ESLint dependencies in components/map/mapbox-map.tsx and components/chat-panel.tsx, and relatedQueries initialization). - Verified build and synchronization with main branch features (e.g., resolution search enhancements). Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…8' into jules-8488824498232079115-26d4e4cd
…ranch head features - Reverted branch to commit 6acfbe5 and merged with main (e95a25b). - Preserved Tentree Icon (Usage View), history layout, and PurchaseCreditsPopup from 6acfbe5. - Restored branch-specific UI components: Header, History button, Profile toggle, and Usage integration in Chat. - Synchronized all other logic with main, including resolution search enhancements. - Fixed an infinite re-render loop in Mapbox map by optimizing useEffect dependencies (removed cameraState from initialization effect). - Re-applied critical bug fixes from 6acfbe5: - Type casting in app/actions.tsx (getUIStateFromAIState). - Streamable value initialization for stability. - setSuggestions dependency in ChatPanel. - Updated Stripe checkout URL in PurchaseCreditsPopup to the official link. - Verified successful build with 'bun run build'. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…p, and history toggle
Jules 8488824498232079115 26d4e4cd
Replace all occurrences of the old Stripe checkout link with https://buy.stripe.com/14A3cv7K72TR3go14Nasg02 in: - components/mobile-icons-bar.tsx - components/purchase-credits-popup.tsx - components/usage-view.tsx Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…514914606268144687 Update Stripe payment links
- Updated 'Daily refresh credits' to 'Yearly refresh credits' - Updated refresh credit value from 300 to 500 - Updated subtext to 'Refresh to 500 every year.' - Replaced task list with 'QCX-TERRA Crop yield Analysis', 'QCX-TERRA Flood predictions', and 'Green OS climate synchronization' - Set task dates to 'upcoming' and credit changes to single digits (-7, -5, -3) Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…resh-14763946411330416327 Update Usage View to Yearly Refresh
This commit refactors the desktop header icon container in `components/header.tsx` to ensure all icons are equidistant. Key changes: - Changed the icon container from `justify-between w-1/2` to `justify-center flex-1` with a fixed `gap-10`. - Removed the unused `<div id="timezone-clock-portal" />` which was causing irregular spacing between the TentTree and ModeToggle icons. - Applied `className="contents"` to the `<div id="header-search-portal" />` so that it doesn't affect the flex layout when empty. - These changes ensure that icons are always perfectly centered and equidistant regardless of the number of active icons. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…1736717079589 Standardize Header Icon Spacing
- Modified `mapbox-map.tsx` to only show `NavigationControl` when in `DrawingMode` on desktop. - Fixed a bug in the mode change `useEffect` where previous state was being updated prematurely, preventing cleanup logic from executing correctly. - Updated `tests/map.spec.ts` to switch to `DrawingMode` before verifying zoom control visibility. Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
…94765400062008 Conditional Map Zoom Controls and Mode Transition Fix
Update usage-view.tsx
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@app/actions.tsx`:
- Around line 697-703: The map callback over aiState.messages (inside the
function handling message rendering where actualLastDataIndex is used) does not
return on every path — update the switch inside that callback (the switch
handling message.type/name/etc.) to include an explicit return in the
default/fallthrough case (e.g., return null or the intended fallback JSX/value)
so the callback always returns a value and you no longer rely on later filtering
to remove undefined results; ensure you reference the same variables (message,
index, role, content, id, type, name) and keep existing early returns for role
=== 'data' and other explicit cases.
In `@components/map/mapbox-map.tsx`:
- Line 471: The map initialization effect is re-running and tearing down the map
because it lists mapData.cameraState and position?.latitude/position?.longitude
as dependencies; since the effect's cleanup sets map.current = null this causes
full reinitialization on every camera or position update. Fix by removing
mapData.cameraState and position?.latitude and position?.longitude from the
dependency array of the effect (keep only setup callbacks like setMap,
setIsMapLoaded, captureMapCenter, handleUserInteraction, stopRotation), so the
map is initialized once and position/camera updates are handled by their
dedicated effects.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (4)
app/actions.tsxcomponents/chat-panel.tsxcomponents/chat.tsxcomponents/map/mapbox-map.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-13T13:26:30.086Z
Learnt from: ngoiyaeric
Repo: QueueLab/QCX PR: 0
File: :0-0
Timestamp: 2026-01-13T13:26:30.086Z
Learning: Workers that rely on ai.generateObject (geojsonParser, map-command-generator, feedback-analyzer) must not use the X.AI 'grok-4-fast-non-reasoning' model because X.AI’s chat/completions rejects JSON Schema structured-output arguments; prefer OpenAI gpt-4o for structured outputs.
Applied to files:
app/actions.tsx
🧬 Code graph analysis (1)
components/map/mapbox-map.tsx (1)
components/map/geojson-layer.tsx (4)
GeoJsonLayer(13-100)map(24-80)map(89-96)GeoJsonLayerProps(8-11)
🪛 Biome (2.3.13)
app/actions.tsx
[error] 698-698: This callback passed to map() iterable method should always return a value.
Add missing return statements so that this callback returns a value on all execution paths.
(lint/suspicious/useIterableCallbackReturn)
🔇 Additional comments (4)
components/chat-panel.tsx (1)
217-218: LGTM! GeoJSON file type support added correctly.The accept attribute properly includes both the file extension (
.geojson) and the MIME type (application/geo+json) for broader compatibility. Thedata-testidattribute improves testability.components/chat.tsx (1)
79-85: Good fix for the infinite refresh loop.The
lastRefreshedMessageIdRefguard effectively prevents repeatedrouter.refresh()calls for the same response message. This addresses the refresh loop issue mentioned in the PR comments.app/actions.tsx (1)
863-882: Good error handling for data message parsing.The try-catch wrapper around JSON parsing and the validation of
uploadedGeoJsonarray structure is a good defensive pattern. This correctly handles malformed data messages without crashing the UI state reconstruction.components/map/mapbox-map.tsx (1)
597-599: No issues with GeoJsonLayer placement.The
GeoJsonLayercomponents are properly positioned as siblings in the JSX return statement. SinceGeoJsonLayerreturnsnulland accesses the Mapbox map instance exclusively via theuseMap()context hook, the DOM relationship is irrelevant. The context is available becauseMapboxis rendered withinMapContextProvider(as shown inapp/layout.tsx). The component'suseEffectsafely handles the async nature of map initialization with aif (!map || !data) returnguard. This is a correct pattern for side-effect components that manipulate the map via context without rendering DOM elements.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| return aiState.messages | ||
| .map((message, index) => { | ||
| const { role, content, id, type, name } = message | ||
|
|
||
| if (role === 'data' && index !== actualLastDataIndex) { | ||
| return null | ||
| } |
There was a problem hiding this comment.
Map callback does not explicitly return on all paths.
The static analysis tool correctly identifies that the callback passed to map() at line 698 doesn't always return a value. While the early returns at lines 701-703 and 711 handle some cases, the switch statement inside may fall through without returning.
The current code relies on the filter at line 890 to remove undefined values, but explicit returns improve clarity and prevent subtle bugs.
🛡️ Proposed fix - add explicit return at switch default
default:
return {
id,
component: null
}
}
+ return null // Explicit return for unhandled cases within switch
})
.filter(message => message !== null) as UIState📝 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.
| return aiState.messages | |
| .map((message, index) => { | |
| const { role, content, id, type, name } = message | |
| if (role === 'data' && index !== actualLastDataIndex) { | |
| return null | |
| } | |
| return aiState.messages | |
| .map((message, index) => { | |
| const { role, content, id, type, name } = message | |
| if (role === 'data' && index !== actualLastDataIndex) { | |
| return null | |
| } | |
| switch (type) { | |
| // ... switch cases ... | |
| default: | |
| return { | |
| id, | |
| component: null | |
| } | |
| } | |
| return null // Explicit return for unhandled cases within switch | |
| }) | |
| .filter(message => message !== null) as UIState |
🧰 Tools
🪛 Biome (2.3.13)
[error] 698-698: This callback passed to map() iterable method should always return a value.
Add missing return statements so that this callback returns a value on all execution paths.
(lint/suspicious/useIterableCallbackReturn)
🤖 Prompt for AI Agents
In `@app/actions.tsx` around lines 697 - 703, The map callback over
aiState.messages (inside the function handling message rendering where
actualLastDataIndex is used) does not return on every path — update the switch
inside that callback (the switch handling message.type/name/etc.) to include an
explicit return in the default/fallthrough case (e.g., return null or the
intended fallback JSX/value) so the callback always returns a value and you no
longer rely on later filtering to remove undefined results; ensure you reference
the same variables (message, index, role, content, id, type, name) and keep
existing early returns for role === 'data' and other explicit cases.
| } | ||
| } | ||
| }, [setMap, setIsMapLoaded, captureMapCenter, handleUserInteraction, stopRotation]) | ||
| }, [setMap, setIsMapLoaded, captureMapCenter, handleUserInteraction, stopRotation, mapData.cameraState, position?.latitude, position?.longitude]) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Read the effect at line 471 with context
sed -n '460,485p' components/map/mapbox-map.tsx | cat -nRepository: QueueLab/QCX
Length of output: 1181
🏁 Script executed:
#!/bin/bash
# Find where mapData.cameraState is defined/updated
rg -n "cameraState" components/map/mapbox-map.tsx -B2 -A2Repository: QueueLab/QCX
Length of output: 708
🏁 Script executed:
#!/bin/bash
# Search for setCameraState to understand update patterns
rg -n "setCameraState" components/map/ -A1Repository: QueueLab/QCX
Length of output: 38
🏁 Script executed:
#!/bin/bash
# Look for the full effect hook starting from line 450
sed -n '440,480p' components/map/mapbox-map.tsx | cat -nRepository: QueueLab/QCX
Length of output: 1780
🏁 Script executed:
#!/bin/bash
# Find the complete effect hook starting from an earlier line
sed -n '330,380p' components/map/mapbox-map.tsx | cat -nRepository: QueueLab/QCX
Length of output: 2010
🏁 Script executed:
#!/bin/bash
# Search for initializedRef to see the guard logic
rg -n "initializedRef" components/map/mapbox-map.tsx -B2 -A2Repository: QueueLab/QCX
Length of output: 619
🏁 Script executed:
#!/bin/bash
# Check if position object is frequently updated in state
rg -n "setPosition|position:" components/map/mapbox-map.tsx -C1 | head -40Repository: QueueLab/QCX
Length of output: 198
🏁 Script executed:
#!/bin/bash
# Find where position state is updated
rg -n "const \[position" components/map/mapbox-map.tsx -A5Repository: QueueLab/QCX
Length of output: 38
🏁 Script executed:
#!/bin/bash
# Look for geolocation updates that would change position
rg -n "watchPosition|success.*position" components/map/mapbox-map.tsx -B2 -A5Repository: QueueLab/QCX
Length of output: 324
🏁 Script executed:
#!/bin/bash
# Get component function signature to see if position is a prop
sed -n '1,50p' components/map/mapbox-map.tsx | cat -nRepository: QueueLab/QCX
Length of output: 2981
🏁 Script executed:
#!/bin/bash
# Search for where position prop/state is passed or defined
rg -n "position" components/map/mapbox-map.tsx | head -20Repository: QueueLab/QCX
Length of output: 1147
🏁 Script executed:
#!/bin/bash
# Check the component's type definition or interface
rg -n "interface.*Props|type.*Props" components/map/mapbox-map.tsx -A20Repository: QueueLab/QCX
Length of output: 38
Remove position and cameraState from map initialization effect dependencies—they cause the map to be torn down and rebuilt on every change.
The effect's cleanup function sets map.current = null, so when these dependencies change, the cleanup runs and the guard condition (!map.current) becomes true again, causing the map to fully reinitialize rather than just re-running setup logic.
position?.latitudeandposition?.longitude: A separate effect already handles position updates properly (lines 541-544). Including them here is redundant and causes map destruction/recreation on every prop change.mapData.cameraState: Updates frequently on everymoveendevent (line 412), triggering reinitializations during normal map interaction. The initial effect only needs to read it once; it should not be a dependency.
Only callbacks that are called during setup should be dependencies. Remove position properties and cameraState from the dependency array.
🤖 Prompt for AI Agents
In `@components/map/mapbox-map.tsx` at line 471, The map initialization effect is
re-running and tearing down the map because it lists mapData.cameraState and
position?.latitude/position?.longitude as dependencies; since the effect's
cleanup sets map.current = null this causes full reinitialization on every
camera or position update. Fix by removing mapData.cameraState and
position?.latitude and position?.longitude from the dependency array of the
effect (keep only setup callbacks like setMap, setIsMapLoaded, captureMapCenter,
handleUserInteraction, stopRotation), so the map is initialized once and
position/camera updates are handled by their dedicated effects.
User description
This change enables users to upload GeoJSON files (.geojson, application/geo+json) directly through the chat interface. Uploaded data is automatically rendered on the map (both Mapbox and Google Maps) and the viewport is adjusted to frame the data's extent using Turf.js.
The implementation includes:
uploadedGeoJsonfield inMapDataContext.submitaction.MapDataUpdatercomponent that manages state synchronization and "fly to" behavior.geospatialToolto allow AI agents to return GeoJSON that is processed through the same pipeline.data-testidattributes.PR created automatically by Jules for task 11124993269366174034 started by @ngoiyaeric
PR Type
Enhancement
Description
Enable GeoJSON file uploads (.geojson, application/geo+json) through chat interface
Automatically render uploaded GeoJSON on maps with viewport framing using Turf.js
Persist GeoJSON layers in database for chat session reload capability
Allow AI agents to return GeoJSON through geospatial tool pipeline
Add MapDataUpdater component for state synchronization and map navigation
Diagram Walkthrough
File Walkthrough
10 files
Add uploadedGeoJson field to context updateImplement GeoJSON file parsing and upload pipelineAdd GeoJSON file type to upload input acceptSync uploadedGeoJson to server via updateDrawingContextAdd uploadedGeoJson array to MapData interfaceCreate headless component for GeoJSON context syncIntegrate MapDataUpdater for tool GeoJSON resultsRender uploaded GeoJSON layers on MapboxRender uploaded GeoJSON layers on Google MapsExtract and pass GeoJSON from geospatial tool results2 files
Add data-testid attribute to bot messageAdd data-testid attribute to user messageSummary by CodeRabbit
New Features
Bug Fixes
Tests