From 3df9107e5829bbaad3462789fa3271124c2f3e3e Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 19 Jun 2026 06:41:28 -0700 Subject: [PATCH] FN-6710: use Working for chat wait states Chat now labels pre-stream assistant activity as work in progress instead of a connection step. - Switch full Chat and Quick Chat waiting copy to the chat.workingStatus translation key. - Rename localized chat loading text from Connecting to Working across shipped locales and generated resource types. - Update chat recovery docs, implementation comments, and tests to expect the Working indicator. Files changed: docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/ChatView.tsx | 6 +++++- packages/dashboard/app/components/QuickChatFAB.tsx | 2 +- packages/dashboard/app/components/__tests__/ChatView.test.tsx | 10 +++++----- .../dashboard/app/components/__tests__/QuickChatFAB.test.tsx | 4 ++-- packages/dashboard/app/hooks/useChat.ts | 4 ++-- packages/dashboard/app/hooks/useQuickChat.ts | 2 +- packages/i18n/locales/en/app.json | 2 +- packages/i18n/locales/es/app.json | 2 +- packages/i18n/locales/fr/app.json | 2 +- packages/i18n/locales/ko/app.json | 2 +- packages/i18n/locales/zh-CN/app.json | 2 +- packages/i18n/locales/zh-TW/app.json | 2 +- packages/i18n/src/resources.d.ts | 2 +- 14 files changed, 24 insertions(+), 20 deletions(-) Fusion-Task-Id: FN-6710 Fusion-Task-Lineage: a7592c7d-605f-4a9b-b0d3-be875ed2eff5 --- docs/dashboard-guide.md | 2 +- packages/dashboard/app/components/ChatView.tsx | 6 +++++- packages/dashboard/app/components/QuickChatFAB.tsx | 2 +- .../app/components/__tests__/ChatView.test.tsx | 10 +++++----- .../app/components/__tests__/QuickChatFAB.test.tsx | 4 ++-- packages/dashboard/app/hooks/useChat.ts | 4 ++-- packages/dashboard/app/hooks/useQuickChat.ts | 2 +- packages/i18n/locales/en/app.json | 2 +- packages/i18n/locales/es/app.json | 2 +- packages/i18n/locales/fr/app.json | 2 +- packages/i18n/locales/ko/app.json | 2 +- packages/i18n/locales/zh-CN/app.json | 2 +- packages/i18n/locales/zh-TW/app.json | 2 +- packages/i18n/src/resources.d.ts | 2 +- 14 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 3047d71c20..936d97a59a 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -238,7 +238,7 @@ Chat view provides project-scoped conversations with agents. - Entering `/new` or `/clear` (exact match after trimming) in the composer starts a fresh thread for the current chat target instead of sending the literal command to the model - On mobile, the New Chat and Delete Conversation dialogs use a compact inset treatment (centered, viewport-bounded, internally scrollable) instead of the app's default full-height mobile modal chrome. - Full Chat and Quick Chat both consume the same streamed `/api/chat/sessions/:id/messages` response contract, and both now prefer the authoritative assistant `message` snapshot on `done` while still accumulating `text` chunks when present (so providers without incremental text streaming still render output immediately) -- In-progress assistant responses now survive refresh/navigation while generation is still active: Chat restores the last durable in-flight text/thinking/tool state immediately, keeps the prior persisted conversation visible, then resumes streaming from the stored replay point; any new text, thinking, or tool-call updates append to that restored bubble instead of replacing it or starting from an empty "Connecting…" placeholder. +- In-progress assistant responses now survive refresh/navigation while generation is still active: Chat restores the last durable in-flight text/thinking/tool state immediately, keeps the prior persisted conversation visible, then resumes streaming from the stored replay point; any new text, thinking, or tool-call updates append to that restored bubble instead of replacing it or starting from an empty "Working…" placeholder. - If a regular Chat stream drops with a hidden-tab/browser-suspension error (for example `Load failed`) while the server is still generating, Chat suppresses the false error banner, re-attaches to the in-progress stream using the durable replay state, and reconciles the final assistant reply when generation completes. - If you queue a follow-up user message while the assistant is still streaming, Chat now persists that queued text per session so leaving and returning to the view still restores and sends it once the active response finishes. - Chat message lists now track near-bottom scroll state: while you are reading older messages, live streaming/new replies do not force-scroll; a **Latest** jump control appears until you return to the tail. diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx index 5b37c1b2e6..33140494bf 100644 --- a/packages/dashboard/app/components/ChatView.tsx +++ b/packages/dashboard/app/components/ChatView.tsx @@ -2892,7 +2892,11 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView renderAssistantContent(streamingText, showAllAsPlain) ) : (
- {streamingThinking ? t("chat.thinkingStatus", "Thinking…") : t("chat.connectingStatus", "Connecting…")} + {/* + FNXC:ChatLoadingCopy 2026-06-19-06:13: + The post-send waiting indicator reads "Working…" when no streamed text or thinking content has arrived yet, because the UI is waiting on work rather than a transport connection. + */} + {streamingThinking ? t("chat.thinkingStatus", "Thinking…") : t("chat.workingStatus", "Working…")}
)} {showProviderResponseCopy && streamingText && renderCopyAction("__streaming__", streamingText, "chat-copy-response-streaming")} diff --git a/packages/dashboard/app/components/QuickChatFAB.tsx b/packages/dashboard/app/components/QuickChatFAB.tsx index dd1c917bfa..a8144de985 100644 --- a/packages/dashboard/app/components/QuickChatFAB.tsx +++ b/packages/dashboard/app/components/QuickChatFAB.tsx @@ -3231,7 +3231,7 @@ export function QuickChatFAB({ ) : (

- {streamingThinking ? t("chat.thinkingStatus", "Thinking…") : t("chat.connectingStatus", "Connecting…")} + {streamingThinking ? t("chat.thinkingStatus", "Thinking…") : t("chat.workingStatus", "Working…")}

)} {renderToolCalls(streamingToolCalls, true, t, { diff --git a/packages/dashboard/app/components/__tests__/ChatView.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.test.tsx index 8b26e1e41b..d0c46c9899 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.test.tsx @@ -2469,10 +2469,10 @@ describe("ChatView", () => { const { rerender } = await renderWithAct(); - expect(document.querySelector(".chat-message--streaming")?.textContent).toContain("Connecting"); + expect(document.querySelector(".chat-message--streaming")?.textContent).toContain("Working"); rerender(); - expect(document.querySelector(".chat-message--streaming")?.textContent).toContain("Connecting"); + expect(document.querySelector(".chat-message--streaming")?.textContent).toContain("Working"); expect(screen.queryByText("Start a new conversation")).not.toBeInTheDocument(); expect(screen.queryByText("No messages yet. Start the conversation!")).not.toBeInTheDocument(); expect(screen.getByTestId("chat-back-btn")).toBeInTheDocument(); @@ -2494,7 +2494,7 @@ describe("ChatView", () => { const streamingMessage = document.querySelector(".chat-message--streaming") as HTMLElement | null; expect(streamingMessage).toBeInTheDocument(); - expect(streamingMessage?.textContent).toContain("Connecting"); + expect(streamingMessage?.textContent).toContain("Working"); expect(screen.queryByText("Loading messages...")).not.toBeInTheDocument(); }); @@ -2511,10 +2511,10 @@ describe("ChatView", () => { await renderWithAct(); - // Streaming message should show with "Connecting..." text + // Streaming message should show with "Working..." text const streamingMessage = document.querySelector(".chat-message--streaming") as HTMLElement | null; expect(streamingMessage).toBeInTheDocument(); - expect(streamingMessage?.textContent).toContain("Connecting"); + expect(streamingMessage?.textContent).toContain("Working"); // Waiting class should be present const waitingContent = streamingMessage?.querySelector(".chat-message-content--waiting"); diff --git a/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx b/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx index e13c1b24d2..46a9394208 100644 --- a/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx +++ b/packages/dashboard/app/components/__tests__/QuickChatFAB.test.tsx @@ -1685,7 +1685,7 @@ describe("QuickChatFAB session-first UX", () => { fireEvent.click(screen.getByTestId("quick-chat-send")); expect(await screen.findByTestId("quick-chat-streaming-message")).toBeInTheDocument(); - expect(screen.getByTestId("quick-chat-waiting")).toHaveTextContent("Connecting…"); + expect(screen.getByTestId("quick-chat-waiting")).toHaveTextContent("Working…"); expect(mockStreamChatResponse).toHaveBeenCalledTimes(2); }); @@ -1747,7 +1747,7 @@ describe("QuickChatFAB session-first UX", () => { fireEvent.click(screen.getByTestId("quick-chat-send")); expect(await screen.findByTestId("quick-chat-streaming-message")).toBeInTheDocument(); - expect(screen.getByTestId("quick-chat-waiting")).toHaveTextContent("Connecting…"); + expect(screen.getByTestId("quick-chat-waiting")).toHaveTextContent("Working…"); expect(screen.queryByText("Loading conversation…")).not.toBeInTheDocument(); }); diff --git a/packages/dashboard/app/hooks/useChat.ts b/packages/dashboard/app/hooks/useChat.ts index 4fe354876f..d28bd23e16 100644 --- a/packages/dashboard/app/hooks/useChat.ts +++ b/packages/dashboard/app/hooks/useChat.ts @@ -684,8 +684,8 @@ export function useChat( // Recover streaming state if the server reports an active generation. // After a reload/HMR, the server keeps generating but the UI loses - // all streaming state. Showing "Connecting…" immediately tells the - // user the AI is still working. + // all streaming state. Showing "Working…" immediately tells the + // user the AI is still processing the request. if (session?.isGenerating) { attachIfGenerating(session.id, session.inFlightGeneration, { priorThreadLoadAlreadyStarted: true }); } diff --git a/packages/dashboard/app/hooks/useQuickChat.ts b/packages/dashboard/app/hooks/useQuickChat.ts index 5c17f7d71b..0cf2032573 100644 --- a/packages/dashboard/app/hooks/useQuickChat.ts +++ b/packages/dashboard/app/hooks/useQuickChat.ts @@ -457,7 +457,7 @@ export function useQuickChat( // Recover streaming state if server is still generating for this session. // After a reload/HMR, the server keeps generating but the UI loses - // all streaming state. Show the "Connecting…" indicator immediately. + // all streaming state. Show the "Working…" indicator immediately. if (existingSession.isGenerating) { attachIfGenerating(existingSession.id, existingSession.inFlightGeneration); } diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json index 59fd830004..2b3b5c672f 100644 --- a/packages/i18n/locales/en/app.json +++ b/packages/i18n/locales/en/app.json @@ -1195,7 +1195,7 @@ "cancelButton": "Cancel", "clearConversationFailed": "Failed to clear conversation", "closeQuickChat": "Close quick chat", - "connectingStatus": "Connecting…", + "workingStatus": "Working…", "conversationArchived": "Conversation archived", "conversationDeleted": "Conversation deleted", "copyFailed": "Copy failed", diff --git a/packages/i18n/locales/es/app.json b/packages/i18n/locales/es/app.json index 94e4074d89..94bc9f1348 100644 --- a/packages/i18n/locales/es/app.json +++ b/packages/i18n/locales/es/app.json @@ -1194,7 +1194,7 @@ "cancelButton": "Cancelar", "clearConversationFailed": "Error al borrar la conversación", "closeQuickChat": "Cerrar chat rápido", - "connectingStatus": "Conectando…", + "workingStatus": "Trabajando…", "conversationArchived": "Conversación archivada", "conversationDeleted": "Conversación eliminada", "copyFailed": "Error al copiar", diff --git a/packages/i18n/locales/fr/app.json b/packages/i18n/locales/fr/app.json index 2e06ede9a6..7df37dc150 100644 --- a/packages/i18n/locales/fr/app.json +++ b/packages/i18n/locales/fr/app.json @@ -1194,7 +1194,7 @@ "cancelButton": "Annuler", "clearConversationFailed": "Échec de l'effacement de la conversation", "closeQuickChat": "Fermer le chat rapide", - "connectingStatus": "Connexion en cours…", + "workingStatus": "Traitement en cours…", "conversationArchived": "Conversation archivée", "conversationDeleted": "Conversation supprimée", "copyFailed": "Échec de la copie", diff --git a/packages/i18n/locales/ko/app.json b/packages/i18n/locales/ko/app.json index 5174aae306..9c8b8be5ea 100644 --- a/packages/i18n/locales/ko/app.json +++ b/packages/i18n/locales/ko/app.json @@ -1194,7 +1194,7 @@ "cancelButton": "취소", "clearConversationFailed": "대화 초기화 실패", "closeQuickChat": "빠른 채팅 닫기", - "connectingStatus": "연결 중…", + "workingStatus": "작업 중…", "conversationArchived": "대화가 보관되었습니다", "conversationDeleted": "대화가 삭제되었습니다", "copyFailed": "복사 실패", diff --git a/packages/i18n/locales/zh-CN/app.json b/packages/i18n/locales/zh-CN/app.json index aac8bfd9c3..4c9435e4d3 100644 --- a/packages/i18n/locales/zh-CN/app.json +++ b/packages/i18n/locales/zh-CN/app.json @@ -1194,7 +1194,7 @@ "cancelButton": "取消", "clearConversationFailed": "清除对话失败", "closeQuickChat": "关闭快速聊天", - "connectingStatus": "连接中……", + "workingStatus": "处理中……", "conversationArchived": "对话已归档", "conversationDeleted": "对话已删除", "copyFailed": "复制失败", diff --git a/packages/i18n/locales/zh-TW/app.json b/packages/i18n/locales/zh-TW/app.json index 0908f3bcd0..8bdaaa6971 100644 --- a/packages/i18n/locales/zh-TW/app.json +++ b/packages/i18n/locales/zh-TW/app.json @@ -1194,7 +1194,7 @@ "cancelButton": "取消", "clearConversationFailed": "清除對話失敗", "closeQuickChat": "關閉快速聊天", - "connectingStatus": "連接中……", + "workingStatus": "處理中……", "conversationArchived": "對話已封存", "conversationDeleted": "對話已刪除", "copyFailed": "複製失敗", diff --git a/packages/i18n/src/resources.d.ts b/packages/i18n/src/resources.d.ts index e68cd286b9..2b8d1d41ae 100644 --- a/packages/i18n/src/resources.d.ts +++ b/packages/i18n/src/resources.d.ts @@ -1197,7 +1197,7 @@ export default interface Resources { "cancelButton": "Cancel", "clearConversationFailed": "Failed to clear conversation", "closeQuickChat": "Close quick chat", - "connectingStatus": "Connecting…", + "workingStatus": "Working…", "conversationArchived": "Conversation archived", "conversationDeleted": "Conversation deleted", "copyFailed": "Copy failed",