From f75c0c317cf2207b7e060ca0e8551dd91b6e418d Mon Sep 17 00:00:00 2001 From: dotta Date: Wed, 8 Apr 2026 07:20:24 -0500 Subject: [PATCH] fix(ui): move useCallback hook before early returns in IssueDetail The handleChatImageClick useCallback (and its dependencies) was defined after conditional early returns, violating React's rules of hooks and causing "Rendered more hooks than during the previous render" crashes. Co-Authored-By: Paperclip --- ui/src/pages/IssueDetail.tsx | 53 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/ui/src/pages/IssueDetail.tsx b/ui/src/pages/IssueDetail.tsx index 066f455b..9c3a417b 100644 --- a/ui/src/pages/IssueDetail.tsx +++ b/ui/src/pages/IssueDetail.tsx @@ -1301,6 +1301,33 @@ export function IssueDetail() { }; }, [archiveFromInbox, canQuickArchiveFromInbox, issue?.id]); + const isImageAttachment = (attachment: IssueAttachment) => attachment.contentType.startsWith("image/"); + const attachmentList = attachments ?? []; + const imageAttachments = attachmentList.filter(isImageAttachment); + const nonImageAttachments = attachmentList.filter((a) => !isImageAttachment(a)); + + const handleChatImageClick = useCallback( + (src: string) => { + // Try exact contentPath match first + let idx = imageAttachments.findIndex((a) => a.contentPath === src); + if (idx < 0) { + // Try matching by asset ID extracted from /api/assets/{assetId}/content URLs + const assetMatch = src.match(/\/api\/assets\/([^/]+)\/content/); + if (assetMatch) { + idx = imageAttachments.findIndex((a) => a.assetId === assetMatch[1]); + } + } + if (idx >= 0) { + setGalleryIndex(idx); + setGalleryOpen(true); + } else { + // Image not in attachment list — open in new tab + window.open(src, "_blank"); + } + }, + [imageAttachments], + ); + const copyIssueToClipboard = async () => { if (!issue) return; const decodeEntities = (text: string) => { @@ -1352,32 +1379,6 @@ export function IssueDetail() { } }; - const isImageAttachment = (attachment: IssueAttachment) => attachment.contentType.startsWith("image/"); - const attachmentList = attachments ?? []; - const imageAttachments = attachmentList.filter(isImageAttachment); - const nonImageAttachments = attachmentList.filter((a) => !isImageAttachment(a)); - - const handleChatImageClick = useCallback( - (src: string) => { - // Try exact contentPath match first - let idx = imageAttachments.findIndex((a) => a.contentPath === src); - if (idx < 0) { - // Try matching by asset ID extracted from /api/assets/{assetId}/content URLs - const assetMatch = src.match(/\/api\/assets\/([^/]+)\/content/); - if (assetMatch) { - idx = imageAttachments.findIndex((a) => a.assetId === assetMatch[1]); - } - } - if (idx >= 0) { - setGalleryIndex(idx); - setGalleryOpen(true); - } else { - // Image not in attachment list — open in new tab - window.open(src, "_blank"); - } - }, - [imageAttachments], - ); const hasAttachments = attachmentList.length > 0; const attachmentUploadButton = ( <>