Skip to content

Commit 3e67769

Browse files
committed
fix(chat): carry attachments through the stored handoff lane too
The unclaimed-event fallback excluded attachment sends and restored them under the disposed mount's pending key. The persisted handoff now carries fileAttachments (they are plain references to already-uploaded files), the home consumer forwards them, and the recovery branch always hands off — no stranded lane remains.
1 parent 45357cb commit 3e67769

4 files changed

Lines changed: 30 additions & 15 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/home.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ export function Home({ chatId, userName, userId, tableViewsEnabled }: HomeProps)
370370
const handoff = MothershipHandoffStorage.consume(workspaceId)
371371
if (!handoff) return
372372
if (handoff.message) {
373-
sendMessage(handoff.message, undefined, handoff.contexts)
373+
sendMessage(handoff.message, handoff.fileAttachments, handoff.contexts)
374374
return
375375
}
376376
const contexts = handoff.contexts ?? []

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.mount-send.test.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,17 @@ describe('useChat mount-settling send recovery', () => {
178178
})
179179

180180
it('re-persists an aborted chatless send as a handoff for the next mount', async () => {
181+
const attachment = {
182+
id: 'file-2',
183+
key: 'uploads/file-2',
184+
filename: 'report.pdf',
185+
media_type: 'application/pdf',
186+
size: 99,
187+
}
181188
const { getResult, unmount } = renderUseChat()
182189

183190
await act(async () => {
184-
void getResult().sendMessage('hello from the palette')
191+
void getResult().sendMessage('hello from the palette', [attachment])
185192
})
186193
await waitFor(() => state.postCalls === 1)
187194

@@ -197,7 +204,9 @@ describe('useChat mount-settling send recovery', () => {
197204
await waitFor(() => window.localStorage.getItem('sim_mothership_handoff') !== null)
198205

199206
expect(allQueuedMessages()).toHaveLength(0)
200-
expect(MothershipHandoffStorage.consume('ws-1')?.message).toBe('hello from the palette')
207+
const handoff = MothershipHandoffStorage.consume('ws-1')
208+
expect(handoff?.message).toBe('hello from the palette')
209+
expect(handoff?.fileAttachments).toEqual([attachment])
201210
})
202211

203212
it('does not re-queue a send the server already received', async () => {

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4506,26 +4506,22 @@ export function useChat(
45064506
the cleanup that aborted this send belongs to a full remount — a
45074507
queue restore would orphan the message under the dead instance's
45084508
key. Deliver to the replacement surface instead: its send listener
4509-
is live by the time this microtask executes, and the event carries
4510-
attachments. When nothing claims it (a real navigation away), a
4511-
one-shot handoff covers attachment-less sends for the next mount;
4512-
attachment payloads exceed what the handoff carries and fall back to
4513-
the queue restore. Chat-bound sends always keep the queue restore
4514-
(their key is the stable chat id). */
4509+
is live by the time this microtask executes. When nothing claims the
4510+
event (a real navigation away), a one-shot handoff covers the next
4511+
mount; both lanes carry attachments. Chat-bound sends keep the queue
4512+
restore — their key is the stable chat id. */
45154513
if (recoverableCleanupAbort && dispatchChatKey.startsWith(PENDING_CHAT_KEY_PREFIX)) {
4516-
if (sendMothershipMessage(msg.content, msg.contexts, msg.fileAttachments)) {
4517-
return
4518-
}
4519-
if (!msg.fileAttachments?.length) {
4514+
if (!sendMothershipMessage(msg.content, msg.contexts, msg.fileAttachments)) {
45204515
MothershipHandoffStorage.store(
45214516
{
45224517
message: msg.content,
45234518
...(msg.contexts?.length ? { contexts: msg.contexts } : {}),
4519+
...(msg.fileAttachments?.length ? { fileAttachments: msg.fileAttachments } : {}),
45244520
},
45254521
workspaceId
45264522
)
4527-
return
45284523
}
4524+
return
45294525
}
45304526
useMothershipQueueStore.getState().insertAt(dispatchChatKey, originalIndex, msg)
45314527
}

apps/sim/lib/core/utils/browser-storage.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { createLogger } from '@sim/logger'
7+
import type { FileAttachmentForApi } from '@/app/workspace/[workspaceId]/home/types'
78
import type { ChatContext } from '@/stores/panel'
89

910
const logger = createLogger('BrowserStorage')
@@ -307,6 +308,8 @@ export interface MothershipHandoff {
307308
message?: string
308309
/** Structured contexts to attach — e.g. a `logs` mention tagging a run. */
309310
contexts?: ChatContext[]
311+
/** Already-uploaded attachment references riding along with the message. */
312+
fileAttachments?: FileAttachmentForApi[]
310313
}
311314

312315
interface StoredHandoff extends MothershipHandoff {
@@ -353,6 +356,7 @@ export class MothershipHandoffStorage {
353356
contexts: message
354357
? contexts
355358
: [...MothershipHandoffStorage.pendingContexts(workspaceId), ...contexts],
359+
...(handoff.fileAttachments?.length ? { fileAttachments: handoff.fileAttachments } : {}),
356360
workspaceId,
357361
timestamp: Date.now(),
358362
})
@@ -409,7 +413,13 @@ export class MothershipHandoffStorage {
409413
return null
410414
}
411415

412-
return { ...(data.message ? { message: data.message } : {}), contexts }
416+
return {
417+
...(data.message ? { message: data.message } : {}),
418+
contexts,
419+
...(Array.isArray(data.fileAttachments) && data.fileAttachments.length > 0
420+
? { fileAttachments: data.fileAttachments }
421+
: {}),
422+
}
413423
}
414424

415425
static clear(): boolean {

0 commit comments

Comments
 (0)