@@ -40,20 +40,44 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
40
40
const messagesLoadedRef = useRef ( false ) ;
41
41
const agentRunsCheckedRef = useRef ( false ) ;
42
42
const hasInitiallyScrolled = useRef < boolean > ( false ) ;
43
+
44
+ // Store messages by thread ID to preserve across navigation
45
+ const threadMessagesRef = useRef < Map < string , UnifiedMessage [ ] > > ( new Map ( ) ) ;
46
+ // Track the last active thread id to correctly cache on navigation
47
+ const lastThreadIdRef = useRef < string > ( threadId ) ;
43
48
44
49
const threadQuery = useThreadQuery ( threadId ) ;
45
50
const messagesQuery = useMessagesQuery ( threadId ) ;
46
51
const projectQuery = useProjectQuery ( projectId ) ;
47
52
const agentRunsQuery = useAgentRunsQuery ( threadId ) ;
48
-
53
+
54
+ // (debug logs removed)
49
55
50
56
useEffect ( ( ) => {
51
57
let isMounted = true ;
52
58
59
+ // Store current messages before switching threads using the actual last thread id
60
+ const previousThreadId = lastThreadIdRef . current ;
61
+ if ( previousThreadId && previousThreadId !== threadId && messages . length > 0 ) {
62
+ threadMessagesRef . current . set ( previousThreadId , [ ...messages ] ) ;
63
+ }
64
+
53
65
// Reset refs when thread changes
66
+ // (debug logs removed)
54
67
agentRunsCheckedRef . current = false ;
55
68
messagesLoadedRef . current = false ;
56
69
initialLoadCompleted . current = false ;
70
+
71
+ // Restore cached messages for this thread if available
72
+ const cachedMessages = threadMessagesRef . current . get ( threadId ) ;
73
+ if ( cachedMessages && cachedMessages . length > 0 ) {
74
+ setMessages ( cachedMessages ) ;
75
+ } else {
76
+ setMessages ( [ ] ) ;
77
+ }
78
+
79
+ // Update last thread id tracker to the new thread
80
+ lastThreadIdRef . current = threadId ;
57
81
58
82
async function initializeData ( ) {
59
83
if ( ! initialLoadCompleted . current ) setIsLoading ( true ) ;
@@ -78,7 +102,7 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
78
102
}
79
103
80
104
if ( messagesQuery . data && ! messagesLoadedRef . current ) {
81
-
105
+ // (debug logs removed)
82
106
83
107
const unifiedMessages = ( messagesQuery . data || [ ] )
84
108
. filter ( ( msg ) => msg . type !== 'status' )
@@ -91,9 +115,30 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
91
115
metadata : msg . metadata || '{}' ,
92
116
created_at : msg . created_at || new Date ( ) . toISOString ( ) ,
93
117
updated_at : msg . updated_at || new Date ( ) . toISOString ( ) ,
118
+ agent_id : ( msg as any ) . agent_id ,
119
+ agents : ( msg as any ) . agents ,
94
120
} ) ) ;
95
121
96
- setMessages ( unifiedMessages ) ;
122
+ // Merge with any local messages that are not present in server data yet
123
+ const serverIds = new Set (
124
+ unifiedMessages . map ( ( m ) => m . message_id ) . filter ( Boolean ) as string [ ] ,
125
+ ) ;
126
+ const localExtras = ( messages || [ ] ) . filter (
127
+ ( m ) =>
128
+ ! m . message_id ||
129
+ ( typeof m . message_id === 'string' && m . message_id . startsWith ( 'temp-' ) ) ||
130
+ ! serverIds . has ( m . message_id as string ) ,
131
+ ) ;
132
+ const mergedMessages = [ ...unifiedMessages , ...localExtras ] . sort ( ( a , b ) => {
133
+ const aTime = a . created_at ? new Date ( a . created_at ) . getTime ( ) : 0 ;
134
+ const bTime = b . created_at ? new Date ( b . created_at ) . getTime ( ) : 0 ;
135
+ return aTime - bTime ;
136
+ } ) ;
137
+
138
+ setMessages ( mergedMessages ) ;
139
+ // Update cache for this thread
140
+ threadMessagesRef . current . set ( threadId , [ ...mergedMessages ] ) ;
141
+ // (debug logs removed)
97
142
messagesLoadedRef . current = true ;
98
143
99
144
if ( ! hasInitiallyScrolled . current ) {
@@ -102,7 +147,7 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
102
147
}
103
148
104
149
if ( agentRunsQuery . data && ! agentRunsCheckedRef . current && isMounted ) {
105
-
150
+ // (debug logs removed)
106
151
107
152
agentRunsCheckedRef . current = true ;
108
153
@@ -169,12 +214,17 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
169
214
agentRunsQuery . data
170
215
] ) ;
171
216
172
- // Disabled automatic message replacement to prevent optimistic message deletion
173
- // Messages are now only loaded on initial page load and updated via streaming
217
+ // Force message reload when thread changes or new data arrives
174
218
useEffect ( ( ) => {
175
- if ( messagesQuery . data && messagesQuery . status === 'success' ) {
176
- // Only load messages on initial load, not when agent status changes
177
- if ( ! isLoading && messages . length === 0 ) {
219
+ if ( messagesQuery . data && messagesQuery . status === 'success' && ! isLoading ) {
220
+ // (debug logs removed)
221
+
222
+ // Always reload messages when thread data changes or we have more raw messages than processed
223
+ const shouldReload = messages . length === 0 || messagesQuery . data . length > messages . length + 50 ; // Allow for status messages
224
+
225
+ if ( shouldReload ) {
226
+ // (debug logs removed)
227
+
178
228
const unifiedMessages = ( messagesQuery . data || [ ] )
179
229
. filter ( ( msg ) => msg . type !== 'status' )
180
230
. map ( ( msg : ApiMessageType ) => ( {
@@ -190,14 +240,47 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
190
240
agents : ( msg as any ) . agents ,
191
241
} ) ) ;
192
242
193
- setMessages ( unifiedMessages ) ;
243
+ // Merge strategy: preserve any local (optimistic/streamed) messages not in server yet
244
+ setMessages ( ( prev ) => {
245
+ const serverIds = new Set (
246
+ unifiedMessages . map ( ( m ) => m . message_id ) . filter ( Boolean ) as string [ ] ,
247
+ ) ;
248
+ const localExtras = ( prev || [ ] ) . filter (
249
+ ( m ) =>
250
+ ! m . message_id ||
251
+ ( typeof m . message_id === 'string' && m . message_id . startsWith ( 'temp-' ) ) ||
252
+ ! serverIds . has ( m . message_id as string ) ,
253
+ ) ;
254
+ const merged = [ ...unifiedMessages , ...localExtras ] . sort ( ( a , b ) => {
255
+ const aTime = a . created_at ? new Date ( a . created_at ) . getTime ( ) : 0 ;
256
+ const bTime = b . created_at ? new Date ( b . created_at ) . getTime ( ) : 0 ;
257
+ return aTime - bTime ;
258
+ } ) ;
259
+
260
+ // Update cache for this thread
261
+ threadMessagesRef . current . set ( threadId , [ ...merged ] ) ;
262
+ // (debug logs removed)
263
+ return merged ;
264
+ } ) ;
265
+ } else {
266
+ // (debug logs removed)
194
267
}
195
268
}
196
269
} , [ messagesQuery . data , messagesQuery . status , isLoading , messages . length , threadId ] ) ;
197
270
271
+ // Wrap setMessages to also update the cache
272
+ const setMessagesWithCache = ( messagesOrUpdater : UnifiedMessage [ ] | ( ( prev : UnifiedMessage [ ] ) => UnifiedMessage [ ] ) ) => {
273
+ setMessages ( ( prev ) => {
274
+ const newMessages = typeof messagesOrUpdater === 'function' ? messagesOrUpdater ( prev ) : messagesOrUpdater ;
275
+ // Update cache whenever messages change
276
+ threadMessagesRef . current . set ( threadId , [ ...newMessages ] ) ;
277
+ return newMessages ;
278
+ } ) ;
279
+ } ;
280
+
198
281
return {
199
282
messages,
200
- setMessages,
283
+ setMessages : setMessagesWithCache ,
201
284
project,
202
285
sandboxId,
203
286
projectName,
0 commit comments