Commit Graph

5 Commits

Author SHA1 Message Date
8267102cd6 跨端: 修「我的」页显得非常挤(三处真因)+ 顺手清掉 3 条已修的自有 ArkTS 告警
用户:「「我的」页面显得非常挤」

先量再改。取了宽屏(3184px)与窄屏(1008px)两态,用 `dumpLayout` 拿真实 bounds,
反推 density=2.875。三个真因,**宽度是主因**。

## ① 内容列没有宽度上限(主因)

WebUI `AccountPage.tsx:104`:

    <div className="w-full max-w-3xl mx-auto px-4 md:px-6 lg:px-10 py-6 space-y-6">

`max-w-3xl` = **48rem = 768px** + `mx-auto` 居中。
鸿蒙这边原来只有 `.width('100%')` —— **没有任何上限**。

后果(实测):宽屏下卡片铺满 **2923px(1376vp)**,而里面全是
"标签 + 短值"的两列行,文字只占左边一小块 ⇒ 右边一大片留白,
整页读起来像一条被拉长的表格。这正是"挤"的观感来源 ——
不是行距小(实测行距 30vp / 字 14vp,其实偏松),是**横向没有收拢**。

修后实测:内容列 **2208px = 768.0vp**(与目标逐位吻合),
中心 1692.0 vs 父容器 1692.5 ⇒ 居中生效。

★ 形状:`Scroll > Row(居中容器)> Column(带上限)`。
  **不能把居中挂在 `Scroll` 上** —— 官方 `ScrollAttribute` 没有
  `.justifyContent()`。我第一版就是直接给 Scroll 挂,那是个编出来的属性。
  ArkUI 没有 `mx-auto` 的直接对应物,"居中"必须靠一个容器节点。

★ 窄屏(1008px = 350vp < 768)下约束**自动不生效**,逐像素确认窄屏形态未变。

## ② 段间距只有 WebUI 的 1/3

WebUI `space-y-6` = **24px**;我们每张卡各写 `margin({ top: 8 })`(7 处)
与 `margin({ top: 10 })`(1 处)—— 既不统一,也贴得太近,
卡片挨在一起时"段"的边界看不出来,整页就是一大块密集表单。

⇒ 收成一个 `SECTION_GAP = 24` 常量,8 处全部改用它。
实测卡片间实测 **69px = 24.0vp** ✓

## ③ `SectionTitle()` 定义了却只调用一次

WebUI `AccountPage` 有 **5 个 `<h3>`** 段标题(基本资料 / 权限范围 /
修改密码 / 登录状态 / 管理),鸿蒙这边只有 **1 个**('权限范围')——
基本资料那六行是裸放在卡片里的,与下面的权限范围只靠一条 `Divider` 硬分。
⇒ 补「基本资料」标题。没有标题就没有"这是另一段"的语义,只能靠线。

## ④ 顺手清掉 3 条自有 ArkTS 告警(37 → 34)

上一条提交我说"`fill` 那 2 处留给下一轮"—— 不该推,这轮做完:

- **2× `fill` API 是 SDK 26.0.0**(`Circle().fill()`,在 WideSidebar 连接点
  与 InboxPage/MainPage 未读点)。查 SDK 头文件确认不是误报:
  `circle.d.ts` 的 `CircleAttribute.fill()` 标 **@since 26.0.0**,
  而基类 `CommonShapeMethod.fill()` 才是 @since 11 —— 子类重载**遮蔽**了它。
  设备实测点**确实渲染**(那是碰巧兼容),但契约上它比编译目标(23)新。
  ⇒ 换成与 `CalendarPage` 小圆点同一形状(普通容器 + width/height + borderRadius),
  只用 API 11 起的通用属性。
- **1× `'packing' has been deprecated`** → `packToData`(SDK 明确给了
  `@useinstead image.ImagePacker#packToData`;两者签名逐字相同:
  `(PixelMap, PackingOption) => Promise<ArrayBuffer>`,只改名字)。
- **1× `This API is unavailable to 2in1`** → 加 `deviceInfo.deviceType !== '2in1'`
  守卫。SDK 原文:「From API version 12, this API does not take effect on
  2-in-1 devices.」告警仍在(静态检查不看运行时分支),但行为已正确分流 ——
  留着这条注释说明为什么不断言消失。

★ 这条告警的噪声价值:同一批里就藏着 `fill` 那个真隐患。
  逐条筛之前,它只是"每次重编都出现的一行字"。

