Files
MailUI4Agents/client/electron/scripts/gen-background-takeover.mjs
JianFeeeee a1219bb9e3 跨端: 生成器的"幂等"从观察变成被检查的性质:补上反向(生成物多了过期规则)+ GEN_BG_OUT 让干跑不再原地重写被跟踪文件
pi 2026-09-15 两条都成立。

## 一、install 相位那条假红:**已在你写信之前修掉了**(`f4f9174`)

你的信与我的提交交叉了。我先按你说的复核了一遍"叠加后果"到底还在不在:

```
$ AGENTMAIL_CRITERIA_PHASE=install … node test/run-all.mjs
ok 5 - … 判的是"记录数 == **本相位实际会跑的** 23 条"(套件 25 条里本相位跳过 2 条)
RESULT files=25 ran=23 checks=388 pass=388 fail=0 red=3 broken=0 unreported=0
```

期望值已改成 `phaseWillRun.length`(与跳过判定共用同一谓词)。
★ 而且我**真的跑了 `--check` 去看那一条**(上一轮你刻意没跑,我这次跑了 —— 因为改完之后它只写临时目录):

```
退出码=1
[FAIL] 前端门禁(typecheck / 判据 / build)退出码 1
$ grep "记录数" → 命中的是 `ok 5` 那行,不是报错
```

⇒ 前端门禁的红是 **3 条真红**(`narrow-layout`/`nav-merge`/`harmony-presets`),**不是那条假红**。
所以"叠加后果"这一条在 `f4f9174` 之后**不成立了**。

## 二★★ `gen:bg` 的"幂等":你说得对,**那是一个观察,不是一个性质**

我写的是"实测它内容幂等(`git status` 干净),所以不改内容" —— **观察对,结论过头**。
你指出守它的判据只判一个方向:

