# Files changed · PR \#23101 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23101/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[refactor\(web\): ChatView's timeline banners render in ChatBannerLane](https://git.cafe/pingdotgg/t3code/pull/23101.md)

Version: 6

Head commit: 54349ea3e2f4799c90fdb701bfe12e55526bd618

Base commit: 300d3ced33bc5b8afd5a823cd7bd27e9102db4c3

Comparison base: 300d3ced33bc5b8afd5a823cd7bd27e9102db4c3

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/web/src/components/ChatView.tsx

Status: modified; +17; −23

```diff
@@ -48,6 +48,7 @@
 import { useUsageLimitsPanel } from "./chat/useUsageLimitsPanel";
 import { useWorktreeSetup } from "./chat/useWorktreeSetup";
 import { useChatShortcuts } from "./chat/useChatShortcuts";
+import { ChatBannerLane } from "./chat/ChatBannerLane";
 import * as Schema from "effect/Schema";
 import {
   AuthOrchestrationOperateScope,
@@ -282,15 +283,12 @@
 } from "./BranchToolbar.logic";
 import {
   getProviderStatusBannerKey,
-  ProviderStatusBanner,
   shouldShowProviderStatusBanner,
 } from "./chat/ProviderStatusBanner";
 import {
-  dismissThreadErrorBannerForSession,
   getThreadErrorBannerKey,
   isThreadErrorBannerDismissedForSession,
   shouldShowThreadErrorBanner,
-  ThreadErrorBanner,
 } from "./chat/ThreadErrorBanner";
 import { QueuedRunsControl, type QueuedRunsControlHandle } from "./chat/QueuedRunsControl";
 import { useThreadLinkedPullRequest } from "./pullRequest/useThreadLinkedPullRequest";
@@ -3229,26 +3227,22 @@
           >
             <ThreadFind onClose={focusComposerStable} />
             {isWorkspaceFileDragActive ? <WorkspaceFileDropOverlay /> : null}
-            <div className="chat-banner-lane pointer-events-none absolute top-0 z-30 flex flex-col">
-              <ProviderStatusBanner
-                status={visibleProviderStatus}
-                onDismiss={() => setDismissedProviderStatusBannerKey(providerStatusBannerKey)}
-                onOpenProviderSetup={openProviderSetupStable}
-              />
-              <ThreadErrorBanner
-                error={timelineThreadError}
-                errorClass={
-                  localServerError === null && visibleThreadError === serverRuntime?.lastError
-                    ? (serverRuntime?.lastErrorClass ?? null)
-                    : null
-                }
-                onDismiss={() => {
-                  setThreadError(activeThread.id, null);
-                  dismissThreadErrorBannerForSession(threadErrorBannerKey);
-                  setThreadErrorBannerDismissTick((tick) => tick + 1);
+            <ChatBannerLane
-                }}
-              />
-            </div>
+              providerStatus={visibleProviderStatus}
+              providerStatusBannerKey={providerStatusBannerKey}
+              setDismissedProviderStatusBannerKey={setDismissedProviderStatusBannerKey}
+              onOpenProviderSetup={openProviderSetupStable}
+              threadId={activeThread.id}
+              threadError={timelineThreadError}
+              threadErrorClass={
+                localServerError === null && visibleThreadError === serverRuntime?.lastError
+                  ? (serverRuntime?.lastErrorClass ?? null)
+                  : null
+              }
+              threadErrorBannerKey={threadErrorBannerKey}
+              setThreadError={setThreadError}
+              setThreadErrorBannerDismissTick={setThreadErrorBannerDismissTick}
+            />
             <div className="relative flex min-h-0 flex-1 flex-col bg-background">
               <MessagesTimeline
                 citationRequest={paintOnlyDisplayedTimeline ? null : citationRequest}
```

## apps/web/src/components/chat/ChatBannerLane.tsx

Status: added; +57; −0

```diff
@@ -1,0 +1,57 @@
+import type {
+  OrchestrationV2ProviderFailureClass,
+  ProviderInstanceId,
+  ServerProvider,
+  ThreadId,
+} from "@t3tools/contracts";
+import type { Dispatch, SetStateAction } from "react";
+import { ProviderStatusBanner } from "./ProviderStatusBanner";
+import { dismissThreadErrorBannerForSession, ThreadErrorBanner } from "./ThreadErrorBanner";
+
+/**
+ * The banners pinned above the timeline. The dismiss handlers are built here from plain
+ * values, so a ChatView render that changes neither banner leaves both cached.
+ */
+export function ChatBannerLane(props: {
+  providerStatus: ServerProvider | null;
+  providerStatusBannerKey: string | null;
+  setDismissedProviderStatusBannerKey: Dispatch<SetStateAction<string | null>>;
+  onOpenProviderSetup: (instanceId: ProviderInstanceId) => void;
+  threadId: ThreadId;
+  threadError: string | null;
+  threadErrorClass: OrchestrationV2ProviderFailureClass | null;
+  threadErrorBannerKey: string | null;
+  setThreadError: (threadId: ThreadId | null, error: string | null) => void;
+  setThreadErrorBannerDismissTick: Dispatch<SetStateAction<number>>;
+}) {
+  const {
+    providerStatus,
+    providerStatusBannerKey,
+    setDismissedProviderStatusBannerKey,
+    onOpenProviderSetup,
+    threadId,
+    threadError,
+    threadErrorClass,
+    threadErrorBannerKey,
+    setThreadError,
+    setThreadErrorBannerDismissTick,
+  } = props;
+  return (
+    <div className="chat-banner-lane pointer-events-none absolute top-0 z-30 flex flex-col">
+      <ProviderStatusBanner
+        status={providerStatus}
+        onDismiss={() => setDismissedProviderStatusBannerKey(providerStatusBannerKey)}
+        onOpenProviderSetup={onOpenProviderSetup}
+      />
+      <ThreadErrorBanner
+        error={threadError}
+        errorClass={threadErrorClass}
+        onDismiss={() => {
+          setThreadError(threadId, null);
+          dismissThreadErrorBannerForSession(threadErrorBannerKey);
+          setThreadErrorBannerDismissTick((tick) => tick + 1);
+        }}
+      />
+    </div>
+  );
+}
```
