Skip to content

Commit f5806b0

Browse files
author
IM.codes
committed
Keep queued transport messages visible until idle
1 parent 4e71c4c commit f5806b0

5 files changed

Lines changed: 70 additions & 9 deletions

File tree

web/src/app.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import { shouldSubscribeTerminalRaw, type TerminalSubscribeViewMode } from './te
6060
import { onWatchCommand } from './watch-bridge.js';
6161
import { watchProjectionStore } from './watch-projection.js';
6262
import { isIdleSessionStateTimelineEvent, isRunningTimelineEvent } from './timeline-running.js';
63-
import { extractTransportPendingMessages } from './transport-queue.js';
63+
import { extractTransportPendingMessages, mergeTransportPendingMessagesForRunningState } from './transport-queue.js';
6464
import { ingestTimelineEventForCache } from './hooks/useTimeline.js';
6565
import { getMobileKeyboardState } from './mobile-keyboard.js';
6666
import { pickReadableSessionDisplay } from '@shared/session-display.js';
@@ -1332,15 +1332,16 @@ export function App() {
13321332
: s,
13331333
));
13341334
} else if (liveState === 'running') {
1335-
const pendingMessages = hasPendingMessagesField
1336-
? extractTransportPendingMessages(event.payload.pendingMessages)
1337-
: null;
13381335
setSessions((prev) => prev.map((s) =>
13391336
s.name === event.sessionId
13401337
? {
13411338
...s,
13421339
state: 'running' as SessionInfo['state'],
1343-
transportPendingMessages: pendingMessages ?? (s.transportPendingMessages ?? []),
1340+
transportPendingMessages: mergeTransportPendingMessagesForRunningState(
1341+
s.transportPendingMessages,
1342+
event.payload.pendingMessages,
1343+
hasPendingMessagesField,
1344+
),
13441345
}
13451346
: s,
13461347
));

web/src/hooks/useSubSessions.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
} from '../api.js';
1212
import type { WsClient } from '../ws-client.js';
1313
import { isRunningTimelineEvent } from '../timeline-running.js';
14-
import { extractTransportPendingMessages } from '../transport-queue.js';
14+
import { extractTransportPendingMessages, mergeTransportPendingMessagesForRunningState } from '../transport-queue.js';
1515

1616
export interface SubSession extends SubSessionData {
1717
sessionName: string;
@@ -252,12 +252,19 @@ export function useSubSessions(
252252
return;
253253
}
254254
if (state === 'running' && hasPendingMessagesField) {
255-
const pendingMessages = extractTransportPendingMessages(msg.event.payload.pendingMessages);
256255
setSubSessions((prev) => {
257256
const idx = prev.findIndex((s) => s.sessionName === sessionName);
258257
if (idx === -1) return prev;
259258
const next = [...prev];
260-
next[idx] = { ...next[idx], state: 'running', transportPendingMessages: pendingMessages };
259+
next[idx] = {
260+
...next[idx],
261+
state: 'running',
262+
transportPendingMessages: mergeTransportPendingMessagesForRunningState(
263+
next[idx].transportPendingMessages,
264+
msg.event.payload.pendingMessages,
265+
true,
266+
),
267+
};
261268
return next;
262269
});
263270
return;

web/src/transport-queue.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ export function extractTransportPendingMessages(value: unknown): string[] {
44
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
55
.filter((entry) => entry.length > 0);
66
}
7+
8+
export function mergeTransportPendingMessagesForRunningState(
9+
existing: string[] | null | undefined,
10+
pendingFromEvent: unknown,
11+
hasPendingMessagesField: boolean,
12+
): string[] {
13+
const existingMessages = Array.isArray(existing) ? existing.filter((entry) => typeof entry === 'string' && entry.length > 0) : [];
14+
if (!hasPendingMessagesField) return existingMessages;
15+
const nextMessages = extractTransportPendingMessages(pendingFromEvent);
16+
if (nextMessages.length > 0) return nextMessages;
17+
return existingMessages;
18+
}

web/test/transport-queue.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import {
4+
extractTransportPendingMessages,
5+
mergeTransportPendingMessagesForRunningState,
6+
} from '../src/transport-queue.js';
7+
8+
describe('extractTransportPendingMessages', () => {
9+
it('keeps only non-empty string entries', () => {
10+
expect(extractTransportPendingMessages([' one ', '', 1, null, 'two'])).toEqual(['one', 'two']);
11+
});
12+
});
13+
14+
describe('mergeTransportPendingMessagesForRunningState', () => {
15+
it('preserves the existing queue when running reports no pendingMessages field', () => {
16+
expect(mergeTransportPendingMessagesForRunningState(['queued one'], undefined, false)).toEqual(['queued one']);
17+
});
18+
19+
it('preserves the existing queue when running reports an empty pendingMessages array', () => {
20+
expect(mergeTransportPendingMessagesForRunningState(['queued one', 'queued two'], [], true)).toEqual(['queued one', 'queued two']);
21+
});
22+
23+
it('replaces the queue when running reports a non-empty pendingMessages array', () => {
24+
expect(mergeTransportPendingMessagesForRunningState(['queued one'], ['queued two'], true)).toEqual(['queued two']);
25+
});
26+
27+
it('returns an empty queue when nothing is queued yet', () => {
28+
expect(mergeTransportPendingMessagesForRunningState([], [], true)).toEqual([]);
29+
});
30+
});

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ describe('sub-session metadata via subsession.sync', () => {
217217
expect(captured[0].quotaUsageLabel).toBe('today 20/1000');
218218
});
219219

220-
it('preserves queued transport messages until running explicitly reports an empty queue', async () => {
220+
it('preserves queued transport messages while the drained send is still running and clears on idle', async () => {
221221
const { ws, send } = createMockWs();
222222
render(<Harness ws={ws} connected={true} />);
223223
await waitFor(() => expect(ws.onMessage).toHaveBeenCalled());
@@ -261,6 +261,17 @@ describe('sub-session metadata via subsession.sync', () => {
261261
},
262262
}));
263263

264+
expect(captured[0].transportPendingMessages).toEqual(['queued one', 'queued two']);
265+
266+
act(() => send({
267+
type: 'timeline.event',
268+
event: {
269+
type: 'session.state',
270+
sessionId: 'deck_sub_q4',
271+
payload: { state: 'idle' },
272+
},
273+
}));
274+
264275
expect(captured[0].transportPendingMessages).toEqual([]);
265276
});
266277
});

0 commit comments

Comments
 (0)