From 4ac3f6754b77a66e4a2e5f8ee9ef3a33db7a495e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 25 Mar 2026 12:33:01 -0700 Subject: [PATCH 1/4] Add copy button to error toasts - Let users copy error toast descriptions - Keep toast text selectable for manual copy --- apps/web/src/components/ui/toast.tsx | 52 ++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index 768a083e2e..25b6d83dea 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -5,8 +5,10 @@ import { useEffect, type CSSProperties } from "react"; import { useParams } from "@tanstack/react-router"; import { ThreadId } from "@t3tools/contracts"; import { + CheckIcon, CircleAlertIcon, CircleCheckIcon, + CopyIcon, InfoIcon, LoaderCircleIcon, TriangleAlertIcon, @@ -14,6 +16,7 @@ import { import { cn } from "~/lib/utils"; import { buttonVariants } from "~/components/ui/button"; +import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; import { buildVisibleToastLayout, shouldHideCollapsedToastContent } from "./toast.logic"; type ThreadToastData = { @@ -35,6 +38,25 @@ const TOAST_ICONS = { warning: TriangleAlertIcon, } as const; +function CopyErrorButton({ text }: { text: string }) { + const { copyToClipboard, isCopied } = useCopyToClipboard(); + + return ( + + ); +} + type ToastPosition = | "top-left" | "top-center" @@ -284,12 +306,17 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { )}
- +
+ + {toast.type === "error" && toast.description && ( + + )} +
@@ -373,12 +400,17 @@ function AnchoredToasts() { )}
- +
+ + {toast.type === "error" && toast.description && ( + + )} +
From 5568558b50f659ed849d06d89e0bbdcd555bd704 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 25 Mar 2026 12:35:59 -0700 Subject: [PATCH 2/4] Copy only string error toast descriptions - Restrict the error copy button to string descriptions - Avoid passing non-string toast content into the clipboard action --- apps/web/src/components/ui/toast.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index 25b6d83dea..7726b1ea9f 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -311,7 +311,7 @@ function Toasts({ position = "top-right" }: { position: ToastPosition }) { className="min-w-0 break-words font-medium" data-slot="toast-title" /> - {toast.type === "error" && toast.description && ( + {toast.type === "error" && typeof toast.description === "string" && ( )} From e68526fc19cd57228c902123144d5c7fc39479f4 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 25 Mar 2026 12:38:59 -0700 Subject: [PATCH 3/4] Copy only string git errors in error toasts - Restrict the copy-error action to string descriptions - Avoid passing non-string toast descriptions to the clipboard --- apps/web/src/components/ui/toast.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index 7726b1ea9f..a55e36c195 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -405,7 +405,7 @@ function AnchoredToasts() { className="min-w-0 break-words font-medium" data-slot="toast-title" /> - {toast.type === "error" && toast.description && ( + {toast.type === "error" && typeof toast.description === "string" && ( )} From 30497a89f2f91c6ca704a3f7997e7ccc93c45f39 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 25 Mar 2026 19:41:25 +0000 Subject: [PATCH 4/4] fix: add typeof string type guard for toast description in anchored toast The floating toast correctly guarded with typeof toast.description === 'string' before passing to CopyErrorButton, but the anchored toast only checked truthiness. This could pass non-string values (e.g. React nodes) to clipboard.writeText(). --- apps/web/src/components/ui/toast.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/ui/toast.tsx b/apps/web/src/components/ui/toast.tsx index 7726b1ea9f..a55e36c195 100644 --- a/apps/web/src/components/ui/toast.tsx +++ b/apps/web/src/components/ui/toast.tsx @@ -405,7 +405,7 @@ function AnchoredToasts() { className="min-w-0 break-words font-medium" data-slot="toast-title" /> - {toast.type === "error" && toast.description && ( + {toast.type === "error" && typeof toast.description === "string" && ( )}