Skip to content

Commit 8611209

Browse files
committed
fix(mothership): anchor a late re-queue on the messages ahead of it, not an index
A restored message went back at an index captured at dispatch, offset by how many messages a chat's queue held when the new-chat queue moved into it. Removing any of those while the POST was out shifted it behind a newer message. It now goes right after the last message still queued that was ahead of it (at dispatch, or in the chat's queue before the move), else at the head.
1 parent 75ae554 commit 8611209

4 files changed

Lines changed: 60 additions & 23 deletions

File tree

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4365,7 +4365,7 @@ export function useChat(
43654365
chatless surface, whose key dies with the mount, goes to the
43664366
cross-surface lanes. */
43674367
/** The new-chat queue may have moved to its chat while the POST was out. */
4368-
const { chatKey: requeueKey, index: requeueIndex } = liveQueuePosition(activeChatKey, 0)
4368+
const { chatKey: requeueKey, index: requeueIndex } = liveQueuePosition(activeChatKey, [])
43694369
const chatless = requeueKey.startsWith(PENDING_CHAT_KEY_PREFIX)
43704370
if (result.reason === 'withdrawn' && chatless) {
43714371
handOffWithdrawnSend({ ...payload, userMessageId: result.userMessageId })
@@ -4906,8 +4906,10 @@ export function useChat(
49064906
const dispatchChatKey = chatKeyRef.current
49074907
const queueAtStart =
49084908
useMothershipQueueStore.getState().queues[dispatchChatKey] ?? EMPTY_MESSAGE_QUEUE
4909-
let originalIndex = queueAtStart.findIndex((queued) => queued.id === msg.id)
4910-
if (originalIndex === -1) {
4909+
const startIndex = queueAtStart.findIndex((queued) => queued.id === msg.id)
4910+
/** What was queued ahead of it, which it goes back behind if it is restored. */
4911+
let aheadIds = queueAtStart.slice(0, Math.max(0, startIndex)).map((queued) => queued.id)
4912+
if (startIndex === -1) {
49114913
queuedMessageDispatchIds.delete(msg.id)
49124914
return
49134915
}
@@ -4936,7 +4938,7 @@ export function useChat(
49364938
admitted, which moved this queue to that chat. */
49374939
const { chatKey: restoreKey, index: restoreIndex } = liveQueuePosition(
49384940
dispatchChatKey,
4939-
originalIndex
4941+
aheadIds
49404942
)
49414943
const chatless = restoreKey.startsWith(PENDING_CHAT_KEY_PREFIX)
49424944
const savedHandoff = readQueuedSendHandoffState()
@@ -5009,7 +5011,7 @@ export function useChat(
50095011
if (currentIndex === -1) {
50105012
return
50115013
}
5012-
originalIndex = currentIndex
5014+
aheadIds = queueAtSend.slice(0, currentIndex).map((queued) => queued.id)
50135015

50145016
// Re-read live: the user may have applied an in-place edit (`replaceAt`)
50155017
// between dispatch scheduling and this send.

‎apps/sim/stores/mothership-queue/store.test.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ describe('useMothershipQueueStore', () => {
200200
useMothershipQueueStore.getState().enqueue('pending::moved', message('moved'))
201201
useMothershipQueueStore.getState().migrate('pending::moved', 'chat-Y')
202202

203-
const position = liveQueuePosition('pending::moved', 0)
203+
const position = liveQueuePosition('pending::moved', [])
204204
useMothershipQueueStore.getState().insertAt(position.chatKey, position.index, message('late'))
205205

206206
expect(position).toEqual({ chatKey: 'chat-Y', index: 2 })
@@ -212,6 +212,23 @@ describe('useMothershipQueueStore', () => {
212212
])
213213
})
214214

215+
it('keeps a late write in order when messages ahead of it were removed meanwhile', () => {
216+
useMothershipQueueStore.getState().enqueue('chat-Z', message('older'))
217+
useMothershipQueueStore.getState().enqueue('pending::sent', message('later'))
218+
useMothershipQueueStore.getState().migrate('pending::sent', 'chat-Z')
219+
useMothershipQueueStore.getState().remove('chat-Z', 'older')
220+
221+
const position = liveQueuePosition('pending::sent', [])
222+
useMothershipQueueStore
223+
.getState()
224+
.insertAt(position.chatKey, position.index, message('follow-up'))
225+
226+
expect(useMothershipQueueStore.getState().queues['chat-Z']?.map((m) => m.id)).toEqual([
227+
'follow-up',
228+
'later',
229+
])
230+
})
231+
215232
it('merges into an existing destination bucket instead of overwriting', () => {
216233
useMothershipQueueStore.getState().enqueue('chat-X', message('existing-1'))
217234
useMothershipQueueStore.getState().enqueue('chat-X', message('existing-2'))

‎apps/sim/stores/mothership-queue/store.ts‎

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -112,27 +112,42 @@ const setQueueForChat = (
112112
next.length === 0 ? omitKey(queues, chatKey) : { ...queues, [chatKey]: next }
113113

114114
/**
115-
* Where a position a write captured before an `await` lies now: in the queue a
116-
* new-chat queue migrated to once its chat became known, if it did, behind the
117-
* messages that queue already held.
115+
* The queue key a write captured before an `await` should use now: the key a
116+
* new-chat queue migrated to once its chat became known, if it did.
117+
*/
118+
export function liveQueueKey(chatKey: string): string {
119+
const { migratedTo } = useMothershipQueueStore.getState()
120+
let key = chatKey
121+
for (let hops = 0; hops < 8 && migratedTo[key] !== undefined; hops++) key = migratedTo[key].key
122+
return key
123+
}
124+
125+
/**
126+
* Where a message goes back into its queue after a write captured before an
127+
* `await`: in the queue's live key, right after the last message still there
128+
* that was ahead of it (`aheadIds`, plus whatever a chat's queue already held
129+
* when a new-chat queue moved into it), else at the head. Anchoring on ids, not
130+
* an index, keeps it in order however the queue changed meanwhile.
118131
*/
119132
export function liveQueuePosition(
120133
chatKey: string,
121-
index: number
134+
aheadIds: readonly string[]
122135
): { chatKey: string; index: number } {
123-
const { migratedTo } = useMothershipQueueStore.getState()
124-
let position = { chatKey, index }
136+
const { migratedTo, queues } = useMothershipQueueStore.getState()
137+
const ahead = new Set(aheadIds)
138+
let key = chatKey
125139
for (let hops = 0; hops < 8; hops++) {
126-
const migration = migratedTo[position.chatKey]
140+
const migration = migratedTo[key]
127141
if (!migration) break
128-
position = { chatKey: migration.key, index: position.index + migration.behind }
142+
for (const id of migration.ahead) ahead.add(id)
143+
key = migration.key
129144
}
130-
return position
131-
}
132-
133-
/** The queue key a write captured before an `await` should use now (see `liveQueuePosition`). */
134-
export function liveQueueKey(chatKey: string): string {
135-
return liveQueuePosition(chatKey, 0).chatKey
145+
const queue = queues[key] ?? []
146+
let index = 0
147+
queue.forEach((message, position) => {
148+
if (ahead.has(message.id)) index = position + 1
149+
})
150+
return { chatKey: key, index }
136151
}
137152

138153
export const useMothershipQueueStore = create<MothershipQueueState>()(
@@ -222,7 +237,10 @@ export const useMothershipQueueStore = create<MothershipQueueState>()(
222237
if (fromKey === toKey) return state
223238
const migratedTo = {
224239
...state.migratedTo,
225-
[fromKey]: { key: toKey, behind: state.queues[toKey]?.length ?? 0 },
240+
[fromKey]: {
241+
key: toKey,
242+
ahead: (state.queues[toKey] ?? []).map((message) => message.id),
243+
},
226244
}
227245
const fromQueue = state.queues[fromKey]
228246
const fromEditing = state.editing[fromKey]

‎apps/sim/stores/mothership-queue/types.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export type QueuedMessageEditPatch = Pick<
5959
/** A new-chat queue's move to its chat's key. */
6060
export interface QueueMigration {
6161
key: string
62-
/** Messages the chat's queue already held, which stay ahead of the moved ones. */
63-
behind: number
62+
/** Ids of the messages the chat's queue already held, which stay ahead of the moved ones. */
63+
ahead: string[]
6464
}
6565

6666
export interface MothershipQueueState {

0 commit comments

Comments
 (0)