## 验证

✓ 宽屏:内容列 768.0vp、居中、段间距 24.0vp(`dumpLayout` 实测 bounds)
✓ 窄屏:约束不生效、形态与改前一致(像素对照)
✓ 自有 ArkTS 告警 37 → 34(`fill`×2 与 `packing` 消失)
✓ 编译通过、设备安装后进程存活
2026-09-22 08:58:56 +08:00
dfcf13ff9d 跨端: 邮件正文/日历三处真 bug(可读性、居中对齐、日视图多余表头)
用户:「还有那邮件还是居中对齐,可读性也极差,而且还看起来根本不像邮件」
+「宽屏布局下日历的周视图日视图还是一塌糊涂」
+「你的图标变成输入框,图标变成邮件的动画呢?你能不能自己好好审计一下」

这轮**不再逐条打补丁**,改成先审计再改。下面每条都有实测数据。

## ① 邮件正文可读性:1.34:1 → 16.67:1

实测(深色,详情页正文):
    文字 rgb(35,33,52)  压 底色 rgb(0,0,0)  ⇒ **1.34:1**(完全不可读)
    同屏只有行内代码(红色 span)看得见(5.02:1)
    头部信息区更差:rgb(34,35,36) 压 rgb(32,33,35) = **1.02:1**

根因:`Markdown({ text: this.body })` —— **只传了文本,没传 `controller`**。
`@luvi/lv-markdown-in` 于是用它自己的默认样式,那套是给**浅色**配的
(实测 rgb(35,33,52) ≈ WebUI 浅色 `--c-gray-800` = `31 41 55`)。

⇒ 这是「**第三方组件不继承宿主主题**」那一类错:它不读 `$r('sys.color.*')`,
  也不读 `AppStorage`,必须显式注入。新增 `mdController()`,
  照 WebUI `.markdown`(`index.css:592-628`)逐条映射 17 个着色入口:

    .markdown            → Theme.textPrimary
    .markdown a          → Theme.accentFor()
    .markdown blockquote → Theme.textMuted / Theme.border
    .markdown code       → Theme.surfaceMuted

★ 17 个 setter 名逐一比对过库的 `.d.ets`(防拼错静默失效)。
★ 不放在 `@State` 里:控制器是普通对象,ArkUI 不会因它内部变化而重渲染 ——
  每次 `build()` 现取,主题一变就拿到新色(存一份反而**不会**刷新,正是本 bug 的形状)。

## ② 邮件「居中对齐」:ArkUI `Column` 默认就是居中

根因**不在那个 `Text`**,而在 **`Column` 的交叉轴默认对齐是
`HorizontalAlign.Center`** —— 官方《线性布局 (Row/Column)》原文:
「HorizontalAlign.Center(**默认值**):子元素在水平方向居中对齐」。

子元素没显式给宽时跟着内容宽居中 ⇒「回复给 …」那行浮在面板中间。
WebUI 是块级流、天然左对齐,所以"同样的代码"看起来不一样。

修:回复条/转发条两个 `Column` 显式 `.alignItems(HorizontalAlign.Start)`。

## ③ 日历:周/日视图「今天」的数字看不见(1.00:1)

实测(宽屏 3184、周视图第 1 列「21」):
    字 rgb(254,254,254) 压 底 rgb(254,254,254) ⇒ **1.00:1**

根因:`cellFg()` **不看档位**,选中一律返回 `accentFg`(白)。
而两档的"选中底色"根本不是同一个东西:
    · 月视图:整格铺**实心** `Theme.accent` ⇒ 白字对;
    · 周/日视图:底色改由日期头承担,用的是**淡蓝** `accentSoftFor()` ⇒ 白字看不见。
⇒ 按档位分流:周/日档返回 `Theme.accent`(蓝字压淡蓝 ≈5.4:1)。
同一处的农历小字也一并改(同一个三目)。

## ④ 日历:日视图多画了一条无意义的星期表头

周表头 `Row` 在 `if (calScale === 'day')` **之前**渲染 ⇒ 日视图上方挂着
「一 二 三 四 五 六 日」,而一天只有一个日子,七个标签全落空。

WebUI 三种视图都不是这么做的:
    · `MonthGrid` 表头在**自己内部**(`grid-cols-7` 第一行);
    · `WeekGrid` 星期名在**每一列内部**(sticky);
    · `DayGrid` **完全没有**星期行(顶上是日期+农历)。
