跨端: 发件箱接上 MailStore(A 的剩余)+ 修 3 处把原始 ISO 印到界面上的时间

══ ① A 的剩余:发件箱走了 store(顺带撞出 store 里的一个真 bug)

`SentTab.load()` 原来把收件箱那 107 行**抄了一遍**(注释里还写着"同收件箱"
—— 抄的时候就知道是重复)。现在改成 3 行调用 `MailStore.loadSent`。

★ 接上之后**立刻炸出一个真 bug**:`MailStore.loadSent` 里写的是
      snap.groups = [];
  而发件箱的列表**就是按会话分组渲染的**(`ForEach(this.groups, …)`)
  ⇒ 接上 store 之后发件箱会**一片空白**,而"接口有返回、loaded > 0"
  会让症状看起来像"数据没到"。

  为什么会写成空数组:它是照收件箱那半抄的,而收件箱的 `groups` 来自
  `splitByPermission` **筛完之后**的 `inboxMails`(授权邮件要挑出去单独成栏)。
  抄的时候只看到"要赋值",没注意发件箱没有那一步筛选 ⇒ 把 `[]` 抄了过来。
  发件箱也**不能**照搬那个筛选:那是为"授权待办栏"服务的,发件箱不显示那栏。

★ 这段经历本身值得记:**死代码不会自己暴露错误**。
  `loadSent` 写完到今天我接上它之前,那行 `groups = []` 一直没人执行过。
  "写完就搁着"和"接上一个调用点"是两件事 —— 后者才算验过。

══ ② 3 处把原始 ISO 直接印到界面上(看到屏幕才发现的)

发件箱那张卡的时间格显示的是
      2026-09-19T02:55:33.10099Z
(23 个字符,把「致 homeagent」那行挤到换行)。

WebUI **三种卡片全部格式化**、一个没漏:`MailList.tsx:151/262`、
`PermissionList.tsx:143/251`,全是 `toLocaleString('zh-CN', {month,day,hour,minute})`
⇒ `MM/DD HH:mm`。鸿蒙的 `compactMailTime` 就是那个实现,收件箱一直在用 ——
所以这不是"要不要格式化"的分歧,是**三处漏调**(收件箱 / 发件箱 / 授权栏)。

══ ③ 补判据(形状而不是实例)

新判据:`Text(<x>.created_at)` 这个**形状**不许出现(`Text` 只负责画,
不做格式化)。为什么枚举形状而不是列举调用点:漏的三处分布在**三个不同 struct**,
按名字枚举一定会再漏第四个。
自检 + 变异验证都做了(把 SentRow 那处改回裸字段 → 判据转红;还原 → 绿)。

设备验证:发件箱正常渲染(会话组 + 条目,非空白),
时间显示 `09/19 10:55` 而非 ISO。
This commit is contained in:
2026-09-20 18:43:35 +08:00
parent 99a7bbccf7
commit f1db99ef41
3 changed files with 99 additions and 37 deletions

View File

@ -305,3 +305,44 @@ test('★ 尺寸/底色不许直接链在 `AmIcon` 上(内层容器固定 icon
assert.equal(probe(bad), 1, '探测器要认得出错误写法');
assert.equal(probe(good), 0, '探测器不许误伤套了 Stack 的正确写法');
});
test('★ 时间戳不许把原始 ISO 直接印到界面上(`Text(mail.created_at)`)', () => {
/*
* ★★ 2026-09-20 加。这条是**先看到屏幕上印着原始 ISO** 才回来补的判据:
*
* 发件箱那张卡的时间那一格显示的是
* 2026-09-19T02:55:33.10099Z
* (23 个字符,把「致 homeagent」那一行挤到换行)。
*
* WebUI **三种卡片全部格式化**,一个都没漏:
* · `MailList.tsx:151`(会话组头)与 `:262`(组内条目)
* · `PermissionList.tsx:143`(授权组头)与 `:251`(授权条目)
* 全部是 `toLocaleString('zh-CN', { month:'2-digit', day:'2-digit',
* hour:'2-digit', minute:'2-digit' })` ⇒ `MM/DD HH:mm`。
*
* 鸿蒙的 `compactMailTime` 就是那个格式的实现,收件箱一直在用 ——
* 所以这不是"要不要格式化"的分歧,是**三处漏调**。
*
* ★ 判据形状为什么是"不许出现 `Text(<x>.created_at)`"而不是列举调用点:
* 漏的那三处分布在三个不同的 struct(收件箱/发件箱/授权),
* 按"名字"枚举一定会再漏第四个。而"把 ISO 交给 Text"这个**形状**
* 本身就是错的(`Text` 只负责画,不做格式化)——
* 枚举形状才挡得住漂移。
*
* ★ 边界:`*For()` 那种"取值函数"不算,这里只管**直接**把字段喂给 Text 的。
* 允许 `created_at` 作为**参数**传给格式化函数(那正是正确写法)。
*/
const src = code(join(ETS_ROOT, 'pages/MainPage.ets'));
// Text( 里直接出现 .created_at / .createdAt,且中间没有函数调用
const bad = [...src.matchAll(/Text\(\s*[\w.]*\.(created_at|createdAt)\s*\)/g)]
.map(m => m[0]);
assert.deepEqual(bad, [],
`这些地方把原始 ISO 直接交给 Text 了(屏上会印出 23 个字符的时间戳):\n ${bad.join('\n ')}\n` +
'改用 compactMailTime(...)(收件箱行一直是这么写的)');
// 自检:探测器要认得错误形状,且不误伤正确写法
const probe = (t) => [...t.matchAll(/Text\(\s*[\w.]*\.(created_at|createdAt)\s*\)/g)].length;
assert.equal(probe("Text(mail.created_at)"), 1, '探测器要认得出裸字段');
assert.equal(probe("Text(compactMailTime(mail.created_at))"), 0, '不许误伤传参给格式化函数的写法');
assert.equal(probe("Text(mail.createdAt)"), 1, '驼峰写法同样要抓');
});