Files
fusion/packages/dashboard/app/hooks/chatPendingMessageStorage.ts
gsxdsm 5066f90db2 FN-7137: stack queued chat messages
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>
2026-06-27 15:56:42 -07:00

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.
}
}