From c77b3966ad3e30581aa7e6eca04540f3e5859110 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 20 Mar 2026 04:58:14 +0000 Subject: [PATCH] fix(web): a11y, loading UX, and chat error surfaces - Add AppLoadingPlaceholder with role=status for auth/route shells - Surface usePersistentChat stream errors with dismiss; resetChatError clears status - Show history loading spinner; empty non-new chat copy; memo-friendly startEdit - Conversation: aria-label on message log; scroll button label - Model selector: stop Tab key trap; model row favorite/info aria-labels - Inline errors: decorative icon hidden; details toggle type and aria-expanded - Landing staircase: aria-hidden decoration Co-authored-by: Leo --- .../__tests__/chat-interface.test.tsx | 1 + .../components/ai-elements/conversation.tsx | 4 +- .../components/app-loading-placeholder.tsx | 44 +++++++++++++ .../src/components/chat/chat-interface.tsx | 30 +++++++-- .../src/components/chat/chat-message-list.tsx | 61 +++++++++++++++++-- .../components/chat/inline-error-message.tsx | 5 +- apps/web/src/components/model-selector.tsx | 4 -- .../components/model-selector/model-item.tsx | 2 + .../__tests__/use-persistent-chat.test.ts | 18 ++++++ apps/web/src/hooks/use-persistent-chat.ts | 10 ++- apps/web/src/routes/__root.tsx | 8 +-- apps/web/src/routes/c/$chatId.tsx | 3 +- apps/web/src/routes/index.tsx | 4 +- 13 files changed, 167 insertions(+), 27 deletions(-) create mode 100644 apps/web/src/components/app-loading-placeholder.tsx diff --git a/apps/web/src/components/__tests__/chat-interface.test.tsx b/apps/web/src/components/__tests__/chat-interface.test.tsx index 2d66ff3d..5ac2b2a7 100644 --- a/apps/web/src/components/__tests__/chat-interface.test.tsx +++ b/apps/web/src/components/__tests__/chat-interface.test.tsx @@ -231,6 +231,7 @@ function makeDefaultState() { chatId: null, isResuming: false, resumedContent: '', + resetChatError: vi.fn(), } } diff --git a/apps/web/src/components/ai-elements/conversation.tsx b/apps/web/src/components/ai-elements/conversation.tsx index 58c7289e..76e9b4ab 100644 --- a/apps/web/src/components/ai-elements/conversation.tsx +++ b/apps/web/src/components/ai-elements/conversation.tsx @@ -83,6 +83,7 @@ export const Conversation = ({ className, children, showScrollButton = false, .. ref={scrollRef} className="absolute inset-0 overflow-y-auto" role="log" + aria-label="Chat messages" > {children} {/* Anchor element for scroll-to-bottom */} @@ -92,10 +93,11 @@ export const Conversation = ({ className, children, showScrollButton = false, .. {showScrollButton && !isAtBottom && ( )} diff --git a/apps/web/src/components/app-loading-placeholder.tsx b/apps/web/src/components/app-loading-placeholder.tsx new file mode 100644 index 00000000..934d6545 --- /dev/null +++ b/apps/web/src/components/app-loading-placeholder.tsx @@ -0,0 +1,44 @@ +import { cn } from "@/lib/utils"; + +type AppLoadingPlaceholderProps = { + /** Announced to screen readers */ + message?: string; + className?: string; + /** Matches authenticated shell: sidebar stub + main pane */ + variant?: "app-shell" | "simple"; +}; + +/** + * Accessible loading placeholder for route-level and auth-gated suspense states. + */ +export function AppLoadingPlaceholder({ + message = "Loading", + className, + variant = "simple", +}: AppLoadingPlaceholderProps) { + if (variant === "app-shell") { + return ( +
+ {message} +