优化运行视窗播放入口
Project CI / Repository checks (pull_request) Failing after 16s
Project CI / Backend tests (pull_request) Failing after 9s
Project CI / Frontend tests (pull_request) Successful in 3m36s
Project CI / Native shell tests (pull_request) Successful in 16m25s

将桌面运行视窗播放按钮移至顶部中间
资源管理点击播放后直接启动本地预览并切换运行视窗
更新播放链路回归测试与工作台规则
This commit is contained in:
2026-08-24 21:58:43 +08:00
parent 0702bdec38
commit a207928d39
4 changed files with 57 additions and 28 deletions
+5 -1
View File
@@ -1141,6 +1141,9 @@ export function App({
const initialProjectOpenedRef = useRef(false);
const pendingUiConfirmationActionRef = useRef<(() => void) | null>(null);
const queueRunLocalShortcutRef = useRef<() => void>(() => undefined);
const executeRunLocalRef = useRef<(announceToChat: boolean) => void>(
() => undefined,
);
function requestRuntimeConfigOpen() {
if (projectSupervisorOnly && !supervisorChatOnly && !gameChatOnly) {
@@ -3045,6 +3048,7 @@ export function App({
]);
}
queueRunLocalShortcutRef.current = queueRunLocalShortcut;
executeRunLocalRef.current = executeRunLocal;
useEffect(() => {
const nextProjectPath = localProject?.projectPath;
@@ -3062,7 +3066,7 @@ export function App({
}
handledPlayRequestRef.current = requestKey;
onPlayRequestHandled?.(playRequest.requestId);
queueRunLocalShortcutRef.current();
void executeRunLocalRef.current(true);
}, [
localProject?.projectPath,
onPlayRequestHandled,
+14
View File
@@ -5087,6 +5087,7 @@ iframe.preview-frame {
}
.game-workbench-toolbar {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
@@ -5183,6 +5184,9 @@ iframe.preview-frame {
}
.game-workbench-view-actions .game-workbench-play-button {
position: absolute;
left: 50%;
transform: translateX(-50%);
border-color: var(--platform-button-primary-border);
background: var(--platform-button-primary-fill);
color: var(--platform-button-primary-text);
@@ -7377,6 +7381,11 @@ iframe.preview-frame {
.game-workbench-view-actions {
justify-content: flex-end;
}
.game-workbench-view-actions .game-workbench-play-button {
position: static;
transform: none;
}
}
@media (max-width: 760px) {
@@ -7407,6 +7416,11 @@ iframe.preview-frame {
flex-direction: column;
}
.game-workbench-view-actions .game-workbench-play-button {
position: static;
transform: none;
}
.game-workbench-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -773,7 +773,9 @@ export function registerProjectWorkbenchFoundationTests() {
'workbench-art-viewport-memory',
'美术资源视口记忆',
);
const codeTask = manifest.tasks.find((task) => task.id === 'code-prototype');
const codeTask = manifest.tasks.find(
(task) => task.id === 'code-prototype',
);
expect(codeTask).toBeDefined();
codeTask!.status = 'completed';
manifest.assets = [
@@ -1071,7 +1073,9 @@ export function registerProjectWorkbenchFoundationTests() {
).not.toBeNull();
fireEvent.click(within(outline).getByRole('button', { name: /设计文档/ }));
dispatchPageWheel(screen.getByRole('button', { name: /下一页\s*美术资源/ }));
dispatchPageWheel(
screen.getByRole('button', { name: /下一页\s*美术资源/ }),
);
expect(
screen.getByRole('region', { name: '美术资源资源画布' }),
).not.toBeNull();
@@ -1134,9 +1138,7 @@ export function registerProjectWorkbenchFoundationTests() {
name: /打开资源详情:设计文档 section\.md/,
}),
);
expect(
screen.getByRole('dialog', { name: 'section.md' }),
).not.toBeNull();
expect(screen.getByRole('dialog', { name: 'section.md' })).not.toBeNull();
dispatchPageWheel(dependencyCanvas);
expect(
screen.getByRole('region', { name: '美术资源资源画布' }),
@@ -1278,9 +1280,7 @@ export function registerProjectWorkbenchFoundationTests() {
}),
);
});
fireEvent.click(
within(outline).getByRole('button', { name: /项目版本/ }),
);
fireEvent.click(within(outline).getByRole('button', { name: /项目版本/ }));
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 220));
});
@@ -1288,9 +1288,7 @@ export function registerProjectWorkbenchFoundationTests() {
screen.getByRole('region', { name: '项目版本资源画布' }),
).not.toBeNull();
fireEvent.click(
within(outline).getByRole('button', { name: /设计文档/ }),
);
fireEvent.click(within(outline).getByRole('button', { name: /设计文档/ }));
act(() => {
dependencyCanvas.dispatchEvent(
new WheelEvent('wheel', {
@@ -3704,10 +3702,7 @@ export function registerProjectWorkbenchFoundationTests() {
'[data-resource-viewport]',
);
const viewportScale = Number(
viewportElement
?.getAttribute('data-resource-viewport')
?.split(',')
.at(2),
viewportElement?.getAttribute('data-resource-viewport')?.split(',').at(2),
);
expect(viewportScale).toBeGreaterThan(0);
const expectedX = Math.round(12 + (-100 - 20) / viewportScale);
@@ -4727,7 +4722,7 @@ export function registerUserSurfaceBoundaryTests() {
}
export function registerProjectSupervisorSurfaceTests() {
it('routes a top workbench play request into the existing game.run_local confirmation', async () => {
it('runs a top workbench play request without a second confirmation', async () => {
const projectPath = '/tmp/top-play-request';
const supervisorHarness = createProjectSupervisorRuntimeHarness({
projectPath,
@@ -4745,6 +4740,19 @@ export function registerProjectSupervisorSurfaceTests() {
recentRunStopReason: null,
};
}
if (command === 'start_local_game_preview') {
return {
url: 'http://127.0.0.1:43127/',
port: 43127,
root: projectPath,
};
}
if (command === 'get_local_game_manifest') {
return createGameCreationAppManifest(
'top-play-request',
'顶部播放请求',
);
}
return supervisorHarness.invoke(command, args);
},
);
@@ -4764,14 +4772,17 @@ export function registerProjectSupervisorSurfaceTests() {
);
expect(
await screen.findByText('game.run_local', {}, { timeout: 3_000 }),
await screen.findByText(
/运行通过,已载入客户端运行视图/,
{},
{ timeout: 3_000 },
),
).not.toBeNull();
expect(handled).toHaveBeenCalledWith(7);
expect(screen.getByText(`启动 ${projectPath}/game/`)).not.toBeNull();
expect(invoke).not.toHaveBeenCalledWith(
'start_local_game_preview',
expect.anything(),
);
expect(invoke).toHaveBeenCalledWith('start_local_game_preview', {
projectPath,
});
expect(screen.queryByText('game.run_local')).toBeNull();
});
it('starts a direct product preview without the legacy Canvas-only static smoke', async () => {
@@ -4812,9 +4823,6 @@ export function registerProjectSupervisorSurfaceTests() {
}),
);
expect(await screen.findByText('game.run_local')).not.toBeNull();
fireEvent.click(screen.getByRole('button', { name: '确认' }));
expect(
await screen.findByText(
'运行通过,已载入客户端运行视图:http://127.0.0.1:43126/',
@@ -9961,7 +9969,8 @@ export function registerProjectSupervisorSurfaceTests() {
projectPath,
});
let directTurnUpdateHandler:
((event: { payload: Record<string, unknown> }) => void) | null = null;
| ((event: { payload: Record<string, unknown> }) => void)
| null = null;
const listen = vi.fn(
async (
eventName: string,
@@ -10919,7 +10928,8 @@ export function registerProjectAgentStatusTests() {
},
);
let runtimeUpdateHandler:
((event: { payload: Record<string, unknown> }) => void) | null = null;
| ((event: { payload: Record<string, unknown> }) => void)
| null = null;
const listen = vi.fn(
async (
eventName: string,
@@ -116,6 +116,7 @@
- 资源总览的“资源依赖 / 资源类型”视图切换使用连通的分段按钮组,相邻选项共享边界并保持唯一选中语义。每个分段都必须有清晰的键盘焦点指示,焦点环不得被分段容器的圆角或 `overflow` 裁切。
- 右侧 Supervisor 对话中,用户消息使用右对齐、最大宽度受限的主题暖色气泡,assistant 消息保持左对齐;消息换行不得产生水平溢出,执行过程卡继续占满消息区可用宽度。气泡正文在 light / dark 平台主题下均须满足 WCAG AA 普通文本 `4.5:1` 对比度。
- 客户端正式产品仍只按最小 `1280×720` 横屏合同交付,并保留 `1280×800` 默认窗口与既有基线验收;更窄浏览器样式只负责不崩溃和开发兼容,不改成移动端创作工作台。
- 工作台顶部播放按钮在桌面视窗中水平居中;资源管理视窗触发播放时直接切换到运行视窗并启动本地预览,不再弹出 `game.run_local` 二次确认。
- 普通用户界面不默认展示内部错误码或本机绝对路径,也不展示 External Editor Base URL 或 Developer API Key;官方服务地址由客户端构建固定,远端编辑自动使用当前陶泥儿登录态。仅独立 standalone game-chat release 或显式高级自定义模式在隔离配置页维护 External v1 URL/Key,确需诊断的信息进入受控详情或开发模式。
- 创建模式素材画布的“素材名称”是用户可编辑的正式输出名称;“资源用途”是 manifest subtype,不向普通用户开放自由文本。新增资源默认“普通游戏美术”,可从普通游戏美术、统一视觉规范、游戏界面原型、核心美术图集四项中选择。图片精修继承源名称和用途,不显示创建模式保存设置;候选图片只从选中图片的“设为最终图”提交。精修顶栏只保留返回、导入、定位当前最终图、撤销和重做,删除进入图片上下文工具栏,通用 AI 生成只保留给创建模式。图片输出统一使用 PNG;创建模式工具动作与保存设置分层展示,“保存到项目”在 `1280×800` 和窄容器中都必须完整可见。
- 首页创作输入区与“最近项目”之间不展示共享项目状态文本,“最近项目”标题下也不追加解释性副标题;默认、成功、进行中或失败状态均不得在该位置形成文字行,项目管理页继续保留自己的状态反馈。