修复 AGC 资源栏目大纲常态不可读:恢复 Dock 底板、栏目图标与完整文字

- styles.css:`.game-resource-outline` 常态从 `opacity: 0.68` + `scale(0.86)` + 透明底 + 无阴影,改回完整尺寸(`opacity: 1`、`transform: translateY(-50%)`)并给现有视觉语言的 Dock 底板(`--platform-subpanel-border` 边框 + `--platform-nav-fill` 背景 + 与右下角缩放 Dock 同款阴影 + `blur(6px)`)。
- styles.css:`.game-resource-outline button` 常态列宽由 `0 minmax(0,1fr)` 改为 `18px minmax(0,1fr)`、`gap: 6px`,栏目图标常态 width 14px / opacity 1,不再只在悬停时才出现。
- styles.css:栏目文字 11px → 12px;当前栏目(`.is-active`)常态就带 `--platform-nav-active-*` 的边框、底色和阴影,用户一眼能认出当前分区。
- styles.css:悬停/键盘聚焦保留为同一套 token 的加强(更深的边框、`--platform-nav-active-shadow`、`blur(10px)`),不再靠"常态缩到只剩文字"表达层级。
- 不改交互语义:大纲仍然是 `openResourceBookChild` 的入口,按钮 key、aria-label、aria-current 与未读小圆点全部保持原样。
- 测试:新增 CSS 声明级断言,钉住常态不降透明度、不 scale、有底板,图标常态 14px 且文字 12px(jsdom 不加载 styles.css,可见性只能钉声明)。
- 测试:新增 DOM 用例,断言每条栏目入口同时具备图标与栏目文字,且切换栏目后当前项带 `aria-current="page"`。
- 文档:同步《GameAgent资源自由画板与快速编辑》的「操作边界」与「验收」两条,把"常态缩小、只露栏目文字"改成"常态即可读可点",并同步 pitfalls 里的对应口径。
This commit is contained in:
2026-09-10 20:01:55 +08:00
parent bf63766d88
commit eda34187fc
4 changed files with 119 additions and 39 deletions
+20 -36
View File
@@ -6393,19 +6393,20 @@ iframe.preview-frame {
max-height: calc(100% - 24px);
padding: 6px;
overflow: auto;
border: 1px solid transparent;
/* 常态就给完整尺寸和 Dock 底板:只留 11px 文字、去掉图标和底色时,用户认不出
这是栏目入口。悬停/键盘聚焦仍在同一套 token 上加强(见下方 :hover 规则)。 */
border: 1px solid var(--platform-subpanel-border);
border-radius: 14px;
background: transparent;
box-shadow: none;
opacity: 0.68;
transform: translateY(-50%) scale(0.86);
background: var(--platform-nav-fill);
box-shadow: 0 6px 18px rgb(112 70 52 / 12%);
opacity: 1;
transform: translateY(-50%);
transform-origin: left center;
transition:
border-color 160ms ease,
opacity 160ms ease,
transform 160ms ease,
box-shadow 160ms ease;
backdrop-filter: none;
backdrop-filter: blur(6px);
}
.game-resource-outline:hover,
@@ -6414,15 +6415,15 @@ iframe.preview-frame {
background: var(--platform-nav-fill);
box-shadow: var(--platform-nav-active-shadow);
opacity: 1;
transform: translateY(-50%) scale(1);
transform: translateY(-50%);
backdrop-filter: blur(10px);
}
.game-resource-outline button {
display: grid;
grid-template-columns: 0 minmax(0, 1fr);
grid-template-columns: 18px minmax(0, 1fr);
align-items: center;
gap: 0;
gap: 6px;
min-height: 34px;
padding: 0 9px;
border: 1px solid transparent;
@@ -6432,32 +6433,19 @@ iframe.preview-frame {
cursor: pointer;
text-align: left;
transition:
grid-template-columns 160ms ease,
gap 160ms ease;
}
.game-resource-outline:hover button,
.game-resource-outline:has(> button:focus-visible) button {
grid-template-columns: 18px minmax(0, 1fr);
gap: 6px;
background-color 160ms ease,
border-color 160ms ease;
}
/* 图标与栏目文字常态就在位(上面两条规则),悬停/键盘聚焦只加强 Dock 底板与当前项高亮。 */
.game-resource-outline button svg {
width: 0;
min-width: 0;
opacity: 0;
transform: scale(0.65);
transition:
width 160ms ease,
opacity 120ms ease,
transform 160ms ease;
}
.game-resource-outline:hover button svg,
.game-resource-outline:has(> button:focus-visible) button svg {
width: 14px;
min-width: 0;
opacity: 1;
transform: scale(1);
transition:
opacity 120ms ease,
transform 160ms ease;
}
.game-resource-outline button:hover,
@@ -6467,19 +6455,15 @@ iframe.preview-frame {
}
.game-resource-outline button.is-active {
color: var(--platform-nav-item-text-active);
}
.game-resource-outline:hover button.is-active,
.game-resource-outline:has(> button:focus-visible) button.is-active {
border-color: var(--platform-nav-active-border);
background: var(--platform-nav-active-fill);
color: var(--platform-nav-item-text-active);
box-shadow: var(--platform-nav-active-shadow);
}
.game-resource-outline button strong {
overflow: hidden;
font-size: 11px;
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -1162,6 +1162,102 @@ export function registerProjectWorkbenchFoundationTests() {
expect(styleNumber(notice, 'z-index')).toBeGreaterThan(sceneZIndex);
});
it('keeps the outline dock readable without hover', () => {
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
);
const outline = styleRuleBody(styles, '\\.game-resource-outline');
// 常态必须有 Dock 底板且不再整体降透明度/缩小,否则用户认不出这是栏目入口。
expect(styleNumber(outline, 'opacity')).toBe(1);
expect(outline).not.toMatch(/scale\(/u);
expect(outline).toMatch(/background:\s*var\(--platform-nav-fill\)/u);
expect(outline).toMatch(
/border:\s*1px solid var\(--platform-subpanel-border\)/u,
);
// 栏目图标常态就展开:常态把 svg 收成 width: 0、只剩 11px 文字是"认不出"的根因。
expect(styleRuleBody(styles, '\\.game-resource-outline button')).toMatch(
/grid-template-columns:\s*18px minmax\(0, 1fr\)/u,
);
const icon = styleRuleBody(styles, '\\.game-resource-outline button svg');
expect(styleNumber(icon, 'width')).toBe(14);
expect(styleNumber(icon, 'opacity')).toBe(1);
expect(
styleNumber(
styleRuleBody(styles, '\\.game-resource-outline button strong'),
'font-size',
),
).toBe(12);
});
it('renders every outline entry with its icon and section label', async () => {
const manifest = createGameCreationAppManifest(
'workbench-outline-dock',
'栏目大纲可读性测试',
);
manifest.assets = [
{
id: 'outline-art',
kind: 'character',
mediaType: 'image/png',
localPath: 'assets/outline-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-outline-dock',
manifest,
attachments: [],
recentRunStatus: null,
recentRunStopReason: null,
supervisor: React.createElement('div', null, '项目总控'),
onHomeOpen: vi.fn(),
onProjectsOpen: vi.fn(),
}),
);
const outline = await screen.findByLabelText('资源栏目大纲');
const entries = Array.from(outline.querySelectorAll('button'));
expect(entries.length).toBeGreaterThan(0);
// 每条栏目都是"图标 + 栏目文字"的完整入口,不是只有一行小字。
expect(
entries.every(
(button) =>
button.querySelector('svg') !== null &&
(button.querySelector('strong')?.textContent ?? '').trim().length > 0,
),
).toBe(true);
await openResourceBookCategory('美术资源');
expect(
within(outline)
.getByRole('button', { name: '美术资源' })
.getAttribute('aria-current'),
).toBe('page');
});
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'),
@@ -5115,7 +5115,7 @@
- 原因二(何时被关掉):`display: none` 是 PR #193 重排工作台时改的(原始提交 `3a8dff140` 里是 `display: flex`),不是设计选择。
- 门禁为什么抓不到:既有用例用 `screen.getByLabelText('搜索项目资源')` + `fireEvent.change``getByLabelText` 默认**不校验可见性**`display: none` 的元素照样命中并成功改值。AGC 的 vitest 也没有开 `css: true``styles.css` 不会被加载,所以 `toBeVisible()` 在 AGC 里是**恒真的假守卫**——可见性只能读 CSS 源文件做声明级断言(现有口径见 `apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts``styleRuleBody` 断言),交互链路另用真实 DOM 用例覆盖。
- 处理:搜索框恢复 `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` 的「操作边界」与「验收」两条仍写着"常态缩小、只露栏目文字",该文件当时有其它未提交改动,本批没有一并改,需要后续同步
- 同批修复(大纲):栏目大纲常态是 `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
@@ -50,7 +50,7 @@
## 操作边界
- 栏目顺序固定为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`。完全空项目显示全部栏目的分区展览;任一栏目出现资源后,分页大纲以左侧垂直居中的悬浮 Dock 展示全部栏目。常态缩小、降低不透明度并移除容器与选中项背景,只露出栏目文字;悬停或键盘聚焦时平滑恢复完整尺寸,显示栏目图标、Dock 背景和选中态视觉强调。默认停留在该顺序中的第一个非空栏目,空栏目仍可打开空画布。游戏代码资产按其 manifest `category` 落入 `待归类` 并正常显示卡片。
- 栏目顺序固定为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`。完全空项目显示全部栏目的分区展览;任一栏目出现资源后,分页大纲以左侧垂直居中的悬浮 Dock 展示全部栏目。Dock 常态即可读可点:Dock 底板、与栏目标题栏同款的栏目图标和 12px 栏目文字常驻,当前栏目常态带选中态强调;悬停或键盘聚焦只做同一套 token 的加强(更深的边框、阴影与背景模糊),不再用"常态缩小、降低不透明度、只露栏目文字"表达层级。默认停留在该顺序中的第一个非空栏目,空栏目仍可打开空画布。游戏代码资产按其 manifest `category` 落入 `待归类` 并正常显示卡片。
- 滚轮不切换栏目。点击大纲、底部“下一页”标题、总览入口或自动定位资源属于显式切页;切页前必须终止旧栏目的画布拖动和 pointer capture,避免旧 viewport 写入新栏目。
- 每种排序模式下的每个栏目画布都保留独立 viewport;首次进入该“排序模式 + 栏目”组合时按当前内容适配视口,离开后再返回则恢复该组合上次的平移和缩放。空白处拖拽平移画布,资源卡拖拽移动卡片并更新依赖线,Ctrl/Meta 缩放只作用于当前组合,不能牵动其它排序模式或栏目。
- 依赖画布复用 `@genarrative/image-canvas-core` 的 viewport 计算,并复用现有资源卡片、布局和依赖连线模型。
@@ -99,7 +99,7 @@
## 验收
1. 完全空项目在按依赖与按类型下都显示相同的分区展览;任一栏目出现资源后,两种模式都切换为栏目分页画布,大纲以左侧垂直居中的悬浮 Dock 覆盖在全宽画布上,包含全部栏目的图标和文字不显示数量,空栏目仍可打开空画布。
1. 完全空项目在按依赖与按类型下都显示相同的分区展览;任一栏目出现资源后,两种模式都切换为栏目分页画布,大纲以左侧垂直居中的悬浮 Dock 覆盖在全宽画布上,常态就带 Dock 底板、全部栏目的图标和文字不显示数量),悬停/键盘聚焦只做加强,空栏目仍可打开空画布。
2. 栏目顺序为 `UI 交互 -> 角色与对象 -> 场景与环境 -> 音频 -> 文档 -> 待归类 -> 项目版本`;点击大纲、总览入口和底部下一页标题切页,滚轮不切换栏目。只登记游戏代码的项目在「待归类」栏目正常显示代码卡片。
3. 在当前栏目内拖拽空白可以无限平移画布,拖拽资源卡可以移动卡片并同步依赖线,Ctrl/Meta + 滚轮以指针位置为锚点缩放,复位按钮可以适配当前栏目内容;普通平移不会因资源 extent、图片测量或窗口 resize 被拉回,连续缩放仍停在共享画布的最小/最大比例范围内。
4. 按依赖模式显示当前栏目内同类型两端资源的合法精确引用,并具有与当前可见连线一致的无障碍关系说明;按类型模式不显示引导线;搜索隐藏任一端点后连线和说明同时消失。