fix(webui): 导航令牌迁移收尾 + 生成 CSS 的头注释提前闭合(会丢规则)+ 两条过期判据重写 + 判据进 npm test

## 改法

- **`Sidebar.tsx` 底部两个按钮还是旧的深色导航假设**(`text-chrome-400
  hover:text-white hover:bg-chrome-800`、外层 `border-chrome-700`):导航改成白玻璃
  (`--nav-bg: 255 255 255 / 0.72`)之后,`chrome-400` 落在白底上约 2.6:1(图标要
  3:1),hover 还会在白导航上闪出一块近黑。账号头像那个按钮当时换成了 `.nav-item`,
  这两个漏了 —— 就是"导航令牌迁移做了一半"。已换成 `.nav-item` + `border-gray-200`。
  没加"Sidebar 里不许有 chrome-*"的判据:另有一处 `bg-chrome-600 text-chrome-100`
  是实心小色块(正常用法),一刀切会误红。

- **`background-takeover.generated.css` 的头注释提前闭合**:生成器在注释里写了
  「src」加「/」加两颗星加「/」加「.tsx」,其中那对「星号 + 斜杠」把 CSS 注释就地
  结束 —— 尾巴变成 CSS 正文,并与第一条规则的选择器连在一起成为非法选择器 ⇒
  **`.bg-amber-100` 那条接管规则被浏览器整条丢掉**(壁纸模式下不再变半透明)。
  构建只给一条 `[WARNING] Unexpected "14" [css-syntax-error]`,不报错、不影响构建,
  正是这套判据存在的理由。改在生成器(注释里只描述、不写 glob 字面量)并重新生成:
  压缩输出现在以 `html[data-bg=on] .bg-amber-100{` 起头、14 条规则全在、无告警。
  另加两条判据("头注释没提前闭合" + 自检)。

- **`background.test.mjs` 第 18 组三条重写**:原先断言「浅色/深色两套 `--nav-bg` 都
  定义」与「壁纸模式下 `.nav-rail` 里有 `backdrop-filter`」,两条编码的都是**已被
  有意撤掉的设计**(`faacd3c` 撤深色导航、`9aa702b` 撤导航自叠模糊),于是 `npm test`
  在 HEAD 上恒红。判据红成常态就不再是判据 —— 该改的是判据本身,而不是把缺陷写回代码。
  改成方向相反的两条:`.dark` 不许单独给导航换色 / 模糊只由壁纸层负责。

- **`cross-client-theme.test.mjs` 从来不在 `npm test` 链里**(vitest 只收
  `test/components` 与 `test/stores`)—— 那条"防漂移判据"从没在默认套件里跑过。
  已加进 `npm test`。

## 验证

- 变异测试:往 `.dark { }` 塞一行 `--nav-bg: 15 23 42 / 0.72;` → 红;往
  `html[data-bg='on'] .nav-rail` 塞 `backdrop-filter: blur(18px);` → 红;撤回 → 绿。
- `npm test` 退出码 0:窄屏布局全通过、主题 30、背景 34、cross-client 8、packaging 3、
  vitest 258/258;`npm run typecheck` 通过。
- packaging 第 3 条此前是红的,但**不是判据过期**:安装包真的落后于 dist,而 `npm test`
  的 `&&` 链一直在 background 那条就中断,`packaging` 从没跑到过。已 `npm run build`
  + `npx electron-builder --linux -c.electronDownload.isVerifyChecksum=false` 重打包。
  ⚠️ deb 目标在本机打不出来(fpm 的 portable ruby 在 `Dir.chdir` 处退出),
  AppImage 与 `linux-unpacked` 正常。
This commit is contained in:
2026-09-14 13:29:57 +08:00
parent b041ea51e4
commit 51522dd8c2
5 changed files with 83 additions and 11 deletions

View File

@ -19,7 +19,7 @@
"build:linux": "vite build && electron-builder --linux",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"test": "node test/markdown-xss.test.mjs && node test/narrow-layout.test.mjs && node test/theme.test.mjs && node test/background.test.mjs && node test/packaging.test.mjs && vitest run",
"test": "node test/markdown-xss.test.mjs && node test/narrow-layout.test.mjs && node test/theme.test.mjs && node test/background.test.mjs && node test/cross-client-theme.test.mjs && node test/packaging.test.mjs && vitest run",
"test:narrow": "node test/manual/narrow-verify.mjs",
"test:wide": "node test/manual/wide-regression.mjs",
"test:components": "vitest run",

View File

