From 8b12eee211823c58d6cef437d56ef0730ba2101b Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Mon, 24 Aug 2026 07:17:22 -0700 Subject: [PATCH 1/2] Show error when pinned messages fail to load Closes CL-6832 --- packages/chat-ui/src/chat-workspace.tsx | 4 +- packages/chat-ui/src/index.ts | 1 + packages/chat-ui/src/pinned-strip.tsx | 28 +++++- packages/chat-ui/src/pins-status.test.ts | 95 +++++++++++++++++++ packages/chat-ui/src/strings.ts | 1 + packages/chat-ui/src/styles.css | 10 ++ packages/chat-ui/src/use-workbench-feed.ts | 60 +++++++++--- .../chat-ui/test/reactions-and-pins.test.tsx | 49 +++++++++- .../zero-refetch-on-stream-event.test.tsx | 3 +- 9 files changed, 230 insertions(+), 21 deletions(-) create mode 100644 packages/chat-ui/src/pins-status.test.ts diff --git a/packages/chat-ui/src/chat-workspace.tsx b/packages/chat-ui/src/chat-workspace.tsx index 7cb26e5bd..f5eff52de 100644 --- a/packages/chat-ui/src/chat-workspace.tsx +++ b/packages/chat-ui/src/chat-workspace.tsx @@ -641,7 +641,7 @@ function ChatWorkspaceInner({ activeWorkbenchId, ...(onWorkbenchNotFound !== undefined ? { onWorkbenchNotFound } : {}), }); - const { threads, rootThreadId, pinnedMessages, refreshFeed } = feed; + const { threads, rootThreadId, pinsStatus, refreshFeed } = feed; const navigation = useThreadNavigation({ tenantId, @@ -1357,7 +1357,7 @@ function ChatWorkspaceInner({ <> {!inThreadView ? ( ) : null} diff --git a/packages/chat-ui/src/index.ts b/packages/chat-ui/src/index.ts index d103a7e04..3abe740f2 100644 --- a/packages/chat-ui/src/index.ts +++ b/packages/chat-ui/src/index.ts @@ -22,6 +22,7 @@ export { export type { AvatarFill, GeneratedAvatarStyle } from "./avatar-identity"; export { PinnedStrip } from "./pinned-strip"; +export type { PinsStatus } from "./use-workbench-feed"; export { Composer, draftAfterSend, diff --git a/packages/chat-ui/src/pinned-strip.tsx b/packages/chat-ui/src/pinned-strip.tsx index 300b1e731..b33c44c22 100644 --- a/packages/chat-ui/src/pinned-strip.tsx +++ b/packages/chat-ui/src/pinned-strip.tsx @@ -3,11 +3,16 @@ // nothing when there are no pins — an empty strip is chrome nobody // asked to see, the same "no items, no surface" rule the timeline's own // empty state follows for the reverse case (see `WorkbenchTimeline`). +// +// A load failure is distinct from honest empty (CL-6832): the strip +// surfaces an alert instead of disappearing. A host with no pins store +// (`unavailable`) still renders nothing — absent feature, absent chrome. import { PushPin } from "@corbits/icons"; import type { PinnedMessage } from "./api"; import { CHAT_STRINGS } from "./strings"; +import type { PinsStatus } from "./use-workbench-feed"; /** * A short, single-line preview of a pinned message's content — its @@ -28,16 +33,31 @@ function previewOf(item: PinnedMessage): string { } export function PinnedStrip({ - items, + status, onJump, }: { - readonly items: readonly PinnedMessage[]; + readonly status: PinsStatus; /** Scrolls the timeline to the pinned message's own row — the host * owns nothing here beyond calling this; `PinnedStrip` resolves the * DOM id itself via `messageDomId`. */ readonly onJump: (messageId: string) => void; }) { - if (items.length === 0) return null; + if (status.kind === "loading" || status.kind === "unavailable") return null; + + if (status.kind === "error") { + return ( +
+
+ ); + } + + if (status.items.length === 0) return null; return (