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 <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-01 22:54:01 +00:00
1 parent 2c6e14dffb
commit df05b1a968
2 files changed
+41

No files matched your search

+26
View File
@@ -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" },
+15
View File
@@ -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 `<Link>` only to
// tear the portal down and swap in a plain `<span>` 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 (
<Link
to={`/issues/${identifier}`}
data-mention-kind="issue"
disableIssueQuicklook={pending}
data-mention-pending={pending ? "true" : undefined}
// Boxless inline mention: the unified status glyph + a regular-weight
// underlined link, optically centered with the body text.
className={cn("paperclip-markdown-issue-ref", "font-normal underline")}