Skip to content

Conversation

@saddlepaddle
Copy link
Collaborator

@saddlepaddle saddlepaddle commented Sep 17, 2025

  • WIP
  • WIP - not broken
  • ** WIP**

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Release
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes


Important

Improves SSE performance by optimizing chat message handling, updating models, and enhancing error management across multiple components and hooks.

  • Behavior:
    • Updates getModelFromType in stream.ts to use OPEN_AI_GPT_5 model.
    • Adds onFinish callback in streamResponse in route.ts to store final messages in the database.
    • Replaces useChatContext with useChat hook for better chat management.
  • Components:
    • Refactors OverlayChatInput and ChatInput components to use new chat handling logic.
    • Updates ChatMessages and StreamMessage components to handle streaming messages more efficiently.
    • Introduces ChatTabContent to manage chat tab state and interactions.
  • Error Handling:
    • Improves error parsing in ErrorMessage component to handle usage limits and other errors.
    • Adds errorHandler function in stream.ts for consistent error logging.
  • Database:
    • Modifies replaceConversationMessages in message.ts to replace messages in a transaction.
    • Normalizes message data before database operations.
  • Misc:
    • Removes useChatContext and related logic, consolidating chat handling in useChat hook.
    • Adjusts auto-scroll behavior in use-auto-scroll.tsx for better user experience.

This description was created by Ellipsis for dd0f12d. You can customize this summary. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • New Features

    • Conversation-based Chat tab with post-stream message persistence and refreshed initialization.
    • Streamed reply view with persistent indicator and Stop control.
    • One-click "Fix errors" action and smarter suggestion generation after replies.
  • Improvements

    • Clearer send-status toasts (sending / sent / failed).
    • Enhanced error UI with usage-limit prompt and “Get more messages”.
    • Buttons (Undo/Redo, chat controls) disable while chat is streaming.
    • Polished streamed “Reasoning” visuals.
  • Documentation

    • Assistant now suggests switching to EDIT MODE for code changes.
    • Branch tool guidance clarifies using branch ID (not name).

@vercel
Copy link

vercel bot commented Sep 17, 2025

@saddlepaddle is attempting to deploy a commit to the Onlook Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Sep 17, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors chat from a provider/context model to a hook-driven, prop-based architecture; introduces useChat, centralizes chat UI in ChatTabContent, replaces client-side message storage with server replaceConversationMessages persistence, rewires editor chat APIs and tool handling, and removes legacy suggestion/error managers.

Changes

Cohort / File(s) Summary
Streaming persistence & server API
apps/web/client/src/app/api/chat/route.ts, apps/web/client/src/server/api/routers/chat/message.ts
Wires onFinish to persist final streamed messages and adds replaceConversationMessages TRPC endpoint; stops persisting parts in normalize/update.
New hook: useChat & utils
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx, .../use-chat/utils.ts
Adds useChat hook coordinating AI transport, tool-calls, streaming, suggestions, and commit attachment; exports SendMessage, EditMessage, ProcessMessage; utilities for preparing messages, creating user messages, and attaching git checkpoints.
Provider removal
apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx (deleted), apps/web/client/src/app/project/[id]/providers.tsx
Deletes old ChatProvider / useChatContext; removes provider wrapper from ProjectProviders.
Chat tab composition
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx, .../chat-tab/chat-tab-content/index.tsx, apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
ChatTab now fetches initialMessages via TRPC and delegates to new ChatTabContent; RightPanel passes conversationId and projectId and only renders CHAT when a conversation exists.
Input, suggestions & error UI
.../chat-tab/chat-input/index.tsx, .../chat-tab/suggestions.tsx, .../chat-tab/error.tsx
Components become prop-driven (messages, suggestions, isStreaming, onStop, onSendMessage); disabled logic uses isStreaming; sends wrapped with toast.promise; error UI accepts error prop and parses usage.
Message list & message components
.../chat-tab/chat-messages/index.tsx, .../user-message.tsx, .../stream-message.tsx, .../error-message.tsx, .../message-content/index.tsx
ChatMessages accepts props; separates streamed vs base messages; UserMessage and StreamMessage now receive explicit props and editing uses onEditMessage; ErrorMessage parses errors from prop; message-content tracks isLastPart.
Overlay, controls, top bar, history
.../canvas/overlay/elements/buttons/chat.tsx, .../buttons/index.tsx, .../chat-tab/controls.tsx, .../top-bar/index.tsx, .../chat-tab/history.tsx
Removes useChatContext usage; replaces isWaiting with editorEngine.chat.isStreaming; unifies send calls to editorEngine.chat.sendMessage; adjusts disabled logic and active conversation checks.
Editor chat store & conversation changes
apps/web/client/src/components/store/editor/chat/index.ts, .../chat/context.ts, .../chat/conversation.ts, .../chat/error.ts (deleted), .../chat/suggestions.ts (deleted), .../chat/message.ts (deleted)
ChatManager simplified to action-driven API (init, setIsStreaming, setChatActions, sendMessage) and adds isStreaming; context gets getters/setters, addContexts, getContextByChatType, getLatestContext; conversation store now uses CurrentConversation with messageCount, adds setConversationLength and generateTitle; removes per-message storage, suggestion manager, error manager, and message factory.
Tooling and handlers
apps/web/client/src/components/tools/tools.ts, apps/web/client/src/components/tools/handlers/edit.ts
handleToolCall becomes async and addToolResult callback returns Promise<void>; fuzzy-edit tool now references conversation.current?.id.
Project start flow
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
Replaces useChatContext flows with editorEngine.chat.* APIs; builds contexts, selects conversation, and calls editorEngine.chat.sendMessage for CREATE flow.
Misc UI & small changes
.../chat-input/context-wheel.tsx, packages/ui/src/components/chat/hooks/use-auto-scroll.tsx, apps/web/client/src/components/store/editor/version/index.ts
Token estimation from messages disabled (messages replaced with empty array), removed hidden comment, and minor TS signature tweak in createCommit.
Prompts & schema text
packages/ai/src/prompt/ask.ts, packages/ai/src/tools/tools/branch.ts
Appends guidance to ASK system prompt to suggest toggling to EDIT MODE; clarifies branch ID schema description.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CI as ChatInput
  participant CT as ChatTabContent / useChat
  participant AI as AI Transport (/api/chat)
  participant TL as Tool Handler
  participant API as TRPC Chat API
  participant ED as EditorEngine

  U->>CI: Type + Submit
  CI->>CT: onSendMessage(content, type)
  CT->>CT: Append local user message (buffer)
  CT->>AI: stream({ conversationId, projectId, chatType, context })
  AI-->>CT: streaming parts / tokens
  alt Tool call emitted
    AI-->>CT: toolCall(payload)
    CT->>TL: handleToolCall(toolCall, editorEngine, addToolResult)
    TL-->>CT: addToolResult(result)
  end
  AI-->>CT: onFinish(finalMessages, metadata)
  CT->>API: replaceConversationMessages({ conversationId, messages })
  API-->>CT: ok
  CT->>API: generate suggestions (last msgs)
  API-->>CT: suggestions
  CT->>ED: setConversationLength / set isStreaming=false
  CT-->>CI: update UI (messages, suggestions)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A nibble of bytes, a hop through the stream,
I queue little messages, neat as a beam.
New hooks in my burrow, I stash what I write—
Replace all the threads at the end of the night.
When tools start to chitter, I pause—then I send. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR includes a helpful high-level "IMPORTANT" summary of changes but the repository's required description template is not completed: Related Issues are not listed, the "Type of Change" checkboxes are not selected, Testing steps are empty, Screenshots and Additional Notes are missing, and the top-level description is marked "WIP", so required template fields and concrete verification instructions are absent. Please complete the repository template before merging: provide a concise Description of behavioral and migration impacts (mention DB replaceConversationMessages and any compatibility concerns), list related issue numbers (or "none"), mark the appropriate Type of Change box(es), add explicit Testing steps (how to reproduce streaming behavior, manual verification, and any automated tests run or needed, plus performance validation), attach screenshots if UI is affected, and either remove the "WIP" tag or state what remains to be done and how reviewers should test the changeset.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "improve sse performance" is concise and accurately reflects the primary intent described in the PR (optimizing SSE/chat streaming, model selection, and message persistence), so it summarizes the main goal of the changeset; however, the changeset also contains broad API and component refactors (new useChat hook, message persistence via replaceConversationMessages, multiple component signature changes) that the title does not surface, making it somewhat generic for reviewers scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@saddlepaddle saddlepaddle force-pushed the improve-sse-performance branch from 9d806a5 to e4ab69f Compare September 17, 2025 04:08
@Kitenite Kitenite marked this pull request as ready for review September 18, 2025 02:51
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (3)
apps/web/client/src/components/store/editor/version/index.ts (1)

53-64: Fix concurrency guard: isSaving never gates work.

this.isSaving is only set to true after the commit succeeds, so concurrent calls are not prevented. Set it to true immediately after the initial check and remove the late assignment.

Apply:

         try {
             if (this.isSaving) {
                 if (showToast) {
                     toast.error('Backup already in progress');
                 }
                 return {
                     success: false,
                     commit: null,
                     errorReason: CreateCommitFailureReason.COMMIT_IN_PROGRESS,
                 };
             }
+            // Gate concurrent saves
+            this.isSaving = true;

@@
-            this.isSaving = true;

Also applies to: 122-148

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)

148-149: Internationalize user-facing strings.

Hardcoded strings (“Failed to send message…”, “Ask a question about your project…”, “No active frame…”, “Failed to capture screenshot…”, “Stop response”) should use next-intl per repo guidelines.

Also applies to: 155-157, 233-235, 258-259, 271-272, 418-418

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)

