pi 读 WebUI 源码后发现两处两边不一致,都处理了。这一提交同时改 harmony 与 electron,故自报家门。
## 1 预设档遮罩:**补上**(选"与 WebUI 一致",因为那服务的是可读性)
pi 的证据:WebUI 的 `applyBackground()` **无条件**写 `--bg-dim`(默认 24),
`.app-backdrop::after` 是 `rgb(var(--bg-scrim) / var(--bg-dim))` —— **遮罩不区分档位**;
它的注释写着目的「背景越花,正文越需要一层遮罩才读得动」。
而鸿蒙当时只在 image 档压(`resolveBackground` 里那行注释还写着"preset 档不用"),
且我们已经让出了页面底 → 正文直接压在原色渐变上,**比 WebUI 更艳更亮、更不好读**。
现在两档用**同一个浓度**(同一个服务端字段),页面在预设分支的渐变之上加一层
系统遮罩色 × 浓度(浅色由系统洗白、深色压黑,不自己写 alpha)。
判据:旧断言"preset 档不压暗"**反过来**(留着理由),另加一条钉"两档同一浓度"+
"两处遮盖层都在"+"WebUI 确实无条件写 --bg-dim"(这条差异有据可查)。
变异:预设档不压 → 红 2 条;页面预设分支的遮盖层被删 → 红。
## 2 多账号缓存键:**鸿蒙是对的,不许退回**(pi 点名)
WebUI 的键是全局常量 `agentmail.background`,后果是切到服务端没有记录的账号时
`saved=false` 分支会把**上一个账号的外观** push 上去(新账号"继承"了外观,还写进了服务端)。
这条差异进 §7.12「有意差异」表,**明确写"鸿蒙是对的"**,
判据防的就是"将来有人为了两边一致把它改回去":取键函数的**正文**里必须拼账号 id
(按块取,不是看调用点出现过 `accountId` 就当数 —— "判结构要配对/解析"那条对我自己也适用),
且不许出现 WebUI 那个全局键。变异:`prefKey` 去掉账号 → 红 2 条。
## 3 "未做" → 按 pi 的三档口径改成**已知不一致**,并把系统侧的接法做掉
pi 指出这不是"没做":色板确实按主题算了,只是没接环境变化事件 ——
现象是运行期切系统深浅色时"系统语义色/材质立刻跟随、我们自己算的色板不重算"的**撕裂**。
他给的规则我记成了通用规则(会咬到 P5/P6):**系统自动跟随的东西不会顺带把
"我们自己计算/缓存的值"一起更新** —— 凡随主题变化的自算值都要挂在**同一个主题变化事件**上。
照做:`applicationContext.on('environment')` → `onConfigurationUpdated` 里
**只在 `colorMode` 真换向时**重算(`applyAppearance()`),页面销毁 `off` 退订。
状态 = **未验**(只有真机/模拟器能验:切一次系统深浅色看预设是否跟着换)。
SDK 锚点与两个编译错都记进 §7.17b:`EnvironmentCallback.onConfigurationUpdated(config: Configuration)`
(`Configuration` 从 `@kit.AbilityKit` 取,**不在** `common` 命名空间下)、
`Configuration.colorMode` 是**枚举 | undefined**(字段写成 `number` 直接编译失败)、
`on('environment')` 返回 **number 型 callbackId**。变异:不订阅 → 红。
## 4 文档
- §7.12 加两行:缓存键(含 WebUI 侧开项)、遮罩浓度默认值那两套(`24/8` vs `12/4`,
按 `clamp` 的 12/4 对齐,因为服务端缺字段时落地的是它)。
- §四 验收纪律改成**四档口径**:没做 / 未验 / 已知不一致(要写触发条件与现象)/
机制上确定不同(必须判),并把 pi 那条"自算值必须挂主题事件"的可复用规则写进去。
## 验证
`hvigorw assembleHap` BUILD SUCCESSFUL;`npm test` 退出码 0
(12 个判据文件全绿 + vitest 258/258;harmony-appearance 20 → 23 条)。
854 lines
63 KiB
Markdown
854 lines
63 KiB
Markdown
# 鸿蒙客户端与 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` 写死)。
|
||
|
||
## 二·五、做法上的一条硬要求:**用系统方案**
|
||
|
||
用户(2026-09-14):「鸿蒙也同步,但是**鸿蒙要求用系统方案**」。
|
||
|
||
含义:能交给系统的就交给系统 —— 用 ArkUI 的组件、材质与语义资源,
|
||
而不是把 WebUI 那套"手写 rgba + 自定义模糊 + 自制卡片"照搬过来。
|
||
系统材质会跟随深色模式、动效曲线与无障碍设置,手写的那套不会。
|
||
|
||
| WebUI 做法 | 鸿蒙应该用 |
|
||
|---|---|
|
||
| 手写 `rgba(...)` + `backdrop-filter` | `backgroundBlurStyle(BlurStyle.*)`(系统材质) |
|
||
| 自制圆角导航条 | 系统 `Tabs` + `TabBar`(`barBackgroundBlurStyle`) |
|
||
| 自制 `Scroll` + `Column` | 系统 `List` / `ListItem`(`divider` / `swipeAction`) |
|
||
| `.glass-card` 手写边框阴影 | `.borderRadius()` + `.backgroundBlurStyle()` + `.shadow()` |
|
||
| 自己切 CSS 变量做深色 | 系统语义色 `$r('sys.color.*')` 自动跟随 |
|
||
| 自定义 transition | `animateTo` / `transition` / `geometryTransition` |
|
||
|
||
⚠ **影响既有判据**:`cross-client-theme.test.mjs` 目前钉的是三个**取值**
|
||
(品牌蓝 / 圆角 14 / 导航玻璃 0.72)。改用系统资源后它应当变红 ——
|
||
那是预期的,届时要把它从"取值相同"改成"**意图相同**"(品牌蓝仍须一致;
|
||
材质与圆角允许各自跟随系统)。改判据需 WebUI 侧(pi)一起改。
|
||
|
||
## 三、分期(按"能独立验收"切)
|
||
|
||
**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. 无法验证的要**如实标注**(例如"编译通过、视觉未验"),不能写成"已完成")。
|
||
⚠️ **四档口径**(pi 2026-09-14 定,这轮每一条都踩过):
|
||
- **没做** —— 功能不存在。不许写成"未验"(那会让人以为只剩观感风险,实际那里什么都没有);
|
||
- **未验** —— 做过、没看结果(例如"编译通过、视觉未验");
|
||
- **已知不一致** —— 做了,但**某条件下会露出破绽**:必须写清**触发条件与现象**
|
||
(例:"运行期切系统深浅色时,系统语义色/材质立刻跟随,而我们自己算的预设色板不重算
|
||
→ 那一刻一部分跟随、一部分不跟随")。这一档最容易被误写成"没做",
|
||
而两者的风险读法完全不同;
|
||
- **机制上确定不同** —— 可判的差异,**必须判**、不许记成"未验"(深色档预设那次就是)。
|
||
|
||
**可复用规则**(会咬到 P5/P6):**系统自动跟随的东西(语义色、材质)不会顺带把
|
||
"我们自己算出来的值"一起更新** —— 凡是我们计算/缓存且随主题变化的值,
|
||
都必须挂在**同一个主题变化事件**上重算,否则它迟早是那唯一一处不跟随的。
|
||
4. **判据怎么写**:见 `client/electron/test/CRITERIA.md` —— 那份规范管**两个客户端**的判据
|
||
(判结构与行为、不判字面与邻接;清单与 allow-list 的形状;变异要红在预期位置;
|
||
"判据自己不会跑"那个家族的六种宿主)。**写在 electron 的测试目录下只是因为
|
||
`run-all.mjs` 在那儿强制它存在**,鸿蒙侧改判据前先读它。
|
||
|
||
---
|
||
|
||
## 五、交接核对(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**(不是断言页签个数)—— 页签状态机判据已落地(见 §7.15) |
|
||
| P2b ✅ | 发件箱页:`GET /me/mail/sent`,复用列表项 | 判据:接口路径、行上主角是收件人、空态有说明(主句与 WebUI 逐字一致) |
|
||
| P3 ⚠️ 主体完成 | 授权页:`GET /permission/pending` + `POST /permission/decide` | 未决口径与 WebUI 一致(无 `permission_result`)✅;拒绝可填备注且备注送出 ✅;`expired` 当场说清"这次批准不会恢复原调用" ✅。**未验**:真机上点同意/拒绝后状态是否"立刻变"(判据只钉到"决策后重新拉列表"这一层) |
|
||
| P4 ✅(P4c 上传除外) | 主题/壁纸(`/me/appearance`) | 换账号外观跟随 ✅(缓存键带账号);服务端无记录时以本地为准 ✅(§7.16);**预设 6 档都能画出来** ✅、图片壁纸渲染 ✅(§7.17 —— 这一版补的,第一版只有数据没有画面)。**未做**:P4c 上传入口。**未验**:真机观感(配色/对比) |
|
||
| P5 ⬜ 未做 | 悬浮玻璃导航(取代系统 TabBar) | 模糊只由壁纸层负责;列表项每项一张卡;命中区 ≥44vp。现状:底栏仍是系统 `Tabs`,只有自绘的 tabBar builder 带了 `backgroundBlurStyle`(§7.10) |
|
||
| P6 ⬜ 未做 | 日历(`/calendar/events`,含 ics 导入导出) | 手势阈值与 WebUI 一致(水平 ≥40px、≥1.5× 垂直、<600ms)。**有意排序**:入口与内容一起上,不留空页签(§7.15) |
|
||
|
||
**视觉/交互怎么验**:本机有 `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`(**19 条**)—— 断言的是**行为**:
|
||
折叠后组头是不是最新一封、单封是不是不成组、同一时刻是否用 `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` 指到工作区所在盘。
|
||
|
||
### 7.9 P2a 收尾:撤掉平级「会话」tab + 权限档位的"强制力"(鸿蒙侧)
|
||
|
||
- **撤 tab**:底部只剩 **收件箱 / 联系人** 两个平级页签。依据"信息没丢":
|
||
会话列表独有的字段现在落在两处 —— 收件箱按会话折叠(组头就是会话),
|
||
联系人页的卡片视图是会话的进度视角;其中 `status`(active/archived) 与 `from_agent`
|
||
**参考实现也不显示**,判据里有一条"卡片字段集与 WebUI `WorkCard` 一致"钉住这件事
|
||
(两边字段集**完全相等**,多一个少一个都红)—— 哪天 WebUI 补上了,这条会红,提醒跟着补。
|
||
- **权限档位 + 强制力**:卡片上的徽标改成"**档位 + 强制力标记**",点它弹出说明。
|
||
WebUI 把说明放在 `title`(悬停提示),**手指没有悬停** —— 所以鸿蒙拆两步:
|
||
标记形状当场可辨(`●` 平台强制 / `◉` 覆盖不完整 / `○` 仅提示),点一下用 toast 说完整那句话。
|
||
三条纪律落进判据:
|
||
1. 档位标签、强制力标签与 WebUI 的 `MODE_LABEL` / `ENFORCEMENT_LABEL` **逐字一致**;
|
||
2. **说明文案从 WebUI 源码里抽出字符串逐字比对**(9 种组合全覆盖)——
|
||
两个客户端对同一个任务不能给两种保证;
|
||
3. 认不出的强制力归一到 `advisory`(保守方向:绝不当成"平台拦得住"),
|
||
且空/未知必须说"仅提示"。
|
||
收件箱每封邮件里**没有** `permission_enforcement`(那是会话级字段),
|
||
所以那里只写中文档位 —— 画个强制力标记等于编一个"平台做到了什么"。
|
||
|
||
**两个"判据自己不可信"的坑,本轮各修一次(都是变异测试逼出来的)**:
|
||
|
||
- 断言一律读**剥掉注释的源码**:把 `showToast` 注释掉,正则照样匹配 —— 注释里有某个调用,
|
||
证明不了那个调用存在(`cross-client-theme` 里遮罩那段早有同款教训)。
|
||
- "在回调里"不能靠正则窗口:`onClick` 体掏空、或把 toast 挪到相邻的 `onHover`,窗口式正则会**放过**。
|
||
改成**括号配对**取那个 `onClick` 的 `{...}` 体,只在里面找。两次变异现在都判红。
|
||
|
||
### 7.10 下一段:改用"系统方案"(jianf 追加要求)
|
||
|
||
用户原话:「鸿蒙也同步,但是**鸿蒙要求用系统方案**」(pi 转达,见 §二·五 的对应表)。
|
||
我这边**同意这个理解**,并且补两条可执行的做法:
|
||
|
||
1. **能给系统的全给系统**:`backgroundBlurStyle(BlurStyle.*)` 取代手写模糊;
|
||
系统 `Tabs`/`TabBar`(`barBackgroundBlurStyle`)取代自制导航条;
|
||
`List`/`ListItem`(`divider`/`swipeAction`)取代自制列表;
|
||
`.borderRadius()`/`.shadow()` 取代手写卡片;`animateTo`/`curves` 取代自定义动画。
|
||
2. **语义色优先 `$r('sys.color.*')`**,而且**名字是可以离线校验的**:
|
||
SDK 里带着系统资源名表
|
||
`sdk/default/openharmony/toolchains/id_defined.json`(本机 API 26 版本,7826 条)。
|
||
实测该表里正好有这批语义色:
|
||
`ohos_id_color_list_card_bg`(**列表每项一张卡**的底色)、`ohos_id_color_list_separator`、
|
||
`ohos_id_color_background` / `_sub_background`、
|
||
`ohos_id_color_text_primary` / `_text_secondary` / `_text_tertiary`、
|
||
`ohos_id_color_emphasize`(强调色)、`ohos_id_color_warning`、`ohos_id_color_alert`、
|
||
`ohos_id_color_mask_light/regular/thick`(遮罩)、
|
||
`ohos_id_blur_style_component_*_color`。
|
||
⇒ 本轮先做成**判据**:源码里出现的每个 `$r('sys.color.*')` / `$r('sys.float.*')` 名字
|
||
都要在这张表里查得到 —— 因为**没有设备**,名字写错在运行前根本发现不了,
|
||
这条判据把它变成构建期就红。做替换之前先把这条判据立起来,再逐处替换。
|
||
3. **判据怎么跟着改(已与 pi 对齐口径)**:`cross-client-theme.test.mjs` 现在钉的三个**取值**
|
||
(品牌蓝 `#2563EB` / 圆角 14 / 导航玻璃 0.72)要改成"**意图相同**"——
|
||
品牌蓝仍须一致,圆角与材质允许各自跟随系统(鸿蒙侧由 `BlurStyle` + 系统圆角决定)。
|
||
**改之前先跟 pi 说一声,两侧一起改**(他明确要求,避免各改一半)。
|
||
本轮新增的那 6 个预算条取值(gray/red/orange 三档)同属这批,一起改。
|
||
4. 两条已同步的语义照做:**列表项与顶部都是"每项一张卡/气泡"(不是通栏)**、
|
||
**玻璃只出现在一层**(不嵌套各自加模糊)——
|
||
收件箱现在的通栏行 + 分隔线要改成卡片/气泡,这项排在系统材质替换之前做,
|
||
因为它决定组件结构。
|
||
|
||
### 7.11 「用系统方案」的第一块地基:系统资源名**离线可校验**(判据先于替换)
|
||
|
||
没有设备,`$r('sys.color.写错了')` 编译期不报、只有真机运行到那一行才炸 ——
|
||
"用系统方案"会变成一块谁都验不了的区域。所以先把校验立起来:
|
||
`client/electron/test/harmony-system-api.test.mjs`(4 条,已接进 `run-all`):
|
||
|
||
- 源码里每个 `$r('sys.<type>.<name>')` 都必须在 SDK 名表
|
||
`sdk/default/openharmony/toolchains/id_defined.json` 里**存在且类型相符**;
|
||
- 每个 `BlurStyle.<MEMBER>` 都必须在 SDK 的 `declare enum BlurStyle` 里;
|
||
- 替换计划里要用的那批系统色**先核过再写代码**(`ohos_id_color_list_card_bg`、
|
||
`_list_separator`、`_background`/`_sub_background`、`_text_primary/secondary/tertiary`、
|
||
`_emphasize`、`_warning`、`_alert`、`_mask_regular`);
|
||
- 名表里没有 `brand` / `confirm` / `success` —— 这条**边界也钉住**:
|
||
权限三档、预算三档这些**业务语义色没有系统对应物**,继续用自定义令牌,
|
||
不许"为了系统化"把 plan 档画成 warning 色。
|
||
|
||
两条纪律写进判据本身:SDK 名表找不到时**判红并说明**(会静默跳过的判据等于没有),
|
||
以及变异验证(拼错色名 `list_cad_bg` → 报"查无此名";写错 `BlurStyle.COMPONENT_不存在` → 报可用取值;
|
||
名字解析精确到 `,`/`=` 分隔符 —— 早先的宽松写法把文档里的 `T`、`R` 也算成了枚举成员)。
|
||
|
||
### 7.12 「有意差异」表(跨端判据从"取值相同"改"意图相同"的依据)
|
||
|
||
按 pi 的要求单列一张表:这些维度**允许两边不同**,因为它们各自跟随自己的平台。
|
||
判据(`cross-client-theme.test.mjs`)只钉"来源正确 + 差异被记录",不再钉取值相等 ——
|
||
但**品牌色不在表内**:它是跨客户端身份,必须逐字一致(`#2563EB`,单独一条判据钉住)。
|
||
|
||
| 维度 | WebUI | 鸿蒙 | 为什么允许不同 |
|
||
|---|---|---|---|
|
||
| 圆角 | 自声明 `--radius-card: 0.875rem` | 系统 `sys.float.ohos_id_corner_radius_card/button` | 系统圆角会随设备/主题/无障碍设置变;跟着系统才是"系统方案" |
|
||
| 材质(玻璃) | 自声明 `--nav-bg: 255 255 255 / 0.72` + `backdrop-filter` | 系统 `backgroundBlurStyle(BlurStyle.COMPONENT_THICK)` | 系统材质自带深浅两套颜色与模糊半径,手写 alpha 跟不了深色 |
|
||
| 动效 | 自定义 transition/时长 | `animateTo` + 系统 `curves` | 动效曲线应跟随系统设置(含"减弱动效") |
|
||
| 遮罩 | 自声明 `--bg-scrim` + `--bg-dim` 两段式 | 系统 `sys.color.ohos_id_color_mask_regular` | 遮罩要随主题换向(浅色洗白/深色压黑),这件事系统已经做了 |
|
||
| **品牌色** | `--c-blue-600: 37 99 235` | `Theme.accent = '#2563EB'` | **不允许差异** —— 两个客户端是同一个产品 |
|
||
| **本地外观缓存的键** | 全局常量 `agentmail.background` | **按账号**:`appearance.<accountId>` | **鸿蒙是对的,不许为"对齐"退回全局键** —— 全局键的后果是切到服务端没有记录的账号时,`saved=false` 分支会把**上一个账号的外观** push 上去(新账号"继承"了外观,而且写进了服务端)。pi 2026-09-14 确认这条记在 WebUI 侧为开项(换键,或至少在把继承来的值当"本地的"推给从未有记录的账号前停一下) |
|
||
| **遮罩浓度的默认值** | 两套:store 的 `dim=24/blur=8` 与 `lib/appearance.ts` 的 `clamp(...,12,4)` | 12 / 4(只有一套) | 服务端**缺字段**时真正落地的是 `clamp` 的默认值,所以按 12/4 对齐;WebUI 那两套值迟早要统一(pi 记在他那边) |
|
||
|
||
**关于 `overlayColor` / `overlayAlpha` 消失**(pi 要求把删除理由记在这里,否则下一个人会当成漏改补回来):
|
||
鸿蒙这边的模态走**系统弹窗**(`bindSheet` / 自绘 `Stack` 只做位置,遮罩本身用系统遮罩色),
|
||
所以"遮罩色 + 遮罩透明度"这两个自定值**整类**都不需要了 —— 系统遮罩色自带随主题换向
|
||
(浅色洗白 / 深色压黑)。保留的是**一个** `Theme.overlay`(值 = `sys.color.ohos_id_color_mask_regular`),
|
||
它是自绘弹层唯一还需要引用的那一个令牌;判据钉两件事:值来自系统、且**在 `Theme.ets` 之外确有使用点**
|
||
(只有声明没有使用 = 死令牌,pi 点过这条)。
|
||
|
||
**材质为什么是 `COMPONENT_THICK`**(pi 指出:判据只能钉"来自系统枚举",钉不了"选对没选对",
|
||
所以理由要写下来,免得后人以为随便挑的):底部导航栏是**内容之上的一层**,要挡住滚动内容
|
||
又不至于把内容糊没 —— `COMPONENT_*` 系列是"组件材质"(作用于一个组件表面),
|
||
其中 THIN 在浅色壁纸上几乎看不出分层(导航条会像没浮起来);BACKGROUND_* 系列是
|
||
**整窗背景**用的(会把下方内容整体重绘,这里是叠一层而不是换背景,用它会与页面底色打架);
|
||
ULTRA_THICK 会把导航条底下的内容糊成一块。所以取 COMPONENT_THICK。
|
||
**这条只有真机能判**,见下面「模拟器起来后第一个要看的项」。
|
||
|
||
### 7.13 系统方案替换(第一批)+ 跨端判据从"取值"改"意图"
|
||
|
||
**改了什么**(鸿蒙侧):
|
||
|
||
- `Theme.ets` 的表面/文字/分隔/遮罩/圆角**来源换成系统**:
|
||
`pageBg→ohos_id_color_background`、`surface→ohos_id_color_list_card_bg`、
|
||
`surfaceMuted→ohos_id_color_sub_background`、`border→ohos_id_color_list_separator`、
|
||
三级文字 `→text_primary/secondary/tertiary`、
|
||
`overlay→ohos_id_color_mask_regular`、`radiusCard/Control→sys.float.ohos_id_corner_radius_card/button`。
|
||
- **删掉手写玻璃**:`navBgLight='#B8FFFFFF'` / `navBgDark='#B80F172A'` 两个常量去掉,
|
||
换成 `navMaterial: BlurStyle = BlurStyle.COMPONENT_THICK`,导航条上
|
||
`.backgroundBlurStyle(Theme.navMaterial)` —— 深浅两套颜色与模糊半径由系统按主题给。
|
||
顺带把**遮罩**的那套"色 + 透明度"两段式也删了(`overlayColor`/`overlayAlpha`/`overlay()`):
|
||
当初拆两段就是为了"遮罩要能随主题换向",而这件事系统已经做了。
|
||
- **每项一张卡/气泡**:收件箱行、会话组头、联系人列表行都改成卡片
|
||
(圆角 + 卡片底色 + 行间距),联系人列表那条贯通分隔线删掉。
|
||
- 保留自己写的只有两类:**品牌色**(`accent = #2563EB`,跨客户端身份)与
|
||
**业务语义色**(权限三档、预算三档 —— 系统只有 warning/alert 两个情绪色,凑不出三档)。
|
||
|
||
**判据怎么改的**(与 pi 对齐后,两侧一起改;他要的 A/B/C 形状 + 品牌色防线):
|
||
|
||
| 判据 | 旧口径 | 新口径 |
|
||
|---|---|---|
|
||
| 品牌蓝 | 取值相同 | **不变**(唯一必须逐字一致的东西),另加"不得退化成 `$r('sys.color.*')`"的防线 |
|
||
| 圆角 | 两边都是 14px | 意图相同:WebUI 自声明令牌 + 鸿蒙来自 `sys.float.*` + 差异记录在 §7.12 |
|
||
| 导航玻璃 | 两边都是 0.72 | 意图相同:WebUI 自声明 alpha + 鸿蒙用 `BlurStyle` + 差异记录 |
|
||
| 遮罩 | 鸿蒙照 WebUI 拆"色 + 透明度" | 鸿蒙用系统遮罩色(换向由系统做),WebUI 仍两段式 |
|
||
| 裸色值 | 只扫 `#RRGGBB(AA)` | **四种形态一起扫**:`#RRGGBB(AA)` / `rgba(` / `0xRRGGBBAA`(pi 指出的洞) |
|
||
| 新增 A | —— | 系统拥有的维度**唯一来源**是 `$r('sys.*')`(且不得退回 string/number) |
|
||
| 新增 B | —— | 旧机制不得回来:`navBg*`、8 位半透明色、`rgb(`/`rgba(`、写死的 14/8 |
|
||
| 新增 C | —— | 玻璃位置必须调 `backgroundBlurStyle`,且**只许一层**(嵌套各加模糊是 pi 点名要避免的) |
|
||
|
||
**变异验证**(判据必须能红,红在哪也要对):
|
||
|
||
| 变异 | 结果 |
|
||
|---|---|
|
||
| 品牌蓝 → `$r('sys.color.ohos_id_color_emphasize')` | 红 3 条(含品牌色防线) |
|
||
| 手写玻璃 `navBgLight='#B8FFFFFF'` 回来 | 红 2 条 |
|
||
| 导航改用写死的半透明色、不调 `backgroundBlurStyle` | 红 2 条 |
|
||
| 卡片上再开一层模糊(嵌套玻璃) | 红 1 条:`玻璃应只出现在一处,实际 2 处` |
|
||
|
||
**未验**:这次替换的**观感**仍然没验 —— 卡片间距是否合适、系统材质在导航条上的实际效果、
|
||
深色模式下的观感,都只有真机才看得到(模拟器需人在命令行启动)。已验证的是:
|
||
`hvigorw assembleHap` BUILD SUCCESSFUL、13 条跨端判据 + 4 条系统资源名判据全绿、
|
||
四种变异都能判红;`sys.*` 名字全部对着 SDK 名表核过(判据持续盯着)。
|
||
|
||
### 7.14 顺手扫掉 23 处废弃 API(全局 `promptAction.showToast`)
|
||
|
||
SDK 里写着:`@ohos.promptAction.d.ts` 的全局 `showToast` 标 `@deprecated since 18`,
|
||
替代品是 `UIContext.getPromptAction()`。仓库里原有 **23 处**这种调用(历史遗留,6 个页面)——
|
||
既然这一轮在按"用系统方案"整理鸿蒙侧,就一次扫干净:
|
||
`promptAction.showToast(...)` → `this.getUIContext().getPromptAction().showToast(...)`,
|
||
并清掉不再需要的 import。
|
||
|
||
**判据**:不得再用全局写法(负向断言,自检里同时验"认得出旧写法"且"不误伤新写法")。
|
||
这条防的不是这次,而是**新增页面照抄旧代码**这条最常见的回退路径 —— 它编译照样通过。
|
||
|
||
(同一批整理里还没做的:全局 `animateTo` 也要走 `getUIContext().animateTo(...)`,
|
||
放到动效那一期一起改 —— 那一期本来就是"自定义 transition → 系统 `curves`"。)
|
||
|
||
### 7.15 P2b + P3 主体:把「收件箱」这一项改成「通信」(内部三栏 + 徽标)
|
||
|
||
用户(2026-09-14):「收件发件授权改为一个导航项,通过内部导航区分,然后新建作为他们
|
||
内部的一个悬浮的圆形加号」。所以这一期不是"再加两个页面",而是**对齐信息架构**:
|
||
|
||
- 底部第一项从 **收件箱** 变成 **通信**(`CommPage`),内部三栏 **收件箱 / 发件箱 / 授权**;
|
||
- 页签带**徽标**:收件箱红(未读)、授权橙(**待决策**)、发件箱无;>99 写 `99+`;
|
||
- 选中态用**下划线**(不是浮动白胶囊)—— WebUI 侧用户原话是「通信页面的二级页面与其他位置极其割裂」;
|
||
- **悬浮圆形加号**挂到通信页这一层(三个栏都要能新建),`⚙` 也搬到这一层
|
||
(否则切到发件箱/授权就够不到设置);
|
||
- **收件箱不再混权限邮件**:按 `splitByPermission` 分家,未读也按筛过之后算 ——
|
||
WebUI 侧实测过"一个会话的 17 封权限邮件挤掉另外两个会话"。
|
||
|
||
**P3 主体(授权栏)**:`GET /permission/pending`(不从收件箱筛,理由见 §5.2①)、
|
||
`POST /permission/decide`。两件如实说的事:**拒绝可填备注且备注必须送出**;
|
||
**请求过期**时当场说明「审批不会让那次调用继续」—— 否则人会以为 Agent 接着跑了。
|
||
|
||
**顺带清掉一处死代码**:收件箱里那个「写邮件」`bindSheet` 的 `composeVisible`
|
||
从来没被置为 true(**谁也打不开**),随新建入口改成悬浮加号一并删除。
|
||
|
||
**日历为什么还没有入口**:它的内容(P6:网格 + 事件读写 + 左右滑动翻页)还没做。
|
||
先放一个点进去空着的入口,比暂时没有入口更糟 —— 所以等内容一起上。
|
||
这是**有意排序**,不是漏做(记在这里以免被当成遗漏)。
|
||
|
||
**判据**(`harmony-logic.test.mjs`,28 条):
|
||
|
||
| 判据 | 说明 |
|
||
|---|---|
|
||
| 页签键与顺序 | 从 WebUI 的 `uiStore.ts` 抽出 `CommTab` 联合类型、从 `CommTabs.tsx` 抽出 `TABS` 标签,比**键集合与顺序**(两边不一致时"恢复上次页签"会悄悄退回) |
|
||
| 页签状态机 | 键↔下标往返;认不出的键/越界下标/非整数 → 回收件箱(不落在空白 pane 上) |
|
||
| 徽标 | 收件箱看未读、授权看**待决策**(不是权限邮件总数)、发件箱无;0 不显示;>99 写 `99+`;红/橙与 WebUI 的 `bg-red-600`/`bg-orange-700` 对应 |
|
||
| 分家 | 权限邮件不进收件箱;**决策过的不再算待决策**(空串与 null 都算未决策);收件箱未读不含授权栏 |
|
||
| 空态 | 主句与 WebUI 的「暂无邮件」逐字一致;三栏各有一句"为什么是空的",且三句互不相同 |
|
||
| 接线 | 底部第一项是「通信」;三个 pane 都真的渲染;悬浮加号是圆形且在通信页这一层;收件箱"先分家再折叠";接口路径/决策体三个字段;拒绝要传 `noteText`;过期分支必须说清后果 |
|
||
|
||
**变异验证**:授权徽标改成看未读 → 红;权限邮件不分出去 → 红;类型名拼错
|
||
(权限邮件全混进收件箱)→ 红;决策过的仍算待决策 → 红;收件箱不再分家 → 红 2 条;
|
||
发件箱空态去掉说明 → 红。
|
||
|
||
**判据自己抓到的真 bug**:`commTabFromIndex` 原来只判范围,`1.5` 会走到
|
||
`COMM_TABS[1.5]` → `undefined`(表现是"点哪都不亮")。范围检查挡不住非整数,
|
||
已加 `Number.isInteger`。
|
||
|
||
**未验**(**模拟器起来后第一个要看的项**:§7.12 里那个材质档次 —— 间距是数字,材质是判断):底部/内部页签在真机上的观感、悬浮加号的位置、徽标与文字的排版 ——
|
||
仍然只有真机(或模拟器,需人在命令行启动)能看。已验证:构建成功、28 条判据全绿、
|
||
六种变异都能判红。
|
||
|
||
### 7.16 P4:外观(主题 + 壁纸)跟着**账号**走
|
||
|
||
服务端 2026-09-13 就已经是权威(`server/internal/handler/appearance.go`,账号级),
|
||
WebUI 也接好了;鸿蒙这边此前**完全没有接** —— 主题与壁纸在鸿蒙上是"不存在的东西"。
|
||
|
||
这一期做的事:
|
||
|
||
- `api/AppearanceApi.ets`:`GET/PUT /me/appearance`、`POST/me/appearance/image`、
|
||
`GET /me/appearance/image`(图片**带认证取回本体**,不用 `?token=`)。
|
||
- `model/Appearance.ts`(纯逻辑,判据直接跑):归一化、PUT 报文、**合并决策**、
|
||
模糊值→系统材质映射、主题→系统色彩模式、遮罩浓度、状态文案。
|
||
- `common/AppearanceStore.ets`:落地副作用 —— 主题交给系统(`setColorMode`),
|
||
壁纸取回 `PixelMap`,缓存**按账号**分键。
|
||
- 入口两处:`MainPage`(进主界面就应用,否则"改完主题一进主界面就变回去")与
|
||
`SettingsPage` 的「外观」段(三档主题 + 同步状态)。
|
||
|
||
**两条最贵的规则**(WebUI 侧都踩过,判据盯着):
|
||
|
||
1. **服务端"没有记录"时必须以本地为准**(`saved === false`)—— 服务端这时回的是一份
|
||
*默认值*,拿它覆盖本地 = 把用户已有的主题/壁纸抹掉(WebUI 原话:
|
||
"每个老用户升级后第一次登录都会发现被重置")。正确动作是把本地那份推上去。
|
||
2. **降级必须可见**(`local-only` → 界面显示「仅本机」)—— 否则用户以为换设备也能带走。
|
||
|
||
**判据**(`harmony-appearance.test.mjs`,11 条,已接进 `run-all.mjs`):
|
||
归一化(脏值/越界/小数/NaN);`image` 档无图 → 退回 `none`;PUT 报文字段名(蛇形);
|
||
★服务端无记录 → 以本地为准且**一个字段都不能被顶掉**;服务端有记录 → 以服务端为准但
|
||
**不擦掉**本地那张服务端还没有的图;离线状态可见;★模糊值→系统材质档次(并与 SDK 的
|
||
`BlurStyle` 成员逐一比对);主题→色彩模式(数值与 SDK 的 `ConfigurationConstant.ColorMode`
|
||
逐一比对);★路径必须是**相对基地址**的;缓存键**带账号**;两处入口都真的应用。
|
||
|
||
**变异验证**:服务端无记录时拿默认值覆盖本地 → 红;不管"image 档但服务端没图" → 红;
|
||
材质档次写成自造名字 → 红;路径多写一层 `/api/v1` → 红;缓存键不带账号 → 红;
|
||
深浅色彩模式数值写反 → 红。
|
||
|
||
**判据抓到的真 bug(两处)**:
|
||
|
||
- `snapshotFromResponse` 在"字段缺失"时返回 `bgDim = 0`,而 WebUI 语义是退回 12 ——
|
||
因为 ArkTS 的反序列化把缺失字段留成**类里写的默认值**,"字段不在"与"字段是 0"分不开。
|
||
已把 `AppearanceResponse` 的默认值对齐 WebUI 的 `clamp(..., dflt)` 语义。
|
||
- 主题落地时我按"0=浅色、1=深色"写了 `setColorMode` —— **正好反了**
|
||
(SDK 里 `COLOR_MODE_DARK = 0`、`COLOR_MODE_LIGHT = 1`):选深色会切成浅色。
|
||
发现方式是判据去 SDK 的枚举文件里读数比对,而不是凭印象。映射也因此搬进了纯逻辑
|
||
(`colorModeValue`),从"某处有个 setColorMode 调用"变成"可判据的行为"。
|
||
|
||
**⚠️ 更正一处我说得比证据强的地方**:上一版这里写的是"壁纸在真机上的**渲染**效果未验",
|
||
听着像"已经画出来了、只是没在真机上看过"。实际情况是:**P4 第一版没有任何东西去画它** ——
|
||
`AppearanceStore` 取回了 `PixelMap`、算好了快照,但没有组件把它渲染出来,
|
||
也就是说那一版里"壁纸"只有数据没有画面。取回像素这件事是真的(提交信息没写错),
|
||
但"渲染未验"这个说法把"没做"说成了"没验"。这条更正记在这里,免得后来人以为是回归。
|
||
|
||
### 7.17 P4b:预设档的画法(pi 指出的**信息对等**缺口)
|
||
|
||
pi 的原话:「WebUI 的背景有**预设渐变**,服务端存的是 preset 名 + 参数,
|
||
鸿蒙拿到 preset 名画得出来吗?如果只支持 `image` 与 `none`,那'换账号后外观跟随'
|
||
对预设档就是**不成立**的 —— 用户设了预设,在鸿蒙看到的是没有背景。
|
||
这是一个信息对等缺口,不是入口缺口,而且它比上传入口更容易被忽略。」
|
||
|
||
他说对了,而且当时比这更糟(见上面那条更正)。现在:
|
||
|
||
- `model/Wallpaper.ts`(纯逻辑,判据直接跑):预设清单(id / 中文标签 / 归一化)、
|
||
色板、六个预设各由哪些层叠出来、`resolveBackground()` 决定画什么;
|
||
- 页面用**系统原语**画:`radialGradient` / `linearGradient`;
|
||
**网格档**(CSS 的 `repeating-linear-gradient`)系统没有对应原语 → 用系统 `Canvas` 画线
|
||
(线色/间隔照抄 CSS:gray-200 / 0.55 / 28),理由写在模块里;
|
||
- 图片档:`Image(pixelMap)` + 系统遮罩色按服务端浓度压暗;
|
||
预设档**同样压**(pi 2026-09-14 指出两边不一致:WebUI 的 `--bg-dim` **不区分档位**,
|
||
它的注释写着目的「背景越花,正文越需要一层遮罩才读得动」——遮罩服务的是可读性;
|
||
而这边已经让出页面底,不压的话正文直接压在原色渐变上,比 WebUI 更艳更亮);
|
||
- `image` 档但图没取回来 → **什么都不画**(画一块空白会被当成"壁纸坏了")。
|
||
|
||
**判据**(`harmony-appearance` 11 → 17 条):预设 id/顺序/标签与 WebUI `PRESETS` 逐字一致;
|
||
**每个预设色值与 CSS 调色板变量逐个对照**(这类"看起来差不多"的色值最容易悄悄分叉);
|
||
色板反向检查(登记了没用的 → 红);透明必须用关键字而不是 8 位色值;
|
||
三档的 resolve 行为;页面真的画了(三种原语 + 图片 + 压暗);
|
||
以及**模糊归属的互斥形式**(见下)。
|
||
|
||
**判据抓到的真 bug**:我把 `--c-blue-200`(191 219 254 = `#BFDBFE`)写成了 `#BFDCFE`
|
||
(两位字母顺序反了)—— 这正是"照 CSS 读出来比"才拦得住的一类错。
|
||
另外判据自己也有两处切片毛病(用 `indexOf('build() {')` 两头夹会跨到别的成员上),已改按行截。
|
||
|
||
### 7.17a 本节代码的提交归属(免得后来人查不到)
|
||
|
||
本节的代码(`model/Wallpaper.ts`、`MainPage` 的壁纸层与页面底让出、判据)**落在这几个提交里**:
|
||
|
||
- `c9717da`、`1717863` —— 这两个的提交信息写的是 **WebUI** 的事("联系人项玻璃卡"、
|
||
"手势与横向滚动分家"):并发写入者与我共用同一个 git 身份,它 `git add -A` 时
|
||
把我尚未提交的工作区一起提交了。**代码是好的,归属是错的。**
|
||
- `962df62` —— 提交信息里是本节完整的"改了什么/为什么/怎么验",但 diff 只有判据文件那一处。
|
||
|
||
所以:`git log -1 -- client/harmony/entry/src/main/ets/model/Wallpaper.ts` 会指向一个
|
||
WebUI 提交,顺着本节就能找回来。**给并发写入者的提醒**:`git add -A` 会把别人的半成品
|
||
一起提交(提交信息与他人工作不符,且一旦对方还要改就只能再补一次提交)。按路径 add 更稳。
|
||
|
||
### 7.18 pi 撤回的那条口径:模糊归属改成**互斥形式**
|
||
|
||
pi 撤回了他原来那句"模糊只由壁纸层负责",并说明了它的来源:那是 **WebUI 的架构结论**
|
||
——它的壁纸图层自带 `filter: blur()`,浮在它上面的面再 `backdrop-filter` 就是把同一张
|
||
糊过的底**糊第二遍**(更脏、更掉帧),所以那条规则在 WebUI 侧是空的;
|
||
而同一条 CSS 里它的**底部导航 `.narrow-nav` 是有 `backdrop-filter` 的**,
|
||
因为那一条背后是**会滚动的内容**,模糊在那里有遮蔽意义。
|
||
|
||
所以正确的形式是两条性质,而不是"归谁":
|
||
|
||
1. **同一张底只许被模糊一次**;
|
||
2. **模糊应出现在"背后是可变内容"的层**。
|
||
|
||
套到鸿蒙:壁纸是整幅图、栏不吃壁纸,用户的模糊偏好被映射成**材质档位**,
|
||
于是栏上的系统材质就是唯一一次模糊,壁纸层不再糊 —— 满足"只一次",也更符合"用系统方案"。
|
||
判据按互斥形式写:**壁纸层不许出现任何模糊/材质**,**导航条必须有系统材质**;
|
||
将来 P5 真做悬浮玻璃条(浮在滚动内容上)时,按"背后是可变内容"这条放行第二处,
|
||
并在 `GLASS_REGISTRY` 里登记 + 说明它背后确实是滚动内容(不是又一层壁纸)。
|
||
|
||
**语义转换要记清**(pi 要求写进文档,否则以后有人拿"`bg_blur=8px` 与档位对不上"当 bug 报):
|
||
WebUI 的"壁纸模糊度(px)"在鸿蒙变成了"**材质档次**"——**不是同一个物理量**;
|
||
前者是给 CSS 图层用的半径,后者是系统材质的档位(Thin/Regular/Thick)。
|
||
映射在 `model/Appearance.ts` 的 `blurStyleFor()`,判据与 SDK 的 `BlurStyle` 成员比对。
|
||
|
||
### 7.19a 判据规范:`client/electron/test/CRITERIA.md`
|
||
|
||
pi 指出"邻接不是结构"这条已经在同一个仓库露头**三次**(① 窗口式正则被一行注释挤爆;
|
||
② 括号配对修掉它;③ 链式修饰符让"看前一个字符"静默失效),共同形式是
|
||
**判结构要配对/解析,看邻接或固定宽度都会被合法写法绕过** —— 比记具体招式有用,
|
||
所以升格成仓库级的判据规范(`client/electron/test/CRITERIA.md`),并在 `run-all.mjs` 里加了
|
||
**自检 3**:规范文件必须在、且必须点到那几条关键规则(规则只活在脑子里等于没有)。
|
||
|
||
规范里现在有七条:配对/解析而非邻接(含 WebUI 侧 `background.test.mjs` 的存量,
|
||
**那是 pi 的地盘,记着不动**)、生成清单 + 结构化 allow-list、变异验证(含"变异没生效"
|
||
这个反向陷阱)、剥注释读代码 vs 读原文读理由、按行取片段、判据要接线 + 扫描范围要有下限、
|
||
判据要点用户真正会点的那一层。另外补了一条本仓刚踩的:**验证要按真实入口跑**
|
||
(`node --test test/run-all.mjs` 会把 runner 内部的 `process.exit(1)` 吞掉)。
|
||
|
||
### 7.19 两条跨端约定(pi 2026-09-14 复核后确认)
|
||
|
||
- **`Theme.` 的成员名不改**(pi 三条理由:判据钉的是"值来自系统 + 品牌色仍手写",
|
||
改名零收益;名字一致本身就是这个跨端词表的价值,`Theme.surface` ↔ WebUI `surface`
|
||
是同一件事;149 处搬运的风险与收益不成比例)。**边界**:将来某处真需要"系统里更具体的面"
|
||
(例如 `ohos_id_color_dialog_bg`),**那时新增一个成员**,而不是把已有名字改一遍。
|
||
- **可选数值字段的"缺省值"本身就是契约的一部分**:WebUI 用 `dflt`、鸿蒙用类字段默认值 ——
|
||
两处默认值不一致就会**静默分叉**(P4 里真踩过:`bg_dim` 缺失时一边给 12、一边给 0)。
|
||
跨端对齐可选数值字段时,先对齐默认值,再对齐取值。
|
||
- **页签键/顺序**:`CommTab`/`TABS` 与 `WorkCard` 字段集是同一类跨端耦合 —— pi 改 `uiStore.ts`
|
||
或 `CommTabs.tsx` 前会先看鸿蒙这条判据,两边同时改;**不让我单方面红**。
|
||
|
||
**未做(P4c)**:壁纸**上传**入口(选图 → `POST /me/appearance/image`)。pi 定为 P4c(算 P4 范围,
|
||
不阻塞别的阶段),照 WebUI 踩过的三条做:**先压缩再上传**(手机直出照片 4–8MB)、
|
||
**失败必须给原因**(别静默失败)、**上传成功后仍以服务端为权威**(`saved` 那套规则对图片同样适用)。
|
||
|
||
**未验**:预设渐变与图片壁纸在真机上的实际观感(配色是否好看、对比够不够)。
|
||
|
||
### 7.17b 预设色板**两套**:这不是"观感未验",是机制上确定不同(pi 指出)
|
||
|
||
我上一版把"深色档预设"记成"观感未验",pi 读完 `Wallpaper.ts` 后指出这是**判错的类别**:
|
||
那边的预设写的是 `rgb(var(--c-blue-100))`,而 `--c-*` 在 `.dark` 里**整体换了一套**
|
||
(blue-100 → `30 43 67`)—— WebUI 的预设**自动随主题变**;这边若只有浅色那套,
|
||
深色主题下就是"浅色渐变垫在深色系统表面之下",正是这一整轮在治的那个病。
|
||
**它不需要真机就能判:机制写在代码里。**
|
||
|
||
处理(选 pi 倾向的那条:跟随主题,与 WebUI 一致):
|
||
|
||
- 色板做成**两套**(`LIGHT_*` 取 CSS `:root`、`DARK_*` 取 CSS `.dark`),
|
||
`paletteFor(dark)` 选一套,`layersFor(id, dark)` 按主题出层;
|
||
- 深浅色的判定放在纯逻辑 `isDarkMode(theme, systemColorMode)`:用户选了 dark/light 就照办,
|
||
`system` 时看系统当时反馈的 `colorMode`(数值锚到 SDK:`COLOR_MODE_DARK = 0`、
|
||
`COLOR_MODE_LIGHT = 1`;读不到/未设置按**浅色**,与 WebUI 的 `:root` 默认一致);
|
||
- 系统当时的深浅从 `resourceManager.getConfigurationSync().colorMode` 读
|
||
(`Context` 基类没有 `config`,`UIAbilityContext.config` 要转型;两个枚举取值一致,都核过 SDK);
|
||
- 判据:两套值与 `index.css` 的 `:root` / `.dark` 段**逐个相等**;每个预设的深浅两套
|
||
**必须真的不同**(否则"两套"是抄了两遍);网格线色也要跟着换;`isDarkMode` 五种输入。
|
||
|
||
**运行期切深浅色(原记"未做",现按 pi 的三档口径改)**:这**不是"没做"** ——
|
||
色板确实按主题算了,只是当时没接环境变化事件,于是运行期切系统深浅色会出现
|
||
"系统语义色/材质立刻跟随、我们自己算的色板不重算"的**撕裂**(这正是**已知不一致**那一档:
|
||
触发条件 = 运行期切系统深浅色;现象 = 一部分跟随、一部分不跟随)。
|
||
|
||
现在按 pi 的规则接上了:`applicationContext.on('environment')` 的 `onConfigurationUpdated`
|
||
里,**只在 `colorMode` 真的换向时**重算我们自己的那部分(`applyAppearance()`),
|
||
页面销毁时 `off` 退订。**状态 = 未验**(这条只有真机/模拟器能验:切一次系统深浅色,
|
||
看预设是否跟着换)。
|
||
|
||
SDK 锚点(免得后人凭记忆写):回调接口是 `@ohos.app.ability.EnvironmentCallback`
|
||
的 `onConfigurationUpdated(config: Configuration): void` 与 `onMemoryLevel(level)`;
|
||
`Configuration` 类型从 `@kit.AbilityKit` 取(不在 `common` 命名空间下)。
|
||
踩过两个编译错:`Configuration.colorMode` 是**枚举 | undefined**,字段写成 `number` 直接报错;
|
||
`ApplicationContext.on('environment')` 返回的是 **number 型 callbackId**(不是 void)。
|
||
|
||
### 7.17c 手写色清册升级成**跨文件按类扫**(pi 建议)
|
||
|
||
原来的 A2 只保护 `Theme.ets`;而 `Wallpaper.ts` 也有手写色(14 个预设色)。
|
||
那边靠"从 CSS 读出来逐个对照"抓到了 `#BFDCFE`,手法是对的 ——
|
||
但若对照是**按名字枚举**的,第 15 个色就会逃掉:这正是 A2 要防的同一件事,只是换了个文件。
|
||
|
||
现在并成**一份清册、按类扫**:全 `ets/` 树里每个 `X: string = '#RRGGBB'` 都必须在清册里 ——
|
||
要么是 `common/Theme.ets` 的品牌/业务语义色,要么是 `model/Wallpaper.ts` 的预设色板
|
||
(后者的值由 CSS 两段比对负责,且常量名必须带 `LIGHT_`/`DARK_` 前缀,判据才知道跟哪一段比)。
|
||
反向也判:清册里登记了却不存在 → 清册过期。
|
||
变异:`Theme.ets` 加一个未登记色 → 红;`Wallpaper.ts` 加一个未登记色 → 红;
|
||
加一个"看起来合规"的 `DARK_EXTRA` → 红(不在清册里就是不在)。
|
||
|
||
### 7.17d 并发写入的两个坑:`TMPDIR` 互踩、提交归属(pi 提出)
|
||
|
||
- **`TMPDIR` 互踩**:这个 worktree 可能同时有多个 agent 跑构建,而 fpm 会把
|
||
`release/linux-unpacked`(约 291MB)**整份复制**进 `TMPDIR` —— 两边落到同一个临时目录
|
||
就是随机的产物损坏/构建失败,比"归属错"难查得多。`whoami` **区分不开**(这里大家都是 root),
|
||
所以按**会话**分家:`TMPDIR=$PWD/.tmp/${DSH_SESSION_ID:-$(id -un)-$$}`
|
||
(已进 `npm run build:linux` 与 BUILD.md 的手敲命令)。
|
||
- **提交归属可判**:同时改 `client/harmony/` 与 `client/electron/` 的提交**必须在 subject 里
|
||
自报家门**(`跨端:`)—— 那种提交是合法的("跨端判据要两侧一起改"),只是要标出来;
|
||
不标的多半是被 `git add -A` 卷进去的。判据在 `test/commit-hygiene.test.mjs`:
|
||
基线 = 该判据文件自己的引入提交(**历史不改**,规则管从今往后),
|
||
配分类逻辑的合成自检(否则"解析没跑起来"时会全绿)。
|