Stack queued chat sends above the composer and flush them in order. - Persist pending chat sends as per-session FIFO arrays with legacy single-message restore fallback. - Render stacked queued previews above the direct and quick chat composer with per-item dismissal. - Flush one queued message after each completed or safely verified idle generation, preserving reconnect guards. - Extend chat hook and composer tests for multi-message queuing, restore, dismissal, and FIFO sends. - Document the queued stack behavior and add a published package changeset. Files changed: .changeset/fn-7137-stack-queued-chat-messages.md | 7 + docs/dashboard-guide.md | 4 +- packages/dashboard/app/components/ChatView.css | 15 +- packages/dashboard/app/components/ChatView.tsx | 38 ++-- .../__tests__/ChatView.autosize.test.tsx | 2 +- .../__tests__/ChatView.cli-mount.test.tsx | 2 +- .../__tests__/ChatView.core-interactions.test.tsx | 27 ++- .../__tests__/ChatView.default-model-icon.test.tsx | 2 +- .../components/__tests__/ChatView.draft.test.tsx | 2 +- .../__tests__/ChatView.hash-mention.test.tsx | 2 +- .../__tests__/ChatView.mobile-render.test.tsx | 2 +- .../components/__tests__/ChatView.rooms.test.tsx | 2 +- .../__tests__/ChatView.scroll-to-top.test.tsx | 2 +- .../__tests__/ChatView.swipe-back.test.tsx | 2 +- .../components/__tests__/ChatView.test-harness.tsx | 2 +- .../dashboard/app/hooks/__tests__/useChat.test.ts | 247 +++++++++++++++++---- .../app/hooks/chatPendingMessageStorage.ts | 38 +++- packages/dashboard/app/hooks/useChat.ts | 125 ++++++++--- 18 files changed, 390 insertions(+), 131 deletions(-) Fusion-Task-Id: FN-7137 Fusion-Task-Lineage: 65dd5ed7-a8db-447b-9a50-ce73ea7b597f Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
const CHAT_PENDING_MESSAGE_STORAGE_PREFIX = "fusion:chat-pending:";
|
|
|
|
export function getChatPendingMessageKey(sessionId: string | null | undefined): string | null {
|
|
if (!sessionId) {
|
|
return null;
|
|
}
|
|
|
|
return `${CHAT_PENDING_MESSAGE_STORAGE_PREFIX}${sessionId}`;
|
|
}
|
|
|
|
export function getPersistedPendingChatMessages(sessionId: string | null | undefined): string[] {
|
|
const key = getChatPendingMessageKey(sessionId);
|
|
if (!key || typeof window === "undefined") {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
const value = localStorage.getItem(key);
|
|
if (!value) {
|
|
return [];
|
|
}
|
|
|
|
/*
|
|
FNXC:ChatComposer 2026-06-27-00:00:
|
|
Queued chat messages persist as a JSON array so reloads retain FIFO order. Legacy single-string values are coerced to a one-item queue so pre-array in-flight messages are not dropped.
|
|
*/
|
|
try {
|
|
const parsed = JSON.parse(value) as unknown;
|
|
if (Array.isArray(parsed)) {
|
|
return parsed.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0);
|
|
}
|
|
} catch {
|
|
// Fall through to legacy single-string handling.
|
|
}
|
|
|
|
const legacyMessage = value.trim();
|
|
return legacyMessage ? [legacyMessage] : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function setPersistedPendingChatMessages(sessionId: string | null | undefined, messages: string[]): void {
|
|
const key = getChatPendingMessageKey(sessionId);
|
|
if (!key || typeof window === "undefined") {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const normalizedMessages = messages.filter((message) => message.trim().length > 0);
|
|
if (normalizedMessages.length === 0) {
|
|
localStorage.removeItem(key);
|
|
return;
|
|
}
|
|
localStorage.setItem(key, JSON.stringify(normalizedMessages));
|
|
} catch {
|
|
// Ignore localStorage failures so chat queuing still works in-memory.
|
|
}
|
|
}
|
|
|
|
export function removePersistedPendingChatMessages(sessionId: string | null | undefined): void {
|
|
const key = getChatPendingMessageKey(sessionId);
|
|
if (!key || typeof window === "undefined") {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
localStorage.removeItem(key);
|
|
} catch {
|
|
// Ignore localStorage failures so cleanup paths do not throw.
|
|
}
|
|
}
|