41-48: Replace hardcoded strings with next-intl and fix invalid Tailwind class text-red.

  • Move the user-facing strings in apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (lines 41–48 and 55–58) into next-intl messages and use useTranslations(); add keys in apps/web/client/messages/*.json.
  • Replace the text-red class with a valid utility or semantic token (e.g. text-red-500, text-destructive, or text-foreground). No .text-red CSS or Tailwind mapping was found in packages/ui/tailwind.config.ts or packages/ui/src/globals.css.
🧹 Nitpick comments (39)
apps/web/client/src/components/store/editor/chat/context.ts (2)

20-20: Make editorEngine immutable.
Prevent accidental reassignment by marking it readonly.

Apply this diff:

-    constructor(private editorEngine: EditorEngine) {
+    constructor(private readonly editorEngine: EditorEngine) {

44-47: Remove redundant await.
Returning the Promise is cleaner; no behavior change.

     async getLatestContext(): Promise<MessageContext[]> {
         const context = await this.getChatContext();
-        return await this.getRefreshedContext(context);
+        return this.getRefreshedContext(context);
     }
apps/web/client/src/components/tools/tools.ts (5)

186-191: Tighten types: prefer unknown over any in API surface

Use unknown for output and the addToolResult contract to comply with “no any” guideline and improve type safety.

-export async function handleToolCall(toolCall: ToolCall<string, unknown>, editorEngine: EditorEngine, addToolResult: (toolResult: { tool: string, toolCallId: string, output: any }) => Promise<void>) {
+export async function handleToolCall(
+    toolCall: ToolCall<string, unknown>,
+    editorEngine: EditorEngine,
+    addToolResult: (toolResult: { tool: string; toolCallId: string; output: unknown }) => Promise<void>
+) {
     const toolName = toolCall.toolName;
     const currentChatMode = editorEngine.state.chatMode;
     const availableTools = getToolSetFromType(currentChatMode);
-    let output: any = null;
+    let output: unknown = null;

202-208: Validate tool input against its Zod schema before invoking handler

You store inputSchema but don’t use it. Parse inputs to fail fast with clear errors and avoid handler-level runtime crashes.

         const clientTool = TOOL_HANDLERS[toolName];

         if (!clientTool) {
             throw new Error(`Unknown tool call: ${toolName}`);
         }
-        output = await clientTool.handler(toolCall.input, editorEngine);
+        const parsed = clientTool.inputSchema.safeParse(toolCall.input);
+        if (!parsed.success) {
+            throw new Error(`Invalid input for tool "${toolName}": ${parsed.error.message}`);
+        }
+        output = await clientTool.handler(parsed.data, editorEngine);

209-209: Harden error serialization and surface diagnostics

String concatenation with a non-Error yields “[object Object]”. Serialize safely and log.

-    } catch (error) {
-        output = 'error handling tool call ' + error;
+    } catch (error) {
+        const message = error instanceof Error ? error.message : String(error);
+        output = `error handling tool call: ${message}`;
+        console.error('handleToolCall error', { tool: toolName, error });

211-215: Don’t silently drop addToolResult rejections

void marks intent but a rejection remains unhandled. Either await or explicitly catch.

-        void addToolResult({
+        void addToolResult({
             tool: toolName,
             toolCallId: toolCall.toolCallId,
             output: output,
-        });
+        }).catch((err) => {
+            console.error('addToolResult failed', err);
+        });

64-68: Replace lingering any with Zod-aware generics

This interface leaks any in multiple places. Use ZodTypeAny + inference to keep handlers precisely typed.

-interface ClientToolMap extends Record<string, {
-    name: string;
-    inputSchema: z.ZodObject<any>;
-    handler: (args: any, editorEngine: EditorEngine) => Promise<any>;
-}> { }
+type ToolDef<T extends z.ZodTypeAny, R = unknown> = {
+    name: string;
+    inputSchema: T;
+    handler: (args: z.infer<T>, editorEngine: EditorEngine) => Promise<R>;
+};
+type ClientToolMap = Record<string, ToolDef<z.ZodTypeAny, unknown>>;
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (5)

71-72: Avoid MobX store objects in effect deps to prevent spurious reruns.

Depending on editorEngine.chat.conversation (an object) can cause unnecessary reruns or loops. The effect only needs conversations.

-    }, [editorEngine.chat.conversation, conversations]);
+    }, [conversations]);

79-79: Non‑awaited call is okay; consider unmount/dup guards.

void resumeCreate(...) is fine to fire‑and‑forget. To avoid side‑effects after unmount and double toasts, optionally gate with an isMounted ref or an AbortController.


89-99: Harden image context mapping; avoid hard‑coded display text.

  • Add a safe fallback for mimeType.
  • "user image" may surface in UI; per guidelines avoid hardcoded user‑facing text—route through next‑intl or centralize as a message key if this is presented to users.
             const imageContexts: ImageMessageContext[] = creationData.context
                 .filter((context) => context.type === CreateRequestContextType.IMAGE)
                 .map((context) => ({
                     type: MessageContextType.IMAGE,
                     content: context.content,
-                    mimeType: context.mimeType,
+                    mimeType: context.mimeType ?? 'image/png',
                     displayName: 'user image',
                 }));

Would you confirm whether displayName is ever rendered to users? If yes, we should internationalize it.


103-106: Normalize the prompt and skip empty fragments.

Trimming and filtering prevents stray newlines and empty strings.

-            const prompt = creationData.context
-                .filter((context) => context.type === CreateRequestContextType.PROMPT)
-                .map((context) => context.content)
-                .join('\n');
+            const prompt = creationData.context
+                .filter((c) => c.type === CreateRequestContextType.PROMPT)
+                .map((c) => c.content?.trim())
+                .filter(Boolean)
+                .join('\n\n');

108-117: Drop redundant fetch/select; reuse the already‑selected conversation.

applyConversations already selects (or starts) the first conversation — re‑fetching + re‑selecting adds latency and can race with that effect. Replace the getConversations/selectConversation block with a direct use of the conversation manager's current conversation and then send the message:

const conversation = editorEngine.chat.conversation.current;
if (!conversation) throw new Error('No conversation found');
await editorEngine.chat.sendMessage(prompt, ChatType.CREATE);

apps/web/client/src/components/store/editor/chat/conversation.ts (2)

44-51: Avoid replacing observable object for a single-field update.

Mutate messageCount to preserve referential stability and reduce unnecessary reactions.

Apply:

-    if (this.current) {
-        this.current = {
-            ...this.current,
-            messageCount: length,
-        };
-    }
+    if (this.current) {
+        this.current.messageCount = length;
+    }

Optional: rename to setMessageCount for clarity.


85-87: Resetting messageCount to 0 on select may cause UI flicker.

Consider tracking a separate isLoadingMessages flag, or preserve the last known count until data arrives.

apps/web/client/src/app/api/chat/route.ts (2)

74-76: Use a broader‑compat fallback instead of findLast.

Array.prototype.findLast requires newer runtimes. Prefer a reverse scan to avoid Node version pitfalls.

-        const lastUserMessage = messages.findLast((message) => message.role === 'user');
+        const lastUserMessage =
+            [...messages].reverse().find((message) => message.role === 'user');

139-150: Add guardrails to onFinish persistence; 'tool' role is not supported

Wrap the persistence call in try/catch, cap it with a short timeout, and log errors so persistence failures don’t break or slow the completed stream. The repo’s DB/schema and routers use only 'user'|'assistant'|'system' (no 'tool'), so filtering out tool messages is correct — decide separately whether to persist 'system' messages.

-                onFinish: async ({ messages: finalMessages }) => {
-                    const messagesToStore = finalMessages
-                        .filter(msg =>
-                            (msg.role === 'user' || msg.role === 'assistant')
-                        )
-                        .map(msg => toDbMessage(msg, conversationId));
-
-                    await api.chat.message.replaceConversationMessages({
-                        conversationId,
-                        messages: messagesToStore,
-                    });
-                },
+                onFinish: async ({ messages: finalMessages }) => {
+                    try {
+                        const messagesToStore = finalMessages
+                            .filter((msg) => msg.role === 'user' || msg.role === 'assistant')
+                            .map((msg) => toDbMessage(msg as ChatMessage, conversationId));
+
+                        // Avoid long tail: cap persistence time
+                        const persist = api.chat.message.replaceConversationMessages({
+                            conversationId,
+                            messages: messagesToStore,
+                        });
+                        const withTimeout = Promise.race([
+                            persist,
+                            new Promise((_, rej) =>
+                                setTimeout(() => rej(new Error('persist timeout')), 5000),
+                            ),
+                        ]);
+                        await withTimeout;
+                    } catch (e) {
+                        // Log but don’t fail the request after streaming completed
+                        console.error('onFinish persistence error', e);
+                    }
+                },
apps/web/client/src/app/api/chat/helpers/stream.ts (1)

9-24: Collapse duplicate branches and make model selection configurable.

All switch branches call initModel with OPENROUTER_MODELS.OPEN_AI_GPT_5 — replace the switch with a single initModel call, keep the chatType parameter for future per-type mapping, and read the model id from config/env (fallback to OPENROUTER_MODELS.OPEN_AI_GPT_5). File: apps/web/client/src/app/api/chat/helpers/stream.ts — getModelFromType.

apps/web/client/src/components/store/editor/version/index.ts (2)

134-141: Avoid logging unsanitized commit messages to analytics.

Use the sanitized message in the failure event as well to prevent leaking raw content.

-            this.editorEngine.posthog.capture('versions_create_commit_failed', {
-                message,
+            const safeMessage = sanitizeCommitMessage(message);
+            this.editorEngine.posthog.capture('versions_create_commit_failed', {
+                message: safeMessage,
                 error: error instanceof Error ? error.message : 'Unknown error',
             });

56-57: Localize user-facing toast strings per app guidelines.

These toasts live under apps/web/client/src and should use next-intl instead of hardcoded English.

If localization from store code is awkward, consider routing toast invocation through a UI layer or a small i18nToast helper that accepts translation keys.

Also applies to: 74-75, 88-89, 105-106, 118-121, 135-136, 215-216, 220-231, 267-269, 294-299, 311-323

apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx (1)

31-34: Localize toast messages.

Follow the “Avoid hardcoded user-facing text” rule; use next-intl.

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx (2)

98-111: Use stable keys for suggestion buttons.

key={suggestion.title} can collide; prefer a stable unique key (id if available, or index as fallback).

-                            key={suggestion.title}
+                            key={`${index}-${suggestion.title}`}

98-136: Small a11y improvement: label suggestion buttons.

Add an aria-label to improve screen reader output.

-                        <motion.button
+                        <motion.button
+                            aria-label={`Suggestion: ${suggestion.title}`}
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (2)

19-38: Localize the “Thinking …” text.

Avoid hardcoded strings; use next-intl.

+import { useTranslations } from 'next-intl';
+import { transKeys } from '@/i18n/keys';
@@
 export const StreamMessage = ({
@@
 }) => {
+    const t = useTranslations();
@@
             <div className="flex w-full h-full flex-row items-center gap-2 px-4 my-2 text-small content-start text-foreground-secondary">
                 <Icons.LoadingSpinner className="animate-spin" />
-                <p>Thinking ...</p>
+                <p>{t(transKeys.editor.chat.thinking)}</p>
             </div>

14-17: Drop unnecessary useMemo.

const isAssistantStreamMessage = streamMessage?.role === 'assistant' is sufficient.

apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx (2)

75-77: Localize “Code” tab label.

Replace the hardcoded string with a translation key.

-                                Code
+                                {t(transKeys.editor.panels.edit.tabs.dev.name)}

84-90: Empty CHAT tab when no conversation.

UI shows nothing if currentConversation is null. Consider a lightweight empty state (localized) or a CTA to start a conversation.

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (3)

83-91: Localize all user-facing strings.

Toasts, buttons, and tooltips should use next-intl per repo rules.

Also applies to: 95-103, 143-149, 169-171, 185-187, 203-206, 257-258


218-221: Avoid nanoid as list keys.

Random keys cause unnecessary remounts; use a stable identifier from context (or index as a last resort).

-                            {message.metadata?.context?.map((context) => (
-                                <SentContextPill key={nanoid()} context={context} />
+                            {message.metadata?.context?.map((context, idx) => (
+                                <SentContextPill key={context.id ?? idx} context={context} />
                             ))}

78-82: Preserve edit UI on failure.

handleSubmit closes edit mode before the async call. Only close on success and allow retry on failure. Also, return the promise from sendMessage to make it awaitable.

-    const handleSubmit = async () => {
-        setIsEditing(false);
-        await sendMessage(editValue);
-    };
+    const handleSubmit = async () => {
+        try {
+            await sendMessage(editValue);
+            setIsEditing(false);
+        } catch {
+            // keep editing; toast already shown
+        }
+    };
@@
-    const sendMessage = async (newContent: string) => {
-        toast.promise(
+    const sendMessage = async (newContent: string) => {
+        return toast.promise(
             onEditMessage(message.id, newContent, ChatType.EDIT),
             {
                 loading: 'Editing message...',
                 success: 'Message resubmitted successfully',
                 error: 'Failed to resubmit message',
             }
-        )
+        );
     };

Also applies to: 94-103

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx (2)

10-13: enabled: !!conversationId is redundant with a required string prop.

Since conversationId: string is non-optional, !!conversationId is always true (unless empty string is passed). Either make conversationId optional in props or drop the enabled guard.


15-17: Avoid returning null during load; render a lightweight placeholder.

Returning null collapses layout and can cause focus jumps. Consider a skeleton or a 0-height placeholder to stabilize the panel.

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)

18-35: Be defensive when parsing error.message JSON.

Parsing error.message is brittle. Consider checking error.cause or a structured field first, and only parse when it looks like JSON (e.g., starts with {). Current try/catch is OK, but a small guard reduces noise.

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)

67-76: Fix stale closure in focus handler (FOCUS_CHAT_INPUT_EVENT).

focusHandler reads isStreaming from initial render due to an empty dependency array. Use a ref to keep the latest value and avoid focusing while streaming.

   const focusInput = () => {
     requestAnimationFrame(() => {
       textareaRef.current?.focus();
     });
   };

+  // Keep latest streaming state for event handlers registered once
+  const isStreamingRef = useRef(isStreaming);
+  useEffect(() => {
+    isStreamingRef.current = isStreaming;
+  }, [isStreaming]);
+
   useEffect(() => {
     const focusHandler = () => {
-      if (textareaRef.current && !isStreaming) {
+      if (textareaRef.current && !isStreamingRef.current) {
         focusInput();
       }
     };
 
     window.addEventListener(FOCUS_CHAT_INPUT_EVENT, focusHandler);
     return () => window.removeEventListener(FOCUS_CHAT_INPUT_EVENT, focusHandler);
   }, []);
 
-  const disabled = isStreaming
+  const disabled = isStreaming;

Also applies to: 95-95

apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)

56-61: Ensure isExecutingToolCall resets even on unexpected failures.

Prefer .finally to guarantee reset.

-            onToolCall: async (toolCall) => {
-                setIsExecutingToolCall(true);
-                void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => {
-                    setIsExecutingToolCall(false);
-                });
-            },
+            onToolCall: async (toolCall) => {
+                setIsExecutingToolCall(true);
+                void handleToolCall(toolCall.toolCall, editorEngine, addToolResult)
+                    .finally(() => setIsExecutingToolCall(false));
+            },
apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (1)

6-16: Preserve text boundaries when flattening parts.

Joining with '' can smash words. Filter non-text parts and join with '\n' (or a space) to retain readability.

 export const prepareMessagesForSuggestions = (messages: ChatMessage[]) => {
-    return messages.slice(-5).map((message) => ({
+    return messages.slice(-5).map((message) => ({
         role: message.role,
-        content: message.parts.map((p) => {
-            if (p.type === 'text') {
-                return p.text;
-            }
-            return '';
-        }).join(''),
+        content: message.parts
+            .filter((p) => p.type === 'text')
+            .map((p) => p.text)
+            .join('\n'),
     }));
 };
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (2)

23-44: Prefer stable keys; drop index from key.

Using both id and index can cause unnecessary remounts.

-        return <div key={`message-${message.id}-${index}`}>{messageNode}</div>;
+        return <div key={message.id}>{messageNode}</div>;

59-63: Optimize ChatMessageList contentKey and localize StreamMessage text

  • Replace the expensive contentKey in apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx — current: contentKey={${messages.map((m) => m.id).join('|')}${isStreaming ? |${messages?.[messages.length - 1]?.id ?? ''} : ''}}. Use a lighter stable key (example): contentKey={${messages.length ? messages[messages.length - 1].id : 'empty'}|${messages.length}|${isStreaming ? '1' : '0'}} to avoid an O(n) map/join on every render while preserving change detection.

  • Localize the hardcoded "Thinking ..." in apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx — import useTranslations and transKeys and replace <p>Thinking ...</p> with a translation call (e.g. <p>{t(transKeys.editor.panels.edit.tabs.chat.streaming)}</p>) and add the key to locale files.

apps/web/client/src/components/store/editor/chat/index.ts (2)

13-16: Make chatSendMessage non-observable.

It’s a callback, not state. Prevent MobX from tracking it.

-        makeAutoObservable(this);
+        makeAutoObservable(this, { chatSendMessage: false });

39-49: Return the ChatMessage (or its id) from ChatManager.sendMessage

Change the signature to async sendMessage(content: string, type: ChatType): Promise<ChatMessage> and return the result of this.chatSendMessage(content, type) instead of discarding it.

Location: apps/web/client/src/components/store/editor/chat/index.ts (sendMessage). Call sites observed: apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (awaits), apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx (void call), apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (setChatActions wiring).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3932f9e and dd0f12d.

📒 Files selected for processing (34)
  • apps/web/client/src/app/api/chat/helpers/stream.ts (1 hunks)
  • apps/web/client/src/app/api/chat/route.ts (2 hunks)
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (14 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (2 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (5 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (6 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx (3 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx (2 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx (3 hunks)
  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx (0 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (1 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3 hunks)
  • apps/web/client/src/app/project/[id]/providers.tsx (1 hunks)
  • apps/web/client/src/components/store/editor/chat/context.ts (3 hunks)
  • apps/web/client/src/components/store/editor/chat/conversation.ts (4 hunks)
  • apps/web/client/src/components/store/editor/chat/error.ts (0 hunks)
  • apps/web/client/src/components/store/editor/chat/helpers.ts (0 hunks)
  • apps/web/client/src/components/store/editor/chat/index.ts (2 hunks)
  • apps/web/client/src/components/store/editor/chat/message.ts (0 hunks)
  • apps/web/client/src/components/store/editor/chat/suggestions.ts (0 hunks)
  • apps/web/client/src/components/store/editor/version/index.ts (1 hunks)
  • apps/web/client/src/components/tools/tools.ts (1 hunks)
  • apps/web/client/src/server/api/routers/chat/message.ts (1 hunks)
  • packages/ui/src/components/chat/hooks/use-auto-scroll.tsx (0 hunks)
💤 Files with no reviewable changes (6)
  • apps/web/client/src/components/store/editor/chat/suggestions.ts
  • packages/ui/src/components/chat/hooks/use-auto-scroll.tsx
  • apps/web/client/src/components/store/editor/chat/message.ts
  • apps/web/client/src/components/store/editor/chat/helpers.ts
  • apps/web/client/src/components/store/editor/chat/error.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx
🧰 Additional context used
📓 Path-based instructions (7)
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx
  • apps/web/client/src/app/project/[id]/providers.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx
  • apps/web/client/src/app/api/chat/route.ts
  • apps/web/client/src/app/project/[id]/providers.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts
  • apps/web/client/src/server/api/routers/chat/message.ts
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
  • apps/web/client/src/components/store/editor/version/index.ts
  • apps/web/client/src/components/tools/tools.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/components/store/editor/chat/index.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx
  • apps/web/client/src/app/project/[id]/providers.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx
  • apps/web/client/src/app/api/chat/route.ts
  • apps/web/client/src/app/project/[id]/providers.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts
  • apps/web/client/src/server/api/routers/chat/message.ts
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
  • apps/web/client/src/components/store/editor/version/index.ts
  • apps/web/client/src/components/tools/tools.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/components/store/editor/chat/index.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx
  • apps/web/client/src/app/api/chat/route.ts
  • apps/web/client/src/app/project/[id]/providers.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx
  • apps/web/client/src/app/api/chat/route.ts
  • apps/web/client/src/app/project/[id]/providers.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/controls.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/api/chat/helpers/stream.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts
  • apps/web/client/src/server/api/routers/chat/message.ts
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
  • apps/web/client/src/components/store/editor/version/index.ts
  • apps/web/client/src/components/tools/tools.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/components/store/editor/chat/index.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
apps/web/client/src/server/api/routers/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under apps/web/client/src/server/api/routers/**
Use publicProcedure/protectedProcedure from apps/web/client/src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization in tRPC procedures

apps/web/client/src/server/api/routers/**/*.ts: Place tRPC routers under src/server/api/routers/**
Use publicProcedure/protectedProcedure from src/server/api/trpc.ts and validate inputs with Zod
Return plain objects/arrays; rely on SuperJSON for serialization

Files:

  • apps/web/client/src/server/api/routers/chat/message.ts
🧠 Learnings (3)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/**/*.tsx : observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx
🧬 Code graph analysis (20)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (17-17)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • useChat (36-228)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (1)
  • ChatMessages (23-65)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx (1)
  • ErrorSection (19-118)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)
  • ChatInput (33-434)
