Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 19 additions & 2 deletions apps/desktop/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ async function withE2eWindow(
gitReviewExtraFiles?: number;
extraConnectionCount?: number;
},
use: (page: Page) => Promise<void>,
use: (page: Page, context: { userDataDir: string }) => Promise<void>,
): Promise<void> {
const userDataDir = await mkdtemp(path.join(tmpdir(), 'maka-e2e-'));
// Lives inside the throwaway userData dir so the existing teardown removes
Expand Down Expand Up @@ -275,7 +275,7 @@ async function withE2eWindow(
const rendererDetail = rendererLogs.length > 0 ? `\nRenderer console:\n${rendererLogs.join('\n')}` : '';
throw new Error(`${detail}${mainDetail}${rendererDetail}`, { cause: error });
}
await use(page);
await use(page, { userDataDir });
} finally {
try {
if (app) await closeElectronApplication(app, 5_000);
Expand All @@ -288,6 +288,7 @@ async function withE2eWindow(
export const test = base.extend<{
window: Page;
artifactPaneWindow: Page;
gitReviewWindow: { page: Page; projectRoot: string };
invocableSkillsWindow: Page;
}>({
// Seeded: a pre-staged connection clears onboarding so the composer is ready.
Expand All @@ -305,6 +306,22 @@ export const test = base.extend<{
use,
);
},
gitReviewWindow: async ({}, use) => {
await withE2eWindow(
{
seed: true,
readinessSelector: COMPOSER_INPUT,
locale: 'zh',
gitReviewExtraFiles: 0,
},
async (page, context) => {
await use({
page,
projectRoot: path.join(context.userDataDir, 'git-review-project'),
});
},
);
},
// Project + workspace Skills for draft/chip journeys.
invocableSkillsWindow: async ({}, use) => {
await withE2eWindow({
Expand Down
44 changes: 43 additions & 1 deletion apps/desktop/e2e/session-workbar.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,46 @@
import { test, expect } from './fixtures';
import { COMPOSER_INPUT, test, expect } from './fixtures';
import type { Page } from '@playwright/test';
import { writeFile } from 'node:fs/promises';
import { join } from 'node:path';

async function openGitChanges(page: Page) {
const composer = page.locator(COMPOSER_INPUT);
await composer.fill('create review session');
await composer.press('Enter');
await expect(page.getByText(/Fake backend received: create review session/)).toBeVisible();
await page.getByRole('button', { name: '展开会话工作栏' }).click();
await page.getByRole('button', { name: '打开工作栏标签' }).first().click();
await page.getByRole('menuitem', { name: '变更' }).click();
return page.getByRole('region', { name: 'Git 变更' });
}

test('Git changes show branch files as a collapsed single-open list', async ({
gitReviewWindow,
}) => {
const panel = await openGitChanges(gitReviewWindow.page);
const feature = panel.getByRole('button', { name: /feature\.txt/ });
const base = panel.getByRole('button', { name: /base\.txt/ });
await expect(feature).toHaveAttribute('aria-expanded', 'false');
await expect(base).toHaveAttribute('aria-expanded', 'false');

await feature.click();
await expect(feature).toHaveAttribute('aria-expanded', 'true');
await base.click();
await expect(base).toHaveAttribute('aria-expanded', 'true');
await expect(feature).toHaveAttribute('aria-expanded', 'false');
});

test('Git changes re-read the workspace after the app regains focus', async ({
gitReviewWindow,
}) => {
const panel = await openGitChanges(gitReviewWindow.page);
await expect(panel.getByText('新增 4 行')).toBeVisible();

await writeFile(join(gitReviewWindow.projectRoot, 'base.txt'), 'base\nunstaged\nexternal\n');
await gitReviewWindow.page.evaluate(() => window.dispatchEvent(new Event('focus')));

await expect(panel.getByText('新增 5 行')).toBeVisible();
});

test('generated files use list-to-preview navigation with a compact action menu', async ({ artifactPaneWindow: page }) => {
const pane = page.getByRole('region', { name: '生成文件预览面板' });
Expand Down
22 changes: 22 additions & 0 deletions apps/desktop/src/main/__tests__/git-review-main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,28 @@ describe('Git Review snapshot authority', () => {
);
});

it('compares a branch from its merge base when the base branch has advanced', async () => {
const root = await repository();
await git(root, 'checkout', '-b', 'feature/review');
await writeFile(join(root, 'feature.txt'), 'feature\n', 'utf8');
await git(root, 'add', 'feature.txt');
await git(root, 'commit', '-m', 'feature');

await git(root, 'checkout', 'main');
await writeFile(join(root, 'main-only.txt'), 'main only\n', 'utf8');
await git(root, 'add', 'main-only.txt');
await git(root, 'commit', '-m', 'advance main');
await git(root, 'checkout', 'feature/review');

const result = await readGitReview(root, 'branch');
assert.equal(result.ok, true);
if (!result.ok) return;
assert.deepEqual(
result.snapshot.files.map((file) => file.path),
['feature.txt'],
);
});

it('stages and unstages only paths admitted by the current snapshot', async () => {
const root = await repository();
await writeFile(join(root, 'base.txt'), 'base\nchanged\n', 'utf8');
Expand Down
14 changes: 10 additions & 4 deletions apps/desktop/src/main/git-review-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ export async function readGitReview(
? requestedBaseBranch ??
(await resolveBaseBranch(repositoryRoot, currentBranch, runGit))
: null;
const branchComparison =
source === 'branch' && baseBranch
? cleanLine(
await runGit(repositoryRoot, ['merge-base', baseBranch, 'HEAD']),
)
: null;
const tracked = await readTrackedChanges({
repositoryRoot,
source,
baseBranch,
branchComparison,
hasHead,
runGit,
});
Expand Down Expand Up @@ -197,7 +203,7 @@ export async function mutateGitReview(input: {
async function readTrackedChanges(input: {
repositoryRoot: string;
source: GitReviewSource;
baseBranch: string | null;
branchComparison: string | null;
hasHead: boolean;
runGit: GitReviewCommandRunner;
}): Promise<{ files: GitReviewFile[]; diffChars: number; truncated: boolean }> {
Expand All @@ -206,8 +212,8 @@ async function readTrackedChanges(input: {
? [['--cached']]
: input.source === 'unstaged'
? [[]]
: input.baseBranch
? [[input.baseBranch]]
: input.branchComparison
? [[input.branchComparison]]
: [
input.hasHead ? ['--cached'] : ['--cached', '--root'],
[],
Expand Down
104 changes: 31 additions & 73 deletions apps/desktop/src/renderer/locales/conversation-copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,34 +99,21 @@ export interface DesktopConversationCopy {
empty: string;
/** The panel-empty (tier 2) sentence under `empty`. */
emptyHelp: string;
sourceLabel: string;
branchSource: string;
unstagedSource: string;
stagedSource: string;
lastTurnSource: string;
notGitRepository: string;
workspaceUnavailable: string;
unbornRepository: string;
gitFailed: string;
invalidBaseBranch: string;
truncated: string;
stageFile: string;
unstageFile: string;
mutationFailed: string;
snapshotChanged: string;
revertFile: string;
revertTitle: string;
revertDescription(path: string): string;
revertConfirm: string;
cancel: string;
showMore(remaining: number): string;
summary(files: number, additions: number, deletions: number): string;
comparison(base: string, current: string): string;
workingTree(branch: string): string;
hiddenLines(count: number): string;
changedFiles(count: number): string;
addedLines(count: number): string;
deletedLines(count: number): string;
added(count: number): string;
deleted(count: number): string;
loadFailed: string;
retry: string;
refresh: string;
diffCount(count: number): string;
};
terminalPanel: {
ariaLabel: string;
Expand Down Expand Up @@ -373,7 +360,7 @@ const COPY = {
workbar: {
ariaLabel: '会话工作栏',
sectionsAriaLabel: '会话工作栏标签',
review: '审阅',
review: '变更',
terminal: '终端',
terminalNumbered: (index) => `终端 ${index}`,
tasks: '任务',
Expand All @@ -395,7 +382,7 @@ const COPY = {
closeOthers: '关闭其他标签',
closeToRight: '关闭右侧标签',
launcher: {
review: '审阅当前会话产生的文件差异',
review: '查看当前 Git 工作区变化',
terminal: '查看当前会话的终端运行和实时输出',
tasks: '查看和维护当前会话的任务台账',
browser: '打开内置浏览器并保留当前页面',
Expand All @@ -405,39 +392,24 @@ const COPY = {
},
},
reviewPanel: {
ariaLabel: '会话审阅',
empty: '当前会话还没有文件差异',
emptyHelp: '这个会话还没有可回顾的更改。',
sourceLabel: '审阅来源',
branchSource: '分支',
unstagedSource: '未暂存',
stagedSource: '已暂存',
lastTurnSource: '上一轮',
ariaLabel: 'Git 变更',
empty: '当前 Git 工作区没有变化',
emptyHelp: '提交、暂存或修改文件后,变化会显示在这里。',
notGitRepository: '当前会话目录不是 Git 仓库',
workspaceUnavailable: '当前会话目录已不可用',
unbornRepository: 'Git 仓库还没有可比较的提交',
gitFailed: '无法读取 Git 工作区变化',
invalidBaseBranch: '选择的比较分支已不可用',
truncated: '变化过多,仅显示前一部分文件',
stageFile: '暂存文件',
unstageFile: '取消暂存文件',
mutationFailed: '无法更新 Git 暂存区',
snapshotChanged: 'Git 变化已更新,请重试',
revertFile: '还原文件',
revertTitle: '还原未暂存变化?',
revertDescription: (path) =>
`${path} 的未暂存变化会被移除;未跟踪文件会被删除。`,
revertConfirm: '确认还原',
cancel: '取消',
showMore: (remaining) => `再显示 ${Math.min(20, remaining)} 个文件`,
summary: (files, additions, deletions) =>
`${files} 个文件 · +${additions} · -${deletions}`,
comparison: (base, current) => `${current} 相对 ${base}`,
workingTree: (branch) => `${branch} 工作区`,
loadFailed: '无法读取会话差异',
hiddenLines: (count) => `另有 ${count} 行未显示`,
changedFiles: (count) => `${count} 个文件有变更`,
addedLines: (count) => `新增 ${count} 行`,
deletedLines: (count) => `删除 ${count} 行`,
added: (count) => `新增 ${count}`,
deleted: (count) => `删除 ${count}`,
loadFailed: '无法读取 Git 变化',
retry: '重试',
refresh: '刷新审阅',
diffCount: (count) => `${count} 个差异`,
},
terminalPanel: {
ariaLabel: '会话终端',
Expand Down Expand Up @@ -570,7 +542,7 @@ const COPY = {
workbar: {
ariaLabel: 'Conversation workbar',
sectionsAriaLabel: 'Conversation workbar tabs',
review: 'Review',
review: 'Changes',
terminal: 'Terminal',
terminalNumbered: (index) => `Terminal ${index}`,
tasks: 'Tasks',
Expand All @@ -592,7 +564,7 @@ const COPY = {
closeOthers: 'Close other tabs',
closeToRight: 'Close tabs to the right',
launcher: {
review: 'Review file diffs produced by this conversation',
review: 'View changes in the current Git workspace',
terminal: 'Inspect terminal runs and live output for this conversation',
tasks: 'View and maintain the task ledger for this conversation',
browser: 'Open the embedded browser and keep the current page',
Expand All @@ -602,40 +574,26 @@ const COPY = {
},
},
reviewPanel: {
ariaLabel: 'Conversation review',
empty: 'No file diffs in this conversation yet',
emptyHelp: 'No changes to review for this session yet.',
sourceLabel: 'Review source',
branchSource: 'Branch',
unstagedSource: 'Unstaged',
stagedSource: 'Staged',
lastTurnSource: 'Last turn',
ariaLabel: 'Git changes',
empty: 'No changes in the current Git workspace',
emptyHelp: 'Committed, staged, and modified files appear here.',
notGitRepository: 'This conversation directory is not a Git repository',
workspaceUnavailable: 'This conversation directory is unavailable',
unbornRepository: 'This Git repository has no commit to compare yet',
gitFailed: 'Could not read Git workspace changes',
invalidBaseBranch: 'The selected comparison branch is unavailable',
truncated: 'Too many changes; showing the first files only',
stageFile: 'Stage file',
unstageFile: 'Unstage file',
mutationFailed: 'Could not update the Git index',
snapshotChanged: 'Git changes were refreshed; try again',
revertFile: 'Revert file',
revertTitle: 'Revert unstaged changes?',
revertDescription: (path) =>
`Unstaged changes in ${path} will be removed; untracked files will be deleted.`,
revertConfirm: 'Revert',
cancel: 'Cancel',
showMore: (remaining) =>
`Show ${Math.min(20, remaining)} more file${Math.min(20, remaining) === 1 ? '' : 's'}`,
summary: (files, additions, deletions) =>
`${files} file${files === 1 ? '' : 's'} · +${additions} · -${deletions}`,
comparison: (base, current) => `${current} compared with ${base}`,
workingTree: (branch) => `${branch} working tree`,
loadFailed: 'Could not read conversation diffs',
hiddenLines: (count) =>
`${count} more line${count === 1 ? '' : 's'} not shown`,
changedFiles: (count) => `${count} changed file${count === 1 ? '' : 's'}`,
addedLines: (count) => `${count} line${count === 1 ? '' : 's'} added`,
deletedLines: (count) => `${count} line${count === 1 ? '' : 's'} deleted`,
added: (count) => `${count} added`,
deleted: (count) => `${count} deleted`,
loadFailed: 'Could not read Git changes',
retry: 'Retry',
refresh: 'Refresh review',
diffCount: (count) => `${count} diff${count === 1 ? '' : 's'}`,
},
terminalPanel: {
ariaLabel: 'Conversation terminal',
Expand Down
Loading
Loading