# 3b · 实测记录(③段界面交互) > 落点:`执行会话/目标-根据1a需求文档生成MCN短视频整合营销-d85687/ui/3b-实测记录.md` > 判据源:`stage-delivery/SKILL.md` §3b/§供给;`references/execution-runbook.md` §3.5、§5.3、§六;`references/layouts-tooling.md` §1、§5.5 > 交付物:`ui/mcn-workbench.html`(单文件自包含原型)+ `ui/DESIGN.md`(规范) ## 〇、声明制(四字段) | 字段 | 内容 | |---|---| | 当前段与子步 | ③段 / 3b 原型构建 + 3b 实测(实测未过不进 3c) | | 主干与挡位 | 供给=`oil-ui-pro`(**基础层**:视觉与体验的底线判据)+ 叠加层(本段自带样式库 `design-system-tiaoyue` 定调与令牌 + 本段自有判据;只许更严、更具体,⛔ 不许放宽基础层)/ 挡位 **标准** | | 依据文件 | `DESIGN.md`(本段契约)、`prd/2b-界面布局.md`(冻结骨架)、`prd/2a-产品功能.md`(F1–F14) | | 产出落点 | `ui/mcn-workbench.html`、`ui/_tools/`(三个可复跑脚本)、`ui/_gate_shots/`(23 张视口帧) | ## 一、怎么复跑(四条命令,全部只读原型) ```bash PY="E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/python.exe" cd "…/ui" $PY _tools/cap.py --mode dom --route selftest --viewport 1440x900 --out _scratch/st.html # 取自检输出 $PY _tools/mutate_test.py # 变异对照 $PY _tools/check_skeleton.py # 骨架冻结闸门 $PY _tools/check_js.py # JS 语法 + 模板里的反引号(第 5 个门禁,第六轮新增) ``` `cap.py` 对外只收 `--viewport`,**视口是声明式给的**(内部走 CDP `Emulation.setDeviceMetricsOverride`,见 §五 的通道口径一节)—— 不再有 `--window-size` 的偏移量口径,也不再自己起浏览器。 🔴 **复跑前先确认浏览器实例在跑**:本机明令「先探测、能复用就复用」(`skills/browser-harness/SKILL.md`)。 ```bash netstat -ano | grep -E "127\.0\.0\.1:9223 .*LISTENING" # 有 pid = 实例在跑 ``` 实例没起来时 `cap.py` 会**直接报错退出**,⛔ 不会偷偷拉起一个(拉起谁、拉几个,是人的决定)。 `_scratch/` 是跑出来的中间件、目录不存在时脚本会自建,看完随手删掉不影响任何结论;交付目录里只留脚本、截图与四份文档。 ## 二、自检 84 项(1440×900 主视口,PASS 84 / FAIL 0) 自检是原型自带的:`#/selftest` 路由会在真实场景里逐个点过每个可点元素。全文落盘如下,⛔ 不是摘要。 ⚠️ **2026-10-08 第六轮重跑**(学参照物的组件细节:功能入口卡补 22px 图标、图标线宽 1.7→2、网格最小宽 180→200、hover 补轻阴影、问答区加虚线分隔)。这一轮**判据条数与 ID 数都没变**(84 项 / 76 个 ID / 72 个真驱动),变的是**几何**:P1 首行顶 688 → **697px**、P2 471px。下面这段是按改动后的原型**重新落盘**的。 ```text 自检结果:PASS 84 / FAIL 0 PASS 路由 #/p/p1 渲染出 7 个顶层节点 PASS 路由 #/p/p2 渲染出 6 个顶层节点 PASS 路由 #/p/p3 渲染出 6 个顶层节点 PASS 路由 #/p/p4 渲染出 6 个顶层节点 PASS 路由 #/p/p5 渲染出 7 个顶层节点 PASS 路由 #/p/p6 渲染出 6 个顶层节点 PASS 路由 #/p/p7 渲染出 6 个顶层节点 PASS 路由 #/p/p8 渲染出 6 个顶层节点 PASS 六态 × 八页 = 48 个组合,全部渲染出内容 PASS 侧栏只有一层:8 个功能项(总览 / 选题 / 资产 / 创作 / 发布 / 画像 / 复盘 / 对账)排成一列药丸浮栏(图标+2 字短名),一项一页,第二层 0 个 PASS 八页一次点击可达:侧栏八个功能项直达 + 各页「功能入口」卡另给上下文入口(卡片共 8 个落点、无缺口) PASS 清单里每个 ID 都在页面上出现过(76 个,无悬空声明) PASS 页面上每个 data-act 都在清单里(共 638 次出现,无未登记的可点元素) PASS 清单里 72 个 ID 都真驱动过并给出可见反馈,0 个死控件(24 个场景,每个实例各重置一次) PASS 当刻被禁用的控件 4 个(有 disabled 属性:按契约在特定条件下不可点):P4-03, P5-06, P6-03, R-02 PASS 清单里每个 ID 都至少被驱动过一次(无「从未跑过」的条目) PASS 侧栏 8 个功能项逐个点过,每一项都落到了自己那一页 PASS 五处「输入后才可用」的联动都通:其中 4 处在空输入时确实是禁用的 → 输入后转可用 → 点了有反应 PASS 工具型纪律:零 .eyebrow / 零 .lead / 零 .hero PASS 入场编排不做逐区块:@keyframes 共 0 处(工具型至多一处协调出场) PASS 骨架冻结:八页 39 个板块的数量与顺序与 ②段 2b「板块清单」逐位一致,⛔ 未增删挪 PASS 结构 ①:八页各一个 h1,标题逐级不跳;每个板块都有文字标题 PASS 结构 ②:字号 12/14/16/18/20/24px 共 6 档,相邻差 2/2/2/2/4,正文档 14–16 在用 PASS 结构 ④:逐页查过,首屏同屏主档按钮 ≤1 个;本页实底按钮 1 种 PASS 组件口径:八页都没有用内联 style 表达选中态或覆盖字号(选中态走 .is-on / .is-active,字号走 --fs-* 档) PASS 交互态与可读性::hover / :focus-visible 都在且没被 outline:none 抹掉;tabular-nums、prefers-reduced-motion、防溢出三件套齐全 PASS 阴影:面板/卡片都用多层叠层,无单层死黑重阴影 PASS 间距:flex/grid 的 gap 取值全部落在登记刻度内(9 个刻度) PASS 浮层「随手记」中性关闭出口 = 1(右上 ×)|另有决策型关闭 1 个(R-03);遮罩是「点层外」通道,不计作出口 PASS 浮层「随手记」打开后焦点在层内(当前=X-close) PASS 浮层「随手记」连按 8 次 Tab,焦点始终锁在层内 PASS 浮层「随手记」Esc 能关 PASS 浮层「随手记」关掉后焦点回到触发件 G-06 PASS 浮层「随手记」点遮罩(层外空白)能关 PASS 浮层「全局检索」中性关闭出口 = 1(右上 ×)|另有决策型关闭 0 个;遮罩是「点层外」通道,不计作出口 PASS 浮层「全局检索」打开后焦点在层内(当前=X-close) PASS 浮层「全局检索」连按 8 次 Tab,焦点始终锁在层内 PASS 浮层「全局检索」Esc 能关 PASS 浮层「全局检索」关掉后焦点回到触发件 G-10 PASS 浮层「全局检索」点遮罩(层外空白)能关 PASS 浮层「两处假设说明」中性关闭出口 = 1(右上 ×)|另有决策型关闭 0 个;遮罩是「点层外」通道,不计作出口 PASS 浮层「两处假设说明」打开后焦点在层内(当前=X-close) PASS 浮层「两处假设说明」连按 8 次 Tab,焦点始终锁在层内 PASS 浮层「两处假设说明」Esc 能关 PASS 浮层「两处假设说明」关掉后焦点回到触发件 G-08 PASS 浮层「两处假设说明」点遮罩(层外空白)能关 PASS 浮层「资产选择器」中性关闭出口 = 1(右上 ×)|另有决策型关闭 0 个;遮罩是「点层外」通道,不计作出口 PASS 浮层「资产选择器」打开后焦点在层内(当前=X-close) PASS 浮层「资产选择器」连按 8 次 Tab,焦点始终锁在层内 PASS 浮层「资产选择器」Esc 能关 PASS 浮层「资产选择器」关掉后焦点回到触发件 P4-04 PASS 浮层「资产选择器」点遮罩(层外空白)能关 PASS 浮层「版本列表」中性关闭出口 = 1(右上 ×)|另有决策型关闭 0 个;遮罩是「点层外」通道,不计作出口 PASS 浮层「版本列表」打开后焦点在层内(当前=X-close) PASS 浮层「版本列表」连按 8 次 Tab,焦点始终锁在层内 PASS 浮层「版本列表」Esc 能关 PASS 浮层「版本列表」关掉后焦点回到触发件 P4-07 PASS 浮层「版本列表」点遮罩(层外空白)能关 PASS 浮层「发布二次确认」中性关闭出口 = 1(右上 ×)|另有决策型关闭 1 个(P5-10);遮罩是「点层外」通道,不计作出口 PASS 浮层「发布二次确认」打开后焦点在层内(当前=X-close) PASS 浮层「发布二次确认」连按 8 次 Tab,焦点始终锁在层内 PASS 浮层「发布二次确认」Esc 能关 PASS 浮层「发布二次确认」关掉后焦点回到触发件 P5-08 PASS 浮层「发布二次确认」点遮罩(层外空白)能关 PASS 浮层「核销二次确认」中性关闭出口 = 1(右上 ×)|另有决策型关闭 1 个(CHECKOUT-CANCEL);遮罩是「点层外」通道,不计作出口 PASS 浮层「核销二次确认」打开后焦点在层内(当前=X-close) PASS 浮层「核销二次确认」连按 8 次 Tab,焦点始终锁在层内 PASS 浮层「核销二次确认」Esc 能关 PASS 浮层「核销二次确认」关掉后焦点回到触发件 P8-03 PASS 浮层「核销二次确认」点遮罩(层外空白)能关 --- 数值取证(布局视口 1440 × 900 ;window.innerWidth 1440)--- PASS 对比度 --warn 对 --surface = 5.93:1(契约 ≥4.5:1) PASS 对比度 --accent-text 对 --surface = 4.76:1(契约 ≥4.5:1) PASS 对比度 --ink-faint 对 --surface = 3.55:1(契约 ≥3:1) PASS 强调色填充面积占比:八页最高 创作台 = 0.52%(1 个填充块,契约 ≤5%;不含 3px 左侧标条 / 描边 / 文字色) 量尺寸时滚动零点(P1):window 0 / html 0 / body 0 / .view 0 PASS 量尺寸前三个滚动容器都停在零点(P1):rect 取到的是相对视口的真值 PASS P1 账号矩阵:首屏从 697px 起,行距 31px,一屏装得下 6 行(契约 ≥6 行/900px 高,实测宽 1440)|示意数据 5 条 ⇒ 实际可见 5 行(数据用尽,非被截断) PASS P1 账号矩阵行高齐:31–31px,差 ≤2px(没有哪一行被状态标或长文字撑高) PASS P1 实际在用字号档 6 档:12px / 14px / 16px / 18px / 20px / 24px(契约 ≥3) PASS 视觉层间距 32px / 块内相邻行分隔 1px(发丝线,行本身不套框)⇒ 比值 32 ≥2(4 个视觉层) P1 各块落位(顶 / 高):今日待处理@333+249 · 账号矩阵列表@614+249 · 异常值提示@895+201 · 全局检索与随手记@1128+231(视口高 900;并排的两块顶相同) 量尺寸时滚动零点(P2):window 0 / html 0 / body 0 / .view 0 PASS 量尺寸前三个滚动容器都停在零点(P2):rect 取到的是相对视口的真值 PASS P2 对标内容列表:首屏从 471px 起,行距 31px,一屏装得下 13 行(契约 ≥6 行/900px 高,实测宽 1440)|示意数据 5 条 ⇒ 实际可见 5 行(数据用尽,非被截断) P2 各块落位(顶 / 高):对标监控概览@143+121 · 对标内容列表@389+249 · 评论区洞察@670+316 · 灵感速记@1018+99 PASS 横向溢出 = 0(scrollWidth 1440 = clientWidth 1440) PASS 八页逐页查横向溢出:全为 0(最差 p1) PASS 纵向滚动只由主区承担:.app 高 900px = 视口 900px;body 未承担滚动(scrollTop 0);主区 overflow-y auto,clientH 851px / scrollH 1374px(内容更高 ⇒ 主区可滚,折叠线以下的块拿得到) (附)P1 行高 31/31/31/31/31px · P1 按钮高度集合 32px · 量时 scrollY=0 · 顶栏高 49px · 导航占 900px(column)· 主区内衬上 24px · innerWidth 1440 − clientWidth 1440 = 0px(窗口滚动条宽度) 控制台报错:0 条 ``` 截图:`_gate_shots/selftest-1440.png`。 ## 三、变异对照 15 / 15(证明上面那串 PASS 不是恒绿) 一个永远 PASS 的自检等于没有自检。做法:复制原型 → 注入一处**已知的假病** → 再跑一次自检 → 要求它报红、**且红在预期那一条上**。 ```text 基线(未注入):PASS 84 / FAIL 0 ✅ M1 ⇒ PASS 83/FAIL 1 | 报红且红在预期处 ✅ M2 ⇒ PASS 82/FAIL 2 | 报红且红在预期处 ✅ M3 ⇒ PASS 82/FAIL 2 | 报红且红在预期处 ✅ M4 ⇒ PASS 83/FAIL 1 | 报红且红在预期处 ✅ M5 ⇒ PASS 83/FAIL 1 | 报红且红在预期处 ✅ M6 ⇒ PASS 82/FAIL 2 | 报红且红在预期处 ✅ M7 ⇒ PASS 82/FAIL 2 | 报红且红在预期处 ✅ M8 ⇒ PASS 82/FAIL 2 | 报红且红在预期处 ✅ M9 ⇒ PASS 83/FAIL 1 | 报红且红在预期处 ✅ M10 ⇒ PASS 83/FAIL 1 | 报红且红在预期处 ✅ M11 ⇒ PASS 75/FAIL 9 | 报红且红在预期处 ✅ M12 ⇒ PASS 77/FAIL 7 | 报红且红在预期处 ✅ M13 ⇒ PASS 77/FAIL 7 | 报红且红在预期处 ✅ M14 ⇒ PASS 83/FAIL 1 | 报红且红在预期处 ✅ M15 ⇒ PASS 83/FAIL 1 | 报红且红在预期处 变异对照:15 / 15 通过 ⇒ 15 处假病全部被自检抓住,且基线回绿:这套判据不是恒绿的。 ``` ⚠️ **M2 与 M8 这两个注入点本轮换过**(第三轮)。形态一改,注入点就必须跟着改,否则变异脚本会「找不到注入点、跳过」或者「注进去了但打不到判据」——**这两种都属于假对照,比没有变异更坏**(它会让人以为这条判据有人看着): - **M2**(未登记的可点元素):P2 的「对标内容列表」由行式列表改成满宽表格后,原注入点 `.action-cell` 里的 `P2-03` **在文件里已经不存在** ⇒ 本轮实测时脚本直接判「跳过」。已把注入点换到表格那一行(``)。 - **M8**(密度退化):密度判据本轮量的已是 P1 那张满宽表格(`.tbl-dense` 的 `td` 内衬),原注入点 `12px → 36px` 的 `.list-row` 内衬**对表格无效** ⇒ 实测 `PASS 83/FAIL 0`(没报红)。已把注入点换成表格 `td` 的内衬。 - 上一轮同样处理过 **M7 / M10**(`.list-row` → 表格 `td`)。⇒ 一条经验:**判据形态改了,必须逐个复核每个变异的注入点还在不在**,`mutate_test.py` 已经把「找不到注入点」显式报成 ⚠️ 而不是静默跳过(本轮就是靠它抓到的)。 ⚠️ **M9 换过两次**。第一版是「拿掉 `applyScene` 末尾的滚动复位」,实测观测不到差别(重绘 `#view` 时浏览器把滚动自然夹回 0)⇒ 假变异。第二版改成 `window.scrollTo(0,300)`,**2026-10-08 又发现它同样抓不到东西**:外壳修完之后滚动容器是 `.view`,`window` 根本滚不动,拿 window 注入等于没注入。现在注入点是 `document.querySelector('.view').scrollTop=300`,也就是这条守卫真正声称要拦的那件事。 ⚠️ **M14 是这一轮新加的**:它注入的就是 §六 第 16 条那个真缺陷的原样(`.app` 退回 `min-height:100vh`)。加它的目的只有一个 —— 证明新判据「纵向滚动只由主区承担」不是恒绿。 ## 四、骨架冻结闸门(正向 + 双向变异) 把②段 `prd/2b-界面布局.md` §二 当权威读,与原型 `FROZEN` 表逐页逐位比(数量相等、顺序相等、名字互为前缀)。 ```text 正向:PASS 骨架冻结:八页 39 个板块,②段 2b-界面布局.md §二 与原型 FROZEN 表逐页逐位一致 反向变异 1(改原型 p3 多一块):p3 板块数 ②段 4 ≠ 原型 5 反向变异 2(改②段副本 p1 第 2 块名):p1 第 2 块 ②段「账号矩阵一览」≠ 原型「账号矩阵列表」 ``` 两条反向变异改的都是**副本**(`_probe/2b-mut.md`),权威件 `prd/2b-界面布局.md` 全程只读。为了事后能复核这件事,这里钉一条指纹: ```text prd/2b-界面布局.md md5 = 61129a3c7944b7d0455d9530d7d373f7 15688 字节 mtime 2026-10-08 09:01 (上一版指纹 1dcc69f9…/15036 字节/02:30 已作废:②段在 09:01 自己动过一次 —— 那时 ②段 的 2b 定稿又改过一轮,③段手里那条指纹就旧了。⛔ 指纹只对「记下来的那一刻」负责。) ``` ✅ 复核方式:`md5sum ../prd/2b-界面布局.md`,与上面比。一致 ⇒ ③段确实没动过冻结骨架。 (这条指纹是删 `_probe/` 之前补登的 —— 备份删掉后,只剩这一个数能证明骨架没被污染,所以先写进正文再清临时件。) ## 五、多视口密度核对(契约:一屏可见行数 ≥6 / 900px 高,`layouts-tooling.md` §1 原文,⛔ 不分档) > 🔴 **2026-10-08 第七轮整表重测替换**(功能入口卡按参照物改成三段纵向之后)。这张表的**用法本轮变了**:不再只报「现在几行」,而是**同一台机器、同一套判据,跑改前/改后两版**逐格对照 —— 本轮改动的正当性就建立在这张对照上:卡长高 43px,但**绿色的视口一个没少**。 > 两版:`_scratch/base.html`(改前,=把本轮两处改动回退后的副本)/`mcn-workbench.html`(改后);各跑 15 个视口,全部走 CDP 声明式视口(见本节末「通道口径」)。 | 视口 | 自检 | P1 账号矩阵(改前 → 改后) | P2 对标列表 | 顶栏 | 侧栏 | |---|---|---|---|---|---| | 1440×900 | PASS 84/0 | 697 → **701px**,**6 行** ✅ | 13 行 | 49px | 64px 药丸浮栏 | | 1280×900 | PASS 84/0 | 697 → **701px**,**6 行** ✅ | 13 行 | 49px | 64px 药丸浮栏 | | 1024×900 | PASS 84/0 | 697 → **701px**,**6 行** ✅ | 13 行 | 49px | 64px 药丸浮栏 | | 1000×900 | PASS 84/0 | 677 → **676px**,**7 行** ✅ | 14 行 | 49px | 64px 药丸浮栏 | | 960×900 | PASS 84/0 | 677 → **676px**,**7 行** ✅ | 14 行 | 49px | 64px 药丸浮栏 | | 920×900 | PASS 84/0 | 677 → **676px**,**7 行** ✅ | 14 行 | 49px | 64px 药丸浮栏 | | 901×900 | PASS 84/0 | 677 → **676px**,**7 行** ✅ | 14 行 | 49px | 64px 药丸浮栏 | | 900×900 | PASS 84/0 | 677 → **676px**,**7 行** ✅ | 14 行 | 49px | 64px 药丸浮栏 | | 840×900 | PASS 84/0 | 677 → **676px**,**7 行** ✅ | 14 行 | 49px | 64px 药丸浮栏 | | 820×900 | PASS 84/0 | 677 → **676px**,**7 行** ✅ | 14 行 | 49px | 64px 药丸浮栏 | | 768×900 | PASS 83/**1** | 741 → 779px,**5 → 3 行** ❌ | 14 行 | 49px | 64px 药丸浮栏 | | 700×900 | PASS 83/**1** | 834 → 872px,**2 → 0 行** ❌ | 11 行 | 57px | 64px 药丸浮栏 | | 640×900 | PASS 84/0 | 711 → **700px**,**6 行** ✅ | 13 行 | 57px | 横滚标签条 | | 560×900 | PASS 83/**1** | 820 → 808px,**2 行** ❌ | 10 行 | 57px | 横滚标签条 | | 500×844 | PASS 83/**1** | 875 → 882px,**−1 → −2 行** ❌ | 8 行 | 57px | 横滚标签条 | **结论:绿/红的分界逐格完全一致** —— 绿:1440 / 1280 / 1024 / 1000 / 960 / 920 / 901 / 900 / 840 / 820 / 640;红:768 / 700 / 560 / 500。行本身没变肥(行高 **30.5–31px**、各行差 ≤0.5px、块内分隔 1px),在**所有**视口都 PASS。 > 🔴 **必须改正上一版的一处错数。** 上一版把 **768×900 记成「首行 677px、7 行 ✅」**,于是得出「**≥640px 宽时 P1 与 P2 都达标**」。本轮在**改前的版本上**重测是 **741px、5 行 ❌**(改后 779px、3 行)⇒ 那句结论是错的,真实分界是 **640 与 ≥820 绿;768 与 700 红;560 与 500 红**。 > > 这处错数的**根因**恰好就是本轮修掉的那件事:旧取证通道用 `chrome.exe --window-size` 定视口,而**窗口尺寸不等于 CSS 视口**(两条通道对同一尺寸的解释还不同,见本节末)。**768 恰好卡在功能入口卡「3 列/2 列」的阈值上(主区内容宽 632px),只差 7px** ⇒ 旧通道多给十几像素视口,卡就从两行变一行、首行顶差 64px。换成 CDP 声明式视口后这个歧义不存在了。 > > **本轮把高度还回去的三处**(实测 P1 首行顶 740 → **701**,−39px):① 删掉「功能入口」小标(−24.6px,八页各一处);② 统计条下边距 `8 → 6`(−2px);③ 块标题与内容之间的间距 `.block-head` `12 → 8`(P1 上方有三处,−12px)。`≤640` 一档另追加一条:卡内衬 `8/12 → 6/8`、图标 `18 → 16`、标题 `18 → 16`、`.block` 上边距 `32 → 24`(实测 724 → **700**,−24px)。 > > ⚠️ **归属必须说清**:本轮修的**只有我自己刚造成的回归**(改卡前 697/6 ✅ → 改完 740/5 ❌)。**768 / 700 / 560 / 500 这四处红是改前就有的,与本轮无关** —— 768/700 的根因是功能入口卡在 768 折成两行、统计条在 700 折成两行;500 是已知缺口。⛔ 本轮没有顺手改它们,免得把「本轮改了什么」和「本来就有什么」搅在一起。 > > ⚠️ **「改完只量主视口」这个毛病犯了两次,两次都是逐视口表兜住的。** 第一次(第六轮):加 22px 图标那轮只量了 1440(697px/6 行 ✅)就往下走,回头才发现 **640 由 5 行掉到 3 行**(卡变高 + 网格最小宽 180→200 ⇒ 608px 主区装不下三张卡、折成两行、卡块高度翻倍)。第二次(本轮):改完卡只量 1440(701/6 ✅,看着没事),是这张表把「改后 768/700/560/500 全红、1440 也从 6 行掉到 5 行」抓出来的。⇒ **只量契约视口、不回量窄屏,是会漏的;这条已经进 DESIGN.md §五 第 7 条。** > 上一轮(第四轮)修掉的两处旧错仍成立: > ①**顶栏列**:旧表在 901–1000 段写「93px(折两行)」、其他段写 69/53px —— 那是演示下拉还在的时候。顶栏收成「产品名 + 随手记」并把内衬 16→8 之后,**全宽度恒为 49px**(640 以下 57px)。 > ②**P2 列**:旧表写「6 行/7 行」,是**没跟着形态改的旧数**。P2 的「对标内容列表」第三轮已由行式列表改成满宽表格 ⇒ 行距 31px、容量 **14 行**,**任何视口都达标**(500×844 也有 8 行)。⇒ 现在**只有 P1 这一列**在窄屏/窄中屏不达标。 > > ⚠️ 另一条反直觉的实测(第三轮记的,首行顶数字已按本轮实测更正):同一个 768px 视口,侧栏 240px 时 P1 首行顶 **816px ⇒ 2 行**,收到 64px 后 **741px ⇒ 5 行**。**主区越窄,密度反而越差**(功能入口卡会被挤成两行)。 > > 还有一条被第三轮推翻的旧结论:第三轮记过「照搬 `3c-GPT参考版.md` 那版的满宽表格顺排 ⇒ 一屏只装得下 2 行」。**真的照搬了之后是 6 行**(行高 67px→31px)⇒ 正确表述是「满宽顺排与『一屏 ≥6 行』可以兼得,代价是行高必须压到 31px 上下」。 ### 通道口径(2026-10-08 第七轮改写:整个通道换掉了) **旧通道(已废)** 是 `chrome.exe --headless=new --window-size=… --dump-dom / --screenshot`。它有两个坑,第二个是这轮才彻底看清的: **坑一:两条模式对同一个 `--window-size` 的解释不一样。** ```text --window-size=656,995 ─┬─ --dump-dom ⇒ 视口 640 × 900 (mq<=640 true) └─ --screenshot ⇒ 视口 656 × 995 (mq<=640 false) ``` ⇒ 当时只能在调用侧按模式补偏移量(dom 补 +16 / +95,shot 原样)。而一旦补错,报告里就会出现「同一个 1440×900,一份证据当成 1440 说话、另一份当成 640 说话」这种自相矛盾 —— 本轮之前的截图全部比标称大 16×95(「1440×900」其实是 1456×995)。 **坑二:窗口尺寸本来就不保证等于 CSS 视口。** 这正是 §五 那处 768 错数的来源(差十几像素就跨过功能入口卡 3 列/2 列的阈值)。旧通道还有个硬上限:Chrome 窗口宽最小 516px ⇒ **视口宽量不到 500 以下**,所以此前契约里凡写 390 的地方一律按 500×844 记。 **新通道**:`_tools/cap.py` 改走浏览器实例的 CDP,用 `Emulation.setDeviceMetricsOverride` **声明式**定视口。三个后果: 1. **要多少是多少**:「1440×900」就是 1440×900(dump 里会印实际 `innerWidth/innerHeight`,不符直接断言失败)。 2. **偏移量那套口径整段作废**,`--window-size` 不再出现。 3. **500px 下限解除** —— 已实测 **390×844**(PNG 尺寸当场比对通过)。 同时保留原来的守卫:**每次截图后直接从 PNG 的 IHDR 读真实像素跟目标视口比**,不等就报红。 ⚠️ 还有一条同样重要的:新通道**只连已有实例,⛔ 不自己起浏览器**(本机明令,见 `skills/browser-harness/SKILL.md` 开头的「先探测、能复用就复用」)。实例没起来时 `cap.py` 直接报错退出,不会偷偷拉起一个。 ### 第八轮(A 方案 + 滚动条修复) > 🔴 **本块替换上一块的数据行。** 用户 2026-10-08 拍板「A 方案」。做法与结果: | 视口 | 改前 首行顶/cap | 改后 首行顶/cap | | |---|---|---|---| | 1440 / 1280 / 1024 ×900 | 701 / 6 | 701 / 6 | 一格未动 | | 1000 / 960 / 920 / 901 / 900 / 840 / 820 ×900 | 676 / 7 | 676 / 7 | 一格未动 | | **768 ×900** | 779 / 3 | **676 / 7** | ✅ 转绿 | | **700 ×900** | 872 / 0 | **676 / 7** | ✅ 转绿 | | **640 ×900** | 700 / 6 | **685 / 6** | ✅ 余量更大 | | **560 ×900** | 808 / 2 | **700 / 6** | ✅ 转绿 | | 500 ×844 | 882 / −2 | **773 / 2** | ❌ 唯一不达标 | **做了两件事 + 修了一个真缺陷**: ① 功能入口卡最小宽 `200 → 160px`(`≤1000` 档)—— 768 下主区内容宽 625px,200px 只能放 2 列 ⇒ 三张卡折成两行、卡块高度翻倍; ② 统计条最小宽 `132 → 96px`(`≤820` 档)—— 700 下 132px 只能放 3 列 ⇒ 4 格折成两行、统计条由 77.59 翻到 171.19; ③ 移动档标签条**藏掉 16px 横向滚动轨**(见 §六 第 32 条)。 ⚠️ 两处 `auto-fit` 都**只在真装不下时才缩** ⇒ 上面 10 个视口一格未动,这是"不是拿宽屏换窄屏"的直接证据。 ## 六、本轮修掉的真缺陷(每条:现象 → 根因 → 证据) | # | 现象 | 根因 | 证据 | |---|---|---|---| | 1 | 整份设计系统在屏幕上失效 | 样式表顶部注释里 `--t-*/--raw-*` 的 `*/` 提前闭掉注释,吞掉整个 `:root` 令牌块 | 修复后对比度取证才量得出来 | | 2 | 全表行分隔线一直没画出来 | `.list-row:first-child{border-top:0}`:每行都包在 `
  • ` 里,于是**每行都是自己 `
  • ` 的 first-child**,`borderTopStyle` 全是 `none` | 变异 M10;另修了判据除数为 0 时恒 PASS(比值 `Infinity`) | | 3 | 浮层不浮 | `#layer-root` 与 `.layer` 都没有定位样式,浮层渲染在文档流末尾,得滚到页尾才看得见 | 改为遮罩 `fixed inset:0` + 层本体 `fixed` 居中 | | 4 | 行高随文字长短乱跳(66/82/100px) | 状态标放在操作列把 `auto` 列撑宽,只有那一行折行 | 状态标移到名称旁;再加「行内每段不许换行 + 省略号」;判据新增「行高齐」 | | 5 | 自检在窄视口给出假 PASS「装得下 10 行」 | 驱动几百次点击后浏览器为聚焦元素自动滚动,`rect.top` 变负,容量公式虚高 | 加「量尺寸时页面在滚动零点」守卫 + 变异 M9 | | 6 | 手机导航「既不是一条、也没滚动」,占 161px | `.sidebar` 只给了 `flex-direction:row`,可导航分组挂在 `