Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Sep 30, 2025

User description

This commit implements a change to the chat interface where the example prompts are now hidden by default. They are revealed only when the user clicks on or focuses the chat input field.

This change improves the initial user experience by providing a cleaner, less cluttered interface upon first load.

Additionally, this commit also introduces a .gitignore file to prevent sensitive environment files and development logs from being accidentally committed to the repository. This is a crucial step for improving the project's security and maintainability.


PR Type

Enhancement


Description

  • Hide example prompts until chat input is focused

  • Improve initial UI experience with cleaner interface

  • Add focus/blur handlers to chat input component

  • Remove sensitive database URL from version control


Diagram Walkthrough

flowchart LR
  A["Chat Input"] -- "onFocus" --> B["Show Example Prompts"]
  A -- "onBlur" --> C["Hide Example Prompts"]
  D["Clean Initial UI"] --> A
Loading

File Walkthrough

Relevant files
Configuration changes
.env
Remove database credentials from repository                           

.env

  • Remove sensitive database URL from version control
+0/-1     
Enhancement
chat-panel.tsx
Add focus/blur handlers to chat input                                       

components/chat-panel.tsx

  • Add optional onFocus and onBlur props to interface
  • Pass focus/blur handlers to textarea input element
  • Enable dynamic visibility control for example prompts
+11/-1   
chat.tsx
Implement focus-based example prompt visibility                   

components/chat.tsx

  • Add isInputFocused state to track input focus
  • Implement submitExampleMessage function for direct submission
  • Replace showEmptyScreen logic with focus-based visibility
  • Show EmptyScreen only when input is focused and no messages exist
+42/-25 

Summary by CodeRabbit

  • New Features

    • Chat input now exposes focus/blur handling for improved context-aware behavior.
    • Empty state updates dynamically when the input is focused to offer contextual guidance.
    • User messages display instantly in the conversation while the assistant response is generated.
  • Chores

    • Added ignores for log files and a developer scratchpad directory.
    • Removed a bundled database connection value from the sample environment configuration.

This commit implements a change to the chat interface where the example prompts are now hidden by default. They are revealed only when the user clicks on or focuses the chat input field.

This change improves the initial user experience by providing a cleaner, less cluttered interface upon first load.

Additionally, this commit also introduces a `.gitignore` file to prevent sensitive environment files and development logs from being accidentally committed to the repository. This is a crucial step for improving the project's security and maintainability.
@vercel
Copy link
Contributor

vercel bot commented Sep 30, 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 30, 2025 5:39pm

@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 30, 2025

Walkthrough

Removed a DATABASE_URL entry from .env and added new ignore patterns to .gitignore. Extended ChatPanel with optional onFocus/onBlur props. Updated Chat to manage messages locally, use a new useActions.submit flow, append user/bot messages, and render EmptyScreen based on input focus.

Changes