apps/web/client/src/app/api/chat/route.ts (2)
packages/db/src/mappers/chat/message.ts (1)
  • toDbMessage (17-34)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (6)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (17-17)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
packages/models/src/chat/suggestion.ts (1)
  • ChatSuggestion (9-12)
apps/web/client/src/components/tools/tools.ts (1)
  • handleToolCall (186-218)
apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (3)
  • getUserChatMessageFromString (18-35)
  • prepareMessagesForSuggestions (6-16)
  • attachCommitToUserMessage (38-68)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (5)
packages/db/src/schema/chat/message.ts (1)
  • messages (11-27)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (17-17)
packages/models/src/chat/message/context.ts (1)
  • MessageContext (53-59)
packages/git/src/git.ts (1)
  • GitCommit (19-25)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/server/api/routers/chat/message.ts (2)
apps/web/client/src/server/api/trpc.ts (1)
  • protectedProcedure (130-149)
packages/db/src/schema/chat/message.ts (2)
  • messageInsertSchema (29-29)
  • messages (11-27)
apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/chat.tsx (1)
apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/helpers.ts (1)
  • DEFAULT_INPUT_STATE (8-13)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (2)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
  • applyConversations (20-28)
packages/models/src/chat/message/context.ts (2)
  • MessageContext (53-59)
  • ImageMessageContext (38-41)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (2)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (17-17)
packages/db/src/schema/chat/message.ts (1)
  • messages (11-27)
apps/web/client/src/components/tools/tools.ts (3)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
packages/ai/src/tools/toolset.ts (1)
  • getToolSetFromType (69-71)
packages/ui/src/components/sonner.tsx (1)
  • toast (19-19)
apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx (1)
  • ChatTab (9-28)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx (2)
packages/models/src/chat/suggestion.ts (1)
  • ChatSuggestion (9-12)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (1)
  • MessageContext (53-59)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (2)
apps/web/client/src/components/store/state/index.ts (1)
  • useStateManager (6-6)
packages/models/src/chat/response.ts (1)
  • Usage (1-5)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx (2)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-tab-content/index.tsx (1)
  • ChatTabContent (13-44)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (17-17)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • EditMessage (19-23)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
  • UserMessage (29-264)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
  • StreamMessage (6-41)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
  • ErrorMessage (11-63)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (17-17)
packages/models/src/chat/suggestion.ts (1)
  • ChatSuggestion (9-12)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • SendMessage (18-18)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
apps/web/client/src/components/store/editor/chat/index.ts (1)
  • sendMessage (43-49)
