diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index a607f465c..b0e8757a3 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -8503,19 +8503,24 @@ iframe.preview-frame { white-space: nowrap; } -/* 右侧对话采用简洁的 Codex 式消息流与编辑框,减少卡片嵌套和视觉噪音。 */ +/* 右侧对话采用简洁的 Codex 式消息流与编辑框,减少卡片嵌套和视觉噪音。 + 消息列表这只盒子就是"对话框"本身:它铺满整个会话区,输入区作为框内的一块内聚区块 + 浮在它底部(见下面 `.project-supervisor-composer` 的内缩),两者不再各画一只盒子 + 上下贴在一起。所以这里底边归 0,并用 `padding-bottom` 给输入区留出位置—— + 256px = 输入区最高约 230px(编辑器 min 96 / max 140 + 操作排 + 内边距)+ 12px 内缩 + + 约 14px 间距,滚到底时最后一条消息不会被输入区盖住。 */ .game-workbench-chat .project-supervisor-surface.is-direct-codex .project-supervisor-message-list { position: absolute; top: 0; right: 0; - bottom: 104px; + bottom: 0; left: 0; gap: 34px; height: auto; - padding: 18px 20px 24px; - scroll-padding-bottom: 24px; + padding: 18px 20px 256px; + scroll-padding-bottom: 256px; border: 1px solid var(--platform-subpanel-border); border-radius: 12px; background: var(--platform-input-fill); @@ -8559,14 +8564,17 @@ iframe.preview-frame { color: var(--platform-text-strong); } +/* 输入区是对话框内部的一块:四边内缩 12px,不贴外框、也不与外框的描边重叠。 + 下方那条 `.project-supervisor-composer.is-direct-codex`(同类选择器多一个类,优先) + 是最终生效的那份几何,这里保持同一组内缩值,避免两条绝对定位规则给出两个位置。 */ .game-workbench-chat .project-supervisor-surface.is-direct-codex .project-supervisor-composer { display: block; position: absolute; - right: 0; - bottom: 0; - left: 0; + right: 12px; + bottom: 12px; + left: 12px; z-index: 5; padding: 0; pointer-events: none; @@ -9823,6 +9831,9 @@ iframe.preview-frame { /* Direct Codex composer: keep the editor and its actions in one vertical structure instead of positioning each control independently. */ +/* 输入区在对话框(`.project-supervisor-message-list` 那只铺满会话区的盒子)内部: + 左右下三边各内缩 12px,圆角收成与外框同一档,读起来是"框内的输入块"而不是贴在 + 对话框下面、四边描边与下边竖线连成一体的第二只盒子。 */ .game-workbench-chat .project-supervisor-surface.is-direct-codex .project-supervisor-composer.is-direct-codex { @@ -9832,13 +9843,13 @@ iframe.preview-frame { gap: 0; align-items: stretch; position: absolute; - right: 0; - bottom: 0; - left: 0; + right: 12px; + bottom: 12px; + left: 12px; z-index: 5; padding: 8px 12px; border: 1px solid var(--platform-surface-border); - border-radius: 14px; + border-radius: 12px; background: var(--platform-input-fill); box-shadow: 0 8px 24px rgb(31 41 55 / 8%); pointer-events: auto; 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 b5040611a..38c3d28b3 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 @@ -4756,7 +4756,7 @@ export function registerProjectWorkbenchFoundationTests() { /\.game-workbench-chat \.project-supervisor-message-list\s*\{[^}]*height:\s*100%[^}]*min-height:\s*96px[^}]*overflow-y:\s*auto[^}]*padding-bottom:\s*196px[^}]*scroll-padding-bottom:\s*196px/s, ); expect(styles).toMatch( - /\.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, + /\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s*\{[^}]*position:\s*absolute[^}]*bottom:\s*0;[^}]*left:\s*0/s, ); expect(styles).toMatch( /\.game-workbench-chat \.agent-runtime-status\s*\{[^}]*max-height:\s*clamp\(120px, 24dvh, 240px\)[^}]*overflow-y:\s*auto/s, @@ -4897,6 +4897,61 @@ export function registerProjectWorkbenchFoundationTests() { ); }); + it('keeps the chat composer an inset block inside the conversation dialog', () => { + const styles = readFileSync( + resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'), + 'utf8', + ); + // 对话框就是消息列表那只铺满会话区的盒子;输入区是它内部的一块,不再是贴在它下边、 + // 四边描边与它连成"两只盒子紧挨着"的第二只盒子。 + const messageListRule = + styles.match( + /\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-message-list\s*\{([^}]*)\}/s, + )?.[1] ?? ''; + expect(messageListRule).not.toBe(''); + expect(messageListRule).toContain('top: 0;'); + expect(messageListRule).toContain('right: 0;'); + expect(messageListRule).toContain('bottom: 0;'); + expect(messageListRule).toContain('left: 0;'); + + const composerRule = + styles.match( + /\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-composer\.is-direct-codex\s*\{([^}]*)\}/s, + )?.[1] ?? ''; + expect(composerRule).not.toBe(''); + // 输入区四边内缩:不贴外框(左右下三边各 12px),也不与外框重叠。 + const inset = styleNumber(composerRule, 'right'); + expect(inset).toBeGreaterThan(0); + expect(styleNumber(composerRule, 'left')).toBe(inset); + expect(styleNumber(composerRule, 'bottom')).toBe(inset); + expect(composerRule).toContain('position: absolute;'); + // 输入区自己那只盒子还在(拆掉边框/底色会让输入区变成没有边界的裸文本)。 + expect(composerRule).toContain( + 'border: 1px solid var(--platform-surface-border);', + ); + expect(composerRule).toContain('background: var(--platform-input-fill);'); + + // 消息列表给输入区留出位置:底边留白必须 ≥ 输入区最高高度 + 内缩。 + // 输入区最高高度 = 编辑器 max-height(140) + 操作排(30) + 网格间隙(8) + 输入区 + // padding-bottom(4) + 自己的上下 padding(16) + 控制排(30 + 2) ≈ 230。 + const composerClearance = styleNumber( + messageListRule, + 'scroll-padding-bottom', + ); + expect(composerClearance).toBeGreaterThanOrEqual(inset + 230); + const messageListPadding = + /padding:\s*18px 20px (\d+)px;/u.exec(messageListRule)?.[1] ?? ''; + expect(Number(messageListPadding)).toBe(composerClearance); + + // 编辑器与操作排的高度上限就是上面那个 230 的来源;改这里就必须同步改留白。 + const editorRule = + styles.match( + /\.game-workbench-chat\s+\.project-supervisor-surface\.is-direct-codex\s+\.project-supervisor-composer\s+\.resource-reference-input-editor\s*\{([^}]*)\}/s, + )?.[1] ?? ''; + expect(styleNumber(editorRule, 'max-height')).toBe(140); + expect(styleNumber(editorRule, 'min-height')).toBe(96); + }); + it('keeps workbench chat bubbles aligned without shrinking process cards', () => { const styles = readFileSync( resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),