AGC 用户输入区按三层重排布局,修掉控件叠压(#600) #607
Reference in New Issue
Block a user
Delete Branch "fix/agc-composer-layout"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #600
背景与根因
AGC 工作台右侧对话面板的输入区有两处结构性缺陷:
flex: 0 0 auto,整组向右溢出输入盒(360px 面板溢出 34px、280px 溢出 114px)。改了什么
.project-chat-composer.is-direct-codex单列网格gap: 8px:控件全部集中在唯一一行工具条(左组@/+;右组 模型 / 语音 / AI 润色(紧贴发送左侧,同为圆角矩形按钮)/ 发送,flex-wrap: nowrap),状态/提示层只在有内容时占行;窄宽度用min-width(控件排 252 / 外壳 278,算式见下)+ 横向溢出可见,不换行、不堆叠;输入盒最高高度12 盒内上内边距 + 160 编辑器 + 0 输入区行距 + 0 润色状态行 + 0 输入区下内边距 + 8 输入盒与工具栏行距 + 30 工具栏 + 12 盒内下内边距 = 222(工具栏行高由 30px 模型钮决定;那三个0里中间一个是输入盒内部的润色状态行——它条件渲染、空态为 0 高、有内容时自带 6px 上间距;输入盒下方那条project-chat-composer-notice(语音/附件回执)是 form 的独立行,不进 222,仍是内联行)当前:X+ 原生<input type="range">)嵌在菜单顶部,与模型列表同一张卡;档位枚举 / 默认值 / 持久化通道与旧下拉完全一致pointercancel/ 卸载 flush,一个手势只写一次;同档位重复提交去重;外部改配置后重读并重新种「最近落盘值」--platform-accent且终点 = 圆钮中心(calc(10px + var(--strength-ratio) * (100% - 20px)))、圆钮 20px 实心暖白 + 1px 描边 + 柔影 + 2px 间隙焦点环;档位圆点已删除;强度区左右不留内边距(滑块铺满卡片内容宽度:档位 0 / 4 时圆钮与轨道两端贴齐,已选段终点落在圆钮中心、被圆钮盖住)tone=error、状态tone=neutral;任何宿主的输入区都不留常驻[role='alert']/[role='status']文本行.conversation-model-select …、.project-chat-composer-strength-*),不带宿主祖先链;首页与工作台共用同一份,宿主作用域只剩工具栏几何关键验收证据
0.25→0.5→0.75→1逐步跟手,整段 1 次落盘测试与 CI
npx vitest run tests/appSurface.test.ts tests/chatDialogFrameLayout.test.ts→ 224 passed / 9 skipped(Test Files 2 passed;tests/appSurface.test.ts219 tests / 9 skipped,其余在chatDialogFrameLayout.test.ts)npm run agc:typecheck(pass 3 / fail 0)、npx eslint(含simple-import-sort)、npx prettier --check、npm run check:encoding(5268 files)、npm run check:spacetime-schema(91 tables)、git diff --check全绿96859666f(#606 / #621 / #624 …),合并提交342cc75d2;冲突只出现在docs/project-memory/shared-memory/pitfalls.md,两侧段落都保留已知边界 / 未验证项
platform-theme--light);forced-colors已有系统色描边回退。overflow: visible,不裁切、不遮输入框),未加夹取策略。对照图
min-width的极端窄宽)改后即为空输入态实拍:工具条仍是单行、不换行不重叠,外壳按 278px 最小宽度横向溢出、发送钮仍在可视区内(elementFromPoint命中它):justify-content: flex-start(改前是space-between,文案被顶到行右端):.tmp/agc-composer-600/、.tmp/600-restyle/(目录被 gitignore,按约定不入库)。3a70028461tof5886560bd- 目的:满足 CI 的 base 门禁(本分支不含 master 当前 tip),按用户要求用 merge 而非 rebase - 合并未产生冲突;两侧内容逐条保留:`tests/appSurface/project-development.suite.ts` 同时含本分支的 composer 几何断言(`toContain('gap: 8px;')`)与 master 的 appSurface / Tauri 替身收口(`tauriRuntimeFake.ts` 等);`pitfalls.md` 同时含本分支 #600 条目与 master 的测试类型门禁条目 - 提交前核对:合并结果相对 origin/master 的两份非 CSS 文件差异只有本分支那 2 处(suite.ts 6 行、pitfalls 8 行) - master 既有红(check:nginx-spa-routes 缺 /pay、/profile/payment;external_mcp::semantic 32 vs 30;http_tracing 偶发;AGC Rust shard 4/4)由 fix/ci-master-red 处理,不在本分支修- 测试:`composerMaxHeight()` 的工具栏行高改取该排最高控件的 `Math.max`(模型触发钮 30px > 发送钮 28px),输入盒最高高度 254 → 256,并新增「触发钮高度 = 30」断言,不再写死发送钮 - `appSurface` 的编辑器高度断言改钉生效规则(六类选择器那条 72 / 160),原五类规则的 96 / 140 已随死重删除 - Direct 推理档的 `flex: 0 0 auto` 写回其所有者规则(原 `flex: 0 1 auto` 那条),末尾区块不再重复声明同属性;区块头注明本区块不依赖源码顺序 - 删死重(均恒被更高特异性规则覆盖):≤1000px 媒体块里五类的 `.resource-reference-input { padding: 10px 12px 6px }`;五类输入规则的 `display` / `width` / `padding` / `border-color` / `border-radius` / `background`(保留仍生效的 `color` 与 `pointer-events`);五类 `:focus-within` 整条(被六类 `border: 0; outline: none; box-shadow: none` 恒覆盖);五类编辑器规则的 `min-height` / `max-height` - 校验:`chatDialogFrameLayout.test.ts` 12/12、`appSurface.test.ts` 196 passed / 9 skipped、`npm run agc:typecheck` 通过、prettier 通过- `:focus-visible` 的圆钮阴影改成三明治:`0 0 0 2px var(--platform-input-fill)`(间隙,卡片/输入区填色的既有**颜色** token)+ `0 0 0 4px var(--platform-input-focus-ring)`(环色与厚度不变,WCAG 对比度不回退)+ 原暖影 - 踩坑记录在注释里:间隙色**不能**用 `--platform-panel-fill`——它是 `linear-gradient`,写进 `box-shadow` 会让整条声明失效(环和影子一起消失,实测键盘态环与投影全丢) - Firefox 同步:`::-moz-focus-outer { border: 0 }` 关掉轨道自带描边,`::-moz-range-thumb` 用同一套三明治 - 取证(无头 Chromium + 像素扫描):键盘态中档 y=289 行色序 199,101,61(填充) → 182,98,62(影) → 250,242,236(**间隙**) → 229,210,195(钮描边) → 252,248,244(**钮内暖白**) → … → 300:182,98,63(**环**) → 231,213,198(未选段);鼠标拖拽态同位置只有 252,248,244 钮白 + 柔影、无任何主色 - 校验:typecheck;`workbenchThemeContrast` + `chatDialogFrameLayout` + `appSurface` 共 215 passed / 9 skipped;prettier / encoding / spacetime-schema / diff-check 全通过- 用户口径:「你干脆把圆点删了吧,很丑…要做就做成几乎透明的」→ 按第一选择**删除**(不做半透明备选) - 标记:`.project-chat-composer-strength-slider` 里那组 `<span class="project-chat-composer-strength-stops">` + 5 个点整体删除 - CSS:`space-between` 排布、`z-index: 2` 抬层、`.is-active{opacity:0}`、点的 `color-mix` 底色、以及只服务点对齐的 `padding: 0 7px` 一并删除;强度区横向内边距 `4px 6px 2px` → `4px 0 2px`(滑块直接铺满卡片内容宽度,填充段与圆钮两端与轨道贴齐,不再留多余空白);滑块容器去掉已无用的 `position: relative` - 保留:圆钮(实心暖白 + 柔影 + 2px 间隙焦点环)、filled 段铺满轨道高度 6px、`--strength-ratio`、`当前:X`、`aria-valuetext`、←/→/Home/End、点轨道跳档、拖动连续跟手(预览 / 松手提交) - pointercancel:触屏取消时与「松手」同口径提交当前预览(否则预览值会滞留到下一次提交或卸载 flush) - 测试:`chatDialogFrameLayout.test.ts` 原来那组「点」断言整段换成**反向守卫**(样式表里不再有 `.project-chat-composer-strength-stops` 规则、组件源码也不再渲染该类名;滑块契约:宽 100% / 高 26 / 圆钮 20×20、强度区左右内边距 0);`home.suite.ts` 首页用例补一条「菜单里不再有那组点」 - 文档:pitfalls 的 D1 条改为现行口径(圆点已删、原因与判据;历史成因「被 6px 轨道盖住 / 与圆钮错位 ±12.2px」只留一句、由 Git 追溯) Refs #600, PR #607- `conversationModelSelect.test.tsx` 两处同步 `screen.getByText('…')` 改为 `await screen.findByText(…)`:状态文案现在渲染在 portal 的 toast 里(发布 → 宿主 re-render 一次),同步查询在慢机/并行下会竞态(CI 上就是这样红的) - 本地:`npx vitest run tests/conversationModelSelect.test.tsx` 29 passed;`appSurface + chatDialogFrameLayout` 225 passed / 9 skipped Refs #600, PR #607- D-B:`voiceNotice` 只在 mic 的 `onNotice` 里 set、从不清理(会常驻到组件卸载)→ 改成「拿到转写就收掉」(`onTranscript` 里 `setVoiceNotice('')`);提示是上一次动作的回执,选这个时机而不是 6s 定时,避免与 toast 的 6s 语义混在一起 - D-B 用例(改前红):`chat-composer.suite.ts`「语音提示是上一次动作的回执:拿到转写后不再常驻」——录音 + `onerror({error:'network'})` → `project-chat-composer-notice` 出现「语音识别服务…」;再 `emitTranscript('做一个小游戏')` → 该行消失且节点脱离文档(旧实现里它一直在) - D-C:模型触发钮最窄可缩到 64px、名字走省略号但没有 `title`,截断后无从查看 → 触发钮补 `title={triggerLabel}`(与显示名同源),并加属性断言:`title` == 触发钮文本 - 测试加强(评审说偏声明级):`chat-composer.suite.ts` 新增「AI 润色钮在工具条这一行里(DOM 位置级)」——润色钮必须位于 `.project-chat-composer-controls` 内、父链在 `-controls-actions` 挂点里、不在 `.resource-reference-input` 里、空态工具条外没有第二行按钮(用真实挂载的 Direct 输入区断言);同排几何(top 差 ≤1px、两两不相交、七宽度)由无头 Chromium 夹具实测(240/280/296/320/360/438/900 全通过) - 校验:`appSurface + chatDialogFrameLayout` **227 passed / 9 skipped**;typecheck pass 3 / fail 0;eslint、prettier、encoding 5327 files、spacetime-schema 92 tables、diff-check 全绿 Refs #600, PR #607- 口径:AI 润色 / 恢复原文与发送钮同为圆角矩形按钮,放一起更协调 → 从「左组 `@` `+` 之后」挪到**右组最末、发送钮左侧**,最终顺序 `@` `+` | 模型 麦克风 润色 发送(`space-between` 左右分组不变) - 实现:portal 挂点搬进右组(`DirectProjectComposer` 里把 `.project-chat-composer-controls-actions` 移到 `-controls-right` 内、发送钮之前),`actionsPortalTarget` 的「可选 prop、不传则行为不变」契约保持;用显式槽位而不是 CSS `order`,因为 DOM 顺序即 Tab 与读屏顺序(现在就是 模型 → 麦克风 → 润色 → 发送,与视觉一致) - 注释/文档同步:composer 与 styles.css 里「与 `@` / `+` 同组、排在左组最后一个」那类旧口径全部改成新口径(「与发送同为圆角矩形按钮、相邻成组」),pitfalls 同段一并更新;全仓 grep `输入类动作` / `生成类动作` / `排在左组` / `左组最后一个` 命中 0 - 测试:`chat-composer.suite.ts` 的 DOM 断言改成新挂点——润色钮必须在 `.project-chat-composer-controls` 内、父链是 `-controls-right`、**`slot.nextElementSibling` 就是发送钮**、且 `compareDocumentPosition` 证明润色在发送之前;`chatDialogFrameLayout.test.ts` 的源码顺序断言改成「挂点在 `-controls-right` 之后、`{canStopTurn` 之前」;空态「工具条之外无第二个按钮」计数仍为 6;七宽度单行/不相交用例不变(含新位置) - min-width 复核(无头 Chromium 夹具,240/280/296/320/360/438/900 七档):控件排仍 **252px** 所需、外壳仍 **278px**;自然宽度 290.4(438/900 档)→ 算式不变;七档 `topSpread ≤ 1px`、控件两两相交 **0** 处、`outsideControlsRow` 空、润色钮与发送钮间距固定 **8px**(同一行、同一组) - 截图:240px 改前→改后 + 438px 改后 + 438px toast 已换新(正文引用与附件 1:1:12 = 12,未引用的旧图已删) Refs #600, PR #607合并 master 76166b08c(模型目录不可用项 / 管理端等):保留双方条目 - apps/ai-game-creator-shell/src/services/clientApi.ts:取 master 的 typed 目录读取(`ClientLlmModelCatalog` 已含 unavailableModels,不再手写 agentMode 映射),但保留本分支的文案「模型服务不可用」(本 PR 的口径,master 那侧还是旧的「本地…」) - ConversationModelSelect.tsx:import 取并集(`Lock` + `ReactNode`),渲染结构保留本分支的「强度区外壳」(`strengthSection ? <shell>{strengthSection}{modelMenu}</shell> : modelMenu`)——master 把菜单内联展开、并加了不可用项列表,那段已自动合并进 `modelMenu` 变量里,两边语义都在 - tests/conversationModelSelect.test.tsx:取 master 的新文案「所选模型已下线,已切换为默认模型」,保留本分支的异步查询(`await screen.findByText`,文案走 portal toast) - docs/project-memory/shared-memory/pitfalls.md:两侧条目都保留(本分支在前,master 新增在后) - 校验:typecheck pass 3 / fail 0;`appSurface + chatDialogFrameLayout + conversationModelSelect` 257 passed / 9 skipped合并 master 52c83cc2a(模板 templateVersion 递增,#643):零冲突 - master 只动 template-library/v1/{blank-3d-scene,phaser-2d-starter,threejs-3d-starter}/meta.json 各 1 行,与本分支不重叠 - 验证:ancestor rc=0、工作树干净;门禁:`vitest` appSurface + chatDialogFrameLayout + conversationModelSelect = 257 passed / 9 skipped、`agc:typecheck` pass 3 / fail 0、`git diff --check` rc=0、模板发布守卫 `node --test scripts/agc-template-library-publish.test.mjs` 28 passed / 0 failed7a32fec1cbto76ad99d2f7- 现象(用户截图 446×330):刷新行的图标在左、「刷新模型列表」文案被顶到行右端 - 根因:`styles.css:11706`(本次修复前)宿主作用域那条「抵消通用按钮样式」的规则统一写了 `justify-content: space-between !important`(8 类 + `!important`)→ 特异性压过 `styles.css:11751` 的 `.conversation-model-menu-refresh { justify-content: flex-start !important }`(1 类) - 改法:宿主那条块**不再写对齐**(只留 position/display/align-items/width/margin/border/shadow/字号),对齐按角色分到共享样式面——`.conversation-model-select .conversation-model-menu button[role='option'] { justify-content: space-between !important }`(选项行名字左 / 勾右),刷新行继续用 `.conversation-model-menu-refresh { justify-content: flex-start !important; gap: 8px !important }`(gap 从 7px 收到卡片内既有的 8px 节奏,与工具条列间距同值,不新造数) - 实测(无头 Chromium,438px / 280px):卡片 `padding-left: 12px`;**列表行 left = 刷新行 left = 459.5**(438 档;280 档同为 380.5);图标 left = 行 left + 行内边距 10 = 469.5;文本 left = 图标右缘 482.5 + **8.00px** = 490.5;计算值 `justify-content: flex-start`、`gap: 8px`;文本右缘 562.5 ≪ 行右缘 693.5(改前会贴近右端) - 测试:`chatDialogFrameLayout.test.ts` 新增「模型菜单刷新行:图标与文本贴最左,选项行才用 space-between」——刷新行必须解出 `flex-start` + `gap: 8px`,选项行必须仍是 `space-between`(宿主块那条 8 类规则会被一并算进层叠求值,写回对齐就会变 `space-between` 而红)。**改前红(实测)**:把 `justify-content: space-between !important` 写回宿主块 → `expected 'space-between' to be 'flex-start'`(1 failed / 15 skipped);移除即绿 - 校验:`vitest run appSurface + chatDialogFrameLayout + conversationModelSelect` **258 passed / 9 skipped**;typecheck pass 3 / fail 0;eslint(TS 部分)0 error;prettier 全绿;encoding 5402 files;spacetime-schema 93 tables;`git diff --check` rc=0 Refs #600, PR #607