Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
916464c
perf(desktop): keep transcript state incremental
M4n5ter Aug 13, 2026
44161c2
fix(desktop): close transcript replica review gaps
M4n5ter Aug 13, 2026
72395bf
fix(desktop): retain transcript intent during reconnect
M4n5ter Aug 13, 2026
ae90b7f
fix(desktop): cancel pending transcript opens
M4n5ter Aug 13, 2026
766ad2f
fix(desktop): close sparse transcript edge cases
M4n5ter Aug 13, 2026
ea4dc7d
fix(desktop): fence subscription handoff
M4n5ter Aug 13, 2026
5c5784b
fix(desktop): make transcript handoff atomic
M4n5ter Aug 13, 2026
f6767e0
fix(desktop): keep observation teardown persistent
M4n5ter Aug 13, 2026
d597e43
fix(desktop): restore transcript bridge lifecycle
M4n5ter Aug 13, 2026
1a64784
fix(desktop): bound transcript delivery
M4n5ter Aug 13, 2026
3a56291
fix(desktop): coalesce transcript delivery
M4n5ter Aug 13, 2026
ad6cdf2
fix(desktop): isolate transcript delivery
M4n5ter Aug 13, 2026
01e8de2
fix(desktop): follow transcript Host replacement
M4n5ter Aug 13, 2026
5374556
fix(desktop): account queued transcript identities
M4n5ter Aug 13, 2026
4f3e40e
fix(desktop): bound transcript assembly
M4n5ter Aug 13, 2026
f68dd9f
perf(desktop): stream transcript history into the viewport
M4n5ter Aug 13, 2026
912a5ee
fix(ui): keep sparse prompt rail selection local
M4n5ter Aug 13, 2026
a0b3bfe
fix(desktop): keep sparse transcript navigation stable
M4n5ter Aug 13, 2026
e89659c
fix(desktop): preserve history beside active overlays
M4n5ter Aug 13, 2026
8ff1159
fix(desktop): commit transcript accounting atomically
M4n5ter Aug 13, 2026
82c876e
fix(desktop): finish transcript open on the current replica
M4n5ter Aug 13, 2026
86d2010
perf(desktop): batch history and bound scroll work
M4n5ter Aug 13, 2026
44128f4
perf(desktop): bound prompt rail discovery
M4n5ter Aug 13, 2026
9998a4c
fix(desktop): preserve prompt rail order
M4n5ter Aug 13, 2026
607d19d
fix(desktop): keep prompt landmarks current
M4n5ter Aug 13, 2026
0e8846c
fix(ui): preserve live answers across steering handoff
M4n5ter Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions apps/desktop/e2e/streaming-remount.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ test('remounting a live surface leaves accumulated output settled', async ({
const sidebar = page.getByRole('navigation', { name: '对话列表' });
await sidebar.getByRole('button', { name: '扩展' }).click();
await expect(page.locator('[data-module="skills"]')).toBeVisible();
await expect(liveBubble).toHaveCount(0);
await sidebar.getByRole('button', { name: '会话', exact: true }).click();
await liveBubble.waitFor({ state: 'attached' });
await expect(liveBubble).toHaveCount(1);
await expect(liveBubble).toContainText(accumulatedOutput);

expect((await liveBubble.textContent())?.split(accumulatedOutput)).toHaveLength(2);
expect(
Expand All @@ -46,7 +48,8 @@ test('remounting a live surface leaves accumulated output settled', async ({
const steering = 'trigger rewrite after returning to this conversation';
await composer.fill(steering);
await composer.press('Enter');
await expect(liveBubble).toContainText('<redacted> NEW');
const finalText = 'prefix <redacted> NEW streamed after the remount';
await expect(liveBubble).toContainText(finalText);

const observed = await page.evaluate(() => (
window as typeof window & {
Expand All @@ -55,9 +58,8 @@ test('remounting a live surface leaves accumulated output settled', async ({
};
}
).__makaStreamingRemountObserved);
expect(observed?.texts.some((text) =>
text.includes('<redacted>') && !text.includes('NEW')
)).toBe(true);
expect(observed?.texts.some((text) => text.includes('<redacted>') && !text.includes(finalText)))
.toBe(true);
});

test('returning to a live conversation settles output accumulated while away', async ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { describe, it } from 'node:test';

import type { SessionSummary } from '@maka/core/session';
import type { LiveTurnProjection } from '@maka/ui';
import type { DesktopTranscriptRangeController } from '../../renderer/desktop-transcript-range-store.js';
import { createAppShellChatActions } from '../../renderer/app-shell-chat-actions.js';
import { createAppShellSessionUiStateController } from '../../renderer/app-shell-session-ui-state.js';
import { settledSessionTransientIds } from '../../renderer/settled-session-transients.js';
Expand Down Expand Up @@ -81,6 +82,7 @@ function createActionsDeps() {
setMessageLoadErrorBySession: () => undefined,
setMessageRetryPendingBySession: () => undefined,
setMessages: () => undefined,
transcriptRangeRef: { current: undefined },
setNavSelection: () => undefined,
setLiveTurnBySession: () => undefined,
setInteractionBySession: () => undefined,
Expand Down Expand Up @@ -144,7 +146,6 @@ describe('composer first-send cleanup', () => {
attachments: [],
skillInvocation: { loaded: [], failed: [] },
}),
readMessages: async () => [],
},
});

Expand Down Expand Up @@ -181,7 +182,6 @@ describe('composer first-send cleanup', () => {
attachments: [],
skillInvocation: { loaded: [], failed: [] },
}),
readMessages: async () => [],
},
});

Expand Down Expand Up @@ -232,7 +232,6 @@ describe('composer first-send cleanup', () => {
remove: async (sessionId: string) => {
removed.push(sessionId);
},
readMessages: async () => [],
},
});

Expand Down Expand Up @@ -273,6 +272,46 @@ describe('composer first-send cleanup', () => {

assert.deepEqual(removed, []);
});

it('returns a sparse existing session to latest before sending', async () => {
const latest = deferred<void>();
const order: string[] = [];
const activeIdRef = { current: 'existing-session' as string | undefined };
const transcript = {
store: {
range: () => ({ sessionId: 'existing-session', hasNewer: true }),
snapshot: () => ({ messages: [] }),
},
async loadLatest() {
order.push('latest');
await latest.promise;
},
} as unknown as DesktopTranscriptRangeController;
const transcriptRangeRef = { current: transcript as DesktopTranscriptRangeController | undefined };
const restoreWindow = installWindow({
sessions: {
send: async () => {
order.push('send');
return { ok: true, attachments: [], skillInvocation: { loaded: [], failed: [] } };
},
},
});

try {
const sending = createAppShellChatActions({
...createActionsDeps(),
activeIdRef,
transcriptRangeRef,
}).send('hello');
await new Promise((resolve) => setImmediate(resolve));
assert.deepEqual(order, ['latest']);
latest.resolve();
assert.equal(await sending, true);
assert.deepEqual(order, ['latest', 'send']);
} finally {
restoreWindow();
}
});
});

function deferred<T>() {
Expand Down Expand Up @@ -389,23 +428,15 @@ describe('composer send failure feedback', () => {
describe('a send in flight versus a stale session list', () => {
const sessionId = 'session-a';

// Echoes the sent turn back through `readMessages`, which is what `send()`
// waits on before it reports success — a window that never shows the user
// message would time the send out rather than exercise the race.
function sendingWindow() {
let sentTurnId: string | undefined;
return {
sessions: {
create: async () => ({ id: sessionId }),
send: async (_sessionId: string, command: { turnId: string }) => {
sentTurnId = command.turnId;
return { ok: true, attachments: [], skillInvocation: { loaded: [], failed: [] } };
},
readMessages: async () => (
sentTurnId
? [{ type: 'user', id: `user-${sentTurnId}`, turnId: sentTurnId, ts: 1, text: 'hello' }]
: []
),
send: async () => ({
ok: true,
attachments: [],
skillInvocation: { loaded: [], failed: [] },
}),
},
};
}
Expand Down
Loading
Loading