Skip to content

Comments

Migrate to AI elements for chat conversation#707

Merged
elie222 merged 4 commits intomainfrom
feat/ai-elements-chat
Aug 21, 2025
Merged

Migrate to AI elements for chat conversation#707
elie222 merged 4 commits intomainfrom
feat/ai-elements-chat

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Aug 21, 2025

Summary by CodeRabbit

  • New Features
    • Rich AI UI kit: message/avatar components, conversation layout with scroll-to-bottom button, loader, response rendering, and reasoning panel with streaming/duration.
    • Prompt input area: Enter-to-send (Shift+Enter newline), model selector, status-aware submit, and localStorage persistence.
    • Code blocks with light/dark themes and one-click copy.
    • Suggestions (horizontal pills) and action buttons with optional tooltips.
    • Tool panels showing parameters, results, and errors.
  • Refactor
    • Rebuilt message flow on new UI primitives; removed legacy motion/markdown/multimodal input and old icon components.
  • Chores
    • Added dependencies for collapsibles, sticky scrolling, syntax highlighting, and streaming.

@vercel
Copy link

vercel bot commented Aug 21, 2025

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

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Aug 21, 2025 5:46pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 21, 2025

Walkthrough

Adds a new ai-elements UI library (actions, code-block, conversation, loader, message, prompt-input, reasoning, response, suggestion, tool); refactors assistant-chat to use these primitives; replaces MultimodalInput with PromptInput and Conversation scroll handling; removes legacy chat components; and adds new package dependencies.

Changes

Cohort / File(s) Summary
AI Elements – New primitives
apps/web/components/ai-elements/actions.tsx, apps/web/components/ai-elements/code-block.tsx, apps/web/components/ai-elements/conversation.tsx, apps/web/components/ai-elements/loader.tsx, apps/web/components/ai-elements/message.tsx, apps/web/components/ai-elements/prompt-input.tsx, apps/web/components/ai-elements/reasoning.tsx, apps/web/components/ai-elements/response.tsx, apps/web/components/ai-elements/suggestion.tsx, apps/web/components/ai-elements/tool.tsx
Adds client-side UI components and associated prop types: Actions/Action, CodeBlock (+ copy button), Conversation (+ content/scroll button), Loader, Message/MessageContent/MessageAvatar, PromptInput suite (textarea, toolbar, buttons, model select), Reasoning collapsible, Response (Streamdown wrapper), Suggestions/Suggestion, and Tool components.
Assistant Chat – Input & rendering refactor
apps/web/components/assistant-chat/chat.tsx, apps/web/components/assistant-chat/messages.tsx, apps/web/components/assistant-chat/message.tsx, apps/web/components/assistant-chat/tools.tsx, apps/web/components/assistant-chat/overview.tsx
Replaces MultimodalInput with PromptInput (adds localStorage-backed input), migrates scrolling to Conversation primitives, rewrites message rendering to use new ai-elements (Reasoning/Response/Message*), consolidates tool card UI, and swaps message icon to lucide's MessageCircleIcon.
Assistant Chat – Removed legacy modules
apps/web/components/assistant-chat/icons.tsx, apps/web/components/assistant-chat/markdown.tsx, apps/web/components/assistant-chat/message-reasoning.tsx, apps/web/components/assistant-chat/multimodal-input.tsx, apps/web/components/assistant-chat/submit-button.tsx, apps/web/components/assistant-chat/use-scroll-to-bottom.ts
Deletes multiple legacy components/hooks: custom icons, markdown renderer, message-reasoning UI, multimodal input, submit button, and custom useScrollToBottom hook.
Layout – Sidebar / Content wrapper changes
apps/web/components/SideNavWithTopNav.tsx, apps/web/components/SidebarRight.tsx
Introduces ContentWrapper to centralize main layout, adapts SidebarRight to a fixed right panel with name-based open state, and updates className/prop signatures for SidebarRight.
Dependencies
apps/web/package.json
Adds dependencies: @radix-ui/react-use-controllable-state, embla-carousel-react, react-syntax-highlighter, streamdown, use-stick-to-bottom and devDependency @types/react-syntax-highlighter.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Prompt as PromptInput
  participant Chat as Chat component
  participant Conv as Conversation
  participant Msg as Message renderer
  participant Reason as Reasoning
  participant Resp as Response (Streamdown)

  User->>Prompt: Type message
  User->>Prompt: Press Enter (no Shift)
  Prompt->>Chat: form.requestSubmit()
  Chat->>Chat: handleSubmit(input) / persist localStorage
  Chat->>Conv: Append user message
  Chat->>Msg: Create assistant placeholder (status=submitted)
  Msg->>Reason: Show thinking / isStreaming
  Chat-->>Resp: Stream model output
  Resp-->>Msg: Render streamed text
  Reason-->>Msg: Auto-close when streaming ends
  Chat->>Conv: Final assistant message rendered
Loading
sequenceDiagram
  autonumber
  actor User
  participant Code as CodeBlockCopyButton
  participant Context as CodeBlockContext
  participant Clipboard as navigator.clipboard

  User->>Code: Click copy
  Code->>Context: read code
  alt Clipboard available
    Code->>Clipboard: writeText(code)
    Clipboard-->>Code: resolve
    Code-->>User: show copied (CheckIcon) → reset after timeout
  else Clipboard missing/fails
    Code-->>User: call onError
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Select persona examples in chat #451 — Modifies assistant-chat modules including multimodal-input and icons (strong overlap with deleted/updated files).
  • Assistant chat #431 — Touches many assistant-chat pieces (message, message-reasoning, multimodal-input, markdown); likely overlaps with large refactor here.
  • New chat #444 — Adjusts assistant-chat/chat.tsx input/state handling; closely related to the PromptInput replacement and localStorage changes.

Poem

I nibble keys in a sunny spot,
New widgets knitted, tidy and hot.
Thoughts fold and unfold with a twitch,
Code copies quick—no hiccup or glitch.
Hop, scroll, send—our chat grew spry, hooray! 🐇✨

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/ai-elements-chat

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 @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai 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:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai 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 @coderabbitai help to get the list of available commands.

Other keywords and placeholders

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

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.

@claude
Copy link

claude bot commented Aug 21, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@socket-security
Copy link

socket-security bot commented Aug 21, 2025

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

Caution

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

⚠️ Outside diff range comments (6)
apps/web/components/assistant-chat/tools.tsx (5)

1-1: Mark as a client module.

This file uses useState and onClick handlers; add the "use client" directive to prevent accidental server import issues and align with project guidance for interactive components.

+ "use client";
  import { useState } from "react";

90-97: Don't use array index as React key for actions.

Using the index breaks reconciliation on edits/reorders (explicitly disallowed in coding guidelines). Use a stable key derived from action content.