Cohort / File(s) Summary
Env & VCS rules
/.env, /.gitignore
Removed DATABASE_URL="postgresql://user:password@host:port/db" from .env. Added ignore patterns *.log, dev.log, and jules-scratch/ to .gitignore.
Chat panel props passthrough
/components/chat-panel.tsx
Added optional onFocus?: () => void and onBlur?: () => void to ChatPanelProps; forwarded these handlers to the Textarea and updated component signature.
Chat state & flow updates
/components/chat.tsx
Switched to [messages, setMessages] from useUIState; added useActions.submit integration; appends UserMessage (uses nanoid) before awaiting bot response; implemented submitExampleMessage to append user then bot response; tracks isInputFocused via onFocus/onBlur and conditionally renders EmptyScreen vs ChatMessages.
AI UI API
/ai/rsc (module)
useUIState() signature changed to return [messages, setMessages]. New useActions() API introduced (exposes submit).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ChatPanel
  participant Chat
  participant Actions as useActions.submit

  rect rgb(245,245,255)
  note over User,Chat: Focus-driven empty state (new)
  User->>ChatPanel: focus input (onFocus)
  ChatPanel-->>Chat: onFocus()
  Chat->>Chat: isInputFocused = true
  Chat->>Chat: if messages.empty ⇒ show EmptyScreen
  end

  rect rgb(245,255,245)
  note over User,Actions: Submit flow (append user, await bot)
  User->>Chat: submitExampleMessage(text)
  Chat->>Chat: append UserMessage(id=nanoid)
  Chat->>Actions: submit(text)
  Actions-->>Chat: botResponse
  Chat->>Chat: append botResponse
  Chat-->>User: render ChatMessages
  end

  rect rgb(255,245,245)
  note over User,ChatPanel: Blur handling
  User->>ChatPanel: blur input (onBlur)
  ChatPanel-->>Chat: onBlur()
  Chat->>Chat: isInputFocused = false
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I twitch my whiskers, focus bright,
A user types — I stir the night.
I drop a note, the bot replies,
Logs hidden, URLs say goodbye.
Hop hop, the chat grows carrot-high! 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely summarizes the primary user-facing change introduced by the pull request, which is hiding example prompts until the chat input gains focus, and it aligns directly with the modifications in both components/chat-panel.tsx and components/chat.tsx without extraneous detail or jargon.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/prompt-visibility

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2563579 and 04dac09.

📒 Files selected for processing (1)
  • components/chat.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/chat.tsx (5)
app/actions.tsx (1)
  • UIState (341-346)
components/user-message.tsx (1)
  • UserMessage (10-22)
components/chat-panel.tsx (1)
  • ChatPanel (22-187)
components/chat-messages.tsx (1)
  • ChatMessages (11-70)
components/empty-screen.tsx (1)
  • EmptyScreen (27-59)
🔇 Additional comments (4)
components/chat.tsx (4)

9-12: LGTM!

The new imports are appropriate for the feature implementation and support better type safety.


26-32: LGTM!

State management correctly supports the focus-based prompt visibility feature.


109-113: Conditional rendering logic is correct.

The ternary chain properly implements the focus-based prompt visibility feature. However, the effectiveness depends on resolving the blur timing issue flagged in lines 104-106.


126-137: Desktop layout mirrors mobile implementation.

The desktop implementation correctly mirrors the mobile layout logic for focus-based prompt visibility. However, it inherits the same blur timing issue flagged for the mobile layout (line 131 needs the same deferred update fix as line 106).


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
🔒 Security concerns

Sensitive information exposure:
Ensure deletion is complemented by .gitignore
The PR deletes the .env file and mentions adding .gitignore, but the diff does not show the .gitignore contents. Verify that environment files (e.g., .env, .env.local) and logs are ignored and that no secrets remain in git history.

⚡ Recommended focus areas for review

Focus Handling

Relying on input focus/blur alone may cause flicker or unintended hiding of the EmptyScreen on mobile or when clicking example prompts; consider managing focus transitions or using a debounced visibility flag.

  <ChatPanel
    messages={messages}
    input={input}
    setInput={setInput}
    onFocus={() => setIsInputFocused(true)}
    onBlur={() => setIsInputFocused(false)}
  />
</div>
<div className="mobile-chat-messages-area">
  {messages.length > 0 ? (
    <ChatMessages messages={messages} />
  ) : isInputFocused ? (
    <EmptyScreen submitMessage={submitExampleMessage} />
  ) : null}
Message Submit

The new submitExampleMessage appends response as any; validate the response shape and ensure error handling/loading state to avoid UI inconsistencies if submit fails or is slow.

const submitExampleMessage = async (message: string) => {
  setMessages(currentMessages => [
    ...currentMessages,
    {
      id: nanoid(),
      component: <UserMessage message={message} />
    }
  ])

  const responseMessage = await submit(message)

  setMessages(currentMessages => [...currentMessages, responseMessage as any])
}
Optional Handlers

