Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ jobs:

# - uses: oven-sh/setup-bun@v1
# with:
# bun-version: latest

# bun-version: 1.2.21
# - name: Cache dependencies
# uses: actions/cache@v4
# with:
Expand All @@ -41,7 +40,7 @@ jobs:

- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
bun-version: 1.2.21

- name: Cache dependencies
uses: actions/cache@v4
Expand All @@ -65,7 +64,7 @@ jobs:

- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
bun-version: 1.2.21

- name: Cache dependencies
uses: actions/cache@v4
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { ChatMessage } from '@onlook/models';
import { observer } from 'mobx-react-lite';
import { memo } from 'react';
import { MessageContent } from './message-content';

export const AssistantMessage = ({ message, isStreaming }: { message: ChatMessage, isStreaming: boolean }) => {
const AssistantMessageComponent = ({ message, isStreaming }: { message: ChatMessage, isStreaming: boolean }) => {
return (
<div className="px-4 py-2 text-small content-start flex flex-col text-wrap gap-2">
<MessageContent
Expand All @@ -13,3 +15,5 @@ export const AssistantMessage = ({ message, isStreaming }: { message: ChatMessag
</div>
);
};

export const AssistantMessage = memo(observer(AssistantMessageComponent));
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import type { ChatMessage } from '@onlook/models';
import { Reasoning, ReasoningContent, ReasoningTrigger, Response } from '@onlook/ui/ai-elements';
import { cn } from '@onlook/ui/utils';
import type { ToolUIPart } from 'ai';
import { observer } from 'mobx-react-lite';
import { ToolCallDisplay } from './tool-call-display';

export const MessageContent = ({
const MessageContentComponent = ({
messageId,
parts,
applied,
Expand Down Expand Up @@ -69,3 +70,5 @@ export const MessageContent = ({
</div>
);
};

export const MessageContent = observer(MessageContentComponent);
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { FuzzyEditFileTool, SearchReplaceEditTool, SearchReplaceMultiEditFileTool, TerminalCommandTool, TypecheckTool, WebSearchTool, WriteFileTool } from '@onlook/ai';
import type { WebSearchResult } from '@onlook/models';
import type { ToolUIPart } from 'ai';
import { observer } from 'mobx-react-lite';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify the necessity of the observer wrapper and consider adding memoization.

The component is now wrapped with observer(), but scanning the implementation reveals no obvious access to MobX observable properties. All props appear to be plain values (strings, booleans, ToolUIPart), and the component body only accesses standard properties like toolPart.type, toolPart.state, etc.

Additionally, other components in this PR (mentioned in the AI summary) follow a memo(observer(...)) pattern for consistency and performance, but this component only uses observer().

Run the following script to verify if ToolUIPart or its accessed properties contain MobX observables:

If ToolUIPart does not contain observables, consider removing the observer() wrapper. Otherwise, for consistency with other components in this PR, apply this pattern:

-import { observer } from 'mobx-react-lite';
+import { memo } from 'react';
+import { observer } from 'mobx-react-lite';

-export const ToolCallDisplay = observer(ToolCallDisplayComponent);
+export const ToolCallDisplay = memo(observer(ToolCallDisplayComponent));

Also applies to: 12-12, 230-230


🏁 Script executed:

#!/bin/bash
# Check if ToolUIPart type contains MobX observable definitions

# Search for ToolUIPart type definition
ast-grep --pattern 'type ToolUIPart = $$$'

# Also search for interface definition
ast-grep --pattern 'interface ToolUIPart {
  $$$
}'

# Check for MobX observable usage in types
rg -n --type=ts --type=tsx 'ToolUIPart' -A 10 -B 2

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Correct search for ToolUIPart type definition using proper ripgrep syntax

# Search for ToolUIPart type definition in TypeScript files
echo "=== Searching for ToolUIPart type/interface definition ==="
rg -n 'type ToolUIPart|interface ToolUIPart' -A 15

# Search for where ToolUIPart is imported/used in the tool-call-display file
echo -e "\n=== Checking imports in tool-call-display.tsx ==="
rg -n 'ToolUIPart' apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-display.tsx -B 5 -A 3

# Look for MobX observable patterns in ToolUIPart related files
echo -e "\n=== Checking for MobX observable patterns ==="
rg -n '@observable|makeObservable|observable\(' -A 3 -B 1

Length of output: 2019


🏁 Script executed:

#!/bin/bash
# View the actual component implementation to check for MobX store/observable access

echo "=== Full tool-call-display.tsx component ==="
cat apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-display.tsx

echo -e "\n=== Check for store hooks or observable access patterns ==="
rg -n 'useStore|useObserver|observer\(|\.get\(\)|@observable' apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-display.tsx

Length of output: 8589


Remove the unnecessary observer() wrapper—this component does not access any MobX observables.

The component only receives plain props (strings, boolean, ToolUIPart type from the ai package) and accesses standard property values without any MobX store hooks or observable patterns. Wrapping a component with observer() when it doesn't access observables adds unnecessary overhead without providing any benefit. The applied prop is passed to child components as a plain prop, not indicating MobX reactivity tracking in this component.

Remove the observer wrapper and import:

-import { observer } from 'mobx-react-lite';
 import stripAnsi from 'strip-ansi';

-export const ToolCallDisplay = observer(ToolCallDisplayComponent);
+export const ToolCallDisplay = ToolCallDisplayComponent;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-display.tsx
around line 4, remove the unnecessary MobX observer usage: delete the import of
observer from 'mobx-react-lite' and remove the observer(...) wrapper around the
component export so the component is a plain React component; ensure any imports
or references to observer are removed and update the export/default export
accordingly to return the component directly.

import stripAnsi from 'strip-ansi';
import { type z } from 'zod';
import { BashCodeDisplay } from '../../code-display/bash-code-display';
import { CollapsibleCodeBlock } from '../../code-display/collapsible-code-block';
import { SearchSourcesDisplay } from '../../code-display/search-sources-display';
import { ToolCallSimple } from './tool-call-simple';

export const ToolCallDisplay = ({
const ToolCallDisplayComponent = ({
messageId,
toolPart,
isStream,
Expand Down Expand Up @@ -224,4 +225,6 @@ export const ToolCallDisplay = ({
key={toolPart.toolCallId}
/>
);
}
};

export const ToolCallDisplay = observer(ToolCallDisplayComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { BaseTool, TOOLS_MAP } from '@onlook/ai';
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@onlook/ui/ai-elements';
import { Icons } from '@onlook/ui/icons';
import type { ToolUIPart } from 'ai';
import { memo } from 'react';

export function ToolCallSimple({
const ToolCallSimpleComponent = ({
toolPart,
className,
loading,
}: {
toolPart: ToolUIPart;
className?: string;
loading?: boolean;
}) {
}) => {
const toolName = toolPart.type.split('-')[1] ?? '';
const ToolClass = TOOLS_MAP.get(toolName);
const Icon = ToolClass?.icon ?? Icons.QuestionMarkCircled;
Expand All @@ -21,12 +22,14 @@ export function ToolCallSimple({
<Tool className={className}>
<ToolHeader loading={loading} title={title} type={toolPart.type} state={toolPart.state} icon={<Icon className="w-4 h-4 flex-shrink-0" />} />
<ToolContent>
<ToolInput input={toolPart.input} />
<ToolOutput errorText={toolPart.errorText} output={toolPart.output} />
<ToolInput input={toolPart.input} isStreaming={loading} />
<ToolOutput errorText={toolPart.errorText} output={toolPart.output} isStreaming={loading} />
</ToolContent>
</Tool>
)
}
);
};

export const ToolCallSimple = memo(ToolCallSimpleComponent);

function getDefaultToolLabel(toolName: string): string {
return toolName?.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react';
import React, { memo, useEffect, useRef, useState } from 'react';
import { nanoid } from 'nanoid';

import type { ChatMessage, GitMessageCheckpoint } from '@onlook/models';
Expand All @@ -21,6 +21,7 @@ import { cn } from '@onlook/ui/utils';
import type { EditMessage } from '@/app/project/[id]/_hooks/use-chat';
import { useEditorEngine } from '@/components/store/editor';
import { restoreCheckpoint } from '@/components/store/editor/git';
import { observer } from 'mobx-react-lite';
import { SentContextPill } from '../context-pills/sent-context-pill';
import { MessageContent } from './message-content';
import { MultiBranchRevertModal } from './multi-branch-revert-modal';
Expand All @@ -41,7 +42,7 @@ export const getUserMessageContent = (message: ChatMessage) => {
.join('');
};

export const UserMessage = ({ onEditMessage, message }: UserMessageProps) => {
const UserMessageComponent = ({ onEditMessage, message }: UserMessageProps) => {
const editorEngine = useEditorEngine();
const [isCopied, setIsCopied] = useState(false);
const [isEditing, setIsEditing] = useState(false);
Expand Down Expand Up @@ -328,3 +329,5 @@ export const UserMessage = ({ onEditMessage, message }: UserMessageProps) => {
</div>
);
};

export const UserMessage = memo(observer(UserMessageComponent));
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@onlook/ui/
import { Icons } from '@onlook/ui/icons';
import { cn, getTruncatedFileName } from '@onlook/ui/utils';
import { AnimatePresence, motion } from 'motion/react';
import { useState } from 'react';
import { observer } from 'mobx-react-lite';
import { memo, useState } from 'react';

interface CollapsibleCodeBlockProps {
path: string;
Expand All @@ -16,7 +17,7 @@ interface CollapsibleCodeBlockProps {
branchId?: string;
}

export const CollapsibleCodeBlock = ({
const CollapsibleCodeBlockComponent = ({
path,
content,
isStream,
Expand All @@ -36,6 +37,10 @@ export const CollapsibleCodeBlock = ({
return isOpen ? { height: 'auto', opacity: 1 } : { height: 0, opacity: 0 };
};

const branch = branchId
? editorEngine.branches.allBranches.find(b => b.id === branchId)
: editorEngine.branches.activeBranch;

return (
<div className="group relative">
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
Expand Down Expand Up @@ -70,16 +75,11 @@ export const CollapsibleCodeBlock = ({
)}
>
<span className="truncate flex-1 min-w-0">{getTruncatedFileName(path)}</span>
{(() => {
const branch = branchId
? editorEngine.branches.allBranches.find(b => b.id === branchId)
: editorEngine.branches.activeBranch;
return branch && (
<span className="text-foreground-tertiary group-hover:text-foreground-secondary text-mini ml-0.5 flex-shrink-0 truncate max-w-24">
{' • '}{branch.name}
</span>
);
})()}
{branch && (
<span className="text-foreground-tertiary group-hover:text-foreground-secondary text-mini ml-0.5 flex-shrink-0 truncate max-w-24">
{' • '}{branch.name}
</span>
)}
</div>
</div>
</CollapsibleTrigger>
Expand All @@ -93,28 +93,32 @@ export const CollapsibleCodeBlock = ({
transition={{ duration: 0.2, ease: 'easeInOut' }}
style={{ overflow: 'hidden' }}
>
<div className="border-t">
<CodeBlock code={content} language="jsx" className="text-xs overflow-x-auto" />
<div className="flex justify-end gap-1.5 p-1 border-t"> <Button
size="sm"
variant="ghost"
className="h-7 px-2 text-foreground-secondary hover:text-foreground font-sans select-none"
onClick={copyToClipboard}
>
{copied ? (
<>
<Icons.Check className="h-4 w-4 mr-2" />
Copied
</>
) : (
<>
<Icons.Copy className="h-4 w-4 mr-2" />
Copy
</>
)}
</Button>
{/* Only render this content when open to avoid rendering the expensive code block. */}
{isOpen && (
<div className="border-t">
<CodeBlock code={content} language="jsx" isStreaming={isStream} className="text-xs overflow-x-auto" />
<div className="flex justify-end gap-1.5 p-1 border-t">
<Button
size="sm"
variant="ghost"
className="h-7 px-2 text-foreground-secondary hover:text-foreground font-sans select-none"
onClick={copyToClipboard}
>
{copied ? (
<>
<Icons.Check className="h-4 w-4 mr-2" />
Copied
</>
) : (
<>
<Icons.Copy className="h-4 w-4 mr-2" />
Copy
</>
)}
</Button>
</div>
</div>
</div>
)}
</motion.div>
</AnimatePresence>
</CollapsibleContent>
Expand All @@ -123,3 +127,5 @@ export const CollapsibleCodeBlock = ({
</div >
);
};

export const CollapsibleCodeBlock = memo(observer(CollapsibleCodeBlockComponent));
48 changes: 11 additions & 37 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,8 @@
},
},
"apps/admin": {
"name": "@onlook/admin",
"version": "0.1.0",
"dependencies": {
"@onlook/db": "*",
"@onlook/ui": "*",
"@onlook/utility": "*",
"@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.45.4",
"@t3-oss/env-nextjs": "^0.12.0",
"@tanstack/react-query": "^5.69.0",
"@trpc/client": "^11.0.0",
"@trpc/react-query": "^11.0.0",
"@trpc/server": "^11.0.0",
"next": ">=15.5.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"server-only": "^0.0.1",
"superjson": "^2.2.1",
"zod": "^4.1.3",
},
"devDependencies": {
"@onlook/eslint": "*",
"@onlook/typescript": "*",
"@tailwindcss/postcss": "^4.0.15",
"@types/node": "^20.14.10",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"eslint": "^9.0.0",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.15",
"typescript": "^5.5.4",
},
"name": "admin",
"version": "0.0.0",
},
"apps/backend": {
"name": "@onlook/backend",
Expand Down Expand Up @@ -1350,8 +1320,6 @@

"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],

"@onlook/admin": ["@onlook/admin@workspace:apps/admin"],

"@onlook/ai": ["@onlook/ai@workspace:packages/ai"],

"@onlook/backend": ["@onlook/backend@workspace:apps/backend"],
Expand Down Expand Up @@ -1876,7 +1844,7 @@

"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],

"@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],
"@types/node": ["@types/node@22.15.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-K0fpC/ZVeb8G9rm7bH7vI0KAec4XHEhBam616nVJCV51bKzJ6oA3luG4WdKoaztxe70QaNjS/xBmcDLmr4PiGw=="],

"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],

Expand Down Expand Up @@ -2008,6 +1976,8 @@

"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],

"admin": ["admin@workspace:apps/admin"],

"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],

"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
Expand Down Expand Up @@ -4678,8 +4648,6 @@

"@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.4.4-cjs.2", "", { "dependencies": { "@octokit/types": "^13.7.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw=="],

"@onlook/docs/@types/node": ["@types/node@22.15.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-K0fpC/ZVeb8G9rm7bH7vI0KAec4XHEhBam616nVJCV51bKzJ6oA3luG4WdKoaztxe70QaNjS/xBmcDLmr4PiGw=="],

"@onlook/email/react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],

"@onlook/email/react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
Expand All @@ -4690,6 +4658,10 @@

"@onlook/github/@types/node": ["@types/node@18.19.127", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA=="],

"@onlook/prettier/@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],

"@onlook/scripts/@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],

"@onlook/stripe/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],

"@onlook/ui/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
Expand All @@ -4700,6 +4672,8 @@

"@onlook/web-client/@types/culori": ["@types/culori@4.0.1", "", {}, "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ=="],

"@onlook/web-client/@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],

"@onlook/web-client/ai": ["ai@5.0.26", "", { "dependencies": { "@ai-sdk/gateway": "1.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-bGNtG+nYQ2U+5mzuLbxIg9WxGQJ2u5jv2gYgP8C+CJ1YI4qqIjvjOgGEZWzvNet8jiOGIlqstsht9aQefKzmBw=="],

"@onlook/web-client/lucide-react": ["lucide-react@0.486.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-xWop/wMsC1ikiEVLZrxXjPKw4vU/eAip33G2mZHgbWnr4Nr5Rt4Vx4s/q1D3B/rQVbxjOuqASkEZcUxDEKzecw=="],
Expand Down
Loading
Loading