apps/web/client/src/components/store/editor/chat/index.ts (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
  • ConversationManager (11-128)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • SendMessage (18-18)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/error.tsx (3)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • SendMessage (18-18)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
packages/ui/src/components/sonner.tsx (1)
  • toast (19-19)
apps/web/client/src/components/store/editor/chat/conversation.ts (3)
packages/models/src/chat/conversation/index.ts (1)
  • ChatConversation (3-12)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (4)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • EditMessage (19-23)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (17-17)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
packages/ui/src/components/sonner.tsx (1)
  • toast (19-19)

Comment on lines +25 to 33
const sendFixError = () => {
toast.promise(
onSendMessage('How can I resolve these errors? If you propose a fix, please make it concise.', ChatType.FIX),
{
error: 'Failed to send fix error message. Please try again.',
}
)
};

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Externalize user-facing strings (next-intl) and add toast states.

Several strings are hardcoded (“How can I resolve…”, “Failed to send…”, “Error/Errors”, “You have … errors”, “Fix”). Per guidelines, move them to next-intl. Also, provide loading/success messages to toast.promise for clearer UX.

Also applies to: 65-71, 83-85

Comment on lines 78 to 81
conversation.id ===
editorEngine.chat.conversation.current?.conversation.id &&
editorEngine.chat.conversation.current?.id &&
'bg-background-onlook text-primary font-semibold',
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Correct: comparison now targets current?.id.

This matches the new shape and fixes the highlight check. Consider sanity‑checking for any remaining references to current?.conversation.id.


🏁 Script executed:

#!/bin/bash
# Find any remaining usages of the old shape `.conversation.current?.conversation.id`
rg -nP --type=ts --type=tsx -C2 '\.conversation\.current\?\.\s*conversation\b'

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Searching repo for remaining usages of the old `.conversation.current?.conversation.id` shape..."

PATTERNS=(
  '\.conversation\.current\?\.\s*conversation\b'
  '\.conversation\.current\?\.\s*conversation\s*\.\s*id\b'
  '\.conversation\.current\?\.\s*id\b'
  '\.conversation\.current\.\s*conversation\b'
  '\.conversation\.current\.\s*conversation\s*\.\s*id\b'
  '\bcurrent\?\.\s*conversation\b'
  '\bcurrent\?\.\s*conversation\s*\.\s*id\b'
  'editorEngine\.chat\.conversation\.current\?'
)

for p in "${PATTERNS[@]}"; do
  echo "=== PATTERN: $p ==="
  rg -n -C2 --hidden -S "$p" -g '**/*.ts' -g '**/*.tsx' -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || true
done

echo "Search complete."

Length of output: 5102


Fix remaining .conversation.current?.conversation.id usage — change to .conversation.current?.id.

Search found editorEngine.chat.conversation.current?.conversation.id in apps/web/client/src/components/tools/handlers/edit.ts:151 — replace with editorEngine.chat.conversation.current?.id (or call the store getter getCurrentConversationId()).

🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
around lines 78 to 81, the code is using
editorEngine.chat.conversation.current?.conversation.id instead of the flattened
current id; update the access to editorEngine.chat.conversation.current?.id (or
call the store getter getCurrentConversationId()) so it no longer dereferences a
nested conversation property. Also update the other occurrence found in
apps/web/client/src/components/tools/handlers/edit.ts at line ~151 to the same
expression to avoid runtime undefined access.

Comment on lines +1 to +3
import { api } from '@/trpc/react';
import { ChatTabContent } from './chat-tab-content';

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add 'use client' at the feature entry boundary.

This component uses client hooks (tRPC React useQuery) and should be the client boundary for the chat feature. Add the directive to avoid RSC/hook runtime errors and keep child observer components server-safe per your guideline.

+'use client';
+
 import { api } from '@/trpc/react';
 import { ChatTabContent } from './chat-tab-content';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { api } from '@/trpc/react';
import { ChatTabContent } from './chat-tab-content';
'use client';
import { api } from '@/trpc/react';
import { ChatTabContent } from './chat-tab-content';
🤖 Prompt for AI Agents
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/index.tsx
lines 1-3: this file uses client hooks (tRPC React useQuery) but lacks the React
Server Components client directive, so add "use client" as the very first line
of the file (before any imports) to make this the client boundary for the chat
feature; keep child components that are server-safe unchanged and ensure no
server-only APIs are used in this file after adding the directive.

Comment on lines +44 to +50
const { addToolResult, messages, error, stop, setMessages, regenerate, status } =
useAiChat<ChatMessage>({
id: 'user-chat',
messages: initialMessages,
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
transport: new DefaultChatTransport({
api: '/api/chat',
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Scope the AI-SDK chat id to the conversation.

Using a constant id: 'user-chat' risks state leakage across conversations. Tie it to conversationId for isolation.

-        useAiChat<ChatMessage>({
-            id: 'user-chat',
+        useAiChat<ChatMessage>({
+            id: conversationId,
             messages: initialMessages,
             sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { addToolResult, messages, error, stop, setMessages, regenerate, status } =
useAiChat<ChatMessage>({
id: 'user-chat',
messages: initialMessages,
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
transport: new DefaultChatTransport({
api: '/api/chat',
const { addToolResult, messages, error, stop, setMessages, regenerate, status } =
useAiChat<ChatMessage>({
id: conversationId,
messages: initialMessages,
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
transport: new DefaultChatTransport({
🤖 Prompt for AI Agents
In apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx around lines
44 to 50, the hard-coded chat id ('user-chat') can cause state to leak between
conversations; replace it with a conversation-scoped id (e.g., include
conversationId or similar unique conversation identifier) such as
`user-chat:${conversationId}` (or use a fallback when conversationId is
undefined) so each conversation gets its own ai-sdk chat state; ensure the
variable you use is in scope and that any places that relied on the old constant
still work with the new scoped id.

Comment on lines 55 to 58
this.creatingConversation = true;
if (this.current?.messages.length === 0 && !this.current?.conversation.title) {
if (this.current?.messageCount === 0 && !this.current?.title) {
throw new Error('Current conversation is already empty.');
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Don’t throw for the “already empty” guard; early‑return and skip the error toast.

Throwing here triggers the catch/Toast for a non-error state and sets a scary UX message. Use a no‑op return before toggling the creating flag.

Also, avoid hardcoded user‑facing strings in toasts; route through next‑intl or bubble the error to the UI to translate there.

Apply:

-        this.creatingConversation = true;
-        if (this.current?.messageCount === 0 && !this.current?.title) {
-            throw new Error('Current conversation is already empty.');
-        }
+        if (this.current?.messageCount === 0 && !this.current?.title) {
+            return;
+        }
+        this.creatingConversation = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.creatingConversation = true;
if (this.current?.messages.length === 0 && !this.current?.conversation.title) {
if (this.current?.messageCount === 0 && !this.current?.title) {
throw new Error('Current conversation is already empty.');
}
if (this.current?.messageCount === 0 && !this.current?.title) {
return;
}
this.creatingConversation = true;
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/chat/conversation.ts around lines
55-58, remove the thrown Error for the "already empty" guard and instead perform
an early no-op return (ensuring you return before setting or leaving
creatingConversation true) so the flow skips the catch/toast path; also stop
using a hardcoded user-facing string here — either return a domain signal/error
object to the caller so the UI can translate/display it via next-intl, or call
the i18n translate function before showing any toast in the UI layer.

Comment on lines +102 to 108
void this.deleteConversationInStorage(id);
if (this.current?.id === id) {
if (this.conversations.length > 0 && !!this.conversations[0]) {
this.selectConversation(this.conversations[0].id);
void this.selectConversation(this.conversations[0].id);
} else {
this.startNewConversation();
void this.startNewConversation();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Avoid fire‑and‑forget Promises; await deletion/selection to prevent races and unhandled rejections.

Make this method async, await storage deletion, and then await the follow‑up selection/start. Handle failures.

Apply:

-    deleteConversation(id: string) {
+    async deleteConversation(id: string) {
       if (!this.current) {
         console.error('No conversation found');
         return;
       }
@@
-      this.conversations.splice(index, 1);
-      void this.deleteConversationInStorage(id);
-      if (this.current?.id === id) {
-          if (this.conversations.length > 0 && !!this.conversations[0]) {
-              void this.selectConversation(this.conversations[0].id);
-          } else {
-              void this.startNewConversation();
-          }
-      }
+      this.conversations.splice(index, 1);
+      try {
+        await this.deleteConversationInStorage(id);
+      } catch (error) {
+        console.error('Error deleting conversation', error);
+        // Optionally: rollback splice here if desired.
+        return;
+      }
+      if (this.current && this.current.id === id) {
+        if (this.conversations.length > 0 && this.conversations[0]) {
+          await this.selectConversation(this.conversations[0].id);
+        } else {
+          await this.startNewConversation();
+        }
+      }
     }

Then verify call sites to ensure they can handle the now‑async API:


🏁 Script executed:

#!/bin/bash
rg -n --type ts --type tsx -C2 '\bdeleteConversation\('

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "rg version:"
rg --version || true

echo
echo "---- Searching for call sites: deleteConversation( ----"
rg -n -C2 '\bdeleteConversation\s*\(' || true

echo
echo "---- Searching for definition: deleteConversation method ----"
rg -n -C4 '^\s*(?:async\s+)?deleteConversation\s*\(' || true

echo
echo "---- Searching for deleteConversationInStorage occurrences ----"
rg -n -C2 '\bdeleteConversationInStorage\s*\(' || true

echo
echo "---- Show file at apps/web/client/src/components/store/editor/chat/conversation.ts if present ----"
file="apps/web/client/src/components/store/editor/chat/conversation.ts"
if [ -f "$file" ]; then
  echo "---- $file (first 300 lines) ----"
  sed -n '1,300p' "$file"
else
  echo "file not found: $file"
fi

Length of output: 7605


Make deleteConversation async and await deletion/selection; update callers

Convert ConversationManager.deleteConversation to async, await deleteConversationInStorage and the subsequent select/start calls to avoid races and unhandled rejections. Apply fix and update all call sites that currently invoke it without awaiting.

Apply:

-    deleteConversation(id: string) {
+    async deleteConversation(id: string) {
       if (!this.current) {
         console.error('No conversation found');
         return;
       }
@@
-      this.conversations.splice(index, 1);
-      void this.deleteConversationInStorage(id);
-      if (this.current?.id === id) {
-          if (this.conversations.length > 0 && !!this.conversations[0]) {
-              void this.selectConversation(this.conversations[0].id);
-          } else {
-              void this.startNewConversation();
-          }
-      }
+      this.conversations.splice(index, 1);
+      try {
+        await this.deleteConversationInStorage(id);
+      } catch (error) {
+        console.error('Error deleting conversation', error);
+        // Optionally: rollback splice here if desired.
+        return;
+      }
+      if (this.current && this.current.id === id) {
+        if (this.conversations.length > 0 && this.conversations[0]) {
+          await this.selectConversation(this.conversations[0].id);
+        } else {
+          await this.startNewConversation();
+        }
+      }
     }

Call sites to update:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx:37 — currently calls editorEngine.chat.conversation.deleteConversation(conversationToDelete); change to await or handle the returned Promise.
  • Run: rg -n '\bdeleteConversation\s*(' to find and update any other callers.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void this.deleteConversationInStorage(id);
if (this.current?.id === id) {
if (this.conversations.length > 0 && !!this.conversations[0]) {
this.selectConversation(this.conversations[0].id);
void this.selectConversation(this.conversations[0].id);
} else {
this.startNewConversation();
void this.startNewConversation();
}
try {
await this.deleteConversationInStorage(id);
} catch (error) {
console.error('Error deleting conversation', error);
// Optionally: rollback splice here if desired.
return;
}
if (this.current && this.current.id === id) {
if (this.conversations.length > 0 && this.conversations[0]) {
await this.selectConversation(this.conversations[0].id);
} else {
await this.startNewConversation();
}
}
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/chat/conversation.ts around lines
102–108, change deleteConversation to be async and await the call to
deleteConversationInStorage as well as the subsequent
selectConversation/startNewConversation calls to prevent races and unhandled
rejections; update the method signature to return a Promise and ensure internal
awaits are added. Then update all callers to await or properly handle the
returned Promise: specifically change the call in
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/history.tsx
at line ~37 to await
editorEngine.chat.conversation.deleteConversation(conversationToDelete), and run
rg -n '\bdeleteConversation\s*\(' to find and update any remaining call sites to
use await or .catch handling. Ensure tests/usage still compile after these
changes.

Comment on lines +192 to +200
try {
if (!availableTools[toolName]) {
toast.error(`Tool "${toolName}" not available in ask mode`, {
description: `Switch to build mode to use this tool.`,
duration: 2000,
});

throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`);
}
throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

🧩 Analysis chain

Fix hardcoded/misleading toast text and internationalize

Toast hardcodes “ask mode” and suggests “build mode” regardless of actual mode; also violates the “no hardcoded user-facing text” rule for this path.

Minimal fix (also prepares for i18n placeholders):

-            toast.error(`Tool "${toolName}" not available in ask mode`, {
-                description: `Switch to build mode to use this tool.`,
+            const targetModeLabel = currentChatMode === ChatType.ASK ? 'build' : 'ask';
+            toast.error(`Tool "${toolName}" not available in ${String(currentChatMode).toLowerCase()} mode`, {
+                description: `Switch to ${targetModeLabel} mode to use this tool.`,
                 duration: 2000,
             });

Add import (outside this hunk):

-    FUZZY_EDIT_FILE_TOOL_PARAMETERS, getToolSetFromType, GLOB_TOOL_NAME,
+    FUZZY_EDIT_FILE_TOOL_PARAMETERS, getToolSetFromType, ChatType, GLOB_TOOL_NAME,

Follow-up: wire next-intl (e.g., pass a t function from caller and replace both strings with message keys).

Verify ChatType is exported from @onlook/ai:


🏁 Script executed:

#!/bin/bash
# Look for ChatType export in the monorepo
rg -nP -C2 'export\s+(?:enum|const|type)\s+ChatType\b' packages/ai

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "--- rg: find any ChatType symbol ---"
rg -n --hidden -S '\bChatType\b' || true

echo "--- rg: find export definitions for ChatType ---"
rg -nP -C2 'export\s+(?:enum|type|const)\s+ChatType\b' || true

echo "--- rg: search packages/ and apps/ for ChatType ---"
rg -n --hidden -S '\bChatType\b' packages || true
rg -n --hidden -S '\bChatType\b' apps || true

echo "--- Show expected tools.ts if present ---"
if [ -f "apps/web/client/src/components/tools/tools.ts" ]; then
  sed -n '1,260p' apps/web/client/src/components/tools/tools.ts || true
else
  echo "File not found at apps/web/client/src/components/tools/tools.ts"
  echo "Listing apps/web/client/src/components (if exists):"
  ls -la apps/web/client/src/components || true
fi

Length of output: 22747


Fix hardcoded/misleading toast text and prepare for i18n

File: apps/web/client/src/components/tools/tools.ts (around lines 192–200)

  • Replace the hardcoded “ask/build” toast with a dynamic message using ChatType and add the correct import from @onlook/models (do not import ChatType from @onlook/ai). Minimal patch:
-            toast.error(`Tool "${toolName}" not available in ask mode`, {
-                description: `Switch to build mode to use this tool.`,
+            const targetModeLabel = currentChatMode === ChatType.ASK ? 'Build' : 'Ask';
+            toast.error(`Tool "${toolName}" not available in ${String(currentChatMode).toLowerCase()} mode`, {
+                description: `Switch to ${targetModeLabel} mode to use this tool.`,
                 duration: 2000,
             });

Add import (top of file):

-} from '@onlook/ai';
+} from '@onlook/ai';
+import { ChatType } from '@onlook/models';
  • Follow-up: replace these strings with next-intl message keys (or reuse the existing ChatType→label mapping used in chat-mode-toggle) to comply with the “no hardcoded user-facing text” rule.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
if (!availableTools[toolName]) {
toast.error(`Tool "${toolName}" not available in ask mode`, {
description: `Switch to build mode to use this tool.`,
duration: 2000,
});
throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`);
}
throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`);
}
} from '@onlook/ai';
import { ChatType } from '@onlook/models';
try {
if (!availableTools[toolName]) {
const targetModeLabel = currentChatMode === ChatType.ASK ? 'Build' : 'Ask';
toast.error(`Tool "${toolName}" not available in ${String(currentChatMode).toLowerCase()} mode`, {
description: `Switch to ${targetModeLabel} mode to use this tool.`,
duration: 2000,
});
throw new Error(`Tool "${toolName}" is not available in ${currentChatMode} mode`);
}
🤖 Prompt for AI Agents
In apps/web/client/src/components/tools/tools.ts around lines 192–200, the toast
message uses hardcoded "ask/build" text which is misleading and not i18n-ready;
import ChatType from @onlook/models at the top of the file, compute the current
mode label dynamically using ChatType (or reuse the existing ChatType→label
mapping from chat-mode-toggle), and replace the hardcoded strings in the toast
error and thrown Error with that dynamic label; afterwards switch these
user-facing strings to next-intl message keys (or reuse existing labels) to
remove hardcoded text and prepare for localization.

Comment on lines +96 to +113
.input(z.object({
conversationId: z.string(),
messages: messageInsertSchema.array(),
}))
.mutation(async ({ ctx, input }) => {
await ctx.db.transaction(async (tx) => {
await tx.delete(messages).where(eq(messages.conversationId, input.conversationId));

if (input.messages.length > 0) {
const normalizedMessages = input.messages.map(normalizeMessage);
await tx.insert(messages).values(normalizedMessages);
}

await tx.update(conversations).set({
updatedAt: new Date()
}).where(eq(conversations.id, input.conversationId));
});
}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enforce conversationId on insert and guard payload size.

Prevent accidental/malicious cross-conversation writes by overriding each message’s conversationId with input.conversationId. Also cap batch size to avoid large transactions.

-    replaceConversationMessages: protectedProcedure
-        .input(z.object({
-            conversationId: z.string(),
-            messages: messageInsertSchema.array(),
-        }))
+    replaceConversationMessages: protectedProcedure
+        .input(z.object({
+            conversationId: z.string(),
+            messages: messageInsertSchema.array().max(2000), // safety cap
+        }))
         .mutation(async ({ ctx, input }) => {
             await ctx.db.transaction(async (tx) => {
                 await tx.delete(messages).where(eq(messages.conversationId, input.conversationId));

                 if (input.messages.length > 0) {
-                    const normalizedMessages = input.messages.map(normalizeMessage);
+                    const normalizedMessages = input.messages.map((m) =>
+                        normalizeMessage({ ...m, conversationId: input.conversationId }),
+                    );
                     await tx.insert(messages).values(normalizedMessages);
                 }

                 await tx.update(conversations).set({
                     updatedAt: new Date()
                 }).where(eq(conversations.id, input.conversationId));
             });
         }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.input(z.object({
conversationId: z.string(),
messages: messageInsertSchema.array(),
}))
.mutation(async ({ ctx, input }) => {
await ctx.db.transaction(async (tx) => {
await tx.delete(messages).where(eq(messages.conversationId, input.conversationId));
if (input.messages.length > 0) {
const normalizedMessages = input.messages.map(normalizeMessage);
await tx.insert(messages).values(normalizedMessages);
}
await tx.update(conversations).set({
updatedAt: new Date()
}).where(eq(conversations.id, input.conversationId));
});
}),
.input(z.object({
conversationId: z.string(),
messages: messageInsertSchema.array().max(2000), // safety cap
}))
.mutation(async ({ ctx, input }) => {
await ctx.db.transaction(async (tx) => {
await tx.delete(messages).where(eq(messages.conversationId, input.conversationId));
if (input.messages.length > 0) {
const normalizedMessages = input.messages.map((m) =>
normalizeMessage({ ...m, conversationId: input.conversationId }),
);
await tx.insert(messages).values(normalizedMessages);
}
await tx.update(conversations).set({
updatedAt: new Date()
}).where(eq(conversations.id, input.conversationId));
});
}),
🤖 Prompt for AI Agents
In apps/web/client/src/server/api/routers/chat/message.ts around lines 96 to
113, ensure we cannot insert messages into a different conversation and prevent
huge batches: before the transaction validate input.messages length (e.g. reject
or throw if messages.length > MAX_BATCH_SIZE, choose a sensible constant like
100), and when building normalizedMessages override each message.conversationId
= input.conversationId (or map to a new object that sets conversationId to
input.conversationId) so the DB insert always uses the provided conversationId;
perform these checks and normalization before calling ctx.db.transaction so the
transaction only runs with a capped, safe payload whose conversationId values
are enforced.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (5)

28-41: Guard streaming when the array is empty.

If isStreaming is true and baseMessages.length === 0, baseMessages[baseMessages.length - 1] is undefined. Add a length check.

Apply:

-        const { messages, streamedMessage } = useMemo(() => {
-            console.log('baseMessages', baseMessages.length, isStreaming);
-            if (isStreaming) {
-                return {
-                    messages: baseMessages.slice(0, -1),
-                    streamedMessage: baseMessages[baseMessages.length - 1],
-                };
-            }
-            return {
-                messages: baseMessages,
-                streamedMessage: null,
-            };
-        }, [baseMessages, isStreaming]);
+        const { messages, streamedMessage } = useMemo(() => {
+            if (isStreaming && baseMessages.length > 0) {
+                return {
+                    messages: baseMessages.slice(0, -1),
+                    streamedMessage: baseMessages[baseMessages.length - 1],
+                };
+            }
+            return {
+                messages: baseMessages,
+                streamedMessage: null,
+            };
+        }, [baseMessages, isStreaming]);

29-30: Remove debug logs or gate behind a config flag.

Console logging in render paths adds noise and small perf overhead; could leak message content in shared environments.

Apply:

-            console.log('baseMessages', baseMessages.length, isStreaming);
...
-        console.log('messages', messages.length, streamedMessage);

If needed, guard via an app-level debug flag from @/env.

Also applies to: 42-42


44-68: Keys: avoid duplicates and index; skip empty wrappers.

  • Don’t pass key to inner components; key the wrapper only.
  • Avoid using index in the key.
  • Skip rendering the wrapper when the role is system.

Apply:

-        const renderMessage = useCallback(
-            (message: ChatMessage, index: number) => {
-                let messageNode;
+        const renderMessage = useCallback(
+            (message: ChatMessage) => {
+                let messageNode;
                 switch (message.role) {
                     case 'assistant':
-                        messageNode = <AssistantMessage key={message.id} message={message} />;
+                        messageNode = <AssistantMessage message={message} />;
                         break;
                     case 'user':
-                        messageNode = (
-                            <UserMessage
-                                key={message.id}
-                                onEditMessage={onEditMessage}
-                                message={message}
-                            />
-                        );
+                        messageNode = <UserMessage onEditMessage={onEditMessage} message={message} />;
                         break;
                     case 'system':
-                        messageNode = null;
-                        break;
+                        return null;
                     default:
                         assertNever(message.role);
                 }
-                return <div key={`message-${message.id}-${index}`}>{messageNode}</div>;
+                return <div key={`message-${message.id}`}>{messageNode}</div>;
             },
             [onEditMessage],
         );
...
-                {messages.map((message, index) => renderMessage(message, index))}
+                {messages.map(renderMessage)}

Also applies to: 88-88


85-91: contentKey should include the streamed message id (not the last non‑stream id).

When streaming, messages excludes the last element; use streamedMessage?.id to keep the key stable/accurate.

Apply:

-            <ChatMessageList
-                contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`}
-            >
+            <ChatMessageList
+                contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming && streamedMessage ? `|${streamedMessage.id}` : ''}`}
+            >

71-82: Return null explicitly when hiding the empty state.

Current return (!selected.length && <div/>) returns false; return null for clarity.

Apply:

-        if (!messages || messages.length === 0) {
-            return (
-                !editorEngine.elements.selected.length && (
-                    <div className="flex-1 flex flex-col items-center justify-center text-foreground-tertiary/80 h-full">
-                        <Icons.EmptyState className="size-32" />
-                        <p className="text-center text-regularPlus text-balance max-w=[300px]">
-                            {t(transKeys.editor.panels.edit.tabs.chat.emptyState)}
-                        </p>
-                    </div>
-                )
-            );
-        }
+        if (!messages || messages.length === 0) {
+            if (editorEngine.elements.selected.length) return null;
+            return (
+                <div className="flex-1 flex flex-col items-center justify-center text-foreground-tertiary/80 h-full">
+                    <Icons.EmptyState className="size-32" />
+                    <p className="text-center text-regularPlus text-balance max-w-[300px]">
+                        {t(transKeys.editor.panels.edit.tabs.chat.emptyState)}
+                    </p>
+                </div>
+            );
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd0f12d and f870bc5.

📒 Files selected for processing (2)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (2 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
🧬 Code graph analysis (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (5)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • EditMessage (19-23)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
  • UserMessage (29-264)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
  • StreamMessage (6-27)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
  • ErrorMessage (11-63)
🔇 Additional comments (3)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (3)

1-1: Type‑only import: good.

Using import type keeps bundles lean and avoids runtime deps.


16-21: Props shape looks solid.

Clear, minimal API with explicit error?: Error.


23-25: Client boundary present at right-panel — no change required.

apps/web/client/src/app/project/[id]/_components/right-panel/index.tsx contains "use client" at the module top and therefore provides the single client boundary covering chat-tab/chat-messages; chat-tab-content/index.tsx does not include "use client" and does not need one.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (4)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (4)

79-87: Double-invoking suggestion selection on Enter (triggers side effects twice).

Call handleEnterSelection once and gate handling on its boolean result.

Apply:

-        const handleGlobalKeyDown = (e: KeyboardEvent) => {
-            if (e.key === 'Enter' && suggestionRef.current?.handleEnterSelection()) {
-                e.preventDefault();
-                e.stopPropagation();
-                // Stop the event from bubbling to the canvas
-                e.stopImmediatePropagation();
-                // Handle the suggestion selection
-                suggestionRef.current.handleEnterSelection();
-            }
-        };
+        const handleGlobalKeyDown = (e: KeyboardEvent) => {
+            if (e.key === 'Enter') {
+                const handled = suggestionRef.current?.handleEnterSelection?.() === true;
+                if (handled) {
+                    e.preventDefault();
+                    e.stopPropagation();
+                    // Stop the event from bubbling to the canvas
+                    e.stopImmediatePropagation();
+                }
+            }
+        };

147-150: Localize user-facing error toast.

Avoid hardcoded text; use next-intl.

Apply:

-            toast.error('Failed to send message. Please try again.');
+            toast.error(t(transKeys.editor.panels.edit.tabs.chat.input.errors.sendFailed));

153-158: Localize ASK-mode placeholder.

Replace hardcoded string with a translation key.

Apply:

-        if (chatMode === ChatType.ASK) {
-            return 'Ask a question about your project...';
-        }
+        if (chatMode === ChatType.ASK) {
+            return t(transKeys.editor.panels.edit.tabs.chat.input.askPlaceholder);
+        }

232-271: Localize screenshot messages and label.

All toasts and labels should use translations.

Apply:

-                toast.error('No active frame available for screenshot');
+                toast.error(t(transKeys.editor.panels.edit.tabs.chat.screenshot.noActiveFrame));
-                toast.error('Failed to capture screenshot. Please refresh the page and try again.');
+                toast.error(t(transKeys.editor.panels.edit.tabs.chat.screenshot.captureFailedWithRefresh));
-                displayName: 'Screenshot',
+                displayName: t(transKeys.editor.panels.edit.tabs.chat.screenshot.label),
-            toast.success('Screenshot added to chat');
+            toast.success(t(transKeys.editor.panels.edit.tabs.chat.screenshot.added));
-            toast.error('Failed to capture screenshot. Please try again.');
+            toast.error(t(transKeys.editor.panels.edit.tabs.chat.screenshot.captureFailed));
🧹 Nitpick comments (4)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (4)

47-48: Stale isStreaming captured in window listener.

The handler closes over the initial value; use a ref to read the current state.

Apply:

   const chatMode = editorEngine.state.chatMode;
-    const [inputValue, setInputValue] = useState('');
+    const [inputValue, setInputValue] = useState('');
+    const isStreamingRef = useRef(isStreaming);
+
+    useEffect(() => {
+        isStreamingRef.current = isStreaming;
+    }, [isStreaming]);
-        const focusHandler = () => {
-            if (textareaRef.current && !isStreaming) {
+        const focusHandler = () => {
+            if (textareaRef.current && !isStreamingRef.current) {
                 focusInput();
             }
         };

Also applies to: 68-71


408-418: Add accessible labels to icon-only buttons and localize tooltip text.

Improve a11y and i18n.

Apply:

-                                <Button
+                                <Button
+                                    aria-label={t(transKeys.editor.panels.edit.tabs.chat.input.stopResponse)}
                                     size={'icon'}
                                     variant={'secondary'}
                                     className="text-smallPlus w-fit h-full py-0.5 px-2.5 text-primary"
                                     onClick={() => {
                                         setActionTooltipOpen(false);
                                         void onStop();
                                     }}
                                 >
-                            <TooltipContent>{'Stop response'}</TooltipContent>
+                            <TooltipContent>{t(transKeys.editor.panels.edit.tabs.chat.input.stopResponse)}</TooltipContent>
-                        <Button
+                        <Button
+                            aria-label={t(transKeys.editor.panels.edit.tabs.chat.input.send)}
                             size={'icon'}
                             variant={'secondary'}
                             className="text-smallPlus w-fit h-full py-0.5 px-2.5 text-primary"
                             disabled={inputEmpty || disabled}
                             onClick={() => void sendMessage()}
                         >

Also applies to: 420-420, 423-432


295-305: DragEvent DataTransfer may not be constructible across browsers. Add a fallback.

Safari/Firefox historically restrict setting dataTransfer in constructed events.

Apply:

-        e.currentTarget.parentElement?.dispatchEvent(
-            new DragEvent(eventType, {
-                bubbles: true,
-                cancelable: true,
-                dataTransfer: e.dataTransfer,
-            }),
-        );
+        const parent = e.currentTarget.parentElement;
+        if (!parent) return;
+        try {
+            parent.dispatchEvent(
+                new DragEvent(eventType, {
+                    bubbles: true,
+                    cancelable: true,
+                    dataTransfer: e.dataTransfer,
+                }),
+            );
+        } catch {
+            // Fallback for browsers that disallow setting dataTransfer
+            parent.dispatchEvent(new Event(eventType, { bubbles: true, cancelable: true }));
+        }

193-201: DRY up image-limit checks.

Factor the repeated limit-check/toast into a helper for consistency.

You can add:

function ensureImageSlotAvailable(limit = 1): boolean {
  const currentImages = editorEngine.chat.context.context.filter(
    (ctx) => ctx.type === MessageContextType.IMAGE
  );
  const { success, errorMessage } = validateImageLimit(currentImages, limit);
  if (!success) toast.error(errorMessage);
  return success;
}

Then:

  • In handleImageEvent: if (!ensureImageSlotAvailable(1)) return;
  • In handleScreenshot: same guard.

Also applies to: 218-228

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f870bc5 and fd5cf9f.

📒 Files selected for processing (6)
  • apps/web/client/src/app/api/chat/route.ts (2 hunks)
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (14 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/web/client/src/app/project/[id]/_components/canvas/overlay/elements/buttons/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
  • apps/web/client/src/app/project/[id]/_components/top-bar/index.tsx
  • apps/web/client/src/app/api/chat/route.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/suggestions.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
🧬 Code graph analysis (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (5)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (18-18)
packages/models/src/chat/suggestion.ts (1)
  • ChatSuggestion (9-12)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • SendMessage (18-18)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
apps/web/client/src/components/store/editor/chat/index.ts (1)
  • sendMessage (43-49)

@@ -1,8 +1,9 @@

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Must mark as Client Component (uses hooks, events, observer).

This file runs client-only code; without a client boundary it will fail in Next.js.

Apply at top:

+ 'use client';

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Looking for the target file (exact path)..."
target="apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx"
if [ -f "$target" ]; then
  echo "FOUND: $target"
  files=("$target")
else
  echo "Not found at exact path. Searching for 'chat-input/index.tsx' under apps/web/client/src/app..."
  mapfile -t files < <(rg --hidden --files apps/web/client/src/app 2>/dev/null | rg 'chat-input/index.tsx' || true)
  if [ ${#files[@]} -eq 0 ]; then
    echo "No matching files found. Listing top-level tree for apps/web/client/src/app/project (maxdepth 4) to help locate file..."
    if [ -d "apps/web/client/src/app/project" ]; then
      find apps/web/client/src/app/project -maxdepth 4 -type f | sed -n '1,200p'
    else
      echo "Directory apps/web/client/src/app/project does not exist."
    fi
    exit 0
  fi
fi

for file in "${files[@]}"; do
  echo
  echo "===== FILE: $file ====="
  echo "---- head (1..200) ----"
  sed -n '1,200p' "$file" || true
  echo
  echo "---- 'use client' directive (exact match) ----"
  rg -n "^['\"]use client['\"]" "$file" || echo "(none)"
  echo
  echo "---- client-only hooks / events / observer usage ----"
  rg -n -C2 --hidden -S '\b(useState|useEffect|useRef|useCallback|useMemo|useContext|useTransition|onClick|onSubmit|onChange|onBlur|observer)\b' "$file" || echo "(none found)"
  echo
  echo "---- imports referencing observer/mobx/react hooks ----"
  rg -n "import .*observer|from (\"|')mobx|mobx-react|mobx-react-lite|react-use|@hookstate" "$file" || echo "(none found)"
  echo "=============================="
done

echo
echo "---- Files under apps/web/client/src/app that include a top-level 'use client' directive ----"
rg -n --hidden "^['\"]use client['\"]" apps/web/client/src/app || echo "(none)"

Length of output: 20299


Add 'use client' at the top of this file (component uses React hooks, mobx observer, and browser/DOM events).

File: apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx

+ 'use client';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'use client';
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
around lines 1 to 1, this component uses React hooks, mobx observer, and
browser/DOM events but is missing the required React Server Components
directive; add the line 'use client' as the very first statement in the file
(above all imports) so the component is executed on the client, then run a quick
build/type-check to ensure no other client-only usages are flagged.

Comment on lines +211 to +215
void addToolResult({
tool: toolName,
toolCallId: toolCall.toolCallId,
output: output,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Incorrect async handling - addToolResult is called with void but the function signature shows it returns Promise. This should be awaited to ensure proper error handling and completion of the tool result addition.

Suggested change
void addToolResult({
tool: toolName,
toolCallId: toolCall.toolCallId,
output: output,
});
await addToolResult({
tool: toolName,
toolCallId: toolCall.toolCallId,
output: output,
});

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (3)
apps/web/client/src/components/tools/handlers/edit.ts (2)

22-37: Guard against empty search strings to prevent pathological replaces.

If old_string is empty, replace/replaceAll can explode content. Add a validation before applying edits.

         let newContent: string;
+        if (!args.old_string) {
+            throw new Error('old_string cannot be empty');
+        }
         if (args.replace_all) {
             newContent = file.content.replaceAll(args.old_string, args.new_string);
         } else {

67-84: Validate each edit: non-empty old_string and non-empty edits array.

Preempt invalid inputs early and give clearer errors.

-        // Validate only the first non-replace_all edit against original content
+        // Validate only the first non-replace_all edit against original content
         // Sequential edits will be validated during application
         let tempContent = originalContent;
+        if (!args.edits.length) {
+            throw new Error('No edits provided');
+        }
         for (const edit of args.edits) {
+            if (!edit.old_string) {
+                throw new Error('old_string cannot be empty');
+            }
             if (!edit.replace_all) {
                 const firstIndex = tempContent.indexOf(edit.old_string);
                 if (firstIndex === -1) {
                     throw new Error(`String not found in file: ${edit.old_string}`);
                 }
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx (1)

15-31: Bug: contextPercentage ignores real usage and hardcodes model window.

Use typed cumulativeUsage from the store, not a zeroed stub; avoid stale “Claude” comment, and parameterize max tokens per active model if available.

-    const contextPercentage = useMemo(() => {
-        // Use cumulative usage data if available, otherwise fall back to estimate
-        const cumulativeUsage = {
-            promptTokens: 0,
-            completionTokens: 0,
-            totalTokens: 0,
-        };
+    const contextPercentage = useMemo(() => {
+        // Use cumulative usage data if available, otherwise fall back to estimate
+        const cumulativeUsage = editorEngine.chat.context.cumulativeUsage;
@@
-        if (cumulativeUsage && cumulativeUsage.totalTokens > 0) {
-            // Use cumulative token usage from all API responses
-            // Assume a context window of ~200k tokens for Claude 4 Sonnet
-            const maxContextTokens = 200000;
+        if (cumulativeUsage && cumulativeUsage.totalTokens > 0) {
+            // Use cumulative token usage from all API responses
+            // TODO: derive maxContextTokens from the active model selection
+            const maxContextTokens = 200000;

Follow-up (cross-file): expose a typed cumulativeUsage on ChatContext to remove any-casts here (see comment in chat/context.ts).

♻️ Duplicate comments (1)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)

46-47: Scope chat id to the conversation to prevent state bleed.

Use a conversation-scoped id instead of a constant. This isolates ai-sdk state per thread.

-            id: 'user-chat',
+            id: conversationId,
🧹 Nitpick comments (11)
apps/web/client/src/components/tools/handlers/edit.ts (2)

86-104: No-op fast path when content didn’t change.

Avoid unnecessary writes when edits result in identical content.

         // Apply edits sequentially in the order provided
         // Each edit operates on the result of the previous edit
         for (const edit of args.edits) {
             if (edit.replace_all) {
                 content = content.replaceAll(edit.old_string, edit.new_string);
             } else {
                 const index = content.indexOf(edit.old_string);
                 if (index === -1) {
                     throw new Error(`String not found in file after previous edits: ${edit.old_string}`);
                 }
                 content = content.replace(edit.old_string, edit.new_string);
             }
         }
 
+        if (content === originalContent) {
+            return `No changes applied to ${args.file_path}`;
+        }
+
         const result = await sandbox.writeFile(args.file_path, content);

45-47: Preserve original error message when rethrowing.

Stringifying the error object can yield “[object Object]”. Use message or String(error).

-    } catch (error) {
-        throw new Error(`Cannot edit file ${args.file_path}: ${error}`);
-    }
+    } catch (error) {
+        const message = error instanceof Error ? error.message : String(error);
+        throw new Error(`Cannot edit file ${args.file_path}: ${message}`);
+    }
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (3)

112-116: Send the actual chatType to analytics.

Don’t hardcode ChatType.EDIT; use the provided chatType.

-            posthog.capture('user_edit_message', { type: ChatType.EDIT });
+            posthog.capture('user_edit_message', { type: chatType });

139-144: Pass context on edit regenerate for parity with send.

Maintain server parity by including context as in sendMessage.

             void regenerate({
                 body: {
                     chatType,
                     conversationId,
+                    context,
                 },
             });

115-116: Remove debug log.

-            console.log('[RERENDER] editMessage');
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (1)

29-30: Remove console log.

-            console.log('baseMessages', baseMessages.length, isStreaming);
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx (4)

47-51: Remove any-casts; use proper type narrowing.

-        messages.forEach(message => {
+        messages.forEach((message) => {
             // Extract text content from message parts
-            const textContent = message.parts
-                .filter(part => part.type === 'text')
-                .map(part => (part as any).text)
-                .join(' ');
+            const textContent = message.parts
+                .map((p) => (p.type === 'text' ? p.text : ''))
+                .join(' ');
@@
-            if (message.metadata?.context) {
-                message.metadata.context.forEach((contextItem: any) => {
-                    if (contextItem.content) {
-                        estimatedTokens += Math.ceil(contextItem.content.length / 4);
-                    }
-                });
-            }
+            message.metadata?.context?.forEach((contextItem) => {
+                if ('content' in contextItem && typeof contextItem.content === 'string') {
+                    estimatedTokens += Math.ceil(contextItem.content.length / 4);
+                }
+            });

Also applies to: 57-63


71-71: Avoid any in deps; depend on a typed field.

Swap (editorEngine.chat.context as any).cumulativeUsage for a typed property.

-    }, [(editorEngine.chat.context as any).cumulativeUsage, editorEngine.chat.context.context]);
+    }, [editorEngine.chat.context.cumulativeUsage, editorEngine.chat.context.context]);

33-34: Messages are hardcoded to an empty array — token estimates and tooltip counts are wrong.

Inject actual messages (e.g., via props) or source them from a typed store to keep estimates accurate.

Would you like a follow-up patch to accept messages?: ChatMessage[] in ContextWheelProps and wire it from ChatTabContent/useChat?

Also applies to: 92-95


137-156: Localize user-facing strings.

Wrap strings in next-intl (title/labels/units) per repo guidelines.

I can generate the minimal i18n keys and replacements if you want.

apps/web/client/src/components/store/editor/chat/context.ts (1)

196-215: getErrorContext: consider truncating large payloads.

Long error contents can balloon context; limit per-branch bytes or entries to protect prompt budget.

I can add a safe truncation (e.g., first 2–3 errors per branch, max chars per error) if helpful.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd5cf9f and 3d4ffbf.

📒 Files selected for processing (7)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx (4 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (2 hunks)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx (2 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx (0 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1 hunks)
  • apps/web/client/src/components/store/editor/chat/context.ts (4 hunks)
  • apps/web/client/src/components/tools/handlers/edit.ts (2 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/components/tools/handlers/edit.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/components/tools/handlers/edit.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/components/tools/handlers/edit.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
🧠 Learnings (2)
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx
🧬 Code graph analysis (4)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/context-wheel.tsx (2)
packages/db/src/schema/chat/message.ts (1)
  • messages (11-27)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (18-18)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (9)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (18-18)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • EditMessage (19-23)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/assistant-message.tsx (1)
  • AssistantMessage (4-15)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
  • UserMessage (29-264)
apps/web/client/src/i18n/keys.ts (1)
  • transKeys (5-5)
packages/ui/src/components/chat/chat-message-list.tsx (1)
  • ChatMessageList (53-53)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
  • StreamMessage (6-27)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
  • ErrorMessage (11-63)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (7)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (18-18)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
packages/models/src/chat/suggestion.ts (1)
  • ChatSuggestion (9-12)
apps/web/client/src/components/tools/tools.ts (1)
  • handleToolCall (186-218)
apps/web/client/src/components/store/editor/chat/index.ts (1)
  • sendMessage (43-49)
apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (3)
  • getUserChatMessageFromString (18-35)
  • prepareMessagesForSuggestions (6-16)
  • attachCommitToUserMessage (38-68)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (2)
  • MessageContext (53-59)
  • ErrorMessageContext (43-46)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
🔇 Additional comments (2)
apps/web/client/src/components/tools/handlers/edit.ts (1)

149-153: Verify conversationId presence in metadata.

If editorEngine.chat.conversation.current?.id can be undefined, api.code.applyDiff may lose association. Either require a conversation or omit the field when absent.

Would you like a follow-up patch that enforces a non-null conversationId (with a user-visible warning) before calling applyDiff?

apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)

176-189: Ensure Array.prototype.findLast is supported at runtime or avoid using it

  • findLast is an ES2023 runtime API; TypeScript "lib" only provides typings — the runtime (browsers/Node) must support it or you must polyfill/replace it.
  • Occurrences: apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx:176, apps/web/client/src/app/api/chat/route.ts:74.
  • Action: confirm the extended config "@onlook/typescript/next-react.json" (used by apps/web/client/tsconfig.json) exposes "lib": ["ES2023"|"ESNext"] for typings and that your production runtimes include findLast; otherwise add a runtime polyfill (e.g., core-js) or replace findLast with a compatible pattern (e.g., reverse+find).

Comment on lines +84 to +85
contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`}
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix contentKey: include streamedMessage.id, not the last non-streaming id.

Current key won’t change with streaming updates, breaking scroll/transition behavior.

-            <ChatMessageList
-                contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`}
+            <ChatMessageList
+                contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${streamedMessage?.id ?? ''}` : ''}`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`}
>
<ChatMessageList
contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${streamedMessage?.id ?? ''}` : ''}`}
>
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
around lines 84-85, the contentKey currently appends the last message id when
isStreaming is true which doesn't change as the streamed text updates; change
the key to include the streaming message's id (streamedMessage?.id) instead of
messages?.[messages.length - 1]?.id so the key updates with streaming progress.
Update the template to use streamedMessage?.id when isStreaming (with a safe
fallback to ''), ensuring proper optional chaining and preserving the rest of
the messages map in the key.

Comment on lines +56 to +61
onToolCall: async (toolCall) => {
setIsExecutingToolCall(true);
void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => {
setIsExecutingToolCall(false);
});
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Await tool-call handling and always clear isExecutingToolCall.

Current .then(...) can flip the flag before addToolResult finishes, misreporting streaming state. Use try/finally and await the handler.

-            onToolCall: async (toolCall) => {
-                setIsExecutingToolCall(true);
-                void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => {
-                    setIsExecutingToolCall(false);
-                });
-            },
+            onToolCall: async (toolCall) => {
+                setIsExecutingToolCall(true);
+                try {
+                    await handleToolCall(toolCall.toolCall, editorEngine, addToolResult);
+                } finally {
+                    setIsExecutingToolCall(false);
+                }
+            },

Additionally, update handleToolCall to await addToolResult so the promise resolves after the result is recorded:

// apps/web/client/src/components/tools/tools.ts (cross-file)
export async function handleToolCall(/*...*/) {
  // ...
  try {
    // ...
    output = await clientTool.handler(toolCall.input, editorEngine);
  } catch (error) {
    output = 'error handling tool call ' + error;
  } finally {
-   void addToolResult({ tool: toolName, toolCallId: toolCall.toolCallId, output });
+   await addToolResult({ tool: toolName, toolCallId: toolCall.toolCallId, output });
  }
}
🤖 Prompt for AI Agents
In apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx around lines
56-61, the onToolCall handler sets isExecutingToolCall and uses .then(...) which
can clear the flag before addToolResult finishes; change this to await the call
inside a try/finally so setIsExecutingToolCall(false) always runs after the
handler completes. Also update the cross-file function handleToolCall
(apps/web/client/src/components/tools/tools.ts) so its finally block awaits
addToolResult (i.e., make addToolResult return a promise and await it) so the
handler’s returned promise only resolves after the result is recorded.

Comment on lines 16 to 23
export class ChatContext {
context: MessageContext[] = [];
private selectedReactionDisposer?: () => void;
constructor(
private editorEngine: EditorEngine,
) {

constructor(private editorEngine: EditorEngine) {
makeAutoObservable(this);
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Expose typed cumulativeUsage on ChatContext.

This removes any-casts in ContextWheel and enables accurate usage-based percentages.

 export class ChatContext {
     context: MessageContext[] = [];
+    // Aggregated token usage across the active conversation (client-updated)
+    cumulativeUsage: { promptTokens: number; completionTokens: number; totalTokens: number } | null = null;
@@
     constructor(private editorEngine: EditorEngine) {
         makeAutoObservable(this);
     }

Optionally add a setter to keep updates observable:

setCumulativeUsage(usage: { promptTokens: number; completionTokens: number; totalTokens: number } | null) {
  this.cumulativeUsage = usage;
}
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/chat/context.ts around lines 16
to 23, add a strongly-typed cumulativeUsage property to ChatContext (type: {
promptTokens: number; completionTokens: number; totalTokens: number } | null)
instead of relying on any casts elsewhere, initialize it to null, and ensure it
remains observable by keeping makeAutoObservable(this) in the constructor;
optionally add a setCumulativeUsage(usage: { promptTokens: number;
completionTokens: number; totalTokens: number } | null) method that assigns
this.cumulativeUsage = usage so updates are tracked.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (2)
apps/web/client/src/components/store/editor/chat/context.ts (1)

24-29: Prevent stale context writes from async reaction (race on rapid selection).

If selection changes quickly, earlier getChatContext() results can overwrite newer context. Gate with a version token.

-    init() {
-        this.selectedReactionDisposer = reaction(
-            () => this.editorEngine.elements.selected,
-            () => this.getChatContext().then((context) => (this.context = context)),
-        );
-    }
+    init() {
+        this.selectedReactionDisposer = reaction(
+            () => this.editorEngine.elements.selected,
+            () => {
+                const version = ++this.selectionVersion;
+                void this.getChatContext().then((context) => {
+                    if (version === this.selectionVersion) {
+                        this.context = context;
+                    }
+                });
+            },
+        );
+    }

Add the backing field (outside the shown range):

// class ChatContext { ...
private selectionVersion = 0;
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (1)

79-88: Fix double invocation of suggestion selection on Enter.

handleEnterSelection() is called twice (in the condition and again inside), causing duplicate handling.

-        const handleGlobalKeyDown = (e: KeyboardEvent) => {
-            if (e.key === 'Enter' && suggestionRef.current?.handleEnterSelection()) {
+        const handleGlobalKeyDown = (e: KeyboardEvent) => {
+            const handled = e.key === 'Enter' && !!suggestionRef.current?.handleEnterSelection();
+            if (handled) {
                 e.preventDefault();
                 e.stopPropagation();
                 // Stop the event from bubbling to the canvas
                 e.stopImmediatePropagation();
-                // Handle the suggestion selection
-                suggestionRef.current.handleEnterSelection();
             }
         };
♻️ Duplicate comments (6)
apps/web/client/src/components/store/editor/chat/conversation.ts (2)

90-110: Make deleteConversation async; await storage ops and follow‑ups.

Fire‑and‑forget promises risk races/unhandled rejections when switching conversations.

-    deleteConversation(id: string) {
+    async deleteConversation(id: string) {
         if (!this.current) {
             console.error('No conversation found');
             return;
         }
@@
         this.conversations.splice(index, 1);
-        void this.deleteConversationInStorage(id);
-        if (this.current?.id === id) {
-            if (this.conversations.length > 0 && !!this.conversations[0]) {
-                void this.selectConversation(this.conversations[0].id);
-            } else {
-                void this.startNewConversation();
-            }
-        }
+        try {
+            await this.deleteConversationInStorage(id);
+        } catch (error) {
+            console.error('Error deleting conversation', error);
+            return;
+        }
+        if (this.current && this.current.id === id) {
+            if (this.conversations.length > 0 && this.conversations[0]) {
+                await this.selectConversation(this.conversations[0].id);
+            } else {
+                await this.startNewConversation();
+            }
+        }
     }
#!/bin/bash
# Find call sites that must now await deleteConversation
rg -n -C2 '\bdeleteConversation\s*\(' --type ts --type tsx

55-57: Don’t throw for “already empty” guard; early-return before toggling the flag.

Throwing here triggers catch/toast for a non-error state and briefly flips creatingConversation. Return no-op instead.

-            this.creatingConversation = true;
-            if (this.current?.messageCount === 0 && !this.current?.title) {
-                throw new Error('Current conversation is already empty.');
-            }
+            if (this.current?.messageCount === 0 && !this.current?.title) {
+                return;
+            }
+            this.creatingConversation = true;
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (2)

170-172: Localize pasted/dropped image labels.

Use next-intl; avoid hardcoded user-facing text.

-                void handleImageEvent(file, 'Pasted image');
+                void handleImageEvent(file, t(transKeys.editor.panels.edit.tabs.chat.input.image.pasted));
-                void handleImageEvent(file, 'Dropped image');
+                void handleImageEvent(file, t(transKeys.editor.panels.edit.tabs.chat.input.image.dropped));

Also applies to: 187-188


1-1: Add 'use client'.

This component uses hooks/events and must be a Client Component in Next.js.

+'use client';
+
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (2)

46-48: Scope chat id to the conversation to prevent cross‑chat state leakage.

Using a constant id merges streams across tabs/conversations.

-            id: 'user-chat',
+            id: conversationId,

56-61: Await tool‑call handling; always clear isExecutingToolCall in finally.

Current .then(...) can flip the flag early and race with addToolResult.

-            onToolCall: async (toolCall) => {
-                setIsExecutingToolCall(true);
-                void handleToolCall(toolCall.toolCall, editorEngine, addToolResult).then(() => {
-                    setIsExecutingToolCall(false);
-                });
-            },
+            onToolCall: async (toolCall) => {
+                setIsExecutingToolCall(true);
+                try {
+                    await handleToolCall(toolCall.toolCall, editorEngine, addToolResult);
+                } finally {
+                    setIsExecutingToolCall(false);
+                }
+            },

Also update the tool handler to await persistence:

--- a/apps/web/client/src/components/tools/tools.ts
+++ b/apps/web/client/src/components/tools/tools.ts
@@
-    } finally {
-        void addToolResult({
+    } finally {
+        await addToolResult({
             tool: toolName,
             toolCallId: toolCall.toolCallId,
             output: output,
         });
     }
🧹 Nitpick comments (21)
packages/ai/src/prompt/ask.ts (1)

33-33: Nit: tighten wording (“code changes”).

Minor copy edit for clarity.

-Remember: You're a thoughtful advisor helping users make informed decisions about their project, not an automated coding assistant. If the user asks for code change, you can prompt them to toggle to EDIT MODE to make the changes.
+Remember: You're a thoughtful advisor helping users make informed decisions about their project, not an automated coding assistant. If the user asks for code changes, prompt them to toggle to EDIT MODE to make the changes.
apps/web/client/src/components/store/editor/chat/context.ts (3)

221-228: Rename shadowed variable for readability.

Inner branchErrors shadows the outer constant. Rename to avoid confusion.

-        return Array.from(branchGroups.entries()).map(([branchId, branchErrors]) => ({
+        return Array.from(branchGroups.entries()).map(([branchId, errors]) => ({
             type: MessageContextType.ERROR,
-            content: branchErrors
+            content: errors
                 .map((e) => `Source: ${e.sourceId}\nContent: ${e.content}\n`)
                 .join('\n'),
-            displayName: `Errors - ${branchErrors[0]?.branchName || 'Unknown Branch'}`,
+            displayName: `Errors - ${errors[0]?.branchName || 'Unknown Branch'}`,
             branchId,
         }));

210-229: Localize or defer user-visible labels.

displayName strings (e.g., “Errors - …”, “Unknown Branch”) can surface in the UI (context pills). Move label formatting to the UI layer with next-intl, or inject translated strings via parameters.

Please confirm whether displayName from context surfaces directly to users. If yes, I can propose a small adapter to map raw context → localized labels at render sites.


104-109: Unnecessary async.

getImageContext is synchronous. Consider removing async to simplify call sites, or keep as-is if you anticipate I/O later.

apps/web/client/src/components/store/editor/chat/conversation.ts (2)

56-66: Emptiness check may be unreliable (messageCount reset on select).

selectConversation sets messageCount: 0, so this guard can block starting a new conversation right after selecting an existing one. Prefer a separate hasMessages flag derived from server or remove the guard.

Confirm where setConversationLength() is called post-select to reflect the true count; otherwise this guard should be removed.


68-71: Localize toast text.

Avoid hardcoded user-facing strings in apps/web/client/src/**/*.{ts,tsx}. Route through next-intl or bubble errors to the UI.

apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)

108-118: Avoid redundant network round-trip; use current conversation.

You already applied conversations and selected one in a prior effect. Use the current selection instead of fetching again.

-            const [conversation] = await editorEngine.chat.conversation.getConversations(
-                editorEngine.projectId,
-            );
-
-            if (!conversation) {
-                throw new Error('No conversation found');
-            }
-
-            await editorEngine.chat.conversation.selectConversation(conversation.id);
+            const current = editorEngine.chat.conversation.current;
+            if (!current) {
+                throw new Error('No conversation found');
+            }
+            await editorEngine.chat.conversation.selectConversation(current.id);

94-101: Localize user-visible labels (“user image”).

Hardcoded display names can appear in the UI. Use next-intl (similar to other components) or pass labels down from UI.


120-137: Localize toast messages.

Replace hardcoded ‘Failed to complete/resume create request’ strings with translated messages.

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (3)

154-158: Localize ASK placeholder.

Replace hardcoded placeholder with a translation key.

-        if (chatMode === ChatType.ASK) {
-            return 'Ask a question about your project...';
-        }
+        if (chatMode === ChatType.ASK) {
+            return t(transKeys.editor.panels.edit.tabs.chat.input.placeholderAsk);
+        }

412-418: Localize the Stop tooltip.

Avoid hardcoded 'Stop response'.

-                            <TooltipContent>{'Stop response'}</TooltipContent>
+                            <TooltipContent>{t(transKeys.editor.panels.edit.tabs.chat.input.stopResponse)}</TooltipContent>

146-150: Localize toast messages.

User-visible toasts should use next-intl.

Examples to update:

  • “Failed to send message. Please try again.”
  • “Screenshot added to chat”
  • “Failed to capture screenshot. Error: …”

Also applies to: 266-269

apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (9)

112-113: Fix analytics: log the actual chatType used for the edit.

-            posthog.capture('user_edit_message', { type: ChatType.EDIT });
+            posthog.capture('user_edit_message', { type: chatType });

114-114: Remove debug log.

-            console.log('[RERENDER] editMessage');

129-138: Avoid in‑place mutation of message; create an updated copy for predictability.

-            message.metadata = {
-                ...message.metadata,
-                context: updatedContext,
-                conversationId,
-                createdAt: message.metadata?.createdAt ?? new Date(),
-                checkpoints: message.metadata?.checkpoints ?? [],
-            };
-            message.parts = [{ type: 'text', text: newContent }];
-
-            setMessages(jsonClone([...updatedMessages, message]));
+            const updatedMessage: ChatMessage = {
+                ...message,
+                metadata: {
+                    ...message.metadata,
+                    context: updatedContext,
+                    conversationId,
+                    createdAt: message.metadata?.createdAt ?? new Date(),
+                    checkpoints: message.metadata?.checkpoints ?? [],
+                },
+                parts: [{ type: 'text', text: newContent }],
+            };
+
+            setMessages(jsonClone([...updatedMessages, updatedMessage]));

140-145: Pass refreshed context on edit regenerate to keep server in sync.

             void regenerate({
                 body: {
                     chatType,
                     conversationId,
+                    context: updatedContext,
                 },
             });

62-65: Avoid shadowing finishReason name for clarity.

-            onFinish: ({ message }) => {
-                const finishReason = message.metadata?.finishReason;
-                setFinishReason(finishReason ?? null);
-            },
+            onFinish: ({ message }) => {
+                const reason = message.metadata?.finishReason;
+                setFinishReason(reason ?? null);
+            },

176-221: Guard applyCommit and cleanup against unhandled promise rejections.

-            const applyCommit = async () => {
-                const lastUserMessage = messagesRef.current.findLast((m) => m.role === 'user');
+            const applyCommit = async () => {
+                try {
+                    const lastUserMessage = messagesRef.current.findLast((m) => m.role === 'user');
 
-                if (!lastUserMessage) {
-                    return;
-                }
+                    if (!lastUserMessage) return;
 
-                const content = lastUserMessage.parts
+                    const content = lastUserMessage.parts
                     .map((p) => {
                         if (p.type === 'text') {
                             return p.text;
                         }
                         return '';
                     })
                     .join('');
 
-                if (!content) {
-                    return;
-                }
+                    if (!content) return;
 
-                const { commit } = await editorEngine.versions.createCommit(content, false);
-                if (!commit) {
-                    throw new Error('Failed to create commit');
-                }
+                    const { commit } = await editorEngine.versions.createCommit(content, false);
+                    if (!commit) throw new Error('Failed to create commit');
 
-                const messageWithCommit = attachCommitToUserMessage(
-                    commit,
-                    lastUserMessage,
-                    conversationId,
-                );
-                setMessages(
-                    jsonClone(
-                        messagesRef.current.map((m) =>
-                            m.id === lastUserMessage.id ? messageWithCommit : m,
-                        ),
-                    ),
-                );
+                    const messageWithCommit = attachCommitToUserMessage(
+                        commit,
+                        lastUserMessage,
+                        conversationId,
+                    );
+                    setMessages(
+                        jsonClone(
+                            messagesRef.current.map((m) =>
+                                m.id === lastUserMessage.id ? messageWithCommit : m,
+                            ),
+                        ),
+                    );
+                } catch (error) {
+                    console.error('applyCommit failed:', error);
+                }
             };
@@
-            void cleanupContext();
+            void cleanupContext().catch(console.error);
             void fetchSuggestions();
             void applyCommit();

99-107: Trim sendMessage deps: remove unused/constant refs to reduce re‑creations.

-        [
-            editorEngine.chat.context,
-            editorEngine.versions,
-            messagesRef,
-            setMessages,
-            regenerate,
-            conversationId,
-            posthog,
-        ],
+        [
+            editorEngine.chat.context,
+            setMessages,
+            regenerate,
+            conversationId,
+            posthog,
+        ],

149-156: Trim editMessage deps: drop unused editorEngine.versions.

-        [
-            editorEngine.chat.context,
-            editorEngine.versions,
-            regenerate,
-            conversationId,
-            setMessages,
-            posthog,
-        ],
+        [
+            editorEngine.chat.context,
+            regenerate,
+            conversationId,
+            setMessages,
+            posthog,
+        ],

120-121: Avoid hard‑coded user‑facing text; prefer i18n or typed error codes.

Consider throwing a coded error (e.g., 'chat.error.messageNotFound') and translate at the UI with next-intl.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d4ffbf and ad7820d.

📒 Files selected for processing (7)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (17 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3 hunks)
  • apps/web/client/src/components/store/editor/chat/context.ts (7 hunks)
  • apps/web/client/src/components/store/editor/chat/conversation.ts (4 hunks)
  • packages/ai/src/prompt/ask.ts (1 hunks)
  • packages/ai/src/tools/tools/branch.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • packages/ai/src/tools/tools/branch.ts
  • packages/ai/src/prompt/ask.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/components/store/editor/chat/context.ts
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • packages/ai/src/tools/tools/branch.ts
  • packages/ai/src/prompt/ask.ts
  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/components/store/editor/chat/conversation.ts
  • apps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
🧠 Learnings (9)
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/**/*.tsx : observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.{ts,tsx} : Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/trpc/react.tsx : Keep tRPC React client provider(s) behind a client boundary (ensure this provider file is a client component)

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Avoid hardcoded user-facing text; use next-intl messages/hooks

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/utils/supabase/client/index.ts : Use the browser Supabase client only in client components

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-16T19:22:52.461Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T19:22:52.461Z
Learning: Applies to apps/web/client/src/trpc/react.tsx : Keep tRPC React client/provider behind a single client boundary in src/trpc/react.tsx

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Do not use process.env in client code; import env from @/env instead

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx
🧬 Code graph analysis (5)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (6)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (18-18)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
packages/models/src/chat/suggestion.ts (1)
  • ChatSuggestion (9-12)
apps/web/client/src/components/tools/tools.ts (1)
  • handleToolCall (186-218)
apps/web/client/src/app/project/[id]/_hooks/use-chat/utils.ts (3)
  • getUserChatMessageFromString (18-35)
  • prepareMessagesForSuggestions (6-16)
  • attachCommitToUserMessage (38-68)
apps/web/client/src/trpc/react.tsx (1)
  • api (23-23)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-input/index.tsx (6)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (18-18)
packages/models/src/chat/suggestion.ts (1)
  • ChatSuggestion (9-12)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • SendMessage (18-18)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
apps/web/client/src/components/store/editor/chat/index.ts (1)
  • sendMessage (43-49)
packages/models/src/chat/message/context.ts (1)
  • ImageMessageContext (38-41)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
  • applyConversations (20-28)
packages/models/src/chat/message/context.ts (2)
  • MessageContext (53-59)
  • ImageMessageContext (38-41)
apps/web/client/src/components/store/editor/chat/context.ts (2)
  • context (31-33)
  • context (35-37)
apps/web/client/src/components/store/editor/chat/conversation.ts (2)
packages/models/src/chat/conversation/index.ts (1)
  • ChatConversation (3-12)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (2)
  • MessageContext (53-59)
  • ErrorMessageContext (43-46)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
🔇 Additional comments (2)
packages/ai/src/tools/tools/branch.ts (1)

4-8: Enforce UUID + brand BranchId — update tests/mocks

  • Evidence: DB and server already treat branch IDs as UUIDs (packages/db/src/schema/canvas/frame.ts — branchId: uuid(...); apps/web/client/src/server/api/routers/project/branch.ts validates with z.string().uuid(); new branches created with uuidv4()).
  • Current: BRANCH_ID_SCHEMA accepts any non-empty string (packages/ai/src/tools/tools/branch.ts).
  • Impact: Switching to z.string().uuid().brand<'BranchId'>() will break many tests/mocks that use non-UUID values (examples: packages/ai/test/tools/read-unit.test.ts, apps/web/client/test/tools/read.test.ts, apps/web/client/test/branch/manager.test.ts).
  • Recommendation: Change BRANCH_ID_SCHEMA to require and brand UUIDs (z.string().uuid().brand<'BranchId'>()) and update tests/mocks to use valid UUIDs or add a test helper to generate/allow test UUIDs. If the system uses CUID2 or a custom format instead, use .cuid2() or a precise .regex(...) and keep the brand.
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)

176-181: Confirm Array.prototype.findLast support or add a polyfill/fallback

Automated checks show no browserslist config or polyfill dependency; ensure your target browsers support Array.prototype.findLast or replace it.

Location: apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (around lines 176–181).

  • Options: add/adjust browserslist so build targets include browsers that support findLast; or add a polyfill (e.g., array.prototype.findlast or core-js); or replace with a safe fallback (reverse loop or messages.slice().reverse().find(...)).

@Kitenite Kitenite merged commit 14a3e54 into onlook-dev:main Sep 18, 2025
1 of 4 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (1)

81-87: Fix contentKey: include streamedMessage.id, not the last non-streaming id.

Prevents stale keys during streaming updates.

-            <ChatMessageList
-                contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${messages?.[messages.length - 1]?.id ?? ''}` : ''}`}
-            >
+            <ChatMessageList
+                contentKey={`${messages.map((m) => m.id).join('|')}${isStreaming ? `|${streamedMessage?.id ?? ''}` : ''}`}
+            >
🧹 Nitpick comments (8)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)

76-81: Don’t depend on a “private” underscore field; expose readiness via ChatManager.

Replace editorEngine.chat._sendMessageAction with a public getter to avoid leaking internals.

Apply in this file:

-        if (creationRequest && processedRequestIdRef.current !== creationRequest.id && isProjectReady && editorEngine.chat._sendMessageAction) {
+        if (creationRequest && processedRequestIdRef.current !== creationRequest.id && isProjectReady && editorEngine.chat.hasSendAction) {

And add this to ChatManager (see inline comment there):

+    get hasSendAction(): boolean {
+        return this._sendMessageAction !== null;
+    }

88-101: Context displayName hardcoded (“user image”).

If this label is ever surfaced in UI, route it through next‑intl or a constants map.


107-116: Avoid redundant conversation fetch.

You already applied/selected a conversation earlier; prefer editorEngine.chat.conversation.current and start a new one if missing, instead of re-querying.

-            const [conversation] = await editorEngine.chat.conversation.getConversations(
-                editorEngine.projectId,
-            );
-            if (!conversation) {
-                throw new Error('No conversation found');
-            }
-            await editorEngine.chat.conversation.selectConversation(conversation.id);
+            const current = editorEngine.chat.conversation.current;
+            if (!current) {
+                await editorEngine.chat.conversation.startNewConversation();
+            }
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (2)

23-26: Ensure this component is under a client boundary.

This file uses React hooks and MobX observer. If not always imported from a client component, add "use client" here.

+ 'use client';
  import type { EditMessage } from '@/app/project/[id]/_hooks/use-chat';

49-55: Remove duplicate keys.

You set keys on child nodes and on the wrapper; keep the key only on the top-level list item to avoid noise.

-                        messageNode = <AssistantMessage key={message.id} message={message} />;
+                        messageNode = <AssistantMessage message={message} />;
@@
-                            <UserMessage
-                                key={message.id}
-                                onEditMessage={onEditMessage}
-                                message={message}
-                            />
+                            <UserMessage onEditMessage={onEditMessage} message={message} />

Also applies to: 63-64

apps/web/client/src/components/store/editor/chat/context.ts (2)

103-108: Drop unnecessary async on getImageContext.

It’s synchronous; removing async avoids needless microtasks.

-    private async getImageContext(): Promise<ImageMessageContext[]> {
+    private getImageContext(): ImageMessageContext[] {
         const imageContext = this.context.filter(
             (context) => context.type === MessageContextType.IMAGE,
         );
         return imageContext;
     }

197-207: Hardcoded user-facing strings in store layer.

‘Project’, ‘Errors - …’, and ‘Unknown Branch’ are UI text. Move labeling to the UI (with next‑intl) and keep store data semantic, or centralize labels in a translatable constants module.

Also applies to: 219-227

apps/web/client/src/components/store/editor/chat/index.ts (1)

13-16: Encapsulate send action; avoid external access to underscored field.

Make _sendMessageAction private and expose a hasSendAction getter used by callers (see use-start-project comment).

-    _sendMessageAction: SendMessage | null = null;
+    private _sendMessageAction: SendMessage | null = null;
@@
     setChatActions(sendMessage: SendMessage) {
         this._sendMessageAction = sendMessage;
     }
+
+    get hasSendAction(): boolean {
+        return this._sendMessageAction !== null;
+    }

Also applies to: 39-41

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad7820d and 042c24e.

📒 Files selected for processing (4)
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (2 hunks)
  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (2 hunks)
  • apps/web/client/src/components/store/editor/chat/context.ts (6 hunks)
  • apps/web/client/src/components/store/editor/chat/index.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Avoid hardcoded user-facing text; use next-intl messages/hooks

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Use path aliases @/* and ~/* for imports mapping to src/*

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/components/store/editor/chat/index.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable identities across renders
Keep the active MobX store in a useRef and clean up asynchronously with setTimeout(() => storeRef.current?.clear(), 0)
Do not use useMemo to create MobX stores
Avoid placing MobX store instances in effect dependency arrays if it causes loops; split concerns instead
observer components must be client components; place a single client boundary at the feature entry; child observers need not repeat 'use client'

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/components/store/editor/chat/index.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
apps/web/client/src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Default to Server Components; add 'use client' only when using events, state/effects, browser APIs, or client-only libs

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Avoid using the any type unless absolutely necessary

Files:

  • apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx
  • apps/web/client/src/components/store/editor/chat/index.ts
  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
  • apps/web/client/src/components/store/editor/chat/context.ts
🧠 Learnings (1)
📚 Learning: 2025-09-14T01:44:21.209Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.209Z
Learning: Applies to apps/web/client/src/**/*.{ts,tsx} : Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx
🧬 Code graph analysis (4)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
  • applyConversations (20-28)
packages/models/src/chat/message/context.ts (2)
  • MessageContext (53-59)
  • ImageMessageContext (38-41)
apps/web/client/src/components/store/editor/chat/context.ts (2)
  • context (31-33)
  • context (35-37)
apps/web/client/src/components/store/editor/chat/index.ts (3)
apps/web/client/src/components/store/editor/chat/conversation.ts (1)
  • ConversationManager (11-156)
apps/web/client/src/components/store/editor/chat/context.ts (1)
  • ChatContext (16-320)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • SendMessage (18-18)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/index.tsx (7)
packages/models/src/chat/message/message.ts (1)
  • ChatMessage (18-18)
apps/web/client/src/app/project/[id]/_hooks/use-chat/index.tsx (1)
  • EditMessage (19-23)
apps/web/client/src/components/store/editor/index.tsx (1)
  • useEditorEngine (10-14)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx (1)
  • UserMessage (29-264)
packages/ui/src/components/chat/chat-message-list.tsx (1)
  • ChatMessageList (53-53)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/stream-message.tsx (1)
  • StreamMessage (6-27)
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/error-message.tsx (1)
  • ErrorMessage (11-63)
apps/web/client/src/components/store/editor/chat/context.ts (2)
packages/models/src/chat/message/context.ts (2)
  • MessageContext (53-59)
  • ErrorMessageContext (43-46)
apps/web/client/src/components/store/editor/engine.ts (1)
  • EditorEngine (33-138)
🔇 Additional comments (1)
apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (1)

102-106: Empty CREATE prompt — confirm server or add a guard.

creationData.context can produce an empty prompt (e.g. only image contexts). Confirm the server accepts empty content for ChatType.CREATE; otherwise prevent sending an empty string (early return) or supply a fallback/default prompt before calling editorEngine.chat.sendMessage(prompt, ChatType.CREATE). Location: apps/web/client/src/app/project/[id]/_hooks/use-start-project.tsx (sendMessage at ~line 116).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants