From 6cb9b782ece65f10e80236ba3ccdcf60e2d48b71 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Tue, 21 Apr 2026 18:53:21 -0500 Subject: [PATCH] Fix error-notification dismissal regressions --- .../chat/ErrorNotificationBar.test.tsx | 69 +++++++++++++++++++ .../components/chat/ErrorNotificationBar.tsx | 10 ++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/chat/ErrorNotificationBar.test.tsx b/apps/web/src/components/chat/ErrorNotificationBar.test.tsx index b23bc1835..773382345 100644 --- a/apps/web/src/components/chat/ErrorNotificationBar.test.tsx +++ b/apps/web/src/components/chat/ErrorNotificationBar.test.tsx @@ -85,4 +85,73 @@ describe("ErrorNotificationBar", () => { expect(markup).toContain("Worktree thread could not start"); expect(markup).toContain("Base branch 'main' does not resolve to a commit yet."); }); + + it("re-shows thread errors when the message changes after dismissal", async () => { + const onDismissThreadError = vi.fn(); + let renderer: ReactTestRenderer | null = null; + + await act(async () => { + renderer = create( + , + ); + }); + + const dismissAll = renderer!.root.findByProps({ "aria-label": "Dismiss notifications" }); + await act(async () => { + dismissAll.props.onClick(); + }); + + expect(renderer!.toJSON()).toBeNull(); + + await act(async () => { + renderer!.update( + , + ); + }); + + expect(renderer!.toJSON()).not.toBeNull(); + expect(renderer!.root.findByProps({ "aria-label": "Show 1 notification" })).toBeTruthy(); + }); + + it("does not hide non-dismissible provider notifications via dismiss all", async () => { + let renderer: ReactTestRenderer | null = null; + + await act(async () => { + renderer = create( + , + ); + }); + + const dismissAll = renderer!.root.findByProps({ "aria-label": "Dismiss notifications" }); + await act(async () => { + dismissAll.props.onClick(); + }); + + expect(renderer!.toJSON()).not.toBeNull(); + expect(renderer!.root.findByProps({ "aria-label": "Show 1 notification" })).toBeTruthy(); + expect(JSON.stringify(renderer!.toJSON())).toContain("OpenAI (Codex CLI) needs verification"); + }); }); diff --git a/apps/web/src/components/chat/ErrorNotificationBar.tsx b/apps/web/src/components/chat/ErrorNotificationBar.tsx index f8b9298d4..b3bab3adc 100644 --- a/apps/web/src/components/chat/ErrorNotificationBar.tsx +++ b/apps/web/src/components/chat/ErrorNotificationBar.tsx @@ -54,6 +54,10 @@ interface NotificationItem { onDismiss?: () => void; } +function buildThreadErrorNotificationId(error: string): string { + return `thread-error:${error}`; +} + export const ErrorNotificationBar = memo(function ErrorNotificationBar({ threadError, showAuthFailuresAsErrors = true, @@ -130,7 +134,7 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({ if (showAuthFailuresAsErrors || !isAuthenticationThreadError(threadError)) { const presentation = humanizeThreadError(threadError); items.push({ - id: "thread-error", + id: buildThreadErrorNotificationId(threadError), kind: "thread-error", icon: CircleAlertIcon, title: presentation.title ?? "Error", @@ -210,8 +214,8 @@ export const ErrorNotificationBar = memo(function ErrorNotificationBar({ notif.onDismiss(); } } - setDismissedIds(new Set(notifications.map((n) => n.id))); - }, [visibleNotifications, notifications]); + setDismissedIds(new Set(visibleNotifications.filter((n) => n.dismissible).map((n) => n.id))); + }, [visibleNotifications]); // Nothing to show if (visibleNotifications.length === 0) return null;