|
|
549836193d
|
跨端: fix(推送客户端) 热启点通知**不跳转** —— 真机实测逼出来的那半(只写格子没人读)
这是"收尾"里**静态判据看不出来**的那一类,只有设备能抓住。
## 实测经过(模拟器,可复核)
只写 `PendingRoute` 的那版(上一个提交 423ff9f):
aa force-stop → aa start --ps data '{…open_mail…}' ⇒ render MailDetailDestination ✓ 冷启能跳
(应用已在运行)aa start --ps data '{…open_mail…}' ⇒ **0 次** ✗ 热启不跳
根因:冷启时页面**刚挂载**,`aboutToAppear` 会读那个静态格子;
热启时页面**早就挂载完**了、`aboutToAppear` 不会重跑 ⇒ 格子写得进去、**没人读**。
症状正是"点了通知,App 弹到前台,停在列表页" —— 而这恰恰是推送**最常见**的用法
(App 在后台,用户点通知回来看那封信)。
## 修法:补"事件发生时就交出去"的那条路
`PushService.deliverRoute(route)`:**有人监听就当场交出去,没人监听才留在格子里**。
两条路都要留着,因为两种启动各走一条(冷启没人监听、热启有人监听):
- `EntryAbility.onCreate` / `onNewWant` 都走 `deliverRoute`(两条启动方式共用一条投递路径);
- `CommPage.aboutToAppear` 注册监听、`aboutToDisappear` 摘除
(不摘会叫醒已销毁的页面);
- 冷启与热启**共用同一个落点** `navigateToRoute`(各写一遍必然漏改一处)。
不用页面生命周期兜(`onPageShow` 之类):那会在"用户手动返回列表"时反复触发跳转,
而这里要的是"事件发生的那一刻"。
## 验证
真机(模拟器,装新包后实测):
- 热启 ⇒ `render current custom node: MailDetailDestination` ✓(修之前 0 次)
- 冷启回归 ⇒ 仍然 1 次 ✓(没被这次改动破坏)
- 截图硬证:详情页(返回箭头 + 详情窗格);"加载失败"是我塞的假 mail_id
(`WARM456`)的**正确**后果 —— 说明确实带着那个 id 去取了
判据:`harmony-push.test.mjs` 18 → 19 条,新增「接线④:热启要能跳」,
**变异验证过两个方向**(deliverRoute 退回"只写格子" ⇒ 红;删掉监听器注册 ⇒ 红)。
它单独存在的理由:接线③那种"有人读"的检查**会放它过去** ——
`aboutToAppear` 里读 pendingRoute 完全满足③,而热启路径是死的。
|
2026-09-17 19:08:27 +08:00 |
|
|
|
423ff9fbb2
|
跨端: feat(推送客户端) 收尾两处接线(pi 邮件 66bbd929 分工)+ 换账号补报
`PushService.ets` 与 `pendingRoute` 都早就写好了,但**没人调用/没人读** ——
而"写好了"与"接上了"是两件事:ArkTS 只编译可达模块,往一个无人 import 的
文件里放必然报错的类型错误,`assembleHap` 照样 BUILD SUCCESSFUL
(pi 与我各复现过一个方向)。这次补的是**边**,不是点。
① 登录成功补报(LoginPage):`EntryAbility.onCreate` 那次在**登录之前**跑,
那时 `ApiClient` 还没有 token ⇒ 服务端 401,而 `reportToken` 的失败是静默的
⇒ "启动时报过"不等于"这个账号登记过"。补三条登录路径,一条都不能漏:
快速路径(已有账号直接进主界面,老用户走这条)/ tryRestore / doLogin。
② 换账号补报(SettingsPage.switchTo,**我加的第三处**):服务端语义是同一
token 换账号**转移**而非并存 ⇒ 新账号其实没登记过。幂等标记按
`accountKey|token` 存,所以这里调一次必然重发,不会被"我报过"挡掉。
不补的症状:切完账号,通知仍推给上一个账号。
③ pendingRoute 消费端(MainPage.CommPage):`EntryAbility` 已把通知 data
解析成 `pendingRoute`(冷启 onCreate / 热启 onNewWant 都写了),
**但没有任何东西读它** ⇒ 点通知只拉起 App、停在列表页。
在 CommPage 消费(详情页是这个 Navigation 的 navPathStack 上的路由,
栈只有它持有),消费后**立刻清空**(它是"待处理"不是"当前页",
不清则返回列表再进来会被反复跳走)。
判据:`harmony-push.test.mjs` 15 → 18 条,三条新判据钉的就是上面这三条边,
**三条都做了变异验证**(各自删掉对应接线即红、恢复即绿)。
`LoginPage` 那条数**恰好 3 处**调用:少了快速路径=老用户永远不补报,
多了要问清是哪条路径。
编译验证(不是"命令成功",是"它真读了这个文件"):三处改动各植入一次必然
类型错误 ⇒ BUILD FAILED 且 `At File:` 指到**新代码那一行**(MainPage:1173 /
LoginPage:108 / SettingsPage:456),恢复后 BUILD SUCCESSFUL。
产物侧证:`modules.abc` 里能查到 `consumePendingRoute`(2) / `reportPushToken`(2)。
|
2026-09-17 19:03:14 +08:00 |
|
|
|
ff75339baa
|
fix(harmony): 图标按 px→vp 正确缩放 + 底栏四入口 + 卡片 10vp 缩进
图标过小的根因(实图硬证):`Path.commands` 的坐标按 **px** 解释,不是 vp。
- `Shape.viewPort` 只裁剪不缩放(官方样例"缩放成立"是因为它用 Rect/Circle
自带 vp 宽高,不是裸 Path.commands);
- 改动前的 `Path.scale({iconSize/24})` 对 iconSize=24 等于 1.0,同样没缩放。
症状:FAB 内 compose 只画 24px(应 24vp=84px),且偏在盒子左上角。
修法:`scale({vp2px(iconSize)/24})` 把 24 单位路径放大到 iconSize vp;
`scale` 会连带放大描边,故 `strokeWidth = strokeWeight / vp2px(1)` 抵消,
否则 1.8vp 描边会变成 ~7.3vp 的"实心块"(实图已验证)。
用 `this.getUIContext().vp2px()` 而非全局 `vp2px`——后者已废弃,
`harmony-system-api` 判据对全局调用默认判红。
验收(模拟器实图,密度 3.489):
- FAB 内 compose 图标 84px = 24vp ✓(原 24px)
- 底栏图标描边 7-8px ≈ 1.8/24×28vp=7.3px ✓(原为实心块)
- 四个图标均为清晰描边,与 WebUI icons.tsx 同几何
底栏四入口:新增"我的"(person → pages/SettingsPage),对齐 WebUI
通信/日历/联系人/我的;NAV_CONTENT_ITEMS 保持宽屏侧栏三项,
避免宽屏出现重复 person 图标。
卡片缩进:`width('100%') + margin({left:10,right:10})` 在 ArkUI 里不会
缩到 90%——margin 加在宽度外面,卡片顶出父容器(实测左缘只留 ~1px,
列表卡才有正确 10vp)。改为外层容器加 padding。验收:页头卡与邮件卡
左缘同为 x=35(10.0vp)、右缘 x=1219。
推送:reportToken 先申请通知权限再取 token(部分设备权限未开时
getToken 返回空 / 报 1600004),并补两条文件形状判据钉住
client_id 配置与调用顺序。
测试:198 passed / 0 failed。
|
2026-09-17 16:27:27 +08:00 |
|
|
|
ab31690348
|
跨端: fix(推送客户端): 登记标记绑定账号(token 换账号是"转移"不是并存 ⇒ 别把登记状态缓存成长期结论)
pi 邮件 `2518e1a3` 的服务端事实:注销只认注册者本人、token 字符串不是凭证,
**同一个 token 换账号登录是"转移"而不是并存** ⇒ "我登记过没有"的答案**随账号而变**。
原来的 `shouldReportToken(lastReported, current)` 只按 token 存标记 ⇒ 换账号后
(同一 token)会**错误地跳过上报**,而那个账号其实还没登记过这个 token。
改成 `reportMarker(accountKey, token) = accountKey|token` + `shouldReportToken(lastMarker, accountKey, token)`:
**账号变了标记必然不同** —— 于是"换账号必然重新上报"是**性质**,不靠人记得 reset。
判据 13 条全绿(新增"换账号后必然重新上报 / 切回来也要重新上报",并断言两次 marker 不相等)。
|
2026-09-15 11:51:39 +08:00 |
|
|
|
30886271ea
|
跨端: docs(引用锚): 把日期锚换成邮件 ID(pi 邮件 b1e23663:日期也是锚,写错一天下一个人找不到那封信)
pi 指出的锚错:`PushContract.ts` 里那段线上形状是 pi **09-15** 发的(邮件 `004983bb`),我写成了 09-14。
照他的推理("引用别人的工作状态时,哈希和'谁在读'一样会漂")往下再走一步:
**日期本身就是会漂的锚 —— 邮件 ID 不会。** 所以不是把 09-14 改成 09-15 就完事,
而是把这一族引用改成**可检索的标识**:
- `PushContract.ts`:契约来源 → `1f9ff3b4`;线上形状 → `004983bb`;
- `harmony-push.test.mjs`:同上两处;
- `Calendar.ts`:表头共用分叉、"今天"的调用侧 → `f60521de`;
- `ALIGN-REFS.json`:参照物版本登记 → `6e14b410`;圆角策略追认 → `90372ca1`;AGC 包名实测 → `1f9ff3b4`。
共 10 处。判据不依赖这些注释文本,改完 `harmony-push` 12/12、`harmony-calendar` 10/10、`align-refs` 3/3 仍绿;
`ALIGN-REFS.json` 仍是合法 JSON。
**没改的 4 处**(`NavItems.ts` 1 处、`Wallpaper.ts` 3 处):它们写于 09-14 那批往来,日期本身没被指出错,
而那几封的邮件 ID 我这边没有(跨过一次上下文压缩)——**宁可留着有争议的日期,也不编一个 ID**。
|
2026-09-15 11:51:39 +08:00 |
|
|
|
ed0ad2508e
|
跨端: feat(推送客户端): 按线上形状补契约层(DELETE 也带 body / 未知字段 400 / provider 无白名单 / 错误是 {"error"})
pi 2026-09-14 给的线上形状(从 handler/push.go 读的,不是猜的)逐条落成可判的:
- **请求体只放已知键**:`PUSH_BODY_KEYS` 登记四个键,**未知字段服务端直接 400**(不是静默忽略)
⇒ 拼错会立刻可见;可选字段为空就不放(空串虽合法,但不放更不容易踩校验)。
- **provider 只做形状校验、没有白名单**(`^[a-z0-9_-]{1,32}$`):判据断言 `apns`/`fcm` 也合法 ——
客户端**不许**硬编码"只有 hms 合法"去先拦一道(服务端没实现的通道不该变成客户端的 400)。
这正是"校验的范围必须等于它真正知道的事"的又一落点。
- **token**:空非法、512 上限;形状不合法 ⇒ `buildTokenBody` 返回 undefined,调用侧**静默跳过**,
不去打一次注定 400 的请求。
- **错误体是 `{"error"}` 不是 `{"message"}`**:判据专门断言 `{"message":"x"}` 解析出 undefined
(用错键会把"没有消息"当有消息)。
- **注销:`deleted:false`(本来没登记)不是失败** ⇒ 它**不改变分类**,所以**不再是入参**
(一个不影响结果的入参只会让人误以为它影响结果),判据断 `classifyUnregister.length === 2` 钉住这点。
- **`ApiClient.del` 补可选 JSON body**:DELETE 端点是 JSON body 形状(不是 query、不是 path 参数),
原来只有 `path` ⇒ 注销会无效。不传 body 时行为与旧版完全一致(向后兼容)。
EOF
|
2026-09-15 11:43:22 +08:00 |
|
|
|
8fed8401de
|
跨端: feat(推送客户端): 契约层 model/PushContract.ts + 6 条设备无关判据(静默失败/enabled:false 正常态/按 provider+tail 比/按 mail_id 去重)
pi 2026-09-14 推送契约的客户端半边。四条不变量里**三条是纯逻辑**,所以先落这三条,
平台调用(Push Kit 取 token、通知权限)留在下一步的 `PushService.ets` 里。
- `shouldReportToken`:取不到 token 不上报;与上次相同不重复上报(否则每次启动打一次接口);
- `tokenTail` / `isRegistered`:GET **只回尾 6 位** ⇒ "登记过没有"必须按 `provider + tail` 比。
比全文是**看起来更严、其实永远为假**的写法(全文永不等于尾 6 位 ⇒ 每次启动重复上报),
所以有判据钉它,并在注释里写明"同尾 6 位即视为同一条 —— 这是服务端给的信息量的上界,不是我们的选择";
- `classifyRegister`:`ok-enabled` / `ok-disabled` / `silent-skip` —— **三种里没有一种是"提示失败"**
(`enabled:false` 是自部署常态 ⇒ 不重试、不提示);
- `parseNotificationData`:不满足约定形状就返回 undefined(坏 JSON 不抛、缺 mail_id/动作不符都忽略)——
推送是可选通道,收到不认识的东西不许有任何副作用;
- `NotificationLedger`:服务端**无幂等键**(至多一次、无重试/去重表)⇒ 重复保护落客户端;台账**有界**。
另有一条判据禁止契约层引入 `@ohos`/`@kit`(否则这些判据会退化成必须上设备)。
它第一次跑**咬到了解释这条规则的那行注释** ⇒ 改扫 `code()`(去注释),与前面扫描口径那次同族。
`npm test`(install 相位)全绿;余额 `debts=13`。
|
2026-09-15 11:40:54 +08:00 |
|