策划阶段收掉空资源画布,对话列改成非位置相关的弹性列
Project CI / Repository checks (pull_request) Failing after 12s
Project CI / Backend tests (pull_request) Failing after 12s
Project CI / Frontend tests (pull_request) Successful in 3m40s
Project CI / Native shell tests (pull_request) Successful in 17m5s

做方案在拿到第一个产物之前,左边的资源画布是空的(文档 0 项、项目版本 0 项),而澄清
问答、决定卡和 GDD 审批全挤在右边 360px 那一列里,屏幕上七成面积空着。
planningStartMode && resources.length === 0 时给工作台加 is-conversation-only,收成单栏。
画布是 display: none 不是不渲染——页签、缩放和选中状态都留着,出现第一个已登记资源后自动
恢复双栏。planningStartMode 只影响这一处布局,不参与任何 runtime 分流。

同时修重叠。.project-supervisor-conversation 的成员数量随链路变化:做游戏时
PlanGddStageProgress 与 GddApprovalCard 都返回 null,做方案时它们出现,pendingCommand /
pendingConfirmation 也各自条件渲染,最多能到八个。原先按位置分配五条轨道,只对做游戏那几
个成员成立——做方案一进来整列后移两格:可伸缩的轨道被阶段进度条占走,审批卡落到能被压到
0 的 minmax(0, auto) 上,末尾成员溢出到隐式行再被容器的 overflow: hidden 裁掉,界面上就是
审批卡截在半句话、消息列表和运行时面板糊在一起。这也是为什么只有做方案会重叠。

改成弹性列后「谁伸缩」由类名决定而不是由出现顺序决定,加减成员不再移位。审批卡另外加了
自己的天花板与内部滚动:它是这一列里唯一没有上限的成员,Fast GDD 的决定项越多它越高,能
把消息列表和运行时面板一起挤出可视区。

