进行中
思考过程
两张图是同一套栅格,先复刻列表页再拿它的商品卡去拼设置页,可以省一半工。
执行计划 · 4 步
复刻商品列表页
首页.png抽出商品卡为共享组件
card.html按同一套间距做设置页
商品卡设置页.pngsettings.html+140−0构建产物,看能不能跑通
接上两页之间的跳转
- 壳头的球与扫光都在动;秒数取
nowMs,静态页里定在一个时刻
⚠️ 这份页面还没上字体 —— 现在量出来的行高 / 文本宽度 / 折行位置 / 卡片高度都是回退面 PingFang SC 的, 不是产品真实的 Albert Sans / JiduMono Pro, 而稿子那一侧是自带内联字体的,比出来的差异会整批带偏。
先跑 node docs/design/chat-mirror/inline-fonts.mjs(再跑 check-fonts.mjs 确认退出码 0),然后再量、再截图、再下结论。
这一页里的每一格都是我们的组件渲染的,数据全部走一遍真实事件流。
编号与 docs/design/chat-matrix/matrix-82.html 一致,两页并排开着逐格对照即可。
覆盖执行记录(组件 7 / 9 / 10 / 11 / 12,第 1–11 格)、理解段(组件 3 / 4 / 5 / 8,第 12–27 格)、
产出收尾(组件 13 / 14 / 15 / 16 / 24,第 28–44 格)、输入(组件 1 / 2 / 21 / 23,第 45–69 格里的 20 格)与
边界(组件 6 / 17 / 18 / 19 / 20 / 22,第 70–84 格)。
各族性质不同,别拿同一把尺子看:执行记录、暂停、重连是这次新建的,每格数据都走一遍真实事件流; 意图澄清 / 记忆卡 / 总结文案 / 产物卡 / 回合状态行 / 下一步引导 / Queue 在产品里已经有生产实现, 这里挂的就是那些现成组件 —— 那几格照出来的是「现有实现离稿子有多远」,要做的是对齐,不是再造。
出不来的格子照样出格,每一格都写清是卡在行为、数据 / 契约、产品裁决, 还是这一页本身够不着(静态标记没有布局、没有 React state、渲染不了 portal)。 没有为了让某一格好看去改组件的行为、样式或默认值,也没有拿近似糊过去。 平铺形态下工具行的缩进(S7)与壳内叙述的颜色档(S22)在下面逐格标了。
页面最后还有一段「端到端 · 真实形态回归」 —— 那几格不是交付稿那几格里的格子, 没有对应的设计稿截图、也不参与逐格比对。它们喂的是一整条真实会话, 用来照「逐格摆拍照不到的分支」和「组件单摆好看、串起来不对」。
两张图是同一套栅格,先复刻列表页再拿它的商品卡去拼设置页,可以省一半工。
首页.pngcard.html商品卡设置页.pngsettings.html+140−0nowMs,静态页里定在一个时刻两张图是同一套栅格,先复刻列表页,再拿它的商品卡去拼设置页。
首页.pngtokens.cssproduct-list.html+182−0product-list.css+96−0product-card.html+64−0product-list.html+6−58设置页.pngsettings.html+140−0product-list.html+4−2首页.pngtokens.css规范.pdf失败首页.pngtokens.css设置页-会员中心-商品卡对齐稿-第三轮评审-f….png规范.pdf失败.ti 是空的),而稿子别处同名的行已经换成图标(8/21 版);我们跟的是新的那一版,请确认settings.html+140−0product-list.html+6−58bundle.js失败.ti 是空的),而稿子别处同名的行已经换成图标(8/21 版);我们跟的是新的那一版,请确认tool_result 是终端内容的唯一载体,命令跑完才一次性到达。要做需要 daemon 新增一条工具进度事件 —— 待产品/设计裁决✓ ✗ 判;设计稿只给了成品截图,没给判定规则 —— 待确认align-items)全部出自这里 —— 是稿子那张静态图内部不自洽,不是实现走样pixel-liquid.js 21:02 版 → PixelLiquid / runtime/pixel-liquid.ts,color_frag 一字未改,速度场用 curl noise 顶替上游的 GPU 解算)。⚠️ 这一页照不出它:陈列页走 renderToStaticMarkup,只出 DOM 不跑 effect,<canvas> 在这里永远是空白的 —— 格子里看到的是它底下 --chat-bg-subtle 那层底色,不是动效坏了。要看真东西请起本地 runtime.ti 是空的),而稿子别处同名的行已经换成图标(8/21 版);我们跟的是新的那一版,请确认意图澄清:现有实现能用,但和稿子有 7 处不一样 —— 逐条列在这儿,好判断要改哪些、哪些是稿子该跟实现走。
| 项 | 现在的实现 | 稿子 |
|---|---|---|
| 卡头文案 | 圆形「?」头像 + agent 给的表单标题 | 图标 + 固定一句「还需要确认一件事」 |
| 选项排布 | 横排胶囊,一行排开 | 竖排一列,每项左边一个选择框 |
| 自己填 | 末尾一枚虚线「其他」胶囊 | 和其它选项同列的一条「自己填」 |
| 主按钮 | 发送答案 | 下一步 |
| 次按钮 | 一键跳过 | 跳过 · 你来判断 |
| 脚注 | 有一行提示语 | 没有,只有两个按钮 |
| 已回答 | 整张表单锁住(灰掉的原表单 + 「已回答」角标) | 收成一条陈述:「已确认 · 商品卡 — 沿用列表页那张」 |
最后一条不是样式问题,是形态问题:锁住的表单和一句陈述,占的高度差好几倍 —— 一轮里问过三次,差别很明显。
记忆卡:现在是一行 chip(条目直接铺在行内);稿子是可折叠的,收起只留「已记住 3 条偏好」,展开才看内容。
两张图的栅格看着是同一套 —— 先量一下列宽和沟槽,对得上就只复刻一次列表页,商品卡抽出来给设置页共用;对不上再分开做。
规格那份 md 里写了断点,得先读完再定。要是断点和图里的列数对不上,以 md 为准 —— 图是某一个宽度下的截屏,md 才是规则。
商品卡里有价格行。价格是数字,得走等宽,不然一列价格对不齐;但商品名是中文,跟着正文字体走。
stream() 一致先量一下列宽和沟槽。
好,先把列表页搭起来,再拿同一张商品卡去做设置页。
两张图的栅格看着是同一套,先量列宽。
tokens.css明白了,我先把商品列表页复刻成能点的原型,再按同一套间距和圆角做设置页。整个过程你可以随时打断。
pnpm tools-dev run web)。没有为了让它在这一页出现去改组件。静态页照不出动画,要看得起真实页面QuestionFormView(1737 行)+ 解析器(832 行),不是新写的组件
这一族在产品里全部有生产实现(音频那两格除外),所以下面挂的都是现成组件 ——
总结文案是 renderMarkdown、产物卡是 FileOpsSummary 里的 ArtifactCards、
回合状态行是 AssistantFooter / AssistantFeedback、下一步引导是 NextStepActions。
照出来的是「现有实现离稿子有多远」,要做的是对齐,不是再造。
这一族现在没有出不来的格子 —— 早先那张「三格出不来」的表已经过期,三格后来都上了页; 留在下面的是各自还欠着的那一半,别把它们当成「画不出来」:
| 格 | 还欠着哪一层 |
|---|---|
| #38 分叉分界 | 形态画出来了;行为还没接 —— Fork 今天是跳走,原地不留痕迹,要留还缺「从哪条消息分叉 + 源会话标题」两个字段(要动契约 + daemon) |
| #40 反馈弹窗 | 面板已抽成 AssistantFeedbackReasons,能单挂;真实触发仍由 React state 驱动,静态页点不出来 |
| #43 / #44 音频 | 组件与采样规则已建;数据那一半还欠 —— 音频进不了产物卡(artifactCardKind 对 .mp3 返回 null),波形与时长契约里也没有(T17) |
第 41 / 42 格挂的是「现状」不是对齐后的样子:内容被 T12 卡住 —— 稿子要的是「跟本轮相关的三条建议」, 而事件流与契约里没有任何这样的字段,产品渲染的是固定的设计工具箱目录。挂现状是为了让人看见这个差。
✅ 建页时照出的三条实现缺陷,现在三条都修好了 —— 这一段以前写的是「都没有改」,那是旧话:
①14:32 只传给「没有反馈按钮」那条分支 → 现在两条分支都传;
②.assistant-feedback-wrap 的 inline-flex + max-width 把整行缩成 220.6px、弹簧撑不开
→ 现在是 flex + width:100%,时间贴得到右端(footer-time.test.tsx 钉着);
③第 39 格(中断)照出绿勾 + 绿字 → 换勾那条规则现在也排除了 data-canceled,中断档是灰点 + --text-muted。
商品列表页和设置页都做完了,商品卡已经抽成共享组件
renderMarkdown(ProseBlocks 未导出,但它内部调的就是这一个)chat/useCharReveal.ts 是在真实 DOM 上按节点切字、逐个入场(W9),SSR 出来的标记里没有 .rv 这一层。没有为了让它出现去改组件 —— 要看它得起真实页面.caret)整个删了,产品当时仍挂着 data-stream-cursor 的闪烁 ::after;用户指着表单加载态那一格拍板「把这个光标干掉,什么地方都不准出现」,于是两枚光标(正文的 ::after 和实时代码的 .live-code-caret)连同 @keyframes 一起删了。钉在 tests/components/chat/stream-cursor-removed.test.tsx<done/> 分界,而 daemon 侧的 prompt 目录里没有任何 agent 会发这个标记 —— 28 / 29 两格今天在产品里渲染上零区分,差的不是画法商品列表页和设置页都做完了,商品卡已经抽成共享组件,两页共用同一套间距与 12px 圆角。右边可以直接点着走。
.say b 只加字重不改色(两者已同色);我们走 markdown 默认的 <strong>,取色跟着 prose 全局规则 —— 逐条核色是这一格要比的事FileOpsSummary 里的 ArtifactCards / ArtifactCard。卡面只有缩略图,不写文件名、不摆工具条、没有「预览」也没有「⋯」,动作在右上角(D28 / B19)artifactCardKind() 判(html / image / video 三种);.md .csv 那类留在下面的文本行里 —— 这一格全是 html,所以只剩两张卡HtmlProjectCoverFrame 的 iframe,src 指向 /api/projects/:id/raw/…,离开 daemon 是打不开的。要比的宽高比(16/10)、圆角、两列栅格、按钮位置照样都在pending 的卡面现在是第 9 格那套像素液体,原来那层灰底 + 呼吸 opacity(artifact-card-pulse)已经撤掉。同样地,陈列页是静态渲染,<canvas> 在这里出不来 —— 这一态要看真 runtimecanPublish 判 item.kind === "html" 且宿主给了 onPublish),这条与稿子一致onPublish 照样传了,卡上仍然只出一枚「导出」—— 少的那一枚是组件自己按 kind 判掉的,不是这一格没给回调.acts 用 flex 靠右,所以少一枚时整排自己往右收、右缘齐(稿子点名的那条)artifact-card--video 变体:竖片按 9/16 居中、两边留白,不拉满变形<video> 的 src 同样指向 daemon,静态页里放不出画面。组件写的是 width:auto + object-fit:contain —— 9/16 那条留白是真片子的内在尺寸撑出来的,加载不了时 width:auto 会落回 <video> 的默认 300px。所以这一页替它补了竖片的长宽比(只补在陈列格里,组件一个字没改),同 data-hover / data-expand 一个性质--tick-img)、按钮 22 → 26px、分隔线换成弹簧--t-cap(12)提到 --font-size-14,勾跟着从 13 提到 16 —— 两个数是一对(14px 中文的字面高度约等于 16,勾和字这才读成一个词),只改一个就是半迁移14:32 已经补上了:原来 AssistantMessage.tsx 只在「没有反馈按钮」那条分支传 createdAt,走 AssistantFeedback 的分支(也就是任何一轮正常跑完的回复)那份 footerProps 里没有它,所以最常见的路径上时间根本不出。现在两条分支都传了,这一格的夹具也跟着补上 —— 陈列页不该再报一个已经不存在的问题.assistant-feedback-wrap 原来是 inline-flex + max-width: min(360px,100%),整行缩成 220.6px、弹簧撑不开;现在是 flex + width: 100%(下面那块原因面板改成自己限宽),.assistant-footer-gap{flex:1} 撑得开了,footer-time.test.tsx 钉着这条。这条注记原来写着「没有顺手改」,那是旧话.assistant-footer{opacity:0}),这一页靠 data-last="true" 那条既有规则常驻 —— 组件一个字没改hideRunStatus 待拍板:这一轮有执行记录或 todo 快照时,产品把整个状态词都不渲染(「run 状态已经在答案顶部了」);稿子里壳头与这一行是同时存在、各说各的。这一格给的是 hideRunStatus=false,也就是「恢复之后」的样子.od-tooltip + TooltipLayer.tsx 的 portal,由 pointer / keyboard modality 驱动;它不是一条 ::after,没有「原样重放一遍」的规则可抄(第 51 格那招在这儿不成立)。机制比稿子的 [data-tip]::after 更完整,不是缺口aria-label 与 data-tooltip 上(19 语全改)。第四条有意不取稿子字面:稿子这里写「新开会话」,面板头那颗写「新会话」,两处稿子自己就不一致 —— 产品裁决 2026-09-03「聊天面板内只说一句」,取「新会话」,依据同在稿子里(body-components.html:1243 的 aria-label="新会话从这里开始")。同一颗按钮的三个态一并统一成 新会话 / 正在开始新会话… / 无法开始新会话。守卫见 w118-feedback-row-icons-and-tips.test.tsx 与 w129-new-session-single-entry.test.tsx--radius)、内距 5px 8px → 5px 9px、行高 1.2 → 1.4、磨砂 → 实底 --bg-elevated(顺带摘掉 backdrop-filter)、边色 --material-separator → --border、--shadow-sm → --shadow-md、字色 --vibrancy-label → --text-strong、离按钮 7 → 6px。这条注记原来写着「待拍板」,那是旧话;守卫见 styles/w126-tooltip-design-parity.test.ts::after 上,opacity: 0 起手、transition: opacity var(--duration-faster) var(--ease-out);产品原来 hide 时把 portal 卸载,元素根本没机会从 0 走到 1,加 transition 是死规则。现在 TooltipLayer 常驻挂载、只切 opacity。代价是那个 role="tooltip" 节点一直在 DOM 里,所以隐藏态挂 aria-hidden="true" 把它从可访问性树里摘掉;按下按钮那一路则一刀切(visibility: hidden,不淡)—— 否则 100ms 的淡出会被「截图到对话」抓进画面。守卫见 components/w129-tooltip-fade.test.tsxwhite-space: nowrap 且不设 max-width,那是为「两三个字的图标名」量身的。这条 primitive 全站共享,上面挂着 202 字符的长描述(fileViewer.publishSingleFileDescription 法语)和一批长度无上限的用户数据(工作目录路径、标签页标题),单行会横穿屏幕 —— 所以限宽 260px 换行留着title=,会和自绘 tip 叠在一起;稿子只有 aria-label + data-tip。TooltipLayer 目前在 pointerover 时把 title 摘下来暂存,所以真机上不会真的叠出两个气泡 —— 是否仍要去掉 title 待拍板--red / --red-bg 一套,不再和赞共用 accent(theater.css 的 [data-selected="true"][data-rating="down"])data-tip,其余几颗还在。看着是疏漏不是设计意图 —— 我们保留了 tip,请确认.is-on 那颗仍是 thumb-up-line 的路径、只换了底色,cmp-ops 也只写「单击切换,互斥」。产品今天是 svg{fill:currentColor} 真的填成了实心 —— 待拍板(这条会连着第 36 格一起改)thumbs-up → thumb-up-line 等四条路径与稿子逐字相同,尺寸同为 13pxhandleForkFromMessage 在分叉成功之后给源会话那条助手消息盖上 forkedInto(承接过来的标题 + 新会话 id)并 PUT 回去;daemon 侧新增 messages.forked_into_json 落库,所以刷新之后分界还在assistant.forkNote,19 语已补齐assistant.canceledLabel 从「已取消」改成「已手动停止」——「说清是谁停的」,19 语已补齐。稿子在 CSS 注释里点名反对「仍有未完成任务」那种限定语:剩没剩、剩几步,上面那段执行记录本来就写着isFeedbackEligible 的 userStoppedTheTurn 上,所以这一格挂的是产线真正走的那条分支(AssistantFooter,没有 AssistantFeedback 那层壳)。跑挂了的那一轮不在此列:那是结果,评得动--text-faint(#bdbdbd)+ 状态词 --text-muted(#5c5c5c),和稿子逐值相同。字色原来输在层叠上 —— routines.css 的 .app .assistant-footer .assistant-label 与 theater 的中断档同为 (0,3,0),而 routines 排在后面,旧皮肤那份 --text-faint 赢;那条规则的每一句都和 composio 逐字相同,已整条删掉14:32 在这一格是真的贴到右端的:没有赞踩就没有 .assistant-feedback-wrap,整行直接满宽,中间那根弹簧撑得开AssistantFeedbackReasons。原来它长在 AssistantFeedback 里、由 React state reasonRating 驱动 —— 静态陈列页永远够不着,那一格只能空着aria-pressed 承担多选语义)、补充框从「勾了『其他』才出」改成常驻且只留一条底线、右下补上「取消」、标题在点踩这一路换成稿子的问句「哪里不对?」(新键 assistant.feedbackReasonTitleNegative,19 个语言包已补齐)weak_visual,不再传一个永远选不中的 visual 假值<od-next key="…">(密钥与 D43 的 <od-done> 同一枚,每轮一次性)→ daemon 在可见文本流上剥离并校验 → 下发 / 落库为 next_steps 事件 → AssistantMessage 读事件。标记本身客户端从来看不到,所以不可能漏进正文、也不会被复制 / 导出带走9px 11px、gap 8px、字号 12、行高 35px —— 逐值和稿子相同(浏览器里量的,不是 diff 规则文本)--text-soft,hover 时和字一起转 --text-strong)+ 一句话;分类图标和行尾 chevron 都已删掉 —— 点一条是往 Composer 填草稿,不会打开下一层菜单,所以行尾不需要另一枚 chevroncomposerRef.setDraft(prompt);用户可以先改写,只有显式点发送才会创建消息和 run。next_steps 事件,这一块整块不出(不退回目录、不出空壳)。陈列页照不出这一态,它钉在 tests/components/NextStepActions.test.tsxdefault 档换掉之后它在常规交付回合上没有落点了。分享 / 下载 / 创建设计系统在别处都还有入口(文件查看器工具条、文件面板「…」菜单)data-hover)—— 稿子的 is-hover 就落在「把商品卡换成两列布局」那一条上;产线上仍旧是鼠标停上去才变--bg-panel 打底,字与箭头一起转 --text-strong,边框不动(本来就没有)。过渡是 background-color / color × var(--duration-faster) var(--ease-out)button:hover:not(:disabled) { background: var(--bg-subtle) } 是 (0,2,1),裸写 .suggestionRow:hover 是 (0,2,0) —— 输了,量出来会是 rgb(237,237,237) 而不是稿子的 rgb(250,250,250)。所以选择器带上 .suggestions 这个祖先凑到 (0,3,0),和稿子里 .nexts 承担的是同一份层叠职责。这一页把 module 关进 .cage-next-step 的笼子,选择器凭空多一个祖先,这个坑正好被盖住components/chat/AudioArtifact.tsx + 采样规则 runtime/chat/audio-wave.ts(有单测)。建之前 chat 面板里零音频 UIartifactCardKind() 对 .mp3 / .wav 直接返回 null 的判断(T41)playedBars 决定点亮到第几条,有单测)data-at="12" data-play(时间那处的 0:00 只是脚本跑起来之前的初值,audio-wave.js 一上来就按 data-at 点亮前 7 条)。原来这里摆 0:00,于是波形前 7 条永远差一片颜色 —— 那是夹具对不上,不是画法对不上wave-pulse 动画,要看得起真实页面这一页是 SSR 出来的静态标记,没有布局 —— 组件里凡是要量像素才成立的东西,在这一页上一次都没跑:
第 46 格的「查看全部」、第 58 格的两枚翻页箭头、第 59 格的中间省略。三处都在各自那格的注记里写明了,
要看它们得起真实页面(pnpm tools-dev run web)。没有为了让它们在这一页出现去改组件。
缩略图取不到:图卡的 src 指向 /api/projects/:id/raw/…,离开 daemon 就是一张打不开的图,
格子里是 57px 的占位灰块。要比的尺寸、圆角、间距、一行里两种卡是否同高,照样都在。
这一族现在全部上了页 —— 早先写的「还没上页:第 47 / 49 / 50 / 54 / 55 格」是旧话,
那五格(hover 无差异、发送失败态、附件失败与 hover 预览)后来都补齐了。
还欠着的是接线不是画法:ChatMessage.sendFailed 与那张失败卡都建好了,
但产品里没有任何代码去把它置上(见第 49 格注记的 B13),而「同一个失败到底显示在哪」
仍要产品裁一次(盘点 §4-B)。组件 23 的第 65–69 格(回答正文取词)五格也全部出了格。
组件 21(第 60–64 格)已上页:盘点 §4-A 把「逐文件上传状态」列为形态级风险,实测不用改契约、不用改后端 ——
uploadProjectFiles 本来就收 File[],一个文件一个请求走的是同一个端点,失败因此能落到具体那张卡上。
唯一没做的是说明文字里那句「这几秒发送键不可用」:那是一条已知的现网 bug,按分工另起红测 + 独立 PR,这一轮没碰。
-webkit-line-clamp: 6,折在里层 .user-text-txt 上而不是气泡上),这一格能比pnpm tools-dev run web)。没有为了让它在这一页出现去改组件。它们只在【真的被截断】时才挂 —— 同一段话在宽一点的面板里可能六行就说完了,那时候挂一枚「…」是在说一句不存在的下文。⚠️ 这一页上那个省略号是 -webkit-line-clamp 自带的,不可点;稿子那枚是个能点的按钮,左边还垫着一段 26px 的渐变。1-4 hover)在稿子样式表里没有任何匹配规则,与本格无可见差异 —— 它后来还是出了格(并排看才证明得了「稿子这一态是空的」),见那一格的注记.bub.mod-clamp.is-hover 在整张样式表里没有任何匹配规则,而气泡内的「查看全部」是常驻的 —— 所以它相对第 46 格没有任何可见差异data-hover),两格并排看正好能证明「稿子这一态是空的」overflow-wrap: anywhere(不是 break-word)—— 差别正好落在这一格:没有空格的一长串必须断开ChatMessage.sendFailed(contracts)+ 气泡下方那一行(.msg-fail)。原来契约里没有「这条发失败了」,所以这一格根本画不出来.msg-act .keep),和时间 / 复制并排;原来我把它另起了一行,于是那条隐藏的动作行照样占 30px,气泡到「重试」之间空出一大截sendFailed、UI 也画得出来,但没有任何代码把它置上,onResend 也没有上游传进来 —— 这一格照的是一个还没接线的状态。接线要动 run 生命周期的错误分支,记在 specs/current/chat-panel-feedback.md 的 B13data-hover):时间与复制浮出、「重试」照旧常驻.msg-row:hover .bub 在交付稿自己那一页上是死规则 —— 普通气泡的祖先是 .msg-me,全文只有两格摆拍用了 .msg-row.is-hover,所以在原稿上 hover 本来就没有任何反应。顺带解掉了它自带的矛盾:状态名写「背景加深」,值却是 #202020 → #494949,白底上是变浅data-hover),和 data-expand 替它点开抽屉是同一个手法;产线上仍旧是鼠标停上去才浮出.tm 排第一是旧的(盘点 §5 第 4 条,待设计确认)--text-strong(#202020)换成 --text(#494949)。⚠️ 稿子状态标题写的是「背景加深」,而这两个值在白底上其实是变浅 —— 按值实现,矛盾已回报(盘点 §5 第 5 条)sendFailed 走(见第 49 / 50 格,契约字段与 .msg-fail 那一行都已经建好),这一格喂的是一条正常发出去的长消息,所以不该有它。还没接线的是「谁来把 sendFailed 置上」,记在 specs/current/chat-panel-feedback.md 的 B13aria-label);原来两种附件都是同一张带序号和文件名的小药丸.msg-stack 这一层:附件行锁 412、气泡锁 380,两条上限各管各的,右边界照样对齐;壳子刻意不设 width: 100%.msg-att-eye),hover 才浮出、触屏常驻 —— 和待发送托盘那枚「×」同一条规矩aria 照实写成「打开」ChatAttachment.size(数据本来就在,只是从来没渲染过)flex: none 永不被吃掉 —— 原来是 CSS 尾部省略,被吃掉的正好是扩展名looksLikeImage() 只认 7 种位图,而稿子把 .pdf .mov 也画成 57px 方卡(盘点 §5 第 7 条)nowrap + overflow-x: auto + 藏滚动条,右对齐靠首个子元素的 margin-inline-start: auto(不能用 justify-content: flex-end,溢出内容会跑到左边滚不回去;也不能用 width: fit-content,叠加 overflow-x 会算塌成 0)pnpm tools-dev run web)。没有为了让它在这一页出现去改组件。它们只在真的被遮住时才出,要量 scrollLeft / scrollWidth / clientWidth(scroll + ResizeObserver + resize + fonts.ready 四路重算)。判据的单测在 tests/runtime/chat/attachment-nav.test.ts,出没出的行为在 tests/components/chat/attachment-row-nav.test.tsxpnpm tools-dev run web)。没有为了让它在这一页出现去改组件。text-overflow 只认两端,中间省略必须按可用宽度倒推 + 对头段二分,而那要量 .msg-att-nm 的 clientWidth 和文字宽度。这一页上看到的是没截过的整串,右侧被 overflow: hidden 硬切 —— 稿子那一格该是 商品卡组件…评审后 + .mdtests/runtime/chat/attachment.test.ts:中文留最后 2–3 字、拉丁留最后一个单词、-v3 这种版本尾巴算一个词)埋点.csv 这张是对照组:放得下就原样不截.staged-row(和 plugin / skill / MCP 芯片混排的那一行)搬进自己的托盘,从左排、单行横滚;卡片与已发送那一侧共用同一份 CSS 规则,不另抄一套模板opacity: 0,逐张 hover / :focus-visible 才出,并补了 @media (hover: none) 常驻 —— 这一页是静态的,看不到 hover,要看得起真实页面.staged-order 已按稿子去掉.pdf / .mov 也算「能出预览」),产品的 looksLikeImage() 只认 7 种位图,所以这里 .pdf / .mov 走文档宽卡。准入名单是盘点 §5 第 7 条的待拍板项,没有自己改<span>,而产品现有「点缩略图看大图」的弹层是已经在用的能力。删掉一个已有入口要产品拍板,所以卡壳仍是 <span>、但缩略图本身可点uploadFiles() 是原子的 —— 一次把整批打包发,成功之后芯片才出现,上传的那几秒界面上一张卡都没有。现在改成一个文件一个请求(uploadProjectFiles 本来就收 File[],给它长度为 1 的数组走的是同一个端点、同一份契约,后端和 packages/contracts 都没动),并发上限 4@property --att-up-angle + conic-gradient 的两层背景(padding-box 盖内部、border-box 只在 1px 描边区露出来)。它是「在忙」的不定式指示,不是百分比进度 —— 57px 的卡上读不出「62%」,那就别假装@media / @keyframes / @property 这类 at 规则(不丢的话条件会丢、里面的规则无条件生效,踩过一次)。所以这里看到的是流光停在 0° 的那一帧;为此 conic-gradient(from var(--att-up-angle, 0deg), …) 特意写了兜底,否则整条 background 会被判无效、卡片变成一个没边没底的白块uploadProjectFiles 不收 AbortSignal,真中止请求要改 providers/registry.ts,这次没动 —— 撤掉之后结果就地丢弃,不进待发列表sendDisabled,行为与改动前逐字一致top/end: 4px,文档卡 5px(两张卡的边框 / 内边距不同,稿子给的就是两个值,照抄,没统一成一个);文档卡在托盘里额外 padding-inline-end: 28px 给它让位flex: none 永不被吃掉;中间省略的量法与第 59 格同一份纯函数,静态页里量不出来,这里是没截过的整串Attachment upload failed for N file(s)…),无法针对某一个文件重试 —— uploadProjectFiles 的 failed[] 只有 name,同名文件根本对不回去。改成逐文件之后每张卡各自记着自己的本地 File,「重试」只重发那一个chat.att.retry)还没落地,已单独交出去;这一页按稿子逐字摆出来,键一落地就把陈列页里的临时文案删掉flex-wrap: wrap + max-height: min(108px,18vh) + overflow-y: auto ——纵向长高再纵向滚,输入框高度会跟着附件数变。现在单行 + 横滚 + 藏滚动条,输入框高度因此是常量(说明文字点名的那句收益)margin-inline-start: 0),与已发送那一侧的贴右正好相反 —— 已发送那行压在用户气泡上方要跟气泡右对齐,而托盘在满宽的输入框里,内容当然从左边起pnpm tools-dev run web)。没有为了让它在这一页出现去改组件。判据直接复用组件 2 那份纯函数(runtime/chat/attachment-nav.ts),没有重写:两处底色相同,--att-fade 那套渐变和箭头也共用。出没出的行为断言在 tests/components/chat/staged-attachment-tray.test.tsx两页都好了,商品卡已经抽成共享组件,改一处两页都跟着变。
components/chat/QuoteBar.tsx + 纯判据 runtime/chat/quote-selection.ts.sel { position: relative } + .selbar { left:50%; translate:-50% 0 };产品里按选区矩形 position: fixed,落点是同一个。浮条根节点也跟着稿子改成了 <span> —— <div> 放进 <p> 会被浏览器当场截断,DOM 一重排浮条就整个不见改一处两页都跟着变,后面再加页也是同一张卡。
quoteBarPlacement,有单测)components/chat/QuotedRefs.tsx,挂在输入框上方,不占写字的地方.refs:hover .del/.pop)texts 一列念出「稿 3 条注释 / 我 5 条注释」、浮层里也多两个 <li>,那两处不是差异这一族五个组件性质各不相同,别拿同一把尺子看:
| 组件 | 状态 |
|---|---|
| 6 Plan 卡(#70–71) | #70 拍板不做 —— D33「不实现、不模拟」、S9「展开态的独立卡不做」;清单的正式落点是执行记录内的分段(B17)。出格是为了让「不做」这件事留痕。#71 已实现:收起态的「第 N / M 步」药丸钉在输入框上方,悬停浮出整张清单(T18 那条冲突已由用户 2026-08-26 裁定:只做收起态) |
| 17 Queue(#72–74) | 早就有(QueuedSendStrip),已按稿子改过版式。这一族最接近对齐的三格 |
| 18 升级(#75–77) | 能力在,形态不是这个 —— 稿子要流水内的一张卡,产品是两个居中弹窗;而且它们 createPortal 到 document.body,SSR 渲染不了 portal,这一页拿不到标记 |
| 19 报错(#78–80) | 主卡拆不出来 —— 200 多行内联 JSX 绑在 ChatPane 上;#79 挂的是产品今天承接「切到 Cloud」的那张附卡,照出「一件事被拆成两张卡」这个差 |
| 20 / 22(#81–84) | 产品里原来完全没有 UI,这一轮从零建的,可以独立挂载 |
✅ 第 72–74 格那条错位已经修好了 —— 这一段以前写的是「现在是坏的」,那是旧话:
.chat-queued-send-row 原来是三条轨道的 grid 装着四个孩子(拖动手柄独占 517px 中间列、
正文被挤到右边、动作排掉到第二行、行高 34 → 38px);现在整行换成 display: flex,
和稿子 .queue .q 同一套排版模型,行高也回到 34。
两条要产品裁的:①#76 与已定口径直接冲突 —— error-ux-design.md §3 写「付费用户余额 0 = 不限量,不拦」,
稿子写「= $0 → 无法开始新任务」;②#78 稿子的「从失败处重试」是常驻动作,而报错设计方案原则 4 写「重试只在有用时出现」。
components/chat/PlanPill.tsx + runtime/chat/plan-pill.ts),钉在 ChatPane 输入框上方、排在发送队列之前 —— 队列有内容时把药丸往上顶,两者不互相压。TodoCard 那个 {done}/{total}(已完成几步)。清单里一条 in_progress 都没有时,第一条未完成的算当前(D36 隐式进行中)——codex 原生清单只有做完 / 没做完两档,没有这条规则它整份清单指不出「第几步」。todoStatusIsUnfinished,和 daemon 盖 endedWithUnfinishedWork 同一个谓词。StatusMark 的四态圆,没有另画一套:做完打勾并划掉、当前一颗绿球、没开始一圈虚线。稿子每个 <li> 里同时写了 .tk(SVG 四态)和 .mk.is-run(绿球),靠 li 的类名二选一显示 ——实测 li.is-now 显示绿球、其余显示 SVG,StatusMark 的 running / ok / pending 正好是同一组判据。left:50% 居中)—— 产品里药丸钉在输入框的左内缩线上,而 .pane 是 overflow: hidden,居中会被面板左边切掉约 50px(稿子那张演示卡不裁,所以看不出来);② 「做完了」那条的划线用执行记录的 .struck(直接 text-decoration),不是稿子那条 ::after 描出来的动效线 ——同一件事不在两个文件里各画一遍,代价是少了「一笔划过去」的落定感。--chat-shadow-lg 其实早就有了(ChatRoot.module.css 亮暗两档都定义了),上一版注记说只到 -md 是记错了。pnpm tools-dev run web)。没有为了让它在这一页出现去改组件。这一格由陈列页替设计师按住(data-hover),同第 42 / 51 格的手法;产线上仍旧是鼠标停上去才浮出,键盘 Tab 到药丸也会浮出(:focus-within,产品这一侧补的 a11y,稿子没有)。头顶那 136px 也是这一页的脚手架 —— 稿子自己那层 .pdemo 同理,产品里一个像素没搬。line-clamp、去掉外框与底色只留条间发丝线、顶对齐.chat-queued-send-row 现在是 display: flex(把手 / 序号 / 动作 flex: none,正文 flex: 1; min-width: 0),和稿子 .queue .q 同一套排版模型。这条注记原来写着「这一格现在是坏的」,那是旧话summarizeQueuedPrompt 会先把它压成一行截断,那样人认不出自己要取消的是哪一条onSteer,所以摆的是引导态,和稿子对得上handleStop() 打断当前 run),照稿删掉等于砍功能。什么时候允许退回、退回时那颗键长什么样,待产品裁QueuedSendMetaChips(附件 / 标记 / 插件 / 技能 / MCP / 连接器计数)。这一格的夹具照稿子给的是纯文本,所以芯片没出现;真实队列里带附件时它会多一行。它是「所见即所发」的信任面,删掉是能力回退od-tooltip + TooltipLayer portal(稿子是 data-tip + ::after)—— 静态页里两者都看不见max-height + 滚动。稿子把 122px 的来历写死了(7×2 内边距 + 20 行高 = 34,加 1px 分隔线,3.5 × 35 ≈ 122),露出的半行就是「下面还有」的提示overflow-y 的裁切在这一页上是成立的(实测列表高度正好 122px),但滚不动min-height 的地板也撤了,行高回到「7 + 20 + 7 = 34」加 1px 分隔线,「3.5 行 ≈ 122px」重新算得平。这条注记原来写着「现在算不平」,那是旧话UserMessageImpl(出队后那条已经发出去的消息),下面是剩下两条的队列 —— 稿子这一格画的就是这两样amrGatePausedQueueConversationsRef),此时队列就停在那儿不动,界面上没有任何解释余额可能撑不完下一个任务 —— 中途用尽会停在半成品上
components/chat/UpgradeCard.tsx —— 流水里的一张卡,不是弹窗、不挡发送(D4 的取向)ChatPane.archiveLowBalanceTurnCardAmrLowBalanceDialog(居中硬阻断)已于 2026-09-06 删除(T53);2026-09-07 产品把整个低余额档也撤了 —— 原话「这个要不先不要了,跟产品说了一下,不要这个了」,追问范围后「余额为零的那个卡片要显示的,并且也要弹窗的」(T66)AMR_LOW_BALANCE_WARN_USD 已删除(不是改成 0)。稿子的「额度不足 · < 5 美金」和 T52 的 $2 偏离随之作废现在无法开始新任务
components/chat/UpgradeCard.tsx —— 流水里的一张卡,不是弹窗、不挡发送(D4 的取向)ChatPane.archiveLowBalanceTurnCardAmrLowBalanceDialog(居中硬阻断)已于 2026-09-06 删除(T53);2026-09-07 产品把整个低余额档也撤了 —— 原话「这个要不先不要了,跟产品说了一下,不要这个了」,追问范围后「余额为零的那个卡片要显示的,并且也要弹窗的」(T66)AMR_HARD_BLOCK_BALANCE_USD = 0,和稿子的「额度耗尽 · = 0 美金」一致。T66 之后这是发送前闸门唯一还会出卡的余额,而且卡和弹窗同时出components/chat/RunErrorCard.tsx —— 原来是 ChatPane.tsx 里 200 多行内联 JSX,样式没法集中对齐、陈列页也照不出来actions 传进来ExportDiagnosticsButton);②动作集合待产品定AmrGuidance,两张卡同时出现;那张卡已删掉,〔切换到 OpenDesign Cloud 并重试〕收进报错卡的主按钮位。文案仍是产品自己那句 chat.amrCard.switchCta,没有换成稿子的「切换到 Cloud」components/chat/SupportDialog.tsx。渠道由调用方给,组件本身不硬编任何社群地址components/chat/ 下是临时位置,要跟产品确认最终归属runStatus: canceled / cancelOrigin: user_stop 是 run 终止,由回合 footer 报「已手动停止」,不能拿来驱动这一行onShowDetail 才出.tool.is-fail 压掉了,实测三态都是透明边透明底,失败唯一的颜色是那枚图标components/chat/UserStatusCard.tsx)ChatPane.tsx 的调用点现在写的是 {false ? <UserStatusCard …> : <UserBubble …>} —— 组件在、文案在、样式在,但产线上还没有任何一条路会把它渲染出来。这一格照的是组件本身ChatPane.streaming.test.tsx 里那句「.user-status-card 必须为 null」钉的是被撤掉的那一版,和新裁决直接冲突,接线时要一起翻转意图澄清:现有实现能用,但和稿子有 7 处不一样 —— 逐条列在这儿,好判断要改哪些、哪些是稿子该跟实现走。
| 项 | 现在的实现 | 稿子 |
|---|---|---|
| 卡头文案 | 圆形「?」头像 + agent 给的表单标题 | 图标 + 固定一句「还需要确认一件事」 |
| 选项排布 | 横排胶囊,一行排开 | 竖排一列,每项左边一个选择框 |
| 自己填 | 末尾一枚虚线「其他」胶囊 | 和其它选项同列的一条「自己填」 |
| 主按钮 | 发送答案 | 下一步 |
| 次按钮 | 一键跳过 | 跳过 · 你来判断 |
| 脚注 | 有一行提示语 | 没有,只有两个按钮 |
| 已回答 | 整张表单锁住(灰掉的原表单 + 「已回答」角标) | 收成一条陈述:「已确认 · 商品卡 — 沿用列表页那张」 |
最后一条不是样式问题,是形态问题:锁住的表单和一句陈述,占的高度差好几倍 —— 一轮里问过三次,差别很明显。
记忆卡:现在是一行 chip(条目直接铺在行内);稿子是可折叠的,收起只留「已记住 3 条偏好」,展开才看内容。
select + 选项带 group / trailingLabel 就走「查找型单选」的版式,组名与行尾语言代码都是契约里的字段,不是为这一格现编的pnpm tools-dev run web)。没有为了让它在这一页出现去改组件。展开由组件内部 state 驱动(点一下才开),静态陈列页点不了;限高滚动要量像素,这一页也量不了。两条都要起真实页面看qf.moreOptions)—— 这个折叠器是任意选项列表共用的(时区 / 国家 / 字体走同一条路),把语言那一档的措辞焊进通用组件就错了,产品原话「更多语言 改成 更多选项」。组名「更多语言」仍旧显示在展开后的列表里,模型的编排意图没有丢ColorChoice(预设色 / 系统取色器 / Hex 输入三条路共用同一个答案值,预览由 --qf-choice-color 驱动)#3b82f6(蓝),所以第五枚色块是按下态AmountChoice(上方数字可直接编辑,与滑杆双向同步;轨道里只有滑杆,稿子这一版把上一版的刻度光点整排删掉了)2 + 一枚 .amount-unit 的「档」,契约里没有 unit 这个字段,所以我们只念数字。补一个字段要产品裁一次(它会跟着进已回答那一行,见第 90 格)1 · 疏朗 / 5 · 紧凑,我们只念 1 / 5 —— 同 unit,契约里没有「给两端配一个说法」这个字段。摆的档位与稿子同为 2AnsweredValue 的 --answer-color),并按 isShortValueAnswer 落在稿子的 .ab.mod-value 那一档行内垂直居中上label,没有「短标签」这一档。要对上得给 FormQuestion 加字段,待产品裁numeric: true 让数值答案和颜色同归稿子说的「短答案」,一起走 .ab.mod-value2,稿子是 2 档 —— 单位在契约里不存在这一族没有对应的交付稿截图,不参与逐格比对。上面陈列格是逐个组件摆拍 —— 一格一个实体、一份夹具、一条分支;这一族喂的是一整条真实会话,回答的是另一个问题: 串起来之后还是那个样子吗。
建它是因为陈列格有一个结构性盲区:它只照得到「喂了夹具的那条分支」。 真实运行时可能走另一条,而那条在陈列格里没有格子,就永远照不到。活例子:
| 照不到的分支 | 为什么 |
|---|---|
用户消息上方那块灰色的「本条消息带了哪些上下文」(.msg-applied-context) |
陈列格的 msg() 传的是 appliedContextItems={[]} —— 这条分支从头到尾没被照过一次 |
runContext.workspaceItems 的「Current」芯片 | 陈列格从没喂过这个字段 |
producedFiles 的产物卡(含拿不出预览图的 .md) |
第 30–33 格走的是另一条产出分支(FileOpsSummary),两条的准入名单不一样 |
| 一轮里两张执行记录壳 | renderCell 只取 shells[shells.length - 1],一格只摆一个实体 |
| 失败轮的报错卡 | 它由 ChatPane 挂在消息列表之后,不在 AssistantMessage 里,单摆一个组件够不着 |
done 密钥协议(done_key + <od-done key=…/>) |
陈列格一条 done_key 都没有,那边跑的一直是「没有 key」的老判据分支 |
这一族按产品的祖先链渲染:.app(ProjectView)→ 接缝(ChatPane 的 chatSeam)→
.chat-log → 消息。组件一个字没改,也没有替设计师点开 / 按住任何东西 ——
这一族要照的是「产线上打开会话第一眼看到什么」。
挂上去当场的收获:
| 照出什么 | 机制 |
|---|---|
✅ 用户气泡在一整条会话里也对上了 —— 量到 rgb(32,32,32) / rgb(255,255,255) /
12px 12px 4px,和第 45 格逐值相同 |
上一个提交删掉了 routines.css 里那条把气泡刷成 #ededed 的旧皮规则。
摆拍对上不等于串起来也对上,这一族是那次修复在真实形态下的复核 |
| 🐞 一轮摞两张壳比想象中常见得多 —— E2E-1 开头只有一句 thinking,照样分了张 | 分张判据是「清单之前那张壳里有没有东西」,一句想法就算「有东西」。 陈列格看不到:第 2 格的夹具其实也分了张,那一句 thinking 在陈列页上从来没出现过。要不要收一收待产品裁 |
| 🐞 那两张壳头写着同一个耗时(E2E-1 都是 1m 12s;E2E-4 第一张壳有工具行,就是 3.1s / 32s 两个数) | thinking 事件不带时刻,第一张壳因此没有自己的跨度,shellElapsed 退回轮次跨度。
回退本身是有意的,但它在最常见的路径上正好制造出 build-turn-blocks 自己点名反对的画面 |
🐞 回合状态行会说「已停止,仍有未完成任务」,而 runStatus 是 succeeded(E2E-4 对 E2E-1) |
agent 收尾时没再发一次清单快照,最后一条 todo 停在 in_progress。这条路径在产线上很常见,待产品裁 |
appliedContextItems,但历史流水不再渲染 msg-applied-context / msg-run-context-row.chat-log 的 gap)—— 单摆一个壳看不见这件事商品列表页做完了,商品卡已经抽成共享组件,改一处两页都跟着变。
background rgb(32,32,32) / color rgb(255,255,255) / border-radius 12px 12px 4px,和第 45 格逐值相同 —— 上一个提交(旧聊天皮肤那次)删掉了 routines.css 里那条把气泡刷成 #ededed 的规则,这一格是它在一整条会话里也成立的证据。这一族存在的意义正是这个:同一件事在摆拍里对上,不等于串起来也对得上renderCell 只取 shells[shells.length - 1],第 2 格的夹具其实也分了张,那一句 thinking 在陈列页上从来没出现过。要不要按「只有一句 thinking 时不分张」收一收,待产品裁thinking 事件不带时刻,第一张壳因此一件带时刻的事都没有,shellElapsed 按注释里写的「没有自己的跨度就退回轮次跨度」把整轮的耗时给了它。回退本身是有意的,但它在最常见的那条路径上正好制造出 build-turn-blocks 自己点名反对的画面 ——「两张都写『已完成』、耗时还都是同一个数,读起来像同一件事说了两遍」。这一轮没有顺手改runContext.workspaceItems,但不再渲染 ActiveWorkspaceContextChipmsg-run-context-row,避免 CURRENT / Using 标签占用纵向空间价格行调到 15px 了,和标题差一档,行高没动。
workspaceItems 和一条 applied design-system,页面仍不出现 CURRENT / Using;数据链由独立 provider / daemon 测试守住producedFiles —— 走的是 ProducedFiles 那条分支,陈列格的第 30–33 格走的是另一条(FileOpsSummary).md 这种拿不出预览图的产出 —— producedArtifactCardKind 给它 doc 档卡FileOpsSummary,它用严格的 artifactCardKind(),.md 进不了卡、掉到下面那条文本行里;没有工具行时走 ProducedFiles,它用 producedArtifactCardKind(),.md 出 doc 档卡。同一个 .md、同一轮产出,画法取决于 agent 用没用 Write —— 待产品裁一次/api/projects/:id/raw/…,离开 daemon 打不开,格子里是占位底色。要比的是三张卡的尺寸、圆角、栅格与右上角动作ExecutionShell 摞在一起的形态 ——陈列格每格只出一张壳(renderCell 只取 shells[shells.length - 1]),这条分支在陈列页上从来没出现过设置页做好了,两页之间的跳转也接上了。
runStatus 是 succeeded:agent 最后那次 Edit 之后没再发一次清单快照(注释里写着「有的干脆整轮只发一次」),最后一条 todo 停在 in_progress,unfinishedTodosFromEvents 就据此判定「还有没干完的」。E2E-1 补了收尾快照,状态行是「已完成」—— 两格并排看的就是这个差。这条路径在产线上很常见,状态行要不要以 runStatus 为准,待产品裁runStatus: "failed" 的一整轮 ——陈列格的第 3 格只有壳,没有它上下文里的用户消息与报错卡errorCardOwnerId 这条只有 ChatPane 才算得出来的分支:命中时消息内的错误药丸不出、回合状态行 hideRunStatuserrorCardOwnerId 传的是这条助手消息自己的 id —— 产品里 ChatPane 只在「最后一条失败的助手消息」上这么传。命中之后消息内那枚灰色错误药丸不渲染(避免和卡说两遍),回合状态行也把状态词让给卡RunErrorCard.module.css 关进 cage-err 笼子,这一族关进 cage-live(两边互不影响,见 scope() 那段注释)ChatPane 的裸 <button class="chat-error-action">,不是稿子第 78 格那套带图标的 Button —— 产品今天就是这两套并存,对齐时要收成一套