Files
MailUI4Agents/client
JianFeeeee 6a8e868d31 fix(harmony)★★: MailStore 收/发共用一份快照 + 壁纸 PixelMap 泄漏 —— 两处 HIGH
★★ 前置事实修正:报告写的「本机无 hvigorw / 无 SDK / HarmonyOS 侧没有编译过」
   **已不成立** —— `/opt/huawei/command-line-tools/bin/hvigorw` 6.26.2 可用,
   `hvigorw assembleHap --no-daemon` 出 **BUILD SUCCESSFUL**(约 26 s)。
   ⇒ 本次两条 HIGH 都是**真编译过**的,不是静态推断。这是本次最大的认知变化:
   「改 ArkTS 无法验证 ⇒ 只能推给下一个人」这个前提可以取消了。

## ① MailStore:收件箱与发件箱共用同一份快照(HIGH)

`loadInbox` 与 `loadSent` 此前**全部往同一个 `snapshot` 写**,而两者是两个独立
`@Component`(`InboxTab`/`SentTab`),各自 `await` 完才 `applyStoreSnapshot(...)`
⇒ 切页签 / SSE 交错时**发件箱那次把收件箱那份覆盖掉**:`unread = 0`、
`mails` 换成发件箱的、`loading` 互相关闭 ⇒ 症状是「收件箱未读被清零 /
列表短暂空白 / 转圈停了但列表是空的」。

★ 为什么此前没人动(旧报告标 CRITICAL 却长期未修):它当时的理由是
  「Navigation 分栏下两个 pane 同时在屏」这个**必然场景**;`commTab` 后来改成
  同一时刻只 mount 一个 ⇒ 那个"必然"没了 ⇒ 从"每次都坏"降级成"交错时坏"。
  **这是判据缺失导致缺陷降级**——本条判据就是为了不让它再降级。

**修法(两份快照 + 代号守卫,各挡一半,都要有)**:
· 新增 `sentSnapshot`,`loadSent`/`paintSentFromCache` 只写它;
  `SentTab` 读 `store.sentSnapshot`(收件箱侧零改动)。
· `inboxGen` / `sentGen` 代号:同一栏**自己**的两次 load 也可能乱序到达
  (SSE 叫醒一次、切页签又触发一次)⇒ 旧的**后**到会盖掉新的。
  ⚠️ 守卫**只拦写快照**,`loading = false` 仍要执行 —— 否则新请求的 spinner 被挂住。
· `clear()` 清两份,**并把两个代号都 +1 作废**:否则**正在飞**的旧请求回来时
  `myGen` 仍等于旧值 ⇒ 会把清空后的快照重新填上旧数据(登出/切账号正是此时)。

★★ 顺带修一个**我差点引入的回归**:拆开之前两份共用一份 ⇒ 归档一个会话会
同时抹掉两边的它。拆开后若只动收件箱,归档完**发件箱仍显示该会话**(而服务端已删)。
⇒ `dropSession` 拆成 `dropSessionFromInbox` / `dropSessionFromSent`,
**两份都要剔**;且发件箱那份**不能用** `splitByPermission(...).normal`
(发件箱不做权限分流,那一筛会抹掉「我发出的授权请求」——09-20 修过的
「发件箱一片空白」同一族)。

## ② AppearanceStore:壁纸 PixelMap 泄漏 + 全尺寸解码(HIGH)

`loadWallpaper` 此前既不设 `desiredSize`、也从不 `release()`:
· `PixelMap` 是**原生内存**,每次同步/每次切账号重新解码一张,全部不释放;
  而它是**静态单例**、活过登出 ⇒ **换账号这条路必然泄漏**(无任何清理入口)。
· 不带 `desiredSize` ⇒ 按原图尺寸解。`BackgroundPicker` 只把上传边长卡在
  `MAX_EDGE = 2560` ⇒ 一张 2560×2560 ARGB ≈ **26 MB** 常驻,
  而它永远被合成器降采样着全屏画。

**修法**(照 `BackgroundPicker` 既有形状,不另创一套):
· `desiredSize` 取 `display.getDefaultDisplaySync()` 的 width/height,
  不写死魔数(随屏变)。★ 它**会抛**(SDK 注释 1400001)⇒ 必须 catch,
  取不到就退回"不限制尺寸",而不是把壁纸整条路断掉。
· 所有权:先 `this.wallpaper = next` 再释放**旧的**,并把局部变量置空 ——
  ★ 若在 `finally` 里释放 `this.wallpaper`,会把**刚装上的那张**释放掉,
  这是最容易写反的一处。
· 新增 `releaseWallpaper()`,并在 `Logout.ets` 里与 `MailStore.clear()` 并排调用
  —— 后者是该方法**唯一**的调用点,没有它它就只是"写好了没人用"。

## 判据(harmony-arkts 8 → 10;harmony-logic 补一格)

新两条按**括号配对**取方法体(`balanced()`,不用 `\{[\s\S]{0,N}` 窗口)。
**变异测试 5 个全部抓住**:loadSent 写回 snapshot / SentTab 读错快照 /
归档不剔发件箱 / 释放的是新图而非旧的 / 去掉 desiredSize。

★ 顺带修一条**HEAD 上就在红的判据**(不是本次引入):`harmony-logic` 那条
  「先分家、后折叠」只认字面量 `groupMailsBySession(split.normal)`,
  而源码**本来就是** `const inboxMails: MailLike[] = split.normal;` 后
  `groupMailsBySession(inboxMails)` ⇒ 恒红。
  ⇒ 补上"经中间变量"这一支;变异验证:把 `inboxMails` 换成 `mergedMails`
  (权限邮件混进收件箱)仍**照红** ✓ —— 放宽的是写法假设,不是判定。

## 编译期硬规则(ArkTS 不接受对象字面量当类型)

`targetSize()` 第一版返回 `{ width: number, height: number }` ⇒ 编译报
`arkts-no-obj-literals-as-types` / `arkts-no-untyped-obj-literals`(连
**返回的对象字面量**也要能对应到**具名**类型)。⇒ 必须先声明 interface,
且每个字面量先赋给**显式类型的局部变量**再 return。
★ 这条**编译期硬规则**在本仓无判据覆盖(harmony-arkts 判的是 import 位置那一类)
⇒ 只能靠真编译抓;而它能抓,再次证明「ArkTS 本机无法验证」已不成立。

## 边界 / 未做

· **本条判据证明不了运行时症状**:切页签交错那条要设备才能造,本仓无那种探针。
  静态层只把住「两栏各写各的」。设备那半**仍未验**。
· `releaseWallpaper` 的**实际内存回收**未在真机验证(memory profiler)。
· 离屏解码(`@Concurrent`/taskpool)**本轮不做** —— 全仓 0 先例,
  单独引入会扩大风险面。
2026-10-03 12:11:25 +08:00
..