diff --git a/apps/ai-game-creator-shell/src/features/resource-canvas/ResourceCanvasAssetGenerationTasksPanelView.tsx b/apps/ai-game-creator-shell/src/features/resource-canvas/ResourceCanvasAssetGenerationTasksPanelView.tsx index f526ea984..3399d5376 100644 --- a/apps/ai-game-creator-shell/src/features/resource-canvas/ResourceCanvasAssetGenerationTasksPanelView.tsx +++ b/apps/ai-game-creator-shell/src/features/resource-canvas/ResourceCanvasAssetGenerationTasksPanelView.tsx @@ -56,7 +56,7 @@ export type ResourceCanvasAssetGenerationTasksPanelViewProps = { * * 两页之间切换时锚点高度走样式里的 transition,不瞬移。 */ - placement?: 'canvas-overview' | 'canvas' | 'run' | 'editor'; + placement?: 'canvas-overview' | 'canvas' | 'editor'; }; /** diff --git a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationTasksSidebar.css b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationTasksSidebar.css index fc6cbf3d3..cb53b2c14 100644 --- a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationTasksSidebar.css +++ b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasAssetGenerationTasksSidebar.css @@ -23,8 +23,9 @@ * 偏移量迟早会压在工具条上(验收现场那条「生成任务和菜单栏重叠」就是这么来的)。 * 放进同一个网格单元格以后,「画布顶边 + 一小段内缩」由网格自己保证,工具条多高都不用管。 * - * 分档:资源栏目画布与 UI 编辑器在第二 / 第三行,运行表现层占 `2 / -1` 且它右上角 - * (`top: 20px; right: 20px`)被版本入口占着,所以那一档把内缩上边距加大到版本入口之下。 + * 分档:资源栏目画布与 UI 编辑器各一档(第二 / 第三行)。**运行表现层没有这一档**—— + * 运行页不挂这个锚点(见 `project-development/index.tsx`),画面右上角留给版本入口与 + * 预览地址小字。 */ .game-resource-generation-tasks-anchor { grid-row: 3; @@ -67,11 +68,6 @@ margin-top: calc(2.625rem + 0.5rem); } -.game-resource-generation-tasks-anchor[data-generation-tasks-placement='run'] { - grid-row: 2 / -1; - margin: 3.5rem 1.1rem 0.85rem 0.85rem; -} - .game-resource-generation-tasks-anchor[data-generation-tasks-placement='editor'] { grid-row: 2; } diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index b1473b56e..89fce1944 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -8397,10 +8397,10 @@ iframe.preview-frame { .game-run-surface { position: relative; display: grid; - grid-template-rows: minmax(300px, 1fr) auto; + /* 三行:状态行(预览地址小字 + 版本入口,都在游戏画面之外)、运行画面、信息面板。 */ + grid-template-rows: auto minmax(300px, 1fr) auto; grid-row: 2 / -1; - /* 与右上角任务锚点同格(见 resourceCanvasAssetGenerationTasksSidebar.css):显式钉第 1 列, - 避免画布被自动列放置挤到隐式列里。 */ + /* 显式钉第 1 列,避免画面被自动列放置挤到隐式列里。 */ grid-column: 1; gap: 12px; height: 100%; @@ -8409,12 +8409,38 @@ iframe.preview-frame { background: transparent; } -/* C7 版本入口:运行模块右上角,没有版本时不渲染。 */ +/* + * 运行区域上方的状态行:左边是预览地址小字,右边是版本入口(没有版本时不渲染)。 + * + * 两者必须在**同一层级、同一个画外侧**:版本入口以前是绝对定位压在游戏画面上 + * (top 20px / right 20px),小字在画面外,两者既不对齐又各自压着画面。 + */ +.game-run-status-bar { + display: flex; + min-width: 0; + min-height: 30px; + align-items: center; + gap: 12px; +} + +.game-run-status-hint { + flex: 1 1 auto; + min-width: 0; + margin: 0; + padding: 0 2px; + color: #96796d; + font-size: 11px; + line-height: 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* C7 版本入口:与预览地址小字同排,靠右;没有版本时不渲染。 */ .game-run-version-picker { - position: absolute; - top: 20px; - right: 20px; - z-index: 5; + flex: 0 0 auto; + /* 没有小字时也要贴右,不能因为 flex 里只剩一个条目就跑到左边。 */ + margin-left: auto; } .game-run-version-trigger { @@ -8497,27 +8523,6 @@ iframe.preview-frame { overflow: hidden; } -/* 运行区域上方的小字:预览地址是过程信息,常驻在这里而不是写进对话区。 */ -.game-run-preview-column { - display: flex; - min-height: 0; - flex-direction: column; - gap: 6px; -} - -.game-run-status-hint { - margin: 0; - padding: 0 2px; - color: #96796d; - font-size: 11px; - line-height: 16px; - overflow-wrap: anywhere; -} - -.game-run-preview-column > .game-run-preview { - flex: 1; -} - .game-run-preview iframe { min-height: 0; } 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 2be66f3c9..048a11597 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 @@ -10845,38 +10845,42 @@ export default function ProjectDevelopmentView({ ) : (
- -
- {/* - 运行区域上方的小字:预览地址属于过程信息,常驻在这里, - 不再以 assistant 消息堆到对话区底部。没有活预览时不占位。 - */} + {/* + 运行区域上方的状态行:左边预览地址小字,右边版本入口。 + 两者同层级、同在游戏画面之外——版本入口不再绝对定位压在画面上, + 小字也不再写进对话区(那是过程信息,不是对话内容)。 + */} +
{embeddedPreviewUrl ? ( -

+

已载入客户端运行视图:{embeddedPreviewUrl}

) : null} -
- {embeddedPreviewUrl ? ( - setRuntimeInspectMode(false)} - /> - ) : ( -
-
- )} -
+ +
+
+ {embeddedPreviewUrl ? ( + setRuntimeInspectMode(false)} + /> + ) : ( +
+
+ )}
@@ -10898,41 +10902,42 @@ export default function ProjectDevelopmentView({
)} {/* - 「生成任务」侧栏:常驻在**工作面右上角**(资源画布 / 运行表现层 / UI 编辑器各自一档坐标), - 可折叠、非模态。它**不**参与 `isResourceCanvasFloatingPanelOpen` 的模态遮挡判据—— - 生成在后台跑,侧栏展开时画布必须照样能看能用;折叠只影响这个视图,任务本身活在账本 - 与本地队列里。位置与开合形态照抄美术画布(开关常驻右上角 + 面板贴着它展开), - 颜色与外形仍走 AGC 的平台 token。 + 「生成任务」侧栏:常驻在**资源画布 / UI 编辑器**的右上角(各一档坐标),可折叠、非模态。 + 它**不**参与 `isResourceCanvasFloatingPanelOpen` 的模态遮挡判据——生成在后台跑, + 侧栏展开时画布必须照样能看能用;折叠只影响这个视图,任务本身活在账本与本地队列里。 + + **运行表现层不挂它**:运行页要留给游戏画面,右上是预览地址小字与版本入口, + 再叠一枚任务开关(或展开的面板)就会压在画面上。任务不会因此丢,切回资源页即可见。 */} - task.projectId === manifest.projectId, - )} - resourceEditTasks={resourceCanvasResourceEdits.filter( - (task) => - !task.restored || - !resourceCanvasResourceEditTaskIsTerminal(task), - )} - open={resourceAssetGenerationTasksPanelOpen} - onToggleOpen={() => - setResourceAssetGenerationTasksPanelOpen((current) => !current) - } - onFocusTask={focusResourceAssetGenerationTask} - /* - 资源总览页与资源栏目画布页的顶部 chrome 不同:画布页钉着整宽的栏目标题栏 - (含「返回资源总览」),总览页没有。两页因此各有一档坐标(见样式里的 - `[data-generation-tasks-placement]`),切换时走锚点高度过渡。 - */ - placement={ - uiEditorRoute - ? 'editor' - : mode === 'run' - ? 'run' + {mode === 'run' ? null : ( + task.projectId === manifest.projectId, + )} + resourceEditTasks={resourceCanvasResourceEdits.filter( + (task) => + !task.restored || + !resourceCanvasResourceEditTaskIsTerminal(task), + )} + open={resourceAssetGenerationTasksPanelOpen} + onToggleOpen={() => + setResourceAssetGenerationTasksPanelOpen((current) => !current) + } + onFocusTask={focusResourceAssetGenerationTask} + /* + 资源总览页与资源栏目画布页的顶部 chrome 不同:画布页钉着整宽的栏目标题栏 + (含「返回资源总览」),总览页没有。两页因此各有一档坐标(见样式里的 + `[data-generation-tasks-placement]`),切换时走锚点高度过渡。 + */ + placement={ + uiEditorRoute + ? 'editor' : resourceBookView === 'child' ? 'canvas' : 'canvas-overview' - } - /> + } + /> + )}
{!uiEditorRoute ? ( diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 196044aab..8f500ec36 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -2401,11 +2401,13 @@ export function registerProjectWorkbenchFoundationTests() { '已载入客户端运行视图:http://127.0.0.1:4173/', ); const surface = screen.getByLabelText('运行表现层'); - expect(surface.contains(hint)).toBe(true); - // 小字紧贴在运行画面之上:它是预览框的前一个兄弟节点,而不是覆盖或寄生在别处。 const preview = surface.querySelector('.game-run-preview'); expect(preview).not.toBeNull(); - expect(preview?.previousElementSibling).toBe(hint); + // 小字在游戏画面**之外**,与版本入口同在运行区域上方那条状态行里。 + expect(preview?.contains(hint)).toBe(false); + const statusBar = preview?.previousElementSibling; + expect(statusBar?.classList.contains('game-run-status-bar')).toBe(true); + expect(statusBar?.contains(hint)).toBe(true); }); it('paints the marquee selection box with the scene token root and non-empty geometry', async () => { @@ -5999,7 +6001,7 @@ export function registerProjectWorkbenchFoundationTests() { /\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-composer\s+button\s*\{/s, ); expect(styles).toMatch( - /\.game-run-surface\s*\{[^}]*grid-template-rows:\s*minmax\(300px, 1fr\) auto[^}]*grid-row:\s*2 \/ -1[^}]*height:\s*100%/s, + /\.game-run-surface\s*\{[^}]*grid-template-rows:\s*auto minmax\(300px, 1fr\) auto[^}]*grid-row:\s*2 \/ -1[^}]*height:\s*100%/s, ); expect(styles).toMatch( /\.local-game-preview-frame\s*\{[^}]*position:\s*relative[^}]*width:\s*100%[^}]*height:\s*100%[^}]*min-width:\s*0[^}]*min-height:\s*0[^}]*overflow:\s*hidden/s, @@ -13276,7 +13278,8 @@ export function registerProjectAgentStatusTests() { ); expect(within(reopened).getByText('待提交设计图')).not.toBeNull(); - // 入口在「运行」页签下同样常驻:侧栏本体在运行态可见,入口若只在资源页签就没法再打开。 + // 运行页签下**不挂**「生成任务」:那一页要留给游戏画面(右上是预览地址小字与版本入口)。 + // 任务不会因此丢——切回资源页签,入口与面板都回来。 fireEvent.click( within(reopened).getByRole('button', { name: '关闭生成任务' }), ); @@ -13286,8 +13289,16 @@ export function registerProjectAgentStatusTests() { expect( screen.getByRole('tab', { name: '运行' }).getAttribute('aria-selected'), ).toBe('true'); + expect( + screen.queryByRole('button', { name: /^生成任务(?: · \d+)?$/ }), + ).toBeNull(); + expect( + document.querySelector('.game-resource-generation-tasks-anchor'), + ).toBeNull(); + + fireEvent.click(screen.getByRole('tab', { name: '资源管理' })); fireEvent.click( - screen.getByRole('button', { name: /^生成任务(?: · \d+)?$/ }), + await screen.findByRole('button', { name: /^生成任务(?: · \d+)?$/ }), ); expect( await screen.findByRole('region', { name: '生成任务' }), diff --git a/apps/ai-game-creator-shell/tests/gameRunStatusBarStyle.test.ts b/apps/ai-game-creator-shell/tests/gameRunStatusBarStyle.test.ts new file mode 100644 index 000000000..8798b4b31 --- /dev/null +++ b/apps/ai-game-creator-shell/tests/gameRunStatusBarStyle.test.ts @@ -0,0 +1,70 @@ +// @vitest-environment jsdom +import { readFileSync } from 'node:fs'; + +import { describe, expect, test } from 'vitest'; + +import { repoPath } from './repoPath'; +import { + declaration, + parseStyleSheet, + resolveDeclarations, +} from './styleCascade'; + +/** + * 运行区域上方那条状态行的声明级断言。 + * + * jsdom 不加载全局样式表,所以这里按仓库既有做法(`styleCascade`)解析真实生效的声明: + * 「预览地址小字与版本入口同排、同在游戏画面之外」这件事只由样式决定,用例必须钉在声明上, + * 否则下一次改动把版本入口挪回绝对定位压画面时没有任何东西会红。 + */ +const GLOBAL_CSS_PATH = repoPath('apps/ai-game-creator-shell/src/styles.css'); +const rules = parseStyleSheet(readFileSync(GLOBAL_CSS_PATH, 'utf8')); +// 同一个求值器限制:全局表里也有 `prefers-reduced-motion` 档,求值时先排除掉。 +const widthRules = rules.filter( + (rule) => !(rule.media ?? '').includes('prefers-reduced-motion'), +); + +function resolved(selectors: readonly string[]): Map { + return resolveDeclarations(widthRules, selectors, 1280); +} + +describe('运行区域状态行样式', () => { + test('状态行排在运行画面上方,画面自己占满剩下的一行', () => { + const surface = resolved(['.game-run-surface']); + // 三行:状态行(auto)、运行画面(至少 300px 且吃满剩余高度)、信息面板(auto)。 + expect(declaration(surface, 'grid-template-rows')).toBe( + 'auto minmax(300px, 1fr) auto', + ); + expect(declaration(surface, 'gap')).toBe('12px'); + }); + + test('小字与版本入口同排:一个弹性占左,一个贴右', () => { + const bar = resolved(['.game-run-status-bar']); + expect(declaration(bar, 'display')).toBe('flex'); + expect(declaration(bar, 'align-items')).toBe('center'); + // 状态行要有下界高度,否则没有版本入口时它会被压成 0,小字会贴着画面。 + expect(declaration(bar, 'min-height')).toBe('30px'); + + const hint = resolved(['.game-run-status-hint']); + expect(declaration(hint, 'flex')).toBe('1 1 auto'); + // 长预览地址单行省略,不把状态行撑成两行、不让它跑进画面。 + expect(declaration(hint, 'white-space')).toBe('nowrap'); + expect(declaration(hint, 'text-overflow')).toBe('ellipsis'); + + const picker = resolved(['.game-run-version-picker']); + // 版本入口不再绝对定位压在游戏画面上:它是状态行里的普通条目,靠 margin 贴右。 + expect(picker.has('position')).toBe(false); + expect(picker.has('top')).toBe(false); + expect(picker.has('right')).toBe(false); + expect(picker.has('z-index')).toBe(false); + expect(declaration(picker, 'margin-left')).toBe('auto'); + }); + + test('预览地址小字不再有自己的列容器,也不再寄生在预览框里', () => { + expect( + widthRules.some((rule) => + rule.selectors.includes('.game-run-preview-column'), + ), + ).toBe(false); + }); +}); diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksPanel.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksPanel.test.tsx index 5d01d2ac8..ce2402179 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksPanel.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksPanel.test.tsx @@ -348,23 +348,12 @@ describe('「生成任务」侧栏', () => { expect(onToggleOpen).toHaveBeenCalledTimes(1); }); - test('锚点按工作面分档:资源画布 / 运行表现层 / UI 编辑器各挂一档', () => { - const { container, rerender } = renderSidebar([], { placement: 'run' }); + test('锚点按工作面分档:资源画布 / UI 编辑器各挂一档', () => { + const { container, rerender } = renderSidebar([], { placement: 'editor' }); const placementOf = () => container .querySelector('.game-resource-generation-tasks-anchor') ?.getAttribute('data-generation-tasks-placement'); - expect(placementOf()).toBe('run'); - - rerender( - , - ); expect(placementOf()).toBe('editor'); // 缺省 = 资源栏目画布,老调用方不传这个 prop 也落在画布右上角。 diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksSidebarStyle.test.ts b/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksSidebarStyle.test.ts index d3aea4611..f4ebcccdd 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksSidebarStyle.test.ts +++ b/apps/ai-game-creator-shell/tests/resourceCanvasAssetGenerationTasksSidebarStyle.test.ts @@ -231,7 +231,7 @@ describe('「生成任务」侧栏样式', () => { expect(sidebar.has('left')).toBe(false); }); - test('锚点贴在工作面右上角,运行表现层让开版本入口,开关沿用次级胶囊样式', () => { + test('锚点贴在工作面右上角,运行表现层不挂这一档,开关沿用次级胶囊样式', () => { const anchor = resolved(['.game-resource-generation-tasks-anchor']); // 锚点是 stage 网格里与工作面同一个单元格的条目:贴右贴顶由网格给,不再用写死 top 的绝对定位 //(写死的 top 会被工具条换行顶穿,验收现场那条「生成任务和菜单栏重叠」就是这么来的)。 @@ -244,14 +244,15 @@ describe('「生成任务」侧栏样式', () => { // 画布顶边内缩一小段,不压在工具条那一行上。 expect(declaration(anchor, 'margin')).toBe('0.85rem'); - // 运行表现层右上角被版本入口(`game-run-version-picker`:top 20px / right 20px)占着, - // 这一档必须把开关压到那枚版本入口下面(内缩上边距 3.5rem)。 - const runAnchor = resolved([ - '.game-resource-generation-tasks-anchor', - ".game-resource-generation-tasks-anchor[data-generation-tasks-placement='run']", - ]); - expect(declaration(runAnchor, 'grid-row')).toBe('2 / -1'); - expect(declaration(runAnchor, 'margin')).toContain('3.5rem'); + // 运行表现层不再挂这个锚点(运行页要留给游戏画面,见 `project-development/index.tsx`): + // 「让开右上角版本入口」那一档坐标随之退役,留一条死规则在这里只会误导下一次改动。 + expect( + widthRules.some((rule) => + rule.selectors.includes( + ".game-resource-generation-tasks-anchor[data-generation-tasks-placement='run']", + ), + ), + ).toBe(false); // UI 编辑器那一档与资源画布同档(第二行),不引入第三套坐标。 const editorAnchor = resolved([ diff --git a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx index 8cb151d57..6103be3e0 100644 --- a/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx +++ b/apps/ai-game-creator-shell/tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx @@ -170,6 +170,55 @@ describe('「生成任务」侧栏的开合与入口位置', () => { ).toBe(true); }); + it('运行页不挂「生成任务」入口与面板,切回资源页又回来', async () => { + installInvoke(); + const manifest = createGameCreationAppManifest( + 'workbench-run-hides-tasks-entry', + '运行页隐藏生成任务', + ); + // 有已完成的可运行原型:运行页签可用,但没有活预览,所以首屏仍停在资源管理。 + manifest.tasks = manifest.tasks.map((task) => + task.id === 'code-prototype' ? { ...task, status: 'completed' } : task, + ); + render( + React.createElement(ProjectDevelopmentView, { + projectName: manifest.name, + projectPath: '/tmp/workbench-run-hides-tasks-entry', + manifest, + attachments: [], + recentRunStatus: null, + recentRunStopReason: null, + supervisor: React.createElement('div', null, '项目总控'), + onHomeOpen: vi.fn(), + onProjectsOpen: vi.fn(), + }), + ); + + // 资源页照旧有入口与锚点。 + expect( + await screen.findByRole('button', { name: '生成任务' }), + ).not.toBeNull(); + expect( + document.querySelector('.game-resource-generation-tasks-anchor'), + ).not.toBeNull(); + + // 运行页:入口、面板、锚点一起消失,画面右上角只留版本入口与预览地址小字。 + fireEvent.click(screen.getByRole('tab', { name: '运行' })); + await waitFor(() => + expect(screen.getByLabelText('运行表现层')).not.toBeNull(), + ); + expect( + document.querySelector('.game-resource-generation-tasks-anchor'), + ).toBeNull(); + expect(screen.queryByRole('button', { name: /^生成任务/ })).toBeNull(); + + // 切回资源页入口回来:任务只是在这页不显示,没有被丢掉。 + fireEvent.click(screen.getByRole('tab', { name: '资源管理' })); + expect( + await screen.findByRole('button', { name: '生成任务' }), + ).not.toBeNull(); + }); + it('「依赖 / 类型」与前一按钮之间的间距跟行内其他按钮一致', () => { const styles = readFileSync( repoPath('apps/ai-game-creator-shell/src/styles.css'), diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 048a40b11..3acce6b0b 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -9219,3 +9219,10 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 决策:对话区只保留对话内容(用户消息、Agent 回复、run trace 摘要、命令确认与错误)。运行 / 预览成功的反馈改由两条通道承载:① 工作台壳的 toast(`ProjectSupervisorComponentProps.onRunNotice` → `RunNoticeToast`,2.6 秒自动收起,同一句连续触发会重新计时);② 运行表现层预览框**上方**的小字(`已载入客户端运行视图:`,只在有活预览时出现,不占位)。失败仍留在对话区——用户需要知道为什么没跑起来;`/preview-status` 这类命令问答也保留在对话区。 - 影响面:`apps/ai-game-creator-shell/src/App.tsx`(四处成功分支)、`src/features/app-shell/{model.ts,WorkspaceLauncher.tsx,RunNoticeToast.tsx}`、`src/view/project-development/index.tsx`、`src/styles.css`;随字符串守卫 `apps/ai-game-creator-shell/scripts/check-config.mjs` 与 `scripts/check-native-shells.mjs` 同步改判据。 - 验证:`appSurface` 全量 539 通过 / 17 跳过、0 失败;AGC 壳目录全量 169 文件 2146 通过 / 22 跳过;`tests/runNoticeToast.test.tsx` 3 条;`npm run agc:typecheck`(含 check-config)与 `npm run check:native-shells:contract` 通过;真机观感未复验。 + +## 2026-09-22 运行页收口:状态行两条信息同在画面外、生成任务入口不进运行页 + +- 背景:运行页的版本入口是绝对定位压在游戏画面上(`top: 20px; right: 20px`),预览地址小字在画面外,两者既不对齐又各挡一块画面;右上角还叠着「生成任务」开关(或展开的面板)。 +- 决策:运行区域上方新增状态行 `.game-run-status-bar`,左边预览地址小字(单行省略 + `title` 给全量地址),右边版本入口,两者同在游戏画面**之外**、同一层级(版本入口去掉绝对定位,靠 `margin-left: auto` 贴右)。运行页不再挂 `ResourceCanvasAssetGenerationTasksPanelView`:入口、面板、锚点都不渲染,`[data-generation-tasks-placement='run']` 那一档坐标与组件 `placement` 联合类型里的 `run` 一并删除;任务不丢,切回资源页即可见。 +- 影响面:`apps/ai-game-creator-shell/src/view/project-development/index.tsx`、`src/styles.css`、`src/features/resource-canvas/{ResourceCanvasAssetGenerationTasksPanelView.tsx,resourceCanvasAssetGenerationTasksSidebar.css}`;用例新增 `tests/gameRunStatusBarStyle.test.ts`,并改 `tests/resourceCanvasAssetGenerationTasksSidebarStyle.test.ts`、`tests/resourceCanvasAssetGenerationTasksPanel.test.tsx`、`tests/resourceCanvasGenerationTasksSidebarDismiss.test.tsx`、`tests/appSurface/project-development.suite.ts`(原来钉着「入口在运行页签常驻」的那条按新口径改写)。 +- 验证:`appSurface` 全量 539 通过 / 17 跳过、0 失败;AGC 壳目录全量 170 文件 2150 通过 / 22 跳过;`npm run agc:typecheck`、`npm run check:native-shells:contract`、编码检查与 `git diff --check` 通过;真机观感未复验。