refactor(ui): improve message visibility filtering and optimize bundle

- Refactor message filtering logic across AgentExecution, ClaudeCodeSession, SessionOutputViewer, and StreamMessage components to better handle visibility of user messages with tool results
- Replace inefficient every() loop with for-loop for better performance when checking message content visibility
- Add renderedSomething tracking in StreamMessage to prevent rendering empty components
- Optimize SessionOutputViewer with useMemo for displayableMessages filtering
- Remove unused GitHub API response fields (url, html_url, git_url) in Rust agents command
- Add Vite build configuration with manual code splitting for better bundle optimization
- Remove TypeScript expect-error comment for process global variable

These changes improve UI performance by reducing unnecessary re-renders and bundle size through better code splitting.
This commit is contained in:
Mufeed VH 2025-06-25 00:52:07 +05:30
parent e0a0ddf6ba
commit 5d69b449be
6 changed files with 176 additions and 88 deletions

View file

@ -1952,9 +1952,6 @@ struct GitHubApiResponse {
path: String, path: String,
sha: String, sha: String,
size: i64, size: i64,
url: String,
html_url: String,
git_url: String,
download_url: Option<String>, download_url: Option<String>,
#[serde(rename = "type")] #[serde(rename = "type")]
file_type: String, file_type: String,

View file

@ -103,45 +103,54 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
// Skip empty user messages // Skip empty user messages
if (message.type === "user" && message.message) { if (message.type === "user" && message.message) {
if (message.isMeta) return false;
const msg = message.message; const msg = message.message;
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) { if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
return false; return false;
} }
// Check if this is a user message with only tool results that are already displayed // Check if user message has visible content by checking its parts
if (Array.isArray(msg.content)) { if (Array.isArray(msg.content)) {
const hasOnlyHiddenToolResults = msg.content.every((content: any) => { let hasVisibleContent = false;
if (content.type !== "tool_result") return false; for (const content of msg.content) {
if (content.type === "text") {
// Check if this tool result should be hidden hasVisibleContent = true;
let hasCorrespondingWidget = false; break;
if (content.tool_use_id) { } else if (content.type === "tool_result") {
// Look for the matching tool_use in previous assistant messages // Check if this tool result will be skipped by a widget
for (let i = index - 1; i >= 0; i--) { let willBeSkipped = false;
const prevMsg = messages[i]; if (content.tool_use_id) {
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { // Look for the matching tool_use in previous assistant messages
const toolUse = prevMsg.message.content.find((c: any) => for (let i = index - 1; i >= 0; i--) {
c.type === 'tool_use' && c.id === content.tool_use_id const prevMsg = messages[i];
); if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
if (toolUse) { const toolUse = prevMsg.message.content.find((c: any) =>
const toolName = toolUse.name?.toLowerCase(); c.type === 'tool_use' && c.id === content.tool_use_id
const toolsWithWidgets = [ );
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', if (toolUse) {
'glob', 'bash', 'write', 'grep' const toolName = toolUse.name?.toLowerCase();
]; const toolsWithWidgets = [
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { 'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
hasCorrespondingWidget = true; 'glob', 'bash', 'write', 'grep'
];
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
willBeSkipped = true;
}
break;
} }
break;
} }
} }
} }
if (!willBeSkipped) {
hasVisibleContent = true;
break;
}
} }
}
return hasCorrespondingWidget && !content.is_error;
});
if (hasOnlyHiddenToolResults) { if (!hasVisibleContent) {
return false; return false;
} }
} }

View file

