Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5b09a63
feat(desktop): preserve Side Conversations across linked sessions
Sep 3, 2026
aee45b4
feat(desktop): preserve Side Conversations across linked sessions
Sep 3, 2026
ef1c674
fix(desktop): refresh renderer architecture token baseline
Sep 4, 2026
63bc672
fix(desktop): preserve side chat through pending linked sessions
Sep 4, 2026
ebb2889
test(desktop): refresh renderer architecture baseline
Sep 4, 2026
ef15f5f
fix(desktop): cache linked session family projection
Sep 4, 2026
490e0fa
test(desktop): cover linked-session side chat continuity
Sep 4, 2026
5d216a6
chore(desktop): refresh renderer architecture ledger after rebase
Sep 5, 2026
f0219b2
fix(desktop): clean side chat on unrelated navigation
Sep 5, 2026
3f96508
ci: retry flaky Storybook smoke check
Sep 5, 2026
f7cc298
fix: treat pending session view as known side-chat family anchor
testikun Sep 8, 2026
bff6929
fix: preserve side-chat retention across pending and revision transit…
testikun Sep 8, 2026
dbe1a9c
Merge remote-tracking branch 'apache/main' into codex/issue-4494-dele…
Sep 8, 2026
e0b42e2
Merge remote-tracking branch 'apache/main' into codex/issue-4494-dele…
Sep 8, 2026
d016aa7
Merge main into codex/issue-4494-delegated after PR recovery
testikun Sep 9, 2026
2830a7b
Merge upstream/main into PR 4625
testikun Sep 9, 2026
7765d19
fix(desktop): sync AppShell architecture ledger after merge
testikun Sep 9, 2026
3a4df15
fix(desktop): retain side chat across pending Host selection only
testikun Sep 10, 2026
357747c
fix(desktop): retain side chat only for linked sessions
testikun Sep 14, 2026
4243253
refactor(desktop): keep Workbar wiring within shell budget
testikun Sep 14, 2026
44cb201
Merge upstream/main into Side Conversation navigation PR
testikun Sep 15, 2026
4ef87a6
Merge remote-tracking branch 'upstream/main' into codex/fix-pr-4625-r…
testikun Sep 16, 2026
9a22eba
fix(desktop): pass session catalog into Workbar workspace
testikun Sep 16, 2026
61d5203
fix(desktop): keep retained side chat mounted during catalog gaps
testikun Sep 17, 2026
d8dded4
Merge upstream/main into Side Conversation navigation fix
testikun Sep 17, 2026
cfb0b43
Merge upstream/main into Side Conversation navigation fix
testikun Sep 17, 2026
5d28269
Merge upstream/main into Side Conversation navigation fix
testikun Sep 18, 2026
94e4977
fix(desktop): keep retained Side Chat mounted through catalog gaps
testikun Sep 18, 2026
b86b75b
Merge remote-tracking branch 'upstream/main' into codex/fix-pr-4625-r…
testikun Sep 18, 2026
2a3e2d6
fix(desktop): scope retained Side Chat to Session workspace
testikun Sep 18, 2026
2cf9385
ci: rerun checks
testikun Sep 18, 2026
a879fa6
Merge remote-tracking branch 'upstream/main' into codex/fix-pr-4625-r…
testikun Sep 18, 2026
6cef7b3
fix(desktop): discard side chat when entering WorkHub
testikun Sep 18, 2026
e04bff9
Merge remote-tracking branch 'upstream/main' into codex/fix-pr-4625-r…
testikun Sep 20, 2026
1861bce
Merge remote-tracking branch 'upstream/main' into codex/pr-4625-blockers
testikun Sep 21, 2026
c063f4f
test(desktop): drop stale protocol declaration update
testikun Sep 21, 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
4 changes: 2 additions & 2 deletions apps/desktop/e2e-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
"electron": "SQLite admission crosses preload and main, survives both renderer reload and complete Electron restart, and cached history must render when the live IPC endpoint is unavailable"
},
"session-workbar.spec.ts": {
"tests": 5,
"electron": "Git changes re-read on native window focus; native PTY ownership survives Session navigation and renderer replacement, while Stop/exit cross preload/main into the terminal view; Side Chat owns a Host fork lifecycle; first send reaches the Host; the composer usage entry opens a workbar whose per-Session visibility survives renderer reload."
"tests": 6,
"electron": "Git changes re-read on native window focus; native PTY ownership survives Session navigation and renderer replacement, while Stop/exit cross preload/main into the terminal view; Side Chat owns a Host fork lifecycle across linked Session navigation and preserves its unsent draft while the Host fork stays live; first send reaches the Host; the composer usage entry opens a workbar whose per-Session visibility survives renderer reload."
},
"settings.spec.ts": {
"tests": 2,
Expand Down
15 changes: 11 additions & 4 deletions apps/desktop/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export async function waitForInvocableSkills(
* backend (BackendRegistry override in main); this only satisfies the UI
* readiness gates. Kept in the fixture so test data stays out of production main.
*/
async function seedE2eConnection(userDataDir: string): Promise<void> {
async function seedE2eConnection(userDataDir: string): Promise<string> {
const workspaceRoot = path.join(userDataDir, 'workspaces', 'default');
const capability = await resolveStorageRoot({ path: workspaceRoot, kind: 'interactive' });
const owner = await tryAcquireInteractiveRootOwner(capability);
Expand Down Expand Up @@ -179,6 +179,7 @@ async function seedE2eConnection(userDataDir: string): Promise<void> {
if (defaultTarget.kind !== 'committed') {
throw new Error(`E2E default target seed was not committed: ${defaultTarget.kind}`);
}
return connection.connectionId;
} finally {
await owner.close();
}
Expand Down Expand Up @@ -213,12 +214,14 @@ async function seedRailRenderSessions(userDataDir: string): Promise<void> {
}
}

async function seedParentRemovalSessions(userDataDir: string): Promise<void> {
async function seedParentRemovalSessions(userDataDir: string, llmConnectionId: string): Promise<void> {
await mkdir(path.join(userDataDir, 'project'), { recursive: true });
const workspaceRoot = path.join(userDataDir, 'workspaces', 'default');
const store = createSessionStore(workspaceRoot);
try {
const parent = await store.create({
cwd: path.join(userDataDir, 'project'),
llmConnectionId,
llmConnectionSlug: 'e2e',
model: 'claude-sonnet-4-5-20250929',
permissionMode: 'ask',
Expand All @@ -227,6 +230,7 @@ async function seedParentRemovalSessions(userDataDir: string): Promise<void> {
});
await store.createSubagent({
cwd: path.join(userDataDir, 'project'),
llmConnectionId,
llmConnectionSlug: 'e2e',
model: 'claude-sonnet-4-5-20250929',
permissionMode: 'ask',
Expand Down Expand Up @@ -435,8 +439,11 @@ export async function withE2eWindow(
const mainLogs: string[] = [];
const rendererLogs: string[] = [];
try {
if (seed) await seedE2eConnection(userDataDir);
if (parentRemovalSessions) await seedParentRemovalSessions(userDataDir);
const llmConnectionId = seed ? await seedE2eConnection(userDataDir) : undefined;
if (parentRemovalSessions) {
if (!llmConnectionId) throw new Error('Parent removal fixture requires a seeded connection');
await seedParentRemovalSessions(userDataDir, llmConnectionId);
}
if (railRenderSessions) await seedRailRenderSessions(userDataDir);
if (invocableSkills) await seedE2eInvocableSkills(userDataDir);
if (gitReviewExtraFiles !== undefined) {
Expand Down
59 changes: 58 additions & 1 deletion apps/desktop/e2e/session-workbar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,15 @@
* under the License.
*/

import { awaitSendReady, COMPOSER_INPUT, test, expect } from './fixtures';
import {
awaitSendReady,
COMPOSER_INPUT,
ensureSidebarExpanded,
PARENT_REMOVAL_CHILD_NAME,
PARENT_REMOVAL_PARENT_NAME,
test,
expect,
} from './fixtures';
import { FAKE_HOLD_OPEN_PROMPT } from '@maka/runtime/test-only/fake-backend';
import type { Page } from '@playwright/test';
import { writeFile } from 'node:fs/promises';
Expand Down Expand Up @@ -345,3 +353,52 @@ test('Side Chat survives collapse and source switches, then cleans up on explici
)
.toBe(false);
});

test('linked child navigation keeps the Side Chat Host fork and draft', async ({
parentRemovalWindow: page,
}) => {
const sessions = await page.evaluate(() => window.maka.sessions.list());
const parent = sessions.find((session) => session.name === PARENT_REMOVAL_PARENT_NAME);
const child = sessions.find((session) => session.name === PARENT_REMOVAL_CHILD_NAME);
expect(parent).toBeDefined();
expect(child).toBeDefined();
const knownSessionIds = new Set(sessions.map((session) => session.id));
await ensureSidebarExpanded(page);
const sidebar = page.getByRole('navigation', { name: '任务列表' });
await sidebar.locator(`[data-session-id=${JSON.stringify(parent!.id)}]`).click();
await page.getByRole('button', { name: '展开任务工作栏' }).click();
await page.getByRole('button', {
name: /侧边对话.*在不打断主任务的情况下追问和只读探索/,
}).click();

const companion = page.locator('.maka-quote-companion');
const draft = companion.locator(COMPOSER_INPUT);
await draft.fill('retain this reply across linked navigation');
await draft.press('Enter');
await expect(companion).toContainText('Fake backend received: retain this reply across linked navigation');
let forkId: string | undefined;
await expect.poll(async () => {
forkId = (await page.evaluate(() => window.maka.sessions.list()))
.find((session) => !knownSessionIds.has(session.id))?.id;
return forkId;
}).not.toBeUndefined();
await draft.fill('unsent linked draft');

await page.getByRole('button', { name: '搜索任务', exact: true }).click();
const search = page.locator('[data-maka-contract="search-modal"]');
await search.locator('input').fill(PARENT_REMOVAL_CHILD_NAME);
await search.locator('.maka-search-modal-result-title', {
hasText: PARENT_REMOVAL_CHILD_NAME,
}).click();
await expect(page.getByRole('region', { name: `对话:${PARENT_REMOVAL_CHILD_NAME}` })).toBeVisible();
await expect(companion).toBeAttached();
await expect(companion).not.toBeVisible();
await expect(draft).toHaveText('unsent linked draft');
await expect.poll(async () => (await page.evaluate(() => window.maka.sessions.list()))
.some((session) => session.id === forkId)).toBe(true);

await sidebar.locator(`[data-session-id=${JSON.stringify(parent!.id)}]`).click();
await expect(companion).toBeVisible();
await expect(draft).toHaveText('unsent linked draft');
await expect(companion).toContainText('Fake backend received: retain this reply across linked navigation');
});