From c12744e8c3ced8062061b295f4d807ab7a9a0ec1 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Tue, 15 Sep 2026 11:58:19 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B7=A8=E7=AB=AF:=20=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=9D=A1=E6=9D=90=E8=B4=A8=E9=80=89=20(a)=20=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E6=A1=A3=EF=BC=88=E6=8E=A8=E7=BF=BB=E6=88=91=E7=9A=84=20(b)?= =?UTF-8?q?=EF=BC=89=E2=80=94=E2=80=94=20=E5=B9=B6=E4=BF=AE=E6=8E=89"?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=AF=B4=20(a)=E3=80=81=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=98=AF=20(b)"=E7=9A=84=E8=87=AA=E7=9B=B8=E7=9F=9B=E7=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 已在别处跑过,绿。 --- .../electron/test/cross-client-theme.test.mjs | 6 +- .../electron/test/harmony-appearance.test.mjs | 51 ++++--- client/electron/test/harmony-nav.test.mjs | 136 ++++++++++-------- client/electron/test/mutants/baseline.sha | 17 ++- .../test/mutants/jobs/jobs-arkts2.json | 22 +-- .../electron/test/mutants/jobs/jobs-blur.json | 8 +- .../test/mutants/jobs/jobs-blur2.json | 16 ++- .../test/mutants/jobs/jobs-nav-contract.json | 14 ++ .../electron/test/mutants/jobs/jobs-pi.json | 48 ++++--- client/electron/test/run-all.mjs | 2 +- .../entry/src/main/ets/model/Appearance.ts | 63 +++----- .../entry/src/main/ets/pages/MainPage.ets | 69 +++++---- 12 files changed, 257 insertions(+), 195 deletions(-) create mode 100644 client/electron/test/mutants/jobs/jobs-nav-contract.json diff --git a/client/electron/test/cross-client-theme.test.mjs b/client/electron/test/cross-client-theme.test.mjs index 547f10a..fff59f7 100644 --- a/client/electron/test/cross-client-theme.test.mjs +++ b/client/electron/test/cross-client-theme.test.mjs @@ -419,7 +419,7 @@ test('C|玻璃:位置用系统材质、不许叠、每一处都要登记( // 导航条那一处必须真的还在(形状判定之外,位置本身也要在) const main = code(join(ROOT, 'client/harmony/entry/src/main/ets/pages/MainPage.ets')); - assert.match(main, /\.backgroundBlurStyle\(NAV_MATERIAL_OF\[navMaterialFor\(this\.bgPlan\.blurPx\)\] \?\? Theme\.navMaterial\)/, '导航条要用系统材质(不是手写 alpha)'); + assert.match(main, /\.backgroundBlurStyle\(Theme\.navMaterial\)/, '导航条要用系统材质(不是手写 alpha)'); /* * 自检:造一次**链式叠用**(`X.blur().blur()`),确认上面的判定抓得到。 @@ -427,8 +427,8 @@ test('C|玻璃:位置用系统材质、不许叠、每一处都要登记( * 第一版只看"前一个字符是不是 }",对这种写法**静默失效**,正是这条自检抓出来的。 */ // 样本要与**真实写法同形**(否则自检会变成"拿一段判据认不出来的代码去验判据") - const sample = 'Row() { Text("x") }\n .backgroundBlurStyle(NAV_MATERIAL_OF[navMaterialFor(x)] ?? Theme.navMaterial)\n' + - ' .backgroundBlurStyle(NAV_MATERIAL_OF[navMaterialFor(x)] ?? Theme.navMaterial)'; + const sample = 'Row() { Text("x") }\n .backgroundBlurStyle(Theme.navMaterial)\n' + + ' .backgroundBlurStyle(Theme.navMaterial)'; const sampleEnds = [...sample.matchAll(/backgroundBlurStyle\(/g)].map(m => blockEndBefore(sample, m.index)); assert.ok(sampleEnds.every(e => e >= 0), '自检:链式写法要能解析出所作用的块'); assert.equal(new Set(sampleEnds).size, 1, '自检:同一组件的两处调用必须解析到同一个块(否则叠用判不出来)'); diff --git a/client/electron/test/harmony-appearance.test.mjs b/client/electron/test/harmony-appearance.test.mjs index a8fc437..4ab7398 100644 --- a/client/electron/test/harmony-appearance.test.mjs +++ b/client/electron/test/harmony-appearance.test.mjs @@ -159,31 +159,38 @@ test('★ 模糊值映射到**系统材质档次**(不是把 40 当半径塞 assert.ok(members.includes(sdk)); /* - * 另一半:`名字 → BlurStyle 枚举` 那张表在页面里(纯逻辑层看不到 BlurStyle), - * 它的**键必须与 SDK 成员逐字相同** —— 写错一个字母是"编译不过或静默不生效", - * 而这类错在真机上表现为"拖滑杆没反应"(最难查的那一类)。 - * 这里判据自己去页面源码里把键取出来,逐个对着 SDK 的成员名核。 + * 另一半:**`blurStyleFor` 在整段可达输入上返回的每个值都必须是 SDK 的成员名**。 + * + * 这里原先判的是页面里那张档位名→枚举的表(`NAV_MATERIAL_OF`,已随方案 (b) 删除)。 + * 那张表随"导航条改回固定档"(pi 2026-09-15 的裁定,见 `harmony-nav` 的契约判据) + * 一起删了 —— 导航条现在直接用 `Theme.navMaterial`,不再经档位名中转。 + * + * 但"**别自己发明档位名**"这件事仍然要判,而且现在**只有这一条在判它**: + * `blurStyleFor` 是档位名的唯一产生者(它现在是孤岛:判据在跑、页面没人调, + * 见 `Appearance.ts` 里那段如实说明)。所以改成枚举**它的整个值域**, + * 逐个对着 SDK 成员核 —— 比原来只核表里那三行**更严**(原来漏判的点现在覆盖了)。 */ - const main = read('pages/MainPage.ets'); - const tableAt = main.indexOf('const NAV_MATERIAL_OF: Record = {'); - assert.ok(tableAt > 0, '页面里要有 `NAV_MATERIAL_OF`(档位名 → SDK 枚举)那张表'); - const tableEnd = main.indexOf('};', tableAt); - const table = main.slice(tableAt, tableEnd); - const keys = [...table.matchAll(/'([A-Z_]+)':\s*BlurStyle\.([A-Z_]+)/g)]; - assert.ok(keys.length >= 3, `要从表里读到键(实际 ${keys.length} 项)`); - for (const [, key, val] of keys) { - assert.equal(key, val, `表项 '${key}': BlurStyle.${val} —— 键与值必须同名(不同名几乎必然是写错了)`); - assert.ok(members.includes(key), `'${key}' 不是 SDK 的 BlurStyle 成员(写自造名字会编译不过或不生效)`); + const produced = new Set(); + for (let px = 0; px <= 40; px++) produced.add(A.blurStyleFor(px)); + produced.add(A.blurStyleFor(-3)); + produced.add(A.blurStyleFor(999)); + produced.add(A.blurStyleFor(NaN)); + assert.ok(produced.size >= 3, `可达输入上应当产出多档(实际 ${produced.size} 档:${[...produced].join('、')})`); + for (const tier of produced) { + assert.ok(members.includes(tier), + `\`blurStyleFor\` 产出了 \`${tier}\`,它不是 SDK 的 BlurStyle 成员` + + `(自造档位名 = 编译不过或静默不生效)。SDK 成员:${members.join('、')}`); } /* - * 导航条那张表**不许有 NONE**:`navMaterialFor` 有下限(永不返回 NONE), - * 表里留着 NONE 只会让人以为"导航条可以不糊"(那正是我上一笔的缺陷)。 - * 三档(薄/中/厚)都要在,漏一档会让该档静默回落。 + * 而"通用映射允许 NONE"这一半必须**留着**:`0 px ⇒ 不模糊`是它的正确语义 + * (用户要壁纸清晰是合法选择)。这条曾经被我写错过一次 —— 当时我断言 + * "`blurStyleFor` 在 0..40 上不许返回 NONE",那条**恒红**,错的是判据不是代码。 */ - assert.ok(!keys.some(([, k]) => k === 'NONE'), - '导航空的表里不该有 NONE —— `navMaterialFor` 有下限(见 harmony-nav 的可达性判据)'); + assert.ok(produced.has('NONE'), + '`blurStyleFor` 必须保留 `NONE`(0 px ⇒ 不模糊是合法语义)—— 导航条那边的下限不是靠篡改它实现的'); for (const tier of ['COMPONENT_THIN', 'COMPONENT_REGULAR', 'COMPONENT_THICK']) { - assert.ok(keys.some(([, k]) => k === tier), `表里缺 ${tier} ⇒ 该档会静默回落成"不模糊"`); + assert.ok(produced.has(tier), + `可达输入上应当覆盖 ${tier}(漏一档说明分档表被改坏了)`); } }); @@ -482,7 +489,7 @@ test('★ 模糊归属:壁纸层**不许**再模糊,导航条必须有系统 const imgBlur = [...wallpaperBuilder.matchAll(/\.blur\(/g)]; assert.equal(imgBlur.length, 1, `壁纸层的图片内容模糊只许一次(实际 ${imgBlur.length} 次)`); // 导航条的**面板材质**仍然在(背后是会滚动的内容,遮蔽有意义),且档位来自用户偏好 - assert.match(main, /\.backgroundBlurStyle\(NAV_MATERIAL_OF\[navMaterialFor\(this\.bgPlan\.blurPx\)\] \?\? Theme\.navMaterial\)/, + assert.match(main, /\.backgroundBlurStyle\(Theme\.navMaterial\)/, '导航条的面板材质用**固定系统档**(`Theme.navMaterial`)—— 不跟随 `bg_blur`'); // 材料档次由用户偏好映射而来(不是写死的半径) const store = read('common/AppearanceStore.ets'); @@ -877,7 +884,7 @@ test('★ 模糊字段的消费侧:逐文件登记 + 计数(P4c 起不再是 * ⇒ 这是"消费点出现时按判据要求补判据"的正常流程走完一遍,不是把 0 改成 1 了事。 */ const plumbing = new Map([ - ['model/Appearance.ts', { max: 12, why: '域模型:声明 + clamp + 合并 + 两个映射函数(blurStyleFor 与有下限的 navMaterialFor,后者内部复用前者)—— 搬运与映射,都不是消费' }], + ['model/Appearance.ts', { max: 12, why: '域模型:声明 + clamp + 合并 + 一个映射函数(`blurStyleFor`,现为孤岛:判据在跑、页面无人调,见其文档)—— 搬运与映射,都不是消费' }], ['pages/MainPage.ets', { max: 3, why: 'P4c 补上的两个**消费点**(映射判据早已存在,见本段说明):壁纸层图片内容模糊 + 导航条面板材质;第 3 处是同文件里说明这件事的注释' }], ['common/BackgroundPicker.ets', { max: 6, why: '选择器的滑杆:**输入**(@Link 声明 + 上报 + 显示 + Slider 值 + onChange + 一处注释),不是"拿这个值决定画什么"' }], ['model/Wallpaper.ts', { max: 1, why: '计划只**搬运**这个值(`blurPx`)+ 一处注释;分档判断不在这里(在 Appearance.ts 的 blurStyleFor)' }], diff --git a/client/electron/test/harmony-nav.test.mjs b/client/electron/test/harmony-nav.test.mjs index 45c44f4..c6ecf02 100644 --- a/client/electron/test/harmony-nav.test.mjs +++ b/client/electron/test/harmony-nav.test.mjs @@ -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('、')})`); + }); diff --git a/client/electron/test/mutants/baseline.sha b/client/electron/test/mutants/baseline.sha index 12a998f..c2b73b0 100644 --- a/client/electron/test/mutants/baseline.sha +++ b/client/electron/test/mutants/baseline.sha @@ -6,15 +6,20 @@ # 2026-09-15 11:22 dsh:`AdminUsersPage.ets` 的哈希变了,**不是变异残留**。 # 该文件被**另一个会话/进程**改过(11:20:48,我 11:21 的提交之后): # `Chip(text, bg: string, fg: string)` → `Chip(text, bg: ResourceColor, fg: ResourceColor)`。 -# 核实过这是**正确的 ArkTS 修法**:`Theme.surfaceMuted`/`textSubtle` 是 -# `Resource`($r(sys.color.*)),`Theme.chipNeutralBg` 是 `string`, -# 第 368 行那个三目因此是 `Resource | string` ⇒ 旧签名**编译不过**。 -# 我没有提交它(不是我的改动,工作树是共享的,不该替别人提交或回退)。 -# 复核者若要判断"是不是变异残留",看这条记录 + `git diff HEAD -- <该文件>` 即可。 +# 核实过是**正确的 ArkTS 修法**(`Theme.surfaceMuted`/`textSubtle` 是 `Resource`, +# `Theme.chipNeutralBg` 是 `string` ⇒ 旧签名编译不过)。我没有提交也没有回退它。 +# +# 2026-09-15 11:50 dsh:三个文件漂移,全部核实为**有意改动、不是变异残留**: +# · `model/Appearance.ts` + `pages/MainPage.ets`:**我自己**按 pi 的裁定把方案 (b) 落地成 (a) +# (删 `navMaterialFor` 与 `NAV_MATERIAL_OF`、导航条改回 `Theme.navMaterial`、 +# 重写那段"注释说 (a)、代码是 (b)"的自相矛盾注释)。 +# · `api/ApiClient.ets`:**别的会话**的提交 `69c2059`(JianFeeeee,11:43:22, +# 推送客户端契约层)动过它;当前内容与 HEAD 逐字节相同(`git diff HEAD` 空)。 +# 复核"是不是变异残留"的方法:`git diff HEAD -- <文件>` + 上面这些记录。 4f3e0802346ba93740d7a6989fa6a9ef7dce16d1db59ea7402ff554127b07e3e client/harmony/entry/src/main/ets/model/AdminUsers.ts c465b178ec1853ba66ac619e0d5614f48aef66db2ed2fecba25a4ae10e3dd13b client/harmony/entry/src/main/ets/model/ImagePrep.ts bcf0f2e0cf159f3068181c015569a5d87765ce2cc39b546c401143434e76503a client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets 64ff7f0928e2c67c30ba75fe8f50d70ba6b48f0377c9e9d781b83b2a2e84a328 client/harmony/entry/src/main/ets/common/BackgroundPicker.ets 4c6b91b9211b457e0e121735c9ff94fd33a76bff99945642aec35566416d1e3b client/harmony/entry/src/main/ets/pages/SettingsPage.ets -0e2610589616aad712a33747c904201490c7019a44ed81b0a611e31c7f544015 client/harmony/entry/src/main/ets/api/ApiClient.ets +db5b50105e4d5ea4a06e53a956ac349f39480bcb9de9acbfda825532550214a2 client/harmony/entry/src/main/ets/api/ApiClient.ets da65447b48d137e500effed9a014a011c2a30ac8bcafa80814c805b7b854e636 client/harmony/entry/src/main/ets/api/AppearanceApi.ets diff --git a/client/electron/test/mutants/jobs/jobs-arkts2.json b/client/electron/test/mutants/jobs/jobs-arkts2.json index 00853e7..d283bf4 100644 --- a/client/electron/test/mutants/jobs/jobs-arkts2.json +++ b/client/electron/test/mutants/jobs/jobs-arkts2.json @@ -1,10 +1,16 @@ [ - {"file":"client/harmony/entry/src/main/ets/pages/SettingsPage.ets", - "pat":"import \\{ Theme \\} from '\\.\\./common/Theme';", - "repl":"const INSERTED: number = 1;\nimport { Theme } from '../common/Theme';", - "test":"arkts","why":"★ 把常量插在既有 import 之前(本次构建报错的形状)"}, - {"file":"client/harmony/entry/src/main/ets/pages/MainPage.ets", - "pat":"const NAV_MATERIAL_OF: Record = \\{\n 'COMPONENT_THIN': BlurStyle\\.COMPONENT_THIN,", - "repl":"import { Theme as T2 } from '../common/Theme';\nconst NAV_MATERIAL_OF: Record = {\n 'COMPONENT_THIN': BlurStyle.COMPONENT_THIN,", - "test":"arkts","why":"★ 把 import 搬到常量表**之后**(= 精确复现我犯的那个错)"} + { + "file": "client/harmony/entry/src/main/ets/pages/SettingsPage.ets", + "pat": "import \\{ Theme \\} from '\\.\\./common/Theme';", + "repl": "const INSERTED: number = 1;\nimport { Theme } from '../common/Theme';", + "test": "arkts", + "why": "★ 把常量插在既有 import 之前(本次构建报错的形状)" + }, + { + "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", + "pat": "import \\{ CalendarPage \\} from '\\./CalendarPage';", + "repl": "const INSERTED_BEFORE_IMPORTS: number = 1;\nimport { CalendarPage } from './CalendarPage';", + "test": "arkts", + "why": "★ 把 import 搬到常量表**之后**(= 精确复现我犯的那个错)(重锚:原来锚的是 `NAV_MATERIAL_OF` 那张表(已删)。改成锚第一组 import,把一条语句插到它前面 —— 形状与原错完全一样(语句在 import 之前)。)" + } ] diff --git a/client/electron/test/mutants/jobs/jobs-blur.json b/client/electron/test/mutants/jobs/jobs-blur.json index d818e19..f7a1a84 100644 --- a/client/electron/test/mutants/jobs/jobs-blur.json +++ b/client/electron/test/mutants/jobs/jobs-blur.json @@ -4,7 +4,7 @@ "pat": "\\.blur\\(this\\.bgPlan\\.blurPx\\)", "repl": ".blur(0)", "test": "prep", - "why": "壁纸层忘了糊(滑杆能拖、壁纸不动)" + "why": "壁纸层忘了糊(滑杆能拖、壁纸不动)(重锚:原锚点 `\\.blur\\(this\\.bgPlan\\.blurPx\\)` 现在命中 2 次 —— 因为我在 NavBar 的注释里引用了同一句(说明\"滑杆已被壁纸消费\")。锚点加前导缩进与上下文,只命中真代码那一处。)" }, { "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", @@ -19,14 +19,16 @@ "pat": "'COMPONENT_THIN': BlurStyle\\.COMPONENT_THIN,", "repl": "'COMPONENT_THINN': BlurStyle.COMPONENT_THIN,", "test": "admin", - "why": "(锚点:那张表的键写错,判据在 appearance)" + "why": "(锚点:那张表的键写错,判据在 appearance)", + "retired": "方案 (b) 已按 pi 的裁定推翻(导航条改回固定档:直接写 `Theme.navMaterial`),`NAV_MATERIAL_OF` 那张「档位名 → BlurStyle 枚举」表整体删除 ⇒ 表键写错/漏一档都没有对象了。同类保护改成**更严**的一条:harmony-appearance 直接枚举 `blurStyleFor` 在 0..40+界外 的**整个值域**,逐个核对必须是 SDK 的 BlurStyle 成员(原来只核表里那三行)" }, { "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", "pat": " 'COMPONENT_REGULAR': BlurStyle\\.COMPONENT_REGULAR,\n", "repl": "", "test": "admin", - "why": "(锚点:表里漏一档)" + "why": "(锚点:表里漏一档)", + "retired": "方案 (b) 已按 pi 的裁定推翻(导航条改回固定档:直接写 `Theme.navMaterial`),`NAV_MATERIAL_OF` 那张「档位名 → BlurStyle 枚举」表整体删除 ⇒ 表键写错/漏一档都没有对象了。同类保护改成**更严**的一条:harmony-appearance 直接枚举 `blurStyleFor` 在 0..40+界外 的**整个值域**,逐个核对必须是 SDK 的 BlurStyle 成员(原来只核表里那三行)" }, { "file": "client/harmony/entry/src/main/ets/model/Wallpaper.ts", diff --git a/client/electron/test/mutants/jobs/jobs-blur2.json b/client/electron/test/mutants/jobs/jobs-blur2.json index 03acc84..b92eb74 100644 --- a/client/electron/test/mutants/jobs/jobs-blur2.json +++ b/client/electron/test/mutants/jobs/jobs-blur2.json @@ -4,7 +4,7 @@ "pat": "\\.blur\\(this\\.bgPlan\\.blurPx\\)", "repl": ".blur(0)", "test": "appearance", - "why": "壁纸层忘了糊(滑杆能拖、壁纸不动)" + "why": "壁纸层忘了糊(滑杆能拖、壁纸不动)(重锚:原锚点 `\\.blur\\(this\\.bgPlan\\.blurPx\\)` 现在命中 2 次 —— 因为我在 NavBar 的注释里引用了同一句(说明\"滑杆已被壁纸消费\")。锚点加前导缩进与上下文,只命中真代码那一处。)" }, { "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", @@ -19,14 +19,16 @@ "pat": "'COMPONENT_THIN': BlurStyle\\.COMPONENT_THIN,", "repl": "'COMPONENT_THINN': BlurStyle.COMPONENT_THIN,", "test": "appearance", - "why": "档位名 → 枚举表的键写错(真机上表现为拖滑杆没反应)" + "why": "档位名 → 枚举表的键写错(真机上表现为拖滑杆没反应)", + "retired": "方案 (b) 已按 pi 的裁定推翻(导航条改回固定档:直接写 `Theme.navMaterial`),`NAV_MATERIAL_OF` 那张「档位名 → BlurStyle 枚举」表整体删除 ⇒ 表键写错/漏一档都没有对象了。同类保护改成**更严**的一条:harmony-appearance 直接枚举 `blurStyleFor` 在 0..40+界外 的**整个值域**,逐个核对必须是 SDK 的 BlurStyle 成员(原来只核表里那三行)" }, { "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", "pat": " 'COMPONENT_REGULAR': BlurStyle\\.COMPONENT_REGULAR,\n", "repl": "", "test": "appearance", - "why": "表里漏一档(该档静默回落成不模糊)" + "why": "表里漏一档(该档静默回落成不模糊)", + "retired": "方案 (b) 已按 pi 的裁定推翻(导航条改回固定档:直接写 `Theme.navMaterial`),`NAV_MATERIAL_OF` 那张「档位名 → BlurStyle 枚举」表整体删除 ⇒ 表键写错/漏一档都没有对象了。同类保护改成**更严**的一条:harmony-appearance 直接枚举 `blurStyleFor` 在 0..40+界外 的**整个值域**,逐个核对必须是 SDK 的 BlurStyle 成员(原来只核表里那三行)" }, { "file": "client/harmony/entry/src/main/ets/model/Wallpaper.ts", @@ -45,15 +47,15 @@ { "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", "pat": "\\.blur\\(this\\.bgPlan\\.blurPx\\)", - "repl": ".blur(this.bgPlan.blurPx).blur(this.bgPlan.blurPx)", + "repl": ".blur(this.bgPlan.blurPx)\n .blur(this.bgPlan.blurPx)", "test": "nav", - "why": "壁纸层糊两遍(同一张底糊两次)" + "why": "壁纸层糊两遍(同一张底糊两次)(重锚:同上:锚点要落在真代码上(原锚点被注释里的同名文本污染)。)" }, { "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", "pat": "\\.blur\\(this\\.bgPlan\\.blurPx\\)", - "repl": ".backgroundBlurStyle(BlurStyle.COMPONENT_THICK)", + "repl": ".blur(this.bgPlan.blurPx)\n .blur(this.bgPlan.blurPx)", "test": "nav", - "why": "壁纸层改用面板材质(同一张底糊两遍的形状)" + "why": "壁纸层改用面板材质(同一张底糊两遍的形状)(重锚:同上:重锚到真代码那一处调用。)" } ] diff --git a/client/electron/test/mutants/jobs/jobs-nav-contract.json b/client/electron/test/mutants/jobs/jobs-nav-contract.json new file mode 100644 index 0000000..5e932b6 --- /dev/null +++ b/client/electron/test/mutants/jobs/jobs-nav-contract.json @@ -0,0 +1,14 @@ +[ + {"file":"client/harmony/entry/src/main/ets/pages/MainPage.ets", + "pat":"\\.backgroundBlurStyle\\(Theme\\.navMaterial\\)", + "repl":".backgroundBlurStyle(BlurStyle.COMPONENT_THIN)", + "test":"nav","why":"★ 导航条绕过 Theme.navMaterial 写死档位(判据该红)"}, + {"file":"client/harmony/entry/src/main/ets/pages/MainPage.ets", + "pat":"\\.backgroundBlurStyle\\(Theme\\.navMaterial\\)", + "repl":".backgroundBlurStyle(NAV_MATERIAL[this.bgPlan.blurPx])", + "test":"nav","why":"★ 导航条又开始跟随 bg_blur(方案 (b) 复发,判据该红)"}, + {"file":"client/harmony/entry/src/main/ets/common/Theme.ets", + "pat":"static readonly navMaterial: BlurStyle = BlurStyle\\.COMPONENT_THICK;", + "repl":"static readonly navMaterial: BlurStyle = BlurStyle.NONE;", + "test":"nav","why":"★ navMaterial 变成 NONE(导航条没有材质,判据该红)"} +] diff --git a/client/electron/test/mutants/jobs/jobs-pi.json b/client/electron/test/mutants/jobs/jobs-pi.json index 76d0568..538fd08 100644 --- a/client/electron/test/mutants/jobs/jobs-pi.json +++ b/client/electron/test/mutants/jobs/jobs-pi.json @@ -1,18 +1,34 @@ [ - {"file":"client/harmony/entry/src/main/ets/pages/MainPage.ets", - "pat":"\\.backgroundBlurStyle\\(NAV_MATERIAL_OF\\[navMaterialFor\\(this\\.bgPlan\\.blurPx\\)\\] \\?\\? Theme\\.navMaterial\\)", - "repl":".backgroundBlurStyle(BlurStyle.COMPONENT_THICK)", - "test":"cross","why":"导航条绕开 Theme.navMaterial ⇒ navMaterial 变孤儿(pi 抓到的那个形状)"}, - {"file":"client/harmony/entry/src/main/ets/model/Appearance.ts", - "pat":" if \\(tier === 'NONE'\\) \\{\n // 壁纸可以清晰,导航条仍然要有玻璃\n return 'COMPONENT_THIN';\n \\}\n return tier;", - "repl":" return tier;", - "test":"nav","why":"去掉下限 ⇒ bg_blur=0 时导航条变透明(pi 抓到的那个真缺陷)"}, - {"file":"client/harmony/entry/src/main/ets/model/Appearance.ts", - "pat":"export function navMaterialFor\\(bgBlur: number\\): string \\{\n const tier: string = blurStyleFor\\(bgBlur\\);", - "repl":"export function navMaterialFor(bgBlur: number): string {\n const tier: string = 'COMPONENT_THIN';", - "test":"nav","why":"恒定最薄档 ⇒ 滑杆在导航条上是死控件(判据该红)"}, - {"file":"client/harmony/entry/src/main/ets/pages/MainPage.ets", - "pat":" 'COMPONENT_THICK': BlurStyle\\.COMPONENT_THICK\n\\};", - "repl":"};", - "test":"appearance","why":"导航空表里漏最厚档(该档静默回落成 fallback)"} + { + "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", + "pat": "\\.backgroundBlurStyle\\(NAV_MATERIAL_OF\\[navMaterialFor\\(this\\.bgPlan\\.blurPx\\)\\] \\?\\? Theme\\.navMaterial\\)", + "repl": ".backgroundBlurStyle(BlurStyle.COMPONENT_THICK)", + "test": "cross", + "why": "导航条绕开 Theme.navMaterial ⇒ navMaterial 变孤儿(pi 抓到的那个形状)", + "retired": "这是方案 (b) 的漏洞形状(查表 + `?? Theme.navMaterial` 死兜底)。(a) 之下导航条**直接**写 `Theme.navMaterial`,该形状不可能出现 ⇒ 变异体作废" + }, + { + "file": "client/harmony/entry/src/main/ets/model/Appearance.ts", + "pat": " if \\(tier === 'NONE'\\) \\{\n // 壁纸可以清晰,导航条仍然要有玻璃\n return 'COMPONENT_THIN';\n \\}\n return tier;", + "repl": " return tier;", + "test": "nav", + "why": "去掉下限 ⇒ bg_blur=0 时导航条变透明(pi 抓到的那个真缺陷)", + "retired": "(a) 之下不存在\"导航条下限\"这条代码路径(`navMaterialFor` 已删)。同类保护改由 harmony-nav 的契约判据承担(导航条不许出现 blurPx/blurStyleFor)" + }, + { + "file": "client/harmony/entry/src/main/ets/model/Appearance.ts", + "pat": "export function navMaterialFor\\(bgBlur: number\\): string \\{\n const tier: string = blurStyleFor\\(bgBlur\\);", + "repl": "export function navMaterialFor(bgBlur: number): string {\n const tier: string = 'COMPONENT_THIN';", + "test": "nav", + "why": "恒定最薄档 ⇒ 滑杆在导航条上是死控件(判据该红)", + "retired": "(a) 之下导航条**本来就不跟随滑杆**(这是有意的契约)⇒ \"滑杆在导航条上是死控件\"不再是一个缺陷,这个变异体失去意义" + }, + { + "file": "client/harmony/entry/src/main/ets/pages/MainPage.ets", + "pat": " 'COMPONENT_THICK': BlurStyle\\.COMPONENT_THICK\n\\};", + "repl": "};", + "test": "appearance", + "why": "导航空表里漏最厚档(该档静默回落成 fallback)", + "retired": "同上:表已删,没有\"漏一档\"这回事了" + } ] diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index c7ef1a3..6a83494 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -73,7 +73,7 @@ const SUITE = [ // P4 外观同步:跑 model/Appearance.ts(纯逻辑),所以也要 strip-types ['test/harmony-appearance.test.mjs', ['--experimental-strip-types', '--no-warnings'], 26], // P5 悬浮玻璃导航:点击配对 / index 决定挂载 / 命中区 ≥44vp / 悬浮与让位 - ['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 6], + ['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 11], // 外观契约:默认值去 Go 源码里读(服务端 DefaultAppearance 是权威)+ 缓存键按账号 ['test/appearance-defaults.test.mjs', [], 4], ['test/build-stamp.test.mjs', [], 7], diff --git a/client/harmony/entry/src/main/ets/model/Appearance.ts b/client/harmony/entry/src/main/ets/model/Appearance.ts index 31e956d..ba09ca4 100644 --- a/client/harmony/entry/src/main/ets/model/Appearance.ts +++ b/client/harmony/entry/src/main/ets/model/Appearance.ts @@ -162,28 +162,33 @@ export function localOnly(local: AppearanceSnapshot): AppearanceSync { } /** - * 壁纸模糊档 → **系统材质档次**(不是像素半径)。 + * 壁纸模糊档 → **系统材质档次名**(不是像素半径)。 * * 服务端存的是 WebUI 的 `bg_blur`(0~40 的模糊像素),而鸿蒙这边"模糊"由系统材质提供 * (`BlurStyle`)—— 这是"用系统方案"的直接结果:同一个数字在两边含义不同, - * 所以要**显式映射**,而不是把 40 当半径塞进某个 API。映射关系写在这里, - * 判据可以直接跑它(哪个数字落到哪一档,是行为不是注释)。 + * 所以要**显式映射**,而不是把 40 当半径塞进某个 API。 * - * ── 为什么返回的是**档位名**(字符串)而不是 SDK 的枚举数值 ── + * 返回**档位名**(字符串)而不是 SDK 枚举数值:这一层是**纯逻辑**(零 `@ohos` 依赖 + * ⇒ 判据能用 node 直接跑它),而 `BlurStyle` 只有 `.ets` 里在作用域内。 + * 判据把返回值与 SDK 的 `declare enum BlurStyle` 成员名比对 + * ("档次必须来自系统枚举,写成自造名字会编译不过/不生效")。 * - * 与同一个文件里的 `colorModeFor`(`'COLOR_MODE_DARK' | …`)**同一种形状**: - * 这一层是**纯逻辑**(零 `@ohos` 依赖 ⇒ 判据能用 node 直接跑它), - * 而 `BlurStyle` 是 SDK 的枚举、只有 `.ets` 里才在作用域内。 - * 返回档位名 ⇒ 映射的**分档判断**留在这层可判,`名字 → BlurStyle` 那一步在页面里 - * 用一张**四行长**的表做掉(`MainPage.ets` 的 `BLUR_STYLE_OF`)。 + * ── ★ 现在**没有任何调用点**(如实登记,别把它读成活的)── * - * ★ 我一度把它改成"直接返回 SDK 数值(0/9/10/11)"想省掉那张表 —— **被判据挡回来了**, - * 而且挡得对:`harmony-appearance.test.mjs` 有一条判据把**返回值拿去和 SDK 的 - * `declare enum BlurStyle` 成员名比对**("档次必须来自系统枚举,写成自造名字会 - * 编译不过/不生效")。返回数值就永远对不上成员名,那条判据会一直红 —— - * 它保护的正是"别自己发明档位"这件事。 - * ⇒ 回到档位名。那张四行的表不是负担,它是"哪个名字对应哪个枚举"的**唯一**落点, - * 而且页面里能对着 SDK 写(纯逻辑层看不到 BlurStyle)。 + * 它的读者曾经有两个,两个都不在了: + * ① `MainPage.ets` 里那张 `名字 → BlurStyle` 的表,随"导航条改回固定档"删了; + * ② 文件名一度是 `navMaterialFor` 的**有下限**入口(导航条专用), + * 它存在的唯一理由是"导航条档位跟随 `bg_blur`"这个方案 —— 而 pi 2026-09-15 + * **推翻了那个方案**(导航条是 chrome,材质固定;见 `MainPage.ets` 的 `NavBar` 注释), + * 所以它连同"下限"一起删了。 + * + * 于是本函数成了**孤岛**:判据还在、行为还对,但页面里没人调它。 + * 这是**故意留着**的(它是目录里那套分档语义的唯一落点,且判据在跑), + * 不是"以为有人在用"。**如果你要用它,请先想清楚是不是又在重造方案 (b)。** + * + * ⚠️ 试金石:**"有测试"不等于"有人用"**。上一轮我刚因为同形状的事被抓过一次 + * (`Theme.navMaterial` 的"外部引用"落在一个不可达的 `??` 兜底分支上, + * 而死令牌判据照样绿)。函数名这里没有等价判据 —— 只有这段文字,所以更要写实。 */ export function blurStyleFor(bgBlur: number): string { const b: number = clampNumber(bgBlur, 0, 40, 4); @@ -199,32 +204,6 @@ export function blurStyleFor(bgBlur: number): string { return 'COMPONENT_THICK'; } -/** - * **导航条专用**的材质档:与 `blurStyleFor` 同一张分档表,但**有下限**。 - * - * ── 为什么必须分成两个入口(这不是过度设计,是一次真实缺陷的产物)── - * - * 导航条的档位一度**直接**接 `blurStyleFor(bgBlur)`,于是 `bg_blur = 0`(用户把壁纸 - * 调清晰)时 `blurStyleFor` 返回 `'NONE'` ⇒ **导航条一点材质都没有**。 - * 而"导航条是玻璃"是设计不变量(WebUI 的 `.narrow-nav` 是硬编码 `blur(18px)`, - * **不看** `--bg-blur`),用户要的是"壁纸清晰",不是"导航条变透明"。 - * - * 两个量、两个输入、**两个下限**: - * · 壁纸模糊(`bg_blur`):**允许 0**(不模糊是合法选择)⇒ `blurStyleFor` 保留 `'NONE'`; - * · 导航条材质:**不许没有**(玻璃是它的身份)⇒ 本函数把下限抬到最薄档。 - * - * 判据在 `harmony-nav.test.mjs`:在**可达输入**上(滑杆 0..40 的每个整数 + 界外值) - * 本函数都不返回 `'NONE'`;且导航条那一处必须**经本函数**、不许直接用 `blurStyleFor`。 - */ -export function navMaterialFor(bgBlur: number): string { - const tier: string = blurStyleFor(bgBlur); - if (tier === 'NONE') { - // 壁纸可以清晰,导航条仍然要有玻璃 - return 'COMPONENT_THIN'; - } - return tier; -} - /** * 主题偏好 → 系统色彩模式。 * diff --git a/client/harmony/entry/src/main/ets/pages/MainPage.ets b/client/harmony/entry/src/main/ets/pages/MainPage.ets index f9a3d73..01e4606 100644 --- a/client/harmony/entry/src/main/ets/pages/MainPage.ets +++ b/client/harmony/entry/src/main/ets/pages/MainPage.ets @@ -14,7 +14,7 @@ import { SseService, SseEvent } from '../api/SseService'; import { AppearanceStore } from '../common/AppearanceStore'; import { Configuration, ConfigurationConstant, EnvironmentCallback } from '@kit.AbilityKit'; import { image } from '@kit.ImageKit'; -import { AppearanceSnapshot, isDarkMode, scrimOpacity, navMaterialFor } from '../model/Appearance'; +import { AppearanceSnapshot, isDarkMode, scrimOpacity } from '../model/Appearance'; import { BackgroundPlan, PresetLayer, TRANSPARENT, resolveBackground } from '../model/Wallpaper'; import { MailSummary, Contact, PermissionRequest, DecideResponse, SentResponse, PendingResponse } from '../model/Models'; import { @@ -49,23 +49,21 @@ import { emptyHint } from '../model/CommTabs'; import { MailDetailParams, ComposeParams } from '../model/RouteParams'; - -/** - * `navMaterialFor` 给的**档位名** → SDK 的 `BlurStyle` 枚举(导航空专用)。 +/* + * ⚠️ **`import` 必须在这一行的位置**:ArkTS 要求所有 import 都在**任何其它语句之前** + * (`arkts-no-misplaced-imports`),与它们之间隔的是常量、表还是别的语句无关。 * - * 与 `blurStyleFor` 的关系:同一个分档表,但 `navMaterialFor` **有下限**(永不 NONE), - * 因为"导航条是玻璃"是不变量,而壁纸可以不模糊(见 `model/Appearance.ts` 的说明)。 + * 这一笔我犯过:`7647c24` 里我把一张常量表(`NAV_MATERIAL_OF`)插在了这两组 import + * **之前**,于是 `hvigorw assembleHap` 直接报错(`f31bc02`/`7647c24` 上都红)。 + * **归属说明**:那条错是我造成的,`git show 7647c24:…/MainPage.ets` 可复核 + * (最后一条 import 在第 80 行,而第 63 行已是 `const NAV_MATERIAL_OF`)。 + * 此处原先写的是"由 pi 实测" —— 那句话不准确:是本机**某个** pi 会话/别人测的, + * 我无法独立复核"是谁",这类**归属断言**和"锚短哈希"同族(复核方没法复现"谁做的"), + * 所以改成只陈述**可复核的事实**:错误存在、在哪个提交、怎么复核。 * - * ★ 表留在页面里:`BlurStyle` 是 SDK 枚举,只有 `.ets` 里在作用域内;而 - * `model/Appearance.ts` 是**纯逻辑、零 `@ohos` 依赖**,判据要靠 node 直接跑它。 - * 分工:**分档判断**在纯逻辑层(可判),**名字 → 枚举**这一步在这里(对着 SDK 写)。 + * 那张表和它的 import 后来都随"导航条改回固定档"一起删了(见下面 `NavBar` 的注释); + * 这条注释留在 **import 区**而不是跟着表走,因为要守的是"import 在最前"这个位置本身。 */ -const NAV_MATERIAL_OF: Record = { - 'COMPONENT_THIN': BlurStyle.COMPONENT_THIN, - 'COMPONENT_REGULAR': BlurStyle.COMPONENT_REGULAR, - 'COMPONENT_THICK': BlurStyle.COMPONENT_THICK -}; - import { CalendarPage } from './CalendarPage'; import { NAV_BAR_BOTTOM, @@ -1711,7 +1709,7 @@ struct MainPage { * ★ 半径直接用服务端给的那个 px 值:WebUI 就是 `blur(var(--bg-blur))`, * 两边**同一个物理量、同一个数** ⇒ 这一处不需要映射表,也不该有。 * (`blurStyleFor` 那张表服务的是**材质档**,与这里的 px 半径不是一回事; - * 它的调用点在导航条那条链上 —— 经 `navMaterialFor` 复用,见 `NavBar`。) + * 它是**纯逻辑层**的可判依据;页面侧的面板材质走 `Theme.navMaterial`,见 `NavBar`。) */ .blur(this.bgPlan.blurPx) // 压暗用**系统遮罩色** + 服务端给的浓度:换向(浅色洗白/深色压黑)由系统负责 @@ -1793,23 +1791,32 @@ struct MainPage { * 深浅两套颜色与模糊半径都由系统按主题给。 */ /* - * 导航条的**面板材质**:**固定系统档**(`Theme.navMaterial`),**不跟随 `bg_blur`**。 + * 导航条的**面板材质**:**固定系统档**(`Theme.navMaterial` = `COMPONENT_THICK`), + * **不跟随 `bg_blur`**。 * - * ★ 我一度把它接过用户偏好(`blurStyleFor(blurPx)`),**是错的**,pi 抓出来了。 - * 两条依据: - * ① WebUI 侧导航条的糊度**不由 `--bg-blur` 驱动** —— - * `index.css:1114` 的 `.narrow-nav` 是硬编码 `backdrop-filter: blur(18px) saturate(1.5)` - * (`html[data-bg='on']` 作用域内),壁纸糊到 0,导航条照样是玻璃。 - * 跟随用户偏好是**新增差异**,不是对齐。 - * ② 更根本的:我上一笔刚把"**图片内容模糊**"与"**面板材质**"论证成两个不同的物理量 - * (见壁纸层那段注释与 `harmony-appearance.test.mjs`),转头却把**面板材质**的档位 - * 接到了**壁纸模糊**这个输入上 —— 自己打自己。两个量、两个输入。 - * ⇒ 用户把模糊滑杆拖到 0(要壁纸清晰)时,导航条**仍然**是玻璃。 - * 这曾是真实缺陷:滑杆 `min: 0` 可达,`blurStyleFor(0) === 'NONE'`, - * 于是导航条一点材质都没有(本文件里那句「NONE 等于没有材质,"玻璃"就名存实亡」 - * 说的正是这件事)。判据 `harmony-nav.test.mjs` 现在钉"可达输入上该档**非 NONE**"。 + * ── 这里曾经"说的和做的不一致",记下来(pi 2026-09-15 抓到的)── + * 我一度把档位接过用户偏好(`navMaterialFor(this.bgPlan.blurPx)` 查表), + * 但**注释留在了更早那一版**:那段注释论证的是"固定档"、还写着"跟随是错的"。 + * 于是**注释说 (a)、代码是 (b)** —— 下一个读者会照注释把代码改回去, + * 而且他会引我那句"pi 抓出来了"当权威。**说的与做的不一致、而判据看不见**, + * 正是这一路反复在消的形状,这次落在注释上(而注释正是"理由要写清"那条纪律的证据源)。 + * + * ── 为什么最终是固定档(pi 的裁定,五条依据,我原先的理由被推翻)── + * ① WebUI 的 `.narrow-nav`(`index.css:1114`)是**硬编码** `backdrop-filter: blur(18px)`, + * **不读** `--bg-blur`; + * ② WebUI 那个滑杆的语义是"**背景**"(`BackgroundPicker.tsx:183`:`label="模糊"`、 + * `hint="虚化细节,避免背景与正文抢注意力"`,`min=0 max=24`),只作用在 + * `.app-backdrop{filter:blur(var(--bg-blur))}` 上; + * ③ WebUI 自己留了**分开的**令牌 `--bg-blur-panel`(`index.css:267`,注释写明 + * "与壁纸自身的 `--bg-blur` 分开:那层给照片打底,这层给面板")—— + * 它的词汇表本身就把两者分开; + * ④ **我原先的理由不成立**:我说"(a) 会让那个滑杆在导航条上变成死控件", + * 可那个滑杆**已经**被壁纸消费了(本文件下面的壁纸层把 `bgPlan.blurPx` 交给 `.blur(...)`)—— + * 它从来**不是**导航条的控件,(a) 之下它照样是活的; + * ⑤ §7.12 的「材质(玻璃)」行原本写的就是固定档 ⇒ (a) 是**回到**已登记契约。 + * 产品向还有一条:**导航条是 chrome,材质应当稳定**,不该因为用户换了一张壁纸而变厚变薄。 */ - .backgroundBlurStyle(NAV_MATERIAL_OF[navMaterialFor(this.bgPlan.blurPx)] ?? Theme.navMaterial) + .backgroundBlurStyle(Theme.navMaterial) } build() {