Passing optional onFocus/onBlur directly is fine, but confirm these events are appropriate on a textarea across platforms; consider onFocusCapture/onBlurCapture for consistent behavior if nested focusable elements are introduced.

onFocus={onFocus}
onBlur={onBlur}

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Sep 30, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
The implementation makes example prompts unclickable

The current onBlur implementation makes the example prompts unclickable because
they disappear when the input loses focus. A better focus management strategy,
like checking event.relatedTarget, is needed to keep the prompts visible when
the user tries to click them.

Examples:

components/chat.tsx [129-136]
            onFocus={() => setIsInputFocused(true)}
            onBlur={() => setIsInputFocused(false)}
          />
          {messages.length > 0 ? (
            <ChatMessages messages={messages} />
          ) : isInputFocused ? (
            <EmptyScreen submitMessage={submitExampleMessage} />
          ) : null}

Solution Walkthrough:

Before:

// In Chat.tsx
const [isInputFocused, setIsInputFocused] = useState(false);

return (
  ...
  <ChatPanel
    ...
    onFocus={() => setIsInputFocused(true)}
    onBlur={() => setIsInputFocused(false)}
  />
  {messages.length === 0 && isInputFocused ? (
    <EmptyScreen ... />
  ) : null}
  ...
)

After:

// In Chat.tsx
const [isInputFocused, setIsInputFocused] = useState(false);

const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {
  // If the new focused element is outside the component, then hide.
  if (!e.currentTarget.contains(e.relatedTarget)) {
    setIsInputFocused(false);
  }
};

// Wrap input and prompts in a div with the blur handler
return (
  <div onBlur={handleBlur}>
    <ChatPanel
      ...
      onFocus={() => setIsInputFocused(true)}
    />
    {messages.length === 0 && isInputFocused ? (
      <EmptyScreen ... />
    ) : null}
  </div>
)
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical usability bug where the onBlur event makes the example prompts disappear before they can be clicked, rendering the feature non-functional.

High
Possible issue
Prevent race conditions during submission

Add a isSubmitting state to the submitExampleMessage function to prevent race
conditions from multiple rapid clicks on example prompts.

components/chat.tsx [33-45]

+const [isSubmitting, setIsSubmitting] = useState(false)
+
 const submitExampleMessage = async (message: string) => {
+  if (isSubmitting) return
+  setIsSubmitting(true)
+
   setMessages(currentMessages => [
     ...currentMessages,
     {
       id: nanoid(),
       component: <UserMessage message={message} />
     }
   ])
 
-  const responseMessage = await submit(message)
-
-  setMessages(currentMessages => [...currentMessages, responseMessage as any])
+  try {
+    const responseMessage = await submit(message)
+    setMessages(currentMessages => [...currentMessages, responseMessage as any])
+  } finally {
+    setIsSubmitting(false)
+  }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential race condition in the new submitExampleMessage function and provides a standard, robust solution using a loading state to prevent out-of-order message rendering.

Medium
  • Update

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

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

  • Example prompts can’t be reliably clicked because onBlur hides them before the click fires; defer the blur update to avoid flicker and missed interactions.
  • dev.log is committed while logs are ignored; remove it from version control.
  • as any usage weakens type safety; prefer a concrete type (e.g., UIState[number]).
  • Minor: .gitignore includes both *.log and dev.log; the latter is redundant.
Additional notes (3)
  • Maintainability | .gitignore:27-28
    Adding both *.log and dev.log is redundant. The *.log pattern already ignores dev.log. Keeping both can confuse future maintainers about which rule is authoritative.

  • Style | .gitignore:27-35
    Minor nit: *.log already covers dev.log, and .env*.local already covers the specific .env.development.local, .env.test.local, and .env.production.local entries. Redundant patterns make the file noisier without adding protection.

  • Maintainability | components/chat.tsx:33-45
    submitExampleMessage reimplements message submission logic that likely overlaps with the form submission flow in ChatPanel. Divergent code paths for sending a message can lead to inconsistencies (e.g., missed validations, different metadata, future features not applied to both).

Summary of changes
  • Deleted committed .env file and expanded .gitignore to cover environment files, logs (including *.log), and a jules-scratch/ directory.
  • Added onFocus and onBlur optional props to ChatPanel and wired them to the <Textarea> to detect input focus.
  • Updated Chat to:
    • Track isInputFocused state and show example prompts (<EmptyScreen>) only when the input is focused and there are no messages.
    • Add submitExampleMessage to submit example prompts directly via useActions().submit, appending both the user message and the response to UI state.
  • Introduced a dev.log file (while also adding ignore rules for logs).

Comment on lines +1 to +6
▲ Next.js 15.3.3 (Turbopack)
- Local: http://localhost:3001
- Network: http://192.168.0.2:3001
- Environments: .env

✓ Starting...

Choose a reason for hiding this comment

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

The dev.log file is being committed even though logs are now ignored. This defeats the goal of preventing logs from entering the repo and can cause noisy diffs or leak environment details. It should be untracked and removed from the repository.

Suggestion

Remove dev.log from version control and rely on .gitignore to prevent future commits. You can do:

  • git rm --cached dev.log (or delete the file in the PR)
  • Commit the change

Reply with "@CharlieHelps yes please" if you'd like me to add a commit removing dev.log from the repo.

Comment on lines 44 to 45
setMessages(currentMessages => [...currentMessages, responseMessage as any])
}

