Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Aug 29, 2025

User description

This feature allows users to hide chat messages by swiping them to the left.

Key changes:

  • Added react-swipeable to handle swipe gestures.
  • Created a SwipeableMessage component to wrap chat messages and detect swipes.
  • Implemented a hideMessage action to update the message state and set an isHidden flag.
  • Updated the UI to filter out hidden messages.

PR Type

Enhancement


Description

  • Added swipe-to-dismiss functionality for chat messages

  • Created SwipeableMessage component with left swipe detection

  • Implemented hideMessage server action to update message state

  • Added isHidden flag to AIMessage type definition


Diagram Walkthrough

flowchart LR
  A["User swipes left"] --> B["SwipeableMessage detects gesture"]
  B --> C["hideMessage action called"]
  C --> D["Message marked as hidden"]
  D --> E["UI filters hidden messages"]
Loading

File Walkthrough

Relevant files
Enhancement
index.ts
Add isHidden property to AIMessage type                                   

lib/types/index.ts

  • Added optional isHidden boolean property to AIMessage type
+1/-0     
actions.tsx
Implement hideMessage action and message filtering             

app/actions.tsx

  • Created hideMessage server action to mark messages as hidden
  • Added hideMessage to AI provider actions
  • Updated getUIStateFromAIState to filter out hidden messages
+21/-0   
chat-messages.tsx
Integrate SwipeableMessage wrapper for chat messages         

components/chat-messages.tsx

  • Imported SwipeableMessage component
  • Wrapped CollapsibleMessage with SwipeableMessage component
+19/-13 
swipeable-message.tsx
Create SwipeableMessage component with gesture detection 

components/swipeable-message.tsx

  • Created new SwipeableMessage component using react-swipeable
  • Implemented left swipe detection to trigger hideMessage action
  • Added console logging for swipe events
+30/-0   
Dependencies
package.json
Add react-swipeable dependency for gesture handling           

package.json

  • Added react-swipeable dependency version 7.0.2
+1/-0     

Summary by CodeRabbit

  • New Features

    • Swipe left on any chat message to hide it from the conversation.
    • Hidden messages no longer appear in the chat view.
    • Works on touch and mouse/trackpad swipes.
    • Existing message grouping and collapse behavior remain unchanged.
  • Chores

    • Added a new dependency to support swipe gestures.

This feature allows users to hide chat messages by swiping them to the left.

Key changes:
- Added `react-swipeable` to handle swipe gestures.
- Created a `SwipeableMessage` component to wrap chat messages and detect swipes.
- Implemented a `hideMessage` action to update the message state and set an `isHidden` flag.
- Updated the UI to filter out hidden messages.
@vercel
Copy link

vercel bot commented Aug 29, 2025

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

Project Deployment Preview Comments Updated (UTC)
qcx Ready Ready Preview Comment Aug 29, 2025 3:45am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 29, 2025

Walkthrough

Introduces swipe-to-hide functionality: adds a server action to mark messages as hidden, updates type definitions, filters hidden messages from UI state, wraps chat messages with a new SwipeableMessage component that triggers hide on left swipe, and adds react-swipeable dependency.

Changes

Cohort / File(s) Summary of Changes
Swipe-to-hide UI integration
components/chat-messages.tsx, components/swipeable-message.tsx
Wraps each grouped chat message in SwipeableMessage, which detects left swipes (via react-swipeable) and invokes hideMessage(message.id). Maintains existing CollapsibleMessage rendering and last-message logic. Adds new exported SwipeableMessage component.
Server-side hide action & type support
app/actions.tsx, lib/types/index.ts
Adds async function hideMessage(messageId: string) to set isHidden: true for a matching message in aiState.messages, exposes via AI.actions. Updates getUIStateFromAIState to exclude hidden messages. Extends AIMessage with optional isHidden?: boolean.
Dependency update
package.json
Adds react-swipeable@^7.0.2 dependency.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant SM as SwipeableMessage (client)
  participant RS as react-swipeable
  participant AA as AI Actions (client proxy)
  participant SA as hideMessage (server action)
  participant ST as AI State
  participant CM as ChatMessages renderer

  U->>SM: Swipe left on message
  SM->>RS: Handle gesture (trackMouse: true)
  RS-->>SM: onSwipedLeft callback
  SM->>AA: actions.hideMessage(message.id)
  AA->>SA: hideMessage(messageId)
  SA->>ST: Set message.isHidden = true
  SA-->>AA: Success
  AA-->>SM: Resolved
  CM->>ST: getUIStateFromAIState()
  ST-->>CM: messages filtered (exclude isHidden)
  CM-->>U: UI re-renders without hidden message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

