Skip to content

Commit 77bdf24

Browse files
author
IM.codes
committed
Fix web queue sync CI failures
1 parent ef68915 commit 77bdf24

8 files changed

Lines changed: 35 additions & 22 deletions

File tree

web/src/app.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2999,7 +2999,7 @@ export function App() {
29992999
{ hasEntriesField: hasPendingEntriesField, hasMessagesField: hasPendingMessagesField },
30003000
);
30013001
const incomingMessages = hasPendingEntriesField ? incomingEntries.map((entry) => entry.text) : parsedIncomingMessages;
3002-
const applyPending = shouldApplyTransportQueueSnapshotForPayload(s.transportPendingMessageVersion, incomingVersion, {
3002+
const applyPending = hasPendingSnapshot && shouldApplyTransportQueueSnapshotForPayload(s.transportPendingMessageVersion, incomingVersion, {
30033003
hasExplicitSnapshot: hasPendingSnapshot,
30043004
isExplicitEmpty: hasPendingSnapshot && incomingMessages.length === 0 && incomingEntries.length === 0,
30053005
});
@@ -3037,7 +3037,7 @@ export function App() {
30373037
{ hasEntriesField: hasPendingEntriesField, hasMessagesField: hasPendingMessagesField },
30383038
);
30393039
const incomingMessages = hasPendingEntriesField ? incomingEntries.map((entry) => entry.text) : parsedIncomingMessages;
3040-
const applyPending = shouldApplyTransportQueueSnapshotForPayload(s.transportPendingMessageVersion, incomingVersion, {
3040+
const applyPending = hasPendingSnapshot && shouldApplyTransportQueueSnapshotForPayload(s.transportPendingMessageVersion, incomingVersion, {
30413041
hasExplicitSnapshot: hasPendingSnapshot,
30423042
isExplicitEmpty: hasPendingSnapshot && incomingMessages.length === 0 && incomingEntries.length === 0,
30433043
});

web/src/components/SessionControls.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,14 @@ export function SessionControls({ ws, activeSession, inputRef, onAfterAction, on
869869
activeSession?.transportPendingMessageEntries,
870870
activeSession?.transportPendingMessages,
871871
activeSession?.name ?? '',
872+
{
873+
// This is already-normalized session state, not a raw daemon payload.
874+
// Keep legacy messages visible when older state lacks structured
875+
// entries, but do not synthesize tails for partial structured entries.
876+
hasEntriesField: Array.isArray(activeSession?.transportPendingMessageEntries)
877+
&& activeSession.transportPendingMessageEntries.length > 0,
878+
hasMessagesField: Array.isArray(activeSession?.transportPendingMessages),
879+
},
872880
)
873881
: [];
874882
const incomingQueuedTransportVersion = typeof activeSession?.transportPendingMessageVersion === 'number'

web/src/hooks/useSubSessions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ export function useSubSessions(
558558
});
559559
const incomingMessages = hasPendingEntriesField ? incomingEntries.map((entry) => entry.text) : parsedIncomingMessages;
560560
const applyIdlePending = state === 'idle'
561+
&& hasPendingSnapshot
561562
&& shouldApplyTransportQueueSnapshotForPayload(prev[idx].transportPendingMessageVersion ?? undefined, incomingPendingVersion, {
562563
hasExplicitSnapshot: hasPendingSnapshot,
563564
isExplicitEmpty: hasPendingSnapshot && incomingMessages.length === 0 && incomingEntries.length === 0,

web/src/transport-queue.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ export function removeTransportPendingEntryForUserMessage(
164164
): TransportPendingQueueSnapshot {
165165
const messages = extractTransportPendingMessages(existingMessages);
166166
const entries = normalizeTransportPendingEntries(existingEntries, messages, scopeKey, {
167-
hasEntriesField: Array.isArray(existingEntries),
167+
// This is stored state, not an incoming authoritative snapshot. Older
168+
// clients may have messages without structured entries; keep legacy
169+
// fallback alive when the stored entry list is empty but messages exist.
170+
hasEntriesField: Array.isArray(existingEntries) && existingEntries.length > 0,
168171
hasMessagesField: Array.isArray(existingMessages),
169172
});
170173
const candidateIds = [
@@ -214,7 +217,7 @@ export function mergeTransportPendingEntriesForRunningState(
214217
pendingMessagesFromEvent: unknown,
215218
hasPendingMessagesField: boolean,
216219
scopeKey: string,
217-
hasPendingEntriesField = Array.isArray(pendingFromEvent),
220+
hasPendingEntriesField = hasPendingMessagesField && Array.isArray(pendingFromEvent),
218221
): TransportPendingMessageEntry[] {
219222
const existingEntries = Array.isArray(existing)
220223
? existing.filter((entry) => typeof entry?.clientMessageId === 'string' && entry.clientMessageId && typeof entry?.text === 'string' && entry.text)
@@ -247,7 +250,7 @@ export function mergeTransportPendingEntriesForIdleState(
247250
pendingMessagesFromEvent: unknown,
248251
hasPendingMessagesField: boolean,
249252
scopeKey: string,
250-
hasPendingEntriesField = Array.isArray(pendingFromEvent),
253+
hasPendingEntriesField = hasPendingMessagesField && Array.isArray(pendingFromEvent),
251254
): TransportPendingMessageEntry[] {
252255
const existingEntries = Array.isArray(existing)
253256
? existing.filter((entry) => typeof entry?.clientMessageId === 'string' && entry.clientMessageId && typeof entry?.text === 'string' && entry.text)

web/test/components/SessionControls.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2824,7 +2824,7 @@ afterEach(() => {
28242824
expect(screen.getByText('queued second')).toBeDefined();
28252825
});
28262826

2827-
it('renders all queued transport messages when pending entries are partial', () => {
2827+
it('treats partial queued transport entries as authoritative', () => {
28282828
const ws = makeWs();
28292829
render(
28302830
<SessionControls
@@ -2843,7 +2843,7 @@ afterEach(() => {
28432843
);
28442844

28452845
expect(screen.getByText('queued first')).toBeDefined();
2846-
expect(screen.getByText('queued second')).toBeDefined();
2846+
expect(screen.queryByText('queued second')).toBeNull();
28472847
});
28482848

28492849
it('renders shared actor labels on queued transport messages', () => {

web/test/session-list-merge.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ describe('mergeSessionListEntry — pending-queue version guard', () => {
449449
expect(merged.transportPendingMessageVersion).toBe(7);
450450
});
451451

452-
it('accepts a fresh-runtime snapshot (version 0) even when the baseline is higher', () => {
452+
it('rejects version 0 snapshots when the baseline is higher', () => {
453453
// After a provider restart the runtime version resets to 0; that snapshot
454454
// must win so the queue does not get stuck behind a stale-high baseline.
455455
const merged = mergeSessionListEntry({
@@ -459,7 +459,7 @@ describe('mergeSessionListEntry — pending-queue version guard', () => {
459459
transportPendingMessageEntries: [],
460460
transportPendingMessageVersion: 0,
461461
}, existing);
462-
expect(merged.transportPendingMessageVersion).toBe(0);
462+
expect(merged.transportPendingMessageVersion).toBe(7);
463463
});
464464

465465
it('rejects unversioned queue snapshots after a versioned baseline exists', () => {

web/test/transport-queue.test.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,19 @@ describe('normalizeTransportPendingMessageEntries', () => {
9595
]);
9696
});
9797

98-
it('fills missing queued entries from pendingMessages when entries are partial', () => {
98+
it('treats present entries as authoritative instead of filling legacy tails', () => {
9999
expect(normalizeTransportPendingEntries([
100100
{ clientMessageId: 'msg-1', text: 'queued one' },
101-
], ['queued one', 'queued two'], 'deck_test')).toEqual([
101+
], ['queued one', 'queued two'], 'deck_test', { hasEntriesField: true, hasMessagesField: true })).toEqual([
102102
{ clientMessageId: 'msg-1', text: 'queued one' },
103-
{ clientMessageId: 'deck_test:legacy:1:queued two', text: 'queued two' },
104103
]);
105104
});
106105

107106
it('synthesizes legacy entries when only pending messages are present', () => {
108-
expect(normalizeTransportPendingEntries([], ['queued one', 'queued two'], 'deck_test')).toEqual([
107+
expect(normalizeTransportPendingEntries(undefined, ['queued one', 'queued two'], 'deck_test', {
108+
hasEntriesField: false,
109+
hasMessagesField: true,
110+
})).toEqual([
109111
{ clientMessageId: 'deck_test:legacy:0:queued one', text: 'queued one' },
110112
{ clientMessageId: 'deck_test:legacy:1:queued two', text: 'queued two' },
111113
]);
@@ -190,14 +192,13 @@ describe('mergeTransportPendingEntriesForRunningState', () => {
190192
]);
191193
});
192194

193-
it('fills missing running queued entries from pendingMessages when entries are partial', () => {
195+
it('treats running entries as authoritative instead of filling legacy tails', () => {
194196
expect(mergeTransportPendingEntriesForRunningState([
195197
{ clientMessageId: 'msg-1', text: 'queued one' },
196198
], [
197199
{ clientMessageId: 'msg-1', text: 'queued one' },
198-
], ['queued one', 'queued two'], true, 'deck_test')).toEqual([
200+
], ['queued one', 'queued two'], true, 'deck_test', true)).toEqual([
199201
{ clientMessageId: 'msg-1', text: 'queued one' },
200-
{ clientMessageId: 'deck_test:legacy:1:queued two', text: 'queued two' },
201202
]);
202203
});
203204

@@ -298,8 +299,8 @@ describe('shouldApplyTransportQueueSnapshot', () => {
298299
expect(shouldApplyTransportQueueSnapshot(3, 3)).toBe(true);
299300
expect(shouldApplyTransportQueueSnapshot(3, 4)).toBe(true);
300301
});
301-
it('always applies version 0 a fresh runtime restarts the sequence', () => {
302-
expect(shouldApplyTransportQueueSnapshot(9, 0)).toBe(true);
302+
it('rejects version 0 after a higher baseline instead of treating it as a reset', () => {
303+
expect(shouldApplyTransportQueueSnapshot(9, 0)).toBe(false);
303304
});
304305
});
305306

@@ -308,8 +309,8 @@ describe('nextTransportQueueVersion', () => {
308309
expect(nextTransportQueueVersion(5, undefined)).toBe(5);
309310
expect(nextTransportQueueVersion(undefined, undefined)).toBeUndefined();
310311
});
311-
it('resets to 0 on a fresh-runtime snapshot', () => {
312-
expect(nextTransportQueueVersion(9, 0)).toBe(0);
312+
it('does not reset to 0 after a higher baseline', () => {
313+
expect(nextTransportQueueVersion(9, 0)).toBe(9);
313314
});
314315
it('advances monotonically', () => {
315316
expect(nextTransportQueueVersion(undefined, 2)).toBe(2);

web/test/use-sub-sessions-metadata.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ describe('sub-session metadata via subsession.sync', () => {
575575
expect(captured[0].transportPendingMessageEntries).toEqual([]);
576576
});
577577

578-
it('fills missing queued transport entries from pendingMessages when daemon sends a partial entry snapshot', async () => {
578+
it('treats partial entry snapshots as authoritative and does not fill legacy tails', async () => {
579579
const { ws, send } = createMockWs();
580580
render(<Harness ws={ws} connected={true} />);
581581
await waitFor(() => expect(ws.onMessage).toHaveBeenCalled());
@@ -603,9 +603,9 @@ describe('sub-session metadata via subsession.sync', () => {
603603
},
604604
}));
605605

606+
expect(captured[0].transportPendingMessages).toEqual(['queued one']);
606607
expect(captured[0].transportPendingMessageEntries).toEqual([
607608
{ clientMessageId: 'msg-1', text: 'queued one' },
608-
{ clientMessageId: 'deck_sub_q4:legacy:1:queued two', text: 'queued two' },
609609
]);
610610
});
611611
});

0 commit comments

Comments
 (0)