修复 AGC 资源画本框选没有任何颜色:给画本场景根补共享画布根类
- 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:补记"复用共享画布子组件要自己渲染根类"的排查口径。
This commit is contained in:
@@ -1069,8 +1069,10 @@ function ResourceBookScene({
|
||||
resourceBookState.view === 'main' ? safeMainViewport : safeViewport;
|
||||
|
||||
return (
|
||||
// 共享 SelectionOverlay 的框选描边/底色 token 只声明在 `.genarrative-image-canvas`
|
||||
// 根类里,这里把场景根补成该根类,AGC 自绘的框选才有非空颜色。
|
||||
<div
|
||||
className={`game-resource-book-scene game-resource-book-scene--${resourceBookState.view} game-resource-book-scene--${resourceBookState.phase}${overviewReady ? '' : ' is-measuring'}`}
|
||||
className={`genarrative-image-canvas game-resource-book-scene game-resource-book-scene--${resourceBookState.view} game-resource-book-scene--${resourceBookState.phase}${overviewReady ? '' : ' is-measuring'}`}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
|
||||
@@ -202,6 +202,15 @@ function styleNumber(body: string, property: string) {
|
||||
return Number(new RegExp(`${property}\\s*:\\s*(\\d+)`).exec(body)?.[1]);
|
||||
}
|
||||
|
||||
function colorAlpha(value: string) {
|
||||
const match = /rgba?\(([^)]*)\)/u.exec(value);
|
||||
if (!match) {
|
||||
return 1;
|
||||
}
|
||||
const parts = match[1]!.split(',').map((part) => 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<string, unknown>) => {
|
||||
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<HTMLElement>(
|
||||
'.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',
|
||||
|
||||
@@ -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`。
|
||||
|
||||
Reference in New Issue
Block a user