跨端: 导航条材质选 (a) 固定档(推翻我的 (b))—— 并修掉"注释说 (a)、代码是 (b)"的自相矛盾
pi 2026-09-15 裁定:**推翻 (b),选 (a)**。我原先给 (b) 的理由不成立,他逐条驳了:
1. **WebUI 的导航条根本不读 `--bg-blur`**:`index.css:1114` 的 `.narrow-nav` 是硬编码
`backdrop-filter: blur(18px) saturate(1.5)`。我引这条支持"两个量不同",
而同一条也说明**它不由用户偏好驱动**。
2. **WebUI 那个滑杆的语义是"背景"**:`BackgroundPicker.tsx:183` —— `label="模糊"`、
`hint="虚化细节,避免背景与正文抢注意力"`、`min=0 max=24`,只作用在
`.app-backdrop{filter:blur(var(--bg-blur))}` 上。
3. WebUI 自己留了**分开的**令牌 `--bg-blur-panel`(`index.css:267`,注释写明
"与壁纸自身的 `--bg-blur` 分开")—— 它的词汇表本身就拒绝把两者等同。
4. **★ 我给 (b) 的理由不成立**:我写"(a) 会让那个滑杆在导航条上变成死控件",
可那个滑杆**已经**被壁纸消费了(`MainPage.ets` 壁纸层的 `.blur(bgPlan.blurPx)`)——
它从来**不是**导航条的控件。(a) 之下它照样是活的。
5. §7.12 的「材质(玻璃)」行原本写的就是固定档 ⇒ (a) 是**回到**已登记契约。
产品向还有一条:**导航条是 chrome,材质应当稳定**,不该因为用户换张壁纸而变厚变薄。
## 最该记的是:我的注释和代码**互相矛盾**
`MainPage.ets` 里那段注释论证的是 (a)、并明确写着"跟随是错的,pi 抓出来了",
而它下面那一行代码是 (b)。**下一个读者会照注释把代码改回去,并引我那句话当权威。**
这是这一路反复在消的形状(说的与做的不一致、而判据看不见),这次落在**注释**上 ——
而注释正是"理由要写清"那条纪律的证据源。已整段重写为真实的 (a) 版本,并把
"(a) 会让滑杆变死控件"这个**错的理由**连同它为什么错一起留在注释里。
## 做掉的东西
- `MainPage.ets`:导航条回到 `.backgroundBlurStyle(Theme.navMaterial)`;
删掉 `NAV_MATERIAL_OF` 表与 `navMaterialFor` 的 import((a) 之下都是孤儿)。
- `model/Appearance.ts`:删 `navMaterialFor`(它存在的唯一理由就是方案 (b))。
`blurStyleFor` 现在**没有任何调用点** —— 如实登记在它的文档注释里
("有测试"不等于"有人用",上一轮我刚因同形状被抓过),不假装它活着。
- **五处"整条字面表达式"断言改成语义断言**(pi §四):`cross-client-theme`、`harmony-appearance`、
`harmony-nav`(3 处)此前都在钉
`/\.backgroundBlurStyle\(NAV_MATERIAL_OF\[navMaterialFor\(…\)\] \?\? Theme\.navMaterial\)/`
—— 字面换字面,正是 `CRITERIA.md` 不许的"对源码形状的匹配"。
现在判:① 那一处的材质**来自 `Theme.navMaterial` 这个系统令牌**;
② **不许跟随** `bg_blur`(NavBar 真代码里不许出现 `blurPx`/`blurStyleFor`);
③ 令牌是 `BlurStyle` 枚举值、不是 `NONE`、且**成员名真实存在于 SDK 枚举**。
- "可达性"那条判据**随契约作废**(它守的是方案 (b)):换成判 (a) 的契约。
**判据随契约走,不随实现走。**
- `harmony-appearance`:原先判"页面里那张表的键必须是 SDK 成员"。表删了,
改成**枚举 `blurStyleFor` 的整个值域**(0..40 + 界外 + NaN),逐个核 SDK 成员 ——
比原来只核表里那三行**更严**。
## 判据自己先错了一次,记下来
新判据第一版**没剥注释**就断言"NavBar 里不许出现 `blurPx`",当场红了 ——
而红的原因不是代码错,是 `NavBar` 的**文档注释**里正好写着
"我一度把档位接过用户偏好(`navMaterialFor(this.bgPlan.blurPx)`)"这句历史说明。
**注释说明禁令 ≠ 违反禁令**;不剥注释,这条判据就会变成"逼人删掉解释",
恰好与本仓库"理由要写清"的纪律相反。改成 `stripComments(bar)` 后再判,并加了一条
"注释里确实留着那处说明"的自检前提。
## 变异体:45 个全部被抓(含 3 个新判据的专属变异)
方案 (b) 落地时配的 13 个变异体**整体作废**(它们锚的代码被删了),标注 `retired`
并写清理由 —— 不是"没跑成"。另有 5 个锚点漂移(我在注释里逐字引用了被锚的那句代码,
污染了通用正则)的**重锚**到真代码;注释里那句逐字引用也一并去掉了
(**注释里逐字抄代码**正是让"按字面锚定"的变异体反复失效的根因)。
新增 3 个针对 (a) 契约的变异体(绕过令牌 / 又跟随 `bg_blur` / 令牌变 `NONE`),全部被抓。
## 未验
- **真机观感仍未验**:三档材质在真机上能不能看出差别、滑杆手感、管理页布局,
只有真机能答。本机模拟器已起(`hdc list targets` 有目标),但
`run-all.mjs` 的**设备闸已经到期**(8 个静态判据的前提成立)⇒ 套件现在会挡在
那道闸上、不打 `RESULT`。**这不是本笔引入的**(前提是环境变了),已单独报给 pi。
- Go 侧 `debt_registry_test.go` 仍未在本机跑(无 Go 模块缓存);pi 已在别处跑过,绿。
This commit is contained in:
@ -190,7 +190,7 @@ test('④ 悬浮 + 让位:自绘浮动层(留白/圆角/系统材质),
|
||||
assert.match(bar, /\.margin\(\{\s*left:\s*NAV_BAR_SIDE,\s*right:\s*NAV_BAR_SIDE,\s*bottom:\s*NAV_BAR_BOTTOM\s*\}\)/,
|
||||
'四周要留白(左右 + 离底),贴边就不是悬浮');
|
||||
assert.match(bar, /\.borderRadius\(NAV_BAR_RADIUS\)/, '要圆角(胶囊)');
|
||||
assert.match(bar, /\.backgroundBlurStyle\(NAV_MATERIAL_OF\[navMaterialFor\(this\.bgPlan\.blurPx\)\] \?\? Theme\.navMaterial\)/,
|
||||
assert.match(bar, /\.backgroundBlurStyle\(Theme\.navMaterial\)/,
|
||||
'材质用系统档次,不手写 alpha(且**不跟随** `bg_blur` —— 理由见本文件末那条"可达性"判据)');
|
||||
/*
|
||||
* 色值检查要读**剥掉注释**的正文 —— 条上的注释正好写着"原来那两个手写玻璃色值",
|
||||
@ -282,7 +282,7 @@ test('★ 玻璃只在两处、且这一处是"背后有可变内容"(GLASS
|
||||
assert.equal(imgBlurs.length, 1,
|
||||
`壁纸层的**图片内容模糊**只许一次(现在 ${imgBlurs.length} 次)—— 同一张底糊两遍 = 更脏更掉帧`);
|
||||
const bar = builderBody(main, 'NavBar() {');
|
||||
assert.match(bar, /backgroundBlurStyle\(NAV_MATERIAL_OF\[navMaterialFor\(this\.bgPlan\.blurPx\)\] \?\? Theme\.navMaterial\)/,
|
||||
assert.match(bar, /backgroundBlurStyle\(Theme\.navMaterial\)/,
|
||||
'悬浮条必须有系统材质(背后是滚动内容),且**经 navMaterialFor 保底**(blur=0 也不许变透明)');
|
||||
// 理由要写在**原文**(注释会被剥掉,而理由就在注释里)
|
||||
const mainRaw = read('pages/MainPage.ets');
|
||||
@ -345,59 +345,83 @@ test('★ ⑤ 变异自检:图标不上色(改造前的写法)必须被判
|
||||
assert.ok(!/fontColor/.test(icon), `旧写法里图标没有 fontColor ⇒ ⑤ 的图标那一半会命中它(切片=${JSON.stringify(icon)})`);
|
||||
});
|
||||
|
||||
/**
|
||||
* ★ 导航条材质**在每一个可达 `bg_blur` 下都存在**(可达性判据,不是源码形状判据)。
|
||||
*
|
||||
* 这条的来历:我一度把导航条的档位**直接**接到用户的 `bg_blur` 上
|
||||
* (`blurStyleFor(this.bgPlan.blurPx)`),而 `blurStyleFor(0)` 是 `'NONE'` ——
|
||||
* 用户把模糊滑杆拖到 0(**要壁纸清晰**)时,**导航条一点材质都没有**。pi 复核抓出来的。
|
||||
*
|
||||
* 为什么原来那几条看不见它:它们钉的是"`Theme.navMaterial` **声明**了、且不是 NONE"
|
||||
* 与"某一行出现了某个表达式" —— 声明是好的,坏的是**那条活的调用路径**。
|
||||
* 判据名替实现作证。
|
||||
*
|
||||
* ── 这条判据自己先错过一次,记在这里 ──
|
||||
* 第一版写成"`blurStyleFor` 在 0..40 上**不许**返回 NONE"。**那是错的**:
|
||||
* `blurStyleFor` 是**通用映射**,"0 px ⇒ 不模糊"是它的**正确语义**,
|
||||
* 该函数必须保留 `'NONE'` 这一档。写"不许 NONE"等于要求"用户把壁纸调清晰时
|
||||
* 还得给壁纸留一点糊"—— 正是用户不要的。所以那条判据**恒红**,是我的判据错,不是代码错。
|
||||
*
|
||||
* 正确的形状是**分层**:
|
||||
* · `blurStyleFor`:通用映射,**允许** NONE(它的边界判据在 `harmony-appearance`);
|
||||
* · `navMaterialFor`:**导航条专用**入口,**有下限**(材质不低于最薄档)——
|
||||
* 因为"导航条是玻璃"是设计不变量,而壁纸可以不模糊。
|
||||
* 这条判据钉的是后者:**在可达输入上不许 NONE**;前者不许被导航条直接用。
|
||||
*/
|
||||
test('★ 导航条材质在**每一个可达的 bg_blur** 下都不为 NONE(可达性判据)', async () => {
|
||||
const { pathToFileURL } = await import('node:url');
|
||||
const A = await import(pathToFileURL(join(HARMONY_ETS, 'model', 'Appearance.ts')).href);
|
||||
assert.equal(typeof A.navMaterialFor, 'function',
|
||||
'要有**导航条专用**入口 `navMaterialFor`(与通用的 blurStyleFor 分开:一个有下限、一个允许 NONE)');
|
||||
|
||||
// 可达输入:滑杆 step=1 + 服务端 clamp 到 0~40 ⇒ 0..40 的每个整数
|
||||
const reachable = [];
|
||||
for (let px = 0; px <= 40; px++) reachable.push(px);
|
||||
// 界外也要挡住(服务端/别的客户端可能送来越界值)
|
||||
reachable.push(-1, -100, 41, 999, NaN);
|
||||
|
||||
const bad = reachable.filter((px) => A.navMaterialFor(px) === 'NONE');
|
||||
assert.deepEqual(bad, [],
|
||||
`★ 这些 bg_blur 取值会让导航条材质变成 NONE(= 没有材质,"玻璃"名存实亡):${bad.join('、')}`);
|
||||
|
||||
/*
|
||||
* 而且它必须**真的跟着走**:不许"恒定返回最薄档"糊弄过去 ——
|
||||
* 那样判据全绿,而用户把滑杆从 0 拉到 40 时导航条**毫无变化**(滑杆又成了死控件)。
|
||||
* 所以钉住两端的**档位**,并要求整段上**至少出现 3 个不同档位**(薄/中/厚都用上)。
|
||||
/**
|
||||
* ★ 导航条材质:**固定系统档**,且**不随 `bg_blur` 变**(契约判据,不是源码形状判据)。
|
||||
*
|
||||
* ── 这条判据换过两次形状,两次都值得记 ──
|
||||
*
|
||||
* ① **不能钉整行字面表达式**。此前它写的是
|
||||
* `/\.backgroundBlurStyle\(NAV_MATERIAL_OF\[navMaterialFor\(\.\.\.\)\]/` 那一整串 ——
|
||||
* 那是"对源码形状的匹配"(`CRITERIA.md` 明令不许退化成这个):换个等价写法就误红,
|
||||
* 而真正的语义("用系统材质、不手写 alpha")它并没在判。
|
||||
* pi 2026-09-15 指出五处都是这个形状 ⇒ 现在改为**语义断言**:
|
||||
* 导航条那一处的材质必须来自 `Theme.navMaterial`(系统枚举令牌)。
|
||||
*
|
||||
* ② **"可达性"那版随方案一起作废**。它曾断言"`navMaterialFor` 在 0..40 的每个整数上
|
||||
* 都不返回 `'NONE'`"—— 那是在给**方案 (b)**(档位跟随 `bg_blur` + 保底下限)把关。
|
||||
* pi 推翻了 (b):导航条是 **chrome**,材质应当稳定,不该因为用户换张壁纸而变厚变薄;
|
||||
* 而滑杆的语义是"**背景**"(`BackgroundPicker.tsx:183` 的 label/hint),
|
||||
* 它**已经**被壁纸层消费(`.blur(this.bgPlan.blurPx)`),从来不是导航条的控件。
|
||||
* ⇒ 方案 (b) 与配套的 `navMaterialFor` 一并删除,"可达性"就**没有对象**了。
|
||||
* **判据随契约走,不随实现走**:所以这里换成判 (a) 的契约。
|
||||
*/
|
||||
assert.equal(A.navMaterialFor(0), 'COMPONENT_THIN', '0 px 时导航条取**最薄档**(不是 NONE)');
|
||||
assert.equal(A.navMaterialFor(40), 'COMPONENT_THICK', '拉满时取最厚档');
|
||||
const tiers = new Set(reachable.map((px) => A.navMaterialFor(px)));
|
||||
assert.equal(tiers.size, 3,
|
||||
`可达输入上应当出现 3 档(薄/中/厚),实际 ${tiers.size} 档:${[...tiers].join('、')} —— ` +
|
||||
'少于 3 档说明滑杆在某一段上是死控件(拉了没反应)');
|
||||
test('★ 导航条材质是**固定系统档**:来自 Theme.navMaterial,且不随 bg_blur 变', () => {
|
||||
const bar = builderBody(main, 'NavBar() {');
|
||||
|
||||
// 导航条那一处必须真的用它(否则上面那条判的是一个没人调的函数)
|
||||
const bar = builderBody(main, 'NavBar() {');
|
||||
assert.match(bar, /\.backgroundBlurStyle\(NAV_MATERIAL_OF\[navMaterialFor\(this\.bgPlan\.blurPx\)\] \?\? Theme\.navMaterial\)/,
|
||||
'导航条的材质要经 `navMaterialFor`(有下限)—— 直接用通用的 `blurStyleFor` 会让 0 px 把玻璃弄没');
|
||||
});
|
||||
// 语义 A:那一处的材质**来自系统令牌**,不是手写色值/alpha("用系统方案"的落点)
|
||||
assert.match(bar, /\.backgroundBlurStyle\(Theme\.navMaterial\)/,
|
||||
'悬浮条必须有系统材质(背后是滚动内容),且来自 `Theme.navMaterial` 这个系统档令牌');
|
||||
|
||||
/*
|
||||
* 语义 B:**不许跟随 `bg_blur`** —— 这是 pi 的裁定,也是本条的核心。
|
||||
* 判法:导航条那一段的**真代码**里不许出现 `blurPx`/`blurStyleFor`
|
||||
* (**协议级**的否定,而不是"没出现某个特定表达式"——后者换个表达式就绕过去了)。
|
||||
*
|
||||
* ★ 必须**剥掉注释**再判(与上面"条上不许手写色值"同一手法):
|
||||
* 本判据第一版没剥,当场红了 —— 而红的原因不是代码错,是 `NavBar` 的
|
||||
* **文档注释**里恰好写着"我一度把档位接过用户偏好
|
||||
* (`navMaterialFor(this.bgPlan.blurPx)`)"这句历史说明。
|
||||
* 注释**说明**禁令 ≠ 违反禁令;不剥注释,这条判据就会变成"逼人删掉解释",
|
||||
* 而那正好与本仓库"理由要写清"的纪律相反。
|
||||
*/
|
||||
const barCode = stripComments(bar);
|
||||
assert.ok(!/blurStyleFor|blurPx/.test(barCode),
|
||||
'★ 导航条材质**不许**由 `bg_blur` 驱动:`blurPx`/`blurStyleFor` 不得出现在 NavBar 的真代码里。\n' +
|
||||
' (导航条是 chrome —— 材质应当稳定;滑杆的语义是"背景",它已经被壁纸层消费了。)');
|
||||
// 自检前提:注释里**确实**留着那处历史说明(否则上面那条"剥注释"就是空跑)
|
||||
assert.ok(/blurPx/.test(bar),
|
||||
'自检前提失效:NavBar 的注释里本应留着一处含 `blurPx` 的历史说明');
|
||||
|
||||
/*
|
||||
* 反面自检:造一个"跟随用户偏好"的样本,确认语义 B **抓得到**。
|
||||
* 没有这一枪,"不出现 blurPx"可能只是因为那段代码里恰好没有别的写法。
|
||||
*/
|
||||
const badSample = 'Row() { Text("x") }.backgroundBlurStyle(MATERIAL[blurStyleFor(this.bgPlan.blurPx)])';
|
||||
assert.ok(/blurStyleFor|blurPx/.test(badSample),
|
||||
'自检失败:跟随 bg_blur 的写法应当被判据抓到(否则语义 B 是个空壳)');
|
||||
// 且合法写法不许被它误伤
|
||||
const goodSample = 'Row() { Text("x") }.backgroundBlurStyle(Theme.navMaterial)';
|
||||
assert.ok(!/blurStyleFor|blurPx/.test(goodSample), '自检失败:合法写法被语义 B 误伤了');
|
||||
|
||||
/*
|
||||
* 语义 C:`Theme.navMaterial` 必须是**系统枚举**里的档位、且**不是 NONE** ——
|
||||
* 否则"固定档"固定到了一个"没有材质"的值上,等于导航条没有玻璃。
|
||||
* (这正是我这轮被抓的另一个形状:令牌有"引用"但那份引用不可达 ⇒ 判据照样绿。)
|
||||
*/
|
||||
const theme = read('common/Theme.ets');
|
||||
const decl = /static readonly navMaterial:\s*BlurStyle\s*=\s*BlurStyle\.(\w+)\s*;/.exec(theme);
|
||||
assert.ok(decl, 'Theme.navMaterial 要声明为 `BlurStyle` 枚举值(具体档位,不是变量)');
|
||||
assert.notEqual(decl[1], 'NONE',
|
||||
'★ `Theme.navMaterial` 不许是 `BlurStyle.NONE` —— 那等于导航条没有材质("玻璃"名存实亡)');
|
||||
|
||||
// 档位名必须**真实存在于 SDK 枚举**(自造名字是"编译不过或不生效",真机上最难查)
|
||||
const commonDts = code(process.env.HARMONY_COMMON_DTS
|
||||
|| '/opt/huawei/command-line-tools/sdk/default/openharmony/ets/component/common.d.ts');
|
||||
const enumBlock = commonDts.slice(commonDts.indexOf('declare enum BlurStyle'));
|
||||
const members = [...enumBlock.slice(0, enumBlock.indexOf('}'))
|
||||
.matchAll(/^\s{2,}([A-Za-z][A-Za-z_0-9]*)\s*[,=]/gm)].map(m => m[1]);
|
||||
assert.ok(members.length > 3, '要从 SDK 里读到 BlurStyle 成员');
|
||||
assert.ok(members.includes(decl[1]),
|
||||
`Theme.navMaterial 用的档位 \`${decl[1]}\` 必须在 SDK 的 BlurStyle 枚举里` +
|
||||
`(成员:${members.join('、')})`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user