test(webui): 「滑动硬截断」诊断脚本(自带浏览器,不依赖共享实例)

用户:「webui 大片界面存在滑动硬截断」。

## 现状:这一轮我**没能复现**

量法是"内容溢出但没有可滚动祖先"⇒ 候选 0 个;换成用户视角四问
(有没有可滚动容器 / 能不能滚到底 / 末尾有没有被切 / 页面自己溢不溢出)后:

    390×700  通信 ✅ 日历 ✅ 联系人 ✅ 我的 ✅
    1400×700 通信 ✅ 日历 ✅ 联系人 ✅ 我的 ✅
    (造了 12 个会话、正文都很长;验完已归档)

即按这四种量法都测不到硬截断。**长正文详情页**那一块我的探针超时没跑完 ⇒
**未判定**,不能算"没问题"。

## 这次的交付物

`client/electron/test/manual/scroll-cut-verify.mjs`:把上面这套量法固化下来,
并且**自带浏览器**(不连共享 9222)—— 共享实例被历次被中断的运行留下僵尸上下文后,
`connectOverCDP` 会一直超时(我这个会话里已经遇到三次),诊断脚本必须在
"共享实例状态未知"时也能跑。

## 我需要的(否则只能继续猜)

请指一下**具体页面 + 具体手势/位置**:哪一屏、滚轮还是触摸拖动、硬截断出现在
列表末尾还是详情正文?也可以直接看截图。我上一轮"窄屏显示不全"也是同样情况 ——
我量不出来、但你说有,那多半是我量的维度不对。
This commit is contained in:
2026-09-14 13:43:11 +08:00
parent e0c68d8bb2
commit f6feb7c0df
559 changed files with 261 additions and 8 deletions

View File

@ -10,6 +10,7 @@
import { readFileSync, readdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import postcss from 'postcss';
const here = dirname(fileURLToPath(import.meta.url));
const read = p => readFileSync(join(here, p), 'utf8');
@ -213,7 +214,8 @@ check('新组件未使用未映射色族', unmapped.length === 0, unmapped.join(
};
walk(join(here, '../src'));
const PAGE_BASE = new Set(['bg-gray-50', 'bg-slate-100']); // 页面底必须保持全透明
const missing = [...used].filter(c => !PAGE_BASE.has(c) && !gen.includes(`.${c} {`));
const manifest = [...used].filter(c => !PAGE_BASE.has(c));
const missing = manifest.filter(c => !gen.includes(`.${c} {`));
check('源码里用到的浅色表面类全被接管', missing.length === 0, `漏了:${missing.join(', ')}`);
/*
* ★ 生成文件的**头注释不能提前闭合**(2026-09-14 修,与上面那条同一根因的两个后果)。
@ -242,6 +244,57 @@ check('新组件未使用未映射色族', unmapped.length === 0, unmapped.join(
check('页面底没有被写进半透明清单', [...PAGE_BASE].every(b => !gen.includes(`.${b} {`)));
check('清单不是空跑(至少扫到 10 个类)', used.size >= 10, `实际 ${used.size}`);
/*
* ★ 生成产物"能被 CSS 解析器吃下"还是"只有浏览器能发现"(2026-09-14,pi 提议)。
*
* 上面那条查的是**文本**:剥掉注释后以第一条选择器起头。而"注释提前闭合"这个 bug
* 更狠的形态是:那条规则在文本里**还在**,甚至在 postcss 里**也还是一条规则**
* —— 但它的选择器前面粘上了注释的尾巴(当年构建只给一句
* `[WARNING] Unexpected "14"`),于是浏览器按"选择器不匹配任何元素"整条丢掉。
*
* 所以光"能解析"不够(实测:postcss 对坏产物照样解析出 1 条规则,只是选择器变了),
* 判据要卡的是**选择器的形状** + **规则条数**:
* - 每条规则的选择器必须**恰好**是 `html[data-bg='on'] .bg-xxx`(多一个字符都不行);
* - 规则条数必须等于源码里扫到的接管清单条数(多一条少一条都要红)。
* 收获:把"生成器写坏产物"从"只有浏览器能发现"变成"跑判据就红"。
*/
const RULE_SHAPE = /^html\[data-bg='on'\] \.bg-[a-z]+-\d{2,3}$/;
let parsedSelectors = null;
let parseErr = '';
try {
parsedSelectors = postcss
.parse(gen)
.nodes.filter(n => n.type === 'rule')
.map(n => n.selector);
} catch (e) {
parseErr = String(e && e.message ? e.message : e);
}
check('生成的接管 CSS 能被 CSS 解析器读下来', parseErr === '', parseErr);
if (parsedSelectors !== null) {
const malformed = parsedSelectors.filter(s => !RULE_SHAPE.test(s));
check(
'每条接管规则的选择器形状都与生成器一致(防注释尾巴粘进选择器)',
malformed.length === 0,
`异常选择器:${malformed.slice(0, 3).join(' | ')}`
);
check(
'接管规则条数与源码扫到的清单条数一致',
parsedSelectors.length === manifest.length,
`规则 ${parsedSelectors.length} 条 / 清单 ${manifest.length} 个`
);
// 反向对照:用生成器**当年**的坏写法(注释里带"星号紧接斜杠")验证判据真的会红
const broken = "/* 来自 src/**/*.tsx 的用法 */\nhtml[data-bg='on'] .bg-amber-100 { background: red }";
const brokenSels = postcss
.parse(broken)
.nodes.filter(n => n.type === 'rule')
.map(n => n.selector);
check(
'判据自检:注释提前闭合后,第一条规则的选择器必须被判为异常形状',
brokenSels.length === 0 || brokenSels.some(s => !RULE_SHAPE.test(s)),
`解析出:${brokenSels.join(' | ')}`
);
}
// 18) ★ 导航栏必须**完全不透明**、内容面板必须更通透、整体圆角玻璃化。
//
// 用户 2026-09-14 原话:「导航栏应当完全不透明……没有正文的位置过于不通透,