From 10b179fb7131202a59f3868d0269a7b290210cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 22:55:07 +0800 Subject: [PATCH 01/17] fix: move Code return navigation into the chat sidebar --- .../20260929-code-back-entry-33d87a81.md | 46 ++++++++++++++++ README.md | 2 +- src/components/layout/Sidebar.tsx | 10 ++-- tests/e2e/main-navigation.spec.ts | 52 ++++++++++++++++++- 4 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md new file mode 100644 index 00000000..9f61148f --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -0,0 +1,46 @@ +# Task: Move Code return navigation into the coding workspace + +## Identity + +- Task ID: 20260929-code-back-entry-33d87a81 +- Mode: Feature +- Branch: codex/20260929-code-back-entry-33d87a81-code-back-entry +- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 +- Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Move the existing “编程 Code” module-home return entry from the project configuration sidebar into the Coding chat/workspace sidebar below the brand. Retain its `/module-select` target and the configuration page's own back action. + +## Intent And Constraints + +- User first requested understanding only, confirmed the two-page interpretation, then explicitly approved implementation. Preserve existing light styling, projects, conversation drafts/history, module access and other module navigation. No live-app update, merge, push or deployment is requested for this new change. +- Reused the clean, completed prior merge checkout after releasing its ready task ownership; old branch/commits remain. Created a new branch at current main a77ddbd, then successfully claimed this new task and verified exact task/mode/worktree/branch/base/reservation. No source from occupied foreign worktrees adopted. + +## Project Context Loaded + +- Task identity: as above, feature mode; ownership verified. Read active task and entry/memory index. Previously loaded positioning, current-state, decision-index, teacher ADR, architecture/domain, evidence, commitments and stale items are unchanged from 5aabaaf, verified by Git diff. Current AGENTS still requires existing light system, local project state and Main-owned APIs. +- Product/context: current main includes empty-work behavior, teacher voice/model controls and compact published shortcuts. This is a small navigation relocation within the existing sidebar; no new visual system is needed. Inspected screenshots, Sidebar, ModuleSwitcher, MainLayout, module route definitions, layout CSS and existing navigation tests. +- Peer scopes reviewed read-only. Active teacher-group-intro and child-language tasks have undefined scope, so coordination context is unknown; neither supplies a known dependency on module-return navigation. Existing teacher/UI tasks are ready. This task owns Sidebar navigation plus focused test/README updates only; no shared canonical docs. +- Relevant behavior: original “编程 Code” ModuleSwitcher navigates to `/module-select`, not a project reset. `/chat` uses the classroom sidebar; project configuration and nested resource pages use the generic sidebar. Retain the original target as the user confirmed. +- Gate: Passed. Plan: show the existing module-return control below the Code workspace brand, omit it on `/project-config` and nested configuration routes, keep a compact accessible layout, adjust the existing navigation regression, run typecheck/lint/build and focused UI verification, review and commit. + +## Outcome + +- Reused the existing ModuleSwitcher beneath the brand in the `/chat` sidebar, retaining its compact arrow/text style, accessible return-home label and `/module-select` action. Project configuration and nested resource pages omit the entire sidebar header block, so no empty placeholder remains. Existing configuration back action, sidebar resizing/peek and other module controls are unchanged. README synchronized. + +## Verification + +- pnpm 10.33.4 standard typecheck, changed-file ESLint and production Renderer/Main/Preload/utility build passed. Existing sidebar/peek/module gate and module-navigation unit coverage: 23 tests across four files passed. +- Extended one existing navigation scenario; isolated Electron E2E 1/1 passed. It checks Code return beneath the brand, no sidebar entry on configuration or plugin subpage, configuration back to chat, and collapsed-sidebar hover followed by real navigation to `/module-select`. Used an independent test profile/ports; no real user application or conversations were changed. Test command: `corepack pnpm exec playwright test tests/e2e/main-navigation.spec.ts --grep 'opens remembered Code projects' --reporter=list`. +- Inspected chat and configuration screenshots; delegated review also checked collapsed peek. Artifacts are under `test-results/main-navigation-Makelore-m-9ce56-instead-of-project-settings/`. Independent code review passed; no additional regression found. Whitespace and task-doc drift checks passed. + +## Follow-ups + +- Source is ready for integration. Main, remote and the running temporary app were not updated by this new task. + +## Promotion Candidates + +- Target: canonical classroom navigation when next integrated. Proposal: Code module-home return is visible below the chat sidebar brand and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. diff --git a/README.md b/README.md index 1f84dca6..812c0653 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 - macOS 窗口按钮:红色关闭当前窗口,全屏时先退出全屏再关闭;黄色最小化,绿色保留原生缩放/全屏行为。关窗后应用和进行中的后台任务继续运行,点击 Dock、托盘或菜单可重新打开窗口;`Cmd+Q` 退出应用。 - 桌面文本右键菜单:主 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 编程默认入口:进入 Code 后保持在 `/chat`。左侧麦洛品牌区下方提供“← 编程 Code”按钮,点击返回模块选择首页;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 - AI 编程历史与配额反馈:Pi 上下文压缩不删除用户可见的 active-branch 历史,压缩摘要保持 Main-private;Renderer 首次渲染最近 120 个节点,向上滚动时按 100 个节点追加更早内容并保持阅读位置。Main 精确识别 `token_point_balance_exhausted` / `词元点数余额不足`,只向 Renderer 投影不可重试的稳定配额错误和安全中文提示,重开会话后仍可见且不暴露上游原始错误或 request id。 - 账号词元点数:左下角账户菜单显示本人永久余额和预占,提供充值、充值订单与分页收支记录。真正新注册赠送 100 点,1 元充值 50 点,永久有效;旧账户不补送,旧会员/订阅/周额度/重置卡入口已移除。家长充值始终进入自己的钱包,青少年可查看自己的精确余额;使用他人共享钱包时仅显示可用状态,不显示付款人的余额或记录。Main 持有凭据并转发充值请求;付款结果未知时沿用原请求并核查原订单,只有服务端确认才显示到账。扫码信息缺失时可到网页账户页继续原订单,付款来源也由网页账户页管理。需配套部署 Works Square 永久点数 API 与 0094 迁移;本地模拟验收不代表真实支付通过。 - 桌面性能策略:应用窗口使用不透明浅色表面并默认保留硬件合成;仅在显式安全模式或短时间内重复 GPU 进程崩溃时启用软件渲染,并把故障原因保存在用户数据目录。启动关键路径只创建本地应用壳,认证、代理、同步、更新和遥测在首帧后延迟初始化;开发版可通过 `app:performance` 快照观察 GPU、进程、WebContents、事件循环与 Renderer Long Task 聚合指标。 diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 730bb70d..d6e37bff 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -133,6 +133,7 @@ export function Sidebar({ const isProgrammingModule = activeModule === 'programming'; const isPaintingModule = activeModule === 'painting'; const isRobotModule = activeModule === 'robot'; + const isProjectConfigPage = location.pathname === '/project-config' || location.pathname.startsWith('/project-config/'); const visibleProjects = projects; const selectedProjectFolderName = getFolderName(newProjectSelectedPath); const accountName = userProfile?.displayName?.trim() || getAuthUserDisplayName(authUser) || '未登录用户'; @@ -403,7 +404,7 @@ export function Sidebar({ )} > {workspaceLayout && !sidebarPinnedCollapsed && } -
- {workspaceLayout ? : } + } +
-
+ }
{isPaintingModule ? ( diff --git a/tests/e2e/main-navigation.spec.ts b/tests/e2e/main-navigation.spec.ts index 7cef5807..d5fef1e5 100644 --- a/tests/e2e/main-navigation.spec.ts +++ b/tests/e2e/main-navigation.spec.ts @@ -50,7 +50,7 @@ async function seedInactiveCodingProjects(resources: { rootDir: string; userData } test.describe('Makelore module navigation without setup flow', () => { - test('offers a create CTA and horizontal cards for remembered inactive Code projects', async ({ + test('opens remembered Code projects and keeps the module return entry in chat instead of project settings', async ({ electronTestResources, launchElectronApp, }) => { @@ -76,6 +76,56 @@ test.describe('Makelore module navigation without setup flow', () => { await expect(page.getByTestId('coding-chat-panel')).toBeVisible(); await expect(page.getByTestId('coding-existing-projects')).toHaveCount(0); await expect(page.getByTestId('sidebar-course-project-project-storybook')).toHaveAttribute('aria-current', 'page'); + + const sidebar = page.getByTestId('sidebar'); + const moduleReturn = sidebar.getByTestId('sidebar-module-switcher-trigger'); + await expect(moduleReturn).toBeVisible(); + await expect(moduleReturn).toContainText('编程 Code'); + await expect(moduleReturn).toHaveAttribute('aria-label', '返回首页'); + await expect(sidebar.getByTestId('sidebar-module-return-icon')).toBeVisible(); + await expect(moduleReturn).toHaveCSS('padding-top', '8px'); + const brandBox = await sidebar.locator('.classroom-brand').boundingBox(); + const returnBox = await moduleReturn.boundingBox(); + expect(brandBox).not.toBeNull(); + expect(returnBox).not.toBeNull(); + expect(returnBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height); + await page.screenshot({ path: test.info().outputPath('chat-code-return-entry.png') }); + + await sidebar.getByRole('button', { name: '项目设置', exact: true }).click(); + await expect(page).toHaveURL(/\/project-config$/); + await expect(page.getByTestId('project-configuration-page')).toBeVisible(); + await expect(sidebar.getByTestId('sidebar-module-switcher')).toHaveCount(0); + const projectBack = page.getByTestId('project-configuration-back-button'); + await expect(projectBack).toBeVisible(); + await page.screenshot({ path: test.info().outputPath('project-config-without-module-return.png') }); + await projectBack.click(); + await expect(page).toHaveURL(/\/chat$/); + await expect(moduleReturn).toBeVisible(); + + await sidebar.getByRole('button', { name: '项目设置', exact: true }).click(); + await page.getByTestId('resource-card-plugins').click(); + await expect(page).toHaveURL(/\/project-config\/plugins\?/); + const plugins = page.getByTestId('project-plugins-sheet'); + await expect(plugins).toBeVisible(); + await expect(sidebar.getByTestId('sidebar-module-switcher')).toHaveCount(0); + await page.screenshot({ path: test.info().outputPath('project-config-plugins-without-module-return.png') }); + await plugins.getByRole('button', { name: '关闭', exact: true }).click(); + await expect(page).toHaveURL(/\/project-config$/); + await expect(projectBack).toBeVisible(); + await sidebar.getByTestId('sidebar-course-project-project-storybook').click(); + await expect(page).toHaveURL(/\/chat$/); + + const sidebarToggle = page.getByTestId('titlebar-sidebar-toggle'); + await sidebarToggle.click(); + await page.getByTestId('coding-chat-panel').click({ position: { x: 500, y: 200 } }); + await expect(sidebar).toHaveAttribute('aria-hidden', 'true'); + await sidebarToggle.hover(); + await expect(sidebar).toHaveAttribute('aria-hidden', 'false'); + await expect(moduleReturn).toBeVisible(); + await page.screenshot({ path: test.info().outputPath('chat-collapsed-hover-code-return.png') }); + await moduleReturn.click(); + await expect(page).toHaveURL(/\/module-select$/); + await expect(page.getByTestId('ai-module-selection-page')).toBeVisible(); } finally { await closeElectronApp(app); } From dcaa7baaccd308fabeab6ee24a57140d4933d6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 23:13:17 +0800 Subject: [PATCH 02/17] docs: record temporary app navigation update --- .../20260929-code-back-entry-33d87a81.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 9f61148f..48eb0f52 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -16,7 +16,7 @@ ## Intent And Constraints -- User first requested understanding only, confirmed the two-page interpretation, then explicitly approved implementation. Preserve existing light styling, projects, conversation drafts/history, module access and other module navigation. No live-app update, merge, push or deployment is requested for this new change. +- User first requested understanding only, confirmed the two-page interpretation, then explicitly approved implementation. Preserve existing light styling, projects, conversation drafts/history, module access and other module navigation. Initial implementation excluded live-app updates. The follow-up explicitly authorizes the temporary Renderer update; main merge, push and formal-app deployment remain outside this change. - Reused the clean, completed prior merge checkout after releasing its ready task ownership; old branch/commits remain. Created a new branch at current main a77ddbd, then successfully claimed this new task and verified exact task/mode/worktree/branch/base/reservation. No source from occupied foreign worktrees adopted. ## Project Context Loaded @@ -39,8 +39,23 @@ ## Follow-ups -- Source is ready for integration. Main, remote and the running temporary app were not updated by this new task. +- Source is ready for integration. The temporary app now includes this navigation change; main and remote remain unchanged. ## Promotion Candidates - Target: canonical classroom navigation when next integrated. Proposal: Code module-home return is visible below the chat sidebar brand and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. + +## Follow-up: Update The Temporary Renderer + +- User explicitly requested the temporary-app update. Same-task ownership resumed and verified. Existing context is unchanged; active peer scopes read. Two other chats are working on teacher presentation changes; their task records and read-only status show no newly published live version yet. No messages sent to other chats. Recheck launch manifest and process identity immediately before applying this narrowly scoped update; adapt to any newer publication rather than replacing it. +- Current trial PID 32583 / Host API 54599 uses composer Main 78cfde6 with empty-work overlay, Renderer efd1894 and latest small-card stylesheet fef3b3a. Prepare the navigation patch against this exact generated Renderer source and preserve current Main/profile. Snapshot drafts/auth/project/teacher selection and histories privately, refresh only the Renderer from its existing served directory, and verify the new navigation plus state preservation. Main merge, remote push and formal installed-app replacement remain outside this follow-up. +- Planning Gate: Passed; only the generated trial artifact and this task record are writable. Keep rollback assets and truthful base/overlay metadata, and never start a model prompt for validation. + + +### Temporary Update Outcome + +- Updated the existing trial Renderer in place at `/var/folders/mx/ltbgwsks5_n8zllj69p_wbcw0000gn/T/makelore-trial-tdq_nuah/teacher-composer-4f829ac1/source/dist`. Main PID 32583, Host API 54599, isolated profile and native window visibility/focus are unchanged. Formal `/Applications/Makelore.app` was not modified. +- Detected another chat publishing teacher-group introductions during preparation. Discarded the earlier candidate and rebuilt from its actual published source, preserving fef3b3a prompt/reply styling, 0167d36 empty-work behavior and 7458c37 grouped introductions. Only source difference is `src/components/layout/Sidebar.tsx`, byte-identical to product commit 10b179f; independently reviewed. +- Final generated source: `code-back-entry-33d87a81/source-with-group-intro` under the trial root; running JS `index-Dh1e8q7U.js`, CSS `index-CA1PKQZ_.css`. Typecheck, scoped lint and Renderer build passed again on this composite source. Launch/guidance metadata and build-source record explicitly retain Main/base/overlay provenance for subsequent updates; old assets and pre-update entry/metadata are retained for rollback. +- Live verification: Code return button visible on `/chat`; same account, active conversation, selected teacher, teacher content, current Work tab and all 14 consultation drafts. All persisted conversation-history hashes unchanged; one generated runtime prompt manifest refreshed during normal conversation rehydration. Visible virtualized message count changed after rehydration, so full on-screen text equality was not claimed. No model prompt was sent for validation. Configuration and nested-route absence were covered by the preceding isolated Electron E2E. +- Used the normal lifecycle lease and conversation-selection API to recover an idle runtime after Renderer reload, then released the lease and restored background throttling. Final live state has zero alerts and unchanged visibility/focus. A native screenshot attempt timed out; actual DOM/button presence, source checks and previous isolated visual/E2E evidence were used instead. Verification summary is in the trial update directory's `verification.json`; private snapshots are mode 0600 and never needed for handoff. From 163f2627539a95b4ae78669f7c40b510ee2c4039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 23:25:41 +0800 Subject: [PATCH 03/17] fix: align Code return and sidebar collapse in the top row --- .../20260929-code-back-entry-33d87a81.md | 16 +++++++++++--- README.md | 2 +- src/components/layout/ModuleSwitcher.tsx | 10 +++++---- src/components/layout/Sidebar.tsx | 16 +++++++++++++- src/components/layout/TitleBar.tsx | 2 +- tests/e2e/main-navigation.spec.ts | 22 +++++++++++++++++-- tests/unit/title-bar.test.tsx | 4 ++-- 7 files changed, 58 insertions(+), 14 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 48eb0f52..cc9efe70 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,11 +8,11 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Ready for Integration +- Status: Planning ## Scope -- Move the existing “编程 Code” module-home return entry from the project configuration sidebar into the Coding chat/workspace sidebar below the brand. Retain its `/module-select` target and the configuration page's own back action. +- Move the existing “编程 Code” module-home return entry from the project configuration sidebar into the Coding chat/workspace sidebar top row above the brand. Retain its `/module-select` target and the configuration page's own back action. ## Intent And Constraints @@ -43,7 +43,7 @@ ## Promotion Candidates -- Target: canonical classroom navigation when next integrated. Proposal: Code module-home return is visible below the chat sidebar brand and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. +- Target: canonical classroom navigation when next integrated. Proposal: Code module-home return is visible at the upper left above the chat sidebar brand, aligned with the right-side collapse control and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. ## Follow-up: Update The Temporary Renderer @@ -59,3 +59,13 @@ - Final generated source: `code-back-entry-33d87a81/source-with-group-intro` under the trial root; running JS `index-Dh1e8q7U.js`, CSS `index-CA1PKQZ_.css`. Typecheck, scoped lint and Renderer build passed again on this composite source. Launch/guidance metadata and build-source record explicitly retain Main/base/overlay provenance for subsequent updates; old assets and pre-update entry/metadata are retained for rollback. - Live verification: Code return button visible on `/chat`; same account, active conversation, selected teacher, teacher content, current Work tab and all 14 consultation drafts. All persisted conversation-history hashes unchanged; one generated runtime prompt manifest refreshed during normal conversation rehydration. Visible virtualized message count changed after rehydration, so full on-screen text equality was not claimed. No model prompt was sent for validation. Configuration and nested-route absence were covered by the preceding isolated Electron E2E. - Used the normal lifecycle lease and conversation-selection API to recover an idle runtime after Renderer reload, then released the lease and restored background throttling. Final live state has zero alerts and unchanged visibility/focus. A native screenshot attempt timed out; actual DOM/button presence, source checks and previous isolated visual/E2E evidence were used instead. Verification summary is in the trial update directory's `verification.json`; private snapshots are mode 0600 and never needed for handoff. + + +## Follow-up: Align Return And Collapse Controls + +- User asks for the Code return at the sidebar upper left and collapse icon at the right of the same row, then update the trial. Same task resumed with exact feature/worktree/branch/base ownership verified. Shared context has no changes since dcaa7ba; active task, entry and all peer scopes reread. Teacher-composer-actions is updating the trial separately; preserve its latest published source when staging. Other ready peers do not create a navigation conflict. +- Project Context Loaded: current light Electron workspace, Main-owned APIs and local project/conversation state remain authoritative. Sidebar default/minimum widths are 171/128px, so macOS traffic-light reservation of 88px cannot accommodate the full return label plus a collapse button in the native row. Use one compact row at the top of the Sidebar above the brand; preserve native titlebar chrome and the stable collapsed/peek expand control. Configuration routes continue to hide return. Narrow sidebars truncate only the visual label, keeping accessible navigation. Linux retains its existing non-collapsible behavior. +- Planning Gate: Passed. Update Sidebar/TitleBar and compact ModuleSwitcher presentation, adapt the existing navigation/layout checks, build, and refresh the latest trial Renderer while preserving conversation state. No main merge, push or formal-app replacement. + +- Implementation: reused ModuleSwitcher in a 40px top sidebar row, removed its vertical offset for this toolbar variant, and aligned a 32px collapse button to the right. TitleBar keeps the existing stable expand/peek button only when the Code sidebar is collapsed. Other module layouts and configuration-page absence remain unchanged. +- Verification: typecheck, changed-file lint and full production build passed. Relevant unit coverage totals 26 passing tests (two old TitleBar assertions updated to the new Sidebar-owned expanded control). Existing Electron navigation scenario passes with actual same-row/right-edge geometry, no duplicate toggle, 128px narrow width without overlap, config/nested absence and collapsed-hover return. Reviewed default-width screenshot. Independent implementation review passed. Temporary Renderer staging is in progress and includes the separately published teacher-composer-actions overlay. diff --git a/README.md b/README.md index 812c0653..d1cf157f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 - macOS 窗口按钮:红色关闭当前窗口,全屏时先退出全屏再关闭;黄色最小化,绿色保留原生缩放/全屏行为。关窗后应用和进行中的后台任务继续运行,点击 Dock、托盘或菜单可重新打开窗口;`Cmd+Q` 退出应用。 - 桌面文本右键菜单:主 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`。左侧麦洛品牌区下方提供“← 编程 Code”按钮,点击返回模块选择首页;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 +- AI 编程默认入口:进入 Code 后保持在 `/chat`。左侧栏顶部在品牌区上方提供同排导航:左侧“← 编程 Code”返回模块选择首页,右侧收起侧栏;窄侧栏仅省略溢出的文字,折叠后的展开入口保持在标题栏;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 - AI 编程历史与配额反馈:Pi 上下文压缩不删除用户可见的 active-branch 历史,压缩摘要保持 Main-private;Renderer 首次渲染最近 120 个节点,向上滚动时按 100 个节点追加更早内容并保持阅读位置。Main 精确识别 `token_point_balance_exhausted` / `词元点数余额不足`,只向 Renderer 投影不可重试的稳定配额错误和安全中文提示,重开会话后仍可见且不暴露上游原始错误或 request id。 - 账号词元点数:左下角账户菜单显示本人永久余额和预占,提供充值、充值订单与分页收支记录。真正新注册赠送 100 点,1 元充值 50 点,永久有效;旧账户不补送,旧会员/订阅/周额度/重置卡入口已移除。家长充值始终进入自己的钱包,青少年可查看自己的精确余额;使用他人共享钱包时仅显示可用状态,不显示付款人的余额或记录。Main 持有凭据并转发充值请求;付款结果未知时沿用原请求并核查原订单,只有服务端确认才显示到账。扫码信息缺失时可到网页账户页继续原订单,付款来源也由网页账户页管理。需配套部署 Works Square 永久点数 API 与 0094 迁移;本地模拟验收不代表真实支付通过。 - 桌面性能策略:应用窗口使用不透明浅色表面并默认保留硬件合成;仅在显式安全模式或短时间内重复 GPU 进程崩溃时启用软件渲染,并把故障原因保存在用户数据目录。启动关键路径只创建本地应用壳,认证、代理、同步、更新和遥测在首帧后延迟初始化;开发版可通过 `app:performance` 快照观察 GPU、进程、WebContents、事件循环与 Renderer Long Task 聚合指标。 diff --git a/src/components/layout/ModuleSwitcher.tsx b/src/components/layout/ModuleSwitcher.tsx index 2c7cb199..d1833127 100644 --- a/src/components/layout/ModuleSwitcher.tsx +++ b/src/components/layout/ModuleSwitcher.tsx @@ -3,7 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { cn } from '@/lib/utils'; import { AI_MODULE_SELECTION_PATH, aiModules, getAiModuleForPath } from '@/lib/ai-modules'; -export function ModuleSwitcher({ sidebarCollapsed, compact = false }: { sidebarCollapsed: boolean; compact?: boolean }) { +export function ModuleSwitcher({ sidebarCollapsed, compact = false, toolbar = false }: { sidebarCollapsed: boolean; compact?: boolean; toolbar?: boolean }) { const location = useLocation(); const navigate = useNavigate(); const activeModuleId = getAiModuleForPath(location.pathname); @@ -14,7 +14,7 @@ export function ModuleSwitcher({ sidebarCollapsed, compact = false }: { sidebarC const compactTrigger = compact && !sidebarCollapsed; return ( -
+
); diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index d6e37bff..f6a4ad26 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -11,6 +11,7 @@ import { FolderKanban, Gamepad2, LogOut, + PanelLeftClose, Plus, RadioTower, Settings as SettingsIcon, @@ -90,6 +91,7 @@ export function Sidebar({ }: SidebarProps = {}) { const { t } = useTranslation('common'); const storedSidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed); + const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed); const sidebarWidth = useSettingsStore((state) => normalizeClassroomSidebarWidth(state.classroomSidebarWidth)); const sidebarPinnedCollapsed = !forceExpanded && storedSidebarCollapsed; const sidebarCollapsed = sidebarPinnedCollapsed && !sidebarPeekOpen; @@ -412,11 +414,23 @@ export function Sidebar({ )} >
+ {workspaceLayout &&
+ + {!sidebarPinnedCollapsed && ['darwin', 'win32'].includes(window.electron?.platform ?? '') && } +
} {workspaceLayout && } - + {!workspaceLayout && }
} diff --git a/src/components/layout/TitleBar.tsx b/src/components/layout/TitleBar.tsx index c73241ec..fbceacfe 100644 --- a/src/components/layout/TitleBar.tsx +++ b/src/components/layout/TitleBar.tsx @@ -178,7 +178,7 @@ function ProductTitleBar({ onPointerEnter={sidebarPreviewTitlebarVisible ? () => handleSidebarPeekEnter('titlebar') : undefined} onPointerLeave={sidebarPreviewTitlebarVisible ? () => handleSidebarPeekLeave('titlebar') : undefined} /> - {typeof document !== 'undefined' ? createPortal(sidebarTogglePortal, document.body) : sidebarTogglePortal} + {(!workspaceLayout || sidebarCollapsed) && (typeof document !== 'undefined' ? createPortal(sidebarTogglePortal, document.body) : sidebarTogglePortal)} ) : null} {integrated ? ( diff --git a/tests/e2e/main-navigation.spec.ts b/tests/e2e/main-navigation.spec.ts index d5fef1e5..af306976 100644 --- a/tests/e2e/main-navigation.spec.ts +++ b/tests/e2e/main-navigation.spec.ts @@ -83,14 +83,32 @@ test.describe('Makelore module navigation without setup flow', () => { await expect(moduleReturn).toContainText('编程 Code'); await expect(moduleReturn).toHaveAttribute('aria-label', '返回首页'); await expect(sidebar.getByTestId('sidebar-module-return-icon')).toBeVisible(); - await expect(moduleReturn).toHaveCSS('padding-top', '8px'); const brandBox = await sidebar.locator('.classroom-brand').boundingBox(); const returnBox = await moduleReturn.boundingBox(); + const collapseBox = await sidebar.getByRole('button', { name: '折叠侧栏' }).boundingBox(); + const sidebarBox = await sidebar.boundingBox(); expect(brandBox).not.toBeNull(); expect(returnBox).not.toBeNull(); - expect(returnBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height); + expect(collapseBox).not.toBeNull(); + expect(returnBox!.y + returnBox!.height).toBeLessThanOrEqual(brandBox!.y); + expect(Math.abs(returnBox!.y + returnBox!.height / 2 - collapseBox!.y - collapseBox!.height / 2)).toBeLessThan(1); + expect(returnBox!.x + returnBox!.width).toBeLessThanOrEqual(collapseBox!.x); + expect(sidebarBox!.x + sidebarBox!.width - collapseBox!.x - collapseBox!.width).toBeLessThanOrEqual(14); + await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveCount(1); await page.screenshot({ path: test.info().outputPath('chat-code-return-entry.png') }); + const sidebarResizer = sidebar.getByRole('separator', { name: '调整左侧功能栏宽度' }); + await sidebarResizer.press('Home'); + await expect(sidebarResizer).toHaveAttribute('aria-valuenow', '128'); + const narrowReturn = await moduleReturn.boundingBox(); + const narrowToggle = await sidebar.getByRole('button', { name: '折叠侧栏' }).boundingBox(); + expect(narrowReturn!.x + narrowReturn!.width).toBeLessThanOrEqual(narrowToggle!.x); + await expect(moduleReturn).toBeVisible(); + await expect(sidebar.getByRole('button', { name: '折叠侧栏' })).toBeVisible(); + await page.screenshot({ path: test.info().outputPath('chat-narrow-navigation-row.png') }); + await sidebarResizer.dblclick(); + await expect(sidebarResizer).toHaveAttribute('aria-valuenow', '171'); + await sidebar.getByRole('button', { name: '项目设置', exact: true }).click(); await expect(page).toHaveURL(/\/project-config$/); await expect(page.getByTestId('project-configuration-page')).toBeVisible(); diff --git a/tests/unit/title-bar.test.tsx b/tests/unit/title-bar.test.tsx index 11e67efd..49c9ad30 100644 --- a/tests/unit/title-bar.test.tsx +++ b/tests/unit/title-bar.test.tsx @@ -91,7 +91,7 @@ describe('TitleBar platform behavior', () => { expect(screen.queryByTestId('titlebar-logo')).not.toBeInTheDocument(); expect(screen.queryByRole('img', { name: 'Makelore logo' })).not.toBeInTheDocument(); expect(screen.queryByTestId('titlebar-project-context')).not.toBeInTheDocument(); - expect(screen.getByRole('button', { name: '折叠侧栏' })).toBeEnabled(); + expect(screen.queryByRole('button', { name: '折叠侧栏' })).not.toBeInTheDocument(); }); it('renders the painting title bar as a transparent overlay with the logo at the window edge', () => { @@ -195,7 +195,7 @@ describe('TitleBar platform behavior', () => { fireEvent.click(pinButton); expect(useSettingsStore.getState().sidebarCollapsed).toBe(false); expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveStyle({ width: '171px', minWidth: '171px', flexBasis: '171px' }); - expect(screen.getByRole('button', { name: '折叠侧栏' })).toBeEnabled(); + expect(screen.queryByRole('button', { name: '折叠侧栏' })).not.toBeInTheDocument(); }); it('extends the ordinary titlebar sidebar surface during a hover preview', () => { From 1329d23921ee76b2f480c3c0d27b34d6fdc29026 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 23:28:25 +0800 Subject: [PATCH 04/17] docs: record aligned sidebar trial verification --- .../30-worklog/tasks/20260929-code-back-entry-33d87a81.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index cc9efe70..e86b4e7c 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Planning +- Status: Ready for Integration ## Scope @@ -29,7 +29,7 @@ ## Outcome -- Reused the existing ModuleSwitcher beneath the brand in the `/chat` sidebar, retaining its compact arrow/text style, accessible return-home label and `/module-select` action. Project configuration and nested resource pages omit the entire sidebar header block, so no empty placeholder remains. Existing configuration back action, sidebar resizing/peek and other module controls are unchanged. README synchronized. +- Reused the existing ModuleSwitcher at the top left above the brand in the `/chat` sidebar, aligned with the collapse control on the right, retaining its accessible return-home label and `/module-select` action. Project configuration and nested resource pages omit the entire sidebar header block, so no empty placeholder remains. Existing configuration back action, sidebar resizing/peek and other module controls are unchanged. README synchronized. ## Verification @@ -69,3 +69,6 @@ - Implementation: reused ModuleSwitcher in a 40px top sidebar row, removed its vertical offset for this toolbar variant, and aligned a 32px collapse button to the right. TitleBar keeps the existing stable expand/peek button only when the Code sidebar is collapsed. Other module layouts and configuration-page absence remain unchanged. - Verification: typecheck, changed-file lint and full production build passed. Relevant unit coverage totals 26 passing tests (two old TitleBar assertions updated to the new Sidebar-owned expanded control). Existing Electron navigation scenario passes with actual same-row/right-edge geometry, no duplicate toggle, 128px narrow width without overlap, config/nested absence and collapsed-hover return. Reviewed default-width screenshot. Independent implementation review passed. Temporary Renderer staging is in progress and includes the separately published teacher-composer-actions overlay. + +- Temporary app updated successfully from the newest built teacher-composer-actions source (8af0ca7), retaining grouped introductions, empty-work behavior and all earlier overlays. Running JS is `index-BSBCYLkH.js`, CSS `index-CA1PKQZ_.css`; generated source is `/var/folders/mx/ltbgwsks5_n8zllj69p_wbcw0000gn/T/makelore-trial-tdq_nuah/code-back-toprow-33d87a81/source`. Main PID 32583/profile remain unchanged. +- Actual live geometry confirms exactly one collapse control, same horizontal center, no overlap, 13px inset from the sidebar right edge and navigation above the brand. Current account, rendered messages, teacher text/panel, Work tab and all 14 drafts match the pre-refresh snapshot; all 34 recorded data files have unchanged hashes. Zero alerts. Normal runtime lease released, prior background throttling restored and the inspector opened for this refresh closed. Launch/guidance provenance now points at the combined built Renderer, including the new 163f262 overlay; separate temporary CSS injection is no longer needed for the preserved teacher-controls update. No paid validation prompt, main merge, push or formal-install change. From ae646f6c7be3ee6a7276d249b8154149aa551ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 23:32:52 +0800 Subject: [PATCH 05/17] fix: keep Makelore branding first in the sidebar --- .../tasks/20260929-code-back-entry-33d87a81.md | 16 ++++++++++++---- README.md | 2 +- src/components/layout/Sidebar.tsx | 8 ++++---- tests/e2e/main-navigation.spec.ts | 3 ++- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index e86b4e7c..47816573 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,11 +8,11 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Ready for Integration +- Status: Planning ## Scope -- Move the existing “编程 Code” module-home return entry from the project configuration sidebar into the Coding chat/workspace sidebar top row above the brand. Retain its `/module-select` target and the configuration page's own back action. +- Move the existing “编程 Code” module-home return entry from the project configuration sidebar into the Coding chat/workspace sidebar navigation row immediately below the topmost brand. Retain its `/module-select` target and the configuration page's own back action. ## Intent And Constraints @@ -29,7 +29,7 @@ ## Outcome -- Reused the existing ModuleSwitcher at the top left above the brand in the `/chat` sidebar, aligned with the collapse control on the right, retaining its accessible return-home label and `/module-select` action. Project configuration and nested resource pages omit the entire sidebar header block, so no empty placeholder remains. Existing configuration back action, sidebar resizing/peek and other module controls are unchanged. README synchronized. +- Reused the existing ModuleSwitcher at the left of the navigation row below the topmost brand in the `/chat` sidebar, aligned with the collapse control on the right, retaining its accessible return-home label and `/module-select` action. Project configuration and nested resource pages omit the entire sidebar header block, so no empty placeholder remains. Existing configuration back action, sidebar resizing/peek and other module controls are unchanged. README synchronized. ## Verification @@ -43,7 +43,7 @@ ## Promotion Candidates -- Target: canonical classroom navigation when next integrated. Proposal: Code module-home return is visible at the upper left above the chat sidebar brand, aligned with the right-side collapse control and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. +- Target: canonical classroom navigation when next integrated. Proposal: Code module-home return is visible below the topmost chat sidebar brand, aligned with the right-side collapse control and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. ## Follow-up: Update The Temporary Renderer @@ -72,3 +72,11 @@ - Temporary app updated successfully from the newest built teacher-composer-actions source (8af0ca7), retaining grouped introductions, empty-work behavior and all earlier overlays. Running JS is `index-BSBCYLkH.js`, CSS `index-CA1PKQZ_.css`; generated source is `/var/folders/mx/ltbgwsks5_n8zllj69p_wbcw0000gn/T/makelore-trial-tdq_nuah/code-back-toprow-33d87a81/source`. Main PID 32583/profile remain unchanged. - Actual live geometry confirms exactly one collapse control, same horizontal center, no overlap, 13px inset from the sidebar right edge and navigation above the brand. Current account, rendered messages, teacher text/panel, Work tab and all 14 drafts match the pre-refresh snapshot; all 34 recorded data files have unchanged hashes. Zero alerts. Normal runtime lease released, prior background throttling restored and the inspector opened for this refresh closed. Launch/guidance provenance now points at the combined built Renderer, including the new 163f262 overlay; separate temporary CSS injection is no longer needed for the preserved teacher-controls update. No paid validation prompt, main merge, push or formal-install change. + + +## Follow-up: Brand First + +- User corrects the order using a brand screenshot: the Makelore logo/name/caption must be at the top. Keep the return/collapse row directly beneath it with left/right alignment. This supersedes the previous interpretation that navigation comes before branding, and includes updating the same trial. +- Project Context Loaded / Planning Gate: Passed. Same-task feature ownership, branch/worktree/base verified. Entry, positioning, integrated state, decisions, architecture/domain and other previously loaded context are unchanged from 1329d23; active record and peer scopes read again. The teacher-controls task is now ready and already preserved in the trial. No semantic conflict or new shared-memory write. Only reorder existing Sidebar markup, align the existing E2E/README description, verify and refresh the latest generated Renderer with existing state preservation. + +- Reordered only the existing Sidebar brand/control blocks. Updated README and the existing Electron scenario to require brand-first positioning while keeping the same-row/right-edge, narrow-width, collapsed return and configuration-route checks. Typecheck, scoped lint, production build and the complete existing navigation scenario passed (1/1); inspected the actual default-width screenshot. No new behavior or dependency was introduced. diff --git a/README.md b/README.md index d1cf157f..8aded260 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 - macOS 窗口按钮:红色关闭当前窗口,全屏时先退出全屏再关闭;黄色最小化,绿色保留原生缩放/全屏行为。关窗后应用和进行中的后台任务继续运行,点击 Dock、托盘或菜单可重新打开窗口;`Cmd+Q` 退出应用。 - 桌面文本右键菜单:主 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`。左侧栏顶部在品牌区上方提供同排导航:左侧“← 编程 Code”返回模块选择首页,右侧收起侧栏;窄侧栏仅省略溢出的文字,折叠后的展开入口保持在标题栏;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 +- AI 编程默认入口:进入 Code 后保持在 `/chat`。左侧栏最顶部显示麦洛品牌区,其下提供同排导航:左侧“← 编程 Code”返回模块选择首页,右侧收起侧栏;窄侧栏仅省略溢出的文字,折叠后的展开入口保持在标题栏;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 - AI 编程历史与配额反馈:Pi 上下文压缩不删除用户可见的 active-branch 历史,压缩摘要保持 Main-private;Renderer 首次渲染最近 120 个节点,向上滚动时按 100 个节点追加更早内容并保持阅读位置。Main 精确识别 `token_point_balance_exhausted` / `词元点数余额不足`,只向 Renderer 投影不可重试的稳定配额错误和安全中文提示,重开会话后仍可见且不暴露上游原始错误或 request id。 - 账号词元点数:左下角账户菜单显示本人永久余额和预占,提供充值、充值订单与分页收支记录。真正新注册赠送 100 点,1 元充值 50 点,永久有效;旧账户不补送,旧会员/订阅/周额度/重置卡入口已移除。家长充值始终进入自己的钱包,青少年可查看自己的精确余额;使用他人共享钱包时仅显示可用状态,不显示付款人的余额或记录。Main 持有凭据并转发充值请求;付款结果未知时沿用原请求并核查原订单,只有服务端确认才显示到账。扫码信息缺失时可到网页账户页继续原订单,付款来源也由网页账户页管理。需配套部署 Works Square 永久点数 API 与 0094 迁移;本地模拟验收不代表真实支付通过。 - 桌面性能策略:应用窗口使用不透明浅色表面并默认保留硬件合成;仅在显式安全模式或短时间内重复 GPU 进程崩溃时启用软件渲染,并把故障原因保存在用户数据目录。启动关键路径只创建本地应用壳,认证、代理、同步、更新和遥测在首帧后延迟初始化;开发版可通过 `app:performance` 快照观察 GPU、进程、WebContents、事件循环与 Renderer Long Task 聚合指标。 diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index f6a4ad26..0d046c6c 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -414,6 +414,10 @@ export function Sidebar({ )} >
+ {workspaceLayout && } {workspaceLayout &&
{!sidebarPinnedCollapsed && ['darwin', 'win32'].includes(window.electron?.platform ?? '') && }
} - {workspaceLayout && } {!workspaceLayout && }
} diff --git a/tests/e2e/main-navigation.spec.ts b/tests/e2e/main-navigation.spec.ts index af306976..f1279cc9 100644 --- a/tests/e2e/main-navigation.spec.ts +++ b/tests/e2e/main-navigation.spec.ts @@ -90,7 +90,8 @@ test.describe('Makelore module navigation without setup flow', () => { expect(brandBox).not.toBeNull(); expect(returnBox).not.toBeNull(); expect(collapseBox).not.toBeNull(); - expect(returnBox!.y + returnBox!.height).toBeLessThanOrEqual(brandBox!.y); + expect(returnBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height); + expect(brandBox!.y - sidebarBox!.y).toBeLessThanOrEqual(8); expect(Math.abs(returnBox!.y + returnBox!.height / 2 - collapseBox!.y - collapseBox!.height / 2)).toBeLessThan(1); expect(returnBox!.x + returnBox!.width).toBeLessThanOrEqual(collapseBox!.x); expect(sidebarBox!.x + sidebarBox!.width - collapseBox!.x - collapseBox!.width).toBeLessThanOrEqual(14); From 41901ee07afc6672f925bd00e0142a5c6b9b60f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 23:34:57 +0800 Subject: [PATCH 06/17] docs: record brand-first temporary app update --- .../30-worklog/tasks/20260929-code-back-entry-33d87a81.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 47816573..45540891 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Planning +- Status: Ready for Integration ## Scope @@ -80,3 +80,5 @@ - Project Context Loaded / Planning Gate: Passed. Same-task feature ownership, branch/worktree/base verified. Entry, positioning, integrated state, decisions, architecture/domain and other previously loaded context are unchanged from 1329d23; active record and peer scopes read again. The teacher-controls task is now ready and already preserved in the trial. No semantic conflict or new shared-memory write. Only reorder existing Sidebar markup, align the existing E2E/README description, verify and refresh the latest generated Renderer with existing state preservation. - Reordered only the existing Sidebar brand/control blocks. Updated README and the existing Electron scenario to require brand-first positioning while keeping the same-row/right-edge, narrow-width, collapsed return and configuration-route checks. Typecheck, scoped lint, production build and the complete existing navigation scenario passed (1/1); inspected the actual default-width screenshot. No new behavior or dependency was introduced. + +- Updated the same live trial to `index-C-y4CmSj.js` / `index-CA1PKQZ_.css` from `code-brand-first-33d87a81/source`, preserving all published overlays. Independent source review confirms Sidebar is the sole product delta; composite typecheck/lint/build passed. Live DOM confirms brand-first at the top of the Sidebar, controls directly below, identical horizontal centers, no overlap and a 13px right inset. Current account, 14 drafts, messages, teacher panel/content and pre-refresh Operation Chat tab were restored and compare equal; all 34 recorded data hashes are unchanged and no alerts remain. Main PID 32583 is unchanged; refresh lease released and task-opened inspector closed. Launch/guidance metadata include ae646f6. No merge, remote push or formal-app changes. From 774f1b7c87306d92d99e0e5f93b6fdb26937762e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Tue, 29 Sep 2026 23:43:21 +0800 Subject: [PATCH 07/17] fix: group sidebar navigation separately from personal projects --- .../tasks/20260929-code-back-entry-33d87a81.md | 12 +++++++++++- README.md | 2 +- src/components/layout/Sidebar.tsx | 4 +++- src/pages/Chat/classroom-workspace.css | 8 +++++--- tests/e2e/main-navigation.spec.ts | 14 +++++++++++++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 45540891..593ac19f 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Ready for Integration +- Status: Planning ## Scope @@ -82,3 +82,13 @@ - Reordered only the existing Sidebar brand/control blocks. Updated README and the existing Electron scenario to require brand-first positioning while keeping the same-row/right-edge, narrow-width, collapsed return and configuration-route checks. Typecheck, scoped lint, production build and the complete existing navigation scenario passed (1/1); inspected the actual default-width screenshot. No new behavior or dependency was introduced. - Updated the same live trial to `index-C-y4CmSj.js` / `index-CA1PKQZ_.css` from `code-brand-first-33d87a81/source`, preserving all published overlays. Independent source review confirms Sidebar is the sole product delta; composite typecheck/lint/build passed. Live DOM confirms brand-first at the top of the Sidebar, controls directly below, identical horizontal centers, no overlap and a 13px right inset. Current account, 14 drafts, messages, teacher panel/content and pre-refresh Operation Chat tab were restored and compare equal; all 34 recorded data hashes are unchanged and no alerts remain. Main PID 32583 is unchanged; refresh lease released and task-opened inspector closed. Launch/guidance metadata include ae646f6. No merge, remote push or formal-app changes. + + +## Follow-up: Group Navigation And Personal Projects + +- User confirmed the hierarchy: move the brand/navigation group upward, then Code return, Works Square, a group gap, New Project, My Projects and its list. Brand stays first; collapse remains aligned on the right of Code. Public discovery belongs with navigation; creation belongs directly with personal project management. +- Project Context Loaded / Planning Gate: Passed. Same-task ownership and recorded feature branch/worktree/base are verified. Previously read entry, positioning, decisions, integrated architecture/domain and commitments remain unchanged from 41901ee. Active record and peer scopes reread; the only new peer merged teacher controls into main without touching the app. The current trial remains our combined brand-first source and all overlays. No conflicting navigation work identified. +- Plan: reduce chat-only header/brand top padding, place the existing CommunityLink directly below the Code control row, keep New Project immediately above My Projects with a larger gap between the two groups, preserve other module/config layouts and behavior. Update the existing layout/navigation verification and README, build against the current live source, and refresh the same temporary Renderer/profile. + +- Implemented the public navigation group in the non-scrolling chat header (brand, Code/collapse, Works Square), followed by a 20px group gap before New Project and an 8px projects-section margin. Reduced the chat header top padding from 8px to 0 and the brand padding from 10px/12px to 0/8px, moving its visible content upward while retaining native chrome. Other routes retain their previous ordering/styles. +- Validation: typecheck, changed-file lint, full production build and 26 relevant unit tests passed. The existing Electron navigation scenario passed with brand-at-top, Code/Works adjacency, gap before creation, New Project/My Projects adjacency, 128px no-overflow, unchanged config routes and collapsed return. Inspected screenshot; independent source review passed. Composite live-source build also passed and differs only in Sidebar and scoped classroom CSS. diff --git a/README.md b/README.md index 8aded260..4e5e03dc 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 - macOS 窗口按钮:红色关闭当前窗口,全屏时先退出全屏再关闭;黄色最小化,绿色保留原生缩放/全屏行为。关窗后应用和进行中的后台任务继续运行,点击 Dock、托盘或菜单可重新打开窗口;`Cmd+Q` 退出应用。 - 桌面文本右键菜单:主 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`。左侧栏最顶部显示麦洛品牌区,其下提供同排导航:左侧“← 编程 Code”返回模块选择首页,右侧收起侧栏;窄侧栏仅省略溢出的文字,折叠后的展开入口保持在标题栏;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 +- AI 编程默认入口:进入 Code 后保持在 `/chat`。左侧栏压缩顶部留白,最顶部显示麦洛品牌区,其下提供同排导航:左侧“← 编程 Code”返回模块选择首页,右侧收起侧栏;窄侧栏仅省略溢出的文字,折叠后的展开入口保持在标题栏。导航行下方紧接“作品广场”;与其留出分组间距后,依次为“新建项目”“我的项目”和项目列表,将公共导航与个人项目操作分组;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 - AI 编程历史与配额反馈:Pi 上下文压缩不删除用户可见的 active-branch 历史,压缩摘要保持 Main-private;Renderer 首次渲染最近 120 个节点,向上滚动时按 100 个节点追加更早内容并保持阅读位置。Main 精确识别 `token_point_balance_exhausted` / `词元点数余额不足`,只向 Renderer 投影不可重试的稳定配额错误和安全中文提示,重开会话后仍可见且不暴露上游原始错误或 request id。 - 账号词元点数:左下角账户菜单显示本人永久余额和预占,提供充值、充值订单与分页收支记录。真正新注册赠送 100 点,1 元充值 50 点,永久有效;旧账户不补送,旧会员/订阅/周额度/重置卡入口已移除。家长充值始终进入自己的钱包,青少年可查看自己的精确余额;使用他人共享钱包时仅显示可用状态,不显示付款人的余额或记录。Main 持有凭据并转发充值请求;付款结果未知时沿用原请求并核查原订单,只有服务端确认才显示到账。扫码信息缺失时可到网页账户页继续原订单,付款来源也由网页账户页管理。需配套部署 Works Square 永久点数 API 与 0094 迁移;本地模拟验收不代表真实支付通过。 - 桌面性能策略:应用窗口使用不透明浅色表面并默认保留硬件合成;仅在显式安全模式或短时间内重复 GPU 进程崩溃时启用软件渲染,并把故障原因保存在用户数据目录。启动关键路径只创建本地应用壳,认证、代理、同步、更新和遥测在首帧后延迟初始化;开发版可通过 `app:performance` 快照观察 GPU、进程、WebContents、事件循环与 Renderer Long Task 聚合指标。 diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 0d046c6c..c5228655 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -409,6 +409,7 @@ export function Sidebar({ {!isProjectConfigPage &&
}
} + {workspaceLayout && } {!workspaceLayout && } } @@ -452,7 +454,7 @@ export function Sidebar({ {!sidebarCollapsed && '新建项目'} - + {!workspaceLayout && }
); diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index c5228655..78da8009 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -415,12 +415,8 @@ export function Sidebar({ )} >
- {workspaceLayout && } {workspaceLayout &&
- + {!sidebarPinnedCollapsed && ['darwin', 'win32'].includes(window.electron?.platform ?? '') && }
} + {workspaceLayout && } {workspaceLayout && } {!workspaceLayout && }
diff --git a/tests/e2e/main-navigation.spec.ts b/tests/e2e/main-navigation.spec.ts index 6fe5e1a0..ecf39be1 100644 --- a/tests/e2e/main-navigation.spec.ts +++ b/tests/e2e/main-navigation.spec.ts @@ -80,8 +80,9 @@ test.describe('Makelore module navigation without setup flow', () => { const sidebar = page.getByTestId('sidebar'); const moduleReturn = sidebar.getByTestId('sidebar-module-switcher-trigger'); await expect(moduleReturn).toBeVisible(); - await expect(moduleReturn).toContainText('编程 Code'); + await expect(moduleReturn).toHaveText(''); await expect(moduleReturn).toHaveAttribute('aria-label', '返回首页'); + await expect(moduleReturn).toHaveAttribute('title', '返回首页'); await expect(sidebar.getByTestId('sidebar-module-return-icon')).toBeVisible(); const brandBox = await sidebar.locator('.classroom-brand').boundingBox(); const returnBox = await moduleReturn.boundingBox(); @@ -90,8 +91,9 @@ test.describe('Makelore module navigation without setup flow', () => { expect(brandBox).not.toBeNull(); expect(returnBox).not.toBeNull(); expect(collapseBox).not.toBeNull(); - expect(returnBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height); - expect(brandBox!.y - sidebarBox!.y).toBeLessThanOrEqual(1); + expect(returnBox!.y + returnBox!.height).toBeLessThanOrEqual(brandBox!.y); + expect(returnBox!.y - sidebarBox!.y).toBeLessThanOrEqual(4); + expect(returnBox!.width).toBe(32); expect(Math.abs(returnBox!.y + returnBox!.height / 2 - collapseBox!.y - collapseBox!.height / 2)).toBeLessThan(1); expect(returnBox!.x + returnBox!.width).toBeLessThanOrEqual(collapseBox!.x); expect(sidebarBox!.x + sidebarBox!.width - collapseBox!.x - collapseBox!.width).toBeLessThanOrEqual(14); @@ -101,8 +103,8 @@ test.describe('Makelore module navigation without setup flow', () => { const communityBox = await community.boundingBox(); const createBox = await sidebar.getByTestId('sidebar-create-project').boundingBox(); const headingBox = await sidebar.locator('.classroom-projects-heading').boundingBox(); - expect(communityBox!.y).toBeGreaterThanOrEqual(returnBox!.y + returnBox!.height); - expect(communityBox!.y - returnBox!.y - returnBox!.height).toBeLessThanOrEqual(8); + expect(communityBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height); + expect(communityBox!.y - brandBox!.y - brandBox!.height).toBeLessThanOrEqual(8); expect(createBox!.y - communityBox!.y - communityBox!.height).toBeGreaterThanOrEqual(16); expect(headingBox!.y).toBeGreaterThanOrEqual(createBox!.y + createBox!.height); expect(headingBox!.y - createBox!.y - createBox!.height).toBeLessThanOrEqual(14); From 7830b7f398b39d483c9371292a906b7bf42fa891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 30 Sep 2026 00:12:49 +0800 Subject: [PATCH 11/17] Document verified icon-only trial navigation update --- .../30-worklog/tasks/20260929-code-back-entry-33d87a81.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 65dfcb5e..f5fbcaf6 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Planning +- Status: Ready for Integration ## Scope @@ -34,7 +34,7 @@ ## Verification - pnpm 10.33.4 standard typecheck, changed-file ESLint and production Renderer/Main/Preload/utility build passed. Existing sidebar/peek/module gate and module-navigation unit coverage: 23 tests across four files passed. -- Extended one existing navigation scenario; isolated Electron E2E 1/1 passed. It checks Code return beneath the brand, no sidebar entry on configuration or plugin subpage, configuration back to chat, and collapsed-sidebar hover followed by real navigation to `/module-select`. Used an independent test profile/ports; no real user application or conversations were changed. Test command: `corepack pnpm exec playwright test tests/e2e/main-navigation.spec.ts --grep 'opens remembered Code projects' --reporter=list`. +- Extended one existing navigation scenario; isolated Electron E2E 1/1 passed. It now checks icon-only return above the brand, no sidebar entry on configuration or plugin subpage, configuration back to chat, and collapsed-sidebar hover followed by real navigation to `/module-select`. Used an independent test profile/ports; no real user application or conversations were changed. Test command: `corepack pnpm exec playwright test tests/e2e/main-navigation.spec.ts --grep 'opens remembered Code projects' --reporter=list`. - Inspected chat and configuration screenshots; delegated review also checked collapsed peek. Artifacts are under `test-results/main-navigation-Makelore-m-9ce56-instead-of-project-settings/`. Independent code review passed; no additional regression found. Whitespace and task-doc drift checks passed. ## Follow-ups @@ -109,3 +109,7 @@ - Implemented an icon-only 32px ModuleSwitcher in the top Code toolbar above branding. Existing return target, accessible name/tooltip, right collapse alignment, Works Square and personal-project grouping are retained. Updated README and the existing navigation scenario. Typecheck, scoped lint, production build and 26 unit tests passed; the existing Electron scenario passed (1/1) with icon text absence, 32px geometry, above-brand/top placement, 128px width, config/nested absence and collapsed hover return. Default-width screenshot inspected; independent review passed. - Trial preparation detected a newer teacher-text-depth publication while our candidate was being validated. Rebase the generated candidate onto the actually served source before updating; no live files changed by this task yet. + +- Final temporary publication preserves the newer colored-shortcut update b934945 (including depth update 83a19fb). A second publication precondition correctly rejected finalization after the peer replaced the served entry; rebuilt from its actual published source and only reapplied Sidebar + ModuleSwitcher. Final generated source is `sidebar-icon-top-33d87a81/source-with-text-colors` under the trial root, running/next JS `index-CKAsiRqM.js`, common CSS `index-CA1PKQZ_.css`, route CSS `index-D03AC39p.css`. Composite typecheck/lint/build and full source comparison passed. Launch/guidance now consistently point at this source and include 19d55e8 plus the preserved style overlays. +- Live geometry verifies icon-only 32px return above branding at the Sidebar top, accessible name/tooltip “返回首页”, one right collapse control with equal horizontal centers and a 13px right inset, Works Square beneath branding, and the unchanged 20px separation before New Project/My Projects. Current account, active conversation, 14 consultation drafts, teacher panel/content and Work tab match the pre-refresh snapshot. Virtualization renders the last 14 of the previous 22 message elements; that suffix is exactly equal and all 34 persisted data-file hashes are unchanged. No alert or model request. Main PID 32583, isolated profile, native visibility/focus and formal installation are unchanged. Refresh lease released, original background throttling restored, task-opened inspector closed. +- Coordination lesson / promotion candidate: future temporary-app publishing should validate both served index and manifest before replacing either, then finalize their provenance together. A peer can hot-swap CSS and overwrite next-load assets while leaving running-asset metadata stale; candidate preparation must use the actual served source and preserve all published overlays. Evidence: both guarded finalization attempts in this follow-up and the final successful composite; impacts only future deployment tooling, no canonical write or policy change performed here. From b4c06e824748e1c4f5d164a73b1282ff02882752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 30 Sep 2026 00:21:32 +0800 Subject: [PATCH 12/17] Refine sidebar control spacing and Works Square surface --- .../tasks/20260929-code-back-entry-33d87a81.md | 11 ++++++++++- README.md | 2 +- src/pages/Chat/classroom-workspace.css | 6 ++++-- tests/e2e/main-navigation.spec.ts | 9 ++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index f5fbcaf6..c7683fc0 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Ready for Integration +- Status: Planning ## Scope @@ -113,3 +113,12 @@ - Final temporary publication preserves the newer colored-shortcut update b934945 (including depth update 83a19fb). A second publication precondition correctly rejected finalization after the peer replaced the served entry; rebuilt from its actual published source and only reapplied Sidebar + ModuleSwitcher. Final generated source is `sidebar-icon-top-33d87a81/source-with-text-colors` under the trial root, running/next JS `index-CKAsiRqM.js`, common CSS `index-CA1PKQZ_.css`, route CSS `index-D03AC39p.css`. Composite typecheck/lint/build and full source comparison passed. Launch/guidance now consistently point at this source and include 19d55e8 plus the preserved style overlays. - Live geometry verifies icon-only 32px return above branding at the Sidebar top, accessible name/tooltip “返回首页”, one right collapse control with equal horizontal centers and a 13px right inset, Works Square beneath branding, and the unchanged 20px separation before New Project/My Projects. Current account, active conversation, 14 consultation drafts, teacher panel/content and Work tab match the pre-refresh snapshot. Virtualization renders the last 14 of the previous 22 message elements; that suffix is exactly equal and all 34 persisted data-file hashes are unchanged. No alert or model request. Main PID 32583, isolated profile, native visibility/focus and formal installation are unchanged. Refresh lease released, original background throttling restored, task-opened inspector closed. - Coordination lesson / promotion candidate: future temporary-app publishing should validate both served index and manifest before replacing either, then finalize their provenance together. A peer can hot-swap CSS and overwrite next-load assets while leaving running-asset metadata stale; candidate preparation must use the actual served source and preserve all published overlays. Evidence: both guarded finalization attempts in this follow-up and the final successful composite; impacts only future deployment tooling, no canonical write or policy change performed here. + + +## Follow-up: Sidebar Spacing And Works Square Surface + +- User accepts icon-only ordering and asks to move the control row upward without moving the Makelore brand, then add a filled Works Square entry and place it centrally between the brand and New Project. Temporary-app authorization continues from this UI iteration. +- Project Context Loaded / Planning Gate: Passed. Task 20260929-code-back-entry-33d87a81, feature mode, same branch/worktree/base as Identity; official resume and matching status verified. Entry, memory index, active task and peer scopes read; positioning, integrated state, decisions, architecture/domain, evidence/reflection and commitments unchanged from 7830b7f. The retired shortcut task has an unreadable record but its replacement merge task confirms source b934945 and no live-app edits; coordination state is provisional and unrelated to sidebar decisions. Composer-height task may update the trial, so preserve the newest served source and compare entry/manifest before publishing. No shared canonical writes. +- Existing product remains the single light classroom system. The design skill database's dark-theme suggestion conflicts with the current product and is not adopted; retain established green community tokens and native controls. Review confirms double overflow clipping permits a 4px upward move of the full 32px controls within the existing 40px slot. Keep that slot and the brand fixed, align controls to its top. Add 10px above Works Square and reduce project-content top padding from 20px to 10px, keeping New Project fixed with equal gaps around the entry. Only scoped classroom CSS and existing E2E/README need changes. All presentation changes can be applied through the existing route stylesheet, enabling a state-preserving live stylesheet swap. No navigation, prompt, data, main-merge or formal-install changes. + +- Implementation is one scoped CSS change: top-align the existing 32px controls in the unchanged 40px slot; use a pale-green filled/bordered Works Square button with 10px above and below. Brand and New Project coordinates stay fixed. Typecheck, changed-file lint and production build passed. The existing Electron navigation scenario passed (1/1), checking exact top/brand coordinates, equal 10px gaps, fill color, 128px no-overflow, configuration exclusion and collapsed return; default and narrow screenshots inspected. No new tests added. Preparing the same CSS-only change against the now-published composer-height trial source. diff --git a/README.md b/README.md index 2b6be659..295b2960 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 - macOS 窗口按钮:红色关闭当前窗口,全屏时先退出全屏再关闭;黄色最小化,绿色保留原生缩放/全屏行为。关窗后应用和进行中的后台任务继续运行,点击 Dock、托盘或菜单可重新打开窗口;`Cmd+Q` 退出应用。 - 桌面文本右键菜单:主 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 编程默认入口:进入 Code 后保持在 `/chat`。左侧栏顶部提供同排图标:左侧仅显示返回箭头,点击返回模块选择首页,右侧收起侧栏;返回按钮保留“返回首页”提示,折叠后的展开入口保持在标题栏。图标行向上对齐,麦洛品牌位置保持不变;浅绿色底的“作品广场”位于品牌与“新建项目”之间,上下各留 10px;“我的项目”和项目列表紧接“新建项目”,将公共导航与个人项目操作分组;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 - AI 编程历史与配额反馈:Pi 上下文压缩不删除用户可见的 active-branch 历史,压缩摘要保持 Main-private;Renderer 首次渲染最近 120 个节点,向上滚动时按 100 个节点追加更早内容并保持阅读位置。Main 精确识别 `token_point_balance_exhausted` / `词元点数余额不足`,只向 Renderer 投影不可重试的稳定配额错误和安全中文提示,重开会话后仍可见且不暴露上游原始错误或 request id。 - 账号词元点数:左下角账户菜单显示本人永久余额和预占,提供充值、充值订单与分页收支记录。真正新注册赠送 100 点,1 元充值 50 点,永久有效;旧账户不补送,旧会员/订阅/周额度/重置卡入口已移除。家长充值始终进入自己的钱包,青少年可查看自己的精确余额;使用他人共享钱包时仅显示可用状态,不显示付款人的余额或记录。Main 持有凭据并转发充值请求;付款结果未知时沿用原请求并核查原订单,只有服务端确认才显示到账。扫码信息缺失时可到网页账户页继续原订单,付款来源也由网页账户页管理。需配套部署 Works Square 永久点数 API 与 0094 迁移;本地模拟验收不代表真实支付通过。 - 桌面性能策略:应用窗口使用不透明浅色表面并默认保留硬件合成;仅在显式安全模式或短时间内重复 GPU 进程崩溃时启用软件渲染,并把故障原因保存在用户数据目录。启动关键路径只创建本地应用壳,认证、代理、同步、更新和遥测在首帧后延迟初始化;开发版可通过 `app:performance` 快照观察 GPU、进程、WebContents、事件循环与 Renderer Long Task 聚合指标。 diff --git a/src/pages/Chat/classroom-workspace.css b/src/pages/Chat/classroom-workspace.css index 3ae0fa2f..de12a66e 100644 --- a/src/pages/Chat/classroom-workspace.css +++ b/src/pages/Chat/classroom-workspace.css @@ -6,12 +6,14 @@ [data-testid="main-layout"][data-classroom="true"] [data-testid="titlebar-sidebar-surface"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; } [data-testid="sidebar"][data-classroom="true"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; color: #26384d; } [data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header { padding-top: 0; padding-bottom: 0; } +[data-testid="sidebar"][data-classroom="true"] [data-testid="classroom-sidebar-navigation"] { align-items: flex-start; } [data-testid="sidebar"][data-classroom="true"] .classroom-brand { gap: 10px; padding: 0 2px 8px; } -[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"] { min-height: 36px; margin-top: 0; gap: 4px; padding: 6px 4px; } +[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"] { min-height: 36px; margin-top: 10px; gap: 4px; padding: 6px 4px; border-color: #cfddc3; border-radius: 11px; background: #e8f0df; } +[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"]:hover:not(:disabled) { background: #dfeacf; border-color: #b7c6a4; } [data-testid="sidebar"][data-classroom="true"] .classroom-brand:hover { background: #eaf1f8; } .classroom-brand-mark { display: grid; place-items: center; width: 40px; height: 42px; flex-shrink: 0; border: 1px solid #f1d1aa; border-radius: 12px; background: #fff0dd; box-shadow: 0 3px 0 #ecdbc5; } .classroom-brand-caption { display: block; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: #64758a; } -[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-content { padding: 20px 12px 14px; } +[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-content { padding: 10px 12px 14px; } [data-testid="sidebar"][data-classroom="true"] [data-testid="sidebar-create-project"] { min-height: 41px; gap: 9px; padding: 8px 11px; border: 1px solid #d5e1ed; border-radius: 11px; background: #fff; color: #304968; box-shadow: 0 3px 0 #e3eaf3; } [data-testid="sidebar"][data-classroom="true"] [data-testid="sidebar-create-project"] svg { color: #c44b23; } [data-testid="sidebar"][data-classroom="true"] [data-testid="sidebar-create-project"]:hover { background: #fff7ef; border-color: #ecc5a5; } diff --git a/tests/e2e/main-navigation.spec.ts b/tests/e2e/main-navigation.spec.ts index ecf39be1..5d1e081f 100644 --- a/tests/e2e/main-navigation.spec.ts +++ b/tests/e2e/main-navigation.spec.ts @@ -92,7 +92,8 @@ test.describe('Makelore module navigation without setup flow', () => { expect(returnBox).not.toBeNull(); expect(collapseBox).not.toBeNull(); expect(returnBox!.y + returnBox!.height).toBeLessThanOrEqual(brandBox!.y); - expect(returnBox!.y - sidebarBox!.y).toBeLessThanOrEqual(4); + expect(returnBox!.y - sidebarBox!.y).toBe(0); + expect(brandBox!.y - sidebarBox!.y).toBe(40); expect(returnBox!.width).toBe(32); expect(Math.abs(returnBox!.y + returnBox!.height / 2 - collapseBox!.y - collapseBox!.height / 2)).toBeLessThan(1); expect(returnBox!.x + returnBox!.width).toBeLessThanOrEqual(collapseBox!.x); @@ -104,8 +105,10 @@ test.describe('Makelore module navigation without setup flow', () => { const createBox = await sidebar.getByTestId('sidebar-create-project').boundingBox(); const headingBox = await sidebar.locator('.classroom-projects-heading').boundingBox(); expect(communityBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height); - expect(communityBox!.y - brandBox!.y - brandBox!.height).toBeLessThanOrEqual(8); - expect(createBox!.y - communityBox!.y - communityBox!.height).toBeGreaterThanOrEqual(16); + expect(communityBox!.y - brandBox!.y - brandBox!.height).toBe(10); + expect(createBox!.y - communityBox!.y - communityBox!.height).toBe(10); + expect(createBox!.y - brandBox!.y - brandBox!.height).toBe(56); + await expect(community).toHaveCSS('background-color', 'rgb(232, 240, 223)'); expect(headingBox!.y).toBeGreaterThanOrEqual(createBox!.y + createBox!.height); expect(headingBox!.y - createBox!.y - createBox!.height).toBeLessThanOrEqual(14); await page.screenshot({ path: test.info().outputPath('chat-code-return-entry.png') }); From 9d47cd2146b4e28f5cc8663aeee7d3c2dace9bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 30 Sep 2026 00:25:00 +0800 Subject: [PATCH 13/17] Record verified sidebar spacing trial update --- .../30-worklog/tasks/20260929-code-back-entry-33d87a81.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index c7683fc0..9801247e 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Planning +- Status: Ready for Integration ## Scope @@ -122,3 +122,6 @@ - Existing product remains the single light classroom system. The design skill database's dark-theme suggestion conflicts with the current product and is not adopted; retain established green community tokens and native controls. Review confirms double overflow clipping permits a 4px upward move of the full 32px controls within the existing 40px slot. Keep that slot and the brand fixed, align controls to its top. Add 10px above Works Square and reduce project-content top padding from 20px to 10px, keeping New Project fixed with equal gaps around the entry. Only scoped classroom CSS and existing E2E/README need changes. All presentation changes can be applied through the existing route stylesheet, enabling a state-preserving live stylesheet swap. No navigation, prompt, data, main-merge or formal-install changes. - Implementation is one scoped CSS change: top-align the existing 32px controls in the unchanged 40px slot; use a pale-green filled/bordered Works Square button with 10px above and below. Brand and New Project coordinates stay fixed. Typecheck, changed-file lint and production build passed. The existing Electron navigation scenario passed (1/1), checking exact top/brand coordinates, equal 10px gaps, fill color, 128px no-overflow, configuration exclusion and collapsed return; default and narrow screenshots inspected. No new tests added. Preparing the same CSS-only change against the now-published composer-height trial source. + +- Updated the live trial using a route-stylesheet swap, with no Renderer reload or Main restart. Candidate based on the finalized composer-height source 66cad87, retaining colored shortcuts and the full icon-only navigation. Sole source difference is the sidebar CSS from b4c06e8; composite typecheck/PostCSS production build and independent code review passed. Final source: `sidebar-spacing-33d87a81/source`; active route CSS `index-CjmETxm0.css`, existing running JS `index-CEjIKC5e.js`, next-load JS `index-BU5aR8dW.js`, common CSS `index-CDJ7LmpW.css`. Served index and launch/guidance provenance are updated together, distinguishing running JS from prepared next-load JS. +- Live before/after geometry confirms both controls moved up exactly 4px, brand and New Project boxes identical, Works Square down 10px with 10px gaps above/below and the requested filled background. Controls remain aligned and unclipped. All local storage, messages, input drafts, teacher content, selected tab, DOM focus, native visibility/focus and 34 recorded data-file hashes are unchanged; zero alerts. Main PID 32583/profile retained; inspector opened for this update was closed. Temporary app is ready; no main merge, push or formal-app modification. From 883ae1d788b204f569f12dbc6b172de4f17d9cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 30 Sep 2026 00:39:24 +0800 Subject: [PATCH 14/17] Align Code navigation and branding with workspace header rows --- .../20260929-code-back-entry-33d87a81.md | 18 +++- README.md | 2 +- src/components/layout/Sidebar.tsx | 13 +-- src/components/layout/TitleBar.tsx | 39 ++++++--- src/components/layout/titlebar-metrics.ts | 9 ++ src/pages/Chat/CodingConversationHeader.tsx | 4 +- src/pages/Chat/classroom-workspace.css | 8 +- tests/e2e/main-navigation.spec.ts | 40 +++++++-- tests/unit/coding-feature-ui.test.tsx | 2 +- tests/unit/title-bar.test.tsx | 84 +++++++++++++++++-- 10 files changed, 169 insertions(+), 50 deletions(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 9801247e..79996cc1 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,11 +8,11 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Ready for Integration +- Status: Planning ## Scope -- Move the existing “编程 Code” module-home return entry from the project configuration sidebar into the Coding chat/workspace sidebar as an icon-only arrow in the top navigation row above the brand. Retain its `/module-select` target and the configuration page's own back action. +- Move the existing “编程 Code” module-home return entry from the project configuration sidebar into the Coding workspace titlebar as an icon-only arrow aligned with the conversation header, with the brand aligned to the tabs below. Retain its `/module-select` target and the configuration page's own back action. ## Intent And Constraints @@ -29,7 +29,7 @@ ## Outcome -- Reused the existing ModuleSwitcher as an icon-only arrow at the left of the top navigation row above the brand in the `/chat` sidebar, aligned with the collapse control on the right, retaining its accessible return-home label and `/module-select` action. Project configuration and nested resource pages omit the entire sidebar header block, so no empty placeholder remains. Existing configuration back action, sidebar resizing/peek and other module controls are unchanged. README synchronized. +- Reused the existing ModuleSwitcher as an icon-only arrow in the `/chat` titlebar, aligned with the collapse control and the right-side conversation header, retaining its accessible return-home label and `/module-select` action. Project configuration and nested resource pages omit the entire sidebar header block, so no empty placeholder remains. Existing configuration back action, sidebar resizing/peek and other module controls are unchanged. README synchronized. ## Verification @@ -43,7 +43,7 @@ ## Promotion Candidates -- Target: canonical classroom navigation when next integrated. Proposal: the icon-only module-home return is visible above the chat sidebar brand, aligned with the right-side collapse control and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. +- Target: canonical classroom navigation when next integrated. Proposal: the icon-only module-home return and collapse control align with the conversation header, with branding aligned to the tabs below and omitted from project configuration/resource sidebars; original configuration back remains. Evidence: explicit user confirmation, existing component reuse, actual Electron navigation and screenshots. No architecture or module-target change; no further product confirmation required. ## Follow-up: Update The Temporary Renderer @@ -125,3 +125,13 @@ - Updated the live trial using a route-stylesheet swap, with no Renderer reload or Main restart. Candidate based on the finalized composer-height source 66cad87, retaining colored shortcuts and the full icon-only navigation. Sole source difference is the sidebar CSS from b4c06e8; composite typecheck/PostCSS production build and independent code review passed. Final source: `sidebar-spacing-33d87a81/source`; active route CSS `index-CjmETxm0.css`, existing running JS `index-CEjIKC5e.js`, next-load JS `index-BU5aR8dW.js`, common CSS `index-CDJ7LmpW.css`. Served index and launch/guidance provenance are updated together, distinguishing running JS from prepared next-load JS. - Live before/after geometry confirms both controls moved up exactly 4px, brand and New Project boxes identical, Works Square down 10px with 10px gaps above/below and the requested filled background. Controls remain aligned and unclipped. All local storage, messages, input drafts, teacher content, selected tab, DOM focus, native visibility/focus and 34 recorded data-file hashes are unchanged; zero alerts. Main PID 32583/profile retained; inspector opened for this update was closed. Temporary app is ready; no main merge, push or formal-app modification. + + +## Follow-up: Align Sidebar With The Two Workspace Header Rows + +- User now explicitly asks for the return/collapse icons to align with the right-side project/conversation header, the Makelore brand to align with Operation Chat/Work tabs, and a lighter Works Square button matching New Project styling with equal spacing. This supersedes keeping the brand at its former lower position. Continue the authorized temporary-app update. +- Project Context Loaded / Planning Gate: Passed. Official same-task feature resume verified exact Identity fields; active record, entry/memory index and peer scopes read. Shared positioning, decisions, architecture/domain, evidence/reflection and commitments remain unchanged from 9d47cd2. Composer height is now published and ready; a separate merge task touches only main/cleanup and excludes live-app updates. Current published trial source is sidebar-spacing-33d87a81/source with all 13 overlays, so no semantic conflict. Task owns navigation presentation and focused regression/docs only; preserve light theme, native window controls, local projects, conversations and teacher features. +- Plan: move chat return/collapse into the native-titlebar-height navigation rail, using shared safe-width math for the right conversation header. macOS reserves 88px for traffic lights and a minimum 168px for the full pair; Windows minimum 88px. Keep both controls accessible while collapsed and stable during peek; the sidebar itself still supports 128px. Remove the redundant chat-sidebar row on platforms with a custom titlebar, retain Linux fallback, align a 46px brand block to the 46px tabs, and match Works Square to New Project's 41px rounded/bordered button with a very pale fill and equal 10px gaps. Validate platform controls, collapse/peek, narrow widths and actual two-row geometry, then build the latest composite and update the existing trial. + +- Implemented the two-control titlebar rail and shared safe-width helper; removed the redundant sidebar row on macOS/Windows while keeping Linux fallback. The brand is 46px tall, aligned to the tabs. Works Square is a 41px very pale green button matching New Project border/radius/padding/font/shadow, with equal 10px gaps. Keyboard focus moves between the titlebar buttons without closing the sidebar preview. +- Verification: typecheck, scoped ESLint and production build passed. Titlebar/platform/header/peek unit coverage: 33 tests in 3 files passed. Existing Electron navigation E2E passed (1/1), verifying both horizontal alignments, exact matching button styles, 128px sidebar with reserved topbar space, actual return hit testing, configuration/nested absence, stable collapsed hover geometry and real return navigation. Default and narrow screenshots inspected. Composite build/typecheck/lint passed; full source comparison confirms only the five intended product files changed and all teacher/composer overlays retained. Temporary publication pending below. diff --git a/README.md b/README.md index 295b2960..a4a31701 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 - macOS 窗口按钮:红色关闭当前窗口,全屏时先退出全屏再关闭;黄色最小化,绿色保留原生缩放/全屏行为。关窗后应用和进行中的后台任务继续运行,点击 Dock、托盘或菜单可重新打开窗口;`Cmd+Q` 退出应用。 - 桌面文本右键菜单:主 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`。左侧栏顶部提供同排图标:左侧仅显示返回箭头,点击返回模块选择首页,右侧收起侧栏;返回按钮保留“返回首页”提示,折叠后的展开入口保持在标题栏。图标行向上对齐,麦洛品牌位置保持不变;浅绿色底的“作品广场”位于品牌与“新建项目”之间,上下各留 10px;“我的项目”和项目列表紧接“新建项目”,将公共导航与个人项目操作分组;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 +- AI 编程默认入口:进入 Code 后保持在 `/chat`。顶部返回箭头与侧栏收起图标同排,与右侧项目/会话名称对齐;折叠时保留返回和展开入口。macOS 为原生窗口按钮保留安全空间,窄侧栏仍可使用。下方麦洛品牌与“操作对话/作品”标签行对齐;更浅底色的“作品广场”与“新建项目”采用相同高度、圆角、边框与阴影,并位于品牌和新建项目之间,上下各留 10px;“我的项目”和项目列表紧接新建项目;项目配置及其资源子页不再重复显示此侧栏入口,保留配置页自身的返回操作。尚未选择本地项目时,页面以麦洛 M 标识和居中引导语建立焦点,下方提供独立的“新增项目”主操作以及可横向浏览、直接进入的已有项目卡片;项目创建和进入仍复用侧栏现有流程。已有项目的新 Conversation 在可编辑 Composer 上方沿用同一品牌引导,第一条消息出现后切换为正常时间线。 - AI 编程历史与配额反馈:Pi 上下文压缩不删除用户可见的 active-branch 历史,压缩摘要保持 Main-private;Renderer 首次渲染最近 120 个节点,向上滚动时按 100 个节点追加更早内容并保持阅读位置。Main 精确识别 `token_point_balance_exhausted` / `词元点数余额不足`,只向 Renderer 投影不可重试的稳定配额错误和安全中文提示,重开会话后仍可见且不暴露上游原始错误或 request id。 - 账号词元点数:左下角账户菜单显示本人永久余额和预占,提供充值、充值订单与分页收支记录。真正新注册赠送 100 点,1 元充值 50 点,永久有效;旧账户不补送,旧会员/订阅/周额度/重置卡入口已移除。家长充值始终进入自己的钱包,青少年可查看自己的精确余额;使用他人共享钱包时仅显示可用状态,不显示付款人的余额或记录。Main 持有凭据并转发充值请求;付款结果未知时沿用原请求并核查原订单,只有服务端确认才显示到账。扫码信息缺失时可到网页账户页继续原订单,付款来源也由网页账户页管理。需配套部署 Works Square 永久点数 API 与 0094 迁移;本地模拟验收不代表真实支付通过。 - 桌面性能策略:应用窗口使用不透明浅色表面并默认保留硬件合成;仅在显式安全模式或短时间内重复 GPU 进程崩溃时启用软件渲染,并把故障原因保存在用户数据目录。启动关键路径只创建本地应用壳,认证、代理、同步、更新和遥测在首帧后延迟初始化;开发版可通过 `app:performance` 快照观察 GPU、进程、WebContents、事件循环与 Renderer Long Task 聚合指标。 diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 78da8009..874529f0 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -11,7 +11,6 @@ import { FolderKanban, Gamepad2, LogOut, - PanelLeftClose, Plus, RadioTower, Settings as SettingsIcon, @@ -91,7 +90,6 @@ export function Sidebar({ }: SidebarProps = {}) { const { t } = useTranslation('common'); const storedSidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed); - const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed); const sidebarWidth = useSettingsStore((state) => normalizeClassroomSidebarWidth(state.classroomSidebarWidth)); const sidebarPinnedCollapsed = !forceExpanded && storedSidebarCollapsed; const sidebarCollapsed = sidebarPinnedCollapsed && !sidebarPeekOpen; @@ -415,17 +413,8 @@ export function Sidebar({ )} >
- {workspaceLayout &&
+ {workspaceLayout && !['darwin', 'win32'].includes(window.electron?.platform ?? '') &&
- {!sidebarPinnedCollapsed && ['darwin', 'win32'].includes(window.electron?.platform ?? '') && }
} {workspaceLayout && @@ -110,10 +112,23 @@ function ProductTitleBar({ data-testid="titlebar-sidebar-toggle-layer" className={cn( 'no-drag pointer-events-auto fixed left-0 top-0 z-[400] h-10', - nativeTrafficLights ? 'w-[132px]' : 'w-[52px]', + !workspaceLayout && (nativeTrafficLights ? 'w-[132px]' : 'w-[52px]'), )} + style={workspaceLayout ? { width: classroomTitlebarWidth } : undefined} + onPointerEnter={workspaceLayout ? () => handleSidebarPeekEnter('titlebar') : undefined} + onPointerLeave={workspaceLayout ? () => handleSidebarPeekLeave('titlebar') : undefined} + onFocusCapture={workspaceLayout ? () => handleSidebarPeekEnter('focus') : undefined} + onBlurCapture={workspaceLayout ? (event) => { + if (!event.currentTarget.contains(event.relatedTarget as Node | null)) handleSidebarPeekLeave('focus'); + } : undefined} > - {sidebarToggle} + {workspaceLayout ?
+ + {sidebarToggle} +
: sidebarToggle}
); const titlebarLogo = ( @@ -155,16 +170,16 @@ function ProductTitleBar({ <>
handleSidebarPeekEnter('titlebar') : undefined} onPointerLeave={sidebarPreviewTitlebarVisible ? () => handleSidebarPeekLeave('titlebar') : undefined} /> - {(!workspaceLayout || sidebarCollapsed) && (typeof document !== 'undefined' ? createPortal(sidebarTogglePortal, document.body) : sidebarTogglePortal)} + {typeof document !== 'undefined' ? createPortal(sidebarTogglePortal, document.body) : sidebarTogglePortal} ) : null} {integrated ? ( diff --git a/src/components/layout/titlebar-metrics.ts b/src/components/layout/titlebar-metrics.ts index 8dd8037d..60884449 100644 --- a/src/components/layout/titlebar-metrics.ts +++ b/src/components/layout/titlebar-metrics.ts @@ -1,4 +1,13 @@ +import { normalizeClassroomSidebarWidth } from '@/lib/classroom-layout'; + export const TITLEBAR_LOGO_WIDTH = 96; // Three 44px buttons plus the control container's 8px padding on each side. export const WINDOWS_TITLEBAR_CONTROLS_WIDTH = 148; + +/** Keep the Code navigation and conversation header outside each other's hit areas. */ +export function getClassroomTitlebarWidth(platform: string | undefined, sidebarCollapsed: boolean, sidebarWidth: number): number { + // Two 32px controls, an 8px gap/right inset, and the native traffic-light safe area. + const minimum = platform === 'darwin' ? 168 : 88; + return sidebarCollapsed ? minimum : Math.max(minimum, normalizeClassroomSidebarWidth(sidebarWidth)); +} diff --git a/src/pages/Chat/CodingConversationHeader.tsx b/src/pages/Chat/CodingConversationHeader.tsx index 3e077f17..140c3b21 100644 --- a/src/pages/Chat/CodingConversationHeader.tsx +++ b/src/pages/Chat/CodingConversationHeader.tsx @@ -14,7 +14,7 @@ import { CodingConversationRenameDialog } from './CodingConversationRenameDialog import { cn } from '@/lib/utils'; import { useSettingsStore } from '@/stores/settings'; import { normalizeClassroomSidebarWidth } from '@/lib/classroom-layout'; -import { WINDOWS_TITLEBAR_CONTROLS_WIDTH } from '@/components/layout/titlebar-metrics'; +import { getClassroomTitlebarWidth, WINDOWS_TITLEBAR_CONTROLS_WIDTH } from '@/components/layout/titlebar-metrics'; import type { ConversationSnapshot } from '@/types/coding-conversation'; import type { CodingConversationMetadata } from '@/types/coding-project'; @@ -189,7 +189,7 @@ export function CodingConversationHeader({ className="pointer-events-none fixed right-0 top-0 z-[80] h-10" data-testid="coding-conversation-header-titlebar" style={{ - left: `${sidebarCollapsed ? (platform === 'darwin' ? 132 : 52) : Math.max(sidebarWidth, platform === 'darwin' ? 132 : 0)}px`, + left: `${getClassroomTitlebarWidth(platform, sidebarCollapsed, sidebarWidth)}px`, // Keep both actions and their hit area outside the product chrome. right: platform === 'win32' ? WINDOWS_TITLEBAR_CONTROLS_WIDTH : 0, }} diff --git a/src/pages/Chat/classroom-workspace.css b/src/pages/Chat/classroom-workspace.css index de12a66e..c6e3c7e7 100644 --- a/src/pages/Chat/classroom-workspace.css +++ b/src/pages/Chat/classroom-workspace.css @@ -6,10 +6,10 @@ [data-testid="main-layout"][data-classroom="true"] [data-testid="titlebar-sidebar-surface"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; } [data-testid="sidebar"][data-classroom="true"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; color: #26384d; } [data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header { padding-top: 0; padding-bottom: 0; } -[data-testid="sidebar"][data-classroom="true"] [data-testid="classroom-sidebar-navigation"] { align-items: flex-start; } -[data-testid="sidebar"][data-classroom="true"] .classroom-brand { gap: 10px; padding: 0 2px 8px; } -[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"] { min-height: 36px; margin-top: 10px; gap: 4px; padding: 6px 4px; border-color: #cfddc3; border-radius: 11px; background: #e8f0df; } -[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"]:hover:not(:disabled) { background: #dfeacf; border-color: #b7c6a4; } +[data-testid="sidebar"][data-classroom="true"] [data-testid="classroom-sidebar-navigation"] { align-items: center; } +[data-testid="sidebar"][data-classroom="true"] .classroom-brand { height: 46px; gap: 10px; padding: 0 2px; } +[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"] { min-height: 41px; margin-top: 10px; gap: 9px; padding: 8px 11px; border: 1px solid #d5e1ed; border-radius: 11px; background: #f8faf5; box-shadow: 0 3px 0 #e3eaf3; font-size: 14px; font-weight: 500; line-height: 20px; white-space: normal; } +[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"]:hover:not(:disabled) { background: #eef3e7; border-color: #b7c6a4; } [data-testid="sidebar"][data-classroom="true"] .classroom-brand:hover { background: #eaf1f8; } .classroom-brand-mark { display: grid; place-items: center; width: 40px; height: 42px; flex-shrink: 0; border: 1px solid #f1d1aa; border-radius: 12px; background: #fff0dd; box-shadow: 0 3px 0 #ecdbc5; } .classroom-brand-caption { display: block; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: #64758a; } diff --git a/tests/e2e/main-navigation.spec.ts b/tests/e2e/main-navigation.spec.ts index 5d1e081f..2f232efc 100644 --- a/tests/e2e/main-navigation.spec.ts +++ b/tests/e2e/main-navigation.spec.ts @@ -78,22 +78,26 @@ test.describe('Makelore module navigation without setup flow', () => { await expect(page.getByTestId('sidebar-course-project-project-storybook')).toHaveAttribute('aria-current', 'page'); const sidebar = page.getByTestId('sidebar'); - const moduleReturn = sidebar.getByTestId('sidebar-module-switcher-trigger'); + const moduleReturn = page.getByTestId('sidebar-module-switcher-trigger'); await expect(moduleReturn).toBeVisible(); await expect(moduleReturn).toHaveText(''); await expect(moduleReturn).toHaveAttribute('aria-label', '返回首页'); await expect(moduleReturn).toHaveAttribute('title', '返回首页'); - await expect(sidebar.getByTestId('sidebar-module-return-icon')).toBeVisible(); + await expect(page.getByTestId('sidebar-module-return-icon')).toBeVisible(); const brandBox = await sidebar.locator('.classroom-brand').boundingBox(); const returnBox = await moduleReturn.boundingBox(); - const collapseBox = await sidebar.getByRole('button', { name: '折叠侧栏' }).boundingBox(); + const collapseBox = await page.getByRole('button', { name: '折叠侧栏' }).boundingBox(); const sidebarBox = await sidebar.boundingBox(); expect(brandBox).not.toBeNull(); expect(returnBox).not.toBeNull(); expect(collapseBox).not.toBeNull(); expect(returnBox!.y + returnBox!.height).toBeLessThanOrEqual(brandBox!.y); - expect(returnBox!.y - sidebarBox!.y).toBe(0); - expect(brandBox!.y - sidebarBox!.y).toBe(40); + const headerBox = await page.getByTestId('coding-conversation-header').boundingBox(); + const tabsBox = await page.getByRole('tablist', { name: '当前工作' }).boundingBox(); + expect(Math.abs(returnBox!.y + returnBox!.height / 2 - headerBox!.y - headerBox!.height / 2)).toBeLessThan(1); + expect(Math.abs(brandBox!.y + brandBox!.height / 2 - tabsBox!.y - tabsBox!.height / 2)).toBeLessThan(1); + expect(brandBox!.y - sidebarBox!.y).toBe(0); + expect(brandBox!.height).toBe(tabsBox!.height); expect(returnBox!.width).toBe(32); expect(Math.abs(returnBox!.y + returnBox!.height / 2 - collapseBox!.y - collapseBox!.height / 2)).toBeLessThan(1); expect(returnBox!.x + returnBox!.width).toBeLessThanOrEqual(collapseBox!.x); @@ -107,8 +111,17 @@ test.describe('Makelore module navigation without setup flow', () => { expect(communityBox!.y).toBeGreaterThanOrEqual(brandBox!.y + brandBox!.height); expect(communityBox!.y - brandBox!.y - brandBox!.height).toBe(10); expect(createBox!.y - communityBox!.y - communityBox!.height).toBe(10); - expect(createBox!.y - brandBox!.y - brandBox!.height).toBe(56); - await expect(community).toHaveCSS('background-color', 'rgb(232, 240, 223)'); + expect(createBox!.y - brandBox!.y - brandBox!.height).toBe(61); + expect(communityBox!.height).toBe(createBox!.height); + await expect(community).toHaveCSS('background-color', 'rgb(248, 250, 245)'); + const buttonStyles = await page.evaluate(() => { + const styles = (selector: string) => { + const s = getComputedStyle(document.querySelector(selector)!); + return [s.borderRadius, s.borderWidth, s.borderColor, s.boxShadow, s.padding, s.fontSize, s.fontWeight]; + }; + return { community: styles('[data-testid=sidebar] .community-link'), create: styles('[data-testid=sidebar-create-project]') }; + }); + expect(buttonStyles.community).toEqual(buttonStyles.create); expect(headingBox!.y).toBeGreaterThanOrEqual(createBox!.y + createBox!.height); expect(headingBox!.y - createBox!.y - createBox!.height).toBeLessThanOrEqual(14); await page.screenshot({ path: test.info().outputPath('chat-code-return-entry.png') }); @@ -117,10 +130,16 @@ test.describe('Makelore module navigation without setup flow', () => { await sidebarResizer.press('Home'); await expect(sidebarResizer).toHaveAttribute('aria-valuenow', '128'); const narrowReturn = await moduleReturn.boundingBox(); - const narrowToggle = await sidebar.getByRole('button', { name: '折叠侧栏' }).boundingBox(); + const narrowToggle = await page.getByRole('button', { name: '折叠侧栏' }).boundingBox(); expect(narrowReturn!.x + narrowReturn!.width).toBeLessThanOrEqual(narrowToggle!.x); await expect(moduleReturn).toBeVisible(); - await expect(sidebar.getByRole('button', { name: '折叠侧栏' })).toBeVisible(); + await expect(page.getByRole('button', { name: '折叠侧栏' })).toBeVisible(); + const narrowHeader = await page.getByTestId('coding-conversation-header').boundingBox(); + expect(narrowToggle!.x + narrowToggle!.width).toBeLessThanOrEqual(narrowHeader!.x); + expect(await moduleReturn.evaluate(el => { + const b = el.getBoundingClientRect(); + return el.contains(document.elementFromPoint(b.x + b.width / 2, b.y + b.height / 2)); + })).toBe(true); await expect(community).toBeVisible(); expect(await community.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true); await page.screenshot({ path: test.info().outputPath('chat-narrow-navigation-row.png') }); @@ -155,8 +174,11 @@ test.describe('Makelore module navigation without setup flow', () => { await sidebarToggle.click(); await page.getByTestId('coding-chat-panel').click({ position: { x: 500, y: 200 } }); await expect(sidebar).toHaveAttribute('aria-hidden', 'true'); + const collapsedToggleBox = await sidebarToggle.boundingBox(); + await expect(moduleReturn).toBeVisible(); await sidebarToggle.hover(); await expect(sidebar).toHaveAttribute('aria-hidden', 'false'); + expect(await sidebarToggle.boundingBox()).toEqual(collapsedToggleBox); await expect(moduleReturn).toBeVisible(); await page.screenshot({ path: test.info().outputPath('chat-collapsed-hover-code-return.png') }); await moduleReturn.click(); diff --git a/tests/unit/coding-feature-ui.test.tsx b/tests/unit/coding-feature-ui.test.tsx index 23d1b72b..f5766686 100644 --- a/tests/unit/coding-feature-ui.test.tsx +++ b/tests/unit/coding-feature-ui.test.tsx @@ -267,7 +267,7 @@ describe('PI-130 feature-complete Coding UI', () => { ); expect(screen.getByTestId('coding-conversation-header-titlebar')).toHaveClass('fixed', 'top-0', 'h-10'); - expect(screen.getByTestId('coding-conversation-header-titlebar')).toHaveStyle({ left: '132px' }); + expect(screen.getByTestId('coding-conversation-header-titlebar')).toHaveStyle({ left: '168px' }); expect(screen.queryByTestId('coding-conversation-sidebar')).not.toBeInTheDocument(); window.electron.platform = originalPlatform; diff --git a/tests/unit/title-bar.test.tsx b/tests/unit/title-bar.test.tsx index 49c9ad30..b63ffd77 100644 --- a/tests/unit/title-bar.test.tsx +++ b/tests/unit/title-bar.test.tsx @@ -1,8 +1,19 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import type { ReactElement } from 'react'; +import { act, fireEvent, render as renderComponent, screen, waitFor } from '@testing-library/react'; +import { MemoryRouter, useLocation } from 'react-router-dom'; import { TitleBar } from '@/components/layout/TitleBar'; +import { CodingConversationHeader } from '@/pages/Chat/CodingConversationHeader'; import { useSettingsStore } from '@/stores/settings'; +function render(ui: ReactElement) { + return renderComponent(ui, { wrapper: ({ children }) => {children} }); +} + +function LocationPath() { + return {useLocation().pathname}; +} + const invokeIpcMock = vi.hoisted(() => vi.fn()); vi.mock('@/lib/api-client', () => ({ @@ -91,7 +102,10 @@ describe('TitleBar platform behavior', () => { expect(screen.queryByTestId('titlebar-logo')).not.toBeInTheDocument(); expect(screen.queryByRole('img', { name: 'Makelore logo' })).not.toBeInTheDocument(); expect(screen.queryByTestId('titlebar-project-context')).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: '折叠侧栏' })).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: '折叠侧栏' })).toBeEnabled(); + expect(screen.getByRole('button', { name: '返回首页' })).toBeEnabled(); + expect(screen.getByTestId('classroom-titlebar-navigation')).toHaveClass('pl-[88px]'); + expect(screen.getByTestId('titlebar-sidebar-toggle')).toHaveClass('right-2'); }); it('renders the painting title bar as a transparent overlay with the logo at the window edge', () => { @@ -179,14 +193,14 @@ describe('TitleBar platform behavior', () => { useSettingsStore.setState({ sidebarCollapsed: true }); const { rerender } = render(); - expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveClass('w-[132px]'); + expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveStyle({ width: '168px' }); expect(screen.queryByTestId('titlebar-conversation-surface')).not.toBeInTheDocument(); expect(screen.queryByTestId('titlebar-logo')).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: '展开侧栏' })).toHaveAttribute('aria-expanded', 'false'); rerender(); - expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveClass('w-[132px]'); + expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveStyle({ width: '168px' }); expect(screen.queryByTestId('titlebar-conversation-surface')).not.toBeInTheDocument(); expect(screen.queryByTestId('titlebar-logo')).not.toBeInTheDocument(); const pinButton = screen.getByRole('button', { name: '固定侧栏' }); @@ -195,7 +209,67 @@ describe('TitleBar platform behavior', () => { fireEvent.click(pinButton); expect(useSettingsStore.getState().sidebarCollapsed).toBe(false); expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveStyle({ width: '171px', minWidth: '171px', flexBasis: '171px' }); - expect(screen.queryByRole('button', { name: '折叠侧栏' })).not.toBeInTheDocument(); + expect(screen.getByRole('button', { name: '折叠侧栏' })).toBeEnabled(); + }); + + it.each([ + ['darwin', 168, 168, 'pl-[88px]'], + ['win32', 128, 88, 'pl-2'], + ] as const)('keeps both %s navigation controls outside the conversation header at narrow and collapsed widths', (platform, narrowWidth, collapsedWidth, insetClass) => { + window.electron.platform = platform; + useSettingsStore.setState({ classroomSidebarWidth: 128 }); + render(<> + + + + ); + + const assertWidth = (width: number) => { + expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveStyle({ width: `${width}px` }); + expect(screen.getByTestId('titlebar-sidebar-toggle-layer')).toHaveStyle({ width: `${width}px` }); + expect(screen.getByTestId('coding-conversation-header-titlebar')).toHaveStyle({ left: `${width}px` }); + }; + assertWidth(narrowWidth); + const rail = screen.getByTestId('classroom-titlebar-navigation'); + expect(rail).toHaveClass(insetClass); + expect(rail).toContainElement(screen.getByRole('button', { name: '返回首页' })); + expect(rail).toContainElement(screen.getByRole('button', { name: '折叠侧栏' })); + expect(screen.getByRole('button', { name: '返回首页' })).toHaveTextContent(''); + expect(screen.getAllByTestId('titlebar-sidebar-toggle')).toHaveLength(1); + + act(() => useSettingsStore.setState({ classroomSidebarWidth: 256 })); + assertWidth(256); + fireEvent.click(screen.getByRole('button', { name: '折叠侧栏' })); + assertWidth(collapsedWidth); + fireEvent.click(screen.getByRole('button', { name: '返回首页' })); + expect(screen.getByTestId('location-path')).toHaveTextContent('/module-select'); + }); + + it('preserves keyboard preview when focus moves between the classroom controls', () => { + window.electron.platform = 'darwin'; + useSettingsStore.setState({ sidebarCollapsed: true }); + const onSidebarPeekChange = vi.fn(); + render(<> + + + ); + const toggle = screen.getByRole('button', { name: '固定侧栏' }); + const back = screen.getByRole('button', { name: '返回首页' }); + act(() => toggle.focus()); + expect(onSidebarPeekChange).toHaveBeenLastCalledWith(true, 'focus'); + onSidebarPeekChange.mockClear(); + act(() => back.focus()); + expect(onSidebarPeekChange).not.toHaveBeenCalledWith(false, 'focus'); + expect(onSidebarPeekChange).toHaveBeenLastCalledWith(true, 'focus'); + act(() => screen.getByRole('button', { name: 'Outside navigation' }).focus()); + expect(onSidebarPeekChange).toHaveBeenLastCalledWith(false, 'focus'); + + const layer = screen.getByTestId('titlebar-sidebar-toggle-layer'); + fireEvent.pointerEnter(layer); + fireEvent.pointerLeave(layer); + expect(onSidebarPeekChange).toHaveBeenCalledWith(true, 'titlebar'); + expect(onSidebarPeekChange).toHaveBeenLastCalledWith(false, 'titlebar'); + expect(layer).toHaveStyle({ width: '168px' }); }); it('extends the ordinary titlebar sidebar surface during a hover preview', () => { From 80b0cb99a81372981806f73f880e7a14da7760a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 30 Sep 2026 00:41:52 +0800 Subject: [PATCH 15/17] Record verified aligned-header trial publication --- .../30-worklog/tasks/20260929-code-back-entry-33d87a81.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 79996cc1..70040c64 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Planning +- Status: Ready for Integration ## Scope @@ -135,3 +135,7 @@ - Implemented the two-control titlebar rail and shared safe-width helper; removed the redundant sidebar row on macOS/Windows while keeping Linux fallback. The brand is 46px tall, aligned to the tabs. Works Square is a 41px very pale green button matching New Project border/radius/padding/font/shadow, with equal 10px gaps. Keyboard focus moves between the titlebar buttons without closing the sidebar preview. - Verification: typecheck, scoped ESLint and production build passed. Titlebar/platform/header/peek unit coverage: 33 tests in 3 files passed. Existing Electron navigation E2E passed (1/1), verifying both horizontal alignments, exact matching button styles, 128px sidebar with reserved topbar space, actual return hit testing, configuration/nested absence, stable collapsed hover geometry and real return navigation. Default and narrow screenshots inspected. Composite build/typecheck/lint passed; full source comparison confirms only the five intended product files changed and all teacher/composer overlays retained. Temporary publication pending below. + +- Temporary app updated in place from the current composite, preserving all 13 prior overlays and adding 883ae1d. Final source: `sidebar-rails-33d87a81/source`; live/next JS `index-pmc1zOQa.js`, common CSS `index-D1Sa4hBY.css`, route CSS `index-CEKmXNFU.css`. Launch/guidance/build metadata finalized consistently. Main PID 32583/profile/native visibility and focus are unchanged; no formal installation or main/remote update. +- Live checks confirm return and collapse centers align with the project/conversation header, the 46px brand aligns with the 46px tab row, native traffic lights and breadcrumb hit areas remain clear, and Works Square/New Project have matching height, border, radius, padding, typography and shadow, with the requested pale fill and equal 10px gaps. An unfocused-window color transition was paused at time zero; completed that button's six pending visual transitions before comparing final computed styles. No navigation or data action was triggered by this check. +- Account, current conversation/messages, all 14 consultation drafts, closed teacher panel/content and Operation Chat selection match the pre-refresh snapshot; all 34 recorded data hashes unchanged, zero alerts. Released refresh lease, restored background throttling and closed the task-opened inspector. Temporary app ready; working branch retains the source for later integration. From 343f88ce59a3b505d41165378ed5864a4fb75653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 30 Sep 2026 00:58:46 +0800 Subject: [PATCH 16/17] Align fullscreen return button with project actions --- .../20260929-code-back-entry-33d87a81.md | 12 ++- electron/main/ipc-handlers.ts | 7 +- electron/main/ipc/window-fullscreen.ts | 20 +++++ electron/preload/index.ts | 2 + src/components/layout/TitleBar.tsx | 7 +- src/lib/window-fullscreen.ts | 29 ++++++++ tests/e2e/main-navigation.spec.ts | 11 +++ tests/unit/main-window-reopen-ipc.test.ts | 73 ++++++++++++++++++- tests/unit/title-bar.test.tsx | 49 ++++++++++++- 9 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 electron/main/ipc/window-fullscreen.ts create mode 100644 src/lib/window-fullscreen.ts diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 70040c64..2bdf595f 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -8,7 +8,7 @@ - Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-empty-work-merge-39c633d0 - Base commit: a77ddbd6813bb71d490544edfef6e1f35240070f - Owner: codex -- Status: Ready for Integration +- Status: Planning ## Scope @@ -139,3 +139,13 @@ - Temporary app updated in place from the current composite, preserving all 13 prior overlays and adding 883ae1d. Final source: `sidebar-rails-33d87a81/source`; live/next JS `index-pmc1zOQa.js`, common CSS `index-D1Sa4hBY.css`, route CSS `index-CEKmXNFU.css`. Launch/guidance/build metadata finalized consistently. Main PID 32583/profile/native visibility and focus are unchanged; no formal installation or main/remote update. - Live checks confirm return and collapse centers align with the project/conversation header, the 46px brand aligns with the 46px tab row, native traffic lights and breadcrumb hit areas remain clear, and Works Square/New Project have matching height, border, radius, padding, typography and shadow, with the requested pale fill and equal 10px gaps. An unfocused-window color transition was paused at time zero; completed that button's six pending visual transitions before comparing final computed styles. No navigation or data action was triggered by this check. - Account, current conversation/messages, all 14 consultation drafts, closed teacher panel/content and Operation Chat selection match the pre-refresh snapshot; all 34 recorded data hashes unchanged, zero alerts. Released refresh lease, restored background throttling and closed the task-opened inspector. Temporary app ready; working branch retains the source for later integration. + + +## Follow-up: Left-Align Return In Native Fullscreen + +- User asks to align the return button's left edge with New Project. Live read-only inspection confirms the current macOS trial is in native fullscreen, while DOM fullscreen and display-mode media queries are both false; the existing 88px native-traffic-light reservation is unnecessary in that state. Keep current vertical alignment and collapse placement; fullscreen and Windows return inset is 12px, while macOS windowed mode retains the native safe inset. +- Project Context Loaded / Planning Gate: Passed. Exact same-task feature identity and official resume verified. Active task, entry and all peer scopes reread; previously loaded memory/architecture/domain/context unchanged from 80b0cb9, no new peer scope conflict. Current trial source sidebar-rails-33d87a81/source is consistent with served and has all 14 overlays. Preserve native window behavior and Main ownership: add only a read-only fullscreen query/event bridge, subscribe before querying with race protection, then adapt the existing titlebar. No screen-size heuristic or native-button hiding. +- Plan: small reusable Main fullscreen IPC binding plus Preload allowlists, renderer subscription adapter and conditional 12px titlebar padding, update focused fullscreen/reopened-window/race tests and existing E2E with simulated native events in the isolated test process. For the existing trial, install only this read-only Main binding and allowlist addition alongside the new Renderer, preserve the current Main/profile/window state, and record the startup overlay for next launch. No main merge/push or formal-app change. + +- Implemented native fullscreen query/event binding with window-reopen support, scoped preload allowlists, a race-safe Renderer observer and 12px fullscreen/Windows return inset. macOS windowed traffic-light space, rail widths, collapse coordinates and brand position are unchanged. The observer also keeps the safe inset when an older preload rejects the event. 27 focused unit tests, typecheck, scoped lint and production build passed; the existing Electron navigation E2E passed including enter/leave native events, exact return/New Project left alignment and unchanged brand/collapse boxes. Fullscreen screenshot inspected. Final candidate includes the fallback fix and passes typecheck/lint/build. +- Temporary composite preserves all previous overlays and differs in exactly five product files. Its older Main adapter retains single-window semantics; the live update will install only the standalone read-only fullscreen module and two preload allowlist additions, not replace the Main bundle. Publication pending. diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 3408fc0a..6fb9f366 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -6,6 +6,7 @@ import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron'; import { registerHostApiProxyHandlers } from './ipc/host-api-proxy'; import { registerTranscriptExportHandler } from './ipc/transcript-export'; import { registerConversationLinkContextMenuHandler } from './ipc/conversation-link-context-menu'; +import { registerWindowFullscreenHandlers } from './ipc/window-fullscreen'; import { applyProxySettings } from './proxy'; import { syncLaunchAtStartupSettingFromStore } from './launch-at-startup'; import { getAllSettings, getSetting, resetSettings, setSetting, type AppSettings } from '../utils/store'; @@ -254,6 +255,7 @@ export function registerIpcHandlers( if (!window || window.isDestroyed()) throw new Error('Main window is unavailable'); return window; }; + const bindWindowFullscreen = registerWindowFullscreenHandlers(requireRequestWindow); registerHostApiProxyHandlers(hostApiContext); registerTranscriptExportHandler(getMainWindow); registerConversationLinkContextMenuHandler(getMainWindow); @@ -272,7 +274,10 @@ export function registerIpcHandlers( return leases; }; const bindWindow = (window: BrowserWindow): void => { - if (!window.isDestroyed() && !window.webContents.isDestroyed()) bindRenderer(window.webContents); + if (!window.isDestroyed() && !window.webContents.isDestroyed()) { + bindRenderer(window.webContents); + bindWindowFullscreen(window); + } }; const initialWindow = getMainWindow(); if (initialWindow) bindWindow(initialWindow); diff --git a/electron/main/ipc/window-fullscreen.ts b/electron/main/ipc/window-fullscreen.ts new file mode 100644 index 00000000..89d1806d --- /dev/null +++ b/electron/main/ipc/window-fullscreen.ts @@ -0,0 +1,20 @@ +import { ipcMain, type BrowserWindow } from 'electron'; + +export function registerWindowFullscreenHandlers( + requireWindow: (event: Electron.IpcMainInvokeEvent) => BrowserWindow, +): (window: BrowserWindow) => void { + ipcMain.handle('window:isFullScreen', (event) => requireWindow(event).isFullScreen()); + + const boundWindows = new WeakSet(); + return (window: BrowserWindow): void => { + if (window.isDestroyed() || window.webContents.isDestroyed() || boundWindows.has(window)) return; + boundWindows.add(window); + + const notify = (fullscreen: boolean): void => { + if (window.isDestroyed() || window.webContents.isDestroyed()) return; + window.webContents.send('window:fullscreen-changed', fullscreen); + }; + window.on('enter-full-screen', () => notify(true)); + window.on('leave-full-screen', () => notify(false)); + }; +} diff --git a/electron/preload/index.ts b/electron/preload/index.ts index a500010c..5ce8a706 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -33,6 +33,7 @@ const validInvokeChannels = [ 'window:maximize', 'window:close', 'window:isMaximized', + 'window:isFullScreen', 'settings:get', 'settings:set', 'settings:setMany', @@ -64,6 +65,7 @@ const validInvokeChannels = [ ]; const validEventChannels = [ + 'window:fullscreen-changed', 'navigate', 'cloud-agent-link', 'update:status-changed', diff --git a/src/components/layout/TitleBar.tsx b/src/components/layout/TitleBar.tsx index 70301311..49f1c3d7 100644 --- a/src/components/layout/TitleBar.tsx +++ b/src/components/layout/TitleBar.tsx @@ -9,6 +9,7 @@ import { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { Copy, FolderOpen, Minus, PanelLeft, PanelLeftClose, Square, X } from 'lucide-react'; import { invokeIpc } from '@/lib/api-client'; +import { observeNativeFullscreen } from '@/lib/window-fullscreen'; import { cn } from '@/lib/utils'; import { useCodingWorkspaceStore } from '@/stores/coding-workspace'; import { useSettingsStore } from '@/stores/settings'; @@ -68,6 +69,10 @@ function ProductTitleBar({ sidebarPeekOpen?: boolean; onSidebarPeekChange?: SidebarPeekChange; }) { + const [nativeFullscreen, setNativeFullscreen] = useState(false); + useEffect(() => { + if (nativeTrafficLights) return observeNativeFullscreen(setNativeFullscreen); + }, [nativeTrafficLights]); const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed); const sidebarWidth = useSettingsStore((state) => normalizeClassroomSidebarWidth(state.classroomSidebarWidth)); const classroomTitlebarWidth = getClassroomTitlebarWidth(nativeTrafficLights ? 'darwin' : 'win32', sidebarCollapsed, sidebarWidth); @@ -124,7 +129,7 @@ function ProductTitleBar({ > {workspaceLayout ?
{sidebarToggle} diff --git a/src/lib/window-fullscreen.ts b/src/lib/window-fullscreen.ts new file mode 100644 index 00000000..b41592c7 --- /dev/null +++ b/src/lib/window-fullscreen.ts @@ -0,0 +1,29 @@ +import { invokeIpc } from './api-client'; + +/** Observe native window fullscreen, which is separate from DOM fullscreen. */ +export function observeNativeFullscreen(onChange: (fullscreen: boolean) => void): () => void { + let active = true; + let revision = 0; + let unsubscribe: (() => void) | void; + try { + unsubscribe = window.electron.ipcRenderer.on('window:fullscreen-changed', (fullscreen) => { + if (!active || typeof fullscreen !== 'boolean') return; + revision += 1; + onChange(fullscreen); + }); + } catch { + // Older preloads reject unknown channels synchronously. Keep the safe inset. + return () => {}; + } + const queryRevision = revision; + void invokeIpc('window:isFullScreen').then((fullscreen) => { + // A transition received during the query is more recent than its snapshot. + if (active && revision === queryRevision && typeof fullscreen === 'boolean') onChange(fullscreen); + }).catch(() => { + // Keep the windowed safe inset when an older host lacks the query. + }); + return () => { + active = false; + unsubscribe?.(); + }; +} diff --git a/tests/e2e/main-navigation.spec.ts b/tests/e2e/main-navigation.spec.ts index 2f232efc..2a9da6e7 100644 --- a/tests/e2e/main-navigation.spec.ts +++ b/tests/e2e/main-navigation.spec.ts @@ -126,6 +126,17 @@ test.describe('Makelore module navigation without setup flow', () => { expect(headingBox!.y - createBox!.y - createBox!.height).toBeLessThanOrEqual(14); await page.screenshot({ path: test.info().outputPath('chat-code-return-entry.png') }); + // Exercise native state events without switching the test app into a macOS Space. + await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].emit('enter-full-screen')); + await expect.poll(async () => (await moduleReturn.boundingBox())!.x).toBe(createBox!.x); + const fullscreenReturn = await moduleReturn.boundingBox(); + expect(fullscreenReturn!.y).toBe(returnBox!.y); + expect(await page.getByRole('button', { name: '折叠侧栏' }).boundingBox()).toEqual(collapseBox); + expect(await sidebar.locator('.classroom-brand').boundingBox()).toEqual(brandBox); + await page.screenshot({ path: test.info().outputPath('chat-fullscreen-return-aligned.png') }); + await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].emit('leave-full-screen')); + await expect.poll(async () => (await moduleReturn.boundingBox())!.x).toBe(returnBox!.x); + const sidebarResizer = sidebar.getByRole('separator', { name: '调整左侧功能栏宽度' }); await sidebarResizer.press('Home'); await expect(sidebarResizer).toHaveAttribute('aria-valuenow', '128'); diff --git a/tests/unit/main-window-reopen-ipc.test.ts b/tests/unit/main-window-reopen-ipc.test.ts index 532456c6..f57306ed 100644 --- a/tests/unit/main-window-reopen-ipc.test.ts +++ b/tests/unit/main-window-reopen-ipc.test.ts @@ -9,7 +9,7 @@ function createWindow(id: number) { isDestroyed: vi.fn(() => false), send: vi.fn(), }); - return { + return Object.assign(new EventEmitter(), { id, webContents: contents, isDestroyed: vi.fn(() => false), @@ -17,8 +17,9 @@ function createWindow(id: number) { maximize: vi.fn(), unmaximize: vi.fn(), isMaximized: vi.fn(() => false), + isFullScreen: vi.fn(() => false), close: vi.fn(), - }; + }); } async function setup() { @@ -104,6 +105,74 @@ describe('IPC after closing and reopening the main window', () => { expect(ctx.second.webContents.listenerCount('destroyed')).toBe(1); }); + it('queries fullscreen for the requesting window before and after reopening', async () => { + const ctx = await setup(); + ctx.first.isFullScreen.mockReturnValue(true); + expect(ctx.invoke('window:isFullScreen', ctx.first)).toBe(true); + expect(ctx.invoke('window:isFullScreen', ctx.second)).toBe(false); + + ctx.reopen(); + expect(ctx.invoke('window:isFullScreen', ctx.second)).toBe(false); + ctx.second.isFullScreen.mockReturnValue(true); + expect(ctx.invoke('window:isFullScreen', ctx.second)).toBe(true); + expect(() => ctx.invoke('window:isFullScreen', ctx.first)).toThrow('Main window is unavailable'); + }); + + it('notifies each bound renderer once when its window enters or leaves fullscreen', async () => { + const ctx = await setup(); + ctx.bindings.bindWindow(ctx.first as unknown as BrowserWindow); + expect(ctx.first.listenerCount('enter-full-screen')).toBe(1); + expect(ctx.first.listenerCount('leave-full-screen')).toBe(1); + ctx.first.emit('enter-full-screen'); + ctx.first.emit('leave-full-screen'); + expect(ctx.first.webContents.send.mock.calls).toEqual([ + ['window:fullscreen-changed', true], + ['window:fullscreen-changed', false], + ]); + expect(ctx.second.webContents.send).not.toHaveBeenCalled(); + + const registeredCount = ctx.handle.mock.calls.length; + ctx.reopen(); + ctx.bindings.bindWindow(ctx.second as unknown as BrowserWindow); + expect(ctx.second.listenerCount('enter-full-screen')).toBe(1); + expect(ctx.second.listenerCount('leave-full-screen')).toBe(1); + ctx.second.emit('enter-full-screen'); + ctx.second.emit('leave-full-screen'); + expect(ctx.second.webContents.send.mock.calls).toEqual([ + ['window:fullscreen-changed', true], + ['window:fullscreen-changed', false], + ]); + expect(ctx.first.webContents.send).toHaveBeenCalledTimes(2); + expect(ctx.handle).toHaveBeenCalledTimes(registeredCount); + }); + + it('does not send fullscreen events after a window or renderer is destroyed', async () => { + const ctx = await setup(); + ctx.reopen(); + ctx.first.emit('enter-full-screen'); + ctx.first.emit('leave-full-screen'); + expect(ctx.first.webContents.send).not.toHaveBeenCalled(); + + ctx.second.webContents.isDestroyed.mockReturnValue(true); + ctx.second.emit('enter-full-screen'); + ctx.second.emit('leave-full-screen'); + expect(ctx.second.webContents.send).not.toHaveBeenCalled(); + }); + + it('does not bind fullscreen events to an already destroyed window or renderer', async () => { + const ctx = await setup(); + const closed = createWindow(3); + closed.isDestroyed.mockReturnValue(true); + ctx.bindings.bindWindow(closed as unknown as BrowserWindow); + expect(closed.listenerCount('enter-full-screen')).toBe(0); + expect(closed.listenerCount('leave-full-screen')).toBe(0); + + ctx.second.webContents.isDestroyed.mockReturnValue(true); + ctx.bindings.bindWindow(ctx.second as unknown as BrowserWindow); + expect(ctx.second.listenerCount('enter-full-screen')).toBe(0); + expect(ctx.second.listenerCount('leave-full-screen')).toBe(0); + }); + it('parents dialogs and link menus to the reopened renderer', async () => { const ctx = await setup(); ctx.reopen(); diff --git a/tests/unit/title-bar.test.tsx b/tests/unit/title-bar.test.tsx index b63ffd77..beca3ead 100644 --- a/tests/unit/title-bar.test.tsx +++ b/tests/unit/title-bar.test.tsx @@ -24,6 +24,7 @@ describe('TitleBar platform behavior', () => { beforeEach(() => { invokeIpcMock.mockReset(); invokeIpcMock.mockResolvedValue(false); + vi.mocked(window.electron.ipcRenderer.on).mockReset(); useSettingsStore.setState({ sidebarCollapsed: false, classroomSidebarWidth: 171 }); }); @@ -84,7 +85,7 @@ describe('TitleBar platform behavior', () => { expect(container.querySelector('.drag-region')).toBeInTheDocument(); expect(screen.queryByTitle('Minimize')).not.toBeInTheDocument(); - expect(invokeIpcMock).not.toHaveBeenCalled(); + expect(invokeIpcMock).toHaveBeenCalledWith('window:isFullScreen'); }); it('aligns the classroom title bar with one project sidebar and no duplicate branding', () => { @@ -108,6 +109,50 @@ describe('TitleBar platform behavior', () => { expect(screen.getByTestId('titlebar-sidebar-toggle')).toHaveClass('right-2'); }); + it('aligns fullscreen return with project buttons and restores native window-control space on exit', async () => { + window.electron.platform = 'darwin'; + invokeIpcMock.mockResolvedValue(true); + const unsubscribe = vi.fn(); + let notify: (...args: unknown[]) => void = () => {}; + vi.mocked(window.electron.ipcRenderer.on).mockImplementation((channel, callback) => { + expect(channel).toBe('window:fullscreen-changed'); + notify = callback; + return unsubscribe; + }); + const { unmount } = render(); + const navigation = screen.getByTestId('classroom-titlebar-navigation'); + await waitFor(() => expect(navigation).toHaveClass('pl-3')); + act(() => notify(false)); + expect(navigation).toHaveClass('pl-[88px]'); + act(() => notify(true)); + expect(navigation).toHaveClass('pl-3'); + unmount(); + expect(unsubscribe).toHaveBeenCalledOnce(); + }); + + it('keeps a native fullscreen transition when an older initial query resolves later', async () => { + window.electron.platform = 'darwin'; + let resolveQuery: (value: boolean) => void = () => {}; + invokeIpcMock.mockReturnValue(new Promise((resolve) => { resolveQuery = resolve; })); + let notify: (...args: unknown[]) => void = () => {}; + vi.mocked(window.electron.ipcRenderer.on).mockImplementation((_channel, callback) => { + notify = callback; + return vi.fn(); + }); + render(); + act(() => notify(true)); + await act(async () => resolveQuery(false)); + expect(screen.getByTestId('classroom-titlebar-navigation')).toHaveClass('pl-3'); + }); + + it('keeps the native safe inset when an older preload rejects fullscreen events', () => { + window.electron.platform = 'darwin'; + vi.mocked(window.electron.ipcRenderer.on).mockImplementation(() => { throw new Error('Invalid IPC channel'); }); + render(); + expect(screen.getByTestId('classroom-titlebar-navigation')).toHaveClass('pl-[88px]'); + expect(invokeIpcMock).not.toHaveBeenCalled(); + }); + it('renders the painting title bar as a transparent overlay with the logo at the window edge', () => { window.electron.platform = 'darwin'; @@ -214,7 +259,7 @@ describe('TitleBar platform behavior', () => { it.each([ ['darwin', 168, 168, 'pl-[88px]'], - ['win32', 128, 88, 'pl-2'], + ['win32', 128, 88, 'pl-3'], ] as const)('keeps both %s navigation controls outside the conversation header at narrow and collapsed widths', (platform, narrowWidth, collapsedWidth, insetClass) => { window.electron.platform = platform; useSettingsStore.setState({ classroomSidebarWidth: 128 }); From d1bf54b8c894f66a2adf53d5fc3502c16a07a883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=A8=E9=B1=BC=E8=BE=A3=E6=A4=92?= Date: Wed, 30 Sep 2026 00:59:56 +0800 Subject: [PATCH 17/17] Record fullscreen return trial verification --- .../30-worklog/tasks/20260929-code-back-entry-33d87a81.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md index 2bdf595f..2c10fdb8 100644 --- a/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md +++ b/.project-docs/30-worklog/tasks/20260929-code-back-entry-33d87a81.md @@ -149,3 +149,6 @@ - Implemented native fullscreen query/event binding with window-reopen support, scoped preload allowlists, a race-safe Renderer observer and 12px fullscreen/Windows return inset. macOS windowed traffic-light space, rail widths, collapse coordinates and brand position are unchanged. The observer also keeps the safe inset when an older preload rejects the event. 27 focused unit tests, typecheck, scoped lint and production build passed; the existing Electron navigation E2E passed including enter/leave native events, exact return/New Project left alignment and unchanged brand/collapse boxes. Fullscreen screenshot inspected. Final candidate includes the fallback fix and passes typecheck/lint/build. - Temporary composite preserves all previous overlays and differs in exactly five product files. Its older Main adapter retains single-window semantics; the live update will install only the standalone read-only fullscreen module and two preload allowlist additions, not replace the Main bundle. Publication pending. + +- Updated the same temporary app in place. Source `sidebar-left-33d87a81/source` includes product commit 343f88c over the prior 14 overlays; live/next JS `index-BgI05aQO.js`, common CSS `index-CDJ7LmpW.css`, route CSS `index-CEKmXNFU.css`. Added the standalone fullscreen module to the running Main and startup wrapper, and patched only the two compiled Preload allowlist entries. Full Main bundle was preserved and Main was not restarted. Launch/guidance/build provenance records the native bridge and old composite adapter explicitly. +- Live verification: native fullscreen is true; return and New Project both start at x=12px. Return/collapse remain aligned with the conversation header, brand remains aligned with tabs; button styles and equal 10px gaps remain correct. Same route, account, conversation, 14 messages, 14 drafts, teacher panel and selected tab; all 34 recorded data-file hashes unchanged, zero alerts. Main PID 32583, native fullscreen, visibility and focus unchanged. Released refresh lease, restored throttling and closed the task-opened inspector. No formal app, main branch or remote changes.