Choose a reason for hiding this comment

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

Using as any here undermines type safety and makes it easier for regressions to slip in. Since you’re already updating UIState, prefer a concrete type over any.

Suggestion

Avoid as any by aligning the type of responseMessage with your UI state. For example:

  • Import the appropriate UI message type (e.g., UIState or UIMessage) and use it explicitly.

Example:

import type { UIState } from 'ai/rsc'
...
const responseMessage = (await submit(message)) as UIState[number]
setMessages(curr => [...curr, responseMessage])

If the action already returns the correct typed UI component, you may be able to drop the assertion entirely.

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

Comment on lines +103 to +105
onFocus={() => setIsInputFocused(true)}
onBlur={() => setIsInputFocused(false)}
/>

Choose a reason for hiding this comment

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

Hiding example prompts on input blur makes them disappear before the user can click an example. In the browser event order, the input blur fires before the example’s click, so your onBlur immediately hides <EmptyScreen>, causing a frustrating flicker and missed clicks.

Suggestion

Delay the blur state update so clicks on the examples still register. Replace the onBlur with a deferred update:

onBlur={() => setTimeout(() => setIsInputFocused(false), 0)}

Apply this change to both mobile and desktop ChatPanel instances.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit updating both places.

@charliecreates charliecreates bot removed the request for review from CharlieHelps September 30, 2025 17:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f027d5b and 2563579.

