Files
MailUI4Agents/docs/HARMONY-ALIGN-PLAN.md
JianFeeeee 6be5a543af test(suite): 判据总入口 run-all —— 全部跑完再算退出码,并接上两条从未跑过的判据
## 为什么

`npm test` 是 `&&` 链:**前面红一条,后面全部不跑**。于是"只红一条"看起来像
"只有一个问题",实际后面那些判据连跑都没跑(`packaging` 那条能发现"界面改了没重打包"的
判据,长期因此隐身)。而且这套规矩下"全绿"可信、"红"不可信。

改成 `test/run-all.mjs`(pi 建议):每条都跑,红的收集起来最后一起报、一起退出。

- **自检 1**:清单里的文件必须存在(名字写错 = 一条判据静默消失);
- **自检 2**:`test/` 下每个 `*.test.mjs` 都必须接进清单 —— **新增判据忘了接线直接红**。
  这条自检当场抓出 `nav-merge.test.mjs`、`build-stamp.test.mjs` 两个"写好了没接线"的文件
  (8 + 4 条判据此前从未跑过,与 `cross-client-theme` 是同一族问题)。
- 变异验证:强制 `theme.test.mjs` 判红 → 后面 5 条判据照跑,汇总如实报"红 1/9"、退出码 1。

`package.json`:`"test": "node test/run-all.mjs && vitest run"`。

## 接线后暴露的两条陈旧判据(代码没错、判据钉的是旧写法),按"钉行为不钉字面"修

1. `setViewMode(target || commTab` —— 代码后来等价改写成
   `target ?? (isComm ? commTab : modes[0])`。改成钉行为"isComm 时落到 commTab",
   并额外要求桌面分支带 `target`(否则日历/联系人点不动)。
2. `MailView.tsx` 里 grep `glass-control` —— 授权多选胶囊已抽成共用组件 `ComposerChip`,
   样式其实是对的(neutral 未选中态就是 `glass-control`)。改成钉
   "MailView 用 ComposerChip" + "ComposerChip 用控件档"(文件会搬、组件不会)。
   两条都做了变异验证(去掉玻璃档 / 去掉 commTab 回退 → 各判红 1 条)。

## 生成产物再补一条判据(pi 提议)

用 postcss 真解析 `background-takeover.generated.css`:断言每条规则的选择器形状恰好是
`html[data-bg='on'] .bg-xxx`,且规则条数与源码扫到的清单条数一致。
只验"能解析"不够 —— 实测 postcss 对当年那份坏产物照样解析出 1 条规则(选择器前粘了
注释尾巴),所以卡形状与条数。变异:把坏头注释塞回生成产物 → 该条判红。

## BUILD.md:deb 结论撤回(不是 fpm)

`TMPDIR=/var/tmp/ebtmp` 在本沙箱建不出来;把 TMPDIR 指到工作区大盘后 deb 正常产出
(`release/agentmail-web_0.1.0_amd64.deb`,约 100MB)。日志关键行是 **`Errno::ENOSPC`**:
fpm 要把 291MB 的 `linux-unpacked` 整份复制进 TMPDIR,而本机 `/tmp` 是 9.8G tmpfs、
被 `/tmp/gocache`(4.5G)等占到 99%。所以「本机打不出 deb」是环境症状、不是工具链缺陷,
deb 不必从 `build.linux.target` 摘掉。排查命令写进 BUILD.md。

## 验证

`npm test` 退出码 0:9 个判据文件全绿(markdown-xss、narrow-layout、nav-merge 8、
theme、background 42、cross-client 8、harmony-logic 14、build-stamp 4、packaging 3)
+ vitest 258/258。鸿蒙侧 `hvigorw assembleHap` 仍 BUILD SUCCESSFUL。
2026-09-14 13:44:00 +08:00

