authorgravatar for crazywolf132@gmail.com0x1f99d <crazywolf132@gmail.com> 2026-03-09 12:24:56+11:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-03-08 18:24:56-07:00
log59e2944fb72d8ef8c3b55fec3be766abe48df820
treec400de0c1380f8aa29e0cc293ea3632c43784cb9
parent17942787aa06070aa21ad6ae357e08f674383c4d
signature Signed by PGP key B5690EEEBB952194

fix(web): add dismiss button to thread error banner (#588)

The thread error banner had no way to be dismissed. Users hitting unrecoverable errors (e.g. missing gh CLI during PR creation) were stuck with a persistent error banner and no way to clear it. Add a dismiss (X) button to ThreadErrorBanner using the existing AlertAction component, wired to clear the thread error via setThreadError(id, null). Closes #496

1 files changed, 24 insertions(+), 3 deletions(-)

apps/web/src/components/ChatView.tsx+24-3
...@@ -123,7 +123,7 @@ import {...@@ -123,7 +123,7 @@ import {
123} from "../keybindings";123} from "../keybindings";
124import ChatMarkdown from "./ChatMarkdown";124import ChatMarkdown from "./ChatMarkdown";
125import ThreadTerminalDrawer from "./ThreadTerminalDrawer";125import ThreadTerminalDrawer from "./ThreadTerminalDrawer";
126import { Alert, AlertDescription, AlertTitle } from "./ui/alert";126import { Alert, AlertAction, AlertDescription, AlertTitle } from "./ui/alert";
127import {127import {
128 BotIcon,128 BotIcon,
129 ChevronDownIcon,129 ChevronDownIcon,
...@@ -3434,7 +3434,10 @@ export default function ChatView({ threadId }: ChatViewProps) {...@@ -3434,7 +3434,10 @@ export default function ChatView({ threadId }: ChatViewProps) {
34343434
3435 {/* Error banner */}3435 {/* Error banner */}
3436 <ProviderHealthBanner status={activeProviderStatus} />3436 <ProviderHealthBanner status={activeProviderStatus} />
3437 <ThreadErrorBanner error={activeThread.error} />3437 <ThreadErrorBanner
3438 error={activeThread.error}
3439 onDismiss={() => setThreadError(activeThread.id, null)}
3440 />
3438 <PlanModePanel activePlan={activePlan} />3441 <PlanModePanel activePlan={activePlan} />
34393442
3440 {/* Messages */}3443 {/* Messages */}
...@@ -4095,7 +4098,13 @@ const ChatHeader = memo(function ChatHeader({...@@ -4095,7 +4098,13 @@ const ChatHeader = memo(function ChatHeader({
4095 );4098 );
4096});4099});
40974100
4098const ThreadErrorBanner = memo(function ThreadErrorBanner({ error }: { error: string | null }) {4101const ThreadErrorBanner = memo(function ThreadErrorBanner({
4102 error,
4103 onDismiss,
4104}: {
4105 error: string | null;
4106 onDismiss?: () => void;
4107}) {
4099 if (!error) return null;4108 if (!error) return null;
4100 return (4109 return (
4101 <div className="pt-3 mx-auto max-w-3xl">4110 <div className="pt-3 mx-auto max-w-3xl">
...@@ -4104,6 +4113,18 @@ const ThreadErrorBanner = memo(function ThreadErrorBanner({ error }: { error: st...@@ -4104,6 +4113,18 @@ const ThreadErrorBanner = memo(function ThreadErrorBanner({ error }: { error: st
4104 <AlertDescription className="line-clamp-3" title={error}>4113 <AlertDescription className="line-clamp-3" title={error}>
4105 {error}4114 {error}
4106 </AlertDescription>4115 </AlertDescription>
4116 {onDismiss && (
4117 <AlertAction>
4118 <button
4119 type="button"
4120 aria-label="Dismiss error"
4121 className="inline-flex size-6 items-center justify-center rounded-md text-destructive/60 transition-colors hover:text-destructive"
4122 onClick={onDismiss}
4123 >
4124 <XIcon className="size-3.5" />
4125 </button>
4126 </AlertAction>
4127 )}
4107 </Alert>4128 </Alert>
4108 </div>4129 </div>
4109 );4130 );