Review effort 1/5

Poem

A bunny with paws that swipe left with glee,
Tucks chatty carrots where none else can see. 🥕
With a whisk and a whisker, the messages hide,
Quiet burrows in the UI’s tide.
Hop, swipe, hush—now the thread’s tidy-free!

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/swipe-to-dismiss

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

@qodo-merge-pro
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

State Consistency

The hideMessage action updates only the in-memory AI state; ensure hidden state persists across sessions/navigation if required and that concurrent updates don't overwrite isHidden changes.

async function hideMessage(messageId: string) {
  'use server'

  const aiState = getMutableAIState<typeof AI>()

  aiState.update({
    ...aiState.get(),
    messages: aiState.get().messages.map(msg => {
      if (msg.id === messageId) {
        return {
          ...msg,
          isHidden: true
        }
      }
      return msg
    })
  })
}
Console Log

Debug logging on every swipe may clutter the console in production; consider removing or gating behind a debug flag.

const handlers = useSwipeable({
  onSwipedLeft: () => {
    console.log(`Swiped left on message ${message.id}`)
    hideMessage(message.id)
  },
  trackMouse: true
})

return <div {...handlers}>{children}</div>
Keys/Nesting

Wrapping with SwipeableMessage adds a second key; verify stable keys and that the outer container doesn't break list virtualization or layout.

  <SwipeableMessage
    key={`${groupedMessage.id}-swipeable`}
    message={groupedMessage}
  >
    <CollapsibleMessage
      key={`${groupedMessage.id}`}
      message={{
        id: groupedMessage.id,
        component: groupedMessage.components.map((component, i) => (
          <div key={`${groupedMessage.id}-${i}`}>{component}</div>
        )),
        isCollapsed: groupedMessage.isCollapsed
      }}
      isLastMessage={
        groupedMessage.id === messages[messages.length - 1].id
      }
    />
  </SwipeableMessage>
)

@qodo-merge-pro
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Persist hidden state end-to-end

hideMessage only mutates the in-memory AI state, so hidden flags will be lost on
refresh and won’t apply to saved or shared chats. Persist the isHidden field
through your chat storage (save/load APIs and schema) and ensure all server-side
loaders/share pages filter hidden messages to prevent them from reappearing.
This keeps behavior consistent across sessions and sharing.

Examples:

app/actions.tsx [35-52]
async function hideMessage(messageId: string) {
  'use server'

  const aiState = getMutableAIState<typeof AI>()

  aiState.update({
    ...aiState.get(),
    messages: aiState.get().messages.map(msg => {
      if (msg.id === messageId) {
        return {

 ... (clipped 8 lines)

Solution Walkthrough:

Before:

// app/actions.tsx

async function hideMessage(messageId: string) {
  'use server'

  const aiState = getMutableAIState<typeof AI>()

  // Only updates the in-memory state
  aiState.update({
    ...aiState.get(),
    messages: aiState.get().messages.map(msg => {
      if (msg.id === messageId) {
        return { ...msg, isHidden: true }
      }
      return msg
    })
  })
  // No call to a persistence function like saveChat()
}

After:

// app/actions.tsx
import { saveChat } from '@/lib/actions/chat';

async function hideMessage(messageId: string) {
  'use server'

  const aiState = getMutableAIState<typeof AI>()
  const currentState = aiState.get();
  const newMessages = currentState.messages.map(msg => {
    if (msg.id === messageId) {
      return { ...msg, isHidden: true }
    }
    return msg
  });

  const newAIState = { ...currentState, messages: newMessages };
  aiState.update(newAIState);

  // Persist the updated state to the database
  await saveChat(newAIState);
}
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical functional gap: the isHidden state is not persisted, making the feature's effect temporary and lost on page refresh, which undermines its purpose.

High
General
Remove debug logging and add error handling

Remove the console.log statement as it's debug code that shouldn't be in
production. Also add error handling around the hideMessage call to gracefully
handle potential failures.

components/swipeable-message.tsx [21-27]

 const handlers = useSwipeable({
-  onSwipedLeft: () => {
-    console.log(`Swiped left on message ${message.id}`)
-    hideMessage(message.id)
+  onSwipedLeft: async () => {
+    try {
+      await hideMessage(message.id)
+    } catch (error) {
+      console.error('Failed to hide message:', error)
+    }
   },
   trackMouse: true
 })
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This is a valuable suggestion as it addresses two important points: removing debug console.log statements from production code and adding crucial try...catch error handling for an async server action, which prevents unhandled promise rejections on the client.

Medium
Add input validation and return status

The function should validate that the messageId parameter is not empty or null
before processing. Additionally, consider returning a success/error status to
provide feedback to the client about the operation result.

app/actions.tsx [35-52]

 async function hideMessage(messageId: string) {
   'use server'
+
+  if (!messageId || messageId.trim() === '') {
+    throw new Error('Message ID is required')
+  }
 
   const aiState = getMutableAIState<typeof AI>()
 
   aiState.update({
     ...aiState.get(),
     messages: aiState.get().messages.map(msg => {
       if (msg.id === messageId) {
         return {
           ...msg,
           isHidden: true
         }
       }
       return msg
     })
   })
+
+  return { success: true }
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that the hideMessage server action lacks input validation for messageId, and adding it improves the function's robustness against invalid inputs.

Medium
Improve swipe detection configuration

Add swipe configuration parameters like delta and preventScrollOnSwipe to
improve the swipe detection accuracy and prevent accidental triggers during
normal scrolling.

components/swipeable-message.tsx [21-27]

 const handlers = useSwipeable({
-  onSwipedLeft: () => {
-    console.log(`Swiped left on message ${message.id}`)
-    hideMessage(message.id)
+  onSwipedLeft: async () => {
+    try {
+      await hideMessage(message.id)
+    } catch (error) {
+      console.error('Failed to hide message:', error)
+    }
   },
-  trackMouse: true
+  trackMouse: true,
+  delta: 50,
+  preventScrollOnSwipe: true,
+  trackTouch: true
 })
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly proposes adding configuration options like delta and preventScrollOnSwipe to the useSwipeable hook, which can significantly improve the user experience by preventing accidental swipes and conflicts with scrolling.

Low
  • More

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: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

💡 Knowledge Base configuration:

  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a43a601 and a0c182a.

⛔ Files ignored due to path filters (1)
  • bun.lockb is excluded by !**/bun.lockb
📒 Files selected for processing (5)
  • app/actions.tsx (3 hunks)
  • components/chat-messages.tsx (2 hunks)
  • components/swipeable-message.tsx (1 hunks)
  • lib/types/index.ts (1 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
lib/types/index.ts (2)
lib/actions/chat.ts (1)
  • msg (119-127)
lib/actions/chat-db.ts (1)
  • msg (117-121)
components/swipeable-message.tsx (1)
components/collapsible-message.tsx (2)
  • data (24-75)
  • CollapsibleMessageProps (15-22)
components/chat-messages.tsx (2)
components/swipeable-message.tsx (1)
  • SwipeableMessage (15-30)
components/collapsible-message.tsx (2)
  • CollapsibleMessage (24-75)
  • CollapsibleMessageProps (15-22)
app/actions.tsx (2)
lib/actions/chat.ts (1)
  • msg (119-127)
lib/actions/chat-db.ts (1)
  • msg (117-121)
🔇 Additional comments (5)
package.json (1)

74-74: react-swipeable import scope verified
Confirmed usage only in components/swipeable-message.tsx which begins with 'use client'; no server-side imports detected.

lib/types/index.ts (1)

74-75: Persist isHidden in your message save/load mapping
You’ve added the flag in the type and toggle it in app/actions.tsx, but I couldn’t find it in your persistence layer (e.g. no isHidden in lib/actions/chat.ts), so hidden messages will reappear after reload. Extend your save/load logic to include isHidden or document it as a UI‐only field.

app/actions.tsx (2)

292-295: Wiring action into AI.actions — LGTM


367-368: Filtering hidden messages before UI mapping — LGTM

This ensures hidden groups are excluded consistently.

components/chat-messages.tsx (1)

6-6: Import of SwipeableMessage — LGTM

Comment on lines +35 to +52
async function hideMessage(messageId: string) {
'use server'

const aiState = getMutableAIState<typeof AI>()

aiState.update({
...aiState.get(),
messages: aiState.get().messages.map(msg => {
if (msg.id === messageId) {
return {
...msg,
isHidden: true
}
}
return msg
})
})
}
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

hideMessage should be idempotent and avoid no-op updates

Avoid updating state when nothing changes; also read aiState once.

 async function hideMessage(messageId: string) {
   'use server'

-  const aiState = getMutableAIState<typeof AI>()
-
-  aiState.update({
-    ...aiState.get(),
-    messages: aiState.get().messages.map(msg => {
-      if (msg.id === messageId) {
-        return {
-          ...msg,
-          isHidden: true
-        }
-      }
-      return msg
-    })
-  })
+  const aiState = getMutableAIState<typeof AI>()
+  const current = aiState.get()
+  let changed = false
+  const messages = current.messages.map(msg => {
+    if (msg.id === messageId && msg.isHidden !== true) {
+      changed = true
+      return { ...msg, isHidden: true }
+    }
+    return msg
+  })
+  if (!changed) return
+  aiState.update({ ...current, messages })
 }
📝 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 hideMessage(messageId: string) {
'use server'
const aiState = getMutableAIState<typeof AI>()
aiState.update({
...aiState.get(),
messages: aiState.get().messages.map(msg => {
if (msg.id === messageId) {
return {
...msg,
isHidden: true
}
}
return msg
})
})
}
async function hideMessage(messageId: string) {
'use server'
const aiState = getMutableAIState<typeof AI>()
const current = aiState.get()
let changed = false
const messages = current.messages.map(msg => {
if (msg.id === messageId && msg.isHidden !== true) {
changed = true
return { ...msg, isHidden: true }
}
return msg
})
if (!changed) return
aiState.update({ ...current, messages })
}
🤖 Prompt for AI Agents
In app/actions.tsx around lines 35 to 52, the hideMessage function repeatedly
reads aiState and always triggers an update even when nothing changes; refactor
to read aiState.get() once into a local variable, find the target message and if
it doesn't exist or already has isHidden true return early (no-op), otherwise
create a new messages array that only changes the matched message's isHidden to
true and call aiState.update once with the new state; this makes the operation
idempotent and avoids unnecessary state updates.

Comment on lines +54 to +71
<SwipeableMessage
key={`${groupedMessage.id}-swipeable`}
message={groupedMessage}
>
<CollapsibleMessage
key={`${groupedMessage.id}`}
message={{
id: groupedMessage.id,
component: groupedMessage.components.map((component, i) => (
<div key={`${groupedMessage.id}-${i}`}>{component}</div>
)),
isCollapsed: groupedMessage.isCollapsed
}}
isLastMessage={
groupedMessage.id === messages[messages.length - 1].id
}
/>
</SwipeableMessage>
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)

Wrap with SwipeableMessage — good; remove redundant key on single child

CollapsibleMessage isn’t part of a list here; the extra key is unnecessary.

           <SwipeableMessage
             key={`${groupedMessage.id}-swipeable`}
             message={groupedMessage}
           >
-            <CollapsibleMessage
-              key={`${groupedMessage.id}`}
+            <CollapsibleMessage
               message={{
                 id: groupedMessage.id,
                 component: groupedMessage.components.map((component, i) => (
                   <div key={`${groupedMessage.id}-${i}`}>{component}</div>
                 )),
                 isCollapsed: groupedMessage.isCollapsed
               }}
               isLastMessage={
                 groupedMessage.id === messages[messages.length - 1].id
               }
             />
           </SwipeableMessage>
📝 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
<SwipeableMessage
key={`${groupedMessage.id}-swipeable`}
message={groupedMessage}
>
<CollapsibleMessage
key={`${groupedMessage.id}`}
message={{
id: groupedMessage.id,
component: groupedMessage.components.map((component, i) => (
<div key={`${groupedMessage.id}-${i}`}>{component}</div>
)),
isCollapsed: groupedMessage.isCollapsed
}}
isLastMessage={
groupedMessage.id === messages[messages.length - 1].id
}
/>
</SwipeableMessage>
<SwipeableMessage
key={`${groupedMessage.id}-swipeable`}
message={groupedMessage}
>
<CollapsibleMessage
message={{
id: groupedMessage.id,
component: groupedMessage.components.map((component, i) => (
<div key={`${groupedMessage.id}-${i}`}>{component}</div>
)),
isCollapsed: groupedMessage.isCollapsed
}}
isLastMessage={
groupedMessage.id === messages[messages.length - 1].id
}
/>
</SwipeableMessage>
🤖 Prompt for AI Agents
In components/chat-messages.tsx around lines 54 to 71, remove the redundant key
prop from the CollapsibleMessage child since SwipeableMessage already has the
unique key; keep the key only on the SwipeableMessage wrapper, delete the key
attribute on the CollapsibleMessage element, and verify TypeScript/JSX props
remain unchanged so rendering and isLastMessage logic still work.

Comment on lines +1 to +30
'use client'

import { useSwipeable } from 'react-swipeable'
import { StreamableValue, useActions } from 'ai/rsc'

interface SwipeableMessageProps {
message: {
id: string
components: React.ReactNode[]
isCollapsed?: StreamableValue<boolean>
}
children: React.ReactNode
}

export function SwipeableMessage({
message,
children
}: SwipeableMessageProps) {
const { hideMessage } = useActions()

const handlers = useSwipeable({
onSwipedLeft: () => {
console.log(`Swiped left on message ${message.id}`)
hideMessage(message.id)
},
trackMouse: true
})

return <div {...handlers}>{children}</div>
}
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

Harden swipe UX and add keyboard/A11y fallback; drop console.log

Prevents accidental hides, supports keyboards/screen readers, and removes debug logging.

 'use client'

 import { useSwipeable } from 'react-swipeable'
 import { StreamableValue, useActions } from 'ai/rsc'

 interface SwipeableMessageProps {
   message: {
     id: string
     components: React.ReactNode[]
     isCollapsed?: StreamableValue<boolean>
   }
   children: React.ReactNode
 }

 export function SwipeableMessage({
   message,
   children
 }: SwipeableMessageProps) {
   const { hideMessage } = useActions()

   const handlers = useSwipeable({
-    onSwipedLeft: () => {
-      console.log(`Swiped left on message ${message.id}`)
-      hideMessage(message.id)
-    },
-    trackMouse: true
+    onSwipedLeft: () => {
+      void hideMessage(message.id)
+    },
+    trackMouse: true,
+    preventScrollOnSwipe: true,
+    delta: 50
   })

-  return <div {...handlers}>{children}</div>
+  return (
+    <div
+      {...handlers}
+      role="button"
+      tabIndex={0}
+      aria-label="Hide message"
+      onKeyDown={(e) => {
+        if (e.key === 'Backspace' || e.key === 'Delete') {
+          e.preventDefault()
+          void hideMessage(message.id)
+        }
+      }}
+    >
+      {children}
+    </div>
+  )
 }
📝 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
'use client'
import { useSwipeable } from 'react-swipeable'
import { StreamableValue, useActions } from 'ai/rsc'
interface SwipeableMessageProps {
message: {
id: string
components: React.ReactNode[]
isCollapsed?: StreamableValue<boolean>
}
children: React.ReactNode
}
export function SwipeableMessage({
message,
children
}: SwipeableMessageProps) {
const { hideMessage } = useActions()
const handlers = useSwipeable({
onSwipedLeft: () => {
console.log(`Swiped left on message ${message.id}`)
hideMessage(message.id)
},
trackMouse: true
})
return <div {...handlers}>{children}</div>
}
'use client'
import { useSwipeable } from 'react-swipeable'
import { StreamableValue, useActions } from 'ai/rsc'
interface SwipeableMessageProps {
message: {
id: string
components: React.ReactNode[]
isCollapsed?: StreamableValue<boolean>
}
children: React.ReactNode
}
export function SwipeableMessage({
message,
children
}: SwipeableMessageProps) {
const { hideMessage } = useActions()
const handlers = useSwipeable({
onSwipedLeft: () => {
void hideMessage(message.id)
},
trackMouse: true,
preventScrollOnSwipe: true,
delta: 50
})
return (
<div
{...handlers}
role="button"
tabIndex={0}
aria-label="Hide message"
onKeyDown={(e) => {
if (e.key === 'Backspace' || e.key === 'Delete') {
e.preventDefault()
void hideMessage(message.id)
}
}}
>
{children}
</div>
)
}

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