fix(webui): 候选菜单改用不透明弹层表面 —— 半透明+背景模糊把背后的表单糊进了列表

用户(2026-09-15,看着抄送/转发两处的候选):「你在选择框的模糊逻辑上用力过猛,
导致只能显示一条信息」。

实测(生产构建 4c2bf26,浏览器里读计算样式,壁纸开):
  菜单 background: rgba(255,255,255,0.5) + backdrop-filter: blur(8px) saturate(1.1)
  而它浮在**表单自己**之上(主题输入框 y=294 正落在菜单 284-510 下面)
⇒ 背后的输入框/标签被糊一遍再从半透明列表底下透上来,只有顶行还读得清。

根因与 2026-09-14 那次「模糊叠模糊」同族:把**控件档**(0.5 透 + 8px 模糊)用在
**弹层**上。控件背后是面板自身,透一点好看;弹层背后是别的内容,透就是叠两份内容。

- 新增 .popup-surface(rgb(var(--c-white)),不取 backdrop-filter;壁纸开时单独压回不透明,
  否则会撞上 html[data-bg='on'] .bg-white 那条接管规则)
- .popup-surface 并入「滚动区不淡」豁免(原来只认 .glass-control,换类会漏掉 → 弹层又被洗白)
- AddressInput 菜单 glass-control → popup-surface;旧断言 nav-merge.test.mjs:109 钉的正是
  那个错的意图(『地址建议菜单』必须用控件档),已改钉新意图

判据:narrow-layout 新增 4 条(表面不透明/不取模糊/壁纸开时也压回/菜单穿的类)+
nav-merge 反向对照。两侧都验:变异①换回 glass-control → 红;变异②改回 0.5 → 红;
复原 → 71 通过 0 失败。生产实测:rgba(...,0.5)+blur → rgb(255,255,255)+无模糊。
This commit is contained in:
2026-09-15 07:46:54 +08:00
parent 00df6bea74
commit e6048e9914
5 changed files with 88 additions and 9 deletions

View File