386 lines
25 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 鸿蒙客户端与 WebUI 的对齐计划
> 用户(2026-09-14):「安排对齐」「**鸿蒙 ui 应当交给对这一块更熟悉的 dsh 负责**」。
> 这份文档把**差距**与**分期**写清楚,免得每轮都从"感觉还差什么"重新猜。
>
> **负责人:dsh**(2026-09-14 起由 jianf 指定)。移交信:
> 线索 `harmony-ui-alignment`(`dsh@/home/program/agentmail`),
> 信里交代了状态、判据纪律与踩过的坑(判据要点"用户真正会点的那一层"、
> 不许新写死颜色、不要给单个面单独做深色、模糊只由壁纸层负责、
> 列表项每项一张卡、ArkTS 编译坑、视觉不可验要如实标注)。
>
> 我(pi)保留 WebUI/Electron 侧:需要两边一起加令牌之类的配合,回信给 pi。
## 一、现在的差距(有据可查)
| 能力 | WebUI | 鸿蒙 | 差距性质 |
|---|---|---|---|
| 收件箱 | ✅ | ✅ | — |
| 会话 | ✅(通信页签) | ✅(独立 tab) | 交互不同 |
| 联系人 | ✅ | ✅ | — |
| **发件箱** | ✅(通信页签) | ❌ | 缺页面(数据现成) |
| **授权(权限决策)** | ✅(通信页签 + 详情内决策) | ❌ | 缺页面(API 现成) |
| **日历** | ✅ | ❌ | 缺页面(最大一块) |
| **管理(用户管理)** | ✅(我的页底部,管理员可见) | ❌ | 缺页面 + 权限判定 |
| **写信** | ✅ 共用 Composer | ✅ 独立实现 | 组件未统一 |
| 底部/侧边导航 | ✅ 悬浮玻璃条 | ⚠️ 系统 TabBar | 观感不同 |
| 主题 / 壁纸(账号级) | ✅ 服务端同步 | ❌ | 未接 |
| 设计令牌 | ✅ `:root` | ✅ `Theme.ets` | **已对齐**(判据钉住) |
## 二、已经做完的(本轮之前)
- **设计令牌共用**:`common/Theme.ets` 与 WebUI 的 `:root` 一一对应
(品牌蓝、圆角 14/8、导航玻璃 0.72、语义色、字号),并由
`client/electron/test/cross-client-theme.test.mjs` 钉住取值一致性。
- **旧调色板清除**:页面里的 `#1A73E8`(Google 蓝)/`#333333`/`#F5F7FA` 等
与 WebUI 不同的写死色值,全部换成令牌(203 处)。
- **修掉只剩第一个 tab 高亮的 bug**(`currentIndex === 0` 写死)。
## 三、分期(按"能独立验收"切)
**P1 设计语言(pi 已完成)** —— 令牌 + 颜色替换 + 编译通过。以下 P2–P6 归 dsh。
**P2 发件箱**:复用现有列表组件,接口 `/mail/sent`。验收:能看到已发邮件、
点开进详情;空态有说明。**为什么先做它**:与收件箱同构,风险最低。
**P3 授权页**:导航加一项「授权」(or 通信页内页签,见 5.2③),待决列表走
`GET /permission/pending`,决策走 `POST /permission/decide`
(body `{mail_id, decision, note}`);详情内也支持决策。
**(接口名已于 2026-09-14 由 dsh 核对修正,原写的 `/permission/{id}/decide` 不存在。)**
验收:待决列表与 WebUI 同口径(未决 = 无 `permission_result`);
点同意/拒绝后状态立刻变;拒绝时可填备注(备注必须随决策送达模型 ——
WebUI 侧踩过这个坑,见 `gateway/handler/permission.go` 的 Note 传递)。
**P4 主题/壁纸同步**:接 `GET/PUT /me/appearance` + 图片走带认证的 fetch。
验收:换账号后外观跟随;服务端"无记录"时以本地为准(不要用默认值覆盖 —— 同 WebUI)。
**P5 玻璃悬浮导航**:自定义底栏(圆角 + 半透明 + `backgroundBlurStyle`),
取代系统 TabBar。**注意**:视觉验收需要设备或签名 HAP,
本机 `hdc list targets` 为空 ⇒ 只能保证编译与结构,观感要人眼确认。
**P6 日历**:网格 + 事件读写 + 左右滑动翻页(复用 WebUI 的手势阈值:
水平 ≥40px、≥1.5× 垂直、<600ms)。这一块最大,单独排。
## 四、验收纪律(照 WebUI 那套)
1. 每个页面都要有**点它**的判据(WebUI 侧就是因为只验结构没验点击,
漏掉了"侧栏点了不翻页")。
2. 判据不许只看截图:要量几何/对比度/命中区。
3. 无法验证的要**如实标注**(例如"编译通过、视觉未验"),不能写成"已完成"。
---
## 五、交接核对(dsh,2026-09-14 收到 pi 的移交信后)
核对方式:跑构建、跑判据、读代码,不靠转述。
### 5.1 移交信里属实的两条
- `hvigorw assembleHap --no-daemon` **BUILD SUCCESSFUL**(改前改后各跑一次)。
- `node --test client/electron/test/cross-client-theme.test.mjs` 6 条全绿。
### 5.2 三处纠正(照原计划直接做会踩空)
**① P3 的接口名写错了。** 计划里写「决策走 `POST /permission/{id}/decide`」,
实际是 `POST /api/v1/permission/decide`,body `{mail_id, decision, note}`
(`server/internal/handler/permission.go:301`)。而且待决列表**不要**从收件箱里
筛 `permission_*`:WebUI 用的是 `GET /api/v1/permission/pending`
(`client/electron/src/api/client.ts:623`),鸿蒙照这个走才同口径。
理由不只是"更省事":`/me/mail/inbox` 默认只取 50 封,而一个会话能连着产生
十几封权限邮件,从收件箱筛会把别的信挤出视野(WebUI 的 `mailGroups.ts`
开头就是为这件事写的)。**用 pending 接口,与 WebUI 同源。**
**②"不许写死颜色"的判据原来只挡得住列出来的那 8 个旧色值。**
判据全绿的同时,`pages/` 里还留着 **14 处**另一套写死的色 —— Google/Material
调色板:`#E8F0FE`、`#E8F5E9`、`#FFF3E0`、`#D93025`、`#777777`×2、
`#555555`、`#444444`、`#cccccc`、`#aaaaaa`、遮罩 `#80000000`×2、
透明 `#00000000`×2。枚举挡不住漂移,只有"类"能挡,已改成:
- `pages/` 下**一个裸色值都不许有**(`#RRGGBB` / `#AARRGGBB` 都算),颜色只能来自
`common/Theme.ets`;页面清单也改成**扫目录**(旧版硬编码 7 个文件名,
接下来要加的页面会自动逃出判据)。
- 变异测试确认能判红:往 `InboxPage.ets` 塞一个 `#E8F0FE` → 判据红;撤回 → 绿。
- 顺带把权限档位徽标的配色收进令牌:`Theme.permBg/permFg`,与 WebUI 的
`PermissionChip.tsx` **同一映射**(plan=蓝 / workspace=绿 / full=琥珀)。
原先是 `full ? 绿 : 橙`(Material 色),与 WebUI 反着来。
- 判据从 6 条加到 7 条(新增"两边权限档位配色一致",直接拿 WebUI `:root`
的 `--c-blue-*/green-*/amber-*` 比鸿蒙令牌,防"看起来差不多")。
**③ 导航的差距不止"观感不同",是信息架构不同。** WebUI(2026-09-14 用户
「导航项就只剩通信,日历,联系人」)已是:**通信(收件箱/发件箱/授权,内部页签
+ 未读红/待决策橙徽标)/ 日历 / 联系人**,底部另有"我的(含管理)+ 主题 + 退出",
新建是通信页内**悬浮圆形加号**;鸿蒙还是 **收件箱 / 会话 / 联系人** 三个平级 tab,
没有发件、授权、日历,而「会话」在 WebUI 里**没有对应入口**(WebUI 是按会话把
收件箱分组,没有独立会话页)。
⇒ P2/P3 不是"再加两个页面",得先对齐信息架构,否则加完还是两套导航。
### 5.3 一条与鸿蒙无关、但该让 pi 知道的
WebUI 侧 `npm test` 在 **HEAD 上就是红的**(`test/background.test.mjs`):
- `浅色与深色两套令牌都定义了(自动反色)` —— 期望 `--nav-bg` 有浅/深两套,
但深色那套已在 `faacd3c`(修"导航栏还是黑色")里被有意去掉,
现在 `index.css` 只剩 `--nav-bg: 255 255 255 / 0.72`。
- `导航在壁纸模式下仍参与模糊` —— 期望 `html[data-bg='on'] .nav-rail` 里有
`backdrop-filter: blur(`,而该规则现在**是空的**("模糊由壁纸层负责,这里不叠",
见 `index.css:892`)。
两条判据编码的都是**已被有意回滚的设计**,判据没跟着改。红成了常态,
判据也就不再是判据 —— 这条纪律问题比色值本身更值得先修。
### 5.4 修订后的分期与"点它"判据
| 期 | 内容 | 判据(必须点到用户会点的那一层) |
|---|---|---|
| P1.5 ✅ | 裸色值清零 + 判据按类挡 + 权限档位配色统一 | 判据 7/7 绿 + 变异能判红 + 构建成功 |
| P2a | 信息架构:「通信」一项,内部页签 收件箱/发件箱/授权(未读红、待决策橙徽标) | **点页签 → 断言落到哪个 pane**(不是断言页签个数);点「通信」回到上次的子页签 |
| P2b | 发件箱页:`GET /me/mail/sent`,复用列表项 | 能看到已发邮件、点开进详情、空态有说明 |
| P3 | 授权页:`GET /permission/pending` + `POST /permission/decide` | 未决口径与 WebUI 一致(无 `permission_result`);点同意/拒绝后状态立刻变;拒绝可填备注且**备注送达模型**;决策请求已过期(`expired`)必须当场说清"这次批准不会恢复原调用" |
| P4 | 主题/壁纸(`/me/appearance`) | 换账号外观跟随;服务端无记录时以本地为准,不用默认值覆盖 |
| P5 | 悬浮玻璃导航(取代系统 TabBar) | 模糊只由壁纸层负责;列表项每项一张卡;命中区 ≥44vp |
| P6 | 日历(`/calendar/events`,含 ics 导入导出) | 手势阈值与 WebUI 一致(水平 ≥40px、≥1.5× 垂直、<600ms) |
**视觉/交互怎么验**:本机有 `harmony-emu`(实例 `HarmonyPhone`,hdc 在
`/opt/harmonyos/ohos-sdk/linux/toolchains`),能起来就能截图 + 真点,
把"编译通过、视觉未验"变成"点过、有截图"。
**实测(2026-09-14)**:当前文件沙箱(workspace-write)下起不来 ——
`harmony-emu start` 卡在等一个永远不来的 hdc(脚本要写 `/run/harmony-emulator.pid`),
直启 `Emulator -start HarmonyPhone -noWindow` 则报
`Error opening logfile /root/.Huawei/Emulator/deployed/HarmonyPhone/Log/qemu.log: Permission denied`
(实例目录在 `/root` 下,沙箱不许写)。**要跑真机/模拟器判据,得先放宽文件权限
(或换有设备的机器)**;在那之前,鸿蒙侧的判据只能到"构建通过 + 结构/令牌一致",
观感与点击必须如实标注未验 —— 不许写成已完成。
### 5.5 待定(需要 pi 或用户一句话)
1. **「会话」这个平级入口留不留?** WebUI 没有它(收件箱按会话分组)。
我倾向跟 WebUI 一致 —— 收件箱按会话折叠、去掉平级「会话」tab;
但这是删入口,改之前要一句话确认。
→ **已答(见 5.6):不是删功能,是两个 tab 合成一个页面的两种视图;先补视图与折叠,再删 tab。**
2. 新增令牌 `accentStrong / warnBg / warnFg / overlay` 取的都是 WebUI **已有**的
tailwind 值(blue-700 / amber-50 / amber-700),WebUI 侧不需要动代码;
`overlay` 是唯一没有对应物的(WebUI 弹层不压遮罩)。若 pi 认为 WebUI 侧也该
立同名令牌,我再改。
→ **已答(见 5.6):WebUI 侧不立同名令牌;鸿蒙侧的 `overlay` 拆成 `overlayColor` + `overlayAlpha`。**
---
## 六、pi 的回复已落地(dsh,2026-09-14 下午)
### 6.1 「会话」入口的结论(pi 作为 WebUI 侧负责人)
不是删功能,是两个 tab 合成**一个页面的两种视图**:**联系人页就是会话列表**
(`ContactPanel.tsx` 有列表 / 卡片两个视图 —— `ContactRow` ≈ 现在的 ContactsTab;
`WorkCard`「工作列表」≈ 现在的 SessionsTab:主题、最新一封摘要、权限档位徽标、
往返预算 = 现在的 `used_rounds/max_rounds`),而**收件箱里会话是组织方式**
(`mailGroups.ts` 的 `groupMailsBySession()` 按 `session_id` 折叠、组头取最新一封,
`isFlatGroup()` 让单封不成组)。
落地顺序(原样采纳):
1. **先**给「联系人」页补列表 / 卡片切换,卡片视图承接预算条 / `status` / 对方 Agent;
收件箱加**按会话折叠**(组头带未读)。折叠前先看 `total` —— 别让"只取了 50 封"
被折叠伪装成"只有这么多会话"。
2. **两件都到位后**再删平级「会话」tab。在那之前删 = 丢信息:轮次预算、`status`、
`from_agent` 会无处可看,而"预算跑满"正是需要人介入的信号。
合并后导航正好是 **通信 / 日历 / 联系人** 三项,P4–P6 都在这个形态上做。
⇒ 分期顺序调整为:**P2a 联系人双视图 + 收件箱按会话折叠 → P2b 通信页签(收件/发件/授权)
→ P3 授权页 → P4 主题壁纸 → P5 悬浮玻璃导航 → P6 日历 → 最后删「会话」tab**。
### 6.2 遮罩令牌
`overlay` 拆成 `overlayColor` + `overlayAlpha`(WebUI 是 `--bg-scrim` 色 + `--bg-dim`
透明度的两段式;色要能随主题换向,焊死成一个 `#AARRGGBB` 等于把枚举写回代码)。
ArkUI 只认单值,故用 `Theme.overlay()` 组装。**WebUI 侧不立同名令牌**(立了没人用、
判据只能验"它存在",那是自证)。判据第 8 条钉住这个形态。
### 6.3 那两条红判据的 patch:已落地,且验证过能判红
`test/background.test.mjs` 第 18 组三条按 pi 的 patch 重写:标签改回它真正断言的东西,
两条方向相反的断言取代原两条(`.dark` 不许单独给导航换色 / 模糊只由壁纸层负责)。
变异验证:
- 往某个 `.dark { }` 里塞一行 `--nav-bg: 15 23 42 / 0.72;` → **红**;
- 往 `html[data-bg='on'] .nav-rail` 里塞 `backdrop-filter: blur(18px);` → **红**;
- 撤回 → 绿。背景判据 32 → **34 通过**。
### 6.4 顺手发现并修掉的真 bug(构建只在日志里警告的那种)
`src/background-takeover.generated.css` 的**头注释提前闭合**:生成器在注释里写了
「src」加「/」加两颗星加「/」加「.tsx」,其中那对「星号 + 斜杠」把 CSS 注释就地结束,
剩下的尾巴变成 CSS 正文、并与第一条规则的选择器连在一起 ⇒ 非法选择器 ⇒
**`.bg-amber-100` 那条接管规则被浏览器整条丢掉**(壁纸模式下它不再变半透明)。
构建只给一条 `[WARNING] Unexpected "14" [css-syntax-error]`,不报错、不影响构建。
改在生成器 `scripts/gen-background-takeover.mjs`(注释里只描述、不写 glob 字面量)并
重新生成:压缩输出现在以 `html[data-bg=on] .bg-amber-100{` 起头、14 条规则全在、无告警。
另加两条判据("头注释没提前闭合" + 自检)。
### 6.5 一条判据从来没被跑过
`cross-client-theme.test.mjs` **不在 `npm test` 链里**(`npm test` 只跑
markdown-xss / narrow-layout / theme / background / packaging + vitest,
而 vitest 只收 `test/components` 与 `test/stores`)—— 那条"防漂移判据"从没在默认套件
里跑过。已加进 `npm test`。
### 6.6 `npm test` 现在全绿(退出码 0)
窄屏布局全通过、主题 30、背景 34、cross-client 8、packaging 3、vitest 258 用例 / 15 文件全过。
其中 **packaging 第 3 条此前是红的**,但它**不是判据过期,是安装包真的落后于 dist**
(`release/linux-unpacked/resources/app.asar` 里装的还是旧前端)。根因有点讽刺:
`npm test` 的 `&&` 链一直在 background 那条就中断,**packaging 从来没跑到过**。
已 `npm run build` + `npx electron-builder --linux -c.electronDownload.isVerifyChecksum=false` 重打包。
⚠️ **deb 目标在本机打不出来**(`fpm` 的 portable ruby 在 `Dir.chdir` 处退出),
AppImage 与 `linux-unpacked` 正常 —— 交付前要确认这个环境问题不影响目标平台。
### 6.7 WebUI 侧同源残留(pi 指出的第三处,已修)
`Sidebar.tsx` 底部的**主题切换 / 退出登录**按钮原为
`text-chrome-400 hover:text-white hover:bg-chrome-800`、外层分隔线 `border-chrome-700`
—— 那是"框架本来就该深"的假设。导航改白玻璃后 `chrome-400` 在白底上约 2.6:1
(图标要 3:1),hover 还会在白导航上闪出一块近黑。已换成 `.nav-item` + `border-gray-200`。
**未加"Sidebar 里不许有 chrome-*"的判据**:`Sidebar.tsx` 另有一处
`bg-chrome-600 text-chrome-100` 是实心小色块(正常用法),一刀切会误红。
### 6.8 仍未验的
鸿蒙侧**视觉与点击**仍未验(模拟器在本机沙箱下起不来,见 5.4)——
本轮的鸿蒙改动只有颜色令牌,判据能覆盖;**到 P2a 一定要有人眼或设备**,
否则"点页签落在哪个 pane"这条判据无法证明。
---
## 七、P2a 落地(dsh,2026-09-14 稍晚)
按 pi 给的顺序:**先补视图与折叠,再删 tab**。本轮做完前一半,tab 保留。
### 7.1 做法:把"用户真正会点的那一层"的内核抽出来,让判据**执行**它
没有设备,"点一下"在鸿蒙上暂时无法自动验。应对不是编个能过的新判据,
而是把会点的那一层的内核抽成纯逻辑文件
`client/harmony/entry/src/main/ets/model/MailGrouping.ts`(无 UI 依赖),
判据用 node 的 `--experimental-strip-types` **直接跑同一份代码**:
- `client/electron/test/harmony-logic.test.mjs`(14 条)—— 断言的是**行为**:
折叠后组头是不是最新一封、单封是不是不成组、同一时刻是否用 `mail_id` 倒序兜底、
时间解析失败会不会让顺序依赖入参、多账号同名会话会不会被错并、预算剩 1 个来回是哪档。
- 页面那一层用源码判据钉"确实调了这些函数"(`groupMailsBySession` / `isFlatGroup` /
`toggleExpanded` / `budgetLabel` / `nextContactView` / `lastFromIsHuman`)——
两层合起来,「逻辑对」与「页面接上了」都有判据。
- **变异验证(4 处,全部判红)**:去掉组内排序 → 2 条红;预算阈值 `<=1` 改 `<1` → 1 条红;
分组键去掉账号前缀 → 1 条红;页面不再区分单封组 → 1 条红。
这条判据已接进 `npm test`(`node --experimental-strip-types --no-warnings --test`)。
### 7.2 收件箱:按会话折叠
- 组头取组内**最新一封**的别名与主题(与 WebUI `mailGroups.ts` 同口径),
带未读数徽标与「N 封」;点组头展开/收起。
- **单封不成组、平铺**(与 WebUI `isFlatGroup` 同结论):给孤立的一封信套组头
只是多一次点击,而收件箱里大多数人类来信就是孤立的一封。
- 多账号是鸿蒙特有:分组键带账号前缀(同一 `session_id` 出现在两个账号是两件事);
`session_id` 缺失时按 `mail:<id>` 各自成组。
### 7.3 顺带修掉一个"看起来是总数、其实是未读数"的显示
`/me/mail/inbox` 返回的 `total` 是 **`CountUnread`(未读总数)**,不是总封数
(`server/internal/handler/me.go` 的 `MeGetInbox`)。鸿蒙底部原来写「共 N 封」,
于是同一屏上会出现「共 7 封」和「未读 7」这种自相矛盾的两行字。
改法:未读数**改用服务端 total**(权威,原来数这一页会少报);
「共 N 封」改成「已加载 N 封」;并且**这一页取满(=50)时如实提示
「已加载 50 封(本页上限 50,可能还有更多)」** —— 客户端手上根本没有可信的总封数,
那就不能把 50 封说成全部(这正是 pi 提醒的"别让只取 50 封伪装成只有这么多会话")。
WebUI 侧完全不读这个字段(`mailStore` 里没有 `total`),所以这条只影响鸿蒙。
### 7.4 联系人页:补上卡片视图(为撤 tab 做准备)
- 右上角切换列表 / 卡片,标题随视图变(「联系人」/「工作列表」,与 WebUI 同词);
切换规则在 `nextContactView()`,判据直接执行它。
- 卡片(对应 WebUI 的 `WorkCard`):Agent 名 + 工作目录 + 未读徽标、
会话别名(空则「(未命名会话)」)、**主题当主角**、最新摘要 + 人/Agent 标记
(`lastFromIsHuman`)、「N 封 · 时间」、权限档位徽标、**往返预算条**
(档位与 WebUI `BudgetChip` 同一判据:剩 0 红 / 剩 ≤1 橙 / 其余中性;上限 0=不限则不显示)。
- 平级「会话」tab **暂时保留** —— 预算、`status`、`from_agent` 现在卡片视图里都能看了,
但按 pi 的顺序,删 tab 排在 P2b 之后、作为独立一步(撤早了会丢信息)。
### 7.5 本轮验证与如实标注
- `hvigorw assembleHap` **BUILD SUCCESSFUL**(`.ts` 纯逻辑模块能被 `.ets` 引用,
实测可行 —— 这是"判据能执行同一份代码"的前提)。
- `npm test` **退出码 0**:窄屏布局全通过、主题 30、背景 34、cross-client 8、
harmony-logic 14、packaging 3、vitest 258/258。
- **视觉与点击仍未验**(无设备 / 模拟器起不来):折叠展开的手感、卡片间距、
组头命中区是否够大,这些**没有**任何自动判据能代替人眼 —— 交付时按"结构/逻辑已验证、
观感未验"写。下一轮:P2b 通信页签(收件箱/发件箱/授权 + 徽标)。
### 7.6 模拟器为什么仍然起不来:权限门是"无人可批准"
7.5 的"无设备"这次查到根上了。DevEco CLI 的说明(`deveco-cli` skill)确认本机
**有**一个按需启动的模拟器实例 `HarmonyPhone`(KVM,`harmony-emu start` 即可,
起来后 `devecocli ui layout / click / screenshot` 能做**真正的点击级验证**)。
但 `harmony-emu start` 要把 PID/日志写到 `/run` 与 `/root/.Huawei`(工作区之外),
在 workspace-write 沙箱下被拒;按规矩用 `sandbox_permissions` 升级重试一次,得到的是:
无法执行 bash:权限询问无法送达:该任务链上没有人类用户
也就是说:**鸿蒙的点击级验证在这条链上不是"还没做",而是"当前做不到"** ——
需要人类在命令行里跑一次 `harmony-emu start`,之后 `devecocli ui` 就能自动点。
在那之前,鸿蒙侧任何界面改动的验收口径只能是
「逻辑有可执行判据 + 页面接线有判据 + 观感未验」,**不能**写"已完成"。
(给上游的可执行请求:在有人的环境里执行
`harmony-emu start && cd client/harmony && hvigorw assembleHap && hdc install entry/build/default/outputs/default/entry-default-unsigned.hap`,
即可让 P2a 之后所有阶段的"点击级判据"落地。)
### 7.7 顺手把"判据套件"本身修可信(同一族问题的总账)
这轮撞出来的问题里,有一类是**判据自己不会跑**,比判据写错更隐蔽(输出看起来一切正常):
1. `cross-client-theme.test.mjs` 从来不在 `npm test` 链里(pi 已认领);
2. 有人新加的 4 条玻璃判据写在 `process.exit()` **之后** —— 一条都不执行、不计通过也不计失败;
3. `nav-merge.test.mjs`、`build-stamp.test.mjs` 写好了**也没接线**(各 8 / 4 条判据从未跑过);
4. `npm test` 是 `&&` 链:**前面红一条,后面全部不跑**(`packaging` 那条因此长期隐身)。
改法(`test/run-all.mjs`,pi 建议的"全跑完再算退出码"):
- 每条判据都跑,红的收集起来最后一起报、一起退出;
- **自检 1**:清单里的文件必须存在(写错名字 = 一条判据静默消失);
- **自检 2**:`test/` 下每个 `*.test.mjs` 都必须在清单里 —— **新增判据忘了接线直接红**。
这条自检当场就抓出上面第 3 条那两个文件。
- 变异验证:把 `theme.test.mjs` 强制红 → 后面 5 条判据照跑,汇总如实报"红 1/9"、退出码 1。
接线后又立刻暴露出两条**陈旧判据**(代码没错、判据钉的是旧写法),已按"钉行为不钉字面"修好:
- `setViewMode(target || commTab` → 代码后来等价改写成
`target ?? (isComm ? commTab : modes[0])`;改成钉"isComm 时落到 commTab"这个行为;
- `MailView.tsx` 里 grep `glass-control` → 授权多选胶囊抽成了共用组件 `ComposerChip`,
样式其实是对的(neutral 未选中态就是 `glass-control`);改成钉
"MailView 用 ComposerChip" + "ComposerChip 用控件档"。两条都做了变异验证(改坏必红)。
按 pi 的提议还给生成产物补了一条判据:用 **postcss 真解析** `background-takeover.generated.css`,
断言**每条规则的选择器形状**都恰好是 `html[data-bg='on'] .bg-xxx` 且**规则条数与清单一致** ——
把"生成器写坏产物"从"只有浏览器能发现"变成"跑判据就红"。
(注意:只验"能解析"不够 —— 实测 postcss 对当年那份坏产物照样解析出 1 条规则,
只是选择器前面粘上了注释的尾巴;所以卡的是形状与条数。)
套件现状:`npm test` = `node test/run-all.mjs && vitest run`,9 个判据文件全绿
(markdown-xss / narrow-layout / nav-merge / theme / background 42 / cross-client 8 /
harmony-logic 14 / build-stamp 4 / packaging 3)+ vitest 258/258。
### 7.8 deb 那条结论要撤回:不是 fpm,是 `/tmp` 满了
pi 问的 deb 复测有结果了:`TMPDIR=/var/tmp/ebtmp` 在本沙箱里**建不出来**
(工作区外不可写),但把 TMPDIR 指到工作区大盘后 **deb 打出来了**:
`release/agentmail-web_0.1.0_amd64.deb`(约 100MB)。
真因不是权限也不再是 fpm:日志里的关键行是 **`Errno::ENOSPC`** —— fpm 会把
`release/linux-unpacked`(291MB)整份复制进 `TMPDIR`,而本机 `/tmp` 是 9.8G 的 tmpfs、
被 `/tmp/gocache`(4.5G)等占到 99%(仅剩 ~100MB 可用)。
所以「本机打不出 deb」这个结论**撤回**:它是环境症状,不是工具链缺陷,
deb 也不必从 targets 里摘。已写进 `client/electron/BUILD.md`(含排查命令)。
顺带(给所有 agent 的提醒):本机 `/tmp` 已 99% 满,`hvigor` 之类的构建会把它进一步挤爆;
大产物构建请把 `TMPDIR` 指到工作区所在盘。