Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Sep 7, 2025

User description

The "new chat" button was disappearing after interacting with the map. This was caused by the isButtonPressed state in the ChatPanel component, which was a remnant of a previous implementation and was not being reset correctly.

This change removes the isButtonPressed state and all associated logic from the ChatPanel component. The "new chat" button's visibility is now solely dependent on the number of messages and the device type.


PR Type

Bug fix


Description

  • Remove isButtonPressed state causing new chat button disappearance

  • Simplify button visibility logic to depend only on messages and device type

  • Clean up associated useEffect and form submission logic


Diagram Walkthrough

flowchart LR
  A["ChatPanel Component"] --> B["Remove isButtonPressed state"]
  B --> C["Simplify button visibility logic"]
  C --> D["Fix new chat button disappearing"]
Loading

File Walkthrough

Relevant files
Bug fix
chat-panel.tsx
Remove isButtonPressed state and associated logic               

components/chat-panel.tsx

  • Remove isButtonPressed state variable and setter
  • Delete useEffect that handled button press focus logic
  • Remove button press checks from form submission handler
  • Simplify new chat button visibility condition
+1/-13   

Summary by CodeRabbit

  • New Features
    • Added a "Clear Chat / New Chat" action that starts a fresh conversation and clears messages without navigation.
  • Bug Fixes
    • New Chat button now consistently appears on non-mobile devices when a conversation has messages.
    • Clearing a chat reliably resets messages; input focus behavior remains stable after send.
  • Chores
    • Added DATABASE_URL environment variable placeholder.

The "new chat" button was disappearing after interacting with the map.
This was caused by the `isButtonPressed` state in the `ChatPanel`
component, which was a remnant of a previous implementation and was not
being reset correctly.

This change removes the `isButtonPressed` state and all associated logic
from the `ChatPanel` component. The "new chat" button's visibility is
now solely dependent on the number of messages and the device type.
@vercel
Copy link
Contributor

vercel bot commented Sep 7, 2025

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

Project Deployment Preview Comments Updated (UTC)
qcx Ready Ready Preview Comment Sep 8, 2025 8:40am

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 7, 2025

Walkthrough

Added a server-side clearChat action and exposed it via AI actions; replaced router-based "new chat" navigation with calls that clear UI messages and invoke clearChat; removed isButtonPressed state and related logic from the chat panel; added DATABASE_URL to .env.

Changes

Cohort / File(s) Summary of Changes
Chat panel state cleanup
components/chat-panel.tsx
Removed isButtonPressed state and its useEffect; removed conditional submit/clear logic tied to it; simplified "New chat" visibility to (messages.length > 0 && !isMobile); noted removal of mcp arg from submit.
Server action: clearChat
app/actions.tsx
Added a server-side clearChat action ("use server") that generates a new chatId and clears messages; exposed clearChat in the exported AI actions map. Diff shows duplicate top-level clearChat definitions (potential duplication). Also added getSystemPrompt usage.
Mobile new-chat flow
components/mobile-icons-bar.tsx
Removed Next useRouter navigation; added AI hooks (useUIState<typeof AI>, useActions<typeof AI>); handleNewChat clears UI messages (setMessages([])) and awaits clearChat() instead of routing.
Env config
.env
Added DATABASE_URL=postgresql://user:password@host:port/db.

Sequence Diagram(s)