@ -181,7 +181,7 @@ export default function AddressInput({
{open && items.length > 0 && (
<div
className={`absolute z-20 w-full overflow-y-auto glass-control border border-gray-200 rounded-lg shadow-2 ${
className={`absolute z-20 w-full overflow-y-auto popup-surface border border-gray-200 rounded-lg shadow-2 ${
menuLayout.flip ? 'bottom-full mb-1' : 'top-full mt-1'
}`}
style={{ maxHeight: menuLayout.maxHeight }}

View File

@ -754,6 +754,9 @@ html[data-bg='on'] .bg-slate-100 {
*
* 必须显式加在元素上、并且**不要同时写 bg-white**:我的 `html[data-bg='on'] .bg-white`
* 接管规则会把任何 bg-white 拉回正文面那一档(0.88),写在这儿的透度会被它盖掉。
*
* ★ 只给**贴着面板、背后是自己人**的小控件用(按钮、输入框底)。
* 浮在别的内容**之上**的弹层不能用它 —— 见 .popup-surface。
*/
html[data-bg='on'] .glass-control {
background-color: rgb(var(--glass-base) / var(--bg-glass-control));
@ -761,6 +764,34 @@ html[data-bg='on'] .glass-control {
-webkit-backdrop-filter: blur(8px) saturate(1.1);
}
/*
* .popup-surface —— 浮在**别的内容之上**的弹层表面(候选菜单、下拉)。
*
* ★ 2026-09-15 用户(看着抄送/转发两处的候选列表):
* 「你在选择框的模糊逻辑上用力过猛,导致只能显示一条信息」
*
* 实测(壁纸开、生产构建 4c2bf26,在浏览器里读计算样式):
* 菜单 `background-color: rgba(255,255,255,0.5)` + `backdrop-filter: blur(8px)`,
* 而它压着的是**表单自己**(主题输入框、各段标签)⇒ 背后的控件被糊成一片灰、
* 从半透明的列表底下透上来。列表越往下,越分不清哪行是候选、哪行是背后的输入框。
*
* 根因与 2026-09-14 那次「模糊叠模糊」同族:把**控件档**(0.5 透 + 8px 模糊)
* 用在了**弹层**上。控件背后是面板自身,透一点好看;弹层背后是别的内容,
* 透就是把两份内容叠在一起。所以弹层给**不透明**表面,且不取背景模糊。
*/
.popup-surface {
background-color: rgb(var(--c-white));
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* 壁纸开着也**不透明**:弹层不是壁纸的一部分,没有人希望壁纸从候选列表里透出来 */
html[data-bg='on'] .popup-surface {
background-color: rgb(var(--c-white));
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
html[data-bg='on'] .bg-white {
background-color: rgb(var(--glass-base) / var(--bg-glass));
}
@ -1500,7 +1531,8 @@ html[data-bg='on'] .glass-card:hover {
* 已经「有交代」了 —— 那个淡出本来是为了不让长列表的卡片显得被硬切断的。
* 而弹层高度本来就小,淡出只会把首尾项洗白(实测:候选菜单 58px,淡掉 24px)。
*/
.overflow-y-auto.glass-control {
.overflow-y-auto.glass-control,
.overflow-y-auto.popup-surface {
-webkit-mask-image: none;
mask-image: none;
}

View File

@ -111,11 +111,13 @@ function record(name, ok, note) {
await toInput.fill('gui');
await page.waitForTimeout(1200);
}
const menu = await alpha('.glass-control');
const menu = await alpha('.popup-surface');
record(
'⑤ 控件档(地址建议)比正文面更透',
menu !== null && card !== null && menu < card - 0.15,
`正文面 α=${card} 控件 α=${menu}`
'⑤ 弹层(地址建议菜单)不透明、也不取背景模糊',
// 2026-09-15 用户:「你在选择框的模糊逻辑上用力过猛,导致只能显示一条信息」——
// 菜单浮在表单之上,半透明 + 背景模糊会把背后的输入框/标签糊进候选行里。
menu !== null && menu >= 0.99 && card !== null && menu > card,
`正文面 α=${card} 弹层 α=${menu}(弹层必须 ≥0.99)`
);
} finally {
await page.close();

View File

@ -2,7 +2,7 @@
//
// 不做视觉快照:那需要 headless 浏览器,且像素级比对在字体差异下极脆。
// 这里守住几条真正会坏掉的不变量。
import { prose } from './lib/read.mjs';
import { code, prose } from './lib/read.mjs';
import { check, finish } from './lib/checks.mjs';
const read = p => prose(new URL(p, import.meta.url));
@ -205,9 +205,43 @@ check(
);
check(
'弹层(控件档滚动区)不做边缘淡出',
/\.overflow-y-auto\.glass-control\s*\{[\s\S]*?mask-image:\s*none/.test(css),
/\.overflow-y-auto\.(glass-control|popup-surface)\s*\{[\s\S]*?mask-image:\s*none/.test(css),
'候选菜单这类弹层还在淡 —— 它高度只有几十像素,首尾项会被洗白'
);
/*
* 候选/转发两个选择框的菜单必须是**不透明**表面(2026-09-15 用户:
* 「你在选择框的模糊逻辑上用力过猛,导致只能显示一条信息」)。
*
* 判据钉的是**结构**而不是某个具体颜色值:
* ① 弹层类必须存在,且用 rgb(var(--c-white)) 而不是带 alpha 的 rgb(... / α);
* ② 弹层规则里不允许出现 backdrop-filter(背后的表单被模糊正是那份乱);
* ③ 壁纸开着时也得另有规则压回不透明(`html[data-bg='on'] .bg-white` 会抢);
* ④ AddressInput 的菜单必须穿这个类,不许再穿 .glass-control。
*/
const popupRule = (css.match(/\.popup-surface\s*\{[\s\S]*?\n\}/) || [''])[0];
const popupBgOn = (css.match(/html\[data-bg='on'\]\s*\.popup-surface\s*\{[\s\S]*?\n\}/) || [''])[0];
const addrSrc = code('src/components/AddressInput.tsx'); // 判「代码里穿了哪个类」→ 用剥注释版
check(
'弹层表面存在且不透明',
/\.popup-surface\s*\{/.test(css) && /background-color:\s*rgb\(var\(--c-white\)\)/.test(popupRule),
'弹层没有独立的不透明表面 —— 半透明列表叠在表单上,下面的行会被背后控件糊掉'
);
check(
'弹层不取背景模糊',
!/backdrop-filter:\s*blur/.test(popupRule),
'弹层又开了 background blur —— 背后的控件被糊住后和候选行叠在一起'
);
check(
'壁纸开时弹层也不透明',
/background-color:\s*rgb\(var\(--c-white\)\)/.test(popupBgOn) && !/backdrop-filter:\s*blur/.test(popupBgOn),
"`html[data-bg='on'] .bg-white` 会把弹层拉回半透明那档(0.88),必须单独压回去"
);
check(
'候选菜单穿的是弹层类',
/absolute[^`]*popup-surface/.test(addrSrc) && !/absolute[^`]*glass-control/.test(addrSrc),
'AddressInput 的菜单还穿着控件档(.glass-control) —— 那档是给贴着面板的小控件用的'
);
check(
'淡出规则同时带 -webkit- 前缀(Electron 里以哪个为准不由我们定)',
(maskRule.match(/-webkit-mask-image:/g) || []).length === 1 &&

View File

@ -106,7 +106,18 @@ test('③ 复选框与地址建议用"控件档"透度(比正文面更透)',
assert.match(mailView, /import \{[^}]*ComposerChip[^}]*\} from '\.\/Composer'/, 'MailView 要用共用的胶囊组件');
assert.match(mailView, /<ComposerChip/, '授权选项必须是那颗胶囊');
assert.match(chip, /glass-control/, '授权多选胶囊(ComposerChip 未选中态)要用控件档');
assert.match(addr, /glass-control/, '地址建议菜单');
/*
* ★ 2026-09-15 用户(看着抄送/转发两处的候选列表):「你在选择框的模糊逻辑上用力过猛,
* 导致只能显示一条信息」。
*
* 实测(壁纸开、生产构建 4c2bf26):建议菜单是 `rgba(255,255,255,0.5)` +
* `backdrop-filter: blur(8px)`,而它浮在**表单自己**(主题输入框、各段标签)之上。
* 控件档背后是自己的面板,透一点好看;**弹层背后是别的内容**,透就是把两份内容叠在一起。
* 所以这里从「菜单必须用控件档」改成「菜单必须用不透明弹层档」——
* 旧断言正是那个错的意图的书面记录。
*/
assert.match(addr, /popup-surface/, '地址建议菜单要用不透明的弹层档');
assert.ok(!/absolute[^`]*glass-control/.test(addr), '建议菜单不能再穿控件档(那档浮在表单上会糊成一片)');
assert.ok(!/glass-control[^']*bg-white/.test(chip), '胶囊不能再同时写 bg-white');
assert.ok(!/glass-control[^"]*bg-white/.test(addr), '建议菜单不能再同时写 bg-white');
// 三档透度必须真的递减(正文 > 嵌套 > 控件)