-          {args.actions.map((action, i) => (
-            <div key={i} className="rounded-md bg-muted p-2 text-sm">
+          {args.actions.map((action) => (
+            <div
+              key={`${action.type}-${action.fields?.label ?? ""}-${action.fields?.subject ?? ""}-${action.fields?.to ?? ""}-${action.fields?.cc ?? ""}-${action.fields?.bcc ?? ""}`}
+              className="rounded-md bg-muted p-2 text-sm"
+            >

258-271: Don't use array index as React key for updated actions.

Same key issue here; derive a stable key from the action payload.

-        {args.actions.map((actionItem, i) => {
+        {args.actions.map((actionItem) => {
           if (!actionItem) return null;

           return (
-            <div key={i} className="rounded-md bg-muted p-2 text-sm">
+            <div
+              key={`${actionItem.type}-${actionItem.fields?.label ?? ""}-${actionItem.fields?.subject ?? ""}-${actionItem.fields?.to ?? ""}-${actionItem.fields?.cc ?? ""}-${actionItem.fields?.bcc ?? ""}`}
+              className="rounded-md bg-muted p-2 text-sm"
+            >

300-335: Don't use array index as React key for learned patterns.

Use a deterministic key from include/exclude fields to avoid UI glitches.

-        {args.learnedPatterns.map((pattern, i) => {
+        {args.learnedPatterns.map((pattern) => {
           if (!pattern) return null;

           return (
-            <div key={i} className="rounded-md bg-muted p-2 text-sm">
+            <div
+              key={`${pattern.include?.from ?? ""}-${pattern.include?.subject ?? ""}-${pattern.exclude?.from ?? ""}-${pattern.exclude?.subject ?? ""}`}
+              className="rounded-md bg-muted p-2 text-sm"
+            >

456-466: Replace Tooltip in DiffToggleButton to Prevent Scroll-to-Bottom
The DiffToggleButton still wraps its icon in a Tooltip, which conflicts with the explicit comment in RuleActions (“Don’t use tooltips as they force scroll to bottom”). Instead of mounting a tooltip portal (which can trigger the chat pane to auto-scroll), give the button a native label via aria-label (for screen readers) and a title (for a simple hover hint).

• File: apps/web/components/assistant-chat/tools.tsx
• Component: DiffToggleButton (around lines 456–466)

Suggested diff:

-  return (
-    <Tooltip content={showChanges ? "Hide Changes" : "Show Changes"}>
-      <Button
-        variant="ghost"
-        size="sm"
-        className="h-8 w-8 p-0"
-        onClick={onToggle}
-      >
-        <FileDiffIcon className="size-4" />
-      </Button>
-    </Tooltip>
-  );
+  const label = showChanges ? "Hide Changes" : "Show Changes";
+  return (
+    <Button
+      variant="ghost"
+      size="sm"
+      className="h-8 w-8 p-0"
+      onClick={onToggle}
+      aria-label={label}
+      title={label}
+    >
+      <FileDiffIcon className="size-4" />
+    </Button>
+  );
apps/web/components/assistant-chat/messages.tsx (1)

64-73: Fix memo comparator; current logic forces re-renders and can also skip updates incorrectly.

  • if (prevProps.status && nextProps.status) return false; always returns false when both statuses are truthy, defeating memoization.
  • if (prevProps.isArtifactVisible && nextProps.isArtifactVisible) return true; can mask changes in messages/status.
-export const Messages = memo(PureMessages, (prevProps, nextProps) => {
-  if (prevProps.isArtifactVisible && nextProps.isArtifactVisible) return true;
-
-  if (prevProps.status !== nextProps.status) return false;
-  if (prevProps.status && nextProps.status) return false;
-  if (prevProps.messages.length !== nextProps.messages.length) return false;
-  if (!equal(prevProps.messages, nextProps.messages)) return false;
-
-  return true;
-});
+export const Messages = memo(PureMessages, (prevProps, nextProps) => {
+  if (prevProps.isArtifactVisible !== nextProps.isArtifactVisible) return false;
+  if (prevProps.status !== nextProps.status) return false;
+  if (prevProps.messages.length !== nextProps.messages.length) return false;
+  if (!equal(prevProps.messages, nextProps.messages)) return false;
+  return true;
+});
🧹 Nitpick comments (26)
apps/web/components/assistant-chat/overview.tsx (1)

15-15: Align icon usage with tailwind/shadcn patterns and mark decorative icon as hidden from SR.

For consistency with the codebase’s Tailwind usage and shadcn patterns, prefer sizing via className and add aria-hidden since this icon is purely decorative here.

-          <MessageCircleIcon size={32} />
+          <MessageCircle className="h-8 w-8 text-muted-foreground" aria-hidden="true" />

Notes:

  • lucide icons support both size props and Tailwind sizing; using h-8 w-8 keeps styling consistent across components. (lucide.dev)
  • If the icon ever becomes informative (not purely decorative), consider providing a visually hidden label or using Radix’s AccessibleIcon. (lucide.dev)
apps/web/components/ai-elements/suggestion.tsx (4)

19-19: Hidden scrollbar may hurt discoverability; consider showing it on md+ screens

Keeping the scrollbar entirely hidden can reduce usability, especially with keyboard/mouse users. Consider showing it responsively.

-    <ScrollBar className="hidden" orientation="horizontal" />
+    <ScrollBar className="hidden md:flex" orientation="horizontal" />

6-6: Prepare for ref + event improvements: import forwardRef and MouseEvent types

This enables passing a ref to the underlying button and exposing the click event to consumers.

-import type { ComponentProps } from "react";
+import { forwardRef } from "react";
+import type { ComponentProps, MouseEvent } from "react";

23-26: Expose the click event to consumers

Providing the event object along with the suggestion improves flexibility (e.g., preventDefault, modifiers).

-export type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {
-  suggestion: string;
-  onClick?: (suggestion: string) => void;
-};
+export type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {
+  suggestion: string;
+  onClick?: (suggestion: string, event: MouseEvent<HTMLButtonElement>) => void;
+};

28-53: Forward ref to the button and avoid overriding disabled cursor

  • Forwarding a ref helps focus management and integration with parent components.
  • Remove cursor-pointer so it doesn’t fight disabled:cursor-not-allowed styles from the design system.
  • Wire the enhanced click handler that passes (suggestion, event).
-export const Suggestion = ({
-  suggestion,
-  onClick,
-  className,
-  variant = "outline",
-  size = "sm",
-  children,
-  ...props
-}: SuggestionProps) => {
-  const handleClick = () => {
-    onClick?.(suggestion);
-  };
-
-  return (
-    <Button
-      className={cn("cursor-pointer rounded-full px-4", className)}
-      onClick={handleClick}
-      size={size}
-      type="button"
-      variant={variant}
-      {...props}
-    >
-      {children || suggestion}
-    </Button>
-  );
-};
+export const Suggestion = forwardRef<HTMLButtonElement, SuggestionProps>(
+  (
+    {
+      suggestion,
+      onClick,
+      className,
+      variant = "outline",
+      size = "sm",
+      children,
+      ...props
+    },
+    ref,
+  ) => {
+    const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
+      onClick?.(suggestion, event);
+    };
+
+    return (
+      <Button
+        ref={ref}
+        className={cn("rounded-full px-4", className)}
+        onClick={handleClick}
+        size={size}
+        type="button"
+        variant={variant}
+        {...props}
+      >
+        {children || suggestion}
+      </Button>
+    );
+  },
+);
apps/web/package.json (1)

64-64: UI dependencies usage and SSR/hydration safety verified

  • Found actual imports for:

    • use-stick-to-bottom in apps/web/components/ai-elements/conversation.tsx
    • streamdown in apps/web/components/ai-elements/response.tsx
    • @radix-ui/react-use-controllable-state in apps/web/components/ai-elements/reasoning.tsx
    • react-syntax-highlighter in apps/web/components/ai-elements/code-block.tsx
      No hydration warnings detected in these components—Response.tsx is already marked "use client".
  • embla-carousel-react does not appear in any source file; remove it from apps/web/package.json (line 64) if unused.

  • react-syntax-highlighter is statically imported in code-block.tsx (lines 8–12). To reduce bundle size and avoid potential SSR/hydration issues, switch to a dynamic import of the Prism build:

    - import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
    - import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
    + import dynamic from "next/dynamic";
    + const SyntaxHighlighter = dynamic(
    +   () => import("react-syntax-highlighter/dist/esm/prism").then(mod => mod.Prism),
    +   { ssr: false }
    + );
    + import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
  • No further SSR/hydration action needed for streamdown—already client-only.

apps/web/components/assistant-chat/tools.tsx (2)

129-133: Change detection ignores static conditions.

hasChanges only compares aiInstructions. If static conditions changed, the diff UI won’t show it.

-  const hasChanges =
-    originalConditions &&
-    updatedConditions &&
-    originalConditions.aiInstructions !== updatedConditions.aiInstructions;
+  const hasAiChange =
+    !!originalConditions &&
+    !!updatedConditions &&
+    originalConditions.aiInstructions !== updatedConditions.aiInstructions;
+  const hasStaticChange =
+    JSON.stringify(originalConditions?.static ?? null) !==
+    JSON.stringify(updatedConditions?.static ?? null);
+  const hasChanges = hasAiChange || hasStaticChange;

And optionally add a second CollapsibleDiff below to display static diffs:

{hasStaticChange && (
  <CollapsibleDiff
    showChanges={showChanges}
    title="Static Conditions:"
    originalText={JSON.stringify(originalConditions?.static ?? {}, null, 2)}
    updatedText={JSON.stringify(updatedConditions?.static ?? {}, null, 2)}
  />
)}

16-16: Inconsistent tooltip component imports across the codebase.

This file imports Tooltip from "@/components/Tooltip", while ai-elements/actions.tsx uses "@/components/ui/tooltip". Consider consolidating on a single tooltip implementation to avoid mixed behavior and styling.

apps/web/components/ai-elements/loader.tsx (2)

8-15: Hide decorative SVG from ATs.

Mark the spinner SVG as aria-hidden to prevent double announcement when the container is labeled.

-const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
+const LoaderIcon = ({ size = 16 }: LoaderIconProps) => (
   <svg
     height={size}
     strokeLinejoin="round"
     style={{ color: "currentcolor" }}
     viewBox="0 0 16 16"
     width={size}
+    aria-hidden="true"
   >

82-96: Add accessible labeling and status semantics.

Provide an accessible name and ARIA status to the spinner container. Allow callers to override the label.

-export type LoaderProps = HTMLAttributes<HTMLDivElement> & {
-  size?: number;
-};
+export type LoaderProps = HTMLAttributes<HTMLDivElement> & {
+  size?: number;
+  label?: string;
+};

-export const Loader = ({ className, size = 16, ...props }: LoaderProps) => (
+export const Loader = ({
+  className,
+  size = 16,
+  label = "Loading",
+  ...props
+}: LoaderProps) => (
   <div
     className={cn(
       "inline-flex animate-spin items-center justify-center",
       className,
     )}
+    role="status"
+    aria-live="polite"
+    aria-label={label}
     {...props}
   >
     <LoaderIcon size={size} />
   </div>
 );
apps/web/components/ai-elements/actions.tsx (2)

36-49: Prefer aria-label over empty sr-only content; keep name generation consistent.

Render sr-only text only when provided, and set aria-label for ATs. This avoids empty nodes when neither label nor tooltip is set.

   const button = (
     <Button
       className={cn(
         "size-9 p-1.5 text-muted-foreground hover:text-foreground relative",
         className,
       )}
       size={size}
       type="button"
       variant={variant}
+      aria-label={label ?? tooltip}
       {...props}
     >
       {children}
-      <span className="sr-only">{label || tooltip}</span>
+      {(label || tooltip) && (
+        <span className="sr-only">{label || tooltip}</span>
+      )}
     </Button>
   );

51-61: Repeated TooltipProvider instances.

Wrapping each Action with its own TooltipProvider is fine but slightly wasteful. If you have a top-level provider (common pattern), you can drop TooltipProvider here.

apps/web/components/ai-elements/conversation.tsx (2)

13-19: Add accessible name to the live log container.

role="log" is great; add an aria-label so SR users know what the region is.

   <StickToBottom
     className={cn("relative flex-1 overflow-y-auto", className)}
     initial="smooth"
     resize="smooth"
-    role="log"
+    role="log"
+    aria-label="Messages"
     {...props}
   />

45-59: Provide an accessible name and ensure button sits above content.

When rendering only an icon, include an aria-label. Consider adding z-10 to avoid being occluded by content.

-      <Button
+      <Button
         className={cn(
-          "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full",
+          "absolute bottom-4 left-[50%] translate-x-[-50%] rounded-full z-10",
           className,
         )}
         onClick={handleScrollToBottom}
         size="icon"
         type="button"
         variant="outline"
+        aria-label="Scroll to latest messages"
         {...props}
       >
         <ArrowDownIcon className="size-4" />
       </Button>
apps/web/components/ai-elements/message.tsx (1)

53-60: Improve avatar accessibility and initials.

  • Prefer alt={name ?? ""} so SRs can announce a name when present.
  • Optional: derive initials from words (“John Doe” -> “JD”) instead of first 2 characters.
-    <AvatarImage alt="" className="mt-0 mb-0" src={src} />
-    <AvatarFallback>{name?.slice(0, 2) || "ME"}</AvatarFallback>
+    <AvatarImage alt={name ?? ""} className="mt-0 mb-0" src={src} />
+    <AvatarFallback>
+      {name
+        ? name
+            .split(/\s+/)
+            .filter(Boolean)
+            .slice(0, 2)
+            .map((p) => p[0]!.toUpperCase())
+            .join("")
+        : "ME"}
+    </AvatarFallback>
apps/web/components/ai-elements/code-block.tsx (1)

138-146: Add an accessible name and prevent consumer props from overriding copy handler.

  • Provide aria-label when rendering only an icon.
  • Guard against onClick in ...props replacing the copy behavior by composing handlers.
-export const CodeBlockCopyButton = ({
-  onCopy,
-  onError,
-  timeout = 2000,
-  children,
-  className,
-  ...props
-}: CodeBlockCopyButtonProps) => {
+export const CodeBlockCopyButton = ({
+  onCopy,
+  onError,
+  timeout = 2000,
+  children,
+  className,
+  ...props
+}: CodeBlockCopyButtonProps) => {
   const [isCopied, setIsCopied] = useState(false);
   const { code } = useContext(CodeBlockContext);
@@
-  return (
-    <Button
-      className={cn("shrink-0", className)}
-      onClick={copyToClipboard}
-      size="icon"
-      variant="ghost"
-      {...props}
-    >
+  return (
+    <Button
+      className={cn("shrink-0", className)}
+      onClick={(e) => {
+        void copyToClipboard();
+        // @ts-expect-error Allow consumer-provided onClick if present.
+        props?.onClick?.(e);
+      }}
+      size="icon"
+      variant="ghost"
+      type="button"
+      aria-label={isCopied ? "Copied" : "Copy code"}
+      {...props}
+    >
       {children ?? <Icon size={14} />}
     </Button>
   );
apps/web/components/ai-elements/tool.tsx (1)

31-36: Forward all trigger props properly.

Typing ToolHeaderProps as a bare object prevents consumers from passing standard trigger props (e.g., disabled). Extend from ComponentProps<typeof CollapsibleTrigger> to preserve the API.

-export type ToolHeaderProps = {
+export type ToolHeaderProps = ComponentProps<typeof CollapsibleTrigger> & {
   type: ToolUIPart["type"];
   state: ToolUIPart["state"];
-  className?: string;
 };
apps/web/components/assistant-chat/messages.tsx (1)

46-57: Optional: Announce “Thinking…” politely to screen readers.

Wrap the assistant loader in an aria-live region for better SR UX while maintaining visual behavior.

-            <Message from="assistant">
-              <MessageContent>
-                <div className="flex items-center gap-2 text-muted-foreground">
+            <Message from="assistant" aria-live="polite" aria-atomic="true">
+              <MessageContent>
+                <div className="flex items-center gap-2 text-muted-foreground">
                   <Loader />
                   <span>Thinking...</span>
                 </div>
               </MessageContent>
             </Message>
apps/web/components/assistant-chat/chat.tsx (2)

100-117: Improve a11y: add an aria-label/title that reflects the current action (Send/Stop/Sending)

Icon-only buttons should expose intent to screen readers. Map the label to the current status.

           <PromptInputSubmit
+            aria-label={
+              status === "streaming"
+                ? "Stop generating"
+                : status === "submitted"
+                  ? "Sending…"
+                  : "Send message"
+            }
+            title={
+              status === "streaming"
+                ? "Stop generating"
+                : status === "submitted"
+                  ? "Sending…"
+                  : "Send message"
+            }
             status={

31-35: Consider namespacing the localStorage key per chatId to avoid cross-chat draft collisions

Using a single "input" key causes drafts from one conversation to appear in another. If that’s unintended, derive the key from chatId. Since useLocalStorage's key is static, you could manage per-chat storage manually.

Example approach:

  • Keep useLocalStorage("chat:lastInput", "") only as a global fallback.
  • Additionally sync localStorage.setItem(\chat:input:${chatId}`, input)whenchatId` changes.
  • On mount or when chatId changes, prefer localStorage.getItem(\chat:input:${chatId}`) ?? lastInput`.

If you want, I can provide a concrete diff that handles per-chat drafts.

Also applies to: 42-46, 49-53

apps/web/components/assistant-chat/message.tsx (1)

61-71: Key stability for MessageEditor inside parts map

MessageEditor is keyed with message.id. Inside a parts .map, that can lead to duplicate keys across siblings or unnecessary remounting when switching edit mode. Use the per-part key you computed.

-                <MessageEditor
-                  key={message.id}
+                <MessageEditor
+                  key={key}
                   message={message}
                   setMode={setMode}
                   setMessages={setMessages}
                   regenerate={regenerate}
                 />
apps/web/components/ai-elements/reasoning.tsx (3)

119-155: title prop on ReasoningTrigger is unused

You accept title but never render it. Either use it or remove it to avoid confusion.

Use it as a prefix label:

-        {children ?? (
+        {children ?? (
           <>
-            <BrainIcon className="size-4" />
-            {isStreaming || duration === 0 ? (
-              <p>Thinking...</p>
-            ) : (
-              <p>Thought for {duration} seconds</p>
-            )}
+            <BrainIcon className="size-4" />
+            <span className="sr-only">{title}</span>
+            {isStreaming || duration === 0 ? (
+              <p>{title}: Thinking...</p>
+            ) : (
+              <p>{title}: Thought for {duration} seconds</p>
+            )}
             <ChevronDownIcon

157-176: Broaden ReasoningContent children type to ReactNode for flexibility

Confining children to string limits reuse. Response supports rich content; allow ReactNode.

-export type ReasoningContentProps = ComponentProps<
-  typeof CollapsibleContent
-> & {
-  children: string;
-};
+export type ReasoningContentProps = ComponentProps<
+  typeof CollapsibleContent
+> & {
+  children: React.ReactNode;
+};

Also update the import:

-import type { ComponentProps } from "react";
+import type { ComponentProps } from "react";
+import React from "react";

78-90: Optional: make auto-close delay configurable

AUTO_CLOSE_DELAY is fixed at 1000ms. Exposing autoCloseDelayMs?: number on ReasoningProps improves UX flexibility without changing defaults.

I can provide a small diff if you want this configurable.

apps/web/components/ai-elements/prompt-input.tsx (2)

34-46: minHeight/maxHeight props are unused; either apply them or remove them

You define the props but don’t use them in styles. Apply as inline styles to Textarea (simple) or wire autosizing to respect the limits.

Simple application as CSS min/max height:

 export const PromptInputTextarea = ({
   onChange,
   className,
   placeholder = "What would you like to know?",
   minHeight = 48,
   maxHeight = 164,
   ...props
 }: PromptInputTextareaProps) => {
@@
   return (
     <Textarea
       className={cn(
         "w-full resize-none rounded-none border-none p-3 shadow-none outline-none ring-0",
         "field-sizing-content max-h-[6lh] bg-transparent dark:bg-transparent",
         "focus-visible:ring-0",
         className,
       )}
+      style={{ minHeight, maxHeight, ...(props.style ?? {}) }}
       name="message"
       onChange={(e) => {
         onChange?.(e);
       }}
       onKeyDown={handleKeyDown}
       placeholder={placeholder}
       {...props}
     />
   );

If you prefer true autosize, we can integrate a small useAutosizeTextArea hook respecting these limits.

Also applies to: 68-85


164-175: A11y: Provide a default aria-label for icon-only submit

Consider passing an aria-label when no children are provided. This can also be set by consumers (e.g., chat.tsx), but a sensible default helps.

   return (
     <Button
       className={cn("gap-1.5 rounded-lg", className)}
       size={size}
       type="submit"
       variant={variant}
+      aria-label={props["aria-label"] ?? "Send message"}
       {...props}
     >
       {children ?? Icon}
     </Button>
   );
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • 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 ef9b423 and 802a562.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • apps/web/components/ai-elements/actions.tsx (1 hunks)
  • apps/web/components/ai-elements/code-block.tsx (1 hunks)
  • apps/web/components/ai-elements/conversation.tsx (1 hunks)
  • apps/web/components/ai-elements/loader.tsx (1 hunks)
  • apps/web/components/ai-elements/message.tsx (1 hunks)
  • apps/web/components/ai-elements/prompt-input.tsx (1 hunks)
  • apps/web/components/ai-elements/reasoning.tsx (1 hunks)
  • apps/web/components/ai-elements/response.tsx (1 hunks)
  • apps/web/components/ai-elements/suggestion.tsx (1 hunks)
  • apps/web/components/ai-elements/tool.tsx (1 hunks)
  • apps/web/components/assistant-chat/chat.tsx (2 hunks)
  • apps/web/components/assistant-chat/icons.tsx (0 hunks)
  • apps/web/components/assistant-chat/markdown.tsx (0 hunks)
  • apps/web/components/assistant-chat/message-reasoning.tsx (0 hunks)
  • apps/web/components/assistant-chat/message.tsx (2 hunks)
  • apps/web/components/assistant-chat/messages.tsx (2 hunks)
  • apps/web/components/assistant-chat/multimodal-input.tsx (0 hunks)
  • apps/web/components/assistant-chat/overview.tsx (2 hunks)
  • apps/web/components/assistant-chat/submit-button.tsx (0 hunks)
  • apps/web/components/assistant-chat/tools.tsx (13 hunks)
  • apps/web/components/assistant-chat/use-scroll-to-bottom.ts (0 hunks)
  • apps/web/package.json (5 hunks)
💤 Files with no reviewable changes (6)
  • apps/web/components/assistant-chat/submit-button.tsx
  • apps/web/components/assistant-chat/use-scroll-to-bottom.ts
  • apps/web/components/assistant-chat/icons.tsx
  • apps/web/components/assistant-chat/markdown.tsx
  • apps/web/components/assistant-chat/message-reasoning.tsx
  • apps/web/components/assistant-chat/multimodal-input.tsx
🧰 Additional context used
📓 Path-based instructions (11)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
apps/web/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Use React Hook Form with Zod validation for form handling

Use the LoadingContent component to handle loading and error states consistently in data-fetching components.

Use PascalCase for components (e.g. components/Button.tsx)

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/package.json
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
apps/web/components/!(ui)/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

All other components are in components/

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/package.json
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/components/assistant-chat/overview.tsx
  • apps/web/components/ai-elements/message.tsx
  • apps/web/components/ai-elements/response.tsx
  • apps/web/components/assistant-chat/tools.tsx
  • apps/web/components/ai-elements/code-block.tsx
  • apps/web/components/ai-elements/loader.tsx
  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/suggestion.tsx
  • apps/web/components/assistant-chat/messages.tsx
  • apps/web/components/ai-elements/tool.tsx
  • apps/web/components/assistant-chat/message.tsx
  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/conversation.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
  • apps/web/components/ai-elements/actions.tsx
🧠 Learnings (14)
📚 Learning: 2025-07-19T17:50:22.078Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-19T17:50:22.078Z
Learning: Applies to components/**/*.tsx : Use the `LoadingContent` component to handle loading states

Applied to files:

  • apps/web/components/ai-elements/loader.tsx
📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-07-18T15:05:16.146Z
Learning: Applies to apps/web/components/**/*.tsx : Use the `LoadingContent` component to handle loading and error states consistently in data-fetching components.

Applied to files:

  • apps/web/components/ai-elements/loader.tsx
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/*.tsx : Use `LoadingContent` component to handle loading and error states consistently

Applied to files:

  • apps/web/components/ai-elements/loader.tsx
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/*.tsx : Use LoadingContent component for async data

Applied to files:

  • apps/web/components/ai-elements/loader.tsx
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/*.tsx : Pass `loading`, `error`, and children props to `LoadingContent`

Applied to files:

  • apps/web/components/ai-elements/loader.tsx
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Keep related AI functions co-located; extract shared logic into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/components/ai-elements/reasoning.tsx
  • apps/web/components/ai-elements/tool.tsx
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Format prompts with XML-like tags; remove excessive whitespace; truncate overly long inputs; keep formatting consistent

Applied to files:

  • apps/web/components/assistant-chat/chat.tsx
  • apps/web/components/ai-elements/prompt-input.tsx
📚 Learning: 2025-07-19T17:50:22.078Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-19T17:50:22.078Z
Learning: Applies to components/**/*.tsx : Use the `Input` component with `autosizeTextarea` and appropriate props for text areas

Applied to files:

  • apps/web/components/ai-elements/prompt-input.tsx
📚 Learning: 2025-08-17T16:57:25.834Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-08-17T16:57:25.834Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.ts : Keep system and user prompts separate; system defines role/task, user contains data/context

Applied to files:

  • apps/web/components/ai-elements/prompt-input.tsx
📚 Learning: 2025-07-19T17:50:22.078Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-19T17:50:22.078Z
Learning: Applies to components/**/*.tsx : Use the `Input` component for text inputs, passing `registerProps` and `error` props for form handling

Applied to files:

  • apps/web/components/ai-elements/prompt-input.tsx
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `adminActionClient` for actions restricted to admin users.

Applied to files:

  • apps/web/components/ai-elements/actions.tsx
📚 Learning: 2025-07-18T17:27:58.249Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-07-18T17:27:58.249Z
Learning: Applies to apps/web/utils/actions/*.ts : Implement all server actions using the `next-safe-action` library for type safety, input validation, context management, and error handling. Refer to `apps/web/utils/actions/safe-action.ts` for client definitions (`actionClient`, `actionClientUser`, `adminActionClient`).

Applied to files:

  • apps/web/components/ai-elements/actions.tsx
📚 Learning: 2025-07-18T15:07:00.269Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/page-structure.mdc:0-0
Timestamp: 2025-07-18T15:07:00.269Z
Learning: Applies to apps/web/app/(app)/*/**/*.tsx : If you need to use onClick in a component, that component is a client component and file must start with 'use client'

Applied to files:

  • apps/web/components/ai-elements/actions.tsx
📚 Learning: 2025-07-20T09:00:16.505Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-07-20T09:00:16.505Z
Learning: Applies to apps/web/app/**/*.tsx : Components with `onClick` must be client components with `use client` directive

Applied to files:

  • apps/web/components/ai-elements/actions.tsx
🧬 Code graph analysis (14)
apps/web/components/ai-elements/message.tsx (1)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/ai-elements/response.tsx (1)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/assistant-chat/tools.tsx (2)
apps/web/utils/ai/assistant/chat.ts (1)
  • UpdateAboutTool (621-621)
apps/web/components/ui/card.tsx (1)
  • Card (138-138)
apps/web/components/ai-elements/code-block.tsx (2)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/Button.tsx (1)
  • Button (60-87)
apps/web/components/ai-elements/loader.tsx (1)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/ai-elements/reasoning.tsx (2)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/ai-elements/response.tsx (1)
  • Response (9-20)
apps/web/components/ai-elements/suggestion.tsx (2)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/Button.tsx (1)
  • Button (60-87)
apps/web/components/assistant-chat/messages.tsx (5)
apps/web/components/ai-elements/conversation.tsx (3)
  • Conversation (12-20)
  • ConversationContent (26-31)
  • ConversationScrollButton (35-62)
apps/web/components/assistant-chat/overview.tsx (1)
  • Overview (6-34)
apps/web/components/assistant-chat/message.tsx (1)
  • PreviewMessage (339-348)
apps/web/components/ai-elements/message.tsx (2)
  • Message (10-20)
  • MessageContent (24-40)
apps/web/components/ai-elements/loader.tsx (1)
  • Loader (86-96)
apps/web/components/ai-elements/tool.tsx (2)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/ai-elements/code-block.tsx (1)
  • CodeBlock (29-100)
apps/web/components/assistant-chat/message.tsx (4)
apps/web/components/ai-elements/message.tsx (2)
  • Message (10-20)
  • MessageContent (24-40)
apps/web/components/ai-elements/reasoning.tsx (3)
  • Reasoning (42-111)
  • ReasoningTrigger (119-155)
  • ReasoningContent (163-176)
apps/web/components/ai-elements/response.tsx (1)
  • Response (9-20)
apps/web/components/assistant-chat/tools.tsx (7)
  • BasicToolInfo (24-30)
  • CreatedRuleToolCard (32-102)
  • UpdatedRuleConditions (104-191)
  • UpdatedRuleActions (193-283)
  • UpdatedLearnedPatterns (285-339)
  • UpdateAbout (341-348)
  • AddToKnowledgeBase (350-373)
apps/web/components/assistant-chat/chat.tsx (2)
apps/web/providers/ChatProvider.tsx (2)
  • Chat (21-21)
  • useChat (116-122)
apps/web/components/ai-elements/prompt-input.tsx (3)
  • PromptInput (24-32)
  • PromptInputTextarea (39-85)
  • PromptInputSubmit (146-175)
apps/web/components/ai-elements/conversation.tsx (2)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/Button.tsx (1)
  • Button (60-87)
apps/web/components/ai-elements/prompt-input.tsx (3)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/Button.tsx (1)
  • Button (60-87)
apps/web/components/Select.tsx (1)
  • Select (16-48)
apps/web/components/ai-elements/actions.tsx (3)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/Button.tsx (1)
  • Button (60-87)
apps/web/components/ui/tooltip.tsx (4)
  • TooltipProvider (61-61)
  • Tooltip (61-61)
  • TooltipTrigger (61-61)
  • TooltipContent (61-61)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
apps/web/components/ai-elements/suggestion.tsx (1)

1-5: Overall, clean, idiomatic UI primitives

Good use of shadcn/ui primitives, prop forwarding, and Tailwind. The suggested tweaks will make the API more predictable and accessible.

apps/web/components/assistant-chat/tools.tsx (1)

429-431: Nice consolidation with ToolCard.

Wrapping tool UIs with a common ToolCard improves consistency and reduces duplication.

apps/web/components/ai-elements/response.tsx (1)

9-18: LGTM overall; pass-through wrapper with sane defaults.

The wrapper cleanly forwards props and normalizes layout classNames. Once the comparator is removed, this is solid.

apps/web/components/ai-elements/actions.tsx (2)

15-19: Simple, reusable Actions container looks good.

Lightweight flex wrapper with cn is appropriate here.


26-66: No <Action tooltip=…> usages detected—no changes needed.

I searched the entire repository for any <Action … tooltip= occurrences and found none. There are currently no tooltip props passed to Action (including in the assistant-chat components), so no further action is required here.

apps/web/components/ai-elements/conversation.tsx (1)

35-44: Confirm usage within provider context.

useStickToBottomContext() requires the component to be a descendant of <StickToBottom>. Current usage looks correct; flagging in case future refactors move the button out of Conversation.

apps/web/components/assistant-chat/chat.tsx (1)

85-91: No changes needed: handleSubmit already clears the input state

The handleSubmit callback defined in ChatProvider.tsx (lines 85–91) invokes setInput("") immediately after sending the message, so the input state is reset to an empty string. Consequently, in your form’s onSubmit handler you don’t need to add another setInput("")—it’s already handled upstream.

Comment on lines +119 to +124
const copyToClipboard = async () => {
if (typeof window === "undefined" || !navigator.clipboard.writeText) {
onError?.(new Error("Clipboard API not available"));
return;
}

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

Fix Clipboard API feature detection to avoid runtime errors.

Accessing navigator.clipboard.writeText when clipboard is undefined throws before your guard runs. Use optional chaining or check navigator.clipboard first.

-  const copyToClipboard = async () => {
-    if (typeof window === "undefined" || !navigator.clipboard.writeText) {
+  const copyToClipboard = async () => {
+    if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
       onError?.(new Error("Clipboard API not available"));
       return;
     }
📝 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 copyToClipboard = async () => {
if (typeof window === "undefined" || !navigator.clipboard.writeText) {
onError?.(new Error("Clipboard API not available"));
return;
}
const copyToClipboard = async () => {
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
onError?.(new Error("Clipboard API not available"));
return;
}
// …rest of function…
🤖 Prompt for AI Agents
In apps/web/components/ai-elements/code-block.tsx around lines 119 to 124, the
current feature detection accesses navigator.clipboard.writeText which throws
when navigator.clipboard is undefined; change the guard to check
navigator.clipboard exists (e.g., navigator.clipboard?.writeText or typeof
navigator !== "undefined" && navigator.clipboard &&
navigator.clipboard.writeText) and call onError with the same error and return
if the Clipboard API is not available.

Comment on lines 31 to 39
"flex flex-col gap-2 overflow-hidden rounded-lg px-4 py-3 text-foreground text-sm",
"group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground",
"group-[.is-assistant]:bg-secondary group-[.is-assistant]:text-foreground",
className,
)}
{...props}
>
<div className="is-user:dark">{children}</div>
</div>
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

Remove invalid Tailwind class "is-user:dark".

is-user:dark isn’t a valid variant and won’t have any effect. It’s safe to drop it.

-    <div className="is-user:dark">{children}</div>
+    <div>{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
"flex flex-col gap-2 overflow-hidden rounded-lg px-4 py-3 text-foreground text-sm",
"group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground",
"group-[.is-assistant]:bg-secondary group-[.is-assistant]:text-foreground",
className,
)}
{...props}
>
<div className="is-user:dark">{children}</div>
</div>
<div
className={cn(
"flex flex-col gap-2 overflow-hidden rounded-lg px-4 py-3 text-foreground text-sm",
"group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground",
"group-[.is-assistant]:bg-secondary group-[.is-assistant]:text-foreground",
className,
)}
{...props}
>
<div>{children}</div>
</div>
🤖 Prompt for AI Agents
In apps/web/components/ai-elements/message.tsx around lines 31 to 39, the inner
div uses an invalid Tailwind class "is-user:dark"; remove that class from the
div's className (drop "is-user:dark" entirely). If the intent was to style user
messages in dark mode, replace it with a valid Tailwind variant such as
"dark:..." combined with the appropriate group selector (e.g.,
"dark:group-[.is-user]:<utility>"), otherwise just delete the invalid token.

Comment on lines +22 to +32
export type PromptInputProps = HTMLAttributes<HTMLFormElement>;

export const PromptInput = ({ className, ...props }: PromptInputProps) => (
<form
className={cn(
"w-full divide-y overflow-hidden rounded-xl border bg-background shadow-sm",
className,
)}
{...props}
/>
);
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

Incorrect prop type for form prevents onSubmit typing

PromptInputProps should be FormHTMLAttributes<HTMLFormElement>, not HTMLAttributes<HTMLFormElement>. The latter doesn’t include onSubmit, causing TS errors where <PromptInput onSubmit={...} /> is used (e.g., chat.tsx).

-import type {
-  ComponentProps,
-  HTMLAttributes,
-  KeyboardEventHandler,
-} from "react";
+import type {
+  ComponentProps,
+  FormHTMLAttributes,
+  HTMLAttributes,
+  KeyboardEventHandler,
+} from "react";
 ...
-export type PromptInputProps = HTMLAttributes<HTMLFormElement>;
+export type PromptInputProps = FormHTMLAttributes<HTMLFormElement>;
📝 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
export type PromptInputProps = HTMLAttributes<HTMLFormElement>;
export const PromptInput = ({ className, ...props }: PromptInputProps) => (
<form
className={cn(
"w-full divide-y overflow-hidden rounded-xl border bg-background shadow-sm",
className,
)}
{...props}
/>
);
// apps/web/components/ai-elements/prompt-input.tsx
import type {
ComponentProps,
FormHTMLAttributes,
HTMLAttributes,
KeyboardEventHandler,
} from "react";
import { cn } from "@/utils/cn";
export type PromptInputProps = FormHTMLAttributes<HTMLFormElement>;
export const PromptInput = ({ className, ...props }: PromptInputProps) => (
<form
className={cn(
"w-full divide-y overflow-hidden rounded-xl border bg-background shadow-sm",
className,
)}
{...props}
/>
);
🤖 Prompt for AI Agents
In apps/web/components/ai-elements/prompt-input.tsx around lines 22 to 32, the
prop type is incorrectly set to HTMLAttributes<HTMLFormElement>, which does not
include onSubmit; change PromptInputProps to FormHTMLAttributes<HTMLFormElement>
and update the import/React type reference as needed so the component accepts
form-specific props (e.g., onSubmit) without TypeScript errors. Ensure the rest
of the component signature stays the same and re-run type checks to verify
usages like <PromptInput onSubmit={...} /> compile.

Comment on lines +117 to +140
export const PromptInputButton = ({
variant = "ghost",
className,
size,
...props
}: PromptInputButtonProps) => {
const newSize =
(size ?? Children.count(props.children) > 1) ? "default" : "icon";

return (
<Button
className={cn(
"shrink-0 gap-1.5 rounded-lg",
variant === "ghost" && "text-muted-foreground",
newSize === "default" && "px-3",
className,
)}
size={newSize}
type="button"
variant={variant}
{...props}
/>
);
};
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

Logic bug: newSize ignores provided size and forces "default"

(size ?? Children.count(props.children) > 1) ? "default" : "icon" evaluates truthy when size is any non-empty string, forcing "default". You likely want: use the provided size, otherwise pick based on children count.

-  const newSize =
-    (size ?? Children.count(props.children) > 1) ? "default" : "icon";
+  const newSize = size ?? (Children.count(props.children) > 1 ? "default" : "icon");
📝 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
export const PromptInputButton = ({
variant = "ghost",
className,
size,
...props
}: PromptInputButtonProps) => {
const newSize =
(size ?? Children.count(props.children) > 1) ? "default" : "icon";
return (
<Button
className={cn(
"shrink-0 gap-1.5 rounded-lg",
variant === "ghost" && "text-muted-foreground",
newSize === "default" && "px-3",
className,
)}
size={newSize}
type="button"
variant={variant}
{...props}
/>
);
};
export const PromptInputButton = ({
variant = "ghost",
className,
size,
...props
}: PromptInputButtonProps) => {
const newSize = size ?? (Children.count(props.children) > 1 ? "default" : "icon");
return (
<Button
className={cn(
"shrink-0 gap-1.5 rounded-lg",
variant === "ghost" && "text-muted-foreground",
newSize === "default" && "px-3",
className,
)}
size={newSize}
type="button"
variant={variant}
{...props}
/>
);
};
🤖 Prompt for AI Agents
In apps/web/components/ai-elements/prompt-input.tsx around lines 117 to 140, the
computed newSize uses the nullish/coercion expression which treats any non-empty
string size as truthy and always yields "default"; change it to use the provided
size when explicitly passed, otherwise derive it from children count (e.g. const
newSize = size !== undefined ? size : (Children.count(props.children) > 1 ?
"default" : "icon")), ensuring the fallback produces one of the expected size
values before passing it to the Button.

Comment on lines +19 to +20
(prevProps, nextProps) => prevProps.children === nextProps.children,
);
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

Custom memo comparator risks missing updates; remove or broaden it.

Comparing only children can skip re-renders when other props change (e.g., className, components, handlers), causing stale UI. The default React.memo shallow compare is safer here.

-export const Response = memo(
-  ({ className, ...props }: ResponseProps) => (
+export const Response = memo(
+  ({ className, ...props }: ResponseProps) => (
     <Streamdown
       className={cn(
         "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
         className,
       )}
       {...props}
     />
   ),
-  (prevProps, nextProps) => prevProps.children === nextProps.children,
 );
📝 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
(prevProps, nextProps) => prevProps.children === nextProps.children,
);
export const Response = memo(
({ className, ...props }: ResponseProps) => (
<Streamdown
className={cn(
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
className,
)}
{...props}
/>
)
);
🤖 Prompt for AI Agents
In apps/web/components/ai-elements/response.tsx around lines 19-20, the
component uses a custom React.memo comparator that only compares
prevProps.children === nextProps.children; remove this custom comparator so
React.memo uses its default shallow prop comparison (or replace it with a
comparator that shallow-compares all relevant props) to ensure changes to
className, handlers, components, etc. trigger re-renders; update the memo call
accordingly.

Comment on lines +8 to +21
export type SuggestionsProps = ComponentProps<typeof ScrollArea>;

export const Suggestions = ({
className,
children,
...props
}: SuggestionsProps) => (
<ScrollArea className="w-full overflow-x-auto whitespace-nowrap" {...props}>
<div className={cn("flex w-max flex-nowrap items-center gap-2", className)}>
{children}
</div>
<ScrollBar className="hidden" orientation="horizontal" />
</ScrollArea>
);
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

Don't hijack ScrollArea's className; add a dedicated rowClassName and merge container classes correctly

Right now, the className prop is typed as ScrollArea’s prop but applied to the inner row, not the container. This is surprising and makes it impossible to style the ScrollArea wrapper. Recommend forwarding className to ScrollArea and introducing rowClassName for the inner flex row.

Apply:

-export type SuggestionsProps = ComponentProps<typeof ScrollArea>;
+export type SuggestionsProps = ComponentProps<typeof ScrollArea> & {
+  rowClassName?: string;
+};

-export const Suggestions = ({
-  className,
-  children,
-  ...props
-}: SuggestionsProps) => (
-  <ScrollArea className="w-full overflow-x-auto whitespace-nowrap" {...props}>
-    <div className={cn("flex w-max flex-nowrap items-center gap-2", className)}>
+export const Suggestions = ({
+  rowClassName,
+  children,
+  ...props
+}: SuggestionsProps) => (
+  <ScrollArea
+    {...props}
+    className={cn("w-full overflow-x-auto whitespace-nowrap", props.className)}
+  >
+    <div className={cn("flex w-max flex-nowrap items-center gap-2", rowClassName)}>
       {children}
     </div>
     <ScrollBar className="hidden" orientation="horizontal" />
   </ScrollArea>
 );
📝 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
export type SuggestionsProps = ComponentProps<typeof ScrollArea>;
export const Suggestions = ({
className,
children,
...props
}: SuggestionsProps) => (
<ScrollArea className="w-full overflow-x-auto whitespace-nowrap" {...props}>
<div className={cn("flex w-max flex-nowrap items-center gap-2", className)}>
{children}
</div>
<ScrollBar className="hidden" orientation="horizontal" />
</ScrollArea>
);
export type SuggestionsProps = ComponentProps<typeof ScrollArea> & {
rowClassName?: string;
};
export const Suggestions = ({
rowClassName,
children,
...props
}: SuggestionsProps) => (
<ScrollArea
{...props}
className={cn("w-full overflow-x-auto whitespace-nowrap", props.className)}
>
<div className={cn("flex w-max flex-nowrap items-center gap-2", rowClassName)}>
{children}
</div>
<ScrollBar className="hidden" orientation="horizontal" />
</ScrollArea>
);
🤖 Prompt for AI Agents
In apps/web/components/ai-elements/suggestion.tsx around lines 8–21, the
component currently treats the incoming className as styles for the inner row
instead of the ScrollArea wrapper; change the props to accept and forward
className to the ScrollArea and add a new optional rowClassName prop for the
inner div. Update the prop type (extend ComponentProps<typeof ScrollArea> with
rowClassName?: string), destructure { className, rowClassName, children,
...props }, pass className to <ScrollArea className={cn("w-full overflow-x-auto
whitespace-nowrap", className)} {...props}>, and use rowClassName when building
the inner div's class with cn("flex w-max flex-nowrap items-center gap-2",
rowClassName). Ensure no other consumers are broken by keeping all other props
forwarded.

Comment on lines +66 to +79
<CollapsibleTrigger
className={cn(
"flex w-full items-center justify-between gap-4 p-3",
className,
)}
{...props}
>
<div className="flex items-center gap-2">
<WrenchIcon className="size-4 text-muted-foreground" />
<span className="font-medium text-sm">{type}</span>
{getStatusBadge(state)}
</div>
<ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
</CollapsibleTrigger>
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

Chevron rotation never triggers; missing group class on trigger.

group-data-[state=open] requires a parent with group. Add group to the trigger or switch to data-[state=open] on the icon. Also add a small gap between icon and text in the status badge for readability.

 export const ToolHeader = ({
   className,
   type,
   state,
   ...props
 }: ToolHeaderProps) => (
   <CollapsibleTrigger
     className={cn(
-      "flex w-full items-center justify-between gap-4 p-3",
+      "group flex w-full items-center justify-between gap-4 p-3",
       className,
     )}
     {...props}
   >
@@
-    <ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
+    <ChevronDownIcon className="size-4 text-muted-foreground transition-transform group-data-[state=open]:rotate-180" />
   </CollapsibleTrigger>
 )

And inside getStatusBadge:

-  return (
-    <Badge className="rounded-full text-xs" variant="secondary">
+  return (
+    <Badge className="inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs" variant="secondary">
       {icons[status]}
       {labels[status]}
     </Badge>
   );
🤖 Prompt for AI Agents
In apps/web/components/ai-elements/tool.tsx around lines 66 to 79, the
ChevronDownIcon never rotates because the trigger lacks the parent "group" class
and the status badge text needs more spacing; add "group" to the
CollapsibleTrigger className (e.g., include "group" in the cn call) or
alternatively change the icon selector to use data-[state=open] instead of
group-data-[state=open], and inside getStatusBadge add a small gap class (e.g.,
ml-1 or gap-1 on the badge wrapper) between the status icon and text for
improved readability.

Comment on lines +101 to +116
status={
status === "streaming"
? "streaming"
: status === "submitted"
? "submitted"
: "ready"
}
disabled={!input.trim() || status !== "ready"}
className="absolute bottom-1 right-1"
onClick={(e) => {
if (status === "streaming") {
e.preventDefault();
stop();
setMessages((messages) => 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

Stop/submit button is always disabled when streaming, making it impossible to stop generation

You're disabling the submit button whenever status !== "ready", but the click handler is intended to stop streaming when status === "streaming". A disabled button won't receive clicks, so users cannot stop generation.

Apply this diff so the button remains clickable while streaming, but is disabled during the brief "submitted" phase and when the input is empty in the "ready" state:

-          <PromptInputSubmit
+          <PromptInputSubmit
             status={
               status === "streaming"
                 ? "streaming"
                 : status === "submitted"
                   ? "submitted"
                   : "ready"
             }
-            disabled={!input.trim() || status !== "ready"}
+            disabled={status === "submitted" || (status === "ready" && !input.trim())}
             className="absolute bottom-1 right-1"
             onClick={(e) => {
               if (status === "streaming") {
                 e.preventDefault();
                 stop();
                 setMessages((messages) => messages);
               }
             }}
           />
🤖 Prompt for AI Agents
In apps/web/components/assistant-chat/chat.tsx around lines 101 to 116, the
submit/stop button is disabled for any status !== "ready", which prevents
stopping during "streaming"; change the disabled prop to only be true when
status === "submitted" or when status === "ready" and input is empty (e.g.
disabled = status === "submitted" || (status === "ready" && !input.trim())),
keep the click handler that calls stop() when status === "streaming", and
preserve the status prop logic so the UI still shows "streaming", "submitted" or
"ready".

Comment on lines +89 to 96
if ("error" in output) {
return (
<MessageReasoning
key={key}
isLoading={isLoading}
reasoning={part.text}
<ErrorToolCard
key={toolCallId}
error={String(output.error)}
/>
);
}
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

Defensive guard for tool outputs to avoid runtime errors when checking for "error"

Several branches assume output is an object. If output is null/undefined/a string, ("error" in output) will throw. Add a narrow type guard before using "error" in output.

Example pattern (apply to each block):

-              if ("error" in output) {
+              if (output && typeof output === "object" && "error" in output) {
                 return (
                   <ErrorToolCard
                     key={toolCallId}
                     error={String(output.error)}
                   />
                 );
               }

Optionally, extract a helper:

const hasToolError = (o: unknown): o is { error: unknown } =>
  typeof o === "object" && o !== null && "error" in o;

Then use if (hasToolError(output)) { … }.

Also applies to: 122-129, 152-159, 186-193, 222-229, 258-265, 290-295, 317-324

🤖 Prompt for AI Agents
In apps/web/components/assistant-chat/message.tsx around lines 89-96 (and also
apply the same change to 122-129, 152-159, 186-193, 222-229, 258-265, 290-295,
317-324): the code uses ("error" in output) assuming output is an object which
can throw if output is null/undefined or a primitive; add a narrow type guard
before using the "in" operator (e.g., create a helper like const hasToolError =
(o: unknown): o is { error: unknown } => typeof o === "object" && o !== null &&
"error" in o; and replace instances with if (hasToolError(output)) { ... }) so
each branch safely checks for an error property without risking runtime
exceptions.

import { Button } from "@/components/ui/button";
import { MessageText, TypographyH3 } from "@/components/Typography";
import { ExamplesDialog } from "./examples-dialog";
import { MessageCircleIcon } from "lucide-react";
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Use the correct lucide-react export: MessageCircle (not MessageCircleIcon).

lucide-react exports icons without the Icon suffix. Importing MessageCircleIcon will fail type-checking and break the build. Switch to MessageCircle.

Apply this diff:

-import { MessageCircleIcon } from "lucide-react";
+import { MessageCircle } from "lucide-react";

References: lucide docs show import { MessageCircle } from 'lucide-react' and list supported props. (lucide.dev)

Run this quick repo scan to catch any other incorrect *Icon imports from lucide-react:


🏁 Script executed:

#!/bin/bash
# Find lucide-react imports that end with "Icon"
rg -nP -C2 --type=ts --type=tsx "import\s*{\s*[^}]*\b[A-Za-z][A-Za-z0-9]*Icon\b[^}]*}\s*from\s*['\"]lucide-react['\"]"

# Specifically check for MessageCircleIcon occurrences
rg -n "MessageCircleIcon" --type=ts --type=tsx

Length of output: 227


🏁 Script executed:

#!/bin/bash
# Search for lucide-react imports ending with an Icon suffix in all TS/TSX files
rg -nP -C2 "import\s*{\s*[^}]*\b\w+Icon\b[^}]*}\s*from\s*['\"]lucide-react['\"]" -g '*.ts' -g '*.tsx'

# Check specifically for any occurrences of MessageCircleIcon
rg -n "MessageCircleIcon" -g '*.ts' -g '*.tsx'

Length of output: 43659


Update all lucide-react imports to remove “Icon” suffix

The lucide-react package exports icons without the Icon suffix. Every import of the form

import { FooIcon } from "lucide-react";

will fail at build time; you must import

import { Foo } from "lucide-react";

instead.

Please address all of these occurrences (not just MessageCircleIcon):

• apps/web/components/Banner.tsx

  • import { XIcon } from "lucide-react";import { X } from "lucide-react";
    • apps/web/components/DatePickerWithRange.tsx
  • import { CalendarIcon } from "lucide-react";import { Calendar } from "lucide-react";
    • apps/web/components/HeroVideoDialog.tsx
  • import { Play, XIcon } from "lucide-react";import { Play, X } from "lucide-react";
    • apps/web/components/assistant-chat/overview.tsx
  • import { MessageCircleIcon } from "lucide-react";import { MessageCircle } from "lucide-react";
    • apps/web/components/assistant-chat/chat.tsx
  • import { HistoryIcon, PlusIcon } from "lucide-react";import { History, Plus } from "lucide-react";
    • …and all other files reporting *Icon imports (e.g., ViewEmailButton.tsx, TooltipExplanation.tsx, OnboardingModal.tsx, etc.)

A quick way to catch and correct them all:

# Find every lucide-react import ending with Icon
rg -lP "import\s*{\s*.*Icon\s*}" -g '*.ts*' | xargs sed -i 's/\([A-Za-z0-9]\+\)Icon\b/\1/g'

After running that replacement, please rebuild and ensure type-checking passes.

🤖 Prompt for AI Agents
In apps/web/components/assistant-chat/overview.tsx around line 4, the import
uses MessageCircleIcon which doesn't exist in lucide-react; update the import to
MessageCircle and likewise scan and replace all other imports that append the
"Icon" suffix (e.g., XIcon → X, CalendarIcon → Calendar, HistoryIcon → History,
PlusIcon → Plus, etc.) across the listed files and any remaining matches; ensure
you update named imports only (remove the trailing "Icon" from the identifier),
run the project build and TypeScript type-check to confirm no remaining
Icon-suffixed imports remain.

@claude
Copy link

claude bot commented Aug 21, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude
Copy link

claude bot commented Aug 21, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude
Copy link

claude bot commented Aug 21, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/web/components/assistant-chat/messages.tsx (1)

64-73: Memo comparator bug: always returns false when both statuses are truthy.

The line if (prevProps.status && nextProps.status) return false; causes re-render whenever both statuses are set (strings are truthy), defeating memoization. Remove it.

Apply:

 export const Messages = memo(PureMessages, (prevProps, nextProps) => {
   if (prevProps.isArtifactVisible && nextProps.isArtifactVisible) return true;

   if (prevProps.status !== nextProps.status) return false;
-  if (prevProps.status && nextProps.status) return false;
   if (prevProps.messages.length !== nextProps.messages.length) return false;
   if (!equal(prevProps.messages, nextProps.messages)) return false;

   return true;
 });

Optional simplification (unchanged behavior after the fix):

-  if (prevProps.messages.length !== nextProps.messages.length) return false;
-  if (!equal(prevProps.messages, nextProps.messages)) return false;
+  if (!equal(prevProps.messages, nextProps.messages)) return false;
🧹 Nitpick comments (9)
apps/web/components/assistant-chat/messages.tsx (4)

32-35: Confirm intended padding override on ConversationContent.

ConversationContent provides base p-4; adding pt-0 results in pt-0 pr-4 pb-4 pl-4. If you intended to remove only top padding, this is correct. If you meant to remove all padding, use p-0 (and re-add sides as needed).

Example if you want zero top padding but keep sides/bottom explicit:

-<ConversationContent className="flex flex-col gap-6 pt-0 h-full">
+<ConversationContent className="flex flex-col gap-6 px-4 pb-4 pt-0 h-full">

Also verify that h-full here doesn’t introduce a nested scroll area alongside Conversation (which already handles overflow); if you see dual scrollbars, drop h-full.


36-44: Ensure keys are stable and unique for all ChatMessage variants.

key={message.id} is good as long as every ChatMessage (including ephemeral/tool messages) has a stable id. If any message can lack id, React will warn and diffing may degrade. Guarantee ids at creation time rather than falling back to array index here.

If there’s any risk of missing ids, consider enforcing it at the producer:

  • Validate ChatMessage includes id: string in its type.
  • Assign a uuid when enqueuing messages.

Optionally, to make intent explicit in the view (still assuming ids exist):

-          <PreviewMessage
-            key={message.id}
+          <PreviewMessage
+            key={String(message.id)}
             message={message}

Performance note: if conversations grow large, consider list virtualization (e.g., react-virtuoso) to avoid rendering the full history every update.


46-57: Improve accessibility of the “Thinking…” indicator.

Expose status changes to assistive tech using ARIA. This avoids silent state changes in a region with role="log".

Apply:

-            <Message from="assistant">
-              <MessageContent>
+            <Message from="assistant">
+              <MessageContent role="status" aria-live="polite" aria-busy={true}>
                 <div className="flex items-center gap-2 text-muted-foreground">
                   <Loader />
                   <span>Thinking...</span>
                 </div>
               </MessageContent>
             </Message>

59-60: Add an accessible name for the scroll-to-bottom control.

Helps screen-reader users discover the control.

-      <ConversationScrollButton />
+      <ConversationScrollButton aria-label="Scroll to latest" />
apps/web/components/SideNavWithTopNav.tsx (2)

20-25: Avoid transition-all and de-duplicate the 450px constant.

  • transition-all animates more than needed; prefer scoping to margin-right to reduce repaints.
  • 450px is duplicated here and in SidebarRight; drift risk if one changes.

Apply this diff to scope the transition and prepare for a shared width token:

-      className={cn(
-        "flex-1 transition-all duration-200 ease-linear",
-        isRightSidebarOpen && "lg:mr-[450px]",
-      )}
+      className={cn(
+        "flex-1 transition-[margin-right] duration-200 ease-linear",
+        isRightSidebarOpen && "lg:mr-[var(--right-sidebar-width,450px)]",
+      )}

Then, ensure the CSS variable is set globally (recommended) or by the sidebar (see my SidebarRight.tsx comment with a matching change).


29-35: Mobile safe-area padding: minor cleanup and intent.

  • md:pt-0 on an element that’s md:hidden is redundant.
  • If the intent is to prevent the bottom nav from colliding with the device home indicator, paddingBottom is typically more appropriate than paddingTop.

Option A (minimal): drop md:pt-0.

-        <div
-          className="md:hidden md:pt-0"
-          style={{ paddingTop: "calc(env(safe-area-inset-bottom) + 1rem)" }}
-        >
+        <div
+          className="md:hidden"
+          style={{ paddingBottom: "calc(env(safe-area-inset-bottom) + 1rem)" }}
+        >

Please confirm the desired visual behavior on iOS devices with a home indicator.

apps/web/components/SidebarRight.tsx (3)

18-24: A11y, width token, and interaction polish for the sliding panel.

  • Use semantic aside + aria-hidden when closed so SRs don’t read off-screen content.
  • Add inert when closed to prevent focus/interaction (progressive enhancement).
  • Replace hard-coded 450px with a CSS variable to stay in sync with ContentWrapper margin.

Apply this diff:

-    <div
-      className={cn(
-        "fixed right-0 top-0 z-10 h-screen w-[450px] border-l bg-background transition-transform duration-200 ease-linear",
-        "hidden lg:block",
-        isOpen ? "translate-x-0" : "translate-x-full",
-        className,
-      )}
-    >
+    <aside
+      aria-hidden={!isOpen}
+      // inert is widely supported; harmless where unsupported
+      {...(!isOpen ? { inert: '' } : {})}
+      className={cn(
+        "fixed right-0 top-0 z-10 h-screen w-[var(--right-sidebar-width,450px)] border-l bg-background transition-transform duration-200 ease-linear",
+        "hidden lg:block",
+        isOpen ? "translate-x-0" : "translate-x-full",
+        className,
+      )}
+      style={{
+        ["--right-sidebar-width" as any]: "450px",
+      }}
+      role="complementary"
+      data-state={isOpen ? "open" : "closed"}
+    >

Note: The CSS var matches the ContentWrapper suggestion, keeping width/margin in lockstep.


26-29: Keep-or-unmount decision for Chat when closed.

Leaving Chat mounted preserves state (good), but it also continues effects/timers while hidden. If you want to free resources when closed, conditionally render:

{isOpen ? <Chat /> : null}

Trade-offs depend on your usage (streaming, sockets, timers).


8-13: SidebarRight name prop usage verified

  • A search across apps/web/**/*.tsx reveals a single usage in apps/web/components/SideNavWithTopNav.tsx passing name="chat-sidebar"; no call sites omit the name prop.
  • Introducing a default value for name (e.g. "chat-sidebar") remains safe but isn’t strictly required for backward compatibility.

You may optionally apply the following refactor to allow callers to omit name in the future:

--- a/apps/web/components/SidebarRight.tsx
+++ b/apps/web/components/SidebarRight.tsx
@@ -5,12 +5,12 @@ interface SidebarRightProps {
   className?: string;
 }

-export function SidebarRight({
-  name,
-  className,
-}: {
-  name: string;
-  className?: string;
-}) {
+export function SidebarRight({
+  name = "chat-sidebar",
+  className,
+}: SidebarRightProps & { name?: string }) {
   // ...component implementation...
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • 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 4a7bafa and edd2e7b.

📒 Files selected for processing (4)
  • apps/web/components/SideNavWithTopNav.tsx (2 hunks)
  • apps/web/components/SidebarRight.tsx (1 hunks)
  • apps/web/components/ai-elements/message.tsx (1 hunks)
  • apps/web/components/assistant-chat/messages.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/components/ai-elements/message.tsx
🧰 Additional context used
📓 Path-based instructions (11)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
apps/web/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.tsx: Follow tailwindcss patterns with prettier-plugin-tailwindcss
Prefer functional components with hooks
Use shadcn/ui components when available
Ensure responsive design with mobile-first approach
Follow consistent naming conventions (PascalCase for components)
Use LoadingContent component for async data
Use result?.serverError with toastError and toastSuccess
Use LoadingContent component to handle loading and error states consistently
Pass loading, error, and children props to LoadingContent

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
apps/web/components/**/*.tsx

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Use React Hook Form with Zod validation for form handling

Use the LoadingContent component to handle loading and error states consistently in data-fetching components.

Use PascalCase for components (e.g. components/Button.tsx)

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
!{.cursor/rules/*.mdc}

📄 CodeRabbit inference engine (.cursor/rules/cursor-rules.mdc)

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.tsx: Use React Hook Form with Zod for validation
Validate form inputs before submission
Show validation errors inline next to form fields

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
!pages/_document.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{jsx,tsx}: Don't destructure props inside JSX components in Solid projects.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use Array index in keys.
Don't assign to React component props.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of ....
Don't insert comments as text nodes.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't use unnecessary fragments.
Don't pass children as props.
Use semantic elements instead of role attributes in JSX.

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{html,jsx,tsx}: Don't use or elements.
Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Only use the scope prop on elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with ARIA roles.
Use valid ARIA state and property values.
Use valid values for the autocomplete attribute on input eleme...

Files:

  • apps/web/components/SidebarRight.tsx
  • apps/web/components/SideNavWithTopNav.tsx
  • apps/web/components/assistant-chat/messages.tsx
apps/web/components/!(ui)/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

All other components are in components/

Files:

  • apps/web/components/assistant-chat/messages.tsx
🧬 Code graph analysis (3)
apps/web/components/SidebarRight.tsx (2)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/assistant-chat/chat.tsx (1)
  • Chat (28-122)
apps/web/components/SideNavWithTopNav.tsx (3)
apps/web/components/ui/sidebar.tsx (2)
  • useSidebar (821-821)
  • SidebarInset (807-807)
apps/web/utils/index.ts (1)
  • cn (4-6)
apps/web/components/NavBottom.tsx (1)
  • NavBottom (73-84)
apps/web/components/assistant-chat/messages.tsx (4)
apps/web/components/ai-elements/conversation.tsx (3)
  • Conversation (12-20)
  • ConversationContent (26-31)
  • ConversationScrollButton (35-62)
apps/web/components/assistant-chat/overview.tsx (1)
  • Overview (6-34)
apps/web/components/ai-elements/message.tsx (2)
  • Message (10-20)
  • MessageContent (24-40)
apps/web/components/ai-elements/loader.tsx (1)
  • Loader (86-96)
🔇 Additional comments (6)
apps/web/components/assistant-chat/messages.tsx (1)

1-13: Imports migration to ai-elements looks solid.

Path aliases are correct and local relative import for PreviewMessage is fine. No issues spotted.

apps/web/components/SideNavWithTopNav.tsx (3)

15-18: Good use of sidebar context inside a colocated wrapper.

ContentWrapper correctly consumes useSidebar and derives isRightSidebarOpen without non-null assertions. Placement under SidebarProvider (Line 57) ensures context is available.


26-28: Toaster moved into ContentWrapper; check onboarding route still shows toasts.

Because the onboarding early-return (Lines 51-55) bypasses ContentWrapper, Toaster won’t render there. If onboarding uses toasts (e.g., errors/success), consider rendering Toaster for that branch too.

If you want parity, wrap the onboarding return with Toaster:

// inside the onboarding branch
return (
  <>
    <Toaster closeButton richColors theme="light" visibleToasts={9} />
    {children}
  </>
);

62-62: Verify <SidebarRight> Usage Across the App

I ran the provided grep check and did not find any <SidebarRight> components in apps/web/**/*.tsx. To ensure the chat sidebar can actually open when triggered:

  • Confirm that you’ve implemented a <SidebarRight name="chat-sidebar">…</SidebarRight> wrapper in the appropriate layout or page component.
  • If the chat panel lives elsewhere (or is rendered via a different pattern), make sure it still uses the exact name="chat-sidebar" prop to match the provider and trigger.
  • Double-check that no other right-sidebar names are in use and that all <SidebarRight> instances (if any) accept a name prop.
apps/web/components/SidebarRight.tsx (2)

15-15: Boolean open state derived from context looks good.

Simple and resilient check via state.includes(name). No unnecessary memoization.


3-6: Lean import surface and cn usage are on point.

Swapping out Sidebar/SidebarContent for a fixed panel keeps this component self-contained. cn centralizes class merging.

@elie222 elie222 merged commit a89e795 into main Aug 21, 2025
13 of 14 checks passed
@elie222 elie222 deleted the feat/ai-elements-chat branch August 21, 2025 18:12
This was referenced Sep 11, 2025
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.

1 participant