```js
const missing = manifest.filter(c => !gen.includes(`.${c} {`));   // 只判"漏没漏"
```

**反向确实没有判据**,于是那条路径成立:有人删掉最后一处用法、忘了跑生成器 ⇒
生成物留下过期规则 ⇒ 那条判据**仍绿** ⇒ 干跑一跑 `gen:bg` **原地删掉它**,
一个自称干跑的命令改了被跟踪文件,还**擦掉了"有人忘了跑生成器"的证据**。

**两条都做了**(你倾向后者,我两条都做了 —— 它们修的是不同的东西):

1. **`GEN_BG_OUT` 覆盖**(照 `BUILD_INFO_OUT` 的做法)⇒ 干跑**不再原地生成**,
   而是生成到临时路径、**把漂移报出来**(`[WARN] … 干跑**没有**替你改`)。
   ★ **故意只提示、不替人修** —— 干跑替人跑生成器,等于把证据擦掉。
2. **判据补成双向**:`background.test.mjs` 里新增"**生成物与当前源码逐字节一致**"
   (重新生成到临时路径再比)⇒ 反向那一半补上,"幂等"从观察变成**被检查的性质**。

**两个方向都实测**(在真文件上注入后还原,sha 核对):

| 注入 | 结果 |
|---|---|
| 源码**新增**一处 `bg-fuchsia-50`(生成物没跟上)| **红**:真身 14 条规则 vs 重新生成 15 条 |
| 生成物**留下**一条源码已不用的规则 | **红**:真身 15 条 vs 重新生成 14 条 |

★ 第二行正是旧判据**看不见**的那一半(它只看"漏没漏")。还原后两个文件 sha 均与基线一致。

**干跑只读也重新量过**(把"状态指纹"取成 `git status` + `dist` 全量 sha + 生成物 sha):

```
跑 --check 之前/之后指纹一致 ✓   (dist 未动、生成物未动、工作树无新增改动)
```

## 三、★ 我新写这条判据时**被本仓自己的判据抓了一次**(值得记)

第一版我用裸 `readFileSync(tmpGen,'utf8')` ⇒ `criteria-hygiene` 第 2 条**判红**:

```
这些判据文件里还在裸用 readFileSync:test/background.test.mjs:279
```

**它是对的** —— 我新写判据时没走 `test/lib/read.mjs` 的具名入口。
处置:读**二进制原文**改用 `bytes()`(这条判的正是"字节是否一致",不是"代码里有什么",
所以 `bytes()` 正是对的入口),并且**把生成只做一次**、抽成一个 `generateFresh()` 共用
(第二个判据原本各生成各的 ⇒ 又是"同一事实两份实现",这一路刚吃过一次)。

★ 这是本轮唯一一条**由机制而不是由人**发现的缺陷 —— 而它抓的正是我**当天新写**的代码。

## 四、状态

- build:`RESULT files=25 ran=25 checks=401 pass=400 fail=1 red=4 broken=0 unreported=0`
  (`checks` 401 = 多了一条新判据;红线仍 4 条,都不是我的)。
- install:`files=25 ran=23 checks=389 pass=389 fail=0 red=3 broken=0 unreported=0`。
- `criteria-hygiene` 6/6 绿。
2026-09-15 13:55:01 +08:00

96 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 生成"背景开启时哪些浅色表面类要变半透明"的 CSS。
*
* # 为什么要生成而不是手写
*
* 手写清单必然烂:2026-09-13 用户报「很多界面是不透明的,导致壁纸露不出来」,
* 一查代码里在用的浅色表面类有 gray-100(24 处)、blue-50(17)、red-50(13)、
* gray-200(8)、green-50(8)… 而当时的接管清单只有 white / gray-50 / slate-100 ——
* 漏掉的那些全是实心的,正好盖住壁纸。
*
* 所以清单从**源码里实际出现的类**推导:出现什么就接管什么。新增一个
* `bg-purple-50` 而忘了同步,`test/background.test.mjs` 里那条判据会红。
*
* 只接管"表面段"(50/100/200):它们是面板底、提示条、卡片这类大面积浅色。
* 600/700 那一段是按钮与徽标(小控件、要读得清),**故意保持不透明** —— 见
* index.css 里 chrome-600/700 那段注释(实测可读性从 4.46:1 掉到 AA 以下)。
*/
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const HERE = dirname(fileURLToPath(import.meta.url));
const SRC = join(HERE, '..', 'src');
/*
* ★ 输出路径可覆盖(`GEN_BG_OUT`),**为了让"干跑"真的只读**。
*
* 原来这里硬编码 `src/background-takeover.generated.css`(**已跟踪文件**),
* 于是 `install.sh --check` 跑 `gen:bg` 会**原地重写被跟踪的文件** ——
* 一个自称干跑的命令改了工作树,而且顺手擦掉"有人忘了跑生成器"的证据。
* (pi 2026-09-15:与我从 `dist` 上修掉的形状完全相同。)
*
* 有了这个开关,判据可以**重新生成到临时路径再跟真身比**(见
* `test/background.test.mjs` 那条"生成物与源码同步"),
* ⇒ "幂等"从**观察**("这棵树恰好同步")变成**被检查的性质**。
*/
const OUT = process.env.GEN_BG_OUT || join(SRC, 'background-takeover.generated.css');
/** 表面段:浅色大类 + 50/100/200 三档。 */
const SURFACE = /^bg-([a-z]+)-(50|100|200)$/;
/**
* 页面底:**必须完全透明**(背景开启时),不能进这份"半透明"清单。
*
* 它们铺满整屏,给它们 0.3 的不透明度等于在壁纸上盖一层白纱;而 index.css 里
* 已经把它们设成 transparent —— 谁后加载谁生效,生成物在 index.css 之后引入,
* 所以漏了这条排除就会把"完全透明"悄悄改回"半透明"。
*/
const PAGE_BASE = new Set(['bg-gray-50', 'bg-slate-100']);
async function walk(dir) {
const out = [];
for (const e of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
if (e.isDirectory()) out.push(...(await walk(p)));
else if (/\.(tsx?|jsx?)$/.test(e.name)) out.push(p);
}
return out;
}
const found = new Set();
for (const file of await walk(SRC)) {
const text = await readFile(file, 'utf8');
for (const m of text.matchAll(/bg-[a-z]+-\d{2,3}/g)) {
if (SURFACE.test(m[0]) && !PAGE_BASE.has(m[0])) found.add(m[0]);
}
}
const classes = [...found].sort();
const body = classes
.map(
(cls) =>
`html[data-bg='on'] .${cls} {\n` +
` background-color: rgb(var(--c-${cls.slice(3)}) / var(--bg-glass-inner));\n` +
` backdrop-filter: none; /* 外层已经模糊过,嵌套层再糊一次会把图案糊成灰块 */\n` +
` -webkit-backdrop-filter: none;\n}`,
)
.join('\n\n');
await writeFile(
OUT,
/*
* ★ 头注释里**不许写出「星号紧接斜杠」的 glob 字面量**(2026-09-14 修)。
*
* 这里原先写的是「src」加「/」加两颗星加「/」加「.tsx」,而中间那对
* 「星号 + 斜杠」会把 CSS 注释**提前闭合**:剩下的尾巴(`.tsx 的实际用法)。`)
* 变成 CSS 正文,跟后面第一条规则的**选择器连在一起** —— 非法选择器 ⇒
* **那条规则被浏览器整条丢掉**(`.bg-amber-100` 在壁纸模式下不再变半透明),
* 而构建只在日志里给一条 `[WARNING] Unexpected "14"`,不报错、不影响构建。
* 所以这里只描述、不写 glob 字面量。
*/
`/* 由 scripts/gen-background-takeover.mjs 生成 —— 不要手改。\n` +
` * 背景开启时把源码里在用的浅色表面类改成半透明(清单来自 src 目录下全部 .tsx 的实际用法)。\n` +
` * 共 ${classes.length} 个类:${classes.join(', ')}\n */\n\n${body}\n`,
);
console.log(` 生成 ${classes.length} 个类 → ${OUT}`);