From b22559a596164160abed82870c6c8c8097f3d1f6 Mon Sep 17 00:00:00 2001 From: inman Date: Mon, 7 Sep 2026 10:06:38 +0800 Subject: [PATCH] feat: remove Canvas inspiration entry --- ...0906-remove-canvas-inspiration-56e64d28.md | 102 ++++++++++++++++++ README.md | 2 +- docs/prompt-museum-server-contract.md | 2 +- src/App.tsx | 3 +- .../layout/ImageWorkspaceSidebar.tsx | 18 ---- src/components/layout/MainLayout.tsx | 4 +- src/components/layout/Sidebar.tsx | 3 +- tests/e2e/image-workspace-v2.spec.ts | 66 ++---------- tests/unit/app-module-provider-gate.test.tsx | 8 ++ tests/unit/main-layout-module-gate.test.tsx | 10 -- tests/unit/title-bar.test.tsx | 6 +- 11 files changed, 126 insertions(+), 98 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260906-remove-canvas-inspiration-56e64d28.md diff --git a/.project-docs/30-worklog/tasks/20260906-remove-canvas-inspiration-56e64d28.md b/.project-docs/30-worklog/tasks/20260906-remove-canvas-inspiration-56e64d28.md new file mode 100644 index 0000000..2bfd19b --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260906-remove-canvas-inspiration-56e64d28.md @@ -0,0 +1,102 @@ +# Task: Remove Canvas inspiration entry + +## Identity + +- Task ID: 20260906-remove-canvas-inspiration-56e64d28 +- Mode: Feature +- Branch: codex/20260906-remove-canvas-inspiration-56e64d28-remove-canvas-inspiration +- Worktree: /Users/inmanx/Documents/makelore-task-remove-canvas-inspiration-56e64d28 +- Base commit: f8eee430f4f4167dc2ecc682ece96c652cdde14b +- Owner: codex +- Status: Ready for integration + +## Scope + +- Remove the user-visible “获取灵感” action from both expanded and collapsed + Canvas sidebars. +- Retire the Prompt Museum Renderer route and redirect historical + `/image-prompts` deep links to the Canvas workspace. +- Remove route-specific title-bar/sidebar treatment and update focused unit, + Electron E2E, README, and retained server-contract wording. + +## Intent And Constraints + +- Keep Canvas conversation, Living Form, Workspace, Quote, Task, Asset, project + creation, and project deletion behavior unchanged. +- Preserve Main-owned Prompt Museum API/auth/media security boundaries in this + small UI-removal task; do not infer authority to delete server data or change + backend contracts. +- Keep old links safe and deterministic by returning users to `/image-canvas` + without initializing Programming providers. +- Maintain the Chinese-only Makelore light interface and existing module-access + policy. + +## Outcome + +- Removed the lightbulb/inspiration action from both expanded and collapsed + Canvas sidebars. +- Retired the Prompt Museum Renderer route and its lazy bundle entry. Historical + `/image-prompts` paths now redirect to `/image-canvas` while remaining covered + by the Canvas module-access gate. +- Removed the obsolete Prompt Museum title-bar and sidebar-spacing special cases, + so Canvas keeps one workspace shell. +- Preserved the dormant Main-owned Prompt Museum Host API, authentication, media + validation, shared DTOs, and their existing focused tests; this task changes + only the AI Design product surface and does not delete server data. +- Updated README, retained server-contract wording, focused unit coverage, and + the Electron E2E flow to describe and verify the retired entry. + +## Verification + +- `corepack pnpm install --frozen-lockfile` — passed with repository-pinned pnpm + 10.33.4. +- Focused Vitest coverage for App routing, Main layout, TitleBar, and the light + visual system — 4 files, 41 tests passed. +- `corepack pnpm run typecheck` — passed. +- `corepack pnpm run lint:check` — passed with zero errors and five pre-existing + warnings in unrelated Home/Makelore files. +- `corepack pnpm run build:vite` — passed for Renderer, Main, Preload, and the + release utility worker. +- `node ./node_modules/@playwright/test/cli.js test tests/e2e/image-workspace-v2.spec.ts --grep "removes the inspiration entry"` + — passed 1/1, confirming the sidebar entry is absent and the historical route + returns to Canvas. +- `corepack pnpm test` — passed: 1,859 ordinary tests passed, three skipped, and + the isolated pressure test passed 1/1. +- `git diff --check` — passed. + +## Follow-ups + +- A serialized Integration task should reconcile canonical Prompt Museum product + statements and split the mixed Museum/Canvas production-smoke commitment so + the still-relevant Canvas deletion and re-Quote checks remain tracked. +- Physically deleting the retained Main API, shared DTOs, or upstream data would + require a separate explicit scope; this task intentionally leaves those secure + boundaries intact and unreachable from the current product UI. + +## Promotion Candidates + +- Target: `.project-docs/30-worklog/current-state.md`, + `.project-docs/20-architecture/system-overview.md`, + `.project-docs/20-architecture/module-map.md`, + `.project-docs/20-architecture/data-flow.md`, + `.project-docs/40-domain/business-rules.md`, + `.project-docs/40-domain/glossary.md`, and + `.project-docs/80-commitments/commitments.md`. +- Proposal: record that Prompt Museum is no longer an active Canvas product + surface: expanded/collapsed sidebars expose no inspiration action, the page is + not mounted or bundled by App routing, and historical `/image-prompts` links + redirect to `/image-canvas`. Retained Main API/auth/media validation and shared + DTO code is dormant compatibility/security infrastructure rather than a visible + module. Remove Museum-specific production-smoke work from the mixed commitment + while retaining its Canvas deletion and re-Quote portions. +- Evidence: the user's explicit direction, focused 41-test regression suite, + full 1,859-test plus pressure pass, typecheck, lint, production build, and the + passing Electron route/sidebar check. +- Future impact: navigation and product documentation must not restore the + inspiration action or Prompt Museum route without a new explicit product + decision. Dormant API removal remains separately scoped. +- Semantic conflict: this supersedes canonical statements that Canvas exposes a + Prompt Museum entry and that its pending Prompt participates in active Canvas + data flow; it does not change ADR-007 or Main-owned security boundaries. +- Human confirmation: already supplied by the user in this task; no additional + product-direction approval is required during integration. diff --git a/README.md b/README.md index 75e6478..d3c9d0c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Makelore 是一个面向软件、视觉创作与智能机器人的 AI 桌面工作台。当前版本为 `2.0.0`,包含三个已开通产品模块。模块入口页采用统一的横向卡片视觉,工作区左上角入口点击后返回模块入口页: - `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。 -- `Makelore Canvas|AI 绘画`:每个设计项目(Workspace)维护一份从创建起就存在的 Living Form。用户通过对话或直接编辑持续完善同一设计方向,再生成图片、单参考图作品或视频;参考素材可从当前项目作品选择或从本地上传。Canvas 侧栏提供“获取灵感”,进入服务端驱动的提示词博物馆。 +- `Makelore Canvas|AI 绘画`:每个设计项目(Workspace)维护一份从创建起就存在的 Living Form。用户通过对话或直接编辑持续完善同一设计方向,再生成图片、单参考图作品或视频;参考素材可从当前项目作品选择或从本地上传。 - `Makelore Robot|AI 机器`:管理机器人智能体、设备激活绑定、智能体配置与设备分配;机器人工作台的智能体位于 Robot 全局侧栏,选中后在内容区先查看绑定设备、再查看基础设置,当前智能体通过 URL 参数保持可分享选择;绑定设备时默认先选择“引导配网”或“已有激活码”。在 Windows 与 macOS 的引导路径中,Makelore 可在弹窗内扫描并连接附近开放的 `Xiaozhi-*` 配网热点,失败时仍可通过系统 Wi-Fi 手动连接;后续继续复用机器人现有热点配网页面,不修改固件,也不由 Makelore 接收 Wi-Fi 密码。 应用启动默认进入 AI 模块入口选择页。入口页可在未登录状态浏览;未登录用户点击已开通模块时进入客户端原生登录页,可使用账号密码或手机号短信验证码登录。密码登录可选“记住密码”:正式安装包仅由 Electron Main 使用系统受保护凭据存储加密保存和回填账号密码,不写入 Renderer 持久状态,未打包开发版或系统安全存储不可用时禁用该选项。登录请求由 Renderer 经 Host API 交给 Electron Main,再由 Main 调用 Works Square;成功后回到入口选择页。已登录时,Electron Main 会从 Works Square `/api/auth/me` 读取当前账号,只向 Renderer 投影用户名、账号/租户/部门标识、权限名列表与三个模块布尔开关,不透传上游资料或凭据。工作区门禁同时要求有效 Token 和完整用户身份;旧状态缺失身份时会先尝试从 Main 恢复,仍无法确认则清除残留会话并返回登录页。被管理员关闭的模块会在入口页置灰且无法点击,直接访问其工作区路径也会返回入口页。旧服务端未返回策略或缺少单项字段时默认开放;这个客户端门禁不替代服务端 API 授权。 diff --git a/docs/prompt-museum-server-contract.md b/docs/prompt-museum-server-contract.md index bc1210c..5924459 100644 --- a/docs/prompt-museum-server-contract.md +++ b/docs/prompt-museum-server-contract.md @@ -20,7 +20,7 @@ V1 不做: - 在客户端打包静态 Prompt 数据集; - 服务端代替用户自动发送 Prompt 或直接生成作品。 -客户端入口位于 Canvas 侧栏“新建设计项目”上方,按钮文案为“获取灵感”。 +当前客户端不再提供 Prompt Museum 的可见入口;历史 `/image-prompts` 地址会重定向到 Canvas 主页面。以下合同仅记录仍由 Main 持有的服务端边界。 ## 2. 请求链路与接口 diff --git a/src/App.tsx b/src/App.tsx index f1cd4a1..2185c82 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,7 +36,6 @@ const ProjectConfiguration = lazy(() => import('./pages/ProjectConfiguration').t const Plugins = lazy(() => import('./pages/Plugins').then(({ Plugins: component }) => ({ default: component }))); const Workbench = lazy(() => import('./pages/Workbench').then(({ Workbench: component }) => ({ default: component }))); const ImageCanvas = lazy(() => import('./pages/ImageCanvas').then(({ ImageCanvas: component }) => ({ default: component }))); -const ImagePromptMuseum = lazy(() => import('./pages/ImagePromptMuseum').then(({ ImagePromptMuseum: component }) => ({ default: component }))); const AiHardware = lazy(() => import('./pages/AiHardware').then(({ AiHardware: component }) => ({ default: component }))); const Settings = lazy(() => import('./pages/Settings').then(({ Settings: component }) => ({ default: component }))); const Setup = lazy(() => import('./pages/Setup').then(({ Setup: component }) => ({ default: component }))); @@ -452,7 +451,7 @@ function App() { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/src/components/layout/ImageWorkspaceSidebar.tsx b/src/components/layout/ImageWorkspaceSidebar.tsx index 5d6ae5a..b15c78b 100644 --- a/src/components/layout/ImageWorkspaceSidebar.tsx +++ b/src/components/layout/ImageWorkspaceSidebar.tsx @@ -1,7 +1,6 @@ import { useEffect, useMemo, useState } from 'react'; import { FolderKanban, - Lightbulb, Loader2, Pencil, Plus, @@ -157,15 +156,6 @@ export function ImageWorkspaceSidebar({ sidebarCollapsed }: ImageWorkspaceSideba > - ); } @@ -181,14 +171,6 @@ export function ImageWorkspaceSidebar({ sidebarCollapsed }: ImageWorkspaceSideba 新建设计项目 -
diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 06c2037..069683c 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -35,7 +35,6 @@ export function MainLayout() { const activeModule = getAiModuleForPath(location.pathname); const isProgrammingModule = activeModule === 'programming'; const isPaintingModule = activeModule === 'painting'; - const isPromptMuseum = location.pathname === '/image-prompts' || location.pathname.startsWith('/image-prompts/'); const isChatWorkspace = location.pathname === '/chat'; const isInitializationSafeRoute = location.pathname === '/project-config' || location.pathname === '/plugins' @@ -95,8 +94,7 @@ export function MainLayout() { diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index f5e9b8b..d4b8604 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -185,7 +185,6 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi const isProgrammingModule = activeModule === 'programming'; const isPaintingModule = activeModule === 'painting'; const isRobotModule = activeModule === 'robot'; - const isPromptMuseum = location.pathname === '/image-prompts' || location.pathname.startsWith('/image-prompts/'); const projectConfigPath = '/project-config'; const visibleProjects = projects; const selectedProjectFolderName = getFolderName(newProjectSelectedPath); @@ -557,7 +556,7 @@ export function Sidebar({ workspaceLayout = false, sidebarPeekOpen = false, onSi
diff --git a/tests/e2e/image-workspace-v2.spec.ts b/tests/e2e/image-workspace-v2.spec.ts index cf9a31b..122513b 100644 --- a/tests/e2e/image-workspace-v2.spec.ts +++ b/tests/e2e/image-workspace-v2.spec.ts @@ -1,7 +1,7 @@ import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron'; test.describe('AI Design V2 workspace', () => { - test('keeps Prompt Museum cards usable when relative media fails', async ({ launchElectronApp }) => { + test('removes the inspiration entry and redirects its retired route to Canvas', async ({ launchElectronApp }) => { test.setTimeout(90_000); const app = await launchElectronApp({ skipSetup: true }); @@ -10,29 +10,6 @@ test.describe('AI Design V2 workspace', () => { await expect(page.getByTestId('ai-module-selection-page')).toBeVisible(); await app.evaluate(({ ipcMain }) => { - const image = { - url: '/api/image-prompt-museum/e2e-entry/media/thumbnail', - width: 1200, - height: 900, - alt: 'E2E museum thumbnail', - }; - const card = { - id: 'e2e-entry', - slug: 'e2e-entry', - title: '相对媒体 E2E', - summary: '验证图片失败不会阻断卡片。', - thumbnail: image, - categories: [], - model: { id: 'e2e-model', name: 'E2E Model' }, - language: 'zh-CN', - attribution: { - author: { name: 'E2E 作者' }, - source: { name: 'E2E 来源' }, - license: { name: '测试许可', attributionText: 'E2E attribution' }, - }, - publishedAt: '2026-08-18T00:00:00.000Z', - updatedAt: '2026-08-18T00:00:00.000Z', - }; const respond = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json }, @@ -50,47 +27,20 @@ test.describe('AI Design V2 workspace', () => { }, }); } - if (path === '/api/works/image-prompt-museum') { - return respond({ - success: true, - data: { - items: [card], - facets: { useCases: [], styles: [], subjects: [] }, - nextCursor: null, - total: 1, - }, - }); - } - if (path === '/api/works/image-prompt-museum/e2e-entry') { - return respond({ - success: true, - data: { - ...card, - prompt: 'Create a resilient image card.', - variables: [], - images: [image], - requiresReferenceImages: false, - }, - }); - } - if (path === '/api/works/image-prompt-museum/e2e-entry/media/thumbnail') { - return respond({ mimeType: 'text/html', dataBase64: 'PGgxPnVuc2FmZTwvaDE+' }); - } return { ok: false, error: { message: `Unexpected E2E Host API request: ${path}` } }; }); }); await page.getByTestId('ai-module-option-painting').click(); const imageSidebar = page.getByTestId('sidebar-image-workspace'); - await expect(imageSidebar.getByRole('button', { name: '获取灵感' })).toBeVisible(); - await imageSidebar.getByRole('button', { name: '获取灵感' }).click(); + await expect(imageSidebar).toBeVisible(); + await expect(imageSidebar.getByRole('button', { name: /获取.*灵感/u })).toHaveCount(0); - const cardButton = page.getByRole('button', { name: '查看 相对媒体 E2E' }); - await expect(cardButton).toBeVisible(); - await expect(page.getByLabel('E2E museum thumbnail加载失败')).toBeVisible(); - await expect(cardButton).toBeEnabled(); - await cardButton.click(); - await expect(page.getByRole('button', { name: '关闭详情' })).toBeVisible(); + await page.evaluate(() => { + window.location.hash = '#/image-prompts'; + }); + await expect.poll(() => page.evaluate(() => window.location.hash)).toBe('#/image-canvas'); + await expect(page.getByTestId('sidebar-image-workspace')).toBeVisible(); } finally { await closeElectronApp(app); } diff --git a/tests/unit/app-module-provider-gate.test.tsx b/tests/unit/app-module-provider-gate.test.tsx index edc7385..aac5da1 100644 --- a/tests/unit/app-module-provider-gate.test.tsx +++ b/tests/unit/app-module-provider-gate.test.tsx @@ -94,6 +94,14 @@ describe('App programming provider initialization gate', () => { await waitFor(() => expect(initProviders).toHaveBeenCalledTimes(1)); }); + it('redirects the retired inspiration route to the Canvas workspace', async () => { + await renderAt('/image-prompts/example'); + + expect(await screen.findByText('Canvas workspace')).toBeInTheDocument(); + expect(screen.queryByText('Module chooser')).not.toBeInTheDocument(); + expect(initProviders).not.toHaveBeenCalled(); + }); + it('redirects an orphaned token without a user identity away from a direct workspace route', async () => { useAuthStore.setState({ initialized: true, diff --git a/tests/unit/main-layout-module-gate.test.tsx b/tests/unit/main-layout-module-gate.test.tsx index 37bb229..1563aaf 100644 --- a/tests/unit/main-layout-module-gate.test.tsx +++ b/tests/unit/main-layout-module-gate.test.tsx @@ -78,16 +78,6 @@ describe('MainLayout module isolation', () => { expect(screen.getByTestId('titlebar-stub')).toHaveAttribute('data-overlay', 'true'); }); - it('keeps the user sidebar preference while moving the inspiration title into the title bar', () => { - renderLayout('/image-prompts'); - - expect(screen.getByTestId('titlebar-stub')).toHaveAttribute('data-page-title', '获取灵感'); - expect(screen.getByTestId('titlebar-stub')).toHaveAttribute('data-overlay', 'false'); - expect(screen.getByTestId('titlebar-stub')).toHaveAttribute('data-sidebar-collapsed', 'unset'); - expect(screen.getByTestId('sidebar-stub')).toHaveAttribute('data-sidebar-collapsed', 'unset'); - expect(useSettingsStore.getState().sidebarCollapsed).toBe(false); - }); - it('does not overlay the local project initialization gate on AI hardware', () => { const load = vi.fn().mockResolvedValue(undefined); useProjectConfigStore.setState({ load }); diff --git a/tests/unit/title-bar.test.tsx b/tests/unit/title-bar.test.tsx index 233e217..e475264 100644 --- a/tests/unit/title-bar.test.tsx +++ b/tests/unit/title-bar.test.tsx @@ -121,13 +121,13 @@ describe('TitleBar platform behavior', () => { expect(screen.queryByTestId('titlebar-project-context')).not.toBeInTheDocument(); }); - it('keeps the painting page title after the macOS traffic-light safe area', () => { + it('keeps an optional page title after the macOS traffic-light safe area', () => { window.electron.platform = 'darwin'; useSettingsStore.setState({ sidebarCollapsed: true }); - render(); + render(); - expect(screen.getByTestId('titlebar-page-title')).toHaveTextContent('获取灵感'); + expect(screen.getByTestId('titlebar-page-title')).toHaveTextContent('页面标题'); expect(screen.getByTestId('titlebar-sidebar-surface')).toHaveClass('w-[132px]'); expect(screen.queryByTestId('titlebar-project-context')).not.toBeInTheDocument(); expect(screen.getByTestId('titlebar-logo').querySelectorAll('img')).toHaveLength(1);