Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions apps/web/src/components/chat/ErrorNotificationBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<ErrorNotificationBar
threadError={THREAD_ERROR}
showAuthFailuresAsErrors
showNotificationDetails={false}
includeDiagnosticsTipsInCopy={false}
providerStatus={null}
isMobileCompanion={false}
onDismissThreadError={onDismissThreadError}
/>,
);
});

const dismissAll = renderer!.root.findByProps({ "aria-label": "Dismiss notifications" });
await act(async () => {
dismissAll.props.onClick();
});

expect(renderer!.toJSON()).toBeNull();

await act(async () => {
renderer!.update(
<ErrorNotificationBar
threadError="Codex CLI is not authenticated. Run `codex login` and try again."
showAuthFailuresAsErrors
showNotificationDetails={false}
includeDiagnosticsTipsInCopy={false}
providerStatus={null}
isMobileCompanion={false}
onDismissThreadError={onDismissThreadError}
/>,
);
});

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(
<ErrorNotificationBar
threadError={null}
showAuthFailuresAsErrors
showNotificationDetails={false}
includeDiagnosticsTipsInCopy={false}
providerStatus={makeProviderStatus()}
isMobileCompanion={false}
/>,
);
});

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");
});
});
10 changes: 7 additions & 3 deletions apps/web/src/components/chat/ErrorNotificationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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;
Expand Down
Loading