From df05b1a96827f55bad36d23901698099231cebee Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 1 Aug 2026 20:30:34 +0000 Subject: [PATCH] fix(ui): hold issue-mention quicklook until readability confirmed Delay the issue quicklook until the referenced issue is confirmed readable. This prevents private issue mentions from mounting preview UI before the access check completes. Co-Authored-By: Paperclip --- ui/src/components/MarkdownBody.test.tsx | 26 +++++++++++++++++++++++++ ui/src/components/MarkdownBody.tsx | 15 ++++++++++++++ 2 files changed, 41 insertions(+) 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 (