Files
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

7.4 KiB
Raw Permalink Blame History

name, description, agent_created
name description agent_created
html-lint-false-positive-zero-visual-fix 当「行级」HTML/设计检测器(如 impeccable 的 detect.mjs)对生成的静态 HTML 产物批量报「压行假红」时,如何做到**零视觉变化**地收敛——不改元素、不改配色、不改字体,只按元素/令牌拆源码行。当用户说「这些告警逐条判真红假红」「消掉检测器的假红」「检测器告警太多,别改样式」时使用。核心手法:把换行插在**解析器会吃掉的位置**(标签收尾 `>` 之前)或 **class 值内部令牌之间**。 true

行级检测器的「压行假红」零视觉变化收敛

治「设计检测器一次报几十上百条告警,但逐条看下来大半是它自己判歪了;用户又明确说『样式保持现在的』,不许借机改字体改配色」。 前置:先确认告警到底是真红还是假红(§1),⛔ 不要一上来就改东西。


0. 铁律

  1. 先读检测器源码,再判真假。 告警的判定逻辑写在规则里(正则 + 行级/文件级 test)。不读源码就判真假,等于猜。
  2. 假红只能「消音」,真红只能「改设计」。 两者混在一起做,会把「改样式」偷偷夹带进「消告警」批次。
  3. 本技能只处理「可零视觉变化收敛」的假红。 真要改视觉(换配色/字体/渐变)=设计决策,必须单独问用户,⛔ 不并入本批。
  4. ⛔ 不要「重跑生成器」来修。 产物往往已被后续手工改动领先于生成器;重跑会回退。一律就地改产物。
  5. 每条改动都要有「渲染零变化」的证据**,不是"我觉得没事"。** 见 §4。

1. 判真假:行级规则长什么样

典型「行级」规则(impeccable detector/engines/regex/detect-text.mjs):

{ id: 'gray-on-color', regex: /\btext-(?:gray|slate|zinc|neutral|stone)-(\d+)\b/g,
  test: (m, line) => /\bbg-(?:red|orange|...|rose)-\d+\b/.test(line),   // ← 按【行】判
  fmt:  (m, line) => `${m[0]} on ${line.match(/\bbg-.../)[0]}` }

⇒ 只要同一源码行里同时出现两类令牌就报,不管它们是不是同一个元素、也不管 bg-* 是不是 hover: 变体。

由此产生的两类假红:

类型 成因 判定依据
压行假红 一行里塞了多个元素(生成的 HTML 常把整段 <main> 压一行,或按钮组压一行):bg-red-500 的元素和 text-neutral-700 的元素相邻但不同元素 灰字元素自己的底是 bg-white/无色
变体假红 同一元素,但被报的 bg-* 出自 hover:bg-*/focus:bg-*,静止态是 bg-white,且悬停时文字也换色(hover:text-rose-600) 静止态与悬停态两态都不是规则描述的病态

判据脚本骨架:把告警行的行号取出 → 取该源码行 → 用正则切标签 → 看「灰令牌」和「彩色 bg 令牌」是否落在同一个 tag 里、且那个 bg 是否带 hover: 之类的变体前缀。


2. 两种零视觉变化的拆行手法(本技能的核心)

目标:让两类令牌落到不同的源码行,但不动渲染。关键是「换行必须插在解析器/序列化器会丢掉的位置」。

手法 插在哪 为什么零变化 保证
A. 标签收尾前 标签的 > 之前(自闭合则 /> 之前) HTML 解析器会吃掉标签名/属性与 > 之间的空白;序列化时又统一输出 > DOM 逐字节不变 ⇒ outerHTML 原样指纹不变
B. class 值内部 class="…" 里两个 class 令牌之间 class 是空白分隔的令牌表,换行 ≡ 空格(CSS 匹配、classList 取值都按令牌集合) classList/计算样式/几何全不变;只有该属性字面量里多一个换行

什么时候用哪个:

  • 跨元素(压行假红)→ 手法 A 就够(每个带令牌的标签前都插一个)。
  • 同一元素内(变体假红)→ 必须用手法 B:按令牌顺序扫,遇到「当前段已含一类、下一个令牌是另一类」就在该令牌前断行(贪心分段,保证每段不含两类)。

切分点够不够:给「行内每个带令牌的标签」都在其 > 前断一次 ⇒ 每个源码行最多只含一个带令牌标签的 class,于是跨元素假红必然消失。

⛔ 安全边界:必须全文件扫描标签,跳过 <!-- --> 与 <script>/<style> 正文里的伪标签——JS 里拼的 HTML 字符串常含 class="text-neutral-500",进去插换行会改坏 JS。⛔ 别做逐行扫描。


3. 执行协议

# 1) 备份(含 md5 记账)
mkdir -p <tmp>/backup-<名>-<日期时间> && cp 0*.html <备份目录>/ && (cd <备份目录> && md5sum 0*.html > _md5-before.txt)
# 2) 干跑 → 看计数与断言;3) 落地
python fix-xxx.py            # 干跑
python fix-xxx.py --apply    # 落地

修法脚本必备的四条断言(任一不过就停手):

  1. new.replace("\n","") == old.replace("\n","") —— 除新增换行外正文逐字符不变;
  2. 标签数不变(同一套扫描函数前后各数一次);
  3. <script 计数不变;
  4. 落盘字节 == 预期字节,且 b"\r\n" not in 落盘。

⚠️ Windows 坑:Path.write_text(...) 默认会把 \n 翻成 \r\n,全文件变字节。必须 write_text(new, encoding="utf-8", newline="\n")。


4. 验收矩阵(「零视觉变化」的证据)

项 期望
检测器计数 目标类别下降,其它类别不升(逐页比对,⛔ 不允许某页变多)
结构体检 原有结构脚本仍全绿(例:health-full.py rc=0)
归一化指纹 outerHTML 折叠连续空白后哈希 不变
几何 每个后代元素的 tag|id|classList排序|left/top/w/h 串哈希 不变
渲染文本 innerText 归一化后哈希 不变
classList 集合 全后代类名排序串哈希 不变
元素数 不变

⚠️ 巨大读数坑:结构脚本若用 outerHTML 的原样哈希(含空白)当「DOM 指纹」,手法 B 一定会让它变。⛔ 别把「指纹变了」当成「渲染变了」;要比折叠空白后的哈希 + 几何 + classList + 渲染文本。

  • 只用手法 A 的页面,原样指纹应完全不变——这是手法 A 正确性的强证据。

⚠️ 真点坑:自动化窗口不在前台时(document.hidden === true / visibilityState === 'hidden'),真实鼠标点击(CDP Input.dispatchMouseEvent / 坐标点击)会被静默丢弃:同坐标 elementFromPoint 明明命中按钮,点击却毫无反应。⇒ 先 Page.bringToFront;仍 hidden 就用 JS .click() 验接线,并把真点项如实记为「未取到读数」。⛔ 别把点不着的按钮判成坏按钮。


5. 交付物要求

  • 逐条判定表落盘(文件名含日期):每条给出 检测器报项 | 判定(真红/假红+子类)| 依据 | 是否已收敛。
  • 「有意保留」项必须逐条写明理由,并在总览里给出「修前 → 修后」计数。
  • 回滚方法(备份目录 + md5 清单)必须写进交付物。
  • 若检测器 rc 不能归零:说明「剩余全是保留项」并给出理由——多数清单的判据本就写的是「rc=0 或把有意保留项写明」。⛔ 别为了把 rc 压成 0 去动字体与配色。