⇒ 加 `if (this.calScale !== 'day')`。

## 诚实交代:我一度想当然,量了才发现不用改

我本来打算"修"周视图**表头与列对不齐**,理由是"表头均分可用宽、
网格列均分 minWidth(322) 的更大宽"。实测列中心 `[356,607,860,1114,1365,1621,1873]`
与表头中心基本吻合(偏差 ≤11px,在文字宽度内)—— **那是我推的,不是量的**。
先量再改,省掉一次"改坏对的东西"。

## 审计产出(供后续,不只是本次修)

· WebUI 全仓动画清单:5 个 `@keyframes`(`cal-in-next/prev`、`menu-in`、
  `pane-in`、`rise-in`)+ 7 处 `transition` + 3 处 `element.animate()`(全是 FLIP morph)。
· 鸿蒙侧挂点计数:`animateTo` 9、`transition()` 12、`pageTransition` 3、
  `geometryTransition` 4、`PressEffectModifier` 60。
· **`Motion.pageEnter` 是自己加的、零调用点**(`pageTransition` 走的是
  三个页各自的 `pageTransition()`)⇒ 死代码,待清。

## 验证

✓ ①②③④ 全部在设备上取**像素/布局**验证(1.34→16.67、1.00→5.44、日视图表头消失)
✓ 编译通过、进程存活、无新 jscrash
✗ 未验证:动画本体(图标→输入框 morph)—— 220ms,而抓图往返 1.5-3s,
  探针比被测对象慢一个数量级(同 `harmony-morph-unverified-middleframes`)
2026-09-21 20:23:46 +08:00
4ecddfc2b6 跨端: 语义色/三级文字在深色下没提亮(设备扫出 12 处)+ 判据取样法修到第三版
## 一、判据基建先修对,否则是在听噪声

深色可读性扫描的**取样法迭代了三版**,每版都因为**假红**才改的
(记在这里,因为"判据自己错"比"界面错"更费时间):

1. **取中心一个像素** ⇒ 落在笔画之间、读到的是底色 ⇒ 一片 ratio=1.00 假红。
2. **取全局最暗/最亮** ⇒ 会采到**两个不同的东西**:细字的抗锯齿中间色。
   实例:segmented「日」报 ink `rgb(32,34,35)` / bg `rgb(98,100,101)` 2.69:1 ——
   而截图里它要么蓝底白字、要么深底浅字,两种都远高于 3:1。
   **反复核对才确认是判据的错,不是界面的错。**
3. **步长采样 + 众数** ⇒ 仍然假红:周表头「一」报 2.03:1,
   而逐像素的真实墨是 `rgb(166,167,167)`(**6.6:1**)——
   稀疏网格**整个跳过了笔画**。这不是调参能修好的(字越小越糟)。
4. **逐像素 + 众数=底 + 离底最远者=墨**(最终版)。
   理由:一个文字框里**面积最大的一定是底**,而"离底色最远"就是笔画最实那部分。
   ⚠️ 逐像素是**负担得起的**(整屏已解码在内存,一个框最多 9 万像素)——
   **不要再为了省开销退回抽样**,前两版都因此假红。

## 二、真 bug 三批(都是"深色下没提亮")

### ① 语义色前景(12 处 → 修完设备复扫 0)
WebUI 的语义色也是**双通道**,`.dark` 段把 `red/green/amber-700` 换成浅色
(`248/118/249 → 164/219/195` 那组)。鸿蒙只有一个深色值:
- 「归档」按钮 `Theme.danger` #B91C1C 压深面 ⇒ **2.47:1**(联系人页 7 处)
- 「今天」/「日」等 ⇒ **2.38–2.77:1**

加 `dangerDark/approveDark/warnFgDark`(逐字对齐 WebUI `.dark`)
+ `dangerFor()/approveFor()/warnFgFor()` 入口,22 处接入。

### ② 日历工具条整块配色抄错(不是"深色档选错")
对照 WebUI `CalendarView.tsx:313-331`:

| 元素 | WebUI | 鸿蒙(改前) |
|---|---|---|
| 「今天」 | `border-gray-300` + `text-gray-700`(**中性**) | `accentSoft` 底 + `accentStrong` 字 |
| 月/周/日 未选中 | `bg-white` + `text-gray-700` | `accentSoft` 底 |

鸿蒙把**次要的中性按钮**画成了**品牌色按钮** —— 视觉重量跟主操作一样,
三个档看起来"都像选中"。已按 WebUI 改成中性。

### ③ 三级文字在深色下 2.82:1(**71 处**,全局最大的一个)
WebUI 的 `.dark` 段注释逐字写着这个坑:

> 「`gray-400/500`(次要文字)→ **提亮**。深底上的浅色 gray-400
>  只有约 2:1 对比度,远低于 WCAG AA 的 4.5:1 —— **看得见但读不动**。」

鸿蒙用的系统三级色 `ohos_id_color_text_tertiary` 深色下是 `rgb(102,103,105)`
压深面 **2.82–3.05:1**,而它被用在 10–11px 的**小字**上
(时间戳、农历、空态、辅助说明)。

**不换掉系统令牌**("系统拥有的维度"纪律照旧),加 `textSubtleFor()`:
深色下改用二级色(实测 `rgb(166,167,167)` ≈ 7.15:1,
且与 WebUI 的 `gray-500` 同档位)。71 处接入。

**设备复扫(修后)**:通信/日历/联系三页 **176 段文字全部 ≥3:1**。

## 三、欠账与底本

- `baseline.sha` 第 4 次重算。仍**先逐个取证**:
  `git diff --quiet HEAD -- <f> && echo STALE || echo INTENTIONAL`
  ⇒ 三个全是 `INTENTIONAL`(我改的),不是变异残留。
  把这条取证命令也写进文件,下次照抄。
- `dangerDark/approveDark/warnFgDark` 已登记进 `SELF_OWNED_COLORS`
  + `Theme.ets` 登记表(各带理由与 WebUI 出处)。

`run-all.mjs` → `checks=514 pass=514 fail=0 skip=0 red=0 broken=0 unreported=0`。

**未验**:①「我的」页(底部头像按钮,扫描脚本没找到入口)没扫;
② 浅色下的对比度没扫(本轮全部针对深色)。
2026-09-19 20:27:09 +08:00
36ef15a8f2 跨端: 顶栏不再自己铺白条 + 日历改左右两栏 + 常驻窗格动画真的会播(用户三处实测指出)
用户三条反馈,逐条对应:

① 「底栏数字为什么显示在图标下面?」
   WebUI 的徽标是 `absolute top-1 right-[22%]`(脱离文档流、浮在图标右上角),
   我写成了 `Column` 的第三个子节点 ⇒ 参与竖向布局、掉到文字下面。
   改用 `Stack({ alignContent: Alignment.TopEnd })` 锚在**图标**上。
   (顺带撞了 skill 里明写的坑:Stack 没有 `.justifyContent()`。)

② 「一个横着过去的白条,我真的服了」/「期望:融进背景」
   WebUI 的顶栏**自身没有底色** —— 只有 `border-b border-gray-200`
   (`ContactPanel.tsx:64`、`CommTabs.tsx:40`、`CalendarView.tsx:295`),
   底色由所在面板给;壁纸开启时那层面板是玻璃色(`index.css:876`)。
   鸿蒙三个窗格顶栏写死了 `Theme.surface`(实心白)⇒ 无论壁纸开没开,
   顶上都是一条不通明白带。改成与**页面底**同一口径
   (`bgActive ? Transparent : surface`)+ 补下边框。

③ 「日历页面和webui布局完全不同」
   WebUI 是**左右两栏**(`CalendarView.tsx:452-457`):左 `flex-1` 网格、
   右 `400px` 常驻面板(日程 / 编辑器 / 小时网格三态互斥)。
   鸿蒙原来是**单栏竖堆**。重搭为两栏,`paneWide` 由 `.onAreaChange`
   量本页**自己的**宽度(不是屏幕宽度 —— 宽屏下这一页已被侧栏占掉一截);
   编辑器改占右栏位置(不再整页盖掉正在看的那个月)。

④ 「最严重的动画问题你一点也不该改」
   日历是**常驻挂载**(`visibility` 控制,因为它里面 today 要随时间重算、
   也要保住"正在看哪个月"),而 `.transition()` 只在**挂载/卸载**时触发
   (SDK 原话 \"when it **appears and disappears**\")⇒ 挂在它上面的
   `.transition(paneRiseIn())` **一帧也不会播**,切过去是硬弹。
   WebUI 踩过同一个坑并把错法写进了 `index.css:1305-1320`
   (「只挂了类,却没让触发窗口出现 ⇒ 类挂着、动画永远不播」),
   它的解法是 `html.view-switch` 重放窗口。ArkUI 对应物是
   `animateTo` + 显式 `calPaneIn` 属性(`opacity` + `translate`)。
   ★ reset 必须在 `animateTo` **外**:写进回调里会与同帧的 1 相抵,
     渲染层只看得见最终值 ⇒ 动画退化成一个瞬移。

顺带修:
· 宽屏侧栏 = 3 项(通信/日历/**联系**)+ 底部一簇(头像/主题/退出),
  与底栏的四项(含「我的」)**不是同一份清单** —— WebUI 的 Sidebar 与
  NarrowNav 本就不同(`Sidebar.tsx:26-44` vs `NarrowNav.tsx:37-40`+143)。
  新增 `NAV_SIDEBAR_ITEMS` / `ME_PANE_INDEX` / `SIDEBAR_ITEM_*`。
· 退出登录抽成 `api/Logout.ets` 的 `performLogout()`:侧栏底簇与「我的」页
  两个入口必须做同一件事(尤其"先注销推送 token"那一步),复制一份就会不一致。
· 徽标 `'plain'` 档底色:WebUI 是石板灰 `--c-chrome-600`(#475569),
  我写成与未读共用红色 ⇒ 「联系」的徽标看起来像"有未读"。
· 主题快捷开关(侧栏底簇):对齐 `ThemeToggleButton` —— 从 `system` 翻转时
  落到**当前生效值的反面**(不是回 system;那可能毫无变化、让按钮看起来坏了)。
· 「浓度」→「压暗」+ 数值带单位(原先屏上印 `56.000000`;WebUI 是 `suffix="%"`)。

判据(8 个套件全绿:logic 28 / nav 18 / widescreen 7 / window 9 /
arkts 5 / contacts 5 / calendar 30 / system-api 5):
· `harmony-widescreen` ② 重写:**回读 `Sidebar.tsx` 数 `short:` 的个数**
  要求鸿蒙同数,并断言底部一簇三键真的被调用(`this.onToggleTheme()` ——
  第一版写成 `/onToggleTheme/`,变异测试当场证明它不咬:属性**声明**还在,
  正则照样匹上)。新增 ⑧:三档色调各自底色,期望值从 `--c-chrome-600` 读出。
· `harmony-nav` 动画条重写:改判**机制真的存在且被驱动**
  (旧断言 `.visibility(...).transition(...)` 锁的正是那个 bug ——
  判据引自己写的注释当依据,就会把错误锁死)。新增 reset-在-animateTo-外
  这条断言(否则动画退化成瞬移)。
· `harmony-nav` 设备条:`navItemsOf` 的宽屏过滤从"左边缘靠左 1/6"
  改成"**整个盒子在侧栏轨道内**" —— 旧条件把日历网格的格子
  (实测 `[229,511][366,794]`)也当成导航项,一屏数出 11~12 个。
  新增 `navRailItemsOf`:导航轨贴顶、底部簇在屏底,按位置切一刀。
· `harmony-logic` 两处:从 `NAV_ITEMS` / `NAV_SIDEBAR_ITEMS`
  **各自的数组**里取 label —— 原先把全文件 `label:` 一网打尽,
  得到 7 个(4+3 混在一起),任何一边改对了它都会红。

设备实测(HATriple 三折叠,3184×2232):侧栏 3 项 + 底簇 / 底栏徽标回到
图标右上角 / 日历左右两栏与 WebUI 并排同构。

server/go.mod:补 2da38bb 漏提交的 lunar-go 依赖。
2026-09-19 12:30:05 +08:00
474cadaf54 跨端: harmony 管理页(用户管理)+ P4c 壁纸上传入口 —— 「功能做全再交付」的两块
pi 的交付清单里缺的两块(`docs/GUI-PLAN-HARMONY.md` 原先把管理后台划在首版之外,
用户明确要求「功能做全再给我」之后收进来)。

标 `跨端:` 是因为本次的判据落在 `client/electron/test/`(鸿蒙的判据目录一向量在那里),
代码本体全在 `client/harmony/`。

## 管理页(用户管理)

- `pages/AdminUsersPage.ets`:新建 / 编辑(显示名·角色·白名单)/ 启停 / 重置密码。
  排布照 `AdminUsersPage.tsx`,包括「受限」徽标的口径(普通用户且白名单非空才显示)、
  最后登录缺席与空串都显示「从未登录」、管理员对白名单两项忽略。
- 入口在设置页底部,**仅管理员可见**(`role === 'admin'` 严格相等,与 `App.tsx` 同口径)。
  读不到身份时**不**显示也不报错(乐观放行会让每个普通用户看到一个点进去 403 的入口)。
- `api/AdminApi.ets` + `model/AdminUsers.ts`(纯逻辑,零 import ⇒ 判据能真跑)。
- 启停**只发 status 一个字段** —— 服务端是部分更新,多发字段会把显示名与白名单一起改掉。
- `model/Models.ets` 补管理端 DTO;`main_pages.json` 注册路由。

## P4c 壁纸上传

- `model/ImagePrep.ts`:阈值与两档策略(2560/0.85 → 1280/0.78,入口 20MB,压后上限 3.5MiB)。
  **一处有意不对齐 WebUI** 并写明理由:WebUI 卡 data-URL 长度(含 base64 膨胀),
  鸿蒙内存直传 ArrayBuffer,卡的是字节数。
- `common/BackgroundPicker.ets`:不设 / 预设 / 自定义图片 + 浓度与模糊滑杆。
  上传链:picker → 判可不可以 → 逐档按 desiredSize 解码压缩 → 上传 → **请页面以服务端为准重新同步**。
  失败**必带原因**(服务端 415/413 文案原样透出)。
- `ApiClient.uploadBytes`:MultiFormData.data 收 ArrayBuffer(核了 SDK,since 11;本工程 23)
  ⇒ 内存直传,不需要 base64、也不需要临时文件。
- 用户取消选图**不算失败**,什么都不说。

## 顺带修掉的两处真问题(都是变异测试逼出来的)

1. **压缩循环的第二档此前是死代码**:循环里的 break 与循环外那句 shouldRetryWithActual
   互相抵消 —— 把循环里那处改成 `if (true)`(永远只压一档)整套判据照样全绿。
   收成一处判定(overLimit),循环外只读结论。
2. **壁纸的模糊档一直是「只写不读」**(计划文档 §7.12 登记过):滑杆能拖、值能存、
   blurStyleFor 也写了,就是**没有调用点**,壁纸一点没糊。本次补上调用点
   (壁纸层 .blur(px) = 图片内容模糊;导航条材质由 blurStyleFor 映射)。
   同时按 §7.12 的原承诺更新了那一行。

## 一并修正的旧判据(都是"太宽/太窄",不是放宽标准)

- 「模糊归属」:原文「壁纸层不许有**任何**模糊调用」把**图片内容模糊**与**面板材质**
  混为一谈(WebUI 侧核实:.app-backdrop 的 filter 与它之上那层的 backdrop-filter
  是两个不同的量)⇒ 改成按两种模糊分别钉。
- 「bgBlur 只写不读,消费侧必须为 0」:值不再成立,**形状保留**(逐文件登记 + 计数 + 理由),
  标题与断言里的假话一并改掉。
- isDarkMode 那条 `/dark\s*\)/` 断的是**参数顺序**(加一个入参就误红)⇒ 改成"dark 在实参里"。
- 导航材质三处断言原本钉 `Theme.navMaterial` 字面量 ⇒ 改成钉新的映射写法。

## 判据

新增 `harmony-admin.test.mjs`(22 条)、`harmony-imageprep.test.mjs`(29 条);
`harmony-presets.test.mjs` 加 1 条(模糊档搬运与归一,含 -0 那个洞)。
全量 203 条:**201 通过**,2 条失败为**改动前就红**的既有项
(BUILD_INFO 比对、词表↔余额)—— 用 stash 对照验证过。

两个新判据文件上跑了 **48 个变异体,全部被抓**(含"接线"类:删掉「受限」徽标、
组件自己宣布成功、release 不 await、按原图尺寸解码…),
其中 2 个变异体**红不了**,因此又补了 5 条判据(纯逻辑接线、退档判定只有一处、
两档都超限必拒、解码尺寸用的是目标尺寸而非原图尺寸、模糊档搬运)。
(数字口径:按 runner 的真实条件"锚点恰好命中 1 次才算跑过"统计;
另有 4 条锚点不命中、根本没跑,不算在这 48 里。我第一次写的是"40"——
凭记忆累加的,错了,已更正。)

**未验**:本机无设备/无模拟器 ⇒ 全部观感未验(管理页排版、滑杆手感、模糊在真机上的
实际档位观感)。代码齐 ≠ 真机验过。
2026-09-15 11:03:22 +08:00