From c0799a2b012321d1a3680e2e875666028108f5de Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Thu, 1 Oct 2026 14:58:15 +0200 Subject: [PATCH 1/2] fix(ui): Make event Context tab attachment previews expandable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `Attachment` component took `expanded` only as a prop with no way to toggle it. On the event Context tab it is rendered without that prop, so the preview stayed collapsed forever and the download link (whose URL is only built when expanded) was inert too — the "Expand to preview attachment." hint did nothing. Let the component manage its own expanded state, seeded from the prop, and make the hint a button that toggles the preview open and closed. Previews stay lazily rendered (collapsed by default), so nothing is decoded until requested. `EnvelopeDetails`, which passes `expanded`, is unaffected. Co-Authored-By: Claude Opus 4.8 --- .../insights/envelopes/Attachment.test.tsx | 26 +++++++++++++- .../insights/envelopes/Attachment.tsx | 36 ++++++++++++++++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.test.tsx b/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.test.tsx index 3b0f97c5..54247880 100644 --- a/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.test.tsx +++ b/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import Attachment from "./Attachment"; @@ -199,6 +199,30 @@ describe("Attachment", () => { expect(container.textContent).toContain("Expand to preview attachment"); }); + + it("should toggle between expanded and collapsed when the prompt is clicked", () => { + const { container } = render( + , + ); + + // Collapsed by default: shows the prompt, no content decoded yet. + expect(container.textContent).toContain("Expand to preview attachment"); + expect(container.textContent).not.toContain("Hello, World!"); + expect(mockCreateObjectURL).not.toHaveBeenCalled(); + + // Expanding renders the content and builds the download URL on demand. + fireEvent.click(screen.getByRole("button", { name: /expand to preview attachment/i })); + expect(container.textContent).toContain("Hello, World!"); + expect(mockCreateObjectURL).toHaveBeenCalledTimes(1); + + // Collapsing hides the content again. + fireEvent.click(screen.getByRole("button", { name: /collapse attachment/i })); + expect(container.textContent).toContain("Expand to preview attachment"); + expect(container.textContent).not.toContain("Hello, World!"); + }); }); describe("cleanup", () => { diff --git a/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx b/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx index 9f7b458f..41b64062 100644 --- a/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx +++ b/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx @@ -1,7 +1,7 @@ import type { EnvelopeItem } from "@sentry/core"; import { ReactComponent as Download } from "@spotlight/ui/assets/download.svg"; import { base64Decode, safeAtob } from "@spotlight/ui/lib/base64"; -import { type ReactNode, useEffect, useMemo } from "react"; +import { type ReactNode, useEffect, useMemo, useState } from "react"; import JsonViewer from "../../shared/JsonViewer"; import { CodeViewer } from "./CodeViewer"; import { inferExtension } from "./contentType"; @@ -23,10 +23,15 @@ export default function Attachment({ const extension = inferExtension(header.content_type as string | null, header.type as string | null); const name = (header.filename as string) || `untitled.${extension}`; + // Lazily render the attachment: start from the `expanded` prop, but let the + // user expand it on demand so previews/downloads work wherever the component + // is used, without eagerly decoding every attachment. + const [isExpanded, setIsExpanded] = useState(expanded); + // Create download URL for attachment // Returns: string (success), null (decode error), undefined (not expanded yet) const downloadUrl = useMemo(() => { - if (!expanded) { + if (!isExpanded) { return undefined; // Not needed yet } @@ -54,7 +59,7 @@ export default function Attachment({ const blob = new Blob([blobData], { type: contentType || "application/octet-stream" }); return URL.createObjectURL(blob); - }, [expanded, attachment, extension, header.content_type]); + }, [isExpanded, attachment, extension, header.content_type]); // Cleanup blob URL on unmount or when URL changes useEffect(() => { @@ -69,7 +74,7 @@ export default function Attachment({ let content: ReactNode = null; - if (expanded) { + if (isExpanded) { if (decodeError) { content = (
@@ -114,7 +119,28 @@ export default function Attachment({
           
         
       
-      {expanded ? content : 

Expand to preview attachment.

} + {isExpanded ? ( +
+ + {content} +
+ ) : ( + + )} ); } From 957113908b6e86380a0961645f8e489bf3d34acd Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Fri, 16 Oct 2026 12:45:55 +0200 Subject: [PATCH 2/2] Fix keyboard focus logic Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../insights/envelopes/Attachment.tsx | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx b/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx index 41b64062..83132d3f 100644 --- a/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx +++ b/packages/spotlight/src/ui/telemetry/components/insights/envelopes/Attachment.tsx @@ -119,28 +119,17 @@ export default function Attachment({ - {isExpanded ? ( -
- - {content} -
- ) : ( +
- )} + {isExpanded ? content : null} +
); }