From 9196ba3c96d0409cff48b2e556a36e92ccceb122 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Thu, 10 Sep 2026 20:00:42 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20AGC=20=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E7=94=BB=E6=9C=AC=E6=A1=86=E9=80=89=E6=B2=A1=E6=9C=89=E4=BB=BB?= =?UTF-8?q?=E4=BD=95=E9=A2=9C=E8=89=B2=EF=BC=9A=E7=BB=99=E7=94=BB=E6=9C=AC?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E6=A0=B9=E8=A1=A5=E5=85=B1=E4=BA=AB=E7=94=BB?= =?UTF-8?q?=E5=B8=83=E6=A0=B9=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.tsx:`ResourceBookScene` 的场景根 `.game-resource-book-scene` 补上 `genarrative-image-canvas` 类,让共享 `SelectionOverlay` 的框选 token 有定义它的祖先。 - 根因:共享选择框的 `border: 1px solid var(--genarrative-image-canvas-selection-border)` / `background: var(--genarrative-image-canvas-selection-fill)` 没有 fallback,这两个 token 只声明在 `packages/image-canvas-react/src/styles.css` 的 `.genarrative-image-canvas` 根类里,而该根类由共享 `CanvasViewport` 渲染;AGC 只渲染 `SelectionOverlay`、从不渲染根类,声明在计算值阶段被整条丢弃,用户框选时看不到任何选择框。 - 安全性:该根类只有 3 条自定义属性声明、无布局副作用,加 className 不改布局。 - 测试:新增真实 `pointerdown` / `pointermove` 框选用例,断言选择框存在、内联几何非空,且最近的 `.genarrative-image-canvas` 祖先就是画本场景根;去掉根类后该用例失败(已实测)。 - 测试:新增 CSS 声明级断言,钉住根类的描边/底色 token 存在且非透明,消费端仍按 var() 取值(jsdom 拿不到计算色,这里只能钉声明)。 - pitfalls.md:补记"复用共享画布子组件要自己渲染根类"的排查口径。 --- .../src/view/project-development/index.tsx | 4 +- .../appSurface/project-development.suite.ts | 122 ++++++++++++++++++ docs/project-memory/shared-memory/pitfalls.md | 9 ++ 3 files changed, 134 insertions(+), 1 deletion(-) diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index c85e0a5cd..6b16331f2 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -1069,8 +1069,10 @@ function ResourceBookScene({ resourceBookState.view === 'main' ? safeMainViewport : safeViewport; return ( + // 共享 SelectionOverlay 的框选描边/底色 token 只声明在 `.genarrative-image-canvas` + // 根类里,这里把场景根补成该根类,AGC 自绘的框选才有非空颜色。
part.trim()); + return parts.length === 4 ? Number(parts[3]) : 1; +} + export function registerProjectWorkbenchFoundationTests() { it('renders the first project workbench slice with honest disabled run and local approval UI', () => { const manifest = createGameCreationAppManifest( @@ -1153,6 +1162,36 @@ export function registerProjectWorkbenchFoundationTests() { expect(styleNumber(notice, 'z-index')).toBeGreaterThan(sceneZIndex); }); + it('keeps the shared selection overlay colours resolvable from the scene root', () => { + const sharedStyles = readFileSync( + resolve(process.cwd(), 'packages/image-canvas-react/src/styles.css'), + 'utf8', + ); + // 消费端 `border/background: var(...)` 没有 fallback:token 缺失或被写透明,框选就没有颜色。 + const root = styleRuleBody(sharedStyles, '\\.genarrative-image-canvas'); + const borderToken = + /--genarrative-image-canvas-selection-border:\s*([^;]+);/u.exec(root); + const fillToken = + /--genarrative-image-canvas-selection-fill:\s*([^;]+);/u.exec(root); + expect(borderToken, '框选描边 token 缺失').not.toBeNull(); + expect(fillToken, '框选底色 token 缺失').not.toBeNull(); + expect(borderToken![1]!.trim()).not.toMatch(/transparent|none/u); + expect(fillToken![1]!.trim()).not.toMatch(/transparent|none/u); + expect(colorAlpha(borderToken![1]!)).toBeGreaterThan(0.5); + expect(colorAlpha(fillToken![1]!)).toBeGreaterThan(0); + + const overlay = styleRuleBody( + sharedStyles, + '\\.genarrative-image-canvas__selection-overlay', + ); + expect(overlay).toMatch( + /border:\s*1px solid var\(--genarrative-image-canvas-selection-border\)/u, + ); + expect(overlay).toMatch( + /background:\s*var\(--genarrative-image-canvas-selection-fill\)/u, + ); + }); + it('filters resources through the resource search box in both views', async () => { const manifest = createGameCreationAppManifest( 'workbench-search-visibility', @@ -1241,6 +1280,89 @@ export function registerProjectWorkbenchFoundationTests() { ); }); + it('paints the marquee selection box with the scene token root and non-empty geometry', async () => { + const manifest = createGameCreationAppManifest( + 'workbench-marquee-colour', + '框选颜色测试', + ); + manifest.assets = [ + { + id: 'marquee-art', + kind: 'character', + mediaType: 'image/png', + localPath: 'assets/marquee-art.png', + source: { kind: 'generated', taskId: 'art-asset-plan' }, + }, + ]; + const invoke = vi.fn( + async (command: string, args?: Record) => { + if (command === 'read_local_project_resource_graph') { + return resourceGraphForInputs(args); + } + if (command === 'read_local_project_resource_canvas_layout') { + return { + schemaVersion: 'game-creator-resource-layout.v1', + projectId: args?.expectedProjectId, + mode: args?.mode, + revision: 0, + positions: [], + updatedAt: 0, + }; + } + throw new Error(`unexpected invoke ${command}`); + }, + ); + window.__TAURI__ = { core: { invoke } }; + + render( + React.createElement(ProjectDevelopmentView, { + projectName: manifest.name, + projectPath: '/tmp/workbench-marquee-colour', + manifest, + attachments: [], + recentRunStatus: null, + recentRunStopReason: null, + supervisor: React.createElement('div', null, '项目总控'), + onHomeOpen: vi.fn(), + onProjectsOpen: vi.fn(), + }), + ); + + fireEvent.click(screen.getByRole('button', { name: '按类型' })); + await openResourceBookCategory('美术资源'); + const canvas = screen.getByLabelText('资源类型视图') as HTMLDivElement; + Object.defineProperties(canvas, { + setPointerCapture: { configurable: true, value: vi.fn() }, + hasPointerCapture: { configurable: true, value: vi.fn(() => true) }, + releasePointerCapture: { configurable: true, value: vi.fn() }, + }); + fireEvent.pointerDown(canvas, { + pointerId: 91, + button: 0, + clientX: 40, + clientY: 60, + }); + fireEvent.pointerMove(canvas, { + pointerId: 91, + clientX: 140, + clientY: 160, + }); + + const overlay = document.querySelector( + '.genarrative-image-canvas__selection-overlay', + ); + expect(overlay).not.toBeNull(); + expect(Number.parseFloat(overlay!.style.width)).toBeGreaterThan(0); + expect(Number.parseFloat(overlay!.style.height)).toBeGreaterThan(0); + // 框选颜色 token 只声明在 `.genarrative-image-canvas` 根类上,场景根必须挂这个类, + // 否则 `border/background: var(...)` 会在计算值阶段被整条丢弃(等于没有选择框)。 + const tokenRoot = overlay!.closest('.genarrative-image-canvas'); + expect(tokenRoot).not.toBeNull(); + expect(tokenRoot!.classList.contains('game-resource-book-scene')).toBe( + true, + ); + }); + it('marks newly added resources on inactive section tabs until the user opens them', async () => { const manifest = createGameCreationAppManifest( 'workbench-resource-unread', diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 501613fdf..997060dc7 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -5117,3 +5117,12 @@ - 处理:搜索框恢复 `display: flex`,常态 `align-self: center` 居中(总览态标题在左上、分页态栏目标题栏左右两端都有内容,只有中段是空的),与缩放 Dock 同级 `z-index: 40`;提示条同样抬到场景之上。 - 同批修复(大纲):栏目大纲常态是 `opacity: 0.68` + `scale(0.86)` + 全透明底 + `svg { width: 0 }`,常态只剩 11px 文字,用户认不出那是分区入口;现在常态就给 Dock 底板、14px 栏目图标和 12px 栏目文字并高亮当前栏目,悬停/键盘聚焦只做同一套 token 的加强。**注意**:`docs/technical/【技术方案】GameAgent资源自由画板与快速编辑-2026-08-20.md` 的「操作边界」与「验收」两条仍写着"常态缩小、只露栏目文字",该文件当时有其它未提交改动,本批没有一并改,需要后续同步。 - 关联:`apps/ai-game-creator-shell/src/styles.css`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`apps/ai-game-creator-shell/tests/projectResourceLiveIntegration.test.tsx`。 + +## AGC 复用共享画布组件时要自己渲染 `.genarrative-image-canvas` 根类(2026-09-10) + +- 现象:AGC 资源画本里框选**完全没有颜色**——`.genarrative-image-canvas__selection-overlay` 节点在、位置尺寸也对,就是一个看不见的框。 +- 原因:共享 `SelectionOverlay` 的描边/底色写成 `border: 1px solid var(--genarrative-image-canvas-selection-border)` / `background: var(--genarrative-image-canvas-selection-fill)`,**没有 fallback**;这两个 token 只声明在 `packages/image-canvas-react/src/styles.css` 的 `.genarrative-image-canvas` 根类里,而该根类原本由共享 `CanvasViewport` 渲染。AGC 自绘画本时只渲染 `SelectionOverlay`、没有渲染根类,变量取不到 → 声明在计算值阶段被整条丢弃。 +- 处理:给画本场景根 `.game-resource-book-scene` 补 `genarrative-image-canvas` 类(该根类只有 3 条自定义属性声明、无布局副作用,加 className 安全)。 +- 排查口径:任何"复用了共享画布子组件、但没复用 `CanvasViewport`"的宿主,都要先确认这些子组件依赖的根类由谁渲染;共享 CSS 里出现没有 fallback 的 `var(--genarrative-image-canvas-*)` 时,缺根类就是静默丢样式。 +- 验证:AGC 侧用真实 `pointerdown` / `pointermove` 造框选,断言选择框存在、内联几何非空,且最近的 `.genarrative-image-canvas` 祖先就是画本场景根;把根类去掉后该用例会失败(已实测)。 +- 关联:`packages/image-canvas-react/src/styles.css`、`packages/image-canvas-react/src/SelectionOverlay.tsx`、`packages/image-canvas-react/src/CanvasViewport.tsx`、`apps/ai-game-creator-shell/src/view/project-development/index.tsx`。