修复188和191合入后的工作台回归

修复右侧 Codex 对话定位和移动端布局
恢复资源页播放入口并保持运行页导航语义
修正资源红点与发送按钮的可访问名称测试
更新项目开发工作台 PRD 约束
This commit is contained in:
2026-08-25 12:09:57 +08:00
parent 23ea6d4906
commit 1d3c062cf9
6 changed files with 143 additions and 58 deletions
@@ -99,9 +99,14 @@ export function ProjectSupervisorView({
onPlanGddDecision,
...runtimePanelProps
}: ProjectSupervisorViewProps) {
const submitLabel = needsUserInput
? '等待回答'
: runtimePanelProps.controlBusy
? '思考中'
: '发送';
return (
<section
className="project-supervisor-surface"
className={`project-supervisor-surface${directCodex ? ' is-direct-codex' : ''}`}
aria-label={directCodex ? '陶泥儿项目对话' : '项目总控对话'}
>
<div className="project-supervisor-conversation">
@@ -249,6 +254,8 @@ export function ProjectSupervisorView({
/>
<button
type="submit"
aria-label={submitLabel}
title={submitLabel}
disabled={runtimePanelProps.controlBusy || needsUserInput}
>
<ArrowUp size={16} aria-hidden="true" />
+92 -16
View File
@@ -5369,6 +5369,7 @@ iframe.preview-frame {
}
.game-workbench-toolbar {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
@@ -5465,9 +5466,12 @@ iframe.preview-frame {
}
.game-workbench-view-actions .game-workbench-play-button {
position: absolute;
left: 50%;
border-color: var(--platform-button-primary-border);
background: var(--platform-button-primary-fill);
color: var(--platform-button-primary-text);
transform: translateX(-50%);
}
.game-workbench-view-actions .game-workbench-play-button:disabled {
@@ -7308,12 +7312,15 @@ iframe.preview-frame {
}
.game-workbench-chat .project-supervisor-conversation {
position: relative;
height: 100%;
min-height: 0;
overflow: hidden;
}
.game-workbench-chat .project-supervisor-message-list {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list {
flex: 1 1 auto;
min-height: 96px;
max-height: none;
@@ -7325,7 +7332,10 @@ iframe.preview-frame {
/* Keep the transcript readable while making the user's turns immediately
distinct from the assistant stream, like a conventional chat surface. */
.game-workbench-chat .project-supervisor-message-list .message {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list
.message {
width: fit-content;
max-width: min(88%, 720px);
margin: 0;
@@ -7334,11 +7344,18 @@ iframe.preview-frame {
line-height: 1.55;
}
.game-workbench-chat .project-supervisor-message-list .message + .message {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list
.message
+ .message {
margin-top: 10px;
}
.game-workbench-chat .project-supervisor-message-list .message--user {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list
.message--user {
margin-left: auto;
border: 1px solid var(--platform-button-primary-border);
border-radius: 14px 14px 4px;
@@ -7347,6 +7364,7 @@ iframe.preview-frame {
}
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list
> .project-supervisor-process-card {
width: 100%;
@@ -7512,7 +7530,9 @@ iframe.preview-frame {
display: none;
}
.game-workbench-chat .project-supervisor-composer {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer {
grid-template-columns: minmax(0, 1fr) 82px;
align-items: stretch;
position: relative;
@@ -7521,7 +7541,10 @@ iframe.preview-frame {
background: transparent;
}
.game-workbench-chat .project-supervisor-composer textarea {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer
textarea {
height: 72px;
min-height: 72px;
max-height: 72px;
@@ -7531,13 +7554,19 @@ iframe.preview-frame {
color: var(--platform-text-strong);
}
.game-workbench-chat .project-supervisor-composer textarea:focus-visible {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer
textarea:focus-visible {
border-color: var(--platform-surface-hover-border);
outline: none;
box-shadow: 0 0 0 3px var(--platform-input-focus-ring);
}
.game-workbench-chat .project-supervisor-composer button {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer
button {
width: auto;
height: auto;
min-height: 72px;
@@ -7549,7 +7578,9 @@ iframe.preview-frame {
}
/* 右侧对话采用简洁的 Codex 式消息流与编辑框,减少卡片嵌套和视觉噪音。 */
.game-workbench-chat .project-supervisor-message-list {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list {
position: absolute;
top: 0;
right: 0;
@@ -7564,7 +7595,10 @@ iframe.preview-frame {
background: var(--platform-input-fill);
}
.game-workbench-chat .project-supervisor-message-list .message {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list
.message {
display: block;
width: 100%;
max-width: 100%;
@@ -7577,11 +7611,18 @@ iframe.preview-frame {
line-height: 1.85;
}
.game-workbench-chat .project-supervisor-message-list .message + .message {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list
.message
+ .message {
margin-top: 0;
}
.game-workbench-chat .project-supervisor-message-list .message--user {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list
.message--user {
width: fit-content;
max-width: min(82%, 640px);
margin-left: auto;
@@ -7592,7 +7633,9 @@ iframe.preview-frame {
color: var(--platform-text-strong);
}
.game-workbench-chat .project-supervisor-composer {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer {
display: block;
position: absolute;
right: 0;
@@ -7603,7 +7646,10 @@ iframe.preview-frame {
pointer-events: none;
}
.game-workbench-chat .project-supervisor-composer textarea {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer
textarea {
display: block;
width: 100%;
min-height: 88px;
@@ -7619,7 +7665,10 @@ iframe.preview-frame {
pointer-events: auto;
}
.game-workbench-chat .project-supervisor-composer button {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer
button {
position: absolute;
right: 10px;
bottom: 10px;
@@ -7637,7 +7686,11 @@ iframe.preview-frame {
pointer-events: auto;
}
.game-workbench-chat .project-supervisor-composer button svg {
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer
button
svg {
width: 14px;
height: 14px;
}
@@ -8053,6 +8106,10 @@ iframe.preview-frame {
justify-content: flex-end;
}
.game-workbench-view-actions .game-workbench-play-button {
position: static;
transform: none;
}
}
@media (max-width: 760px) {
@@ -8105,6 +8162,25 @@ iframe.preview-frame {
max-height: 52vh;
}
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-message-list {
position: static;
min-height: 320px;
height: 52vh;
flex: 1 1 auto;
}
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer {
position: relative;
right: auto;
bottom: auto;
left: auto;
flex: 0 0 auto;
}
.game-workbench-chat .project-supervisor-surface,
.game-workbench-chat .project-supervisor-conversation {
height: auto;
@@ -4019,37 +4019,27 @@ export default function ProjectDevelopmentView({
data-unavailable={!runAvailable || uiEditorRoute || undefined}
className={mode === 'run' ? 'is-active' : ''}
disabled={uiEditorRoute !== null}
onClick={() => {
if (!runAvailable) {
return;
}
showRunView();
onPlay?.();
}}
onClick={showRunView}
>
</button>
</div>
<div className="game-workbench-view-actions">
{mode === 'run' ? (
<button
type="button"
className="game-workbench-play-button"
disabled={!runAvailable || !onPlay || uiEditorRoute !== null}
onClick={() => {
showRunView();
onPlay?.();
}}
title={
runAvailable
? '运行当前游戏'
: '首个可运行原型完成后才能播放'
}
>
<Play size={15} aria-hidden="true" />
</button>
) : null}
<button
type="button"
className="game-workbench-play-button"
disabled={!runAvailable || !onPlay || uiEditorRoute !== null}
onClick={() => {
showRunView();
onPlay?.();
}}
title={
runAvailable ? '运行当前游戏' : '首个可运行原型完成后才能播放'
}
>
<Play size={15} aria-hidden="true" />
</button>
{mode === 'resources' &&
!assetCanvasRoute &&
!resourceEditorRoute &&
@@ -170,7 +170,7 @@ export function registerClientHomeTests() {
).not.toBeNull();
fireEvent.click(
within(screen.getByLabelText('资源栏目大纲')).getByRole('button', {
name: '项目版本',
name: /^项目版本/,
}),
);
expect(screen.getByRole('button', { name: /版本 1/ })).not.toBeNull();
@@ -345,7 +345,7 @@ export function registerClientHomeTests() {
).not.toBeNull();
fireEvent.click(
within(screen.getByLabelText('资源栏目大纲')).getByRole('button', {
name: '项目版本',
name: /^项目版本/,
}),
);
expect(screen.getByRole('button', { name: /版本 1/ })).not.toBeNull();
@@ -1323,7 +1323,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));
});
@@ -1379,7 +1379,7 @@ export function registerProjectWorkbenchFoundationTests() {
screen.getByRole('region', { name: '音乐音效资源画布' }),
).not.toBeNull();
fireEvent.click(within(outline).getByRole('button', { name: /项目版本/ }));
fireEvent.click(within(outline).getByRole('button', { name: /^项目版本/ }));
expect(
screen.getByRole('region', { name: '项目版本资源画布' }),
).not.toBeNull();
@@ -4330,7 +4330,13 @@ export function registerProjectWorkbenchFoundationTests() {
/\.launcher-main:has\(\.game-project-workbench\)\s*\{[^}]*padding-bottom:\s*0/s,
);
expect(styles).toMatch(
/\.game-workbench-toolbar\s*\{[^}]*padding:\s*8px 12px[^}]*background:\s*transparent/s,
/\.game-workbench-toolbar\s*\{[^}]*position:\s*relative[^}]*padding:\s*8px 12px[^}]*background:\s*transparent/s,
);
expect(styles).toMatch(
/\.game-workbench-view-actions \.game-workbench-play-button\s*\{[^}]*position:\s*absolute[^}]*left:\s*50%[^}]*transform:\s*translateX\(-50%\)/s,
);
expect(styles).toMatch(
/@media \(max-width: 1000px\)[\s\S]*?\.game-workbench-view-actions \.game-workbench-play-button\s*\{[^}]*position:\s*static[^}]*transform:\s*none/s,
);
expect(styles).toMatch(
/@media \(min-width: 761px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/,
@@ -4345,10 +4351,13 @@ export function registerProjectWorkbenchFoundationTests() {
/\.project-supervisor-conversation\s*\{[^}]*grid-template-rows/s,
);
expect(styles).toMatch(
/\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*height:\s*100%[^}]*min-height:\s*0[^}]*overflow:\s*hidden/s,
/\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*position:\s*relative[^}]*height:\s*100%[^}]*min-height:\s*0[^}]*overflow:\s*hidden/s,
);
expect(styles).toMatch(
/\.game-workbench-chat \.project-supervisor-message-list\s*\{[^}]*flex:\s*1 1 auto[^}]*min-height:\s*96px[^}]*overflow-y:\s*auto/s,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s*\{[^}]*position:\s*absolute[^}]*bottom:\s*104px[^}]*left:\s*0/s,
);
expect(styles).toMatch(
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s*\{[^}]*flex:\s*1 1 auto[^}]*min-height:\s*96px[^}]*overflow-y:\s*auto/s,
);
// 审批卡与运行状态各自带上限并内部滚动,不能靠挤别人来容纳自己的内容。锁的是
// 「有 clamp 上限 + 自己滚」这个不变量,不锁具体数值——三个断点是随排版调整的
@@ -4383,14 +4392,14 @@ export function registerProjectWorkbenchFoundationTests() {
/\.game-workbench-chat \.project-runtime-summary\s*\{[^}]*position:\s*sticky[^}]*top:\s*-10px/s,
);
expect(styles).toMatch(
/\.game-workbench-chat \.project-supervisor-composer\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 82px[^}]*z-index:\s*2[^}]*padding-top:\s*8px[^}]*background:\s*transparent/s,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-composer\s*\{[^}]*grid-template-columns:\s*minmax\(0, 1fr\) 82px[^}]*z-index:\s*2[^}]*padding-top:\s*8px[^}]*background:\s*transparent/s,
);
const composerRule = styles.match(
/\.game-workbench-chat \.project-supervisor-composer\s*\{([^}]*)\}/s,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-composer\s*\{([^}]*)\}/s,
);
expect(composerRule?.[1]).not.toContain('border-top:');
expect(styles).toMatch(
/\.game-workbench-chat \.project-supervisor-composer button\s*\{[^}]*min-height:\s*72px[^}]*white-space:\s*nowrap/s,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-composer\s+button\s*\{[^}]*min-height:\s*72px[^}]*white-space:\s*nowrap/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,
@@ -4420,6 +4429,9 @@ export function registerProjectWorkbenchFoundationTests() {
expect(styles).toMatch(
/@media \(max-width: 760px\)[\s\S]*?\.game-workbench-layout\s*\{[^}]*height:\s*auto/,
);
expect(styles).toMatch(
/@media \(max-width: 760px\)[\s\S]*?\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s*\{[^}]*position:\s*static[^}]*height:\s*52vh[^}]*flex:\s*1 1 auto/s,
);
expect(styles).toMatch(
/\.game-resource-canvas-content\s*\{[^}]*width:\s*100%[^}]*min-width:\s*max\(100%, 620px\)/s,
);
@@ -4505,19 +4517,19 @@ export function registerProjectWorkbenchFoundationTests() {
);
const messageListRule =
styles.match(
/\.game-workbench-chat \.project-supervisor-message-list\s*\{([^}]*)\}/s,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s*\{([^}]*)\}/s,
)?.[1] ?? '';
const messageRule =
styles.match(
/\.game-workbench-chat \.project-supervisor-message-list \.message\s*\{([^}]*)\}/s,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s+\.message\s*\{([^}]*)\}/s,
)?.[1] ?? '';
const userMessageRule =
styles.match(
/\.game-workbench-chat \.project-supervisor-message-list \.message--user\s*\{([^}]*)\}/s,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s+\.message--user\s*\{([^}]*)\}/s,
)?.[1] ?? '';
const processCardRules = Array.from(
styles.matchAll(
/\.game-workbench-chat\s+\.project-supervisor-message-list\s*>\s*\.project-supervisor-process-card\s*\{([^}]*)\}/gs,
/\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s*>\s*\.project-supervisor-process-card\s*\{([^}]*)\}/gs,
),
(match) => match[1],
);
@@ -114,7 +114,7 @@
- 工作台四区作为同一应用工作面直接衔接,外层不使用卡片圆角或留白间距。中央主视窗与右侧 Supervisor 使用各自的平台区域底色,并以稳定的竖向分隔线明确边界;不通过外层圆角容器、阴影或区块间缝重新包装四区。
- 右侧正式钱包入口属于客户端宿主 chrome,在普通项目工作台与 UI Editor 子路由中都必须持续可见、键盘可达且可操作,不得因进入素材编辑壳或隐藏全局 chrome 而丢失余额、充值和使用详情入口。
- 资源总览的“资源依赖 / 资源类型”视图切换使用连通的分段按钮组,相邻选项共享边界并保持唯一选中语义。每个分段都必须有清晰的键盘焦点指示,焦点环不得被分段容器的圆角或 `overflow` 裁切。
- 右侧 Supervisor 对话中,用户消息使用右对齐、最大宽度受限的主题暖色气泡,assistant 消息保持左对齐;消息换行不得产生水平溢出,执行过程卡继续占满消息区可用宽度。气泡正文在 light / dark 平台主题下均须满足 WCAG AA 普通文本 `4.5:1` 对比度。
- 右侧 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,确需诊断的信息进入受控详情或开发模式。