Skip to content

Commit f60b616

Browse files
jeffdyerclaude
andcommitted
Fix image flashing during code generation and preserve tab state
- Memoize ReactMarkdown components and remarkPlugins to prevent image remounting on every processingTime tick - Keep Make and Images tabs mounted (hidden) to preserve state across tab switches Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7cff7b8 commit f60b616

1 file changed

Lines changed: 87 additions & 81 deletions

File tree

src/components/HelpPanel.tsx

Lines changed: 87 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1525,6 +1525,91 @@ export const HelpPanel = ({
15251525
};
15261526
}, [isLoading]);
15271527

1528+
// Stable remarkPlugins array to prevent re-renders
1529+
const remarkPlugins = useMemo(() => [remarkGfm], []);
1530+
1531+
// Stable ReactMarkdown components to prevent image remounting on re-render
1532+
const markdownComponents = useMemo(() => ({
1533+
img({ src, alt, ...props }) {
1534+
const displayName = alt && alt !== 'uploaded image'
1535+
? alt.replace(/\.[^.]+$/, '') : null;
1536+
return (
1537+
<span className="block mt-2">
1538+
<img
1539+
src={src}
1540+
alt={alt || 'uploaded image'}
1541+
className="max-w-full h-auto rounded"
1542+
style={{ maxHeight: '300px', objectFit: 'contain' as const }}
1543+
loading="lazy"
1544+
{...props}
1545+
/>
1546+
{displayName && (
1547+
<span className="block text-xs text-gray-500 -mt-0.5 leading-tight">{displayName}</span>
1548+
)}
1549+
</span>
1550+
);
1551+
},
1552+
code({className, children, ...props}) {
1553+
const match = /language-(\w+)/.exec(className || '');
1554+
return match ? (
1555+
<SyntaxHighlighter
1556+
style={tomorrow}
1557+
language={match[1]}
1558+
PreTag="div"
1559+
{...props}
1560+
>{String(children).replace(/\n$/, '')}</SyntaxHighlighter>
1561+
) : (
1562+
<code className={className} {...props}>
1563+
{children}
1564+
</code>
1565+
);
1566+
},
1567+
table({node, className, children, ...props}) {
1568+
return (
1569+
<div className="overflow-x-auto my-2 w-full">
1570+
<table className="table-auto border-collapse w-full text-xs" {...props}>
1571+
{children}
1572+
</table>
1573+
</div>
1574+
);
1575+
},
1576+
thead({node, children, ...props}) {
1577+
return (
1578+
<thead className="bg-gray-50" {...props}>
1579+
{children}
1580+
</thead>
1581+
);
1582+
},
1583+
tbody({node, children, ...props}) {
1584+
return (
1585+
<tbody className="divide-y divide-gray-100" {...props}>
1586+
{children}
1587+
</tbody>
1588+
);
1589+
},
1590+
tr({node, children, ...props}) {
1591+
return (
1592+
<tr className="hover:bg-gray-50" {...props}>
1593+
{children}
1594+
</tr>
1595+
);
1596+
},
1597+
th({node, children, ...props}) {
1598+
return (
1599+
<th className="px-2 py-1 text-left text-xs font-medium text-gray-600 uppercase tracking-wider border border-gray-200 whitespace-nowrap" {...props}>
1600+
{children}
1601+
</th>
1602+
);
1603+
},
1604+
td({node, children, ...props}) {
1605+
return (
1606+
<td className="px-2 py-1 text-xs text-gray-700 border border-gray-200" {...props}>
1607+
{children}
1608+
</td>
1609+
);
1610+
}
1611+
}), []);
1612+
15281613
// Function to prepare messages for display
15291614
const prepareMessagesForDisplay = () => {
15301615
// Find all user and system messages in chronological order (oldest to newest)
@@ -2388,87 +2473,8 @@ export const HelpPanel = ({
23882473
<div className={`px-3 pb-3 ${message.timestamp ? 'pt-0' : 'pt-3'}`}>
23892474
<div className="text-sm prose prose-sm prose-blue max-w-none">
23902475
<ReactMarkdown
2391-
remarkPlugins={[remarkGfm]}
2392-
components={{
2393-
img({ src, alt, ...props }) {
2394-
const displayName = alt && alt !== 'uploaded image'
2395-
? alt.replace(/\.[^.]+$/, '') : null;
2396-
return (
2397-
<span className="block mt-2">
2398-
<img
2399-
src={src}
2400-
alt={alt || 'uploaded image'}
2401-
className="max-w-full h-auto rounded"
2402-
style={{ maxHeight: '300px', objectFit: 'contain' as const }}
2403-
loading="lazy"
2404-
{...props}
2405-
/>
2406-
{displayName && (
2407-
<span className="block text-xs text-gray-500 -mt-0.5 leading-tight">{displayName}</span>
2408-
)}
2409-
</span>
2410-
);
2411-
},
2412-
code({className, children, ...props}) {
2413-
const match = /language-(\w+)/.exec(className || '');
2414-
return match ? (
2415-
<SyntaxHighlighter
2416-
style={tomorrow}
2417-
language={match[1]}
2418-
PreTag="div"
2419-
{...props}
2420-
>{String(children).replace(/\n$/, '')}</SyntaxHighlighter>
2421-
) : (
2422-
<code className={className} {...props}>
2423-
{children}
2424-
</code>
2425-
);
2426-
},
2427-
table({node, className, children, ...props}) {
2428-
return (
2429-
<div className="overflow-x-auto my-2 w-full">
2430-
<table className="table-auto border-collapse w-full text-xs" {...props}>
2431-
{children}
2432-
</table>
2433-
</div>
2434-
);
2435-
},
2436-
thead({node, children, ...props}) {
2437-
return (
2438-
<thead className="bg-gray-50" {...props}>
2439-
{children}
2440-
</thead>
2441-
);
2442-
},
2443-
tbody({node, children, ...props}) {
2444-
return (
2445-
<tbody className="divide-y divide-gray-100" {...props}>
2446-
{children}
2447-
</tbody>
2448-
);
2449-
},
2450-
tr({node, children, ...props}) {
2451-
return (
2452-
<tr className="hover:bg-gray-50" {...props}>
2453-
{children}
2454-
</tr>
2455-
);
2456-
},
2457-
th({node, children, ...props}) {
2458-
return (
2459-
<th className="px-2 py-1 text-left text-xs font-medium text-gray-600 uppercase tracking-wider border border-gray-200 whitespace-nowrap" {...props}>
2460-
{children}
2461-
</th>
2462-
);
2463-
},
2464-
td({node, children, ...props}) {
2465-
return (
2466-
<td className="px-2 py-1 text-xs text-gray-700 border border-gray-200" {...props}>
2467-
{children}
2468-
</td>
2469-
);
2470-
}
2471-
}}
2476+
remarkPlugins={remarkPlugins}
2477+
components={markdownComponents}
24722478
>
24732479
{message.role === 'system' ? message.content : message.user}
24742480
</ReactMarkdown>

0 commit comments

Comments
 (0)