diff --git a/apps/desktop/src/main/__tests__/streaming-handoff.test.ts b/apps/desktop/src/main/__tests__/streaming-handoff.test.ts index 7bcb6280e2..5b869d8239 100644 --- a/apps/desktop/src/main/__tests__/streaming-handoff.test.ts +++ b/apps/desktop/src/main/__tests__/streaming-handoff.test.ts @@ -88,7 +88,7 @@ function renderLiveTurn(liveTurn: LiveTurnProjection): string { } describe('single live-turn handoff', () => { - it('keeps activity in the answer footer before the session or Turn arrives', () => { + it('keeps activity in the process row before the session or Turn arrives', () => { const session: NonNullable[0]['activeSession']> = { id: 'session-1', name: 'pending', status: 'running' as const, backend: 'ai-sdk', labels: [], isFlagged: false, isArchived: false, hasUnread: false, @@ -107,8 +107,9 @@ describe('single live-turn handoff', () => { onNew() {}, } satisfies Parameters[0])); const { document } = parseHTML(markup); - const status = document.querySelector('.maka-assistant-answer [role="status"]'); - assert.ok(status?.closest('.maka-turn-footer'), 'activity must occupy the shared footer'); + const status = document.querySelector('.maka-processing-summary [role="status"]'); + assert.ok(status, 'activity must occupy the process row'); + assert.equal(document.querySelector('.maka-turn-footer'), null); assert.equal(document.querySelector('.maka-assistant-answer [role="toolbar"]'), null); } }); diff --git a/apps/desktop/src/main/__tests__/transient-message-projection.test.ts b/apps/desktop/src/main/__tests__/transient-message-projection.test.ts index 961938d14d..fb5cb24477 100644 --- a/apps/desktop/src/main/__tests__/transient-message-projection.test.ts +++ b/apps/desktop/src/main/__tests__/transient-message-projection.test.ts @@ -205,3 +205,10 @@ test('keeps a Host-bound current Turn when a later IPC result has no Turn identi hostTurnId: 'host-turn', }); }); + +test('keeps a transient message send time when a later update carries a new timestamp', () => { + const first = { ...transient, ts: 2 }; + const later = { ...transient, ts: 9, text: 'edited text' }; + + assert.deepEqual(mergeTransientMessageProjection(first, later), { ...later, ts: 2 }); +}); diff --git a/apps/desktop/src/renderer/application/contracts/transient-message-projection.ts b/apps/desktop/src/renderer/application/contracts/transient-message-projection.ts index 803a6024b6..b06bea0c6e 100644 --- a/apps/desktop/src/renderer/application/contracts/transient-message-projection.ts +++ b/apps/desktop/src/renderer/application/contracts/transient-message-projection.ts @@ -49,6 +49,8 @@ export function mergeTransientMessageProjection( ): TransientUserMessage { update = { ...update, + // A Message's send time is written once; an update's `ts` must not move it. + ts: current.ts, ...(update.pendingSteering === undefined && current.pendingSteering !== undefined ? { pendingSteering: current.pendingSteering } : {}), ...(!Object.hasOwn(update, 'deliveryStatus') && current.deliveryStatus !== undefined ? { deliveryStatus: current.deliveryStatus } : {}), ...(!Object.hasOwn(update, 'deliveryDetail') && current.deliveryDetail !== undefined ? { deliveryDetail: current.deliveryDetail } : {}), diff --git a/apps/desktop/src/renderer/styles/native-cursor.css b/apps/desktop/src/renderer/styles/native-cursor.css index ed42ad88f3..5f34bbd16a 100644 --- a/apps/desktop/src/renderer/styles/native-cursor.css +++ b/apps/desktop/src/renderer/styles/native-cursor.css @@ -54,7 +54,8 @@ * group label (InputGroup: `isGroupLabel`), which names a group via * `aria-labelledby` and forwards no click — the `label` selector misses it, * so it kept StyleX's hand cursor. Match the stable themeProps class to catch - * that span too (real