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`。