From bdb07c141b99111f7d65da9a3780db63868ec77a Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Fri, 11 Sep 2026 01:00:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=8A=E5=A4=A9=E8=BE=93=E5=85=A5=E5=8C=BA?= =?UTF-8?q?=E6=94=B6=E5=9B=9E=E5=AF=B9=E8=AF=9D=E6=A1=86=E5=86=85=E9=83=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根因:`.project-supervisor-surface.is-direct-codex` 下消息列表与输入区是两只各自带边框、 各自绝对定位的盒子——列表 `bottom: 104px`、输入区 `bottom: 0; left/right: 0`,两盒上下 紧贴。输入区自己那只盒子的左右描边与列表的左右描边接成两条贯穿整栏的竖线,加上列表 只留 24px 底部内边距,最后一条消息还会被不透明的输入区盖住。观感就是"输入框紧挨着 对话框、两侧有突兀竖条"。 - styles.css:消息列表(direct-codex)改为铺满整个会话区(`bottom: 104px` → `0`),它自己 那只带描边和圆角的盒子就是"对话框";`padding-bottom` 与 `scroll-padding-bottom` 由 24px 提到 256px,给框内输入区留出位置(256 ≈ 输入区最高 230px + 12px 内缩 + 约 14px 间距)。 - styles.css:输入区(direct-codex,含同类选择器共两条绝对定位规则)由贴边改为框内内聚 区块:左右下三边各内缩 12px,圆角 14px 收到与外框同一档 12px;边框、底色、阴影继续用 既有 token(`--platform-surface-border` / `--platform-input-fill`),不新增配色。 - 不改交互语义:输入区仍是同一棵 DOM(编辑器、@ 引用、AI 润色、模型选择、提交按钮全部 原位),只改它在对话框里的落点与留白。 - project-development.suite.ts:新增守卫——对话框盒子四边归 0、输入区三边内缩同一个值、 仍是绝对定位且保留边框与底色、消息列表留白 ≥ 输入区最高高度 + 内缩且与 `scroll-padding-bottom` 同值、编辑器 min/max 高度就是留白算式的来源;并同步既有那条 钉 `bottom: 104px` 的断言为 `bottom: 0`。 - 说明:本机无无头浏览器、vitest 未开 `css: true`,像素级观感只能靠真机复核;这里钉的是 声明级几何关系与"输入区仍在同一棵渲染树里"。 --- apps/ai-game-creator-shell/src/styles.css | 33 +++++++---- .../appSurface/project-development.suite.ts | 57 ++++++++++++++++++- 2 files changed, 78 insertions(+), 12 deletions(-) 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'),