refactor(site/src/pages/AgentsPage): remove redundant memo and Context.Provider (#23507)

The React Compiler (babel-plugin-react-compiler@1.0.0) handles
memoization automatically for all components in the AgentsPage
compiled path. Three memo() wrappers were redundant:

- ChatMessageItem in ConversationTimeline.tsx
- LazyFileDiff in DiffViewer.tsx
- ChatTreeNode in AgentsSidebar.tsx

Also migrate three Context.Provider usages to the React 19
shorthand (<Context value={...}>) and simplify the EmbedContext
export to use the context directly instead of re-exporting
.Provider as an alias.
This commit is contained in:
Mathias Fredriksson
2026-03-24 18:38:23 +00:00
committed by GitHub
parent 32acc73047
commit d126a86c5d
5 changed files with 302 additions and 309 deletions
+3 -3
View File
@@ -11,7 +11,7 @@ import { Outlet, useParams } from "react-router";
import type { AgentsOutletContext } from "./AgentsPage";
import {
bootstrapChatEmbedSession,
EmbedProvider,
EmbedContext,
} from "./components/EmbedContext";
import type { ChatDetailError } from "./utils/usageLimitMessage";
@@ -178,13 +178,13 @@ const AgentEmbedPage: FC = () => {
if (auth.isSignedIn) {
return (
<EmbedProvider value={{ isEmbedded: true }}>
<EmbedContext value={{ isEmbedded: true }}>
<DashboardProvider>
<ProxyProvider>
<Outlet context={outletContext} />
</ProxyProvider>
</DashboardProvider>
</EmbedProvider>
</EmbedContext>
);
}
@@ -21,7 +21,6 @@ import { FileTextIcon, PencilIcon } from "lucide-react";
import {
type FC,
Fragment,
memo,
type ReactNode,
useEffect,
useLayoutEffect,
@@ -371,7 +370,7 @@ function renderBlockList({
return { elements, renderedToolIDs };
}
const ChatMessageItem = memo<{
interface ChatMessageItemProps {
message: TypesGen.ChatMessage;
parsed: ParsedMessageContent;
onEditUserMessage?: (
@@ -387,250 +386,246 @@ const ChatMessageItem = memo<{
// overlay to indicate truncated content.
fadeFromBottom?: boolean;
urlTransform?: UrlTransform;
}>(
({
message,
parsed,
onEditUserMessage,
editingMessageId,
savingMessageId,
isAfterEditingMessage = false,
fadeFromBottom = false,
}
const ChatMessageItem: FC<ChatMessageItemProps> = ({
message,
parsed,
onEditUserMessage,
editingMessageId,
savingMessageId,
isAfterEditingMessage = false,
fadeFromBottom = false,
urlTransform,
}) => {
const isUser = message.role === "user";
const isSavingMessage = savingMessageId === message.id;
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [previewText, setPreviewText] = useState<string | null>(null);
const toolByID = new Map(parsed.tools.map((tool) => [tool.id, tool]));
if (
parsed.toolResults.length > 0 &&
parsed.toolCalls.length === 0 &&
parsed.markdown === "" &&
parsed.reasoning === ""
) {
return null;
}
// Hide messages that consist entirely of provider-executed
// tool results. The parser skips these parts, so the parsed
// output is empty and would show a "no renderable content"
// fallback.
const parts = message.content ?? [];
if (
parts.length > 0 &&
parts.every((p) => p.type === "tool-result" && p.provider_executed)
) {
return null;
}
const hasRenderableContent =
parsed.blocks.length > 0 ||
parsed.tools.length > 0 ||
parsed.sources.length > 0;
// Pre-compute the inline content for user messages so we
// avoid a filter + map inside the JSX return path.
const userInlineContent = isUser
? parsed.blocks.filter(
(
b,
): b is
| Extract<RenderBlock, { type: "response" }>
| Extract<RenderBlock, { type: "file-reference" }> =>
b.type === "response" || b.type === "file-reference",
)
: [];
const userFileBlocks = isUser
? parsed.blocks.filter(
(b): b is Extract<RenderBlock, { type: "file" }> => b.type === "file",
)
: [];
const hasUserMessageBody =
userInlineContent.length > 0 || Boolean(parsed.markdown?.trim());
const hasFileBlocks = userFileBlocks.length > 0;
const conversationItemProps: { role: "user" | "assistant" } = {
role: isUser ? "user" : "assistant",
};
const { elements: orderedBlocks, renderedToolIDs } = renderBlockList({
blocks: parsed.blocks,
toolByID,
keyPrefix: String(message.id),
onImageClick: setPreviewImage,
onTextFileClick: (content) => setPreviewText(content),
urlTransform,
}) => {
const isUser = message.role === "user";
const isSavingMessage = savingMessageId === message.id;
const [previewImage, setPreviewImage] = useState<string | null>(null);
const [previewText, setPreviewText] = useState<string | null>(null);
const toolByID = new Map(parsed.tools.map((tool) => [tool.id, tool]));
});
const remainingTools = parsed.tools.filter(
(tool) => !renderedToolIDs.has(tool.id),
);
if (
parsed.toolResults.length > 0 &&
parsed.toolCalls.length === 0 &&
parsed.markdown === "" &&
parsed.reasoning === ""
) {
return null;
}
// Hide messages that consist entirely of provider-executed
// tool results. The parser skips these parts, so the parsed
// output is empty and would show a "no renderable content"
// fallback.
const parts = message.content ?? [];
if (
parts.length > 0 &&
parts.every((p) => p.type === "tool-result" && p.provider_executed)
) {
return null;
}
const hasRenderableContent =
parsed.blocks.length > 0 ||
parsed.tools.length > 0 ||
parsed.sources.length > 0;
// Pre-compute the inline content for user messages so we
// avoid a filter + map inside the JSX return path.
const userInlineContent = isUser
? parsed.blocks.filter(
(
b,
): b is
| Extract<RenderBlock, { type: "response" }>
| Extract<RenderBlock, { type: "file-reference" }> =>
b.type === "response" || b.type === "file-reference",
)
: [];
const userFileBlocks = isUser
? parsed.blocks.filter(
(b): b is Extract<RenderBlock, { type: "file" }> => b.type === "file",
)
: [];
const hasUserMessageBody =
userInlineContent.length > 0 || Boolean(parsed.markdown?.trim());
const hasFileBlocks = userFileBlocks.length > 0;
const conversationItemProps: { role: "user" | "assistant" } = {
role: isUser ? "user" : "assistant",
};
const { elements: orderedBlocks, renderedToolIDs } = renderBlockList({
blocks: parsed.blocks,
toolByID,
keyPrefix: String(message.id),
onImageClick: setPreviewImage,
onTextFileClick: (content) => setPreviewText(content),
urlTransform,
});
const remainingTools = parsed.tools.filter(
(tool) => !renderedToolIDs.has(tool.id),
);
return (
<div
className={cn(
isAfterEditingMessage && "opacity-40 pointer-events-none",
"transition-opacity duration-200",
)}
>
<ConversationItem {...conversationItemProps}>
{isUser ? (
<Message className="w-full max-w-none">
<MessageContent
className={cn(
"group/msg rounded-lg border border-solid border-border-default bg-surface-secondary px-3 py-2 font-sans shadow-sm transition-shadow",
editingMessageId === message.id &&
"border-surface-secondary shadow-[0_0_0_2px_hsla(var(--border-warning),0.6)]",
isSavingMessage && "ring-2 ring-content-secondary/40",
fadeFromBottom && "relative overflow-hidden",
return (
<div
className={cn(
isAfterEditingMessage && "opacity-40 pointer-events-none",
"transition-opacity duration-200",
)}
>
<ConversationItem {...conversationItemProps}>
{isUser ? (
<Message className="w-full max-w-none">
<MessageContent
className={cn(
"group/msg rounded-lg border border-solid border-border-default bg-surface-secondary px-3 py-2 font-sans shadow-sm transition-shadow",
editingMessageId === message.id &&
"border-surface-secondary shadow-[0_0_0_2px_hsla(var(--border-warning),0.6)]",
isSavingMessage && "ring-2 ring-content-secondary/40",
fadeFromBottom && "relative overflow-hidden",
)}
style={
fadeFromBottom
? { maxHeight: "var(--clip-h, none)" }
: undefined
}
>
<div className="flex flex-col gap-1.5">
{(hasUserMessageBody || hasFileBlocks) && (
<div className="flex items-start gap-2">
{hasUserMessageBody && (
<span className="min-w-0 flex-1">
{userInlineContent.length > 0
? userInlineContent.map((block, i) =>
block.type === "response" ? (
<Fragment key={i}>{block.text}</Fragment>
) : (
<FileReferenceChip
key={i}
fileName={block.file_name}
startLine={block.start_line}
endLine={block.end_line}
className="mx-1"
/>
),
)
: parsed.markdown || ""}
</span>
)}
{isSavingMessage && (
<Spinner
className="mt-0.5 h-3.5 w-3.5 shrink-0 text-content-secondary"
aria-label="Saving message edit"
loading
/>
)}
{onEditUserMessage && !isSavingMessage && (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="mt-0.5 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-content-secondary opacity-0 transition-opacity hover:bg-surface-tertiary hover:text-content-primary focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link group-hover/msg:opacity-100"
aria-label="Edit message"
onClick={() => {
const fileBlocks = parsed.blocks.filter(
(
b,
): b is Extract<
RenderBlock,
{ type: "file" }
> =>
b.type === "file" &&
(b.media_type.startsWith("image/") ||
b.media_type === "text/plain"),
);
onEditUserMessage(
message.id,
parsed.markdown || "",
fileBlocks.length > 0 ? fileBlocks : undefined,
);
}}
>
<PencilIcon className="size-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="top">Edit message</TooltipContent>
</Tooltip>
)}
</div>
)}
style={
fadeFromBottom
? { maxHeight: "var(--clip-h, none)" }
: undefined
}
>
<div className="flex flex-col gap-1.5">
{(hasUserMessageBody || hasFileBlocks) && (
<div className="flex items-start gap-2">
{hasUserMessageBody && (
<span className="min-w-0 flex-1">
{userInlineContent.length > 0
? userInlineContent.map((block, i) =>
block.type === "response" ? (
<Fragment key={i}>{block.text}</Fragment>
) : (
<FileReferenceChip
key={i}
fileName={block.file_name}
startLine={block.start_line}
endLine={block.end_line}
className="mx-1"
/>
),
)
: parsed.markdown || ""}
</span>
)}
{isSavingMessage && (
<Spinner
className="mt-0.5 h-3.5 w-3.5 shrink-0 text-content-secondary"
aria-label="Saving message edit"
loading
/>
)}
{onEditUserMessage && !isSavingMessage && (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className="mt-0.5 inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md border-none bg-transparent p-0 text-content-secondary opacity-0 transition-opacity hover:bg-surface-tertiary hover:text-content-primary focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-content-link group-hover/msg:opacity-100"
aria-label="Edit message"
onClick={() => {
const fileBlocks = parsed.blocks.filter(
(
b,
): b is Extract<
RenderBlock,
{ type: "file" }
> =>
b.type === "file" &&
(b.media_type.startsWith("image/") ||
b.media_type === "text/plain"),
);
onEditUserMessage(
message.id,
parsed.markdown || "",
fileBlocks.length > 0
? fileBlocks
: undefined,
);
}}
>
<PencilIcon className="size-3.5" />
</button>
</TooltipTrigger>
<TooltipContent side="top">
Edit message
</TooltipContent>
</Tooltip>
)}
</div>
)}
{(() => {
if (userFileBlocks.length === 0) return null;
return (
<div
className={cn(
hasUserMessageBody && "mt-2",
"flex flex-wrap gap-2",
)}
>
{userFileBlocks.map((block, i) =>
renderFileBlock({
block,
key: `user-file-${block.file_id ?? i}`,
onImageClick: setPreviewImage,
onTextFileClick: setPreviewText,
}),
)}
</div>
);
})()}
{fadeFromBottom && (
{(() => {
if (userFileBlocks.length === 0) return null;
return (
<div
className="pointer-events-none absolute inset-x-0 bottom-0 h-1/2 max-h-12"
style={{
background:
"linear-gradient(to top, hsl(var(--surface-secondary)), transparent)",
}}
/>
)}
</div>
</MessageContent>
</Message>
) : (
<Message className="w-full">
<MessageContent className="whitespace-normal">
<div className="space-y-3">
{orderedBlocks}
{remainingTools.map((tool) => (
<Tool
key={tool.id}
name={tool.name}
args={tool.args}
result={tool.result}
status={tool.status}
isError={tool.isError}
/>
))}
{!hasRenderableContent && (
<div className="text-xs text-content-secondary">
Message has no renderable content.
className={cn(
hasUserMessageBody && "mt-2",
"flex flex-wrap gap-2",
)}
>
{userFileBlocks.map((block, i) =>
renderFileBlock({
block,
key: `user-file-${block.file_id ?? i}`,
onImageClick: setPreviewImage,
onTextFileClick: setPreviewText,
}),
)}
</div>
)}
</div>
</MessageContent>
</Message>
)}
</ConversationItem>
{previewImage && (
<ImageLightbox
src={previewImage}
onClose={() => setPreviewImage(null)}
/>
);
})()}
{fadeFromBottom && (
<div
className="pointer-events-none absolute inset-x-0 bottom-0 h-1/2 max-h-12"
style={{
background:
"linear-gradient(to top, hsl(var(--surface-secondary)), transparent)",
}}
/>
)}
</div>
</MessageContent>
</Message>
) : (
<Message className="w-full">
<MessageContent className="whitespace-normal">
<div className="space-y-3">
{orderedBlocks}
{remainingTools.map((tool) => (
<Tool
key={tool.id}
name={tool.name}
args={tool.args}
result={tool.result}
status={tool.status}
isError={tool.isError}
/>
))}
{!hasRenderableContent && (
<div className="text-xs text-content-secondary">
Message has no renderable content.
</div>
)}
</div>
</MessageContent>
</Message>
)}
{previewText !== null && (
<TextPreviewDialog
content={previewText}
onClose={() => setPreviewText(null)}
/>
)}
</div>
);
},
);
</ConversationItem>
{previewImage && (
<ImageLightbox
src={previewImage}
onClose={() => setPreviewImage(null)}
/>
)}
{previewText !== null && (
<TextPreviewDialog
content={previewText}
onClose={() => setPreviewText(null)}
/>
)}
</div>
);
};
export const StreamingOutput: FC<{
streamState: StreamState | null;
@@ -19,7 +19,6 @@ import { ChevronRightIcon } from "lucide-react";
import {
type ComponentProps,
type FC,
memo,
type ReactNode,
useCallback,
useEffect,
@@ -408,11 +407,11 @@ const DiffScrollContainer: FC<{
scrollBarClassName="w-1.5"
viewportClassName="[&>div]:!block"
>
<VirtualizerContext.Provider value={virtualizer}>
<VirtualizerContext value={virtualizer}>
<div ref={contentRef} className="min-w-0 text-xs">
{children}
</div>
</VirtualizerContext.Provider>
</VirtualizerContext>
</ScrollArea>
);
};
@@ -430,72 +429,72 @@ const DiffScrollContainer: FC<{
* FileDiff that the user has already scrolled past, which avoids
* layout shifts and repeated highlighting work.
*/
const LazyFileDiff = memo<{
interface LazyFileDiffProps {
fileDiff: FileDiffMetadata;
options: ComponentProps<typeof FileDiff>["options"];
lineAnnotations?: DiffLineAnnotation<string>[];
renderAnnotation?: (annotation: DiffLineAnnotation<string>) => ReactNode;
selectedLines?: SelectedLineRange | null;
}>(
({
fileDiff,
options,
lineAnnotations,
renderAnnotation: renderAnnotationProp,
selectedLines,
}) => {
const placeholderRef = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
}
useEffect(() => {
const el = placeholderRef.current;
if (!el || visible) {
return;
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true);
observer.disconnect();
}
},
// Pre-load files that are within one viewport-height of
// the visible area so they are ready before the user
// scrolls to them.
{ rootMargin: "100% 0px" },
);
observer.observe(el);
return () => observer.disconnect();
}, [visible]);
const LazyFileDiff: FC<LazyFileDiffProps> = ({
fileDiff,
options,
lineAnnotations,
renderAnnotation: renderAnnotationProp,
selectedLines,
}) => {
const placeholderRef = useRef<HTMLDivElement>(null);
const [visible, setVisible] = useState(false);
if (!visible) {
return (
<div
ref={placeholderRef}
style={{ height: estimateDiffHeight(fileDiff) }}
className="p-4 space-y-2"
>
<Skeleton className="h-4 w-48" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-3/4" />
</div>
);
useEffect(() => {
const el = placeholderRef.current;
if (!el || visible) {
return;
}
return (
<FileDiff
fileDiff={fileDiff}
options={options}
metrics={VIRTUALIZER_METRICS}
style={DIFFS_FONT_STYLE}
lineAnnotations={lineAnnotations}
renderAnnotation={renderAnnotationProp}
selectedLines={selectedLines}
/>
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setVisible(true);
observer.disconnect();
}
},
// Pre-load files that are within one viewport-height of
// the visible area so they are ready before the user
// scrolls to them.
{ rootMargin: "100% 0px" },
);
},
);
observer.observe(el);
return () => observer.disconnect();
}, [visible]);
if (!visible) {
return (
<div
ref={placeholderRef}
style={{ height: estimateDiffHeight(fileDiff) }}
className="p-4 space-y-2"
>
<Skeleton className="h-4 w-48" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-3/4" />
</div>
);
}
return (
<FileDiff
fileDiff={fileDiff}
options={options}
metrics={VIRTUALIZER_METRICS}
style={DIFFS_FONT_STYLE}
lineAnnotations={lineAnnotations}
renderAnnotation={renderAnnotationProp}
selectedLines={selectedLines}
/>
);
};
// -------------------------------------------------------------------
// Main component
@@ -13,7 +13,7 @@ const EmbedContext = createContext<EmbedContextValue>({
isEmbedded: false,
});
export const EmbedProvider = EmbedContext.Provider;
export { EmbedContext };
export const useEmbedContext = () => useContext(EmbedContext);
@@ -59,7 +59,6 @@ import { useDashboard } from "modules/dashboard/useDashboard";
import {
createContext,
type FC,
memo,
useContext,
useEffect,
useRef,
@@ -354,7 +353,7 @@ interface ChatTreeNodeProps {
readonly isChildNode: boolean;
}
const ChatTreeNode = memo<ChatTreeNodeProps>(({ chat, isChildNode }) => {
const ChatTreeNode: FC<ChatTreeNodeProps> = ({ chat, isChildNode }) => {
const {
chatTree,
chatById,
@@ -578,7 +577,7 @@ const ChatTreeNode = memo<ChatTreeNodeProps>(({ chat, isChildNode }) => {
)}
</div>
);
});
};
export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
const {
@@ -786,7 +785,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
</div>
</>
) : (
<ChatTreeContext.Provider value={chatTreeCtx}>
<ChatTreeContext value={chatTreeCtx}>
{visibleRootIDs.length === 0 ? (
<div className="rounded-lg border border-dashed border-border-default bg-surface-primary p-4 text-center text-xs text-content-secondary">
<p className="m-0">
@@ -889,7 +888,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
isFetchingNextPage={isFetchingNextPage}
/>
)}
</ChatTreeContext.Provider>
</ChatTreeContext>
)}
</div>
</ScrollArea>