diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index af4501670..046a93732 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -1818,7 +1818,6 @@ textarea { line-height: 1.55; } - .supervisor-chat-only-message-list .message--user { align-self: flex-end; border-color: #cfd6df; @@ -1923,7 +1922,6 @@ textarea { opacity: 0.55; } - .local-game-preview-frame { position: relative; width: 100%; @@ -1946,7 +1944,6 @@ textarea { transform-origin: center; } - @media (max-width: 600px) { .supervisor-chat-only-header { padding: 0 12px; @@ -1970,7 +1967,6 @@ textarea { } } - .project-supervisor-surface { display: grid; grid-template-columns: minmax(0, 1fr) minmax(220px, 280px); @@ -6082,9 +6078,7 @@ iframe.preview-frame { max-width: none; } -.game-workbench-chat - .supervisor-chat-only-runtime-controls - .pending-command { +.game-workbench-chat .supervisor-chat-only-runtime-controls .pending-command { gap: 12px; padding: 10px 12px; border: 1px solid var(--platform-surface-border); @@ -6149,7 +6143,9 @@ iframe.preview-frame { line-height: 1.4; } -.agent-runtime-status .project-runtime-pending-command .pending-command-actions { +.agent-runtime-status + .project-runtime-pending-command + .pending-command-actions { display: flex; align-items: center; gap: 8px; @@ -6165,9 +6161,7 @@ iframe.preview-frame { color: var(--platform-button-primary-text); } -.game-workbench-chat - .agent-runtime-status - .project-runtime-pending-command { +.game-workbench-chat .agent-runtime-status .project-runtime-pending-command { grid-template-columns: minmax(0, 1fr) 136px; } @@ -6206,8 +6200,12 @@ iframe.preview-frame { background: var(--platform-warm-bg); } -.game-workbench-chat .project-supervisor-surface > .project-runtime-pending-command, -.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command { +.game-workbench-chat + .project-supervisor-surface + > .project-runtime-pending-command, +.game-workbench-chat + .project-supervisor-conversation + .project-runtime-pending-command { display: grid; grid-template-columns: minmax(0, 1fr) 136px; align-items: center; @@ -6220,7 +6218,10 @@ iframe.preview-frame { background: var(--platform-warm-bg); } -.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command > span { +.game-workbench-chat + .project-supervisor-conversation + .project-runtime-pending-command + > span { min-width: 0; margin: 0; overflow-wrap: anywhere; @@ -6229,7 +6230,11 @@ iframe.preview-frame { font-weight: 800; } -.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command > span small { +.game-workbench-chat + .project-supervisor-conversation + .project-runtime-pending-command + > span + small { display: block; margin-top: 4px; color: var(--platform-text-soft); @@ -6238,7 +6243,10 @@ iframe.preview-frame { line-height: 1.4; } -.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command .pending-command-actions { +.game-workbench-chat + .project-supervisor-conversation + .project-runtime-pending-command + .pending-command-actions { display: grid; grid-template-columns: repeat(2, 64px); gap: 8px; @@ -6246,7 +6254,11 @@ iframe.preview-frame { flex: 0 0 136px; } -.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command .pending-command-actions button { +.game-workbench-chat + .project-supervisor-conversation + .project-runtime-pending-command + .pending-command-actions + button { width: 64px; min-width: 64px; max-width: 64px; @@ -6261,7 +6273,11 @@ iframe.preview-frame { white-space: nowrap; } -.game-workbench-chat .project-supervisor-conversation .project-runtime-pending-command .pending-command-actions button:last-child { +.game-workbench-chat + .project-supervisor-conversation + .project-runtime-pending-command + .pending-command-actions + button:last-child { border-color: var(--platform-button-primary-border); background: var(--platform-button-primary-fill); color: var(--platform-button-primary-text); @@ -7210,7 +7226,6 @@ iframe.preview-frame { } } - /* ============================================================ 立项策划前端样式(PlanGddSurface / 阶段进度 / GDD 审批卡 / 交付行 / planning-lane-runtime-strip)。 @@ -7223,7 +7238,7 @@ iframe.preview-frame { /* 策划区是一个面:阶段进度是标题栏,审批卡是正文。边框和圆角只画在外壳上,里面两块 不再各自带框。 */ -.plan-gdd-surface{ +.plan-gdd-surface { display: grid; min-width: 0; border: 1px solid #cfd7e6; @@ -7232,18 +7247,18 @@ iframe.preview-frame { overflow: hidden; } -.gdd-approval-card{ +.gdd-approval-card { display: grid; gap: 14px; padding: 16px 18px 18px; background: #f8fbff; } -.plan-gdd-surface--with-card .plan-gdd-stage-progress{ +.plan-gdd-surface--with-card .plan-gdd-stage-progress { border-bottom: 1px solid #dbe4f1; } -.plan-gdd-stage-progress{ +.plan-gdd-stage-progress { display: grid; gap: 6px; padding: 10px 12px; @@ -7253,7 +7268,7 @@ iframe.preview-frame { } .plan-gdd-stage-progress__header, -.plan-gdd-stage-progress__meta{ +.plan-gdd-stage-progress__meta { display: flex; flex-wrap: wrap; align-items: center; @@ -7263,7 +7278,7 @@ iframe.preview-frame { /* 批准后的交付行。它是策划阶段唯一的产物出口,所以给一条分隔线把它和上面的状态 区分开,而不是混成第三行元信息。 */ -.plan-gdd-stage-progress__delivery{ +.plan-gdd-stage-progress__delivery { display: grid; gap: 8px; margin-top: 4px; @@ -7271,20 +7286,20 @@ iframe.preview-frame { border-top: 1px solid #e6ecf5; } -.plan-gdd-stage-progress__delivery code{ +.plan-gdd-stage-progress__delivery code { min-width: 0; color: #3c4a5c; font-size: 11px; overflow-wrap: anywhere; } -.plan-gdd-stage-progress__delivery-actions{ +.plan-gdd-stage-progress__delivery-actions { display: flex; flex-wrap: wrap; gap: 8px; } -.plan-gdd-stage-progress__delivery-actions button{ +.plan-gdd-stage-progress__delivery-actions button { min-height: 30px; padding: 0 12px; border: 1px solid #cfd7e6; @@ -7295,32 +7310,32 @@ iframe.preview-frame { } .plan-gdd-stage-progress__delivery-actions button:hover:not(:disabled), -.plan-gdd-stage-progress__delivery-actions button:focus-visible{ +.plan-gdd-stage-progress__delivery-actions button:focus-visible { border-color: #1f6feb; color: #1f6feb; } -.plan-gdd-stage-progress__delivery-actions button:disabled{ +.plan-gdd-stage-progress__delivery-actions button:disabled { opacity: 0.58; } -.plan-gdd-stage-progress__delivery-error{ +.plan-gdd-stage-progress__delivery-error { color: #b42323; overflow-wrap: anywhere; } -.plan-gdd-stage-progress__header strong{ +.plan-gdd-stage-progress__header strong { color: #27364a; } -.plan-gdd-stage-progress__header span{ +.plan-gdd-stage-progress__header span { padding: 3px 8px; border-radius: 999px; background: #eef6ff; color: #1f6feb; } -.gdd-approval-card__header{ +.gdd-approval-card__header { display: grid; gap: 5px; min-width: 0; @@ -7328,26 +7343,26 @@ iframe.preview-frame { .gdd-approval-card h2, .gdd-approval-card h3, -.gdd-approval-card p{ +.gdd-approval-card p { margin: 0; } -.gdd-approval-card h2{ +.gdd-approval-card h2 { font-size: 18px; } -.gdd-approval-card__header p{ +.gdd-approval-card__header p { color: #526173; overflow-wrap: anywhere; } -.gdd-approval-card__details-trace{ +.gdd-approval-card__details-trace { color: #6b7a8c; font-size: 11px; overflow-wrap: anywhere; } -.gdd-approval-card__details-trigger{ +.gdd-approval-card__details-trigger { justify-self: start; min-height: 30px; padding: 0 10px; @@ -7357,12 +7372,12 @@ iframe.preview-frame { background: #fff; } -.gdd-approval-card__decisions{ +.gdd-approval-card__decisions { display: grid; gap: 8px; } -.gdd-approval-card__decisions article{ +.gdd-approval-card__decisions article { display: grid; gap: 3px; padding: 9px 10px; @@ -7371,19 +7386,19 @@ iframe.preview-frame { background: #fff; } -.gdd-approval-card__decisions article span{ +.gdd-approval-card__decisions article span { color: #1f6feb; font-size: 12px; } -.gdd-approval-card__decisions article small{ +.gdd-approval-card__decisions article small { color: #526173; overflow-wrap: anywhere; } .gdd-approval-card__actions, .gdd-approval-card__dialog-actions, -.gdd-approval-card__recovery{ +.gdd-approval-card__recovery { display: flex; flex-wrap: wrap; align-items: center; @@ -7392,7 +7407,7 @@ iframe.preview-frame { .gdd-approval-card__actions button, .gdd-approval-card__dialog-actions button, -.gdd-approval-card__recovery button{ +.gdd-approval-card__recovery button { min-height: 32px; padding: 0 12px; border: 1px solid #cfd7e6; @@ -7402,17 +7417,17 @@ iframe.preview-frame { } .gdd-approval-card__actions button:nth-child(n + 2), -.gdd-approval-card__dialog-actions button:first-child{ +.gdd-approval-card__dialog-actions button:first-child { color: #27364a; background: #fff; } -.gdd-approval-card button:disabled{ +.gdd-approval-card button:disabled { cursor: not-allowed; opacity: 0.55; } -.gdd-approval-card__recovery{ +.gdd-approval-card__recovery { justify-content: space-between; padding: 9px 10px; border-radius: 7px; @@ -7421,7 +7436,7 @@ iframe.preview-frame { font-size: 13px; } -.gdd-approval-card__error{ +.gdd-approval-card__error { padding: 9px 10px; border-radius: 7px; color: #991b1b; @@ -7429,7 +7444,7 @@ iframe.preview-frame { overflow-wrap: anywhere; } -.gdd-approval-card__dialog-backdrop{ +.gdd-approval-card__dialog-backdrop { position: fixed; inset: 0; z-index: 220; @@ -7439,7 +7454,7 @@ iframe.preview-frame { place-items: center; } -.gdd-approval-card__dialog{ +.gdd-approval-card__dialog { display: grid; gap: 12px; width: min(520px, 100%); @@ -7450,28 +7465,28 @@ iframe.preview-frame { box-shadow: 0 18px 52px rgb(0 0 0 / 18%); } -.gdd-approval-card__details{ +.gdd-approval-card__details { max-height: min(760px, calc(100vh - 32px)); overflow: auto; } .gdd-approval-card__details header, -.gdd-approval-card__details article{ +.gdd-approval-card__details article { display: grid; gap: 5px; } -.gdd-approval-card__details article{ +.gdd-approval-card__details article { padding-top: 10px; border-top: 1px solid #e2e8f0; } -.gdd-approval-card__details p{ +.gdd-approval-card__details p { white-space: pre-line; overflow-wrap: anywhere; } -.gdd-approval-card__dialog textarea{ +.gdd-approval-card__dialog textarea { width: 100%; min-height: 110px; padding: 9px; @@ -7480,7 +7495,7 @@ iframe.preview-frame { resize: vertical; } -.gdd-approval-card__dialog-hint{ +.gdd-approval-card__dialog-hint { padding: 9px 10px; border-radius: 7px; color: #854d0e; @@ -7491,21 +7506,73 @@ iframe.preview-frame { /* 审批卡是这一列里唯一没有天花板的成员:Fast GDD 的决定项越多它越高,能把消息列表和运行 时面板一起挤出可视区。和 `.agent-runtime-status` 一样给它自己的上限加内部滚动,而不是 让它去挤别人。 */ +/* 策划链路的会话列比另外两条多一个成员:策划面(阶段进度 + 审批卡)。#193(5802048c3 + 「Codex/agent chat layout fix」)把这个容器从基础规则继承来的 `display: grid` 改成 + `display: block`、并把消息列表从 `flex: 1 1 auto` 改成 `height: 100%`,于是列表独吞 + 整列高度,排在它后面的策划窄条、待确认命令、输入框和状态行全部被 `overflow: hidden` + 静默裁掉——组件照常渲染、DOM 里查得到,只是位置落在裁剪线外,屏幕上什么都没有。 + 实测:窄条 top=858 而容器 bottom=788,超出 384px;scrollHeight 1235 vs clientHeight 734。 + + 同一批改动还删掉了 `.game-workbench-chat .project-supervisor-composer` 的 4 条规则, + 所以列表上那个 `padding-bottom: 196px`(本意是给「浮起来的输入框」留位)从来没有对应 + 的定位规则,那个模型压根没成立过。 + + `:has(.plan-gdd-surface)` 只在策划面在场时命中,做游戏与做素材保持 master 的 block + 列原样不动。用 flex 列而不是 grid:子元素个数随待确认命令的有无浮动,固定行模板会 + 错位;而且下面那条 `.plan-gdd-surface { flex: 0 0 auto }` 本来就是照 flex 写的,容器 + 变成 flex 之后它才真正生效——在此之前它一直是死声明。 */ +.game-workbench-chat .project-supervisor-conversation:has(.plan-gdd-surface) { + display: flex; + flex-direction: column; + gap: 10px; + /* 兜底:正常情况下上面的行分配已经让整列放得下,这条不会产生滚动条。但审批卡展开 + 到 52dvh 且窄条同时在场时仍可能超出,届时给滚动而不是继承 `overflow: hidden` + 的裁剪——这个 bug 的全部代价就来自「悄悄裁掉」,宁可多一根滚动条。 */ + overflow-y: auto; +} + +/* 整列里只有消息列表可收缩:其余成员各自有天花板(策划面按内容、窄条 52dvh 且内滚、 + 输入框定高),由列表吸收剩余空间。`height: 100%` 必须撤掉,否则它照旧独吞整列; + `padding-bottom` 也一并收回,输入框已经回到文档流里,不再需要给它留空。 */ +.game-workbench-chat + .project-supervisor-conversation:has(.plan-gdd-surface) + .project-supervisor-message-list { + flex: 1 1 auto; + height: auto; + min-height: 96px; + padding-bottom: 12px; + scroll-padding-bottom: 12px; +} + +/* 这三个成员不参与压缩:消息列表变长时不能把它们挤没——它们正是用户唯一需要动手的 + 地方(澄清卡、输入框)。 */ +.game-workbench-chat + .project-supervisor-conversation:has(.plan-gdd-surface) + .planning-lane-runtime-strip, +.game-workbench-chat + .project-supervisor-conversation:has(.plan-gdd-surface) + .project-supervisor-composer, +.game-workbench-chat + .project-supervisor-conversation:has(.plan-gdd-surface) + .project-supervisor-workspace-status { + flex: 0 0 auto; +} + /* 这个面不参与 flex 压缩。它自己不会长高:标题栏是固定几行,审批卡下面有自己的 max-height 和内滚,所以高度天然有界。让它可压缩的话,`overflow: hidden`(画圆角 要的)会把底部切掉——批准后交付行正好在那儿,表现是路径和两个按钮凭空消失。 */ -.game-workbench-chat .plan-gdd-surface{ +.game-workbench-chat .plan-gdd-surface { flex: 0 0 auto; } /* 只有审批卡在场时才需要「标题栏定高 + 正文占余下」的两行轨道。批准后卡片收掉, 面里只剩标题栏,这条声明会给它套上一个不存在的第二行。 */ -.game-workbench-chat .plan-gdd-surface--with-card{ +.game-workbench-chat .plan-gdd-surface--with-card { min-height: 0; grid-template-rows: auto minmax(0, 1fr); } -.game-workbench-chat .gdd-approval-card{ +.game-workbench-chat .gdd-approval-card { min-height: 0; max-height: clamp(240px, 52dvh, 640px); overflow-y: auto; @@ -7516,7 +7583,7 @@ iframe.preview-frame { /* 上面 24dvh/240px 的天花板是给做游戏链路常驻的调试面板设的。策划链路的窄条只在 需要用户动手时出现(澄清卡 / 失败恢复),内容是要读完再回答的题面,给它和审批 卡同级的空间。 */ -.game-workbench-chat .planning-lane-runtime-strip{ +.game-workbench-chat .planning-lane-runtime-strip { max-height: clamp(240px, 52dvh, 640px); } diff --git a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts index a7ffae767..e6ccb2fd3 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/home.suite.ts @@ -3,6 +3,7 @@ import type { ProjectSupervisorComponentProps } from '../../src/features/app-she import { WorkspaceLauncherShell } from '../../src/features/app-shell/WorkspaceLauncher'; import { act, + agentRuntimeUserInputRequest, App, cleanup, createGameCreationAppManifest, @@ -1611,6 +1612,93 @@ export function registerHomeProjectCreationTests() { ).toHaveLength(1); }, ); + + it('surfaces the planning clarification card after 做方案 creates the project from home', async () => { + // 上面那条只断言到「run 起来了、source 对」。真实故障恰好落在它之后:plan 根 run + // 停在 waiting-for-user-input 并带回澄清请求,而工作台一直停在前端本地的占位文案, + // 澄清卡永远不出现。策划链路现有用例全部走「打开已有项目 + 直接注入 initialRuntime」, + // 正好绕开首页自动建项目这条路,所以这个缺口一直没人守。 + const projectPath = '/tmp/home-planning-clarification'; + const manifest = createGameCreationAppManifest( + 'local-project-draft', + 'home-planning-clarification', + ); + const supervisorHarness = createProjectSupervisorRuntimeHarness({ + projectPath, + expectedRunProfile: 'standard', + }); + let planRootRunId = ''; + const invoke = vi.fn( + async (command: string, args?: Record) => { + if (command === 'pick_local_project_directory') { + return projectPath; + } + if (command === 'is_local_project_directory_non_empty') { + return false; + } + if (command === 'create_automatic_local_game_project') { + return { + projectPath, + manifestPath: `${projectPath}/.agent/manifest.json`, + manifest, + }; + } + if (command === 'start_game_creator_supervisor_runtime_task') { + planRootRunId = String(args?.runId ?? ''); + } + const result = await supervisorHarness.invoke(command, args); + if (command !== 'read_game_creator_agent_runtime' || !planRootRunId) { + return result; + } + // 后端此刻的真实形态:pending 是 user.input_request,读命令把澄清请求投影在 + // 结果的**顶层**(`AgentRuntimeResult.user_input_request`,与 `state` 平级), + // 前端的 agentRuntimeStateFromResult 也优先读顶层。放进 state 会被顶层的 null + // 盖掉,那是 fixture 写错,不是产品缺陷。 + const runtimeResult = result as { state: Record }; + return { + ...runtimeResult, + state: { + ...runtimeResult.state, + status: 'waiting-for-user-input', + phase: 'waiting-for-user-input', + }, + userInputRequest: agentRuntimeUserInputRequest({ + agentId: 'project-supervisor', + sessionId: supervisorHarness.sessionId, + runId: planRootRunId, + requestId: 'request-plan-round-1', + actionId: 'action-plan-round-1', + }), + }; + }, + ); + window.__TAURI__ = { + core: { invoke }, + event: { listen: supervisorHarness.listen }, + }; + renderLauncherAt('/?launcher', 'home', true); + + fireEvent.click(screen.getByRole('button', { name: '做方案' })); + const promptInput = screen.getByLabelText('创作想法'); + nativeClipboardMock.text = '2D射击游戏'; + fireEvent.paste(promptInput); + await waitFor(() => { + expect(promptInput.textContent).toContain('2D射击游戏'); + }); + fireEvent.keyDown(promptInput, { key: 'Enter', code: 'Enter' }); + + await waitFor(() => { + expect(invoke).toHaveBeenCalledWith( + 'start_game_creator_supervisor_runtime_task', + expect.objectContaining({ source: PROJECT_SUPERVISOR_PLAN_SOURCE }), + ); + }); + + const strip = await screen.findByLabelText('立项策划运行状态'); + expect( + within(strip).getByText('首版角色规范图采用哪种美术方向?'), + ).not.toBeNull(); + }); it('refreshes Direct Codex art commits while the turn is still running and after a later failure', async () => { const projectPath = 'C:\\Users\\tester\\Documents\\Genarrative GameAgent\\live-direct-art';