**授权请求不是「一封信」而是「一件待办」。** 生产取证:一个 pi 会话独占 17 封权限邮件(后涨到 39),另外两个会话各 3 封 / 1 封 —— 收件箱被一件事塞满,失去了它唯一的作用(让人知道有哪几件 事在等我)。 第一版做成会话折叠,用户纠正后重做:权限请求的生命周期是「等人点头 → 决策完就作废」,与普通邮件混在一个箱子里两者互相伤害。收件箱只放要读的, 授权项只放要批的。 `groupPermissions` 的排序判据是「要不要我动手」而非时间:三天前发起、 至今还卡着的授权比十分钟前刚批完的重要得多。纯按时间排会把它压到底部, 而 Agent 那条会话正在等 —— 那正是权限死锁在 UI 上的样子。 未读徽标用红色、待决策用橙色,且两个数字**互不重复计数**:未读是 「有内容没看」,待决策是「有 Agent 卡着等我」。 --- **回复发给自己的 bug(用户报)。** 根因:ReplyBar 的对端判定写死 `from_name === 'human'` —— 单用户时代遗留 (当时人类只有 human@ 一个身份)。登录名是 jianf 时判据恒为假, 于是取 from_name(自己)。 生产链条:`27c22900 jianf→pi` 对(锚点是 pi 发来的), `8e519925 jianf→jianf` 错(锚点是自己发的)。 两处修正: 1. **判据必须是当前登录用户名**,不是字面量 'human'。同一遗留判据在三处: 对端解析 / replyAll 去自己 / ThreadCard 图标。 2. **会话视图的回复对端是会话的属性,不看任何单封邮件。** 人在那儿打字就是「给这次任务的对方追加一句」;用「最后一封」当锚点时, 自己刚发过信就会把自己算成对端。sessionCounterpart 扫全会话取首个 非我参与方(收件人优先于发件人,同刻用 mail_id 定序)。 ThreadCard 顺带显示真实发件人名而不是统一渲染成 'human' —— 会话里可能有 多个人类参与方。 测试:mailGroups 30 例(含「生产实测形状:17 封权限邮件」)、 replyTarget 24 例(含生产链条重现:断言 target 以 pi@ 开头而非 jianf@)。 手工验收脚本 inbox-group-verify.mjs 六项全过。
手工浏览器实测脚本
不进 npm test —— 它们需要一个跑着的 Chromium 与一个活的 Gateway。
日常回归靠 ../narrow-layout.test.mjs(读源码验形态,无外部依赖)。
为什么两套都要
结构性断言守住「代码写成了什么形态」,量不出「按钮实际多大、点下去命中谁」。
窄屏那轮修复里最严重的一个 bug 是抽屉式侧栏(fixed ... z-50 铺满视口高度)
把底部导航最左那一项盖住 —— 按钮在那里、尺寸也够、md:hidden 之类的规则也
没写错,只有 elementFromPoint 才能发现它命中的是抽屉里的 SVG。
用法
# 窄屏:390px(iPhone 14 Pro)+ 320px(iPhone SE)
ADMIN_PW=<密码> npm run test:narrow
# 宽屏回归:窄屏修复不能把桌面改坏
ADMIN_PW=<密码> npm run test:wide
环境变量:
| 变量 | 默认 | 说明 |
|---|---|---|
ADMIN_PW |
无(必填) | 管理员密码 |
ADMIN_USER |
admin |
登录用户名 |
AGENTMAIL_URL |
https://mail.jianfgit.xyz |
目标地址 |
CDP_URL |
http://127.0.0.1:9222 |
浏览器 CDP 端点 |
PLAYWRIGHT |
/usr/lib/node_modules/playwright/index.mjs |
playwright 入口 |
浏览器用的是本机 systemd 托管的共享 Chromium(homeagent-browser.service),
通过 CDP 连上去开自己的标签页,用完关掉。没有它时先
systemctl start homeagent-browser。
文件
| 文件 | 作用 |
|---|---|
narrow-probe-helper.mjs |
连浏览器、登录、量盒子/溢出/命中区/命中测试 |
narrow-verify.mjs |
窄屏 13 项验收 |
wide-regression.mjs |
宽屏 5 项回归 |
narrow-probe-helper.mjs 里两个函数值得单独知道:
tapTargets(page, labels)—— 量.tap按钮的真实命中区(::after伪元素的尺寸)。.tap刻意不改变视觉尺寸,所以只看boundingBox会误判成偏小hitTest(page, selector)—— 每个元素点下去是否命中自己。遮挡类 bug 只能这样查
已知限制
headless Chromium 报告 hover: none,因此 .reveal(只在支持悬停的设备上隐藏)
在这里永远是可见的 —— 脚本只能验「触摸设备上可见」这一半,
「鼠标设备上隐藏」那一半靠 ../narrow-layout.test.mjs 检查 CSS 规则存在。
没有像素级视觉比对:字体差异下极脆,维护成本高于收益。