@ -117,52 +117,57 @@ export const ClaudeCodeSession: React.FC<ClaudeCodeSessionProps> = ({
return false; return false;
} }
// Skip empty user messages // Skip user messages that only contain tool results that are already displayed
if (message.type === "user" && message.message) { if (message.type === "user" && message.message) {
if (message.isMeta) return false;
const msg = message.message; const msg = message.message;
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) { if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
return false; return false;
} }
// Check if this is a user message with only tool results that are already displayed
if (Array.isArray(msg.content)) { if (Array.isArray(msg.content)) {
const hasOnlyHiddenToolResults = msg.content.every((content: any) => { let hasVisibleContent = false;
if (content.type !== "tool_result") return false; for (const content of msg.content) {
if (content.type === "text") {
// Check if this tool result should be hidden hasVisibleContent = true;
let hasCorrespondingWidget = false; break;
if (content.tool_use_id) { }
// Look for the matching tool_use in previous assistant messages if (content.type === "tool_result") {
for (let i = index - 1; i >= 0; i--) { let willBeSkipped = false;
const prevMsg = messages[i]; if (content.tool_use_id) {
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { // Look for the matching tool_use in previous assistant messages
const toolUse = prevMsg.message.content.find((c: any) => for (let i = index - 1; i >= 0; i--) {
c.type === 'tool_use' && c.id === content.tool_use_id const prevMsg = messages[i];
); if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
if (toolUse) { const toolUse = prevMsg.message.content.find((c: any) =>
const toolName = toolUse.name?.toLowerCase(); c.type === 'tool_use' && c.id === content.tool_use_id
const toolsWithWidgets = [ );
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read', if (toolUse) {
'glob', 'bash', 'write', 'grep' const toolName = toolUse.name?.toLowerCase();
]; const toolsWithWidgets = [
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { 'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
hasCorrespondingWidget = true; 'glob', 'bash', 'write', 'grep'
];
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
willBeSkipped = true;
}
break;
} }
break;
} }
} }
} }
if (!willBeSkipped) {
hasVisibleContent = true;
break;
}
} }
}
return hasCorrespondingWidget && !content.is_error; if (!hasVisibleContent) {
});
if (hasOnlyHiddenToolResults) {
return false; return false;
} }
} }
} }
return true; return true;
}); });
}, [messages]); }, [messages]);

