Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
}

.mobile-chat-input-area {
height: 70px;
height: auto;
padding: 10px;
background-color: hsl(var(--background));
/* border-top: 1px solid hsl(var(--border)); */ /* Removed for cleaner separation */
Expand Down
10 changes: 6 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 15 additions & 15 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
if (!input && !selectedFile) {
if (!input.trim() && !selectedFile) {
return
}

Expand Down Expand Up @@ -137,7 +137,7 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
}

const wordCount = value.trim().split(/\s+/).filter(Boolean).length
if (wordCount < 3) {
if (wordCount < 2) {
setSuggestions(null)
return
}
Expand Down Expand Up @@ -194,18 +194,6 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
: 'sticky bottom-0 bg-background z-10 w-full border-t border-border px-2 py-3 md:px-4'
)}
>
{selectedFile && (
<div className="w-full px-4 pb-2">
<div className="flex items-center justify-between p-2 bg-muted rounded-lg">
<span className="text-sm text-muted-foreground truncate max-w-xs">
{selectedFile.name}
</span>
<Button variant="ghost" size="icon" onClick={clearAttachment} data-testid="clear-attachment-button">
<X size={16} />
</Button>
</div>
</div>
)}
<form
ref={formRef}
onSubmit={handleSubmit}
Expand Down Expand Up @@ -237,7 +225,7 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
'absolute top-1/2 transform -translate-y-1/2 left-3'
)}
onClick={handleAttachmentClick}
data-testid="attachment-button"
data-testid="desktop-attachment-button"
>
<Paperclip size={isMobile ? 18 : 20} />
</Button>
Expand Down Expand Up @@ -303,6 +291,18 @@ export const ChatPanel = forwardRef<ChatPanelRef, ChatPanelProps>(({ messages, i
{/* Suggestions are now handled by the parent component (chat.tsx) as an overlay */}
</div>
</form>
{selectedFile && (
<div className="w-full px-4 pb-2 mb-2">
<div className="flex items-center justify-between p-2 bg-muted rounded-lg">
<span className="text-sm text-muted-foreground truncate max-w-xs">
{selectedFile.name}
</span>
<Button variant="ghost" size="icon" onClick={clearAttachment} data-testid="clear-attachment-button">
<X size={16} />
</Button>
</div>
</div>
)}
</div>
)
})
Expand Down
6 changes: 5 additions & 1 deletion components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export function Chat({ id }: ChatProps) {
const handleAttachment = () => {
chatPanelRef.current?.handleAttachmentClick();
};

const handleMobileSubmit = () => {
chatPanelRef.current?.submitForm();
};

useEffect(() => {
setShowEmptyScreen(messages.length === 0)
Expand Down Expand Up @@ -106,7 +110,7 @@ export function Chat({ id }: ChatProps) {
{activeView ? <SettingsView /> : <MapProvider />}
</div>
<div className="mobile-icons-bar">
<MobileIconsBar onAttachmentClick={handleAttachment} />
<MobileIconsBar onAttachmentClick={handleAttachment} onSubmitClick={handleMobileSubmit} />
</div>
<div className="mobile-chat-input-area">
<ChatPanel
Expand Down
7 changes: 3 additions & 4 deletions components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ export const Header = () => {
<Image
src="/images/logo.svg"
alt="Logo"
width={24}
height={24}
className="h-6 w-auto"
width={20}
height={20}
className="h-5 w-auto"
/>
</Button>
<h1 className="text-2xl font-poppins font-semibold text-primary">
Expand Down Expand Up @@ -66,7 +66,6 @@ export const Header = () => {

{/* Mobile menu buttons */}
<div className="flex md:hidden gap-2">
<div id="mobile-header-search-portal" />

<a href="https://buy.stripe.com/3cIaEX3tRcur9EM7tbasg00" target="_blank" rel="noopener noreferrer">
<Button variant="ghost" size="icon">
Expand Down
4 changes: 1 addition & 3 deletions components/history-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ type HistoryContainerProps = {
location: 'sidebar' | 'header'
}

const HistoryContainer: React.FC<HistoryContainerProps> = async ({
location
}) => {
const HistoryContainer: React.FC<HistoryContainerProps> = ({ location }) => {
return (
<div className="sm:hidden block">
<History location={location} />
Expand Down
4 changes: 3 additions & 1 deletion components/map/mapbox-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,9 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
preserveDrawingBuffer: true
})

map.current.addControl(new mapboxgl.NavigationControl(), 'top-left')
if (window.innerWidth > 768) {
map.current.addControl(new mapboxgl.NavigationControl(), 'top-left')
}

// Register event listeners
map.current.on('moveend', captureMapCenter)
Expand Down
7 changes: 4 additions & 3 deletions components/mobile-icons-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import { useCalendarToggle } from './calendar-toggle-context'

interface MobileIconsBarProps {
onAttachmentClick: () => void;
onSubmitClick: () => void;
}

export const MobileIconsBar: React.FC<MobileIconsBarProps> = ({ onAttachmentClick }) => {
export const MobileIconsBar: React.FC<MobileIconsBarProps> = ({ onAttachmentClick, onSubmitClick }) => {
const [, setMessages] = useUIState<typeof AI>()
const { clearChat } = useActions()
const { toggleCalendar } = useCalendarToggle()
Expand Down Expand Up @@ -52,10 +53,10 @@ export const MobileIconsBar: React.FC<MobileIconsBarProps> = ({ onAttachmentClic
<TentTree className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
</a>
<Button variant="ghost" size="icon" onClick={onAttachmentClick}>
<Button variant="ghost" size="icon" onClick={onAttachmentClick} data-testid="mobile-attachment-button">
<Paperclip className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
<Button variant="ghost" size="icon" data-testid="mobile-submit-button" disabled aria-disabled="true">
<Button variant="ghost" size="icon" data-testid="mobile-submit-button" onClick={onSubmitClick}>
<ArrowRight className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
<History location="header" />
Expand Down
2 changes: 1 addition & 1 deletion components/search-related.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const SearchRelated: React.FC<SearchRelatedProps> = ({
{data?.items
?.filter(item => item?.query !== '')
.map((item, index) => (
<div className="flex items-start w-full" key={index}>
<div className="flex items-start w-full animate-in fade-in slide-in-from-bottom-2 duration-300" key={index}>
<ArrowRight className="h-4 w-4 mr-2 mt-1 flex-shrink-0 text-accent-foreground/50" />
<Button
variant="link"
Expand Down
2 changes: 1 addition & 1 deletion lib/agents/tools/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ async function tavilySearch(
includeRawContent: include_raw_content ? 'text' : undefined
})

return response
return { ...response, results: response.results.reverse() }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Read the file to understand the context around line 102
cat -n lib/agents/tools/search.tsx | head -120 | tail -30

Repository: QueueLab/QCX

Length of output: 1297


🏁 Script executed:

# Search for more context about the response object and how it's used
rg -B 10 -A 5 "response.results.reverse" lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 590


🏁 Script executed:

# Check if there are any comments or documentation about the reversal intent
rg -B 5 -A 5 "reverse|Reverse" lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 435


🏁 Script executed:

# Look for how the function is called and whether response is reused
rg -B 20 "return.*response.*results.*reverse" lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 884


🏁 Script executed:

# Search for comments or documentation about the reversal
git log --oneline -20 -- lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 118


🏁 Script executed:

# Look for any tests or examples that might explain the intent
fd -t f "test|spec" | xargs rg -l "tavily|tavilySearch" 2>/dev/null || echo "No test files found with tavily"

Repository: QueueLab/QCX

Length of output: 87


🏁 Script executed:

# Check if there's a README or docs about the search behavior
find . -type f \( -name "README*" -o -name "*.md" \) | xargs rg -l "tavily|reverse" 2>/dev/null || echo "No docs found"

Repository: QueueLab/QCX

Length of output: 67


🏁 Script executed:

# Check the entire function to see if there's context about why reversing is needed
rg -B 30 "return.*response.*results.*reverse" lib/agents/tools/search.tsx | head -50

Repository: QueueLab/QCX

Length of output: 1045


🏁 Script executed:

# Check if there's any commit message that explains the reversal
git show dffafc9 --stat || git log -p -S "reverse" -- lib/agents/tools/search.tsx | head -80

Repository: QueueLab/QCX

Length of output: 38


🏁 Script executed:

# Look at the entire file to understand the context better
wc -l lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 87


🏁 Script executed:

# Check if there are any other uses of results to see ordering expectations
rg "results\[" lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 38


🏁 Script executed:

# Search across the codebase for usage of tavilySearch to see how results are handled
rg "tavilySearch" --type tsx --type ts -A 5 | head -80

Repository: QueueLab/QCX

Length of output: 83


🏁 Script executed:

# Search for tavilySearch usage with corrected file type
rg "tavilySearch" -A 5 | head -100

Repository: QueueLab/QCX

Length of output: 757


🏁 Script executed:

# Check for any configuration or constants related to ordering
rg "order|sort|ascending|descending" lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 38


🏁 Script executed:

# Look at entire file to get full context
cat -n lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 4263


🏁 Script executed:

# Check Tavily API documentation/types in node_modules or imported
rg "@tavily" lib/agents/tools/search.tsx

Repository: QueueLab/QCX

Length of output: 93


🏁 Script executed:

# Check if there's any PR or commit message context
git log --all --oneline | head -20

Repository: QueueLab/QCX

Length of output: 1548


🏁 Script executed:

# Look for any configuration files that might explain the decision
find . -type f \( -name "*.md" -o -name "*.txt" -o -name ".env*" \) -not -path "*/node_modules/*" | xargs rg -l "reverse|tavily" 2>/dev/null || true

Repository: QueueLab/QCX

Length of output: 67


🏁 Script executed:

# Check the specific commit that added the reversal
git show a6584c9 --stat | head -30

Repository: QueueLab/QCX

Length of output: 1085


🏁 Script executed:

# Look for any PR description or comments
git log -p a6584c9 -- lib/agents/tools/search.tsx | head -100

Repository: QueueLab/QCX

Length of output: 38


🏁 Script executed:

# Check if there's documentation about why results should be reversed
cat README.md | grep -A 10 -B 10 -i "tavily\|reverse\|search" | head -50

Repository: QueueLab/QCX

Length of output: 1150


🌐 Web query:

Tavily API search results order relevance score

💡 Result:

Short answer: Tavily returns results sorted by relevance (highest score first) and each result includes a numeric "score" indicating relevance; you can use that score to filter or re-sort client-side. [1][2][3]

Details and useful parameters:

  • results[] is "a list of sorted search results, ranked by relevancy" (results.score is a float shown per result). Higher = more relevant. [1]
  • The docs explain the meaning and usage of score (numerical measure of relevance; use a minimum-score cutoff to exclude low‑relevance results). [2]
  • Search behavior can be tuned via parameters that affect scoring and ordering: search_depth (basic/advanced/fast/ultra-fast), chunks_per_source, max_results, topic/time_range and the auto_parameters option. Use these to trade latency vs. relevance and control returned items. [3][1]

If you want, tell me which SDK/language you’re using and I’ll show a short code example to sort/filter by score.


🌐 Web query:

Tavily AI documentation results ordering

💡 Result:

Short answer: Tavily returns results sorted by relevancy (highest relevancy first). The Search API response's results array is "ranked by relevancy" and each result includes a score and origin (local or foreign). You can control ordering via API parameters (max_results, max_local/max_foreign, search_depth, time_range, etc.) and—when using the SDK—provide a custom ranking_function that must return an ordered list of documents sorted by decreasing relevancy. [1][2][3]

Sources:

  • Tavily Search API docs (results ranked by relevancy; response fields and parameters). [1]
  • Tavily Python SDK reference (search returns documents sorted by decreasing relevancy; supports ranking_function). [2]
  • Tavily JavaScript SDK reference (same ranking_function behavior). [3]

Avoid mutating the original array.

Array.reverse() mutates in-place, and while response is local and not reused in this function, it's still an antipattern. Use a non-mutating approach for clarity and robustness.

Proposed fix
-  return { ...response, results: response.results.reverse() }
+  return { ...response, results: [...response.results].reverse() }

Or use ES2023 toReversed() if available:

-  return { ...response, results: response.results.reverse() }
+  return { ...response, results: response.results.toReversed() }

Clarify the intent of reversing results.

Tavily API returns results sorted by relevance (highest score first). Reversing the array places the least relevant results first, which contradicts the stated goal of "prioritizing relevance." Confirm whether reversing is intentional or a mistake.

📝 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
return { ...response, results: response.results.reverse() }
return { ...response, results: [...response.results].reverse() }
🤖 Prompt for AI Agents
In @lib/agents/tools/search.tsx at line 102, The current return uses
response.results.reverse() which mutates the original array; change it to a
non-mutating alternative such as [...response.results].reverse() or
response.results.toReversed() (ES2023) to avoid in-place mutation of
response.results, and while editing, confirm whether reversing is actually
intended given Tavily returns results by relevance (i.e., verify the intent in
the code path that constructs/uses response and results before flipping the
order).

}

async function exaSearch(query: string, maxResults: number = 10): Promise<any> {
Expand Down
6 changes: 6 additions & 0 deletions tests/map.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ test.describe('Map functionality', () => {
});

test('should zoom in and out using map controls', async ({ page }) => {
// This test should only run on desktop where the controls are visible
if (page.viewportSize()!.width <= 768) {
test.skip(true, 'Zoom controls are not visible on mobile');
return;
}

const isMapbox = await page.locator('.mapboxgl-canvas').isVisible();
if (!isMapbox) {
test.skip(true, 'Zoom controls test is only for Mapbox');
Expand Down
4 changes: 2 additions & 2 deletions tests/mobile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ test.describe('Mobile UI', () => {
await expect(page.locator('.mobile-icons-bar-content [data-testid="profile-toggle"]')).toBeDisabled();
});

test('should have a disabled submit button', async ({ page }) => {
test('should have an enabled submit button', async ({ page }) => {
const submitButton = page.locator('[data-testid="mobile-submit-button"]');
await expect(submitButton).toBeDisabled();
await expect(submitButton).toBeEnabled();
});
Comment on lines +17 to 20
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Test correctly updated to reflect the enabled submit button.

The assertion change from toBeDisabled() to toBeEnabled() properly reflects the new mobile submit functionality.

Consider adding a follow-up test that verifies the submit button actually triggers form submission (e.g., by typing input and clicking the button, then asserting the message appears).

});