Skip to content

📝 CodeRabbit Chat: Implement requested code changes#599

Open
coderabbitai[bot] wants to merge 1 commit into
fix/followup-panel-streaming-5274824833186492759from
coderabbitai/chat/7ab4e93
Open

📝 CodeRabbit Chat: Implement requested code changes#599
coderabbitai[bot] wants to merge 1 commit into
fix/followup-panel-streaming-5274824833186492759from
coderabbitai/chat/7ab4e93

Conversation

@coderabbitai
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot commented May 7, 2026

Code changes was requested by @ngoiyaeric.

The following files were modified:

  • app/actions.tsx

@coderabbitai coderabbitai Bot requested a review from ngoiyaeric May 7, 2026 14:12
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 7, 2026

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

Project Deployment Actions Updated (UTC)
qcx Error Error May 7, 2026 2:12pm

@coderabbitai
Copy link
Copy Markdown
Contributor Author

coderabbitai Bot commented May 7, 2026

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f06feb8f-85aa-49ca-a21d-7f5e10cadc6e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@CLAassistant
Copy link
Copy Markdown

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.

@ngoiyaeric
Copy link
Copy Markdown
Collaborator

@coderabbitai the reads is loading open instead of animating consecutively after the page loads to preview all the text as the user scrolls, fix this bug

@coderabbitai
Copy link
Copy Markdown
Contributor Author

coderabbitai Bot commented May 9, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me look at the relevant files to understand the current implementation.


🏁 Script executed:

#!/bin/bash
# Find the reads-related components
fd -e tsx -e ts -e jsx -e js | xargs grep -l -i "reads\|accordion\|collapse\|expand" 2>/dev/null | head -30

echo "---"
# Look at app/actions.tsx
cat app/actions.tsx 2>/dev/null || echo "File not found"

Length of output: 25352


Let me look at the collapsible message and chat message components.


🏁 Script executed:

#!/bin/bash
cat components/collapsible-message.tsx
echo "==="
cat components/chat-messages.tsx
echo "==="
cat components/section.tsx 2>/dev/null || echo "section not found"

Length of output: 5945


@ngoiyaeric, I found the root causes. There are three bugs working together:

  1. AnimatePresence initial={false} — This explicitly suppresses the entry animation on first render, so collapsible sections that are open on mount never animate in.
  2. open initialised to isLastMessage — The last message starts as open = true immediately on page load, with no animation.
  3. Non-collapsed messages have zero animation — The if (!isCollapsed) return message.component path renders content instantly with no scroll-triggered entrance at all.

Here is the fix across both files:


components/collapsible-message.tsx — add index prop, start open as false, use whileInView for scroll-triggered consecutive animation, and remove initial={false}:

'use client'

import React, { useEffect, useRef, useState } from 'react'
import {
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent
} from '@radix-ui/react-collapsible'
import { Button } from './ui/button'
import { ChevronDown } from 'lucide-react'
import { StreamableValue, useStreamableValue } from 'ai/rsc'
import { motion, AnimatePresence, useInView } from 'framer-motion'
import { cn } from '@/lib/utils'
import { Separator } from './ui/separator'

interface CollapsibleMessageProps {
  message: {
    id: string
    isCollapsed?: StreamableValue<boolean>
    component: React.ReactNode
  }
  isLastMessage?: boolean
  index?: number
}

export const CollapsibleMessage: React.FC<CollapsibleMessageProps> = ({
  message,
  isLastMessage = false,
  index = 0
}) => {
  const [data] = useStreamableValue(message.isCollapsed)
  const isCollapsed = data ?? false

  // Always start closed so the staggered open animation can play
  const [open, setOpen] = useState(false)

  // Ref used to detect when this message scrolls into view
  const ref = useRef<HTMLDivElement>(null)
  const isInView = useInView(ref, { once: true, margin: '-60px' })

  useEffect(() => {
    if (isInView) {
      // Stagger: each message waits a little longer based on its position
      const timer = setTimeout(
        () => setOpen(true),
        index * 120          // 0 ms, 120 ms, 240 ms …
      )
      return () => clearTimeout(timer)
    }
  }, [isInView, index])

  // When the user manually toggles, respect that immediately
  const handleOpenChange = (value: boolean) => setOpen(value)

  // Non-collapsed messages (BotMessage, Section wrappers, etc.)
  // Animate in with a fade + slight upward slide as they enter the viewport
  if (!isCollapsed) {
    return (
      <motion.div
        ref={ref}
        initial={{ opacity: 0, y: 16 }}
        animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 16 }}
        transition={{ duration: 0.45, ease: 'easeOut', delay: index * 0.08 }}
      >
        {message.component}
      </motion.div>
    )
  }

  return (
    <div ref={ref}>
      <Collapsible open={open} onOpenChange={handleOpenChange}>
        <CollapsibleTrigger asChild>
          <div className="w-full flex justify-end">
            <Button
              variant="ghost"
              size={'icon'}
              className={cn('-mt-3 rounded-full')}
            >
              <ChevronDown
                size={14}
                className={cn(
                  open ? 'rotate-180' : 'rotate-0',
                  'h-4 w-4 transition-all'
                )}
              />
              <span className="sr-only">collapse</span>
            </Button>
          </div>
        </CollapsibleTrigger>

        {/* Remove initial={false} so the animation fires on first mount */}
        <AnimatePresence>
          {open && (
            <CollapsibleContent asChild forceMount>
              <motion.div
                initial={{ opacity: 0, height: 0 }}
                animate={{ opacity: 1, height: 'auto' }}
                exit={{ opacity: 0, height: 0 }}
                transition={{ duration: 0.35, ease: 'easeInOut' }}
                className="overflow-hidden"
              >
                {message.component}
              </motion.div>
            </CollapsibleContent>
          )}
        </AnimatePresence>

        {!open && <Separator className="my-2 bg-muted" />}
      </Collapsible>
    </div>
  )
}

components/chat-messages.tsx — pass index so each message gets its own stagger delay:

return (
  <>
    {groupedMessagesArray.map(
      (
        groupedMessage: {
          id: string
          components: React.ReactNode[]
          isCollapsed?: StreamableValue<boolean>
        },
        index          // ← already available, just pass it down
      ) => (
        <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
          }
          index={index}   // ← add this
        />
      )
    )}
  </>
)

What this achieves:

Before After
All sections render open immediately on page load All sections start hidden (opacity: 0, y: 16)
AnimatePresence initial={false} kills mount animation Entry animation fires when each section scrolls into the viewport
No stagger — everything pops in at once Each section waits index × 120 ms, creating a consecutive wave as you scroll
Non-collapsed messages had zero animation Non-collapsed messages now fade+slide in via whileInView
  • Apply animation fix to components/collapsible-message.tsx — add index prop, useInView hook, viewport-triggered staggered open, remove AnimatePresence initial={false}, wrap non-collapsed path in animated motion.div
  • Apply index prop pass-through in components/chat-messages.tsx

The requested changes have been implemented and a pull request has been created: View PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants