跨端: 顶部页签条改悬浮玻璃 + 深色压暗下限(修 8 处深色可读性)+ 手势判据自搭现场

用户:「顶部的收件箱发件箱和授权为什么不做成悬浮玻璃?」
问得对,而且它当时是**整页唯一一块实心白**。

★ 顶部页签条 → 悬浮玻璃
  `CommTabBar` 原来写的是 `backgroundColor(Theme.surface)`(系统卡片色=实体)。
  上一轮把列表容器、卡片、底部导航条都玻璃化了,**唯独漏了这条** ——
  它既不是卡片也不是容器,是"条状 chrome",逐处修时最容易漏的那一类。
  ⇒ 现在与底部浮动条**同一族**:圆角 + 左右留白 + `Theme.navMaterial`。
  ★ 几何常量复用底部条的(`TAB_BAR_RADIUS = NAV_BAR_RADIUS`、
    `TAB_BAR_SIDE = NAV_BAR_SIDE`),不各写一份 —— 两个数一旦分叉,
    "同一条轴线上的两种条"就不齐了。为什么选悬浮而不是 WebUI 的"通栏无底色"
    (WebUI 页签条自己**没有**底色,靠父面板的玻璃)也记在那个常量的注释里。
  新增判据:页签条必须与导航条同族(不许实体面 / 要引 TAB_BAR_* 常量),
  变异验证过(改回 `Theme.surface` 即红)。

★ 深色压暗下限 —— 本轮最重要的真 bug
  玻璃让壁纸**真的透进卡片**之后,"浅壁纸 + 深色文字令牌"这个组合会在
  **卡片内部**发生。设备实测(深色、aurora、压暗 37):
      「角色」ink rgb(183,195,211) on bg rgb(68,95,128) = 2.57:1
      「创建时间」2.27:1、「状态」2.68:1、「压暗」2.19:1(共 8 段 <3:1)
  而**同一套代码在浅色下全部达标** ⇒ 差别不在"选错了色",在"底没暗下来"。
  WebUI 早就撞过并写下了必然值(`index.css:442`):
      .dark { --bg-dim-min: 92%; }     /* 浅色下是 0% */
      background-color: rgb(var(--bg-scrim) / max(var(--bg-dim), var(--bg-dim-min)));
  我们只有 `scrimOpacity(bgDim)`、**没有下限**。补上 `DARK_DIM_MIN = 0.92`
  (浅色下用户设的值照旧生效,不忽略设置)。
  复扫:低对比 8 段 → 通信 0 / 日历 0 / 联系 1 / 我的 1
  (剩下两条经手核是扫描器的假阳性:近黑底上把抗锯齿暗像素当成了"墨")。

★ 手势判据"没有自己搭现场"(套件红、单独绿)
  `cross-client-gesture` 只保证"在月档"、**不保证在哪一月**,而它算的是
  相对最初那一月的 delta ⇒ 前面某个判据(或一次失败的手势)把日历翻到 10 月后,
  期望值就差一格。修法:先点「今天」归位到本月(该按钮语义就是"回本月")。
  验证:从 9 月起步、从 10 月起步,两种脏状态都通过。
  ★ 顺带确认手势本体是好的:`--speed 1500` 时 1000px 要走 670ms,
    紧贴 `SWIPE_MAX_DURATION_MS=700` 而被丢弃;`--speed 3000` 两个方向都翻。

★ 判据基建:修掉一个**静默失效**的 API 名(沿用上轮的教训修法)
  `Surface.ets` 里两处块注释内嵌了 `/* ... */` —— 会把块注释**提前闭合**,
  于是后面的代码跑到注释外面(报"Use let instead of var"/"Invalid character")。
  本仓已有这条纪律("当 `code()` 因注释里的 `/*` 吞掉 import 时,改注释文本"),
  这次又踩了两处(`Surface.ets` 与 `Appearance.ts`)。

判据:files=32 ran=32 checks=519 pass=519 fail=0 red=0;baseline 7/7✓。
This commit is contained in:
2026-09-20 09:01:46 +08:00
parent eb4e413c67
commit c0ab3f57b2
6 changed files with 147 additions and 6 deletions

View File

@ -359,6 +359,26 @@ test('★ 设备:在网格列上左滑,日历标题真的翻到下一段', a
await new Promise((r) => setTimeout(r, 2000));
}
/*
* ★★ 2026-09-20 修(本判据在套件里红、单独跑绿):**先回到已知的月份**。
*
* 本判据下面算的是"相对最初那一月的 delta",而它原来**只保证在月档、
* 不保证在哪一月** —— 于是前一屏停在哪个月、它就从那个月开始数。
* 套件里跑时,日历页可能已被别的判据(或上一次失败的手势)翻到 10 月,
* 而右滑那一步的期望值是按"最初那一月"算的 ⇒ 差一格就判红。
*
* 这不是"设备抖动",是**判据没有自己搭现场**:它依赖进入时的状态,
* 于是"通过与否取决于跑之前那一屏是什么"。同族毛病本仓已修过多次
* (`harmony-appearance` 那条设备判据、以及 `backToMain` 那段注释)。
*
* 修法:点「今天」把 anchor 归位到本月(该按钮的语义就是"回本月",
* 与 WebUI `goToday` 一致),再从那个确定的状态开始算 delta。
*/
assert.ok(tapText(hdc, '今天'), '要能点「今天」把月份归位(本判据需要一个已知起点)');
await new Promise((r) => setTimeout(r, 2000));
assert.ok(monthTitle(dumpLayout(hdc)) !== null,
'点了「今天」之后仍应在月档(否则下面的 delta 无从算起)');
/*
* ★★ 2026-09-19:本判据第一版用「滑一次 + 固定等 2500ms + dump」。
* 结果**单独跑通过、接进 run-all 后失败**(同一台设备、同一份代码)。

View File

@ -585,6 +585,47 @@ test('★ 出现/消失的那类元素真的挂了过渡(`if` 包的弹层不
assert.deepEqual(missing, [], `这些位置会硬弹:${missing.join('、')}`);
});
test('★ 顶部页签条与底部导航条同一族(都是悬浮玻璃)—— 一块实心白就是"割裂"', () => {
/*
* 用户 2026-09-20:「顶部的收件箱发件箱和授权为什么不做成悬浮玻璃?」
*
* 那条页签条当时是**整页唯一一块实心白**:上一轮把列表容器、卡片、
* 底部导航条都玻璃化了,唯独它还写着 `backgroundColor(Theme.surface)`(系统卡片色=实体)
* ⇒ 壁纸从四周透出来,只有它在顶上白着一横条。
*
* ★ 这条判据要抓的形状:**"同一页里少数几处忘了跟着改"** ——
* 逐处修的时候最容易漏的就是"不是卡片、也不是容器"的条状 chrome。
* 所以判据盯的是"页签条有没有和导航条**用同一族**的三种属性",
* 而不是"有没有某个字符串":
* ① 圆角不是自己拍的值(必须引 `TAB_BAR_RADIUS`,它 = `NAV_BAR_RADIUS`);
* ② 左右留白引 `TAB_BAR_SIDE`(它 = `NAV_BAR_SIDE`)⇒ 两条轴线对齐;
* ③ 材质走 `Theme.navMaterial`(与底部条同一档次),不是 `Theme.surface`。
*/
const page = stripComments(main);
const at = page.indexOf('CommTabBar()');
assert.ok(at > 0, '通信页要有页签条(收件箱/发件箱/授权)');
const body = page.slice(at, at + 3000);
assert.ok(!/\.backgroundColor\(Theme\.surface\)/.test(body),
'页签条不许铺实体面(Theme.surface)—— 那是壁纸模式下唯一一块实心白,"割裂"就是这么来的');
assert.match(body, /\.backgroundBlurStyle\(this\.bgActive \? Theme\.navMaterial : BlurStyle\.NONE\)/,
'页签条要吃系统材质(与底部导航条同档),否则它和玻璃卡片拼在一起不是一套东西');
assert.match(body, /\.borderRadius\(TAB_BAR_RADIUS\)/,
'页签条要用 TAB_BAR_RADIUS(= NAV_BAR_RADIUS)—— 自己拍一个数会让上下两条不齐');
assert.match(body, /\.margin\(\{[^}]*left: TAB_BAR_SIDE[^}]*right: TAB_BAR_SIDE/,
'页签条的左右留白要用 TAB_BAR_SIDE(= NAV_BAR_SIDE)—— 悬浮条的侧边必须与底部条同值');
/*
* 自检:把页签条改回实心面,上面第一条必须判红。
* (不写自检的话,这条判据可能因为 `body` 切片取错位置而**恒绿** ——
* 本仓撞过"判据自己在骗自己",所以凡按位置切片的都验一次。)
*/
const broken = 'CommTabBar() {\n Row() {}\n .width(\'100%\')\n .backgroundColor(Theme.surface)\n}';
const bAt = broken.indexOf('CommTabBar()');
assert.ok(/\.backgroundColor\(Theme\.surface\)/.test(broken.slice(bAt, bAt + 3000)),
'自检:实心面这个写法必须能被命中(否则上面的断言是假的)');
});
test('★ 玻璃只在两处、且这一处是"背后有可变内容"(GLASS 登记的放行条件)', () => {
/*
* pi 撤回"模糊只由壁纸层负责"后给的是两条:①同一张底只许糊一次;

View File

@ -83,7 +83,7 @@ const SUITE = [
// P4 外观同步:跑 model/Appearance.ts(纯逻辑),所以也要 strip-types
['test/harmony-appearance.test.mjs', ['--experimental-strip-types', '--no-warnings'], 27],
// P5 悬浮玻璃导航:点击配对 / index 决定挂载 / 命中区 ≥44vp / 悬浮与让位
['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 20],
['test/harmony-nav.test.mjs', ['--experimental-strip-types', '--no-warnings'], 21],
// 上下黑边(用户 2026-09-17/18 报过两次)——钉的是一整套东西的两半:
// `setWindowLayoutFullScreen(true)`(消黑边)+ `getWindowAvoidArea`(让开时钟/手势条)。
// 只做前半 ⇒ 页签被时钟盖住(上一次就是这样退回去的,黑边于是留了三天);