From 888d2b47a13a6347717e7f7f606fd7ac30e369af Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 12:48:08 +0000 Subject: [PATCH 1/3] Restyle in-app toasts to match shadcn design language Replace the solid saturated-color toast backgrounds (bg-destructive, bg-primary, bg-green-600, bg-orange-600) with the app's shared shadcn surface treatment: bg-popover, border, shadow-lg, rounded-lg. Each toast type is now distinguished by a colored lucide icon and subtle border tint instead of a full-color fill, matching Card/Dialog/Popover. Add a toast-styles screenshot spec covering all four toast types in light and dark mode, and update existing screenshot spec expectations that described the old "green toast" look. --- .../specs/commits-tab-after-push.spec.tsx | 2 +- ...eader-sync-indicator-after-commit.spec.tsx | 2 +- .../specs/sync-after-commit.spec.tsx | 2 +- .../screenshot/specs/toast-styles.spec.tsx | 95 +++++++++++++++++++ src/components/ui/toast.tsx | 93 ++++++++++-------- 5 files changed, 153 insertions(+), 41 deletions(-) create mode 100644 scripts/screenshot/specs/toast-styles.spec.tsx diff --git a/scripts/screenshot/specs/commits-tab-after-push.spec.tsx b/scripts/screenshot/specs/commits-tab-after-push.spec.tsx index c1560ea7..476f4124 100644 --- a/scripts/screenshot/specs/commits-tab-after-push.spec.tsx +++ b/scripts/screenshot/specs/commits-tab-after-push.spec.tsx @@ -110,7 +110,7 @@ it("captures the Commits tab before and after pushing a workspace to remote", as name: "commits-tab-after-push-02-after-push", expectations: [ "The 'Push to remote' button is gone from the header (only 'Merge...' and the overflow menu remain).", - "A green toast in the bottom-left corner reads 'Pushed to remote' / 'Changes pushed successfully'.", + "A toast in the bottom-left corner with a green check icon reads 'Pushed to remote' / 'Changes pushed successfully'.", "Workspace commit 1 and Workspace commit 2 are still listed, unchanged from the before-push screenshot.", ], }); diff --git a/scripts/screenshot/specs/header-sync-indicator-after-commit.spec.tsx b/scripts/screenshot/specs/header-sync-indicator-after-commit.spec.tsx index 9d9d008c..fd6a1ca1 100644 --- a/scripts/screenshot/specs/header-sync-indicator-after-commit.spec.tsx +++ b/scripts/screenshot/specs/header-sync-indicator-after-commit.spec.tsx @@ -89,7 +89,7 @@ it("captures the header sync indicator before and after committing from the Revi name: "header-sync-indicator-02-after-commit", expectations: [ "The header now shows an '↑1' sync indicator with the circular refresh-arrows icon, just left of the green 'Merge...' button -- it appeared without navigating away from this workspace.", - "A green 'Commit created' toast is visible in the bottom-left corner.", + "A 'Commit created' toast with a green check icon is visible in the bottom-left corner.", "The Review tab's left sidebar no longer has a 'Changes' section; sync-indicator.txt now sits under 'Committed', tagged 'A'.", ], }); diff --git a/scripts/screenshot/specs/sync-after-commit.spec.tsx b/scripts/screenshot/specs/sync-after-commit.spec.tsx index 0d737593..0ea6b968 100644 --- a/scripts/screenshot/specs/sync-after-commit.spec.tsx +++ b/scripts/screenshot/specs/sync-after-commit.spec.tsx @@ -138,7 +138,7 @@ it("captures workspace state before and after syncing a fresh commit", async () name: "sync-after-commit-02-after-sync", expectations: [ "'Commit that must survive sync' is still listed in the Commits tab, in the same position as the before screenshot.", - "A green toast in the bottom-left reads 'Synced with remote' / 'Fetched and pushed changes'.", + "A toast in the bottom-left with a green check icon reads 'Synced with remote' / 'Fetched and pushed changes'.", "The '↑1' ahead indicator is gone from the header — the workspace is now in sync with the remote.", "No empty '(no description set)' commit appears above the real commit.", ], diff --git a/scripts/screenshot/specs/toast-styles.spec.tsx b/scripts/screenshot/specs/toast-styles.spec.tsx new file mode 100644 index 00000000..0c7827d6 --- /dev/null +++ b/scripts/screenshot/specs/toast-styles.spec.tsx @@ -0,0 +1,95 @@ +import * as React from "react"; +import { it } from "vitest"; +import userEvent from "@testing-library/user-event"; +import { render, screen } from "../../../test/test-utils"; +import { useToast } from "../../../src/components/ui/toast"; +import { captureDocument } from "../capture"; + +// Toast styling is a pure presentational concern with no backend involvement, +// so this drives the toast context directly (via useToast) rather than a real +// repo/workspace flow, to get all four toast types on screen deterministically. +function ToastHarness() { + const { addToast } = useToast(); + + return ( + + ); +} + +it("captures all four toast type styles", async () => { + const user = userEvent.setup(); + render(); + + await user.click(await screen.findByRole("button", { name: "Fire toasts" })); + + await screen.findByText("Synced with remote"); + await screen.findByText("Push failed"); + await screen.findByText("Uncommitted changes"); + await screen.findByText("Terminal Restarting"); + + await captureDocument(document, { + name: "toast-styles-01-all-types", + expectations: [ + "Four stacked toasts are visible in the bottom-left corner of the page.", + "Every toast shares the same neutral card-like surface (light background, subtle border, drop shadow, rounded corners) rather than a solid saturated color fill.", + "The 'Synced with remote' toast shows a green circular check icon on its left.", + "The 'Push failed' toast shows a red/destructive circular X icon on its left.", + "The 'Uncommitted changes' toast shows an orange triangle warning icon on its left.", + "The 'Terminal Restarting' toast shows a blue/primary-colored info icon on its left.", + "Each toast has a bold title line and a smaller, muted-colored description line below it.", + "Each toast has a small close (X) button on its right edge.", + ], + }); +}, 30000); + +it("captures all four toast type styles in dark mode", async () => { + const user = userEvent.setup(); + render(); + document.documentElement.classList.add("dark"); + + await user.click(await screen.findByRole("button", { name: "Fire toasts" })); + + await screen.findByText("Synced with remote"); + await screen.findByText("Push failed"); + await screen.findByText("Uncommitted changes"); + await screen.findByText("Terminal Restarting"); + + await captureDocument(document, { + name: "toast-styles-02-all-types-dark", + expectations: [ + "The page background is dark (dark mode is active).", + "Four stacked toasts are visible in the bottom-left corner, using a dark neutral surface consistent with the rest of the dark-mode UI, not a solid saturated color fill.", + "The 'Synced with remote' toast shows a green check icon.", + "The 'Push failed' toast shows a red/destructive X icon.", + "The 'Uncommitted changes' toast shows an orange triangle warning icon.", + "The 'Terminal Restarting' toast shows a blue/primary info icon.", + "Toast title and description text is legible against the dark background.", + ], + }); +}, 30000); diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index 19b3bcbb..4ab0910c 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { X } from "lucide-react"; +import { AlertTriangle, CheckCircle2, Info, X, XCircle } from "lucide-react"; import { cn } from "../../lib/utils"; export type ToastType = "success" | "error" | "info" | "warning"; @@ -56,52 +56,69 @@ export function ToastProvider({ children }: { children: React.ReactNode }) { ); } +const toastIcons: Record = { + error: XCircle, + info: Info, + success: CheckCircle2, + warning: AlertTriangle, +}; + +const toastIconStyles: Record = { + error: "text-destructive", + info: "text-primary", + success: "text-green-600 dark:text-green-500", + warning: "text-orange-500 dark:text-orange-400", +}; + +const toastBorderStyles: Record = { + error: "border-destructive/40", + info: "border-primary/30", + success: "border-green-500/30", + warning: "border-orange-500/30", +}; + function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) { - const typeStyles = { - error: - "bg-destructive dark:bg-destructive/90 border border-destructive/30 text-destructive-foreground", - info: "bg-primary dark:bg-primary/90 border border-primary/30 text-primary-foreground", - success: - "bg-green-600 dark:bg-green-700 border border-green-500/30 dark:border-green-600/30 text-white", - warning: - "bg-orange-600 dark:bg-orange-700 border border-orange-500/30 dark:border-orange-600/30 text-white", - }; + const Icon = toastIcons[toast.type]; return (
-
-
-
{toast.title}
- {toast.description && ( -
- {toast.description} -
- )} - {toast.action && ( - - )} + +
+
+ {toast.title}
- + {toast.description && ( +
+ {toast.description} +
+ )} + {toast.action && ( + + )}
+
); } From 58363fcd3e8f4f3e5fb38f5c985832754a3b4262 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 11:03:50 +0000 Subject: [PATCH 2/3] Remove per-type colored border from toast Toasts now use the same neutral border-border as Card/Dialog; type is communicated solely by the icon color. --- src/components/ui/toast.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index 4ab0910c..7d12bfe3 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -70,23 +70,13 @@ const toastIconStyles: Record = { warning: "text-orange-500 dark:text-orange-400", }; -const toastBorderStyles: Record = { - error: "border-destructive/40", - info: "border-primary/30", - success: "border-green-500/30", - warning: "border-orange-500/30", -}; - function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) { const Icon = toastIcons[toast.type]; return (
Date: Mon, 27 Jul 2026 19:51:35 +0000 Subject: [PATCH 3/3] style: use the app's native icon/color conventions for toasts Match how the rest of the app signals status: AlertCircle+destructive for errors (RenameWorkspaceDialog), AlertTriangle+yellow for warnings (MergeDialog's uncommitted-changes banner) instead of a generic XCircle/orange pairing, and drop the icon size to w-4 h-4 to match other inline banner icons. --- .../screenshot/specs/toast-styles.spec.tsx | 8 ++++---- src/components/ui/toast.tsx | 20 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/scripts/screenshot/specs/toast-styles.spec.tsx b/scripts/screenshot/specs/toast-styles.spec.tsx index 0c7827d6..00fe3f7a 100644 --- a/scripts/screenshot/specs/toast-styles.spec.tsx +++ b/scripts/screenshot/specs/toast-styles.spec.tsx @@ -59,8 +59,8 @@ it("captures all four toast type styles", async () => { "Four stacked toasts are visible in the bottom-left corner of the page.", "Every toast shares the same neutral card-like surface (light background, subtle border, drop shadow, rounded corners) rather than a solid saturated color fill.", "The 'Synced with remote' toast shows a green circular check icon on its left.", - "The 'Push failed' toast shows a red/destructive circular X icon on its left.", - "The 'Uncommitted changes' toast shows an orange triangle warning icon on its left.", + "The 'Push failed' toast shows a red/destructive circular alert (exclamation) icon on its left, the same style used for errors elsewhere in the app (e.g. the rename workspace dialog).", + "The 'Uncommitted changes' toast shows a yellow triangle warning icon on its left, matching the yellow warning color used elsewhere in the app (e.g. the merge dialog's uncommitted-changes banner).", "The 'Terminal Restarting' toast shows a blue/primary-colored info icon on its left.", "Each toast has a bold title line and a smaller, muted-colored description line below it.", "Each toast has a small close (X) button on its right edge.", @@ -86,8 +86,8 @@ it("captures all four toast type styles in dark mode", async () => { "The page background is dark (dark mode is active).", "Four stacked toasts are visible in the bottom-left corner, using a dark neutral surface consistent with the rest of the dark-mode UI, not a solid saturated color fill.", "The 'Synced with remote' toast shows a green check icon.", - "The 'Push failed' toast shows a red/destructive X icon.", - "The 'Uncommitted changes' toast shows an orange triangle warning icon.", + "The 'Push failed' toast shows a red/destructive circular alert (exclamation) icon.", + "The 'Uncommitted changes' toast shows a yellow triangle warning icon.", "The 'Terminal Restarting' toast shows a blue/primary info icon.", "Toast title and description text is legible against the dark background.", ], diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index 7d12bfe3..e97961a0 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -1,5 +1,11 @@ import * as React from "react"; -import { AlertTriangle, CheckCircle2, Info, X, XCircle } from "lucide-react"; +import { + AlertCircle, + AlertTriangle, + CheckCircle2, + Info, + X, +} from "lucide-react"; import { cn } from "../../lib/utils"; export type ToastType = "success" | "error" | "info" | "warning"; @@ -57,7 +63,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) { } const toastIcons: Record = { - error: XCircle, + error: AlertCircle, info: Info, success: CheckCircle2, warning: AlertTriangle, @@ -66,8 +72,8 @@ const toastIcons: Record = { const toastIconStyles: Record = { error: "text-destructive", info: "text-primary", - success: "text-green-600 dark:text-green-500", - warning: "text-orange-500 dark:text-orange-400", + success: "text-green-500", + warning: "text-yellow-600 dark:text-yellow-400", }; function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) { @@ -79,12 +85,10 @@ function ToastItem({ toast, onClose }: { toast: Toast; onClose: () => void }) { className="pointer-events-auto flex w-full min-w-[320px] max-w-sm items-start gap-3 rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-lg animate-in slide-in-from-left" >
-
- {toast.title} -
+
{toast.title}
{toast.description && (
{toast.description}