Migrate to AI elements for chat conversation#707
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
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
TheDiffToggleButtonstill wraps its icon in aTooltip, which conflicts with the explicit comment inRuleActions(“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 viaaria-label(for screen readers) and atitle(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-hiddensince 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
sizeprops and Tailwind sizing; usingh-8 w-8keeps 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+ screensKeeping 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 typesThis 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 consumersProviding 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-pointerso it doesn’t fightdisabled:cursor-not-allowedstyles 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-bottomin apps/web/components/ai-elements/conversation.tsxstreamdownin apps/web/components/ai-elements/response.tsx@radix-ui/react-use-controllable-statein apps/web/components/ai-elements/reasoning.tsxreact-syntax-highlighterin 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-labelwhen rendering only an icon.- Guard against
onClickin...propsreplacing 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
ToolHeaderPropsas a bare object prevents consumers from passing standard trigger props (e.g.,disabled). Extend fromComponentProps<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 collisionsUsing a single
"input"key causes drafts from one conversation to appear in another. If that’s unintended, derive the key fromchatId. SinceuseLocalStorage'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
chatIdchanges, preferlocalStorage.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
MessageEditoris keyed withmessage.id. Inside a parts.map, that can lead to duplicate keys across siblings or unnecessary remounting when switching edit mode. Use the per-partkeyyou 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:titleprop on ReasoningTrigger is unusedYou accept
titlebut 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 flexibilityConfining
childrentostringlimits reuse.Responsesupports rich content; allowReactNode.-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_DELAYis fixed at 1000ms. ExposingautoCloseDelayMs?: numberonReasoningPropsimproves 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/maxHeightprops are unused; either apply them or remove themYou 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
useAutosizeTextAreahook respecting these limits.Also applies to: 68-85
164-175: A11y: Provide a default aria-label for icon-only submitConsider passing an
aria-labelwhen nochildrenare 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.
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/components/assistant-chat/overview.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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
LoadingContentcomponent 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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/package.jsonapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/web/components/ai-elements/actions.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/web/components/ai-elements/actions.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/package.jsonapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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.tsxapps/web/components/ai-elements/message.tsxapps/web/components/ai-elements/response.tsxapps/web/components/assistant-chat/tools.tsxapps/web/components/ai-elements/code-block.tsxapps/web/components/ai-elements/loader.tsxapps/web/components/ai-elements/reasoning.tsxapps/web/components/ai-elements/suggestion.tsxapps/web/components/assistant-chat/messages.tsxapps/web/components/ai-elements/tool.tsxapps/web/components/assistant-chat/message.tsxapps/web/components/assistant-chat/chat.tsxapps/web/components/ai-elements/conversation.tsxapps/web/components/ai-elements/prompt-input.tsxapps/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 statesApplied 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 consistentlyApplied 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 dataApplied 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 commentsApplied to files:
apps/web/components/ai-elements/reasoning.tsxapps/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 consistentApplied to files:
apps/web/components/assistant-chat/chat.tsxapps/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 areasApplied 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/contextApplied 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 handlingApplied 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` directiveApplied 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 primitivesGood 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 toAction(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 ofConversation.apps/web/components/assistant-chat/chat.tsx (1)
85-91: No changes needed:handleSubmitalready clears the input stateThe
handleSubmitcallback defined inChatProvider.tsx(lines 85–91) invokessetInput("")immediately after sending the message, so theinputstate is reset to an empty string. Consequently, in your form’sonSubmithandler you don’t need to add anothersetInput("")—it’s already handled upstream.
| const copyToClipboard = async () => { | ||
| if (typeof window === "undefined" || !navigator.clipboard.writeText) { | ||
| onError?.(new Error("Clipboard API not available")); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| "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> |
There was a problem hiding this comment.
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.
| "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.
| 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} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
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.
| 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.
| 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} | ||
| /> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
| (prevProps, nextProps) => prevProps.children === nextProps.children, | ||
| ); |
There was a problem hiding this comment.
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.
| (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.
| 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> | ||
| ); |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| <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> |
There was a problem hiding this comment.
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.
| 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); | ||
| } | ||
| }} |
There was a problem hiding this comment.
🛠️ 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".
| if ("error" in output) { | ||
| return ( | ||
| <MessageReasoning | ||
| key={key} | ||
| isLoading={isLoading} | ||
| reasoning={part.text} | ||
| <ErrorToolCard | ||
| key={toolCallId} | ||
| error={String(output.error)} | ||
| /> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ 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"; |
There was a problem hiding this comment.
💡 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=tsxLength 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.tsximport { CalendarIcon } from "lucide-react";⇒import { Calendar } from "lucide-react";
• apps/web/components/HeroVideoDialog.tsximport { Play, XIcon } from "lucide-react";⇒import { Play, X } from "lucide-react";
• apps/web/components/assistant-chat/overview.tsximport { MessageCircleIcon } from "lucide-react";⇒import { MessageCircle } from "lucide-react";
• apps/web/components/assistant-chat/chat.tsximport { HistoryIcon, PlusIcon } from "lucide-react";⇒import { History, Plus } from "lucide-react";
• …and all other files reporting*Iconimports (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 encountered an error —— View job I'll analyze this and get back to you. |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
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 thenameprop.- 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
namein 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.
📒 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.tsxapps/web/components/SideNavWithTopNav.tsxapps/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
Useresult?.serverErrorwithtoastErrorandtoastSuccess
UseLoadingContentcomponent to handle loading and error states consistently
Passloading,error, and children props toLoadingContent
Files:
apps/web/components/SidebarRight.tsxapps/web/components/SideNavWithTopNav.tsxapps/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
LoadingContentcomponent 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.tsxapps/web/components/SideNavWithTopNav.tsxapps/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.tsxapps/web/components/SideNavWithTopNav.tsxapps/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.tsxapps/web/components/SideNavWithTopNav.tsxapps/web/components/assistant-chat/messages.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsxapps/web/components/SideNavWithTopNav.tsxapps/web/components/assistant-chat/messages.tsx
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsxapps/web/components/SideNavWithTopNav.tsxapps/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.tsxapps/web/components/SideNavWithTopNav.tsxapps/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.tsxapps/web/components/SideNavWithTopNav.tsxapps/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.tsxapps/web/components/SideNavWithTopNav.tsxapps/web/components/assistant-chat/messages.tsxapps/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 AppI ran the provided grep check and did not find any
<SidebarRight>components inapps/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 anameprop.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.
Summary by CodeRabbit