sequenceDiagram
  participant Mobile as MobileIconsBar / ChatPanel
  participant UI as Client UI State (useUIState)
  participant Actions as AI Actions (useActions)
  participant Server as Server Action (clearChat)

  Mobile->>UI: setMessages([])      %%{style: fill:#f3f4f6}%%
  Mobile->>Actions: clearChat()     %%{style: fill:#e6fffa}%%
  Actions->>Server: invoke clearChat() 
  Server-->>Actions: new chatId, messages=[]
  Actions-->>UI: propagate new AI state (chatId/messages)
  UI-->>Mobile: UI updates (empty conversation)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Review effort 3/5 needs review server-action

Poem

I hop and clear the cluttered lair,
New chat seeds scattered in the air.
No pressing state, a lighter trail,
I nudge the server — fresh chat set sail.
Thump-thump, code carrots everywhere. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/new-chat-button-disappears

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

❤️ Share

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

@qodo-code-review
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

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

UX Regression

Removing the isButtonPressed gate from the visibility condition means the "New chat" button will always show on desktop when there are messages. Confirm this does not reintroduce prior issues (e.g., button lingering during form submission or after starting a new chat) and that expected UX matches product requirements.

if (messages.length > 0 && !isMobile) {
  return (
    <div
      className={cn(
        'fixed bottom-2 left-2 flex justify-start items-center pointer-events-none',
        isMobile ? 'w-full px-2' : 'md:bottom-8'
Focus Behavior

The previous effect tied to isButtonPressed ensured input focus after certain interactions. With the blanket focus-on-mount useEffect, validate that focus still behaves correctly across route changes and when toggling between mobile/desktop breakpoints.

useEffect(() => {
  inputRef.current?.focus(); 
}, [])

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Sep 7, 2025

PR Code Suggestions ✨

No code suggestions found for the PR.

This commit addresses an issue where the "new chat" button did not correctly start a new chat session. The previous implementation using client-side routing (`router.push('/')`) did not reset the AI state, causing the old chat to persist.

This change introduces a new `clearChat` server action that programmatically resets the AI and UI states. Both the desktop (`ChatPanel`) and mobile (`MobileIconsBar`) "new chat" buttons now use this action to ensure a clean state for new conversations. This approach is more robust and aligns with the application's state management patterns.

This also resolves the initial issue where the button would disappear incorrectly.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
components/chat-panel.tsx (2)

4-6: Fix invalid type-only import for AI and add proper typing for useActions.

useUIState<typeof AI>() requires importing AI as a value, not a type. Current import will fail TypeScript. Also type useActions for safer calls.

-import type { AI, UIState } from '@/app/actions'
+import { AI } from '@/app/actions'
+import type { UIState } from '@/app/actions'
@@
-  const [, setMessages] = useUIState<typeof AI>()
-  const { submit, clearChat } = useActions()
+  const [, setMessages] = useUIState<typeof AI>()
+  const { submit, clearChat } = useActions<typeof AI>()

Also applies to: 21-23


38-51: Prevent duplicate submissions and whitespace-only sends; clear input on send

+  const [isSubmitting, setIsSubmitting] = useState(false)
@@
   const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
     e.preventDefault()
+    if (isSubmitting) return
+    if (input.trim().length === 0) return
+    setIsSubmitting(true)
     setMessages(currentMessages => [
       ...currentMessages,
       {
         id: nanoid(),
         component: <UserMessage message={input} />
       }
     ])
+    setInput('')
     const formData = new FormData(e.currentTarget)
     // Removed mcp argument from submit call
-    const responseMessage = await submit(formData)
-    setMessages(currentMessages => [...currentMessages, responseMessage as any])
+    try {
+      const responseMessage = await submit(formData)
+      setMessages(currentMessages => [...currentMessages, responseMessage as any])
+    } finally {
+      setIsSubmitting(false)
+    }
   }
@@
-          <Button
+          <Button
             type="submit"
             size={'icon'}
             variant={'ghost'}
             className={cn(
               'absolute top-1/2 transform -translate-y-1/2',
               isMobile ? 'right-1' : 'right-2'
             )}
-            disabled={input.length === 0}
+            disabled={isSubmitting || input.trim().length === 0}
           >

After applying, test by rapidly pressing Enter or clicking the send button to confirm only one request is sent per message and blank inputs are blocked.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d59f1c2 and ad49728.

📒 Files selected for processing (3)
  • app/actions.tsx (2 hunks)
  • components/chat-panel.tsx (2 hunks)
  • components/mobile-icons-bar.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
components/mobile-icons-bar.tsx (4)
app/actions.tsx (1)
  • AI (283-353)
components/chat.tsx (3)
  • aiState (56-61)
  • Chat (20-123)
  • path (50-54)
app/page.tsx (1)
  • Page (9-18)
app/search/[id]/page.tsx (1)
  • SearchPage (27-76)
app/actions.tsx (2)
lib/actions/chat.ts (3)
  • msg (119-127)
  • clearChats (77-99)
  • updateDrawingContext (165-205)
components/chat.tsx (2)
  • aiState (56-61)
  • Chat (20-123)
components/chat-panel.tsx (2)
components/chat.tsx (2)
  • Chat (20-123)
  • path (50-54)
components/sidebar/chat-history-client.tsx (1)
  • ChatHistoryClient (28-161)

Comment on lines +251 to +260
async function clearChat() {
'use server'

const aiState = getMutableAIState<typeof AI>()

aiState.done({
chatId: nanoid(),
messages: []
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Return new chatId and preserve other AI state on clear.

Return the generated chatId so clients can sync the URL, and keep other fields (e.g., isSharePage).

-async function clearChat() {
-  'use server'
-
-  const aiState = getMutableAIState<typeof AI>()
-
-  aiState.done({
-    chatId: nanoid(),
-    messages: []
-  })
-}
+async function clearChat() {
+  'use server'
+  const aiState = getMutableAIState<typeof AI>()
+  const prev = aiState.get()
+  const newChatId = nanoid()
+  aiState.done({
+    ...prev,
+    chatId: newChatId,
+    messages: []
+  })
+  return { chatId: newChatId }
+}
📝 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
async function clearChat() {
'use server'
const aiState = getMutableAIState<typeof AI>()
aiState.done({
chatId: nanoid(),
messages: []
})
}
async function clearChat() {
'use server'
const aiState = getMutableAIState<typeof AI>()
const prev = aiState.get()
const newChatId = nanoid()
aiState.done({
...prev,
chatId: newChatId,
messages: []
})
return { chatId: newChatId }
}
🤖 Prompt for AI Agents
In app/actions.tsx around lines 251 to 260, the clearChat function currently
replaces the AI state wholesale with a new object containing only chatId and
messages; change it to generate the new chatId, read the current mutable AI
state, and call aiState.done with a merged object that preserves existing fields
(for example isSharePage and any other properties) while setting chatId to the
new id and messages to an empty array, and finally return the generated chatId
so callers can sync the URL.

Comment on lines +286 to 287
clearChat,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Expose typed actions to clients expecting clearChat result.

After returning { chatId } from clearChat, ensure clients await and use it to keep route and state consistent.

🤖 Prompt for AI Agents
In app/actions.tsx around lines 286-287, the actions export needs to expose
clearChat with an explicit return type so callers can await the returned {
chatId } and keep route/state consistent; update the clearChat function
signature to return Promise<{ chatId: string }>, ensure the exported actions
object includes the typed clearChat, and update any call sites to await
clearChat() and use the returned chatId to update route/state.

Comment on lines +53 to 56
const handleClear = async () => {
setMessages([])
await clearChat()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider URL-and-state alignment after “New” on desktop.

handleClear resets state but not the route; saved chats use /search/${chatId}. After clear, the path may still reference the old chat. Once clearChat returns the new chatId, replace the URL similar to MobileIconsBar.

Also applies to: 63-83

🤖 Prompt for AI Agents
In components/chat-panel.tsx around lines 53-56 (and similarly 63-83),
handleClear resets local state but doesn't update the URL so the route can still
reference the old chat; change it to await clearChat(), capture the returned new
chatId, then reset messages/state and call
router.replace(`/search/${newChatId}`) (use replace not push) to align URL and
state like MobileIconsBar; ensure router is available and handle
errors/undefined chatId gracefully.

Comment on lines +25 to 28
const handleNewChat = async () => {
setMessages([])
await clearChat()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

Guard against double taps; optionally update route after clear.

Add a pending flag to prevent repeated clears, and (once clearChat returns the new chatId) replace the URL so it matches the fresh chat.

 export const MobileIconsBar: React.FC = () => {
-  const [, setMessages] = useUIState<typeof AI>()
-  const { clearChat } = useActions<typeof AI>()
+  const [, setMessages] = useUIState<typeof AI>()
+  const { clearChat } = useActions<typeof AI>()
+  const [isClearing, setIsClearing] = React.useState(false)
 
-  const handleNewChat = async () => {
-    setMessages([])
-    await clearChat()
+  const handleNewChat = async () => {
+    if (isClearing) return
+    setIsClearing(true)
+    try {
+      setMessages([])
+      const result = await clearChat() // expect { chatId }
+      if (result?.chatId && window.location.pathname.startsWith('/search/')) {
+        window.history.replaceState({}, '', `/search/${result.chatId}`)
+      }
+    } finally {
+      setIsClearing(false)
+    }
   }

And disable the button while clearing:

-      <Button variant="ghost" size="icon" onClick={handleNewChat}>
+      <Button variant="ghost" size="icon" onClick={handleNewChat} disabled={isClearing}>
         <Plus className="h-[1.2rem] w-[1.2rem]" />
       </Button>
📝 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
const handleNewChat = async () => {
setMessages([])
await clearChat()
}
// components/mobile-icons-bar.tsx
export const MobileIconsBar: React.FC = () => {
const [, setMessages] = useUIState<typeof AI>()
const { clearChat } = useActions<typeof AI>()
const [isClearing, setIsClearing] = React.useState(false)
const handleNewChat = async () => {
if (isClearing) return
setIsClearing(true)
try {
setMessages([])
const result = await clearChat() // expect { chatId }
if (result?.chatId && window.location.pathname.startsWith('/search/')) {
window.history.replaceState({}, '', `/search/${result.chatId}`)
}
} finally {
setIsClearing(false)
}
}
return (
<div className="mobile-icons-bar">
{/* other icons… */}
<Button
variant="ghost"
size="icon"
onClick={handleNewChat}
disabled={isClearing}
>
<Plus className="h-[1.2rem] w-[1.2rem]" />
</Button>
{/* other icons… */}
</div>
)
}
🤖 Prompt for AI Agents
In components/mobile-icons-bar.tsx around lines 25–28, guard against double taps
by adding a local pending state (e.g., isClearing) and early-return if already
pending; set it true before starting, and false in a finally block. Call
setMessages([]) and then await clearChat(), capture its returned newChatId, and
if present call the router replace (or use history.replaceState) to update the
URL to the fresh chatId. Also bind the clear button's disabled attribute to the
pending flag so the UI is disabled while clearing.

This commit addresses an issue where the "new chat" button did not correctly start a new chat session. The previous implementation using client-side routing (`router.push('/')`) did not reset the AI state, causing the old chat to persist.

This change introduces a new `clearChat` server action that programmatically resets the AI and UI states. Both the desktop (`ChatPanel`) and mobile (`MobileIconsBar`) "new chat" buttons now use this action to ensure a clean state for new conversations. This approach is more robust and aligns with the application's state management patterns.

This also resolves the initial issue where the button would disappear incorrectly.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
components/mobile-icons-bar.tsx (1)

32-34: Disable the button while clearing.

Improves UX and prevents re-entry.

-      <Button variant="ghost" size="icon" onClick={handleNewChat}>
+      <Button variant="ghost" size="icon" onClick={handleNewChat} disabled={isClearing} aria-busy={isClearing}>
         <Plus className="h-[1.2rem] w-[1.2rem]" />
       </Button>
♻️ Duplicate comments (2)
components/mobile-icons-bar.tsx (2)

25-28: Guard double taps and update URL with new chatId.

Prevents repeated clears and keeps the address bar in sync with the fresh chat.

-  const handleNewChat = async () => {
-    setMessages([])
-    await clearChat()
-  }
+  const handleNewChat = async () => {
+    if (isClearing) return
+    setIsClearing(true)
+    try {
+      setMessages([])
+      const result = await clearChat() // expect { chatId }
+      if (result?.chatId && window.location.pathname.startsWith('/search/')) {
+        window.history.replaceState({}, '', `/search/${result.chatId}`)
+      }
+    } finally {
+      setIsClearing(false)
+    }
+  }

Verify server-side shape and duplication of clearChat, and ensure the old ChatPanel flag is gone:

#!/bin/bash
set -euo pipefail

echo "Leftover isButtonPressed usages:"
rg -n 'isButtonPressed|setIsButtonPressed' -S

echo "clearChat definitions:"
rg -nP '^\s*(export\s+)?(async\s+)?function\s+clearChat\b|^\s*const\s+clearChat\s*=' -C2

22-24: Type actions and prep pending state.

Use the generic for useActions and add a local pending flag for subsequent diff.

-  const [, setMessages] = useUIState<typeof AI>()
-  const { clearChat } = useActions()
+  const [, setMessages] = useUIState<typeof AI>()
+  const { clearChat } = useActions<typeof AI>()
+  const [isClearing, setIsClearing] = React.useState(false)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad49728 and 7cc3024.

⛔ Files ignored due to path filters (1)
  • bun.lockb is excluded by !**/bun.lockb
📒 Files selected for processing (2)
  • .env (1 hunks)
  • components/mobile-icons-bar.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
.env (1)
lib/db/migrate.ts (1)
  • runMigrations (8-34)
components/mobile-icons-bar.tsx (4)
app/actions.tsx (4)
  • AI (283-353)
  • aiState (343-478)
  • state (290-340)
  • aiState (278-289)
components/chat.tsx (3)
  • aiState (56-61)
  • Chat (20-123)
  • path (50-54)
app/page.tsx (1)
  • Page (9-18)
components/chat-panel.tsx (1)
  • router (66-68)
🪛 dotenv-linter (3.3.0)
.env

[warning] 1-1: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🔇 Additional comments (1)
components/mobile-icons-bar.tsx (1)

4-6: Type your AI actions for safety.

Since AI is exported from app/actions, prefer typed hooks for autocomplete and compile-time checks.

Apply:

-import { useUIState, useActions } from 'ai/rsc'
+import { useUIState, useActions } from 'ai/rsc'
+// below, use: useActions<typeof AI>()

Likely an incorrect or invalid review comment.

@@ -0,0 +1 @@
DATABASE_URL="postgresql://user:password@host:port/db"
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

⚠️ Potential issue

Do not commit .env; move to .env.example and drop quotes.

Committing environment files is risky. Replace this with an example file and ensure .env is gitignored. Also fix the dotenv-linter warning by removing quotes.

Apply:

-DATABASE_URL="postgresql://user:password@host:port/db"
+DATABASE_URL=postgresql://<user>:<password>@<host>:<port>/<db>

Also:

  • Add to .gitignore:
+.env
+.env.local
  • Rename this file to .env.example (I can draft it if you want).
🧰 Tools
🪛 dotenv-linter (3.3.0)

[warning] 1-1: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🤖 Prompt for AI Agents
In .env around line 1, the committed environment file with quoted sensitive
values must be removed from the repo, replaced by an example file, and quotes
dropped; rename the current .env to .env.example (remove any real secrets and
unquote values), add entries .env and .env.local to .gitignore, and remove the
committed .env from git history/staging (e.g., git rm --cached .env and commit)
so only .env.example remains tracked.

@ngoiyaeric ngoiyaeric merged commit f3df729 into main Sep 8, 2025
4 of 5 checks passed
@ngoiyaeric ngoiyaeric deleted the fix/new-chat-button-disappears branch September 10, 2025 07:02
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.

2 participants