diff --git a/ui/src/components/MarkdownBody.test.tsx b/ui/src/components/MarkdownBody.test.tsx index bdcfd28393b..1a6d8b8fcf2 100644 --- a/ui/src/components/MarkdownBody.test.tsx +++ b/ui/src/components/MarkdownBody.test.tsx @@ -241,6 +241,32 @@ describe("MarkdownBody", () => { expect(html).not.toContain("paperclip-mention-chip--issue"); }); + it("holds the quicklook preview until an issue mention is confirmed readable (PAP-16070)", () => { + // While the fetch is in flight we don't know if the viewer can read the + // issue. The mention stays a clickable link, but the IssueLinkQuicklook + // hover preview (a Radix Popover portal) must NOT mount yet: a private + // mention 404s straight to the locked chip, and mounting then tearing down + // that portal to swap in the chip is the element churn that crashed the + // chat transcript renderer. + const html = renderMarkdown("Depends on PAP-1271 for the hover state."); + + expect(html).toContain('data-mention-pending="true"'); + expect(html).toContain('data-mention-kind="issue"'); + expect(html).toContain("paperclip-markdown-issue-ref"); + // Clickability is preserved through the loading state. + expect(html).toContain('href="/issues/PAP-1271"'); + expect(html).toContain(">PAP-1271<"); + }); + + it("drops the pending marker once a seeded (readable) issue mention resolves", () => { + const html = renderMarkdown("Depends on PAP-1271 for the hover state.", [ + { identifier: "PAP-1271", status: "done" }, + ]); + + expect(html).toContain('href="/issues/PAP-1271"'); + expect(html).not.toContain('data-mention-pending="true"'); + }); + it("uses concise issue aria labels until a distinct title is available", () => { const html = renderMarkdown("Depends on PAP-1271 and PAP-1272.", [ { identifier: "PAP-1271", status: "done" }, diff --git a/ui/src/components/MarkdownBody.tsx b/ui/src/components/MarkdownBody.tsx index e7e6cecf5e4..56543006a2b 100644 --- a/ui/src/components/MarkdownBody.tsx +++ b/ui/src/components/MarkdownBody.tsx @@ -119,10 +119,25 @@ function MarkdownIssueLink({ const status = data?.status; const issueLabel = title !== identifier ? `Issue ${identifier}: ${title}` : `Issue ${identifier}`; + // Until the fetch settles we don't yet know whether this viewer can read the + // issue. Keep the mention a plain link (clickable, styled as today) but hold + // off mounting the IssueLinkQuicklook hover preview — a Radix Popover portal — + // until `data` confirms the issue is readable. + // - Correctness (PAP-16070): a private mention 404s straight to the locked + // chip. Mounting the quicklook popover during the loading `` only to + // tear the portal down and swap in a plain `` chip on the 404 is the + // element churn that crashed the chat transcript's primary renderer (it + // fell through to the safe fallback). Loading link → chip stays portal-free. + // - Privacy: don't prefetch / hover-preview an issue of unconfirmed + // readability. `data-mention-pending` marks the transient state for tests. + const pending = !data; + return (