diff --git a/client/electron/BUILD.md b/client/electron/BUILD.md index e828761..b03894f 100644 --- a/client/electron/BUILD.md +++ b/client/electron/BUILD.md @@ -141,6 +141,10 @@ AGENTMAIL_USER_KEY=<用户密钥> ./agentmail-web ### deb 打不出来时:先看 `/tmp` 有没有空间(2026-09-14 实测) +> 顺带一条已经**固化进脚本**的前置:本机 `/tmp` 是 tmpfs(占内存)且常年接近满, +> `npm run build:linux` 现在自带 `TMPDIR=${TMPDIR:-$PWD/.tmp}`(工作区所在大盘)。 +> 手敲 `electron-builder` 时请照着加 —— "记得加 TMPDIR"这种约定活不过两次踩坑(hvigor 与 fpm 各踩过一次)。 + 本机曾以为"deb 打不出来是 fpm 的毛病"(报错停在 `fpm process failed 1`,栈顶是 portable ruby 的 `Dir.chdir`,看着像路径权限)。**实际是 ENOSPC** —— fpm 会把 `release/linux-unpacked`(约 291MB)**整份复制**到 `TMPDIR` 里再打包,而本机 diff --git a/client/electron/package.json b/client/electron/package.json index 9b59132..8dcbd5b 100644 --- a/client/electron/package.json +++ b/client/electron/package.json @@ -16,7 +16,7 @@ "dev:electron": "ELECTRON_START_URL=http://localhost:5173 electron electron/main.cjs", "build": "npm run gen:bg && vite build", "build:win": "vite build && electron-builder --win", - "build:linux": "vite build && electron-builder --linux", + "build:linux": "mkdir -p .tmp && vite build && TMPDIR=${TMPDIR:-$PWD/.tmp} electron-builder --linux", "preview": "vite preview", "typecheck": "tsc --noEmit", "test": "node test/run-all.mjs && vitest run", diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index 1fd8a71..b712422 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -18,7 +18,7 @@ * 在它进套件之前一直是隐身状态。加了这条,**新增判据忘了接线会直接红**。 */ import { spawnSync } from 'node:child_process'; -import { existsSync, readdirSync } from 'node:fs'; +import { existsSync, readFileSync, readdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -59,6 +59,31 @@ if (ghosts.length || unwired.length) { process.exit(1); } +/* + * 自检 3:判据不得写在 `process.exit()` **之后**(pi 提议,2026-09-14)。 + * + * 自检 1/2 管的是"文件没接线",管不到"检查写在了退出之后" —— 而那正是实际发生过的 + * 第 4 例:4 条玻璃判据被并发写入落到了文件末尾、`process.exit()` 后面, + * 于是**一条都不执行、也不计入通过/失败**,输出看起来完全正常。 + * 这种事的成因是结构性的(并发写入总是往文件末尾追加),所以它一定会再发生, + * 而它下一次仍然不报错 —— 静态扫一遍最省事。 + */ +const buried = []; +for (const [file, flags] of SUITE) { + if (flags.includes('--test')) { + continue; // node:test 那几条没有 process.exit,结构上不会踩这个 + } + const src = readFileSync(join(ROOT, file), 'utf8'); + const exitAt = src.lastIndexOf('process.exit('); + if (exitAt >= 0 && /(^|\n)\s*check\(/.test(src.slice(exitAt))) { + buried.push(file); + } +} +if (buried.length) { + console.error(`判据写在 process.exit() 之后,永远不会跑(挪到汇总之前):${buried.join('、')}`); + process.exit(1); +} + const reds = []; for (const [file, flags] of SUITE) { console.log(`\n========== ${file} ==========`); diff --git a/docs/API.md b/docs/API.md index f499b30..acf7140 100644 --- a/docs/API.md +++ b/docs/API.md @@ -64,6 +64,23 @@ curl {host}/api/v1/me/mail/inbox -H "Authorization: Bearer $TOKEN" ## 三、人类接口 +> **先读这一条:`total` 不是"总封数"。** +> `GET /me/mail/inbox` 的响应是 `{"mails": [...], "total": N}`,而那个 `N` 是 +> **未读总数**(服务端 `repo.CountUnread`,与 `?status=` 过滤无关),**不是**本页/全部邮件数。 +> 它叫 `total` 是历史命名所致。后果很具体:鸿蒙端底部曾写「共 N 封」, +> 于是同一屏上出现「共 7 封」和「未读 7」两行自相矛盾的字 +> (2026-09-14 修;WebUI 侧不读这个字段,故未受影响)。 +> 客户端**没有任何可信的"总封数"**可用 —— 想要"还有更多吗"只能看这一页是否取满 +> (`mails.length === limit`),不能把 `limit` 封说成全部。 + +### 同名不同义 / 同义不同名(改代码前先看这张表) + +| 名字 | 在一处的意思 | 在另一处的意思 | +|---|---|---| +| `total` | `/me/mail/inbox`:**未读总数** | 别处(如 `/me/mail/sent` 等)才是"条数",同名不同义,别看名字取值 | +| `status` | 邮件上:`unread` / `read` | 会话上:`active` / `archived`(两套取值域,共用字段名) | +| `status` + `is_read` | 邮件上这两个字段说的是同一件事(同义不同名) | 判断已读时别只看一个,旧数据可能只有一个被写对 | + ### 邮件 ```