@@ -884,7 +884,8 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
884884 // Keep legacy messages visible when older state lacks structured
885885 // entries, but do not synthesize tails for partial structured entries.
886886 hasEntriesField : Array . isArray ( activeSession ?. transportPendingMessageEntries )
887- && activeSession . transportPendingMessageEntries . length > 0 ,
887+ && ( activeSession . transportPendingMessageEntries . length > 0
888+ || typeof activeSession . transportPendingMessageVersion === 'number' ) ,
888889 hasMessagesField : Array . isArray ( activeSession ?. transportPendingMessages ) ,
889890 } ,
890891 )
@@ -1508,14 +1509,32 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
15081509 : typeof event . payload . clientMessageId === 'string'
15091510 ? event . payload . clientMessageId
15101511 : '' ;
1511- removeLocalQueuedEntry ( commandId , typeof event . payload . text === 'string' ? event . payload . text : undefined ) ;
1512+ const deliveredText = typeof event . payload . text === 'string' ? event . payload . text : undefined ;
1513+ removeLocalQueuedEntry ( commandId , deliveredText ) ;
15121514 // Record the delivered id so a stale daemon pending snapshot can't keep
15131515 // showing it as queued (the incoming snapshot is not under our control,
1514- // unlike the optimistic set cleared above).
1515- if ( commandId ) {
1516+ // unlike the optimistic set cleared above). Legacy snapshots synthesize
1517+ // ids from text, so also settle the single matching legacy id when the
1518+ // authoritative timeline echo carries a newer real command/client id.
1519+ const idsToSettle = commandId ? [ commandId ] : [ ] ;
1520+ const normalizedDeliveredText = typeof deliveredText === 'string' ? normalizeQueuedText ( deliveredText ) : '' ;
1521+ if ( normalizedDeliveredText ) {
1522+ const legacyTextMatches = incomingQueuedTransportEntries
1523+ . filter ( ( entry ) => isLegacyTransportPendingMessageId ( entry . clientMessageId , activeSession . name )
1524+ && normalizeQueuedText ( entry . text ) === normalizedDeliveredText )
1525+ . map ( ( entry ) => entry . clientMessageId ) ;
1526+ if ( legacyTextMatches . length === 1 ) idsToSettle . push ( legacyTextMatches [ 0 ] ) ;
1527+ }
1528+ if ( idsToSettle . length > 0 ) {
15161529 setSettledQueuedIds ( ( prev ) => {
1517- if ( prev . has ( commandId ) ) return prev ;
1518- const ids = [ ...prev , commandId ] ;
1530+ let changed = false ;
1531+ const ids = [ ...prev ] ;
1532+ for ( const id of idsToSettle ) {
1533+ if ( ! id || prev . has ( id ) ) continue ;
1534+ ids . push ( id ) ;
1535+ changed = true ;
1536+ }
1537+ if ( ! changed ) return prev ;
15191538 return new Set ( ids . length > 500 ? ids . slice ( - 500 ) : ids ) ;
15201539 } ) ;
15211540 }
@@ -1539,7 +1558,7 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
15391558 } ) ;
15401559 }
15411560 } ) ;
1542- } , [ activeSession , ws ] ) ;
1561+ } , [ activeSession , incomingQueuedTransportEntries , ws ] ) ;
15431562
15441563 // Reset P2P mode on session change
15451564 useEffect ( ( ) => { setP2pMode ( 'solo' ) ; setP2pOpen ( false ) ; } , [ activeSession ?. name ] ) ;
0 commit comments