@ -66,8 +66,18 @@ const body = classes
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` +
` * 背景开启时把源码里在用的浅色表面类改成半透明(清单来自 src 目录下全部 .tsx 的实际用法)。\n` +
` * 共 ${classes.length} 个类:${classes.join(', ')}\n */\n\n${body}\n`,
);
console.log(` 生成 ${classes.length} 个类 → ${OUT}`);

View File

@ -1,5 +1,5 @@
/* 由 scripts/gen-background-takeover.mjs 生成 —— 不要手改。
* 背景开启时把源码里在用的浅色表面类改成半透明(清单来自 src/**/*.tsx 的实际用法
* 背景开启时把源码里在用的浅色表面类改成半透明(清单来自 src 目录下全部 .tsx 的实际用法)。
* 共 14 个类bg-amber-100, bg-amber-50, bg-blue-100, bg-blue-50, bg-gray-100, bg-gray-200, bg-green-100, bg-green-50, bg-orange-100, bg-orange-200, bg-orange-50, bg-red-100, bg-red-50, bg-yellow-100
*/

View File

@ -133,7 +133,15 @@ export default function Sidebar() {
(见 App 里的 ComposeFab导航栏就只剩三项。
*/}
<div className="mt-2 pt-2 w-full flex flex-col items-center gap-1 border-t border-chrome-700">
{/*
底部这一块原先还是**旧的深色导航假设**(外层 border-chrome-700、
两个按钮 text-chrome-400 + hover:bg-chrome-800—— 导航改成白玻璃
--nav-bg: 255 255 255 / 0.72之后chrome-400 落在白底上只有约 2.6:1
(图标要 3:1hover 还会在白导航上闪出一块近黑。账号头像那个按钮当时
换成了 .nav-item这两个漏了 —— 就是"导航令牌迁移做了一半"。
chrome 那条色阶是给"框架本来就该深"的假设用的,白玻璃导航不该再用它。
*/}
<div className="mt-2 pt-2 w-full flex flex-col items-center gap-1 border-t border-gray-200">
<button
onClick={() => setViewMode('account')}
title={`${user?.display_name || user?.username}(点击管理账号)`}
@ -146,11 +154,15 @@ export default function Sidebar() {
<ConnectionIndicator />
</span>
</button>
<ThemeToggleButton className="w-9 h-7 rounded flex items-center justify-center text-chrome-400 hover:text-white hover:bg-chrome-800" />
{/*
.nav-item 是未分层 CSS级联上压得过 Tailwind utilities
所以直接替掉类名即可,不必再补 text-*。
*/}
<ThemeToggleButton className="nav-item w-9 h-7 rounded flex items-center justify-center" />
<button
onClick={logout}
title="退出登录"
className="w-9 h-7 rounded flex items-center justify-center text-chrome-400 hover:text-white hover:bg-chrome-800"
className="nav-item w-9 h-7 rounded flex items-center justify-center"
>
<LogoutIcon className="w-3.5 h-3.5" />
</button>

View File

@ -215,6 +215,30 @@ check('新组件未使用未映射色族', unmapped.length === 0, unmapped.join(
const PAGE_BASE = new Set(['bg-gray-50', 'bg-slate-100']); // 页面底必须保持全透明
const missing = [...used].filter(c => !PAGE_BASE.has(c) && !gen.includes(`.${c} {`));
check('源码里用到的浅色表面类全被接管', missing.length === 0, `漏了:${missing.join(', ')}`);
/*
* ★ 生成文件的**头注释不能提前闭合**2026-09-14 修,与上面那条同一根因的两个后果)。
*
* 生成器原先在头注释里写了「src」加「/」加两颗星加「/」加「.tsx」其中那对
* 「星号 + 斜杠」把注释**提前闭合**:尾巴变成 CSS 正文,跟第一条规则的选择器
* 连在一起成为非法选择器 ⇒ **那条规则被浏览器整条丢掉**`bg-amber-100` 在
* 壁纸模式下不再变半透明)。构建只给一条 `[WARNING] Unexpected "14"`
* 不报错、不影响构建 —— 正是这套判据存在的理由。
*
* 判据:把注释剥掉之后,文件必须以第一条规则的**选择器**起头。
*/
const stripped = gen.replace(/\/\*[\s\S]*?\*\//g, '').trim();
check(
'生成 CSS 的头注释没有提前闭合(否则第一条规则会被整条丢掉)',
stripped.startsWith("html[data-bg='on'] .bg-"),
`剥掉注释后以「${stripped.slice(0, 40)}…」起头`
);
// 反向对照:判据要真能抓到"注释里带星号+斜杠"这种写法
const bad = "/* 来自 src/**/*.tsx 的用法 */\nhtml[data-bg='on'] .bg-amber-100 { color: red }";
check(
'判据自检:注释里带「星号紧接斜杠」时必须判红',
!bad.replace(/\/\*[\s\S]*?\*\//g, '').trim().startsWith("html[data-bg='on'] .bg-"),
'自检失败 —— 这条判据抓不到它要抓的东西'
);
check('页面底没有被写进半透明清单', [...PAGE_BASE].every(b => !gen.includes(`.${b} {`)));
check('清单不是空跑(至少扫到 10 个类)', used.size >= 10, `实际 ${used.size}`);
@ -239,16 +263,42 @@ check('新组件未使用未映射色族', unmapped.length === 0, unmapped.join(
* 黑字或者自动反色或者描边呢?」):
* ① 不透明实心(最早)
* ② 深色玻璃(我为了"白字对比度"做的 —— 用错误的方式解决对比度,全页唯一一块黑)
* ③ **按主题走的令牌**:浅色=白玻璃+深字深色=深玻璃+亮字
* ③ **按主题走的令牌**:浅色=白玻璃+深字深色主题尚未存在,见下)
* 判据因此不再断言某个固定颜色,而是断言"走令牌"这件事 + 对比度由
* test/manual/nav-contrast-verify.mjs 用 WCAG 比值来量。
*/
check('导航底色走 --nav-bg 令牌(浅色/深色各一套,不写死)',
check('导航底色走 --nav-bg 令牌(不写死)',
/\.nav-rail \{[\s\S]{0,80}background-color: rgb\(var\(--nav-bg\)\)/.test(css) &&
/\.nav-item \{[\s\S]{0,80}color: rgb\(var\(--nav-fg-muted\)\)/.test(css));
check('浅色与深色两套令牌都定义了(自动反色)',
/--nav-bg: 255 255 255 \/ 0\.72/.test(css) && /--nav-bg: 15 23 42 \/ 0\.72/.test(css));
check('导航在壁纸模式下仍参与模糊', /html\[data-bg='on'\] \.nav-rail,[\s\S]{0,200}backdrop-filter: blur\(/.test(css));
/*
* ★ 下面两条 2026-09-14 重写:原先断言「浅色/深色两套 --nav-bg 都定义」与
* 「壁纸模式下 .nav-rail 里有 backdrop-filter」两条编码的都是**已被有意撤掉的
* 设计**,于是 `npm test` 在 HEAD 上恒红。判据红成常态就不再是判据 —— 该改的是
* 判据本身,而**不是**把缺陷写回代码。
*
* ① 深色导航撤掉了(用户「导航栏为什么还是黑色」)。根因不是令牌抄错,而是这个
* 应用**还没有深色主题**`darkMode:'class'` 配着,却没有任何组件写 `dark:`
* 变体 ⇒ 只把导航压深就得到「导航黑、正文白」,比全浅更割裂。现在的契约是
* 导航跟随内容的实际形态(浅色玻璃),所以这里断言的是**「没有深色主题之前,
* .dark 不许单独给导航换色」**。真做深色主题时,这条要连同 dark: 变体一起改。
*
* ② 导航不再自己模糊:模糊只由壁纸层负责(用户「你又犯了模糊叠模糊的毛病……
* 整体的模糊是由壁纸那一层模糊确定的」)。导航浮在壁纸上,背后是**已经模糊过**
* 的壁纸,再 backdrop-filter 一次只会更脏更掉帧。所以断言是反向的:壁纸层有模糊,
* 而壁纸模式下的 .nav-rail 没有。
*/
const darkBodies = [...css.matchAll(/\.dark\s*\{([^}]*)\}/g)].map(m => m[1]);
check(
'没有深色主题之前,.dark 不单独给导航换色',
darkBodies.length >= 1 && darkBodies.every(b => !/--nav-/.test(b)),
'.dark 里出现了 --nav-* 令牌 —— 要让导航变暗,必须同时给组件补 dark: 变体'
);
check(
'模糊只由壁纸层负责(壁纸模式下的导航不再自叠一层)',
/\.app-backdrop \{[\s\S]*?filter: blur\(/.test(css) &&
!/html\[data-bg='on'\] \.nav-rail[^{]*\{[^}]*backdrop-filter/.test(css),
'导航自己 backdrop-filter 会把已经模糊过的壁纸再糊一次'
);
check('内层面板自带圆角(不靠裁剪,否则与滚动冲突)',
/\.comm-pane > \*:not\(\[data-testid='comm-tabs'\]\) \{[\s\S]{0,80}border-radius: var\(--radius-card\)/.test(css));
/*