View file

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { X, Maximize2, Minimize2, Copy, RefreshCw, RotateCcw, ChevronDown } from 'lucide-react'; import { X, Maximize2, Minimize2, Copy, RefreshCw, RotateCcw, ChevronDown } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -282,6 +282,47 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
loadOutput(); loadOutput();
}, [session.id]); }, [session.id]);
const displayableMessages = useMemo(() => {
return messages.filter((message, index) => {
if (message.isMeta && !message.leafUuid && !message.summary) return false;
if (message.type === "user" && message.message) {
if (message.isMeta) return false;
const msg = message.message;
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) return false;
if (Array.isArray(msg.content)) {
let hasVisibleContent = false;
for (const content of msg.content) {
if (content.type === "text") { hasVisibleContent = true; break; }
if (content.type === "tool_result") {
let willBeSkipped = false;
if (content.tool_use_id) {
for (let i = index - 1; i >= 0; i--) {
const prevMsg = messages[i];
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
const toolUse = prevMsg.message.content.find((c: any) => c.type === 'tool_use' && c.id === content.tool_use_id);
if (toolUse) {
const toolName = toolUse.name?.toLowerCase();
const toolsWithWidgets = ['task','edit','multiedit','todowrite','ls','read','glob','bash','write','grep'];
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
willBeSkipped = true;
}
break;
}
}
}
}
if (!willBeSkipped) { hasVisibleContent = true; break; }
}
}
if (!hasVisibleContent) return false;
}
}
return true;
});
}, [messages]);
return ( return (
<> <>
@ -432,7 +473,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
) : ( ) : (
<> <>
<AnimatePresence> <AnimatePresence>
{messages.map((message: ClaudeStreamMessage, index: number) => ( {displayableMessages.map((message: ClaudeStreamMessage, index: number) => (
<motion.div <motion.div
key={index} key={index}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
@ -556,7 +597,7 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
) : ( ) : (
<> <>
<AnimatePresence> <AnimatePresence>
{messages.map((message: ClaudeStreamMessage, index: number) => ( {displayableMessages.map((message: ClaudeStreamMessage, index: number) => (
<motion.div <motion.div
key={index} key={index}
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}

View file

@ -100,7 +100,9 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
if (message.type === "assistant" && message.message) { if (message.type === "assistant" && message.message) {
const msg = message.message; const msg = message.message;
return ( let renderedSomething = false;
const renderedCard = (
<Card className={cn("border-primary/20 bg-primary/5", className)}> <Card className={cn("border-primary/20 bg-primary/5", className)}>
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
@ -114,6 +116,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
? content.text ? content.text
: (content.text?.text || JSON.stringify(content.text || content)); : (content.text?.text || JSON.stringify(content.text || content));
renderedSomething = true;
return ( return (
<div key={idx} className="prose prose-sm dark:prose-invert max-w-none"> <div key={idx} className="prose prose-sm dark:prose-invert max-w-none">
<ReactMarkdown <ReactMarkdown
@ -157,56 +160,67 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
const renderToolWidget = () => { const renderToolWidget = () => {
// Task tool - for sub-agent tasks // Task tool - for sub-agent tasks
if (toolName === "task" && input) { if (toolName === "task" && input) {
renderedSomething = true;
return <TaskWidget description={input.description} prompt={input.prompt} result={toolResult} />; return <TaskWidget description={input.description} prompt={input.prompt} result={toolResult} />;
} }
// Edit tool // Edit tool
if (toolName === "edit" && input?.file_path) { if (toolName === "edit" && input?.file_path) {
renderedSomething = true;
return <EditWidget {...input} result={toolResult} />; return <EditWidget {...input} result={toolResult} />;
} }
// MultiEdit tool // MultiEdit tool
if (toolName === "multiedit" && input?.file_path && input?.edits) { if (toolName === "multiedit" && input?.file_path && input?.edits) {
renderedSomething = true;
return <MultiEditWidget {...input} result={toolResult} />; return <MultiEditWidget {...input} result={toolResult} />;
} }
// MCP tools (starting with mcp__) // MCP tools (starting with mcp__)
if (content.name?.startsWith("mcp__")) { if (content.name?.startsWith("mcp__")) {
renderedSomething = true;
return <MCPWidget toolName={content.name} input={input} result={toolResult} />; return <MCPWidget toolName={content.name} input={input} result={toolResult} />;
} }
// TodoWrite tool // TodoWrite tool
if (toolName === "todowrite" && input?.todos) { if (toolName === "todowrite" && input?.todos) {
renderedSomething = true;
return <TodoWidget todos={input.todos} result={toolResult} />; return <TodoWidget todos={input.todos} result={toolResult} />;
} }
// LS tool // LS tool
if (toolName === "ls" && input?.path) { if (toolName === "ls" && input?.path) {
renderedSomething = true;
return <LSWidget path={input.path} result={toolResult} />; return <LSWidget path={input.path} result={toolResult} />;
} }
// Read tool // Read tool
if (toolName === "read" && input?.file_path) { if (toolName === "read" && input?.file_path) {
renderedSomething = true;
return <ReadWidget filePath={input.file_path} result={toolResult} />; return <ReadWidget filePath={input.file_path} result={toolResult} />;
} }
// Glob tool // Glob tool
if (toolName === "glob" && input?.pattern) { if (toolName === "glob" && input?.pattern) {
renderedSomething = true;
return <GlobWidget pattern={input.pattern} result={toolResult} />; return <GlobWidget pattern={input.pattern} result={toolResult} />;
} }
// Bash tool // Bash tool
if (toolName === "bash" && input?.command) { if (toolName === "bash" && input?.command) {
renderedSomething = true;
return <BashWidget command={input.command} description={input.description} result={toolResult} />; return <BashWidget command={input.command} description={input.description} result={toolResult} />;
} }
// Write tool // Write tool
if (toolName === "write" && input?.file_path && input?.content) { if (toolName === "write" && input?.file_path && input?.content) {
renderedSomething = true;
return <WriteWidget filePath={input.file_path} content={input.content} result={toolResult} />; return <WriteWidget filePath={input.file_path} content={input.content} result={toolResult} />;
} }
// Grep tool // Grep tool
if (toolName === "grep" && input?.pattern) { if (toolName === "grep" && input?.pattern) {
renderedSomething = true;
return <GrepWidget pattern={input.pattern} include={input.include} path={input.path} exclude={input.exclude} result={toolResult} />; return <GrepWidget pattern={input.pattern} include={input.include} path={input.path} exclude={input.exclude} result={toolResult} />;
} }
@ -217,10 +231,12 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
// Render the tool widget // Render the tool widget
const widget = renderToolWidget(); const widget = renderToolWidget();
if (widget) { if (widget) {
renderedSomething = true;
return <div key={idx}>{widget}</div>; return <div key={idx}>{widget}</div>;
} }
// Fallback to basic tool display // Fallback to basic tool display
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -252,6 +268,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
</CardContent> </CardContent>
</Card> </Card>
); );
if (!renderedSomething) return null;
return renderedCard;
} }
// User message // User message
@ -261,12 +279,9 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
const msg = message.message; const msg = message.message;
// Skip empty user messages let renderedSomething = false;
if (!msg.content || (Array.isArray(msg.content) && msg.content.length === 0)) {
return null;
}
return ( const renderedCard = (
<Card className={cn("border-muted-foreground/20 bg-muted/20", className)}> <Card className={cn("border-muted-foreground/20 bg-muted/20", className)}>
<CardContent className="p-4"> <CardContent className="p-4">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
@ -276,6 +291,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
{typeof msg.content === 'string' && ( {typeof msg.content === 'string' && (
(() => { (() => {
const contentStr = msg.content as string; const contentStr = msg.content as string;
if (contentStr.trim() === '') return null;
renderedSomething = true;
// Check if it's a command message // Check if it's a command message
const commandMatch = contentStr.match(/<command-name>(.+?)<\/command-name>[\s\S]*?<command-message>(.+?)<\/command-message>[\s\S]*?<command-args>(.*?)<\/command-args>/); const commandMatch = contentStr.match(/<command-name>(.+?)<\/command-name>[\s\S]*?<command-message>(.+?)<\/command-message>[\s\S]*?<command-args>(.*?)<\/command-args>/);
@ -310,27 +327,16 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
{Array.isArray(msg.content) && msg.content.map((content: any, idx: number) => { {Array.isArray(msg.content) && msg.content.map((content: any, idx: number) => {
// Tool result // Tool result
if (content.type === "tool_result") { if (content.type === "tool_result") {
// Skip rendering tool results that are already displayed by tool widgets // Skip duplicate tool_result if a dedicated widget is present
// We need to check if this result corresponds to a tool that has its own widget
// Find the corresponding tool use for this result
let hasCorrespondingWidget = false; let hasCorrespondingWidget = false;
if (content.tool_use_id && streamMessages) { if (content.tool_use_id && streamMessages) {
// Look for the matching tool_use in previous assistant messages
for (let i = streamMessages.length - 1; i >= 0; i--) { for (let i = streamMessages.length - 1; i >= 0; i--) {
const prevMsg = streamMessages[i]; const prevMsg = streamMessages[i];
if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) { if (prevMsg.type === 'assistant' && prevMsg.message?.content && Array.isArray(prevMsg.message.content)) {
const toolUse = prevMsg.message.content.find((c: any) => const toolUse = prevMsg.message.content.find((c: any) => c.type === 'tool_use' && c.id === content.tool_use_id);
c.type === 'tool_use' && c.id === content.tool_use_id
);
if (toolUse) { if (toolUse) {
const toolName = toolUse.name?.toLowerCase(); const toolName = toolUse.name?.toLowerCase();
// List of tools that display their own results const toolsWithWidgets = ['task','edit','multiedit','todowrite','ls','read','glob','bash','write','grep'];
const toolsWithWidgets = [
'task', 'edit', 'multiedit', 'todowrite', 'ls', 'read',
'glob', 'bash', 'write', 'grep'
];
// Also check for MCP tools
if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) { if (toolsWithWidgets.includes(toolName) || toolUse.name?.startsWith('mcp__')) {
hasCorrespondingWidget = true; hasCorrespondingWidget = true;
} }
@ -339,9 +345,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
} }
} }
} }
// If the tool has its own widget that displays results, skip rendering the duplicate if (hasCorrespondingWidget) {
if (hasCorrespondingWidget && !content.is_error) {
return null; return null;
} }
// Extract the actual content string // Extract the actual content string
@ -370,6 +375,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
const beforeReminder = contentText.substring(0, reminderMatch.index || 0).trim(); const beforeReminder = contentText.substring(0, reminderMatch.index || 0).trim();
const afterReminder = contentText.substring((reminderMatch.index || 0) + reminderMatch[0].length).trim(); const afterReminder = contentText.substring((reminderMatch.index || 0) + reminderMatch[0].length).trim();
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -404,6 +410,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
const isEditResult = contentText.includes("has been updated. Here's the result of running `cat -n`"); const isEditResult = contentText.includes("has been updated. Here's the result of running `cat -n`");
if (isEditResult) { if (isEditResult) {
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -421,6 +428,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
contentText.includes("Applied multiple edits to"); contentText.includes("Applied multiple edits to");
if (isMultiEditResult) { if (isMultiEditResult) {
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -470,6 +478,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
})(); })();
if (isLSResult) { if (isLSResult) {
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -508,6 +517,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
} }
} }
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -521,6 +531,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
// Handle empty tool results // Handle empty tool results
if (!contentText || contentText.trim() === '') { if (!contentText || contentText.trim() === '') {
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -534,6 +545,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
); );
} }
renderedSomething = true;
return ( return (
<div key={idx} className="space-y-2"> <div key={idx} className="space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -560,6 +572,7 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
? content.text ? content.text
: (content.text?.text || JSON.stringify(content.text)); : (content.text?.text || JSON.stringify(content.text));
renderedSomething = true;
return ( return (
<div key={idx} className="text-sm"> <div key={idx} className="text-sm">
{textContent} {textContent}
@ -574,6 +587,8 @@ const StreamMessageComponent: React.FC<StreamMessageProps> = ({ message, classNa
</CardContent> </CardContent>
</Card> </Card>
); );
if (!renderedSomething) return null;
return renderedCard;
} }
// Result message - render with markdown // Result message - render with markdown

View file

@ -3,7 +3,6 @@ import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL } from "node:url";
// @ts-expect-error process is a nodejs global
const host = process.env.TAURI_DEV_HOST; const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/ // https://vitejs.dev/config/
@ -38,4 +37,26 @@ export default defineConfig(async () => ({
ignored: ["**/src-tauri/**"], ignored: ["**/src-tauri/**"],
}, },
}, },
// Build configuration for code splitting
build: {
// Increase chunk size warning limit to 2000 KB
chunkSizeWarningLimit: 2000,
rollupOptions: {
output: {
// Manual chunks for better code splitting
manualChunks: {
// Vendor chunks
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-select', '@radix-ui/react-tabs', '@radix-ui/react-tooltip', '@radix-ui/react-switch', '@radix-ui/react-popover'],
'editor-vendor': ['@uiw/react-md-editor'],
'syntax-vendor': ['react-syntax-highlighter'],
// Tauri and other utilities
'tauri': ['@tauri-apps/api', '@tauri-apps/plugin-dialog', '@tauri-apps/plugin-shell'],
'utils': ['date-fns', 'clsx', 'tailwind-merge'],
},
},
},
},
})); }));