From cddb379145efe65f515653bcaaeadc4862115167 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 24 Apr 2026 07:43:41 -0500 Subject: [PATCH] Stabilize image drops in issue composer --- ui/src/components/IssueChatThread.test.tsx | 4 +++- ui/src/components/IssueChatThread.tsx | 2 +- ui/src/components/MarkdownEditor.test.tsx | 26 ++++++++++++++++++++++ ui/src/components/MarkdownEditor.tsx | 7 ++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/ui/src/components/IssueChatThread.test.tsx b/ui/src/components/IssueChatThread.test.tsx index 3b12888f21c..968315d153b 100644 --- a/ui/src/components/IssueChatThread.test.tsx +++ b/ui/src/components/IssueChatThread.test.tsx @@ -928,7 +928,9 @@ describe("IssueChatThread", () => { }); expect(onAttachImage).toHaveBeenCalledWith(file); - expect(container.querySelector('[data-testid="issue-chat-composer-attachments"]')).not.toBeNull(); + const attachmentList = container.querySelector('[data-testid="issue-chat-composer-attachments"]'); + expect(attachmentList).not.toBeNull(); + expect(attachmentList?.className).toContain("mb-3"); expect(container.textContent).toContain("report.pdf"); expect(container.textContent).toContain("Attached to issue"); diff --git a/ui/src/components/IssueChatThread.tsx b/ui/src/components/IssueChatThread.tsx index b2cd4aa7ad9..97b42eb430b 100644 --- a/ui/src/components/IssueChatThread.tsx +++ b/ui/src/components/IssueChatThread.tsx @@ -2362,7 +2362,7 @@ const IssueChatComposer = forwardRef 0 ? (
{composerAttachments.map((attachment) => { const sizeLabel = formatAttachmentSize(attachment.size); diff --git a/ui/src/components/MarkdownEditor.test.tsx b/ui/src/components/MarkdownEditor.test.tsx index aac6345cd86..98f60bdb39b 100644 --- a/ui/src/components/MarkdownEditor.test.tsx +++ b/ui/src/components/MarkdownEditor.test.tsx @@ -347,6 +347,32 @@ describe("MarkdownEditor", () => { root.unmount(); }); }); + + it("does not show the raw fallback while image-only markdown is settling", async () => { + mdxEditorMockState.emitMountSilentEmptyState = true; + const root = createRoot(container); + + await act(async () => { + root.render( + {}} + placeholder="Markdown body" + />, + ); + }); + + await flush(); + await flush(); + + expect(container.querySelector("textarea")).toBeNull(); + expect(container.textContent).not.toContain("Rich editor unavailable for this markdown"); + + await act(async () => { + root.unmount(); + }); + }); + it("anchors the mention menu inside the visual viewport when mobile offsets are present", () => { expect( computeMentionMenuPosition( diff --git a/ui/src/components/MarkdownEditor.tsx b/ui/src/components/MarkdownEditor.tsx index 2ccdb85278c..506ed5dc965 100644 --- a/ui/src/components/MarkdownEditor.tsx +++ b/ui/src/components/MarkdownEditor.tsx @@ -126,6 +126,10 @@ function hasMeaningfulEditorContent(node: Node | null): boolean { return Array.from(element.childNodes).some((child) => hasMeaningfulEditorContent(child)); } +function hasMarkdownImage(value: string): boolean { + return /!\[[\s\S]*?\]\([^)]+\)/.test(value); +} + function isRichEditorDomEmpty( editable: HTMLElement, expectedValue: string, @@ -133,9 +137,11 @@ function isRichEditorDomEmpty( ): boolean { const expectedText = expectedValue.trim(); if (!expectedText) return false; + const expectedHasImage = hasMarkdownImage(expectedText); const visibleText = (editable.textContent ?? "").trim(); if (visibleText.length === 0) { + if (expectedHasImage) return false; return !Array.from(editable.childNodes).some((child) => hasMeaningfulEditorContent(child)); } @@ -145,6 +151,7 @@ function isRichEditorDomEmpty( && visibleText === normalizedPlaceholder && expectedText !== normalizedPlaceholder ) { + if (expectedHasImage) return false; return true; }