⛔ Files ignored due to path filters (1)
  • dev.log is excluded by !**/*.log
📒 Files selected for processing (4)
  • .env (0 hunks)
  • .gitignore (1 hunks)
  • components/chat-panel.tsx (2 hunks)
  • components/chat.tsx (4 hunks)
💤 Files with no reviewable changes (1)
  • .env
🧰 Additional context used
🧬 Code graph analysis (1)
components/chat.tsx (4)
components/user-message.tsx (1)
  • UserMessage (10-22)
components/chat-panel.tsx (1)
  • ChatPanel (22-187)
components/chat-messages.tsx (1)
  • ChatMessages (11-70)
components/empty-screen.tsx (1)
  • EmptyScreen (27-59)

Comment on lines +129 to +130
onFocus={onFocus}
onBlur={onBlur}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Auto-focus defeats the “show on focus” requirement

Forwarding onFocus/onBlur is good, but because the textarea still auto-focuses on mount (Line 66), the parent handler flips isInputFocused to true immediately. The EmptyScreen—and its example prompts—becomes visible before the user interacts, which contradicts the stated goal of hiding prompts until the chat input is manually focused. Please drop or gate the auto-focus so the new visibility logic only triggers after an intentional focus event.

-  useEffect(() => {
-    inputRef.current?.focus(); 
-  }, [])
📝 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
onFocus={onFocus}
onBlur={onBlur}
// … earlier in ChatPanel component …
const ChatPanel = () => {
const inputRef = useRef<HTMLTextAreaElement>(null);
// — Removed the auto-focus effect so that onFocus/onBlur
// are only triggered by user interaction, not on mount.
// useEffect(() => {
// inputRef.current?.focus();
// }, []);
// … other hooks and logic …
return (
<textarea
ref={inputRef}
onFocus={onFocus}
onBlur={onBlur}
// … other props …
/>
);
};
// … rest of file …
🤖 Prompt for AI Agents
In components/chat-panel.tsx around lines 66 and 129-130, the textarea still
auto-focuses on mount (line 66) which immediately fires the forwarded onFocus
and makes the parent treat the input as user-focused; remove or gate that
autoFocus so focus is only set after an intentional user action. Concretely,
stop passing autoFocus unconditionally (either remove the prop or wrap it behind
an explicit prop/state like allowAutoFocus or only set autoFocus after a
confirmed user interaction) so the forwarded onFocus/onBlur reflect actual
manual focus and the EmptyScreen visibility remains hidden until the user
intentionally focuses the input.

Comment on lines 33 to 45
const submitExampleMessage = async (message: string) => {
setMessages(currentMessages => [
...currentMessages,
{
id: nanoid(),
component: <UserMessage message={message} />
}
])

const responseMessage = await submit(message)

setMessages(currentMessages => [...currentMessages, responseMessage as any])
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

submitExampleMessage sends the wrong payload to submit

handleSubmit still builds a FormData payload before calling submit, which matches the server action’s contract. Here we now pass a plain string (submit(message)), so clicking an example prompt will either throw (type mismatch) or reach the server without the expected input field. Align the example flow with the form submission by packaging the message into FormData.

   const submitExampleMessage = async (message: string) => {
     setMessages(currentMessages => [
       ...currentMessages,
       {
         id: nanoid(),
         component: <UserMessage message={message} />
       }
     ])

-    const responseMessage = await submit(message)
+    const formData = new FormData()
+    formData.append('input', message)
+    const responseMessage = await submit(formData)
 
     setMessages(currentMessages => [...currentMessages, responseMessage as any])
   }
📝 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 submitExampleMessage = async (message: string) => {
setMessages(currentMessages => [
...currentMessages,
{
id: nanoid(),
component: <UserMessage message={message} />
}
])
const responseMessage = await submit(message)
setMessages(currentMessages => [...currentMessages, responseMessage as any])
}
const submitExampleMessage = async (message: string) => {
setMessages(currentMessages => [
...currentMessages,
{
id: nanoid(),
component: <UserMessage message={message} />
}
])
const formData = new FormData()
formData.append('input', message)
const responseMessage = await submit(formData)
setMessages(currentMessages => [...currentMessages, responseMessage as any])
}
🤖 Prompt for AI Agents
components/chat.tsx around lines 33 to 45: submitExampleMessage passes the raw
string to submit but the server action expects a FormData payload like
handleSubmit does; wrap the message in a FormData instance (e.g., const form =
new FormData(); form.append('input', message);) and call submit(form) instead of
submit(message), and ensure any type annotations for submit are satisfied (cast
to the expected type if needed).

This commit implements a change to the chat interface where the example prompts are now hidden by default. They are revealed only when the user clicks on or focuses the chat input field.

This change improves the initial user experience by providing a cleaner, less cluttered interface upon first load.

Additionally, this commit also introduces a `.gitignore` file to prevent sensitive environment files and development logs from being accidentally committed to the repository. This is a crucial step for improving the project's security and maintainability.
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