原契约用例钉死了旧的轨道字符串,改成断言新的不变量:flex 列、不再出现 grid-template-rows、
消息列表是伸缩的那个、审批卡有上限。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 08:15:38 +00:00
parent c1a8ca6481
commit 0482d08a69
4 changed files with 72 additions and 10 deletions
@@ -307,6 +307,7 @@ export function WorkspaceLauncherShell({
preview={activeProjectPreview}
agentRuntimeSummaries={activeProjectAgentRuntimeSummaries}
agentResults={activeProjectAgentResults}
planningStartMode={currentProjectContext.startMode === 'planning'}
onPlay={() =>
requestCurrentProjectPlay(currentProjectContext.projectPath)
}
+34 -8
View File
@@ -2581,15 +2581,23 @@ textarea {
min-height: 560px;
}
/* 这一列的成员数量随链路变化:做游戏时 PlanGddStageProgress 与 GddApprovalCard 都返回
null,做方案时它们出现,pendingCommand / pendingConfirmation 也各自条件渲染,最多能到
八个。原先用 `grid-template-rows` 按位置分配轨道,只对做游戏那几个成员成立——做方案一
进来整列后移两格:可伸缩的轨道被阶段进度条占走,审批卡落到能被压到 0 的
`minmax(0, auto)` 上,末尾成员溢出到隐式行再被容器的 `overflow: hidden` 裁掉,界面上
就是审批卡截在半句话、消息列表和运行时面板糊在一起。
改成弹性列后「谁伸缩」由类名决定而不是由出现顺序决定,加减成员不再移位。 */
.project-supervisor-conversation {
display: grid;
grid-template-rows: minmax(340px, 1fr) auto auto auto;
align-content: stretch;
display: flex;
flex-direction: column;
gap: 10px;
min-width: 0;
min-height: 0;
}
.project-supervisor-message-list {
flex: 1 1 auto;
min-height: 340px;
max-height: calc(100vh - 330px);
border-radius: 8px;
@@ -3153,11 +3161,8 @@ textarea {
min-height: auto;
}
.project-supervisor-conversation {
grid-template-rows: minmax(320px, auto) auto auto auto;
}
.project-supervisor-message-list {
min-height: 320px;
max-height: 52vh;
}
@@ -5085,6 +5090,16 @@ iframe.preview-frame {
min-height: 0;
}
/* 做方案在拿到第一个已登记资源之前,资源画布是空的,双栏等于把对话挤进 360px 又空着
一大片。这段时间收成单栏,画布只是 `display: none`,内部状态留着,有产物后自动恢复。 */
.game-workbench-layout.is-conversation-only {
grid-template-columns: minmax(0, 1fr);
}
.game-workbench-layout.is-conversation-only .game-workbench-stage {
display: none;
}
.game-workbench-stage,
.game-workbench-chat {
min-width: 0;
@@ -6377,13 +6392,13 @@ iframe.preview-frame {
}
.game-workbench-chat .project-supervisor-conversation {
grid-template-rows: minmax(96px, 1fr) minmax(0, auto) auto auto auto;
height: 100%;
min-height: 0;
overflow: hidden;
}
.game-workbench-chat .project-supervisor-message-list {
flex: 1 1 auto;
min-height: 96px;
max-height: none;
overflow-y: auto;
@@ -6392,6 +6407,17 @@ iframe.preview-frame {
scrollbar-gutter: stable;
}
/* 审批卡是这一列里唯一没有天花板的成员:Fast GDD 的决定项越多它越高,能把消息列表和运行
时面板一起挤出可视区。和 `.agent-runtime-status` 一样给它自己的上限加内部滚动,而不是
让它去挤别人。 */
.game-workbench-chat .gdd-approval-card {
flex: 0 1 auto;
max-height: clamp(160px, 34dvh, 380px);
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
}
.game-workbench-chat .agent-runtime-status {
max-height: clamp(120px, 24dvh, 240px);
min-height: 0;
@@ -282,6 +282,13 @@ export type ProjectDevelopmentViewProps = {
preview?: GameCreationAppPreviewState | null;
agentRuntimeSummaries?: ProjectAgentRuntimeSummary[];
agentResults?: ProjectAgentResultSummary[];
/**
* 本项目是从「做方案」入口建的。
*
* 只用来决定工作台在拿到第一个已登记资源之前是否收掉左边的资源画布,不参与任何
* runtime 分流——做游戏与做素材的链路不受影响。
*/
planningStartMode?: boolean;
supervisor: ReactNode;
onHomeOpen: () => void;
onProjectsOpen: () => void;
@@ -721,6 +728,7 @@ export default function ProjectDevelopmentView({
preview: previewOverride,
agentRuntimeSummaries = emptyProjectAgentRuntimeSummaries,
agentResults = emptyProjectAgentResults,
planningStartMode = false,
supervisor,
onManifestChange,
onPlay,
@@ -1026,6 +1034,11 @@ export default function ProjectDevelopmentView({
}),
[manifestTaskById, projectedResources, resourceGraph],
);
// 做方案在拿到第一个产物之前,左边的资源画布是空的(文档 0 项、项目版本 0 项),而澄清
// 问答、决定卡和 GDD 审批全挤在右边那一列里。这段时间先把画布收掉让对话铺满,一旦有第
// 一个已登记资源就自动恢复双栏。用 CSS 收而不是不渲染:画布里的页签、缩放和选中状态都
// 留着,恢复双栏时不会被重置。
const conversationOnlyWorkbench = planningStartMode && resources.length === 0;
const resourceLayoutTopology = useMemo(() => {
const categoryByResourceId = new Map(
resources.map((resource) => [resource.id, resource.category]),
@@ -3040,7 +3053,13 @@ export default function ProjectDevelopmentView({
/>
</div>
) : (
<div className="game-workbench-layout">
<div
className={
conversationOnlyWorkbench
? 'game-workbench-layout is-conversation-only'
: 'game-workbench-layout'
}
>
<section
className="game-workbench-stage"
aria-label="项目主视窗"
@@ -3477,8 +3477,24 @@ export function registerProjectWorkbenchFoundationTests() {
expect(styles).toMatch(
/@media \(min-width: 761px\)[\s\S]*?\.game-project-workbench\s*\{[^}]*grid-template-rows:\s*minmax\(0, 1fr\) auto[^}]*height:\s*100dvh/,
);
// 这一列的成员数量随链路变化(做方案会多出阶段进度条与审批卡),所以「谁伸缩」必须由
// 类名决定而不是由出现顺序决定:一旦回到按位置分配轨道,审批卡就会落到能被压到 0 的
// 那条上、末尾成员溢出后被 overflow: hidden 裁掉。
expect(styles).toMatch(
/\.game-workbench-chat \.project-supervisor-conversation\s*\{[^}]*grid-template-rows:\s*minmax\(96px, 1fr\) minmax\(0, auto\) auto auto auto[^}]*height:\s*100%[^}]*overflow:\s*hidden/s,
/\.project-supervisor-conversation\s*\{[^}]*display:\s*flex[^}]*flex-direction:\s*column/s,
);
expect(styles).not.toMatch(
/\.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,
);
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,
);
// 审批卡自带上限并内部滚动,不能靠挤别人来容纳决定项。
expect(styles).toMatch(
/\.game-workbench-chat \.gdd-approval-card\s*\{[^}]*max-height:\s*clamp\(160px, 34dvh, 380px\)[^}]*overflow-y:\s*auto/s,
);
expect(styles).toMatch(
/\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\(120px, 24dvh, 240px\)[^}]*overflow-y:\s*auto/s,