Files
orca/mobile/src/session/mobile-native-chat-pending-echo.ts
T
Brennan Benson d4dfc35ac4 fix(mobile): preserve multi-image chat attachments (#12639)
* fix(mobile): preserve multi-image chat attachments

* fix(mobile): use preferred array syntax

* fix(mobile): harden multi-image attachment flow

* fix(mobile): retain first-send image previews
2026-08-05 13:23:29 -07:00

92 lines
2.6 KiB
TypeScript

export type MobileNativeChatPendingMessage = {
id: string
text: string
expectedOccurrence: number
/** Local preview URIs carried by the send for its optimistic echo. */
images?: string[]
baselineTailMessageId: string | null
}
export type MobileNativeChatSendOrigin = {
draftKey: string
pendingKey: string | null
normalizedText: string
baselineOccurrences: number
baselineTailMessageId: string | null
}
type PendingByKey = Record<string, MobileNativeChatPendingMessage[]>
export function combineMobileNativeChatPending(
session: MobileNativeChatPendingMessage[],
waiting: readonly MobileNativeChatPendingMessage[]
): MobileNativeChatPendingMessage[] {
if (waiting.length === 0) {
return session
}
const sessionIds = new Set(session.map((item) => item.id))
return [...session, ...waiting.filter((item) => !sessionIds.has(item.id))]
}
export function appendMobileNativeChatPending(
previous: PendingByKey,
key: string,
id: string,
origin: MobileNativeChatSendOrigin,
text: string,
images?: string[]
): PendingByKey {
const current = previous[key] ?? []
const earlierOutstanding = current.filter(
(pending) =>
pending.text.trim() === origin.normalizedText &&
pending.expectedOccurrence > origin.baselineOccurrences
).length
const expectedImageEchoOrdinal =
current.filter((pending) => pending.text.trim() === '' && pending.images?.length).length + 1
return {
...previous,
[key]: [
...current,
{
id,
text,
expectedOccurrence:
origin.normalizedText === ''
? expectedImageEchoOrdinal
: origin.baselineOccurrences + earlierOutstanding + 1,
baselineTailMessageId: origin.baselineTailMessageId,
...(images?.length ? { images } : {})
}
]
}
}
export function mergeWaitingSessionPending(
previous: PendingByKey,
sessionKey: string,
waiting: readonly MobileNativeChatPendingMessage[]
): PendingByKey {
const current = previous[sessionKey] ?? []
const currentIds = new Set(current.map((item) => item.id))
const moved = waiting.filter((item) => !currentIds.has(item.id))
return moved.length > 0 ? { ...previous, [sessionKey]: [...current, ...moved] } : previous
}
export function removeWaitingSessionPending(
previous: PendingByKey,
draftKey: string,
movedIds: ReadonlySet<string>
): PendingByKey {
const remaining = (previous[draftKey] ?? []).filter((item) => !movedIds.has(item.id))
if (remaining.length > 0) {
return { ...previous, [draftKey]: remaining }
}
if (!(draftKey in previous)) {
return previous
}
const next = { ...previous }
delete next[draftKey]
return next
}