Compare commits

...
4 Commits
Author SHA1 Message Date
brother7 bce20d9e4d Merge branch 'main' of https://git.nianxx.cn/wangxuming/makelore
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled
2026-09-07 20:28:05 +08:00
brother7 171602f302 docs: record context menu integration 2026-09-07 18:10:08 +08:00
brother7 6fbb9c077e test: cover design text context menu 2026-09-07 17:19:04 +08:00
brother7 813d1ca02e fix: restore native text context menu 2026-09-07 17:07:57 +08:00
8 changed files with 479 additions and 0 deletions

No files matched your search

+15
View File
@@ -4,6 +4,21 @@ This file is the integrated default-branch snapshot. Feature tasks record progre
## Integrated Through
- Native text context-menu source frontier
`6fbb9c077ecda23e2ee6472631fe374527c49cb7` from task
`20260907-input-context-menu-a7c31e` is fast-forwarded onto local `main` by
task `20260907-integrate-context-menu-5a9e3c71`. The primary Renderer window
now receives one Electron Main-owned native menu for editable controls or an
existing text selection, exposing Chinese Cut, Copy, Paste, and Select All
roles according to Chromium edit capabilities. The same window-level listener
covers both Code and AI Design textareas without Renderer clipboard IPC or a
duplicate Canvas listener; ordinary non-editable content remains unaffected.
Verification passed the 5 focused unit cases, typecheck, full lint with 0
errors and 5 existing warnings, all Vite targets, and an Electron E2E that
observed one popup for each Composer. The full unit run passed 1,938 tests with
2 skips; its only failure was the unchanged Pi real-process two-second timing
assertion, which passed all 6 tests in isolation. No push, package, deployment,
publication, Provider call, or user-data mutation was performed.
- Local AI Design activity frontier
`4b894c7e320cdf829888f63f501d176f051be7d7` and fetched remote frontier
`918f8f80dca61b0f561f96b168e4ef541734d414` are reconciled by the ordinary
@@ -0,0 +1,105 @@
# Task: Fix AI programming input context menu
## Identity
- Task ID: 20260907-input-context-menu-a7c31e
- Mode: Feature
- Branch: codex/20260907-input-context-menu-a7c31e-input-context-menu
- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260907-input-context-menu-a7c31e
- Base commit: 8c13ae76db6fa47972bdf9ff3de932eb761aaffe
- Owner: codex
- Status: Ready for Integration
## Scope
- Reproduce the AI Programming composer symptom where right-clicking editable text
exposes no native editing menu.
- Compare the current integrated primary-window wiring with completed task
`20260819-context-menu-impl-c7d4a2e1` and reuse only behavior still compatible
with the current window lifecycle.
- Restore the smallest Main-owned native text-editing context-menu behavior and add
focused regression coverage at the real window/event seam.
- Verify that the AI Design `design-chat-composer` textarea shares the same
primary Renderer seam, and extend the Electron regression to cover both Code
and Design textarea controls without adding a duplicate listener.
- Update user-facing documentation or Electron E2E coverage only where current
behavior and the shared fixture require it.
## Intent And Constraints
- Electron Main owns desktop context-menu integration. Do not add Renderer,
Preload, IPC, Host API, or direct clipboard access for this fix.
- Use the primary window's `webContents` `context-menu` event and native editing
roles when that remains the confirmed compatible seam.
- Show a menu only for editable controls or an existing text selection; derive
enabled actions from the actual Electron edit context.
- Preserve Renderer-owned custom menus, Monaco behavior, the detached Agent Browser,
existing application-menu behavior, Chinese-only UI, and the single light visual
system.
- Build a deterministic red-capable feedback loop for the reported input symptom
before selecting a cause or changing production code.
- Work only in the isolated task worktree; do not modify, adopt, clean, or merge the
occupied root `main` worktree or any peer worktree.
## Outcome
- Confirmed the failure at the real Electron window seam: a renderer right-click
produced no native menu because the primary `webContents` had zero
`context-menu` listeners. The earlier implementation in task
`20260819-context-menu-impl-c7d4a2e1` exists only on its isolated branch and
was never integrated into the current `main` history.
- Added a Main-owned text context-menu module that builds native Chinese
`cut`, `copy`, `paste`, and `selectAll` roles from Electron's actual
`isEditable`, `selectionText`, and `editFlags` context. Ordinary non-editable
content still produces no menu.
- Registered the listener once during primary-window creation without adding
Renderer, Preload, IPC, Host API, or direct clipboard access.
- Confirmed `DesignConversationPane` renders the AI Design composer as the
ordinary `<textarea id="design-chat-composer">` produced by the shared
`Textarea` component inside that same primary Renderer. The window-level
listener therefore covers both AI Code and AI Design without a second
production listener.
- Added five focused unit cases and an Electron E2E regression that dispatches
real right-clicks on Code and Design textarea controls inside the primary
Renderer window and observes two native menu popups with the expected editing
actions.
- Synchronized `README.md` with the restored desktop behavior and ownership
boundary.
## Verification
- Red phase:
- `corepack pnpm exec playwright test tests/e2e/context-menu.spec.ts` failed
twice with `popupCount: 0` before the production change.
- A temporary targeted probe reported zero `context-menu` listeners on the
primary `webContents`; the probe was removed before completion.
- `corepack pnpm exec vitest run tests/unit/context-menu.test.ts` failed to
resolve the absent Main module before implementation.
- Green phase:
- `corepack pnpm exec vitest run tests/unit/context-menu.test.ts` — 5 passed.
- `corepack pnpm exec playwright test tests/e2e/context-menu.spec.ts` — 1
passed, covering both Code and Design textareas with two observed native
popups.
- `corepack pnpm run typecheck` — passed.
- `corepack pnpm exec eslint electron/main/context-menu.ts electron/main/index.ts tests/unit/context-menu.test.ts tests/e2e/context-menu.spec.ts`
— passed.
- `corepack pnpm run lint:check` — passed with 5 pre-existing warnings in
untouched files and no errors.
- `corepack pnpm run build:vite` — passed for Renderer, Main, Preload, and
release utility bundles; only existing chunk/dynamic-import warnings were
reported.
- `corepack pnpm test` — 226 test files passed; one unrelated real-process
timing assertion in `pi-agent-server-process-real.test.ts` measured 2323ms
against a 2000ms threshold. Its isolated rerun with
`corepack pnpm exec vitest run tests/unit/pi-agent-server-process-real.test.ts --maxWorkers=1`
passed all 6 tests, so no unrelated timing code or threshold was changed.
- `git diff --check` — passed.
## Follow-ups
- Integrate this ready branch through the project Integration Gate after the
current owner of the root `main` worktree releases it.
## Promotion Candidates
- None recorded.
@@ -0,0 +1,88 @@
# Task: Integrate native text context menu
## Identity
- Task ID: 20260907-integrate-context-menu-5a9e3c71
- Mode: Integration
- Branch: main
- Worktree: D:\Datas\OthersProjects\makelore
- Base commit: 6fbb9c077ecda23e2ee6472631fe374527c49cb7
- Owner: codex-root
- Status: Ready for Integration
## Scope
- Integrate the reviewed source branch
`codex/20260907-input-context-menu-a7c31e-input-context-menu@6fbb9c0` onto
local `main`.
- Integrate the Main-owned native text context menu for editable controls and
selected text, including explicit Code and AI Design textarea coverage.
- Re-run focused verification on merged `main`, update the canonical integrated
snapshot, and retain the source task record in source history unchanged.
## Intent And Constraints
- Preserve the source task's Electron Main ownership boundary: no Renderer,
Preload, IPC, Host API, or direct clipboard surface is added.
- Preserve the three explicitly adopted untracked task records byte-for-byte;
do not add, edit, delete, move, or commit them.
- Do not merge the superseded August implementation branch separately; the
current source already revalidates that behavior against the latest window
lifecycle and covers both Code and Design textareas.
- Do not change unrelated Pi timing code or thresholds, package, deploy,
publish, push to `origin`, or alter user data as part of this integration.
## Outcome
- After the user's explicit confirmation, released the completed prior
Integration ownership and claimed `main` while adopting the three known
untracked task records by fingerprint. Their files remain byte-for-byte
untouched and outside this integration commit.
- Verified that local `main@8c13ae7` was an ancestor of the reviewed source tip
`6fbb9c0`, then fast-forwarded `main` without conflicts or history rewriting.
- Re-established this Integration task's comparison base at the accepted source
tip using the bundled ownership scripts, so the source task record remains
read-only source history rather than being attributed to this task.
- Integrated one Electron Main-owned native text menu for editable controls and
selected text. Code and AI Design textarea controls share the same primary
Renderer listener; no duplicate Canvas listener or Renderer clipboard surface
was introduced.
- Retained the source task record and README update, and recorded the source tip
under `Current State / Integrated Through`. No additional ADR or architecture
change was required.
## Verification
- Before fast-forward, the source tip matched the reviewed
`6fbb9c077ecda23e2ee6472631fe374527c49cb7`, `git merge-base --is-ancestor`
passed, and `git diff --check` reported no source whitespace errors.
- `corepack pnpm --version` — exact pinned `10.33.4`.
- `corepack pnpm exec vitest run tests/unit/context-menu.test.ts` — 5 passed.
- `corepack pnpm run typecheck` — passed.
- Focused ESLint for the Main module, window registration, unit test, and E2E —
passed with no output.
- `corepack pnpm run build:vite` — Renderer, Main, Preload, and release utility
bundles passed; only existing Browserslist, mixed-import, and chunk-size
warnings were emitted.
- `corepack pnpm exec playwright test tests/e2e/context-menu.spec.ts` — 1 passed,
observing two native popups across the Code and AI Design textarea controls.
- `corepack pnpm run lint:check` — passed with 0 errors and 5 existing warnings
in untouched files.
- `corepack pnpm test` — 226 files / 1,938 tests passed with 2 skipped; the only
failure was the unchanged Pi real-process two-second timing assertion (2112ms
against 2000ms in the final report). Its isolated rerun with one worker passed
all 6 tests, so no unrelated Pi code or threshold was changed.
- Task-aware documentation drift and adopted-record fingerprint checks passed;
`git diff --check` also passed.
## Follow-ups
- A rebuilt or development client is required to observe the newly integrated
Main bundle; this task does not package or publish an installer.
- Push `main` to `origin` only if the user separately requests publication.
## Promotion Candidates
- Applied in this integration: `current-state.md` records the source frontier,
Main ownership boundary, shared Code/Design behavior, and verification result.
No further promotion is pending.
+1
View File
@@ -15,6 +15,7 @@ Makelore 是一个面向软件、视觉创作与智能机器人的 AI 桌面工
## 当前产品状态
- 桌面技术栈:Electron、React 19、Vite、TypeScript、Zustand、Tailwind CSS。
- 桌面文本右键菜单:主 Renderer 窗口由 Electron Main 提供原生“剪切、复制、粘贴、全选”操作;菜单只在可编辑区域或已有文本选择时出现,普通空白区域不显示,也不向 Renderer 暴露剪贴板 IPC。
- AI 编程核心对话运行时:Electron Main 按需启动一个长驻 Pi Agent Server,同一进程内为每条 Conversation 建立独立的 Runtime、Session、凭证视图、扩展上下文和 JSONL 通道,同时复用 Pi 模块与进程启动成本;Server 异常退出时所有旧通道统一失效,下一次恢复只重启一个 Server。正式包中的 Agent Server 从明确传入的 `resources/pi-runtime` 根定位依赖清单与包入口,不从脚本所在目录或应用 `node_modules` 猜测解析。Renderer 不直接启动或调用 Pi,也不读取其 wire 类型。项目与 Conversation 元数据先从本地读取,选中对话后才按需准备对应逻辑线程;输入框不等待运行时准备完成。同一助手工具批次中,会取得项目写租约的 `bash`、`edit`、`write` 与动态产品工具按顺序执行,避免批次在命令启动前自锁;纯只读工具仍可并行。子 Agent 仍使用独立的短生命周期进程,以保留并发配额、故障和写入边界;macOS 上两类后台进程都使用 Electron 自带的 Helper,不在 Dock 中创建额外应用图标。
- AI 编程默认入口:进入 Code 后保持在 `/chat`。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。
- AI 编程历史与配额反馈:Pi 上下文压缩不删除用户可见的 active-branch 历史,压缩摘要保持 Main-private;Renderer 首次渲染最近 120 个节点,向上滚动时按 100 个节点追加更早内容并保持阅读位置。Main 精确识别 `token_point_balance_exhausted` / `词元点数余额不足`,只向 Renderer 投影不可重试的稳定配额错误和安全中文提示,重开会话后仍可见且不暴露上游原始错误或 request id。
+52
View File
@@ -0,0 +1,52 @@
import { Menu } from 'electron';
import type { BrowserWindow } from 'electron';
export type TextContextMenuContext = Pick<
Electron.ContextMenuParams,
'isEditable' | 'selectionText' | 'editFlags'
>;
export function createTextContextMenuTemplate(
context: TextContextMenuContext,
): Electron.MenuItemConstructorOptions[] | null {
if (!context.isEditable && context.selectionText.length === 0) {
return null;
}
return [
{
label: '剪切',
role: 'cut',
enabled: context.isEditable && context.editFlags.canCut,
},
{
label: '复制',
role: 'copy',
enabled: context.editFlags.canCopy,
},
{
label: '粘贴',
role: 'paste',
enabled: context.isEditable && context.editFlags.canPaste,
},
{ type: 'separator' },
{
label: '全选',
role: 'selectAll',
enabled: context.editFlags.canSelectAll,
},
];
}
export function registerTextContextMenu(win: BrowserWindow): void {
win.webContents.on('context-menu', (_event, params) => {
const template = createTextContextMenuTemplate(params);
if (!template) return;
Menu.buildFromTemplate(template).popup({
window: win,
frame: params.frame ?? undefined,
sourceType: params.menuSourceType,
});
});
}
+2
View File
@@ -9,6 +9,7 @@ import { join } from 'node:path';
import { registerIpcHandlers } from './ipc-handlers';
import { createTray } from './tray';
import { createMenu } from './menu';
import { registerTextContextMenu } from './context-menu';
import { registerZoomShortcuts } from './zoom-shortcuts';
import { getNativeWindowMaterialOptions } from './window-material';
@@ -272,6 +273,7 @@ function createWindow(): BrowserWindow {
});
installMediaPermissionHandler(win);
registerTextContextMenu(win);
registerZoomShortcuts(win);
// Handle external links — only allow safe protocols to prevent arbitrary
+96
View File
@@ -0,0 +1,96 @@
import type { ElectronApplication } from '@playwright/test';
import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
type ContextMenuProbe = {
popupCount: number;
template: Array<{
label: string | null;
role: string | null;
type: string;
}>;
};
async function installContextMenuProbe(app: ElectronApplication): Promise<void> {
await app.evaluate(({ Menu }) => {
const probe: ContextMenuProbe = {
popupCount: 0,
template: [],
};
const globalWithProbe = globalThis as typeof globalThis & {
__makeloreContextMenuProbe?: ContextMenuProbe;
};
globalWithProbe.__makeloreContextMenuProbe = probe;
Menu.buildFromTemplate = ((template) => {
probe.template = template.map((item) => ({
label: item.label ?? null,
role: item.role ?? null,
type: item.type ?? 'normal',
}));
return {
popup: () => {
probe.popupCount += 1;
},
} as unknown as Electron.Menu;
}) as typeof Menu.buildFromTemplate;
});
}
async function readContextMenuProbe(app: ElectronApplication): Promise<ContextMenuProbe | null> {
return await app.evaluate(() => {
const globalWithProbe = globalThis as typeof globalThis & {
__makeloreContextMenuProbe?: ContextMenuProbe;
};
return globalWithProbe.__makeloreContextMenuProbe ?? null;
});
}
test.describe('Makelore text context menu', () => {
test('opens native editing actions for Code and Design textareas', async ({ launchElectronApp }) => {
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await installContextMenuProbe(app);
await page.evaluate(() => {
const composerIds = [
'context-menu-e2e-code-composer',
'design-chat-composer',
];
for (const [index, id] of composerIds.entries()) {
const textarea = document.createElement('textarea');
textarea.id = id;
textarea.style.position = 'fixed';
textarea.style.inset = `${80 + index * 80}px auto auto 80px`;
textarea.style.zIndex = '2147483647';
document.body.append(textarea);
}
});
for (const selector of [
'#context-menu-e2e-code-composer',
'#design-chat-composer',
]) {
const textarea = page.locator(selector);
await textarea.fill('Makelore');
await textarea.selectText();
await textarea.click({ button: 'right' });
}
await expect.poll(async () => await readContextMenuProbe(app)).toEqual({
popupCount: 2,
template: [
{ label: '剪切', role: 'cut', type: 'normal' },
{ label: '复制', role: 'copy', type: 'normal' },
{ label: '粘贴', role: 'paste', type: 'normal' },
{ label: null, role: null, type: 'separator' },
{ label: '全选', role: 'selectAll', type: 'normal' },
],
});
} finally {
await closeElectronApp(app);
}
});
});
+120
View File
@@ -0,0 +1,120 @@
import { describe, expect, it } from 'vitest';
import {
createTextContextMenuTemplate,
type TextContextMenuContext,
} from '@electron/main/context-menu';
type ContextOverrides = {
isEditable?: boolean;
selectionText?: string;
editFlags?: Partial<Electron.EditFlags>;
};
function context(overrides: ContextOverrides = {}): TextContextMenuContext {
return {
isEditable: overrides.isEditable ?? false,
selectionText: overrides.selectionText ?? '',
editFlags: {
canUndo: false,
canRedo: false,
canCut: false,
canCopy: false,
canPaste: false,
canDelete: false,
canSelectAll: false,
canEditRichly: false,
...overrides.editFlags,
},
};
}
function enabledByRole(template: Electron.MenuItemConstructorOptions[] | null) {
return Object.fromEntries(
(template ?? [])
.filter((item) => item.role)
.map((item) => [item.role, item.enabled]),
);
}
describe('text context menu', () => {
it('does not create a menu for ordinary non-editable content', () => {
expect(createTextContextMenuTemplate(context())).toBeNull();
});
it('allows paste and select all in an editable control without a selection', () => {
const template = createTextContextMenuTemplate(context({
isEditable: true,
editFlags: {
canPaste: true,
canSelectAll: true,
},
}));
expect(template?.map((item) => item.label ?? item.type)).toEqual([
'剪切',
'复制',
'粘贴',
'separator',
'全选',
]);
expect(enabledByRole(template)).toEqual({
cut: false,
copy: false,
paste: true,
selectAll: true,
});
});
it('enables every requested action for an editable selection when supported', () => {
const template = createTextContextMenuTemplate(context({
isEditable: true,
selectionText: 'Makelore',
editFlags: {
canCut: true,
canCopy: true,
canPaste: true,
canSelectAll: true,
},
}));
expect(enabledByRole(template)).toEqual({
cut: true,
copy: true,
paste: true,
selectAll: true,
});
});
it('keeps cut and paste disabled for a read-only selection', () => {
const template = createTextContextMenuTemplate(context({
selectionText: 'Makelore',
editFlags: {
canCut: true,
canCopy: true,
canPaste: true,
canSelectAll: true,
},
}));
expect(enabledByRole(template)).toEqual({
cut: false,
copy: true,
paste: false,
selectAll: true,
});
});
it('respects disabled renderer editing capabilities', () => {
const template = createTextContextMenuTemplate(context({
isEditable: true,
selectionText: 'Makelore',
}));
expect(enabledByRole(template)).toEqual({
cut: false,
copy: false,
paste: false,
selectAll: false,
});
});
});